From be54ddddc8393f7986bef573476373e45c96eff6 Mon Sep 17 00:00:00 2001 From: Raunak Singwi Date: Mon, 24 Jan 2022 09:23:53 +0000 Subject: [PATCH] Add version flag to host agent Add version flag to the host agent so that the user can know the version being used. The output shows the version of the binary and the time it was built. --- .github/workflows/draft-release.yaml | 2 +- Makefile | 6 +- agent/help_flag_test.go | 1 + agent/host_agent_test.go | 42 +++++ agent/installer/internal/algo/shell_step.go | 162 ++++++++++---------- agent/main.go | 9 ++ agent/reconciler/reconciler_test.go | 2 +- agent/version/version.go | 67 ++++++++ agent/version/version_suite_test.go | 16 ++ agent/version/version_test.go | 130 ++++++++++++++++ 10 files changed, 353 insertions(+), 84 deletions(-) create mode 100644 agent/version/version.go create mode 100644 agent/version/version_suite_test.go create mode 100644 agent/version/version_test.go diff --git a/.github/workflows/draft-release.yaml b/.github/workflows/draft-release.yaml index 4f4b7da45..3a157e15e 100644 --- a/.github/workflows/draft-release.yaml +++ b/.github/workflows/draft-release.yaml @@ -20,7 +20,7 @@ jobs: go-version: 1.16 - name: Build Release Artifacts - run: IMG="projects.registry.vmware.com/cluster_api_provider_bringyourownhost/cluster-api-byoh-controller:${{ github.ref_name }}" make build-release-artifacts + run: IMG="projects.registry.vmware.com/cluster_api_provider_bringyourownhost/cluster-api-byoh-controller:${{ github.ref_name }}" AGENT_BINARY_VERSION="${{ github.ref_name }}" make build-release-artifacts - name: Publish Release uses: softprops/action-gh-release@v1 diff --git a/Makefile b/Makefile index fd2f69630..1d033b86d 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ BYOH_TEMPLATES := $(REPO_ROOT)/test/e2e/data/infrastructure-provider-byoh LDFLAGS=-w -s STATIC=-extldflags '-static' +AGENT_BINARY_VERSION ?= dev # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -171,7 +172,10 @@ kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.9.1) host-agent-binaries: ## Builds the binaries for the host-agent - RELEASE_BINARY=./byoh-hostagent GOOS=linux GOARCH=amd64 GOLDFLAGS="$(LDFLAGS) $(STATIC)" HOST_AGENT_DIR=./$(HOST_AGENT_DIR) $(MAKE) host-agent-binary + RELEASE_BINARY=./byoh-hostagent GOOS=linux GOARCH=amd64 GOLDFLAGS="$(LDFLAGS) $(STATIC) \ + -X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.Version=${AGENT_BINARY_VERSION}' \ + -X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.BuildDate=$(shell date -u +\"%Y-%m-%dT%H:%M:%SZ\")'" \ + HOST_AGENT_DIR=./$(HOST_AGENT_DIR) $(MAKE) host-agent-binary host-agent-binary: $(RELEASE_DIR) docker run \ diff --git a/agent/help_flag_test.go b/agent/help_flag_test.go index 7db66dd01..f9bffe497 100644 --- a/agent/help_flag_test.go +++ b/agent/help_flag_test.go @@ -23,6 +23,7 @@ var _ = Describe("Help flag for host agent", func() { "-metricsbindaddress string", "-namespace string", "-skip-installation", + "-version", } ) diff --git a/agent/host_agent_test.go b/agent/host_agent_test.go index 3e5aeb91b..8d23a408c 100644 --- a/agent/host_agent_test.go +++ b/agent/host_agent_test.go @@ -6,10 +6,12 @@ package main import ( "context" + "fmt" "io/ioutil" "net" "os" "os/exec" + "runtime" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,6 +20,7 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" + "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version" infrastructurev1beta1 "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/apis/infrastructure/v1beta1" "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/test/builder" corev1 "k8s.io/api/core/v1" @@ -188,4 +191,43 @@ var _ = Describe("Agent", func() { Consistently(session.Err, "10s").ShouldNot(gbytes.Say(byoHost.Name)) }) }) + + Context("When host agent is executed with --version flag", func() { + var ( + tmpHostAgentBinary string + ) + BeforeEach(func() { + date, err := exec.Command("date").Output() + Expect(err).NotTo(HaveOccurred()) + version.BuildDate = string(date) + version.Version = "v1.2.3" + ldflags := fmt.Sprintf("-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.Version=%s'"+ + " -X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.BuildDate=%s'", version.Version, version.BuildDate) + tmpHostAgentBinary, err = gexec.Build("github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent", "-ldflags", ldflags) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + version.BuildDate = "" + version.Version = "" + tmpHostAgentBinary = "" + }) + + It("Shows the appropriate version of the agent", func() { + expectedStruct := version.Info{ + Major: "1", + Minor: "2", + Patch: "3", + BuildDate: version.BuildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } + expected := fmt.Sprintf("byoh-hostagent version: %#v\n", expectedStruct) + out, err := exec.Command(tmpHostAgentBinary, "--version").Output() + Expect(err).NotTo(HaveOccurred()) + output := string(out) + Expect(output).Should(Equal(expected)) + }) + }) }) diff --git a/agent/installer/internal/algo/shell_step.go b/agent/installer/internal/algo/shell_step.go index 4509b308c..2ea27e1eb 100755 --- a/agent/installer/internal/algo/shell_step.go +++ b/agent/installer/internal/algo/shell_step.go @@ -1,81 +1,81 @@ -// Copyright 2021 VMware, Inc. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package algo - -import ( - "bytes" - "os/exec" -) - -/* -######################################## -# Extends Step to a shell exec command # -######################################## -*/ -type ShellStep struct { - Step - DoCmd string - UndoCmd string - Desc string - *BaseK8sInstaller -} - -func (s *ShellStep) do() error { - s.OutputBuilder.Msg("Installing: " + s.Desc) - return s.runStep(s.DoCmd) -} - -func (s *ShellStep) undo() error { - s.OutputBuilder.Msg("Uninstalling: " + s.Desc) - return s.runStep(s.UndoCmd) -} - -func (s *ShellStep) runStep(command string) error { - var stdOut bytes.Buffer - var stdErr bytes.Buffer - - const defaultShell = "bash" - - // TODO: check for exit(-1) or similar code - cmd := exec.Command(defaultShell, "-c", command) - s.OutputBuilder.Cmd(cmd.String()) - - if s.BundlePath == "" { - return nil - } - - cmd.Stdout = &stdOut - cmd.Stderr = &stdErr - err := cmd.Run() - - if len(stdErr.String()) > 0 { - /* - this is a non critical error - the installer is still running properly - but some package stderrored. - e.g.: - - swap is already off; - - apt tells us it is installing from a local pkg - and cannot confirm the repository - - do not return err! just log it. - otherwise it will cause execution of the rollback procedure - - we only return error if the shellExec - cannot be executed due to erroneous shell command, etc. - */ - s.OutputBuilder.Err(stdErr.String()) - } - - if err != nil { - s.OutputBuilder.Err(err.Error()) - return err - } - - if len(stdOut.String()) > 0 { - s.OutputBuilder.Out(stdOut.String()) - } - - return nil -} +// Copyright 2021 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package algo + +import ( + "bytes" + "os/exec" +) + +/* +######################################## +# Extends Step to a shell exec command # +######################################## +*/ +type ShellStep struct { + Step + DoCmd string + UndoCmd string + Desc string + *BaseK8sInstaller +} + +func (s *ShellStep) do() error { + s.OutputBuilder.Msg("Installing: " + s.Desc) + return s.runStep(s.DoCmd) +} + +func (s *ShellStep) undo() error { + s.OutputBuilder.Msg("Uninstalling: " + s.Desc) + return s.runStep(s.UndoCmd) +} + +func (s *ShellStep) runStep(command string) error { + var stdOut bytes.Buffer + var stdErr bytes.Buffer + + const defaultShell = "bash" + + // TODO: check for exit(-1) or similar code + cmd := exec.Command(defaultShell, "-c", command) + s.OutputBuilder.Cmd(cmd.String()) + + if s.BundlePath == "" { + return nil + } + + cmd.Stdout = &stdOut + cmd.Stderr = &stdErr + err := cmd.Run() + + if len(stdErr.String()) > 0 { + /* + this is a non critical error + the installer is still running properly + but some package stderrored. + e.g.: + - swap is already off; + - apt tells us it is installing from a local pkg + and cannot confirm the repository + + do not return err! just log it. + otherwise it will cause execution of the rollback procedure + + we only return error if the shellExec + cannot be executed due to erroneous shell command, etc. + */ + s.OutputBuilder.Err(stdErr.String()) + } + + if err != nil { + s.OutputBuilder.Err(err.Error()) + return err + } + + if len(stdOut.String()) > 0 { + s.OutputBuilder.Out(stdOut.String()) + } + + return nil +} diff --git a/agent/main.go b/agent/main.go index abf430e14..80981781a 100644 --- a/agent/main.go +++ b/agent/main.go @@ -13,6 +13,7 @@ import ( "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/cloudinit" "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/reconciler" "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/registration" + "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version" infrastructurev1beta1 "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/apis/infrastructure/v1beta1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" @@ -77,6 +78,7 @@ var ( metricsbindaddress string downloadpath string skipInstallation bool + printVersion bool ) // TODO - fix logging @@ -87,7 +89,14 @@ func main() { flag.StringVar(&metricsbindaddress, "metricsbindaddress", ":8080", "metricsbindaddress is the TCP address that the controller should bind to for serving prometheus metrics.It can be set to \"0\" to disable the metrics serving") flag.StringVar(&downloadpath, "downloadpath", "/var/lib/byoh/bundles", "File System path to keep the downloads") flag.BoolVar(&skipInstallation, "skip-installation", false, "If you want to skip installation of the kubernetes component binaries") + flag.BoolVar(&printVersion, "version", false, "Print the version of the agent") flag.Parse() + + if printVersion { + info := version.Get() + fmt.Printf("byoh-hostagent version: %#v\n", info) + return + } scheme = runtime.NewScheme() _ = infrastructurev1beta1.AddToScheme(scheme) _ = corev1.AddToScheme(scheme) diff --git a/agent/reconciler/reconciler_test.go b/agent/reconciler/reconciler_test.go index f4b34f9dc..e0ddb6bc0 100644 --- a/agent/reconciler/reconciler_test.go +++ b/agent/reconciler/reconciler_test.go @@ -332,7 +332,7 @@ runCmd: patchHelper, err = patch.NewHelper(byoHost, k8sClient) Expect(err).ShouldNot(HaveOccurred()) - conditions.MarkFalse(byoHost, infrastructurev1beta1.K8sComponentsInstallationSucceeded, + conditions.MarkFalse(byoHost, infrastructurev1beta1.K8sComponentsInstallationSucceeded, infrastructurev1beta1.K8sComponentsInstallationFailedReason, clusterv1.ConditionSeverityInfo, "") Expect(patchHelper.Patch(ctx, byoHost, patch.WithStatusObservedGeneration{})).NotTo(HaveOccurred()) result, reconcilerErr := hostReconciler.Reconcile(ctx, controllerruntime.Request{ diff --git a/agent/version/version.go b/agent/version/version.go new file mode 100644 index 000000000..a9b1646d1 --- /dev/null +++ b/agent/version/version.go @@ -0,0 +1,67 @@ +// Copyright 2021 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package version + +import ( + "fmt" + "runtime" + "strings" +) + +var ( + Version string + BuildDate string +) + +const ( + Dev = "dev" + GitTagLength = 3 +) + +// Info exposes information about the version used for the current running code. +type Info struct { + Major string `json:"major,omitempty"` + Minor string `json:"minor,omitempty"` + Patch string `json:"patch,omitempty"` + BuildDate string `json:"BuildDate,omitempty"` + GoVersion string `json:"goVersion,omitempty"` + Platform string `json:"platform,omitempty"` + Compiler string `json:"compiler,omitempty"` +} + +// Get returns an Info object with all the information about the current running code. +func Get() Info { + var major, minor, patch string + extractVersion(&major, &minor, &patch) + return Info{ + Major: major, + Minor: minor, + Patch: patch, + BuildDate: BuildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } +} + +func extractVersion(major, minor, patch *string) { + if Version == Dev { + *major = Dev + return + } + + version := strings.Split(Version, ".") + if len(version) != GitTagLength { + return + } + + // The git tag is preceded by a 'v', eg. v1.2.3 + if len(version[0]) != 2 || version[0][0:1] != "v" { + return + } + + *major = version[0][1:2] + *minor = version[1] + *patch = version[2] +} diff --git a/agent/version/version_suite_test.go b/agent/version/version_suite_test.go new file mode 100644 index 000000000..433012284 --- /dev/null +++ b/agent/version/version_suite_test.go @@ -0,0 +1,16 @@ +// Copyright 2021 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package version_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestVersion(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Version Suite") +} diff --git a/agent/version/version_test.go b/agent/version/version_test.go new file mode 100644 index 000000000..7dcc3ff24 --- /dev/null +++ b/agent/version/version_test.go @@ -0,0 +1,130 @@ +// Copyright 2021 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package version_test + +import ( + "fmt" + "os/exec" + "runtime" + + "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Agent version", func() { + + Context("When the version number and date are not set", func() { + + It("Leaves the version and date fields empty in response", func() { + expected := version.Info{ + Major: "", + Minor: "", + Patch: "", + BuildDate: "", + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } + Expect(version.Get()).Should(Equal(expected)) + }) + }) + + Context("When only date is set", func() { + BeforeEach(func() { + date, err := exec.Command("date").Output() + Expect(err).NotTo(HaveOccurred()) + version.BuildDate = string(date) + }) + + AfterEach(func() { + version.BuildDate = "" + }) + + It("Leaves version field empty in response", func() { + expected := version.Info{ + Major: "", + Minor: "", + Patch: "", + BuildDate: version.BuildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } + Expect(version.Get()).Should(Equal(expected)) + }) + }) + + Context("When version is set", func() { + Context("When version is set to dev", func() { + BeforeEach(func() { + version.Version = version.Dev + }) + + AfterEach(func() { + version.Version = "" + }) + + It("Shows the version major to be dev", func() { + expected := version.Info{ + Major: version.Dev, + Minor: "", + Patch: "", + BuildDate: "", + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } + Expect(version.Get()).Should(Equal(expected)) + }) + }) + + Context("When version is set as git tag", func() { + BeforeEach(func() { + version.Version = "v1.2.3" + }) + + AfterEach(func() { + version.Version = "" + }) + + It("Shows the version according to the git tag passed", func() { + expected := version.Info{ + Major: "1", + Minor: "2", + Patch: "3", + BuildDate: "", + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } + Expect(version.Get()).Should(Equal(expected)) + }) + }) + + Context("When version is set as invalid git tag", func() { + BeforeEach(func() { + version.Version = "1.2.3" + }) + + AfterEach(func() { + version.Version = "" + }) + + It("Leaves the version fields empty", func() { + expected := version.Info{ + Major: "", + Minor: "", + Patch: "", + BuildDate: "", + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } + Expect(version.Get()).Should(Equal(expected)) + }) + }) + }) +})