Skip to content
Open
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ BIN_GOIMPORTS ?= "$(PWD)/bin/goimports"
# If the current commit does not have a semver tag, 'tip' is used, unless there
# is a TAG environment variable. Precedence is git tag, environment variable, 'tip'
HASH := $(shell git rev-parse --short HEAD 2>/dev/null)
DATE := $(shell git log -1 --format=%cI HEAD 2>/dev/null)
VTAG := $(shell git tag --points-at HEAD | head -1)
VTAG := $(shell [ -z $(VTAG) ] && echo $(ETAG) || echo $(VTAG))
VERS ?= $(shell git describe --tags --match 'v*')
KVER ?= $(shell git describe --tags --match 'knative-*')

LDFLAGS := -X knative.dev/func/pkg/version.Vers=$(VERS) -X knative.dev/func/pkg/version.Kver=$(KVER) -X knative.dev/func/pkg/version.Hash=$(HASH)
LDFLAGS := -X knative.dev/func/pkg/version.Vers=$(VERS) -X knative.dev/func/pkg/version.Kver=$(KVER) -X knative.dev/func/pkg/version.Hash=$(HASH) -X knative.dev/func/pkg/version.BuildDate=$(DATE)

FUNC_UTILS_IMG ?= ghcr.io/knative/func-utils:v2
LDFLAGS += -X knative.dev/func/pkg/k8s.SocatImage=$(FUNC_UTILS_IMG)
Expand Down
239 changes: 196 additions & 43 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,54 +161,207 @@ func TestRoot_CommandNameParameterized(t *testing.T) {
}

func TestVerbose(t *testing.T) {
tests := []struct {
name string
args []string
want string
wantLF int
}{
{
name: "verbose as version's flag",
args: []string{"version", "-v"},
want: "Version: v0.42.0",
wantLF: 24,
},
{
name: "no verbose",
args: []string{"version"},
want: "v0.42.0",
wantLF: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
viper.Reset()

var out bytes.Buffer

cmd := NewRootCmd(RootCommandConfig{
Name: "func",
Version: Version{
Vers: "v0.42.0",
Hash: "cafe",
Kver: "v1.10.0",
}})

cmd.SetArgs(tt.args)
cmd.SetOut(&out)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
t.Run("no verbose", func(t *testing.T) {
viper.Reset()
var out bytes.Buffer
cmd := NewRootCmd(RootCommandConfig{
Name: "func",
Version: Version{Vers: "v0.42.0"},
})
cmd.SetArgs([]string{"version"})
cmd.SetOut(&out)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
if got := strings.TrimRight(out.String(), "\n"); got != "v0.42.0" {
t.Errorf("expected %q but got %q", "v0.42.0", got)
}
})

t.Run("verbose includes populated fields", func(t *testing.T) {
viper.Reset()
var out bytes.Buffer
cmd := NewRootCmd(RootCommandConfig{
Name: "func",
Version: Version{
Vers: "v0.42.0",
Hash: "cafe",
Kver: "v1.10.0",
BuildDate: "2024-01-01T00:00:00Z",
},
})
cmd.SetArgs([]string{"version", "-v"})
cmd.SetOut(&out)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
output := out.String()
for _, want := range []string{
"Version: v0.42.0",
"Knative: v1.10.0",
"Commit: cafe",
"BuildDate: 2024-01-01T00:00:00Z",
} {
if !strings.Contains(output, want) {
t.Errorf("expected output to contain %q but got:\n%s", want, output)
}
}
})

outLines := strings.Split(out.String(), "\n")
if len(outLines)-1 != tt.wantLF {
t.Errorf("expected output with %v line breaks but got %v:", tt.wantLF, len(outLines)-1)
t.Run("verbose omits empty fields", func(t *testing.T) {
viper.Reset()
var out bytes.Buffer
cmd := NewRootCmd(RootCommandConfig{
Name: "func",
// All fields except Vers are intentionally left empty.
Version: Version{Vers: "v0.42.0"},
})
cmd.SetArgs([]string{"version", "-v"})
cmd.SetOut(&out)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
output := out.String()
for _, absent := range []string{"Knative:", "Commit:", "BuildDate:"} {
if strings.Contains(output, absent) {
t.Errorf("expected output to omit %q but got:\n%s", absent, output)
}
if outLines[0] != tt.want {
t.Errorf("expected output: %q but got: %q", tt.want, outLines[0])
}
if !strings.HasPrefix(output, "Version: v0.42.0\n") {
t.Errorf("expected output to start with %q but got:\n%s", "Version: v0.42.0\n", output)
}
})

// kver prefix stripped verifies that a Knative version tag with the
// "knative-" prefix is stripped correctly for an exact release tag.
t.Run("kver prefix stripped exact tag", func(t *testing.T) {
v := Version{Vers: "v0.42.0", Kver: "knative-v1.10.0"}
output := v.StringVerbose()
if !strings.Contains(output, "Knative: v1.10.0") {
t.Errorf("expected 'knative-' prefix stripped for exact tag, got:\n%s", output)
}
})

// kver prefix stripped also verifies commit-distance suffixes are preserved
// (e.g. knative-v1.10.0-5-gabcdef1 → v1.10.0-5-gabcdef1).
t.Run("kver prefix stripped with commit distance", func(t *testing.T) {
v := Version{Vers: "v0.42.0", Kver: "knative-v1.10.0-5-gabcdef1"}
output := v.StringVerbose()
if !strings.Contains(output, "Knative: v1.10.0-5-gabcdef1") {
t.Errorf("expected 'knative-' prefix stripped with commit distance preserved, got:\n%s", output)
}
})

t.Run("output json", func(t *testing.T) {
viper.Reset()
var out bytes.Buffer
cmd := NewRootCmd(RootCommandConfig{
Name: "func",
Version: Version{
Vers: "v0.42.0",
BuildDate: "2024-01-01T00:00:00Z",
},
})
cmd.SetArgs([]string{"version", "--output", "json"})
cmd.SetOut(&out)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
output := out.String()
for _, want := range []string{
`"version": "v0.42.0"`,
`"buildDate": "2024-01-01T00:00:00Z"`,
} {
if !strings.Contains(output, want) {
t.Errorf("expected JSON to contain %q, got:\n%s", want, output)
}
}
})

t.Run("output yaml", func(t *testing.T) {
viper.Reset()
var out bytes.Buffer
cmd := NewRootCmd(RootCommandConfig{
Name: "func",
Version: Version{Vers: "v0.42.0"},
})
}
cmd.SetArgs([]string{"version", "--output", "yaml"})
cmd.SetOut(&out)
if err := cmd.Execute(); err != nil {
t.Fatal(err)
}
if !strings.Contains(out.String(), "version: v0.42.0") {
t.Errorf("expected YAML to contain version field, got:\n%s", out.String())
}
})

t.Run("url unsupported", func(t *testing.T) {
v := Version{Vers: "v0.42.0"}
var buf bytes.Buffer
if err := v.URL(&buf); err == nil {
t.Error("expected URL format to return an error, got nil")
}
})

// exact output guards against extra blank lines or whitespace being
// reintroduced between populated fields.
t.Run("verbose exact output", func(t *testing.T) {
v := Version{
Vers: "v0.42.0",
Kver: "knative-v1.10.0",
Hash: "cafe",
BuildDate: "2024-01-01T00:00:00Z",
SocatImage: "ghcr.io/knative/func-utils:v2",
TarImage: "ghcr.io/knative/func-utils:v2",
}
want := "Version: v0.42.0\n" +
"Knative: v1.10.0\n" +
"Commit: cafe\n" +
"BuildDate: 2024-01-01T00:00:00Z\n" +
"SocatImage: ghcr.io/knative/func-utils:v2\n" +
"TarImage: ghcr.io/knative/func-utils:v2\n"
if got := v.StringVerbose(); got != want {
t.Errorf("unexpected verbose output:\nwant:\n%s\ngot:\n%s", want, got)
}
})

t.Run("middleware versions omitted when empty", func(t *testing.T) {
v := Version{
Vers: "v0.42.0",
MiddlewareVersions: MiddlewareVersions{},
}
output := v.StringVerbose()
if strings.Contains(output, "Middleware Versions:") {
t.Errorf("expected empty MiddlewareVersions to be omitted, got:\n%s", output)
}
})

t.Run("socat and tar images omitted when empty", func(t *testing.T) {
v := Version{Vers: "v0.42.0"}
output := v.StringVerbose()
for _, absent := range []string{"SocatImage:", "TarImage:"} {
if strings.Contains(output, absent) {
t.Errorf("expected %q to be omitted when empty, got:\n%s", absent, output)
}
}
})

t.Run("socat and tar images present when populated", func(t *testing.T) {
v := Version{
Vers: "v0.42.0",
SocatImage: "ghcr.io/knative/func-utils:v2",
TarImage: "ghcr.io/knative/func-utils:v2",
}
output := v.StringVerbose()
for _, want := range []string{
"SocatImage: ghcr.io/knative/func-utils:v2",
"TarImage: ghcr.io/knative/func-utils:v2",
} {
if !strings.Contains(output, want) {
t.Errorf("expected output to contain %q, got:\n%s", want, output)
}
}
})
}

// TestRoot_effectivePath ensures that the path method returns the effective path
Expand Down
46 changes: 25 additions & 21 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ func runVersion(cmd *cobra.Command, v Version) error {
v.Vers = DefaultVersion
}

// Kver, Hash are already set from build
// Kver, Hash, BuildDate are already set from build via ldflags,
// injected into the Version struct at startup (see pkg/app/app.go).
// Populate image fields from k8s package constants
v.SocatImage = k8s.SocatImage
v.TarImage = k8s.TarImage
Expand All @@ -110,6 +111,8 @@ type Version struct {
Kver string `json:"knative,omitempty" yaml:"knative,omitempty"`
// Hash of the currently active git commit on build.
Hash string `json:"commit,omitempty" yaml:"commit,omitempty"`
// BuildDate is the UTC timestamp at which the binary was built.
BuildDate string `json:"buildDate,omitempty" yaml:"buildDate,omitempty"`
// SocatImage is the socat image used by the function.
SocatImage string `json:"socatImage,omitempty" yaml:"socatImage,omitempty"`
// TarImage is the tar image used by the function.
Expand All @@ -132,28 +135,29 @@ func (v Version) String() string {
}

// StringVerbose returns the version along with extended version metadata.
// Fields with empty values are omitted.
func (v Version) StringVerbose() string {
var (
vers = v.Vers
kver = v.Kver
hash = v.Hash
)
if strings.HasPrefix(kver, "knative-") {
kver = strings.Split(kver, "-")[1]
var sb strings.Builder
sb.WriteString("Version: " + v.Vers + "\n")
if v.Kver != "" {
sb.WriteString("Knative: " + strings.TrimPrefix(v.Kver, "knative-") + "\n")
}
return fmt.Sprintf(
"Version: %s\n"+
"Knative: %s\n"+
"Commit: %s\n"+
"SocatImage: %s\n"+
"TarImage: %s\n"+
"Middleware Versions: \n%s",
vers,
kver,
hash,
v.SocatImage,
v.TarImage,
v.MiddlewareVersions)
if v.Hash != "" {
sb.WriteString("Commit: " + v.Hash + "\n")
}
if v.BuildDate != "" {
sb.WriteString("BuildDate: " + v.BuildDate + "\n")
}
if v.SocatImage != "" {
sb.WriteString("SocatImage: " + v.SocatImage + "\n")
}
if v.TarImage != "" {
sb.WriteString("TarImage: " + v.TarImage + "\n")
}
if mw := v.MiddlewareVersions.String(); mw != "" {
sb.WriteString("Middleware Versions:\n" + mw)
}
return sb.String()
}

// Human prints version information in human-readable format.
Expand Down
7 changes: 4 additions & 3 deletions pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ func Main() {
cfg := cmd.RootCommandConfig{
Name: "func",
Version: cmd.Version{
Vers: version.Vers,
Kver: version.Kver,
Hash: version.Hash,
Vers: version.Vers,
Kver: version.Kver,
Hash: version.Hash,
BuildDate: version.BuildDate,
}}

if err := cmd.NewRootCmd(cfg).ExecuteContext(ctx); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/version/version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package version

var Vers, Kver, Hash string
var Vers, Kver, Hash, BuildDate string
Loading