diff --git a/cmd/cmd.go b/cmd/cmd.go index d8d2d9b308a..c9360384448 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -56,7 +56,7 @@ func Run() { c, err := cli.New( cli.WithCommandName("kubebuilder"), cli.WithVersion(versionString()), - cli.WithCliVersion(getKubebuilderVersion()), + cli.WithCliVersion(getKubeBuilderVersion()), cli.WithPlugins( golangv4.Plugin{}, gov4Bundle, diff --git a/cmd/version.go b/cmd/version.go index a84c3ea576b..f7642850ea7 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -17,58 +17,91 @@ limitations under the License. package cmd import ( + "encoding/json" "fmt" + "runtime" "runtime/debug" ) const unknown = "unknown" -// var needs to be used instead of const as ldflags is used to fill this -// information in the release process +// These are filled via ldflags during build var ( kubeBuilderVersion = unknown kubernetesVendorVersion = "1.33.0" goos = unknown goarch = unknown - gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) - - buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') + gitCommit = "$Format:%H$" + buildDate = "1970-01-01T00:00:00Z" ) -// version contains all the information related to the CLI version -type version struct { +// VersionInfo holds all CLI version-related information +type VersionInfo struct { KubeBuilderVersion string `json:"kubeBuilderVersion"` KubernetesVendor string `json:"kubernetesVendor"` GitCommit string `json:"gitCommit"` BuildDate string `json:"buildDate"` - GoOs string `json:"goOs"` + GoOS string `json:"goOs"` GoArch string `json:"goArch"` } -// versionString returns the Full CLI version -func versionString() string { +// resolveBuildInfo ensures dynamic fields are populated +func resolveBuildInfo() { if kubeBuilderVersion == unknown { if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" { kubeBuilderVersion = info.Main.Version } } - - return fmt.Sprintf("Version: %#v", version{ - kubeBuilderVersion, - kubernetesVendorVersion, - gitCommit, - buildDate, - goos, - goarch, - }) + if goos == unknown { + goos = runtime.GOOS + } + if goarch == unknown { + goarch = runtime.GOARCH + } + if gitCommit == "$Format:%H$" || gitCommit == "" { + gitCommit = unknown + } } -// getKubebuilderVersion returns only the CLI version string -func getKubebuilderVersion() string { - if kubeBuilderVersion == unknown { - if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" { - kubeBuilderVersion = info.Main.Version - } +// getVersionInfo returns populated VersionInfo +func getVersionInfo() VersionInfo { + resolveBuildInfo() + return VersionInfo{ + KubeBuilderVersion: kubeBuilderVersion, + KubernetesVendor: kubernetesVendorVersion, + GitCommit: gitCommit, + BuildDate: buildDate, + GoOS: goos, + GoArch: goarch, } +} + +// versionString returns a human-friendly string version +func versionString() string { + v := getVersionInfo() + return fmt.Sprintf(`KubeBuilder Version: %s +Kubernetes Vendor: %s +Git Commit: %s +Build Date: %s +Go OS/Arch: %s/%s`, + v.KubeBuilderVersion, + v.KubernetesVendor, + v.GitCommit, + v.BuildDate, + v.GoOS, + v.GoArch, + ) +} + +// getKubeBuilderVersion returns just the CLI version +func getKubeBuilderVersion() string { + resolveBuildInfo() return kubeBuilderVersion } + +// versionJSON returns version as JSON string +func versionJSON() string { + v := getVersionInfo() + b, _ := json.MarshalIndent(v, "", " ") + return string(b) +} diff --git a/cmd/version_test.go b/cmd/version_test.go new file mode 100644 index 00000000000..6a93990e857 --- /dev/null +++ b/cmd/version_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 cmd + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVersionStringIncludesExpectedFields(t *testing.T) { + output := versionString() + + assert.Contains(t, output, "KubeBuilder Version:") + assert.Contains(t, output, "Kubernetes Vendor:") + assert.Contains(t, output, "Git Commit:") + assert.Contains(t, output, "Build Date:") + assert.Contains(t, output, "Go OS/Arch:") +} + +func TestVersionJSONFormatAndKeys(t *testing.T) { + jsonStr := versionJSON() + + var result map[string]string + err := json.Unmarshal([]byte(jsonStr), &result) + assert.NoError(t, err) + + assert.Contains(t, result, "kubeBuilderVersion") + assert.Contains(t, result, "kubernetesVendor") + assert.Contains(t, result, "gitCommit") + assert.Contains(t, result, "buildDate") + assert.Contains(t, result, "goOs") + assert.Contains(t, result, "goArch") +} + +func TestGetVersionInfoFieldsArePopulated(t *testing.T) { + v := getVersionInfo() + + assert.NotEmpty(t, v.KubeBuilderVersion) + assert.NotEmpty(t, v.KubernetesVendor) + assert.NotEmpty(t, v.GitCommit) + assert.NotEmpty(t, v.BuildDate) + assert.NotEmpty(t, v.GoOS) + assert.NotEmpty(t, v.GoArch) + + assert.NotEqual(t, "unknown", v.KubeBuilderVersion, "KubeBuilderVersion should not be 'unknown'") + assert.NotEqual(t, "unknown", v.GoOS, "GoOS should not be 'unknown'") + assert.NotEqual(t, "unknown", v.GoArch, "GoArch should not be 'unknown'") + assert.NotEqual(t, "$Format:%H$", v.GitCommit, "GitCommit should not be default placeholder") +} diff --git a/go.mod b/go.mod index c6b610bc9a4..f5b42687651 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/spf13/afero v1.14.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.7 + github.com/stretchr/testify v1.10.0 golang.org/x/mod v0.26.0 golang.org/x/text v0.27.0 golang.org/x/tools v0.35.0 @@ -19,6 +20,7 @@ require ( require ( github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -26,6 +28,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect