From 19788c25ce19e1252894e97e01787f05601803d9 Mon Sep 17 00:00:00 2001 From: klizhentas Date: Mon, 14 Mar 2016 11:22:49 -0700 Subject: [PATCH] introduce teleport version, fixes #241 Here's how it works: * It takes the closest tag that is present in the build * Automatically applies this tag * Adds git commit as well * Is 100% go gettable * No external deps, all vendored --- .gitignore | 1 + Godeps/Godeps.json | 4 + Makefile | 8 +- lib/utils/utils.go | 12 + tool/teleport/main.go | 2 +- tool/tsh/main.go | 2 +- .../gravitational/version/README.md | 72 ++++++ .../github.com/gravitational/version/base.go | 29 +++ .../version/cmd/linkflags/main.go | 213 ++++++++++++++++++ .../github.com/gravitational/version/doc.go | 17 ++ .../gravitational/version/pkg/tool/tool.go | 65 ++++++ .../gravitational/version/test/main.go | 13 ++ .../gravitational/version/version.go | 51 +++++ 13 files changed, 485 insertions(+), 4 deletions(-) create mode 100644 vendor/github.com/gravitational/version/README.md create mode 100644 vendor/github.com/gravitational/version/base.go create mode 100644 vendor/github.com/gravitational/version/cmd/linkflags/main.go create mode 100644 vendor/github.com/gravitational/version/doc.go create mode 100644 vendor/github.com/gravitational/version/pkg/tool/tool.go create mode 100644 vendor/github.com/gravitational/version/test/main.go create mode 100644 vendor/github.com/gravitational/version/version.go diff --git a/.gitignore b/.gitignore index 1cdd97d78f8..99900cb07bc 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ _testmain.go flymake* QR.png +/vendor/github.com/gravitational/version/LICENSE diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 05cc42b77ee..48929220dd5 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -97,6 +97,10 @@ "ImportPath": "github.com/gravitational/trace", "Rev": "0fd0db9ea941edb81e90278f4eb8f29f5615637e" }, + { + "ImportPath": "github.com/gravitational/version", + "Rev": "cfc6250a884a833eed6ac0417a6f245f5ac49d7a" + }, { "ImportPath": "github.com/jonboulle/clockwork", "Rev": "ed104f61ea4877bea08af6f759805674861e968d" diff --git a/Makefile b/Makefile index 89f12df60f8..bf7d4cd30d2 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ teleport: tsh: go build -o $(OUT)/tsh -i github.com/gravitational/teleport/tool/tsh -install: remove-temp-files - go install github.com/gravitational/teleport/tool/teleport \ +install: remove-temp-files flags + go install -ldflags $(TELEPORT_LINKFLAGS) github.com/gravitational/teleport/tool/teleport \ github.com/gravitational/teleport/tool/tctl \ github.com/gravitational/teleport/tool/tsh \ @@ -50,6 +50,10 @@ test: github.com/gravitational/teleport/tool/teleport... $(FLAGS) go vet ./tool/... ./lib/... +flags: + go install github.com/gravitational/teleport/vendor/github.com/gravitational/version/cmd/linkflags + $(eval TELEPORT_LINKFLAGS := "$(shell linkflags -pkg=$(PWD) -verpkg=github.com/gravitational/teleport/vendor/github.com/gravitational/version)") + test-with-etcd: install ${ETCD_FLAGS} go test -v -test.parallel=0 $(shell go list ./... | grep -v /vendor/) -cover diff --git a/lib/utils/utils.go b/lib/utils/utils.go index b348922921c..1bcd2316a81 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -17,6 +17,7 @@ limitations under the License. package utils import ( + "fmt" "io" "io/ioutil" "net" @@ -29,6 +30,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/gravitational/trace" + "github.com/gravitational/version" "github.com/pborman/uuid" "golang.org/x/crypto/ssh" ) @@ -143,6 +145,16 @@ func ReadOrMakeHostUUID(dataDir string) (string, error) { return string(bytes), nil } +// PrintVersion prints human readable version +func PrintVersion() { + ver := version.Get() + if ver.GitCommit != "" { + fmt.Printf("%v git:%v\n", ver.Version, ver.GitCommit) + } else { + fmt.Printf("%v\n", ver.Version) + } +} + const ( // CertTeleportUser specifies teleport user CertTeleportUser = "x-teleport-user" diff --git a/tool/teleport/main.go b/tool/teleport/main.go index 19944101edf..2aee58ec393 100644 --- a/tool/teleport/main.go +++ b/tool/teleport/main.go @@ -154,5 +154,5 @@ func onConfigDump() { // onVersion is the handler for "version" func onVersion() { - fmt.Println("'version' command is not implemented") + utils.PrintVersion() } diff --git a/tool/tsh/main.go b/tool/tsh/main.go index e265d7a86fc..64ef4c1496f 100644 --- a/tool/tsh/main.go +++ b/tool/tsh/main.go @@ -284,7 +284,7 @@ func makeClient(cf *CLIConf) (tc *client.TeleportClient, err error) { } func onVersion() { - fmt.Println("Version!") + utils.PrintVersion() } func printHeader(t *goterm.Table, cols []string) { diff --git a/vendor/github.com/gravitational/version/README.md b/vendor/github.com/gravitational/version/README.md new file mode 100644 index 00000000000..f4a7e79bb91 --- /dev/null +++ b/vendor/github.com/gravitational/version/README.md @@ -0,0 +1,72 @@ +# version + +`version` is a Go library for automatic build versioning. It attempts to simplify the mundane task of adding build version information to any Go package. + +### Usage + +Install the command line utility to generate the linker flags necessary for versioning from the cmd/linkflags: + +```shell +go install github.com/gravitational/version/cmd/linkflags +``` + +Add the following configuration to your build script / Makefile +(assuming a bash script): + +```bash +GO_LDFLAGS=$(linkflags -pkg=path/to/your/package) + +# build with the linker flags: +go build -ldflags="${GO_LDFLAGS}" +``` + +To use, simply import the package and either obtain the version with `Get` or print the JSON-formatted version with `Print`: + +```go +package main + +import "github.com/gravitational/version" + +func main() { + version.Print() +} +``` + +If you have a custom vendoring solution, you might have this package stored under a different path than the default (`go get`). +In this case, you can override the default with a command line option (using [godep] as a vendoring solution): + +```shell +MY_PACKAGE=github.com/my/package +MY_PACKAGE_PATH=$(pwd) +GO_LDFLAGS=$(linkflags -pkg=${MY_PACKAGE_PATH} -verpkg=${MY_PACKAGE}/Godeps/_workspace/src/github.com/gravitational/version) +``` + +The version part of the version information requires that you properly [tag] your packages: + +```shell +$ git tag +v1.0.0-alpha.1 +v1.0.0-beta.1 +``` + +The build versioning scheme is a slight modification of the scheme from the [Kubernetes] project. +It consists of three parts: + - a version string in [semver] format + - git commit ID + - git tree state (`clean` or `dirty`) + +```go +type Info struct { + Version string `json:"version"` + GitCommit string `json:"gitCommit"` + GitTreeState string `json:"gitTreeState"` +} +``` + + +[//]: # (Footnots and references) + +[Kubernetes]: +[semver]: +[godep]: +[tag]: diff --git a/vendor/github.com/gravitational/version/base.go b/vendor/github.com/gravitational/version/base.go new file mode 100644 index 00000000000..2864a02ff94 --- /dev/null +++ b/vendor/github.com/gravitational/version/base.go @@ -0,0 +1,29 @@ +/* +Copyright 2015 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package version + +// Version value defaults +var ( + // Version string, a slightly modified version of `git describe` to be semver-complaint + version string = "v0.0.0-master+$Format:%h$" + gitCommit string = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) + gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" +) + +// Init sets an alternative default for the version string. +func Init(baseVersion string) { + version = baseVersion +} diff --git a/vendor/github.com/gravitational/version/cmd/linkflags/main.go b/vendor/github.com/gravitational/version/cmd/linkflags/main.go new file mode 100644 index 00000000000..d08fe76c7d6 --- /dev/null +++ b/vendor/github.com/gravitational/version/cmd/linkflags/main.go @@ -0,0 +1,213 @@ +/* +Copyright 2015 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import ( + "flag" + "fmt" + "log" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/gravitational/version" + "github.com/gravitational/version/pkg/tool" +) + +// pkg is the path to the package the tool will create linker flags for. +var pkg = flag.String("pkg", "", "root package path") + +// versionPackage is the path to this version package. +// It is used to access version information attributes during link time. +// This flag is useful when the version package is custom-vendored and has a different package path. +var versionPackage = flag.String("verpkg", "github.com/gravitational/version", "path to the version package") + +var compatMode = flag.Bool("compat", false, "generate linker flags using go1.4 syntax") + +// semverPattern defines a regexp pattern to modify the results of `git describe` to be semver-complaint. +var semverPattern = regexp.MustCompile(`(.+)-([0-9]{1,})-g([0-9a-f]{14})$`) + +// goVersionPattern defines a regexp pattern to parse versions of the `go tool`. +var goVersionPattern = regexp.MustCompile(`go([1-9])\.(\d+)(?:.\d+)*`) + +func main() { + if err := run(); err != nil { + log.Fatalln(err) + } +} + +func run() error { + log.SetFlags(0) + flag.Parse() + if *pkg == "" { + return fmt.Errorf("-pkg required") + } + + goVersion, err := goToolVersion() + if err != nil { + return fmt.Errorf("failed to determine go tool version: %v\n", err) + } + + info, err := getVersionInfo(*pkg) + if err != nil { + return fmt.Errorf("failed to determine version information: %v\n", err) + } + + var linkFlags []string + linkFlag := func(key, value string) string { + if goVersion <= 14 || *compatMode { + return fmt.Sprintf("-X %s.%s %s", *versionPackage, key, value) + } else { + return fmt.Sprintf("-X %s.%s=%s", *versionPackage, key, value) + } + } + + // Determine the values of version-related variables as commands to the go linker. + if info.GitCommit != "" { + linkFlags = append(linkFlags, linkFlag("gitCommit", info.GitCommit)) + linkFlags = append(linkFlags, linkFlag("gitTreeState", info.GitTreeState)) + } + if info.Version != "" { + linkFlags = append(linkFlags, linkFlag("version", info.Version)) + } + + fmt.Printf("%s", strings.Join(linkFlags, " ")) + return nil +} + +// getVersionInfo collects the build version information for package pkg. +func getVersionInfo(pkg string) (*version.Info, error) { + git := newGit(pkg) + commitID, err := git.commitID() + if err != nil { + return nil, fmt.Errorf("failed to obtain git commit ID: %v\n", err) + } + treeState, err := git.treeState() + if err != nil { + return nil, fmt.Errorf("failed to determine git tree state: %v\n", err) + } + tag, err := git.tag(commitID) + if err != nil { + tag = "" + } + if tag != "" { + tag = semverify(tag) + if treeState == dirty { + tag = tag + "-" + string(treeState) + } + } + return &version.Info{ + Version: tag, + GitCommit: commitID, + GitTreeState: string(treeState), + }, nil +} + +// goToolVersion determines the version of the `go tool`. +func goToolVersion() (toolVersion, error) { + goTool := &tool.T{Cmd: "go"} + out, err := goTool.Exec("version") + if err != nil { + return toolVersionUnknown, err + } + build := strings.Split(out, " ") + if len(build) > 2 { + return parseToolVersion(build[2]), nil + } + return toolVersionUnknown, nil +} + +// parseToolVersion translates a string version of the form 'go1.4.3' to a numeric value 14. +func parseToolVersion(version string) toolVersion { + match := goVersionPattern.FindStringSubmatch(version) + if len(match) > 2 { + // After a successful match, match[1] and match[2] are integers + major := mustAtoi(match[1]) + minor := mustAtoi(match[2]) + return toolVersion(major*10 + minor) + } + return toolVersionUnknown +} + +func newGit(pkg string) *git { + args := []string{"--work-tree", pkg, "--git-dir", filepath.Join(pkg, ".git")} + return &git{&tool.T{ + Cmd: "git", + Args: args, + }} +} + +// git represents an instance of the git tool. +type git struct { + *tool.T +} + +// treeState describes the state of the git tree. +// `git describe --dirty` only considers changes to existing files. +// We track tree state and consider untracked files as they also affect the build. +type treeState string + +const ( + clean treeState = "clean" + dirty = "dirty" +) + +// toolVersion represents a tool version as an integer. +// toolVersion only considers the first two significant version parts and is computed as follows: +// majorVersion*10+minorVersion +type toolVersion int + +const toolVersionUnknown toolVersion = 0 + +func (r *git) commitID() (string, error) { + return r.Exec("rev-parse", "HEAD^{commit}") +} + +func (r *git) treeState() (treeState, error) { + out, err := r.Exec("status", "--porcelain") + if err != nil { + return "", err + } + if len(out) == 0 { + return clean, nil + } + return dirty, nil +} + +func (r *git) tag(commitID string) (string, error) { + return r.Exec("describe", "--tags", "--abbrev=14", commitID+"^{commit}") +} + +// semverify transforms the output of `git describe` to be semver-complaint. +func semverify(version string) string { + var result []byte + match := semverPattern.FindStringSubmatchIndex(version) + if match != nil { + return string(semverPattern.ExpandString(result, "$1.$2+$3", string(version), match)) + } + return version +} + +// mustAtoi converts value to an integer. +// It panics if the value does not represent a valid integer. +func mustAtoi(value string) int { + result, err := strconv.Atoi(value) + if err != nil { + panic(err) + } + return result +} diff --git a/vendor/github.com/gravitational/version/doc.go b/vendor/github.com/gravitational/version/doc.go new file mode 100644 index 00000000000..2db5702183e --- /dev/null +++ b/vendor/github.com/gravitational/version/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2015 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Package version supplies version information collected at build time. +package version diff --git a/vendor/github.com/gravitational/version/pkg/tool/tool.go b/vendor/github.com/gravitational/version/pkg/tool/tool.go new file mode 100644 index 00000000000..2dfb7deca22 --- /dev/null +++ b/vendor/github.com/gravitational/version/pkg/tool/tool.go @@ -0,0 +1,65 @@ +/* +Copyright 2015 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Package tool provides a currying wrapper around os/exec.Command that fixes a set of arguments. +package tool + +import ( + "bytes" + "fmt" + "os/exec" +) + +// T represents an instance of a running tool specified with cmd and +// an optional list of fixed initial arguments args. +type T struct { + Cmd string + Args []string +} + +// Error is a tool execution error. +type Error struct { + Tool string + Output []byte + Err error +} + +func (r *Error) Error() string { + return fmt.Sprintf("error executing `%s`: %v (%s)", r.Tool, r.Err, r.Output) +} + +// exec executes a given command specified with args prepending a set of fixed arguments. +// Otherwise behaves exactly as rawExec +func (r *T) Exec(args ...string) (string, error) { + args = append(r.Args[:], args...) + return r.RawExec(args...) +} + +// RawExec executes a given command specified with args and returns the output +// with whitespace trimmed. +func (r *T) RawExec(args ...string) (string, error) { + out, err := exec.Command(r.Cmd, args...).CombinedOutput() + if err == nil { + out = bytes.TrimSpace(out) + } + if err != nil { + err = &Error{ + Tool: r.Cmd, + Output: out, + Err: err, + } + } + return string(out), err +} diff --git a/vendor/github.com/gravitational/version/test/main.go b/vendor/github.com/gravitational/version/test/main.go new file mode 100644 index 00000000000..ebe0ae73697 --- /dev/null +++ b/vendor/github.com/gravitational/version/test/main.go @@ -0,0 +1,13 @@ +package main + +import "github.com/gravitational/version" + +func init() { + // Reset base version to a custom one in case no tag has been created. + // It will be reset with a git tag if there's one. + version.Init("v0.0.1") +} + +func main() { + version.Print() +} diff --git a/vendor/github.com/gravitational/version/version.go b/vendor/github.com/gravitational/version/version.go new file mode 100644 index 00000000000..9fcbfbb668a --- /dev/null +++ b/vendor/github.com/gravitational/version/version.go @@ -0,0 +1,51 @@ +/* +Copyright 2015 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package version + +import ( + "encoding/json" + "fmt" +) + +// Info describes build version with a semver-complaint version string and +// git-related commit/tree state details. +type Info struct { + Version string `json:"version"` + GitCommit string `json:"gitCommit"` + GitTreeState string `json:"gitTreeState"` +} + +// Get returns current build version. +func Get() Info { + return Info{ + Version: version, + GitCommit: gitCommit, + GitTreeState: gitTreeState, + } +} + +func (r Info) String() string { + return r.Version +} + +// Print prints build version in default format. +func Print() { + payload, err := json.Marshal(Get()) + if err != nil { + panic(err) + } + fmt.Printf("%s", payload) +}