From c20c0f9958ceeefd3597120fcb4013d857276076 Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Thu, 14 Dec 2017 15:01:18 -0800 Subject: [PATCH] Comply with semantic versioning. Include build metadata in `argo version` (resolves #594) --- Makefile | 41 +++++++++-------- cmd/argo/commands/install.go | 18 ++++++-- cmd/argoexec/commands/root.go | 2 +- util/cmd/cmd.go | 20 ++++++++- version.go | 74 +++++++++++++++++++++++-------- workflow/common/common.go | 7 --- workflow/controller/controller.go | 2 + 7 files changed, 113 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index c8f6d7049cf1..975367bd1ba3 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ CURRENT_DIR=$(shell pwd) DIST_DIR=${CURRENT_DIR}/dist VERSION=$(shell cat ${CURRENT_DIR}/VERSION) -REVISION=$(shell git rev-parse HEAD) -BRANCH=$(shell git rev-parse --abbrev-ref HEAD) -TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi) -TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi) +BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ') +GIT_COMMIT=$(shell git rev-parse HEAD) +GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi) +GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; else echo "dirty"; fi) BUILDER_IMAGE=argo-builder # NOTE: the volume mount of ${DIST_DIR}/pkg below is optional and serves only @@ -16,22 +16,25 @@ BUILDER_CMD=docker run --rm \ -v ${DIST_DIR}/pkg:/root/go/pkg \ -w /root/go/src/${PACKAGE} ${BUILDER_IMAGE} +LDFLAGS = \ + -X ${PACKAGE}.version=${VERSION} \ + -X ${PACKAGE}.buildDate=${BUILD_DATE} \ + -X ${PACKAGE}.gitCommit=${GIT_COMMIT} \ + -X ${PACKAGE}.gitTreeState=${GIT_TREE_STATE} + # docker image publishing options DOCKER_PUSH=false -ifeq (${IMAGE_TAG},) -ifneq (${TAG},) -IMAGE_TAG=${TAG} -else -IMAGE_TAG=${VERSION} +IMAGE_TAG=latest +ifneq (${GIT_TAG},) +IMAGE_TAG=${GIT_TAG} +LDFLAGS += -X ${PACKAGE}.gitTag=${GIT_TAG} endif +ifneq (${IMAGE_NAMESPACE},) +LDFLAGS += -X ${PACKAGE}/cmd/argo/commands.imageNamespace=${IMAGE_NAMESPACE} +endif +ifneq (${IMAGE_TAG},) +LDFLAGS += -X ${PACKAGE}/cmd/argo/commands.imageTag=${IMAGE_TAG} endif - -LDFLAGS = -ldflags "-X ${PACKAGE}.Version=${VERSION} \ - -X ${PACKAGE}.Revision=${REVISION} \ - -X ${PACKAGE}.Branch=${BRANCH} \ - -X ${PACKAGE}.Tag=${TAG} \ - -X ${PACKAGE}.ImageNamespace=${IMAGE_NAMESPACE} \ - -X ${PACKAGE}.ImageTag=${IMAGE_TAG}" ifeq (${DOCKER_PUSH},true) ifndef IMAGE_NAMESPACE @@ -52,7 +55,7 @@ builder: docker build -t ${BUILDER_IMAGE} -f Dockerfile-builder . cli: - go build -v -i ${LDFLAGS} -o ${DIST_DIR}/argo ./cmd/argo + go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argo ./cmd/argo cli-linux: builder ${BUILDER_CMD} make cli IMAGE_TAG=$(IMAGE_TAG) @@ -63,7 +66,7 @@ cli-darwin: builder mv ${DIST_DIR}/argo ${DIST_DIR}/argo-darwin-amd64 controller: - go build -v -i ${LDFLAGS} -o ${DIST_DIR}/workflow-controller ./cmd/workflow-controller + go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/workflow-controller ./cmd/workflow-controller controller-linux: builder ${BUILDER_CMD} make controller @@ -73,7 +76,7 @@ controller-image: controller-linux if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)workflow-controller:$(IMAGE_TAG) ; fi executor: - go build -v -i ${LDFLAGS} -o ${DIST_DIR}/argoexec ./cmd/argoexec + go build -v -i -ldflags '${LDFLAGS}' -o ${DIST_DIR}/argoexec ./cmd/argoexec executor-linux: builder ${BUILDER_CMD} make executor diff --git a/cmd/argo/commands/install.go b/cmd/argo/commands/install.go index 55673b6a9cbc..1b11cfb14bf1 100644 --- a/cmd/argo/commands/install.go +++ b/cmd/argo/commands/install.go @@ -24,15 +24,27 @@ import ( const clusterAdmin = "cluster-admin" +var ( + // These values may be overridden by the link flags during build + // (e.g. imageTag will use the official release tag on tagged builds) + imageNamespace = "argoproj" + imageTag = "latest" + + // These are the default image names which `argo install` uses during install + DefaultControllerImage = imageNamespace + "/workflow-controller:" + imageTag + DefaultExecutorImage = imageNamespace + "/argoexec:" + imageTag + DefaultUiImage = imageNamespace + "/argoui:" + imageTag +) + func init() { RootCmd.AddCommand(installCmd) installCmd.Flags().StringVar(&installArgs.ControllerName, "controller-name", common.DefaultControllerDeploymentName, "name of controller deployment") installCmd.Flags().StringVar(&installArgs.UIName, "ui-name", common.DefaultUiDeploymentName, "name of ui deployment") installCmd.Flags().StringVar(&installArgs.Namespace, "install-namespace", common.DefaultControllerNamespace, "install into a specific Namespace") installCmd.Flags().StringVar(&installArgs.ConfigMap, "configmap", common.DefaultConfigMapName(common.DefaultControllerDeploymentName), "install controller using preconfigured configmap") - installCmd.Flags().StringVar(&installArgs.ControllerImage, "controller-image", common.DefaultControllerImage, "use a specified controller image") - installCmd.Flags().StringVar(&installArgs.UIImage, "ui-image", common.DefaultUiImage, "use a specified ui image") - installCmd.Flags().StringVar(&installArgs.ExecutorImage, "executor-image", common.DefaultExecutorImage, "use a specified executor image") + installCmd.Flags().StringVar(&installArgs.ControllerImage, "controller-image", DefaultControllerImage, "use a specified controller image") + installCmd.Flags().StringVar(&installArgs.UIImage, "ui-image", DefaultUiImage, "use a specified ui image") + installCmd.Flags().StringVar(&installArgs.ExecutorImage, "executor-image", DefaultExecutorImage, "use a specified executor image") installCmd.Flags().StringVar(&installArgs.ServiceAccount, "service-account", "", "use a specified service account for the workflow-controller deployment") } diff --git a/cmd/argoexec/commands/root.go b/cmd/argoexec/commands/root.go index 92594489a1e5..80e9b7dd6d00 100644 --- a/cmd/argoexec/commands/root.go +++ b/cmd/argoexec/commands/root.go @@ -98,7 +98,7 @@ func initExecutor() *executor.WorkflowExecutor { Namespace: namespace, } yamlBytes, _ := yaml.Marshal(&wfExecutor.Template) - log.Infof("Executor (version: %s) initialized with template:\n%s", argo.FullVersion, string(yamlBytes)) + log.Infof("Executor (version: %s) initialized with template:\n%s", argo.GetVersion(), string(yamlBytes)) return &wfExecutor } diff --git a/util/cmd/cmd.go b/util/cmd/cmd.go index b68659b59bd7..644c061f1a5d 100644 --- a/util/cmd/cmd.go +++ b/util/cmd/cmd.go @@ -15,13 +15,29 @@ import ( // NewVersionCmd returns a new `version` command to be used as a sub-command to root func NewVersionCmd(cliName string) *cobra.Command { - return &cobra.Command{ + var short bool + versionCmd := cobra.Command{ Use: "version", Short: fmt.Sprintf("Print version information"), Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("%s version %s\n", cliName, argo.FullVersion) + version := argo.GetVersion() + fmt.Printf("%s: %s\n", cliName, version) + if short { + return + } + fmt.Printf(" BuildDate: %s\n", version.BuildDate) + fmt.Printf(" GitCommit: %s\n", version.GitCommit) + fmt.Printf(" GitTreeState: %s\n", version.GitTreeState) + if version.GitTag != "" { + fmt.Printf(" GitTag: %s\n", version.GitTag) + } + fmt.Printf(" GoVersion: %s\n", version.GoVersion) + fmt.Printf(" Compiler: %s\n", version.Compiler) + fmt.Printf(" Platform: %s\n", version.Platform) }, } + versionCmd.Flags().BoolVar(&short, "short", false, "print just the version number") + return &versionCmd } // MustIsDir returns whether or not the given filePath is a directory. Exits if path does not exist diff --git a/version.go b/version.go index 347363e71eb4..fe7522e1d4b0 100644 --- a/version.go +++ b/version.go @@ -1,28 +1,64 @@ package argo -import "fmt" +import ( + "fmt" + "runtime" +) -// Version information set by link flags during build +// Version information set by link flags during build. We fall back to these sane +// default values when we build outside the Makefile context (e.g. go build or go test). var ( - Version = "unknown" - Revision = "unknown" - Branch = "unknown" - Tag = "" - BuildDate = "unknown" - FullVersion = "unknown" - ImageNamespace = "" - ImageTag = Version + version = "0.0.0" // value from VERSION file + buildDate = "1970-01-01T00:00:00Z" // output from `date -u +'%Y-%m-%dT%H:%M:%SZ'` + gitCommit = "" // output from `git rev-parse HEAD` + gitTag = "" // output from `git describe --exact-match --tags HEAD` (if clean tree state) + gitTreeState = "" // determined from `git status --porcelain`. either 'clean' or 'dirty' ) -func init() { - if ImageNamespace == "" { - ImageNamespace = "argoproj" - } - if Tag != "" { - // if a git tag was set, use that as our version - FullVersion = Tag - ImageTag = Tag +// Version contains Argo version information +type Version struct { + Version string + BuildDate string + GitCommit string + GitTag string + GitTreeState string + GoVersion string + Compiler string + Platform string +} + +func (v Version) String() string { + return v.Version +} + +// GetVersion returns the version information +func GetVersion() Version { + var versionStr string + if gitCommit != "" && gitTag != "" && gitTreeState == "clean" { + // if we have a clean tree state and the current commit is tagged, + // this is an official release. + versionStr = gitTag } else { - FullVersion = fmt.Sprintf("v%s-%s", Version, Revision[0:7]) + // otherwise formulate a version string based on as much metadata + // information we have available. + versionStr = "v" + version + if len(gitCommit) >= 7 { + versionStr += "+" + gitCommit[0:7] + if gitTreeState != "clean" { + versionStr += ".dirty" + } + } else { + versionStr += "+unknown" + } + } + return Version{ + Version: versionStr, + BuildDate: buildDate, + GitCommit: gitCommit, + GitTag: gitTag, + GitTreeState: gitTreeState, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } diff --git a/workflow/common/common.go b/workflow/common/common.go index b3bcaafd0f36..1c873c86eeaa 100644 --- a/workflow/common/common.go +++ b/workflow/common/common.go @@ -1,16 +1,9 @@ package common import ( - "github.com/argoproj/argo" wfv1 "github.com/argoproj/argo/api/workflow/v1alpha1" ) -var ( - DefaultControllerImage = argo.ImageNamespace + "/workflow-controller:" + argo.ImageTag - DefaultExecutorImage = argo.ImageNamespace + "/argoexec:" + argo.ImageTag - DefaultUiImage = argo.ImageNamespace + "/argoui:" + argo.ImageTag -) - const ( // DefaultControllerDeploymentName is the default deployment name of the workflow controller DefaultControllerDeploymentName = "workflow-controller" diff --git a/workflow/controller/controller.go b/workflow/controller/controller.go index 29df29fcf124..d48f2a28be81 100644 --- a/workflow/controller/controller.go +++ b/workflow/controller/controller.go @@ -7,6 +7,7 @@ import ( goruntime "runtime" "time" + "github.com/argoproj/argo" wfv1 "github.com/argoproj/argo/api/workflow/v1alpha1" "github.com/argoproj/argo/errors" workflowclient "github.com/argoproj/argo/workflow/client" @@ -98,6 +99,7 @@ func (wfc *WorkflowController) Run(ctx context.Context) { defer wfc.wfQueue.ShutDown() defer wfc.podQueue.ShutDown() + log.Infof("Workflow Controller (version: %s) starting", argo.GetVersion()) log.Info("Watch Workflow controller config map updates") _, err := wfc.watchControllerConfigMap(ctx) if err != nil {