diff --git a/Makefile b/Makefile index 0f3c0a0507..3658d177f3 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,8 @@ $(MYGOBIN)/pluginator: # Build from local source. $(MYGOBIN)/kustomize: build-kustomize-api cd kustomize; \ - go install . + go install -ldflags "-X sigs.k8s.io/kustomize/api/provenance.buildDate=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + . kustomize: $(MYGOBIN)/kustomize diff --git a/api/Makefile b/api/Makefile index 6a4ce1ed68..1c10c3ffa9 100644 --- a/api/Makefile +++ b/api/Makefile @@ -4,10 +4,10 @@ include ../Makefile-modules.mk test: - go test -v -timeout 45m -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" + go test -v -timeout 45m -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.buildDate=2023-01-31T23:38:41Z -X sigs.k8s.io/kustomize/api/provenance.version=(test)" build: - go build -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" ./... + go build -ldflags "-X sigs.k8s.io/kustomize/api/provenance.buildDate=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ")" ./... generate: $(MYGOBIN)/k8scopy $(MYGOBIN)/stringer go generate ./... diff --git a/api/krusty/managedbylabel_test.go b/api/krusty/managedbylabel_test.go index 8a566c0eea..f096e48234 100644 --- a/api/krusty/managedbylabel_test.go +++ b/api/krusty/managedbylabel_test.go @@ -13,14 +13,14 @@ const expected = `apiVersion: v1 kind: Service metadata: labels: - app.kubernetes.io/managed-by: kustomize-v444.333.222 + app.kubernetes.io/managed-by: kustomize-(test) name: myService spec: ports: - port: 7002 ` -// This test may failed when running on package tests using the go command because `v444.333.222` is set on makefile. +// This test may fail when running on package tests using the go command because `(test)` is set on makefile. func TestAddManagedbyLabel(t *testing.T) { tests := []struct { kustFile string diff --git a/api/provenance/provenance.go b/api/provenance/provenance.go index 4ad40395a1..0e24fc36ca 100644 --- a/api/provenance/provenance.go +++ b/api/provenance/provenance.go @@ -6,47 +6,63 @@ package provenance import ( "fmt" "runtime" + "runtime/debug" "strings" ) +// These variables are set at build time using ldflags. +// +//nolint:gochecknoglobals var ( - version = "unknown" - // sha1 from git, output of $(git rev-parse HEAD) - gitCommit = "$Format:%H$" + // During a release, this will be set to the release tag, e.g. "kustomize/v4.5.7" + version = developmentVersion // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') - buildDate = "1970-01-01T00:00:00Z" - goos = runtime.GOOS - goarch = runtime.GOARCH + buildDate = "unknown" ) +// This default value, (devel), matches +// the value debug.BuildInfo uses for an unset main module version. +const developmentVersion = "(devel)" + // Provenance holds information about the build of an executable. type Provenance struct { // Version of the kustomize binary. - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty" yaml:"version,omitempty"` // GitCommit is a git commit - GitCommit string `json:"gitCommit,omitempty"` + GitCommit string `json:"gitCommit,omitempty" yaml:"gitCommit,omitempty"` // BuildDate is date of the build. - BuildDate string `json:"buildDate,omitempty"` + BuildDate string `json:"buildDate,omitempty" yaml:"buildDate,omitempty"` // GoOs holds OS name. - GoOs string `json:"goOs,omitempty"` + GoOs string `json:"goOs,omitempty" yaml:"goOs,omitempty"` // GoArch holds architecture name. - GoArch string `json:"goArch,omitempty"` + GoArch string `json:"goArch,omitempty" yaml:"goArch,omitempty"` + // GoVersion holds Go version. + GoVersion string `json:"goVersion,omitempty" yaml:"goVersion,omitempty"` } // GetProvenance returns an instance of Provenance. func GetProvenance() Provenance { - return Provenance{ - version, - gitCommit, - buildDate, - goos, - goarch, + p := Provenance{ + BuildDate: buildDate, + Version: version, + GitCommit: "unknown", + GoOs: runtime.GOOS, + GoArch: runtime.GOARCH, + GoVersion: runtime.Version(), + } + info, ok := debug.ReadBuildInfo() + if !ok { + return p } -} -// Full returns the full provenance stamp. -func (v Provenance) Full() string { - return fmt.Sprintf("%+v", v) + for _, setting := range info.Settings { + // For now, the git commit is the only information of interest. + // We could consider adding other info such as the commit date in the future. + if setting.Key == "vcs.revision" { + p.GitCommit = setting.Value + } + } + return p } // Short returns the shortened provenance stamp. diff --git a/api/provenance/provenance_test.go b/api/provenance/provenance_test.go new file mode 100644 index 0000000000..9aede8d643 --- /dev/null +++ b/api/provenance/provenance_test.go @@ -0,0 +1,47 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package provenance_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/api/provenance" +) + +const expectedBuildDateFromLdFlag = "2023-01-31T23:38:41Z" +const expectedVersionFromLdFlag = "(test)" + +func TestGetProvenance(t *testing.T) { + p := provenance.GetProvenance() + // These are set by ldflags in our Makefile + assert.Equal(t, expectedVersionFromLdFlag, p.Version) + assert.Equal(t, expectedBuildDateFromLdFlag, p.BuildDate) + // This comes from BuildInfo, which is not set during go test: https://github.com/golang/go/issues/33976 + assert.Equal(t, "unknown", p.GitCommit) + + // These are set properly during go test + assert.NotEmpty(t, p.GoArch) + assert.NotEmpty(t, p.GoOs) + assert.Contains(t, p.GoVersion, "go1.") +} + +func TestProvenance_Short(t *testing.T) { + p := provenance.GetProvenance() + // The version not set during go test, so this comes from an ldflag: https://github.com/golang/go/issues/33976 + assert.Equal(t, fmt.Sprintf("{%s %s }", expectedVersionFromLdFlag, expectedBuildDateFromLdFlag), p.Short()) + + p.Version = "kustomize/v4.11.12" + assert.Equal(t, fmt.Sprintf("{kustomize/v4.11.12 %s }", expectedBuildDateFromLdFlag), p.Short()) +} + +func TestProvenance_Semver(t *testing.T) { + p := provenance.GetProvenance() + // The version not set during go test + assert.Equal(t, "(test)", p.Semver()) + + p.Version = "kustomize/v4.11.12" + assert.Equal(t, "v4.11.12", p.Semver()) +} diff --git a/kustomize.Dockerfile b/kustomize.Dockerfile index dad3777585..0a3df069b4 100644 --- a/kustomize.Dockerfile +++ b/kustomize.Dockerfile @@ -12,7 +12,6 @@ ADD . /build/ WORKDIR /build/kustomize RUN CGO_ENABLED=0 GO111MODULE=on go build \ -ldflags="-s -X sigs.k8s.io/kustomize/api/provenance.version=${VERSION} \ - -X sigs.k8s.io/kustomize/api/provenance.gitCommit=${COMMIT} \ -X sigs.k8s.io/kustomize/api/provenance.buildDate=${DATE}" # only copy binary diff --git a/kustomize/commands/version/version.go b/kustomize/commands/version/version.go index dac2aa8277..61cd921693 100644 --- a/kustomize/commands/version/version.go +++ b/kustomize/commands/version/version.go @@ -4,30 +4,83 @@ package version import ( + "encoding/json" "fmt" "io" + "os" "github.com/spf13/cobra" "sigs.k8s.io/kustomize/api/provenance" + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/yaml" ) +type Options struct { + Short bool + Output string + Writer io.Writer +} + // NewCmdVersion makes a new version command. func NewCmdVersion(w io.Writer) *cobra.Command { - var short bool - + o := NewOptions(w) versionCmd := cobra.Command{ Use: "version", Short: "Prints the kustomize version", Example: `kustomize version`, - Run: func(cmd *cobra.Command, args []string) { - if short { - fmt.Fprintln(w, provenance.GetProvenance().Short()) - } else { - fmt.Fprintln(w, provenance.GetProvenance().Full()) + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.Validate(args); err != nil { + return err + } + if err := o.Run(); err != nil { + return err } + return nil }, } - versionCmd.Flags().BoolVar(&short, "short", false, "short form") + versionCmd.Flags().BoolVar(&o.Short, "short", false, "short form") + _ = versionCmd.Flags().MarkDeprecated("short", "and will be removed in the future.") + versionCmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "One of 'yaml' or 'json'.") return &versionCmd } + +func NewOptions(w io.Writer) *Options { + if w == nil { + w = io.Writer(os.Stdout) + } + return &Options{Writer: w} +} + +func (o *Options) Validate(_ []string) error { + if o.Short { + if o.Output != "" { + return fmt.Errorf("--short and --output are mutually exclusive") + } + } + return nil +} + +func (o *Options) Run() error { + switch o.Output { + case "": + if o.Short { + fmt.Fprintln(o.Writer, provenance.GetProvenance().Short()) + } else { + fmt.Fprintln(o.Writer, provenance.GetProvenance().Semver()) + } + case "yaml": + marshalled, err := yaml.Marshal(provenance.GetProvenance()) + if err != nil { + return errors.WrapPrefixf(err, "marshalling provenance to yaml") + } + fmt.Fprintln(o.Writer, string(marshalled)) + case "json": + marshalled, err := json.MarshalIndent(provenance.GetProvenance(), "", " ") + if err != nil { + return errors.WrapPrefixf(err, "marshalling provenance to json") + } + fmt.Fprintln(o.Writer, string(marshalled)) + } + return nil +} diff --git a/releasing/run-goreleaser.sh b/releasing/run-goreleaser.sh index 6ebaae46eb..dc9354831a 100755 --- a/releasing/run-goreleaser.sh +++ b/releasing/run-goreleaser.sh @@ -101,7 +101,6 @@ builds: ldflags: > -s -X sigs.k8s.io/kustomize/api/provenance.version={{.Version}} - -X sigs.k8s.io/kustomize/api/provenance.gitCommit={{.Commit}} -X sigs.k8s.io/kustomize/api/provenance.buildDate={{.Date}} goos: