feat: Add buildinfo package to embed version (#840)

This also resolves build time and commit hash using the
Go 1.18 debug/buildinfo package. An external URL is outputted
on running version as well to easily route the caller to a
release or commit.
This commit is contained in:
Kyle Carberry
2022-04-04 20:35:03 -05:00
committed by GitHub
parent d4e26ff8c2
commit 5ae71f0958
6 changed files with 137 additions and 3 deletions
+5 -2
View File
@@ -16,7 +16,7 @@ before:
builds:
- id: coder-slim
dir: cmd/coder
ldflags: ["-s -w"]
ldflags: ["-s -w -X github.com/coder/coder/cli/buildinfo.tag={{ .Version }}"]
env: [CGO_ENABLED=0]
goos: [darwin, linux, windows]
goarch: [amd64]
@@ -28,7 +28,7 @@ builds:
- id: coder
dir: cmd/coder
flags: [-tags=embed]
ldflags: ["-s -w"]
ldflags: ["-s -w -X github.com/coder/coder/cli/buildinfo.tag={{ .Version }}"]
env: [CGO_ENABLED=0]
goos: [darwin, linux, windows]
goarch: [amd64, arm64]
@@ -58,3 +58,6 @@ nfpms:
release:
ids: [coder, packages]
snapshot:
name_template: '{{ .Version }}-devel+{{ .ShortCommit }}'
+1
View File
@@ -5,6 +5,7 @@
"coderd",
"coderdtest",
"codersdk",
"devel",
"drpc",
"drpcconn",
"drpcmux",
+83
View File
@@ -0,0 +1,83 @@
package buildinfo
import (
"path"
"runtime/debug"
"sync"
"time"
"golang.org/x/mod/semver"
)
var (
buildInfo *debug.BuildInfo
buildInfoValid bool
readBuildInfo sync.Once
// Injected with ldflags at build!
tag string
)
// Version returns the semantic version of the build.
// Use golang.org/x/mod/semver to compare versions.
func Version() string {
revision, valid := revision()
if valid {
revision = "+" + revision[:7]
}
if tag == "" {
return "v0.0.0-devel" + revision
}
if semver.Build(tag) == "" {
tag += revision
}
return "v" + tag
}
// ExternalURL returns a URL referencing the current Coder version.
// For production builds, this will link directly to a release.
// For development builds, this will link to a commit.
func ExternalURL() string {
repo := "https://github.com/coder/coder"
revision, valid := revision()
if !valid {
return repo
}
return path.Join(repo, "commit", revision)
}
// Time returns when the Git revision was published.
func Time() (time.Time, bool) {
value, valid := find("vcs.time")
if !valid {
return time.Time{}, false
}
parsed, err := time.Parse(time.RFC3339, value)
if err != nil {
panic("couldn't parse time: " + err.Error())
}
return parsed, true
}
// revision returns the Git hash of the build.
func revision() (string, bool) {
return find("vcs.revision")
}
// find panics if a setting with the specific key was not
// found in the build info.
func find(key string) (string, bool) {
readBuildInfo.Do(func() {
buildInfo, buildInfoValid = debug.ReadBuildInfo()
})
if !buildInfoValid {
panic("couldn't read build info")
}
for _, setting := range buildInfo.Settings {
if setting.Key != key {
continue
}
return setting.Value, true
}
return "", false
}
+32
View File
@@ -0,0 +1,32 @@
package buildinfo_test
import (
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/mod/semver"
"github.com/coder/coder/cli/buildinfo"
)
func TestBuildInfo(t *testing.T) {
t.Parallel()
t.Run("Version", func(t *testing.T) {
t.Parallel()
version := buildinfo.Version()
require.True(t, semver.IsValid(version))
prerelease := semver.Prerelease(version)
require.Equal(t, "-devel", prerelease)
require.Equal(t, "v0", semver.Major(version))
})
t.Run("ExternalURL", func(t *testing.T) {
t.Parallel()
require.Equal(t, "https://github.com/coder/coder", buildinfo.ExternalURL())
})
// Tests don't include Go build info.
t.Run("NoTime", func(t *testing.T) {
t.Parallel()
_, valid := buildinfo.Time()
require.False(t, valid)
})
}
+15
View File
@@ -4,12 +4,14 @@ import (
"net/url"
"os"
"strings"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/kirsle/configdir"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"github.com/coder/coder/cli/buildinfo"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/cli/config"
"github.com/coder/coder/codersdk"
@@ -28,6 +30,7 @@ const (
func Root() *cobra.Command {
cmd := &cobra.Command{
Use: "coder",
Version: buildinfo.Version(),
SilenceUsage: true,
Long: ` ▄█▀ ▀█▄
▄▄ ▀▀▀ █▌ ██▀▀█▄ ▐█
@@ -55,6 +58,7 @@ func Root() *cobra.Command {
`Flags:`, header.Render("Flags:"),
`Additional help topics:`, header.Render("Additional help:"),
).Replace(cmd.UsageTemplate()))
cmd.SetVersionTemplate(versionTemplate())
cmd.AddCommand(
configSSH(),
@@ -142,3 +146,14 @@ func isTTY(cmd *cobra.Command) bool {
}
return isatty.IsTerminal(file.Fd())
}
func versionTemplate() string {
template := `Coder {{printf "%s" .Version}}`
buildTime, valid := buildinfo.Time()
if valid {
template += " " + buildTime.Format(time.UnixDate)
}
template += "\r\n" + buildinfo.ExternalURL()
template += "\r\n"
return template
}
+1 -1
View File
@@ -237,7 +237,7 @@ require (
github.com/zclconf/go-cty v1.10.0 // indirect
github.com/zeebo/errs v1.2.2 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/mod v0.5.1
golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7 // indirect