diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 004a47be6..7d69544e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: turn off swap run: sudo swapoff -a diff --git a/.github/workflows/draft-release.yaml b/.github/workflows/draft-release.yaml index 3a157e15e..4f4b7da45 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 }}" AGENT_BINARY_VERSION="${{ github.ref_name }}" make build-release-artifacts + run: IMG="projects.registry.vmware.com/cluster_api_provider_bringyourownhost/cluster-api-byoh-controller:${{ github.ref_name }}" make build-release-artifacts - name: Publish Release uses: softprops/action-gh-release@v1 diff --git a/Makefile b/Makefile index ce4ab3b4b..f8b2b1f3c 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,8 @@ GINKGO := $(TOOLS_BIN_DIR)/ginkgo BYOH_TEMPLATES := $(REPO_ROOT)/test/e2e/data/infrastructure-provider-byoh -LDFLAGS=-w -s +LDFLAGS := -w -s $(shell hack/version.sh) 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)) @@ -201,9 +200,7 @@ 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) \ - -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\")'" \ + RELEASE_BINARY=./byoh-hostagent GOOS=linux GOARCH=amd64 GOLDFLAGS="$(LDFLAGS) $(STATIC)" \ HOST_AGENT_DIR=./$(HOST_AGENT_DIR) $(MAKE) host-agent-binary host-agent-binary: $(RELEASE_DIR) diff --git a/agent/host_agent_test.go b/agent/host_agent_test.go index 797b7c993..01f9e2f10 100644 --- a/agent/host_agent_test.go +++ b/agent/host_agent_test.go @@ -337,29 +337,47 @@ var _ = Describe("Agent", func() { BeforeEach(func() { date, err := exec.Command("date").Output() Expect(err).NotTo(HaveOccurred()) + + version.GitMajor = "1" + version.GitMinor = "2" + version.GitVersion = "v1.2.3" + version.GitCommit = "abc" + version.GitTreeState = "clean" 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) + + ldflags := fmt.Sprintf("-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.GitMajor=%s'"+ + "-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.GitMinor=%s'"+ + "-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.GitVersion=%s'"+ + "-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.GitCommit=%s'"+ + "-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.GitTreeState=%s'"+ + "-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.BuildDate=%s'", + version.GitMajor, version.GitMinor, version.GitVersion, version.GitCommit, version.GitTreeState, version.BuildDate) + tmpHostAgentBinary, err = gexec.Build("github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent", "-ldflags", ldflags) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { + version.GitMajor = "" + version.GitMinor = "" + version.GitVersion = "" + version.GitCommit = "" + version.GitTreeState = "" 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), + Major: "1", + Minor: "2", + GitVersion: "v1.2.3", + GitCommit: "abc", + GitTreeState: "clean", + 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() @@ -369,6 +387,57 @@ var _ = Describe("Agent", func() { }) }) + Context("When --version flag is created using 'version.sh' script", func() { + var ( + tmpHostAgentBinary string + gitMajor string + gitMinor string + gitVersion string + err error + ) + BeforeEach(func() { + command := exec.Command("/bin/sh", "-c", "git describe --tags --abbrev=14 --match 'v[0-9]*' 2>/dev/null") + command.Stderr = os.Stderr + cmdOut, _ := command.Output() + gitVersion = strings.TrimSuffix(string(cmdOut), "\n") + + gitVersion = strings.Split(gitVersion, "-")[0] + gitVars := strings.Split(gitVersion, ".") + if len(gitVars) > 1 { + gitMajor = gitVars[0][1:] + gitMinor = gitVars[1] + } + + root, _ := exec.Command("/bin/sh", "-c", "git rev-parse --show-toplevel").Output() + cmd := exec.Command("/bin/sh", "-c", strings.TrimSuffix(string(root), "\n")+"/hack/version.sh") + ldflags, _ := cmd.Output() + tmpHostAgentBinary, err = gexec.Build("github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent", "-ldflags", string(ldflags)) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + tmpHostAgentBinary = "" + gitMajor = "" + gitMinor = "" + gitVersion = "" + }) + + It("should match local generated git values", func() { + out, err := exec.Command(tmpHostAgentBinary, "--version").Output() + Expect(err).NotTo(HaveOccurred()) + + majorExpected := "Major:\"" + gitMajor + "\"" + Expect(out).Should(ContainSubstring(majorExpected)) + + minorExpected := "Minor:\"" + gitMinor + "\"" + Expect(out).Should(ContainSubstring(minorExpected)) + + gitVersionExpected := "GitVersion:\"" + gitVersion + Expect(out).Should(ContainSubstring(gitVersionExpected)) + + }) + }) + Context("When the host agent is executed with --skip-installation flag", func() { var ( ns *corev1.Namespace diff --git a/agent/version/version.go b/agent/version/version.go index 71612fd4f..2ca55163c 100644 --- a/agent/version/version.go +++ b/agent/version/version.go @@ -6,65 +6,41 @@ package version import ( "fmt" "runtime" - "strings" ) var ( - // Version is the version of the agent. - Version string - // BuildDate is the date the agent was built. - BuildDate string -) - -const ( - // Dev development version string - Dev = "dev" - gitTagLength = 3 + GitMajor string // major version, always numeric + GitMinor string // minor version, numeric possibly followed by "+" + GitVersion string // semantic version, derived by build scripts + GitCommit string // sha1 from git, output of $(git rev-parse HEAD) + GitTreeState string // state of git tree, either "clean" or "dirty" + BuildDate string // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) // 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"` + Major string `json:"major,omitempty"` + Minor string `json:"minor,omitempty"` + GitVersion string `json:"gitVersion,omitempty"` + GitCommit string `json:"gitCommit,omitempty"` + GitTreeState string `json:"gitTreeState,omitempty"` + BuildDate string `json:"buildDate,omitempty"` + GoVersion string `json:"goVersion,omitempty"` + Compiler string `json:"compiler,omitempty"` + Platform string `json:"platform,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 + Major: GitMajor, + Minor: GitMinor, + GitVersion: GitVersion, + GitCommit: GitCommit, + GitTreeState: GitTreeState, + BuildDate: BuildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } - - 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 deleted file mode 100644 index 433012284..000000000 --- a/agent/version/version_suite_test.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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 deleted file mode 100644 index 7dcc3ff24..000000000 --- a/agent/version/version_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// 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)) - }) - }) - }) -}) diff --git a/hack/version.sh b/hack/version.sh new file mode 100755 index 000000000..67d1c1b52 --- /dev/null +++ b/hack/version.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +version::get_version_vars() { + # shellcheck disable=SC1083 + GIT_COMMIT="$(git rev-parse HEAD^{commit})" + + if git_status=$(git status --porcelain 2>/dev/null) && [[ -z ${git_status} ]]; then + GIT_TREE_STATE="clean" + else + GIT_TREE_STATE="dirty" + fi + + # stolen from sigs.k8s.io/cluster-api/hack/version.sh + # Use git describe to find the version based on annotated tags. + # TODO: is '--tags' flag required? + if [[ -n ${GIT_VERSION-} ]] || GIT_VERSION=$(git describe --tags --abbrev=14 --match "v[0-9]*" 2>/dev/null); then + # This translates the "git describe" to an actual semver.org + # compatible semantic version that looks something like this: + # v1.1.0-alpha.0.6+84c76d1142ea4d + # + # TODO: We continue calling this "git version" because so many + # downstream consumers are expecting it there. + # shellcheck disable=SC2001 + DASHES_IN_VERSION=$(echo "${GIT_VERSION}" | sed "s/[^-]//g") + if [[ "${DASHES_IN_VERSION}" == "---" ]] ; then + # We have distance to subversion (v1.1.0-subversion-1-gCommitHash) + # shellcheck disable=SC2001 + GIT_VERSION=$(echo "${GIT_VERSION}" | sed "s/-\([0-9]\{1,\}\)-g\([0-9a-f]\{14\}\)$/.\1\-\2/") + elif [[ "${DASHES_IN_VERSION}" == "--" ]] ; then + # We have distance to base tag (v1.1.0-1-gCommitHash) + # shellcheck disable=SC2001 + GIT_VERSION=$(echo "${GIT_VERSION}" | sed "s/-g\([0-9a-f]\{14\}\)$/-\1/") + fi + if [[ "${GIT_TREE_STATE}" == "dirty" ]]; then + # git describe --dirty only considers changes to existing files, but + # that is problematic since new untracked .go files affect the build, + # so use our idea of "dirty" from git status instead. + GIT_VERSION+="-dirty" + fi + + + # Try to match the "git describe" output to a regex to try to extract + # the "major" and "minor" versions and whether this is the exact tagged + # version or whether the tree is between two tagged versions. + if [[ "${GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?([-].*)?([+].*)?$ ]]; then + GIT_MAJOR=${BASH_REMATCH[1]} + GIT_MINOR=${BASH_REMATCH[2]} + fi + + # If GIT_VERSION is not a valid Semantic Version, then refuse to build. + if ! [[ "${GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then + echo "GIT_VERSION should be a valid Semantic Version. Current value: ${GIT_VERSION}" + echo "Please see more details here: https://semver.org" + exit 1 + fi + fi + + GIT_RELEASE_TAG=$(git describe --abbrev=0 --tags) +} + +# stolen from sigs.k8s.io/cluster-api/hack/version.sh and modified +# Prints the value that needs to be passed to the -ldflags parameter of go build +version::ldflags() { + version::get_version_vars + + local -a ldflags + function add_ldflag() { + local key=${1} + local val=${2} + ldflags+=( + "-X 'github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/agent/version.${key}=${val}'" + ) + } + + add_ldflag "GitMajor" "${GIT_MAJOR}" + add_ldflag "GitMinor" "${GIT_MINOR}" + add_ldflag "GitVersion" "${GIT_VERSION}" + add_ldflag "GitCommit" "${GIT_COMMIT}" + add_ldflag "GitTreeState" "${GIT_TREE_STATE}" + add_ldflag "BuildDate" "$(date ${SOURCE_DATE_EPOCH:+"--date=@${SOURCE_DATE_EPOCH}"} -u +'%Y-%m-%dT%H:%M:%SZ')" + + # The -ldflags parameter takes a single string, so join the output. + echo "${ldflags[*]-}" +} + +version::ldflags \ No newline at end of file