From 62263ec19e56fe96eec0a2f79c35b1b4e511fab7 Mon Sep 17 00:00:00 2001 From: James C Scott III Date: Mon, 14 Oct 2019 14:12:11 -0400 Subject: [PATCH] Improve Application Versioning (#171) * Improve Application Versioning This will allow for better bug reports and information the binary running. This information can be retrieved upon providing the -v or --version flag * fixup: fix update-version and use variables to be more consistent --- Makefile | 36 ++++++++++------ deploy/Dockerfile | 5 ++- deploy/Dockerfile.local | 5 ++- deploy/packaging/caduceus.spec | 2 +- main.go | 47 +++++++++++++++++++-- main_test.go | 75 ++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 18f0af4b..74033fe6 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,15 @@ DEFAULT: build GO ?= go GOFMT ?= $(GO)fmt APP := caduceus +DOCKER_ORG := xmidt FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) BINARY := $(FIRST_GOPATH)/bin/$(APP) -PROGVER = $(shell grep 'applicationVersion.*= ' main.go | awk '{print $$3}' | sed -e 's/\"//g') +PROGVER = $(shell git describe --tags `git rev-list --tags --max-count=1` | tail -1 | sed 's/v\(.*\)/\1/') +RPM_VERSION=$(shell echo $(PROGVER) | sed 's/\(.*\)-\(.*\)/\1/') +RPM_RELEASE=$(shell echo $(PROGVER) | sed -n 's/.*-\(.*\)/\1/p' | grep . && (echo "$(echo $(PROGVER) | sed 's/.*-\(.*\)/\1/')") || echo "1") +BUILDTIME = $(shell date -u '+%Y-%m-%d %H:%M:%S') +GITCOMMIT = $(shell git rev-parse --short HEAD) .PHONY: go-mod-vendor go-mod-vendor: @@ -18,16 +23,16 @@ build: go-mod-vendor rpm: mkdir -p ./.ignore/SOURCES - tar -czf ./.ignore/SOURCES/$(APP)-$(PROGVER).tar.gz --transform 's/^\./$(APP)-$(PROGVER)/' --exclude ./.git --exclude ./.ignore --exclude ./conf --exclude ./deploy --exclude ./vendor --exclude ./vendor . + tar -czf ./.ignore/SOURCES/$(APP)-$(RPM_VERSION)-$(RPM_RELEASE).tar.gz --transform 's/^\./$(APP)-$(RPM_VERSION)-$(RPM_RELEASE)/' --exclude ./.git --exclude ./.ignore --exclude ./conf --exclude ./deploy --exclude ./vendor --exclude ./vendor . cp conf/$(APP).service ./.ignore/SOURCES cp $(APP).yaml ./.ignore/SOURCES cp LICENSE ./.ignore/SOURCES cp NOTICE ./.ignore/SOURCES cp CHANGELOG.md ./.ignore/SOURCES rpmbuild --define "_topdir $(CURDIR)/.ignore" \ - --define "_version $(PROGVER)" \ - --define "_release 1" \ - -ba deploy/packaging/$(APP).spec + --define "_version $(RPM_VERSION)" \ + --define "_release $(RPM_RELEASE)" \ + -ba deploy/packaging/$(APP).spec .PHONY: version version: @@ -44,28 +49,35 @@ endif .PHONY: update-version update-version: @echo "Update Version $(PROGVER) to $(RUN_ARGS)" - sed -i "s/$(PROGVER)/$(RUN_ARGS)/g" main.go + git tag v$(RUN_ARGS) .PHONY: install install: go-mod-vendor - echo $(GO) build -o $(BINARY) $(PROGVER) + go install -ldflags "-X 'main.BuildTime=$(BUILDTIME)' -X main.GitCommit=$(GITCOMMIT) -X main.Version=$(PROGVER)" .PHONY: release-artifacts release-artifacts: go-mod-vendor mkdir -p ./.ignore - GOOS=darwin GOARCH=amd64 $(GO) build -o ./.ignore/$(APP)-$(PROGVER).darwin-amd64 - GOOS=linux GOARCH=amd64 $(GO) build -o ./.ignore/$(APP)-$(PROGVER).linux-amd64 + GOOS=darwin GOARCH=amd64 $(GO) build -ldflags "-X 'main.BuildTime=$(BUILDTIME)' -X main.GitCommit=$(GITCOMMIT) -X main.Version=$(PROGVER)" -o ./.ignore/$(APP)-$(PROGVER).darwin-amd64 + GOOS=linux GOARCH=amd64 $(GO) build -ldflags "-X 'main.BuildTime=$(BUILDTIME)' -X main.GitCommit=$(GITCOMMIT) -X main.Version=$(PROGVER)" -o ./.ignore/$(APP)-$(PROGVER).linux-amd64 .PHONY: docker docker: - docker build -f ./deploy/Dockerfile -t $(APP):$(PROGVER) . + docker build \ + --build-arg VERSION=$(PROGVER) \ + --build-arg GITCOMMIT=$(GITCOMMIT) \ + --build-arg BUILDTIME='$(BUILDTIME)' \ + -f ./deploy/Dockerfile -t $(DOCKER_ORG)/$(APP):$(PROGVER) . # build docker without running modules .PHONY: local-docker local-docker: - GOOS=linux GOARCH=amd64 $(GO) build -o $(APP)_linux_amd64 - docker build -f ./deploy/Dockerfile.local -t $(APP):local . + docker build \ + --build-arg VERSION=$(PROGVER)+local \ + --build-arg GITCOMMIT=$(GITCOMMIT) \ + --build-arg BUILDTIME='$(BUILDTIME)' \ + -f ./deploy/Dockerfile.local -t $(DOCKER_ORG)/$(APP):local . .PHONY: style style: diff --git a/deploy/Dockerfile b/deploy/Dockerfile index da300f10..c7feb8d8 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -2,12 +2,15 @@ FROM golang:alpine as builder MAINTAINER Jack Murdock WORKDIR /go/src/github.com/xmidt-org/caduceus +ARG VERSION=undefined +ARG GITCOMMIT=undefined +ARG BUILDTIME=undefined RUN apk update && apk upgrade && \ apk add --no-cache bash git openssh COPY . . -RUN GO111MODULE=on go build -o caduceus_linux_amd64 +RUN GO111MODULE=on go build -ldflags "-X 'main.BuildTime=${BUILDTIME}' -X main.GitCommit=${GITCOMMIT} -X main.Version=${VERSION}" -o caduceus_linux_amd64 FROM alpine diff --git a/deploy/Dockerfile.local b/deploy/Dockerfile.local index 42d816d9..d418420c 100644 --- a/deploy/Dockerfile.local +++ b/deploy/Dockerfile.local @@ -2,12 +2,15 @@ FROM golang:alpine as builder MAINTAINER Jack Murdock WORKDIR /go/src/github.com/xmidt-org/caduceus +ARG VERSION=undefined +ARG GITCOMMIT=undefined +ARG BUILDTIME=undefined RUN apk add --update git curl COPY . . -RUN go build -o caduceus_linux_amd64 +RUN go build -ldflags "-X 'main.BuildTime=${BUILDTIME}' -X main.GitCommit=${GITCOMMIT} -X main.Version=${VERSION}" -o caduceus_linux_amd64 FROM alpine diff --git a/deploy/packaging/caduceus.spec b/deploy/packaging/caduceus.spec index 71c8a59f..5c157a28 100644 --- a/deploy/packaging/caduceus.spec +++ b/deploy/packaging/caduceus.spec @@ -22,7 +22,7 @@ BuildRequires: golang >= 1.12 The Xmidt API interface server. %build -GO111MODULE=on go build -o $RPM_SOURCE_DIR/%{name} %{_topdir}/.. +GO111MODULE=on go build -ldflags "-X 'main.BuildTime=`date -u '+%Y-%m-%d %H:%M:%S'`' -X main.GitCommit=`git rev-parse --short HEAD` -X main.Version=%{_version}" -o $RPM_SOURCE_DIR/%{name} %{_topdir}/.. %install echo rm -rf %{buildroot} diff --git a/main.go b/main.go index 0a239869..f58632e6 100644 --- a/main.go +++ b/main.go @@ -19,11 +19,13 @@ package main import ( "crypto/tls" "fmt" + "io" "net/http" _ "net/http/pprof" "net/url" "os" "os/signal" + "runtime" "time" "github.com/go-kit/kit/log/level" @@ -39,9 +41,14 @@ import ( ) const ( - applicationName = "caduceus" - DEFAULT_KEY_ID = "current" - applicationVersion = "0.2.1" + applicationName = "caduceus" + DEFAULT_KEY_ID = "current" +) + +var ( + GitCommit = "undefined" + Version = "undefined" + BuildTime = "undefined" ) // caduceus is the driver function for Caduceus. It performs everything main() would do, @@ -57,6 +64,18 @@ func caduceus(arguments []string) int { logger, metricsRegistry, webPA, err = server.Initialize(applicationName, arguments, f, v, Metrics, webhook.Metrics, aws.Metrics) ) + if parseErr, done := printVersion(f, arguments); done { + // if we're done, we're exiting no matter what + if parseErr != nil { + friendlyError := fmt.Sprintf("failed to parse arguments. detailed error: %s", parseErr) + logging.Error(logger).Log( + logging.ErrorKey(), + friendlyError) + os.Exit(1) + } + os.Exit(0) + } + if err != nil { fmt.Fprintf(os.Stderr, "Unable to initialize Viper environment: %s\n", err) return 1 @@ -224,6 +243,28 @@ func caduceus(arguments []string) int { return 0 } +func printVersion(f *pflag.FlagSet, arguments []string) (error, bool) { + printVer := f.BoolP("version", "v", false, "displays the version number") + if err := f.Parse(arguments); err != nil { + return err, true + } + + if *printVer { + printVersionInfo(os.Stdout) + return nil, true + } + return nil, false +} + +func printVersionInfo(writer io.Writer) { + fmt.Fprintf(writer, "%s:\n", applicationName) + fmt.Fprintf(writer, " version: \t%s\n", Version) + fmt.Fprintf(writer, " go version: \t%s\n", runtime.Version()) + fmt.Fprintf(writer, " built time: \t%s\n", BuildTime) + fmt.Fprintf(writer, " git commit: \t%s\n", GitCommit) + fmt.Fprintf(writer, " os/arch: \t%s/%s\n", runtime.GOOS, runtime.GOARCH) +} + func main() { os.Exit(caduceus(os.Args)) } diff --git a/main_test.go b/main_test.go index 831ade23..3c080e7a 100644 --- a/main_test.go +++ b/main_test.go @@ -17,10 +17,85 @@ package main import ( + "bytes" "os" + "strings" "testing" + + "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { os.Exit(m.Run()) } + +func TestPrintVersionInfo(t *testing.T) { + testCases := []struct { + name string + expectedOutput []string + overrideValues func() + lineCount int + }{ + { + "default", + []string{ + "caduceus:", + "version: \tundefined", + "go version: \tgo", + "built time: \tundefined", + "git commit: \tundefined", + "os/arch: \t", + }, + func() {}, + 6, + }, + { + "set values", + []string{ + "caduceus:", + "version: \t1.0.0\n", + "go version: \tgo", + "built time: \tsome time\n", + "git commit: \tgit sha\n", + "os/arch: \t", + }, + func() { + Version = "1.0.0" + BuildTime = "some time" + GitCommit = "git sha" + }, + 6, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resetGlobals() + tc.overrideValues() + buf := &bytes.Buffer{} + printVersionInfo(buf) + count := 0 + for { + line, err := buf.ReadString(byte('\n')) + if err != nil { + break + } + assert.Contains(t, line, tc.expectedOutput[count]) + if strings.Contains(line, "\t") { + keyAndValue := strings.Split(line, "\t") + // The value after the tab should have more than 2 characters + // 1) the first character of the value and the new line + assert.True(t, len(keyAndValue[1]) > 2) + } + count++ + } + assert.Equal(t, tc.lineCount, count) + resetGlobals() + }) + } +} + +func resetGlobals() { + Version = "undefined" + BuildTime = "undefined" + GitCommit = "undefined" +}