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
This commit is contained in:
klizhentas 2016-03-14 11:22:49 -07:00
parent 9da1e2baf4
commit 19788c25ce
13 changed files with 485 additions and 4 deletions

1
.gitignore vendored
View file

@ -30,3 +30,4 @@ _testmain.go
flymake*
QR.png
/vendor/github.com/gravitational/version/LICENSE

4
Godeps/Godeps.json generated
View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -154,5 +154,5 @@ func onConfigDump() {
// onVersion is the handler for "version"
func onVersion() {
fmt.Println("'version' command is not implemented")
utils.PrintVersion()
}

View file

@ -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) {

72
vendor/github.com/gravitational/version/README.md generated vendored Normal file
View file

@ -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]: <https://github.com/kubernetes/kubernetes>
[semver]: <http://semver.org>
[godep]: <https://github.com/tools/godep>
[tag]: <https://git-scm.com/book/en/v2/Git-Basics-Tagging>

29
vendor/github.com/gravitational/version/base.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

17
vendor/github.com/gravitational/version/doc.go generated vendored Normal file
View file

@ -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

View file

@ -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
}

13
vendor/github.com/gravitational/version/test/main.go generated vendored Normal file
View file

@ -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()
}

51
vendor/github.com/gravitational/version/version.go generated vendored Normal file
View file

@ -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)
}