diff --git a/cmd/finch/version.go b/cmd/finch/version.go index 36ae3ce7a..1179f5499 100644 --- a/cmd/finch/version.go +++ b/cmd/finch/version.go @@ -4,19 +4,48 @@ package main import ( + "bytes" "encoding/json" "errors" "fmt" "io" + "text/template" "github.com/spf13/cobra" "github.com/runfinch/finch/pkg/command" "github.com/runfinch/finch/pkg/flog" "github.com/runfinch/finch/pkg/lima" + "github.com/runfinch/finch/pkg/templates" "github.com/runfinch/finch/pkg/version" ) +const defaultVersionTemplate = `{{with .Client -}} +Client: + Version: {{.Version}} + GitCommit: {{.GitCommit}} + {{with .NerdctlClient -}} + OS/Arch: {{.Os}}/{{.Arch}} + nerdctl: + Version: {{.Version}} + GitCommit: {{.GitCommit}} + {{- range $component := .Components}} + {{$component.Name}}: + Version: {{.Version}} + GitCommit: {{.Details.GitCommit}} + {{- end}} + {{- end}} +{{- end}} + +{{with .Server -}} +Server: +{{- range $component := .Components}} + {{$component.Name}}: + Version: {{.Version}} + GitCommit: {{.Details.GitCommit}} +{{- end}} +{{- end}}` + func newVersionCommand(limaCmdCreator command.LimaCmdCreator, logger flog.Logger, stdOut io.Writer) *cobra.Command { versionCommand := &cobra.Command{ Use: "version", @@ -25,6 +54,8 @@ func newVersionCommand(limaCmdCreator command.LimaCmdCreator, logger flog.Logger RunE: newVersionAction(limaCmdCreator, logger, stdOut).runAdapter, } + versionCommand.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + return versionCommand } @@ -40,6 +71,19 @@ type NerdctlVersionOutput struct { Server NerdctlServerOutput `json:"Server"` } +// FinchVersionOutput captures the finch version. +type FinchVersionOutput struct { + Client ClientVersionOutput `json:"Client"` + Server NerdctlServerOutput `json:"Server"` +} + +// ClientVersionOutput captures the commit ID for finch and the nerdctl Client output. +type ClientVersionOutput struct { + Version string `json:"Version"` + GitCommit string `json:"GitCommit"` + NerdctlClient NerdctlClientOutput `json:"NerdctlClient"` +} + // NerdctlClientOutput captures the nerdctl Client output. type NerdctlClientOutput struct { Version string `json:"Version"` @@ -50,11 +94,6 @@ type NerdctlClientOutput struct { Components []NerdctlComponentsOutput `json:"Components"` } -// NerdctlServerOutput captures the nerdctl Server output. -type NerdctlServerOutput struct { - Components []NerdctlComponentsOutput `json:"Components"` -} - // NerdctlComponentsOutput captures the nerdctl components output. type NerdctlComponentsOutput struct { Name string `json:"Name"` @@ -64,23 +103,75 @@ type NerdctlComponentsOutput struct { } } +// NerdctlServerOutput captures the nerdctl Server output. +type NerdctlServerOutput struct { + Components []NerdctlComponentsOutput `json:"Components"` +} + func newVersionAction(creator command.LimaCmdCreator, logger flog.Logger, stdOut io.Writer) *versionAction { return &versionAction{creator: creator, logger: logger, stdOut: stdOut} } -func (va *versionAction) runAdapter(_ *cobra.Command, _ []string) error { - return va.run() +func (va *versionAction) runAdapter(cmd *cobra.Command, _ []string) error { + format, err := cmd.Flags().GetString("format") + if err != nil { + return err + } + + return va.run(format) } -func (va *versionAction) run() error { - if err := va.printVersion(); err != nil { +func (va *versionAction) run(format string) error { + if err := va.printVersion(format); err != nil { fmt.Fprintf(va.stdOut, "Finch version:\t%s\n", version.Version) return err } return nil } -func (va *versionAction) printVersion() error { +func newVersionTemplate(templateFormat string) (*template.Template, error) { + switch templateFormat { + case "": + templateFormat = defaultVersionTemplate + case templates.JSONFormatKey: + templateFormat = templates.JSONFormat + } + + tmpl := templates.New("version") + tmpl, err := tmpl.Parse(templateFormat) + if err != nil { + return nil, err + } + + return tmpl, nil +} + +func createFinchVersionOutput(nerdctlVersion NerdctlVersionOutput) FinchVersionOutput { + var finchVersionOutput FinchVersionOutput + + finchVersionOutput.Client.Version = version.Version + finchVersionOutput.Client.GitCommit = version.GitCommit + finchVersionOutput.Client.NerdctlClient = nerdctlVersion.Client + finchVersionOutput.Server = nerdctlVersion.Server + + return finchVersionOutput +} + +func (va *versionAction) showVersionMessage(tmpl *template.Template, nerdctlVersion NerdctlVersionOutput) error { + var b bytes.Buffer + + finchVersionOutput := createFinchVersionOutput(nerdctlVersion) + if err := tmpl.Execute(&b, finchVersionOutput); err != nil { + return err + } + if _, err := fmt.Fprintln(va.stdOut, b.String()); err != nil { + return err + } + + return nil +} + +func (va *versionAction) printVersion(format string) error { status, err := lima.GetVMStatus(va.creator, va.logger, limaInstanceName) if err != nil { return fmt.Errorf("failed to get VM status: %w", err) @@ -102,24 +193,13 @@ func (va *versionAction) printVersion() error { return fmt.Errorf("failed to JSON-unmarshal the nerdctl version output: %w", err) } - fmt.Fprintf(va.stdOut, "Client:\n") - fmt.Fprintf(va.stdOut, " Version:\t%s\n", version.Version) - fmt.Fprintf(va.stdOut, " OS/Arch:\t%s/%s\n", nerdctlVersion.Client.Os, nerdctlVersion.Client.Arch) - fmt.Fprintf(va.stdOut, " GitCommit:\t%s\n", version.GitCommit) - fmt.Fprintf(va.stdOut, " nerdctl:\n") - fmt.Fprintf(va.stdOut, " Version:\t%s\n", nerdctlVersion.Client.Version) - fmt.Fprintf(va.stdOut, " GitCommit:\t%s\n", nerdctlVersion.Client.GitCommit) - for _, compo := range nerdctlVersion.Client.Components { - fmt.Fprintf(va.stdOut, " %s:\n", compo.Name) - fmt.Fprintf(va.stdOut, " Version:\t%s\n", compo.Version) - fmt.Fprintf(va.stdOut, " GitCommit:\t%s\n", compo.Details.GitCommit) + tmpl, err := newVersionTemplate(format) + if err != nil { + return err } - fmt.Fprintf(va.stdOut, "\n") - fmt.Fprintf(va.stdOut, "Server:\n") - for _, compo := range nerdctlVersion.Server.Components { - fmt.Fprintf(va.stdOut, " %s:\n", compo.Name) - fmt.Fprintf(va.stdOut, " Version:\t%s\n", compo.Version) - fmt.Fprintf(va.stdOut, " GitCommit:\t%s\n", compo.Details.GitCommit) + err = va.showVersionMessage(tmpl, nerdctlVersion) + if err != nil { + return err } return nil diff --git a/cmd/finch/version_test.go b/cmd/finch/version_test.go index e6f413d6f..428c30c61 100644 --- a/cmd/finch/version_test.go +++ b/cmd/finch/version_test.go @@ -19,6 +19,43 @@ import ( "github.com/stretchr/testify/require" ) +const nerdctlMockVersion = `{ + "Client":{ + "Version":"v1.0.0", + "GitCommit":"c00780a1f5b905b09812722459c54936c9e070e6", + "GoVersion":"go1.19.2", + "Os":"linux", + "Arch":"arm64", + "Components":[ + { + "Name":"buildctl", + "Version":"v0.10.5", + "Details":{ + "GitCommit":"bc26045116045516ff2427201abd299043eaf8f7" + } + } + ] + }, + "Server":{ + "Components":[ + { + "Name":"containerd", + "Version":"v1.6.8", + "Details":{ + "GitCommit":"9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6" + } + }, + { + "Name":"runc", + "Version":"1.1.4", + "Details":{ + "GitCommit":"v1.1.4-0-g5fd4c4d1" + } + } + ] + } +}` + func TestNewVersionCommand(t *testing.T) { t.Parallel() @@ -35,14 +72,19 @@ func TestVersionAction_runAdaptor(t *testing.T) { testCases := []struct { name string - cmd *cobra.Command + cmd func(t *testing.T) *cobra.Command args []string mockSvc func(*mocks.LimaCmdCreator, *mocks.Logger, *gomock.Controller) }{ { name: "happy path", - cmd: &cobra.Command{ - Use: "version", + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c }, args: []string{}, mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { @@ -51,6 +93,30 @@ func TestVersionAction_runAdaptor(t *testing.T) { getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + command := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", + "--format", "json").Return(command) + //nolint: lll // Version output format is larger than 500 + command.EXPECT().Output().Return([]byte(`{"Client":{"Version":"v1.0.0","GitCommit":"c00780a1f5b905b09812722459c54936c9e070e6","GoVersion":"go1.19.2","Os":"linux","Arch":"arm64","Components":[{"Name":"buildctl","Version":"v0.10.5","Details":{"GitCommit":"bc26045116045516ff2427201abd299043eaf8f7"}}]},"Server":{"Components":[{"Name":"containerd","Version":"v1.6.8","Details":{"GitCommit":"9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6"}},{"Name":"runc","Version":"1.1.4","Details":{"GitCommit":"v1.1.4-0-g5fd4c4d1"}}]}}`), nil) + }, + }, + { + name: "with --format json", + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c + }, + args: []string{"--format", "json"}, + mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + command := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", "--format", "json").Return(command) @@ -71,7 +137,7 @@ func TestVersionAction_runAdaptor(t *testing.T) { var stdOut bytes.Buffer tc.mockSvc(lcc, logger, ctrl) - assert.NoError(t, newVersionAction(lcc, logger, &stdOut).runAdapter(tc.cmd, tc.args)) + assert.NoError(t, newVersionAction(lcc, logger, &stdOut).runAdapter(tc.cmd(t), tc.args)) }) } } @@ -82,15 +148,21 @@ func TestVersionAction_run(t *testing.T) { testCases := []struct { name string wantErr error - cmd *cobra.Command + cmd func(t *testing.T) *cobra.Command mockSvc func(*mocks.LimaCmdCreator, *mocks.Logger, *gomock.Controller) postRunCheck func(t *testing.T, stdout []byte) + format string }{ { name: "print finch version with Client and Server", wantErr: nil, - cmd: &cobra.Command{ - Use: "version", + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c }, mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { getVMStatusC := mocks.NewCommand(ctrl) @@ -101,42 +173,7 @@ func TestVersionAction_run(t *testing.T) { command := mocks.NewCommand(ctrl) lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", "--format", "json").Return(command) - command.EXPECT().Output().Return([]byte(`{ - "Client":{ - "Version":"v1.0.0", - "GitCommit":"c00780a1f5b905b09812722459c54936c9e070e6", - "GoVersion":"go1.19.2", - "Os":"linux", - "Arch":"arm64", - "Components":[ - { - "Name":"buildctl", - "Version":"v0.10.5", - "Details":{ - "GitCommit":"bc26045116045516ff2427201abd299043eaf8f7" - } - } - ] - }, - "Server":{ - "Components":[ - { - "Name":"containerd", - "Version":"v1.6.8", - "Details":{ - "GitCommit":"9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6" - } - }, - { - "Name":"runc", - "Version":"1.1.4", - "Details":{ - "GitCommit":"v1.1.4-0-g5fd4c4d1" - } - } - ] - } - }`), nil) + command.EXPECT().Output().Return([]byte(nerdctlMockVersion), nil) }, postRunCheck: func(t *testing.T, stdout []byte) { lines := strings.SplitAfter(string(stdout), "\n") @@ -144,8 +181,8 @@ func TestVersionAction_run(t *testing.T) { assert.Equal(t, lines[0], "Client:\n") assert.Equal(t, lines[1], " Version:\t\n") - assert.Equal(t, lines[2], " OS/Arch:\tlinux/arm64\n") - assert.Equal(t, lines[3], " GitCommit:\t\n") + assert.Equal(t, lines[2], " GitCommit:\t\n") + assert.Equal(t, lines[3], " OS/Arch:\tlinux/arm64\n") assert.Equal(t, lines[4], " nerdctl:\n") assert.Equal(t, lines[5], " Version:\tv1.0.0\n") assert.Equal(t, lines[6], " GitCommit:\tc00780a1f5b905b09812722459c54936c9e070e6\n") @@ -162,12 +199,18 @@ func TestVersionAction_run(t *testing.T) { assert.Equal(t, lines[17], " GitCommit:\tv1.1.4-0-g5fd4c4d1\n") assert.Equal(t, lines[18], "") }, + format: "", }, { name: "print only finch version only while VM not running", wantErr: errors.New("detailed version info is unavailable because VM is not running"), - cmd: &cobra.Command{ - Use: "version", + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c }, mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { getVMStatusC := mocks.NewCommand(ctrl) @@ -178,6 +221,7 @@ func TestVersionAction_run(t *testing.T) { postRunCheck: func(t *testing.T, stdout []byte) { assert.Equal(t, string(stdout), "Finch version:\t\n") }, + format: "", }, { name: "print only finch version if VM getting error", @@ -185,8 +229,13 @@ func TestVersionAction_run(t *testing.T) { "failed to get VM status: %w", errors.New("get status error"), ), - cmd: &cobra.Command{ - Use: "version", + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c }, mockSvc: func(lcc *mocks.LimaCmdCreator, _ *mocks.Logger, ctrl *gomock.Controller) { getVMStatusC := mocks.NewCommand(ctrl) @@ -196,6 +245,146 @@ func TestVersionAction_run(t *testing.T) { postRunCheck: func(t *testing.T, stdout []byte) { assert.Equal(t, string(stdout), "Finch version:\t\n") }, + format: "", + }, + { + name: "with --format json", + wantErr: nil, + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c + }, + mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + + command := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", + "--format", "json").Return(command) + command.EXPECT().Output().Return([]byte(nerdctlMockVersion), nil) + }, + postRunCheck: func(t *testing.T, stdout []byte) { + //nolint: lll // Version output format is larger than 500 + assert.Equal(t, string(stdout), "{\"Client\":{\"Version\":\"\",\"GitCommit\":\"\",\"NerdctlClient\":{\"Version\":\"v1.0.0\",\"GitCommit\":\"c00780a1f5b905b09812722459c54936c9e070e6\",\"GoVersion\":\"go1.19.2\",\"Os\":\"linux\",\"Arch\":\"arm64\",\"Components\":[{\"Name\":\"buildctl\",\"Version\":\"v0.10.5\",\"Details\":{\"GitCommit\":\"bc26045116045516ff2427201abd299043eaf8f7\"}}]}},\"Server\":{\"Components\":[{\"Name\":\"containerd\",\"Version\":\"v1.6.8\",\"Details\":{\"GitCommit\":\"9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6\"}},{\"Name\":\"runc\",\"Version\":\"1.1.4\",\"Details\":{\"GitCommit\":\"v1.1.4-0-g5fd4c4d1\"}}]}}\n") + }, + format: "json", + }, + { + name: "with --format '{{json .}}'", + wantErr: nil, + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c + }, + mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + + command := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", + "--format", "json").Return(command) + command.EXPECT().Output().Return([]byte(nerdctlMockVersion), nil) + }, + postRunCheck: func(t *testing.T, stdout []byte) { + //nolint: lll // Version output format is larger than 500 + assert.Equal(t, string(stdout), "{\"Client\":{\"Version\":\"\",\"GitCommit\":\"\",\"NerdctlClient\":{\"Version\":\"v1.0.0\",\"GitCommit\":\"c00780a1f5b905b09812722459c54936c9e070e6\",\"GoVersion\":\"go1.19.2\",\"Os\":\"linux\",\"Arch\":\"arm64\",\"Components\":[{\"Name\":\"buildctl\",\"Version\":\"v0.10.5\",\"Details\":{\"GitCommit\":\"bc26045116045516ff2427201abd299043eaf8f7\"}}]}},\"Server\":{\"Components\":[{\"Name\":\"containerd\",\"Version\":\"v1.6.8\",\"Details\":{\"GitCommit\":\"9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6\"}},{\"Name\":\"runc\",\"Version\":\"1.1.4\",\"Details\":{\"GitCommit\":\"v1.1.4-0-g5fd4c4d1\"}}]}}\n") + }, + format: "{{json .}}", + }, + { + name: "with --format '{{.Client}'", + wantErr: nil, + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c + }, + mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + + command := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", + "--format", "json").Return(command) + command.EXPECT().Output().Return([]byte(nerdctlMockVersion), nil) + }, + postRunCheck: func(t *testing.T, stdout []byte) { + //nolint: lll // Version output format is larger than 150 + assert.Equal(t, string(stdout), "{ {v1.0.0 c00780a1f5b905b09812722459c54936c9e070e6 go1.19.2 linux arm64 [{buildctl v0.10.5 {bc26045116045516ff2427201abd299043eaf8f7}}]}}\n") + }, + format: "{{.Client}}", + }, + { + name: "with --format {{.Client.NerdctlClient.Version}}", + wantErr: nil, + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c + }, + mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + + command := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", + "--format", "json").Return(command) + command.EXPECT().Output().Return([]byte(nerdctlMockVersion), nil) + }, + postRunCheck: func(t *testing.T, stdout []byte) { + assert.Equal(t, string(stdout), "v1.0.0\n") + }, + format: "{{.Client.NerdctlClient.Version}}", + }, + { + name: "with --format {{.Server}}", + wantErr: nil, + cmd: func(_ *testing.T) *cobra.Command { + c := &cobra.Command{ + Use: "version", + } + c.Flags().StringP("format", "f", "", "Format the output using the given Go template, e.g, '{{json .}}'") + + return c + }, + mockSvc: func(lcc *mocks.LimaCmdCreator, logger *mocks.Logger, ctrl *gomock.Controller) { + getVMStatusC := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(getVMStatusC) + getVMStatusC.EXPECT().Output().Return([]byte("Running"), nil) + logger.EXPECT().Debugf("Status of virtual machine: %s", "Running") + + command := mocks.NewCommand(ctrl) + lcc.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "sudo", "-E", "nerdctl", "version", + "--format", "json").Return(command) + command.EXPECT().Output().Return([]byte(nerdctlMockVersion), nil) + }, + postRunCheck: func(t *testing.T, stdout []byte) { + //nolint: lll // Version output format is larger than 100 + assert.Equal(t, string(stdout), "{[{containerd v1.6.8 {9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6}} {runc 1.1.4 {v1.1.4-0-g5fd4c4d1}}]}\n") + }, + format: "{{.Server}}", }, } @@ -211,7 +400,7 @@ func TestVersionAction_run(t *testing.T) { tc.mockSvc(lcc, logger, ctrl) var stdOut bytes.Buffer - err := newVersionAction(lcc, logger, &stdOut).run() + err := newVersionAction(lcc, logger, &stdOut).run(tc.format) assert.Equal(t, tc.wantErr, err) tc.postRunCheck(t, stdOut.Bytes()) diff --git a/docs/cmd/finch_version.md b/docs/cmd/finch_version.md index 9f18cd88f..0d99ff6c7 100644 --- a/docs/cmd/finch_version.md +++ b/docs/cmd/finch_version.md @@ -9,5 +9,6 @@ Shows Finch version information ## Options ```text - -h, --help help for version + -f, --format string Format the output using the given Go template, e.g, '{{json .}}' + -h, --help help for version ``` diff --git a/e2e/vm/version_test.go b/e2e/vm/version_test.go index 77fbab9df..2582c930e 100644 --- a/e2e/vm/version_test.go +++ b/e2e/vm/version_test.go @@ -47,8 +47,8 @@ var testVersion = func(o *option.Option) { ginkgo.It("Should print finch version information", func() { tmpl, err := template.New("versionTemplate").Parse(`Client: Version: {{ .FinchVersion }} - OS\/Arch: [A-Za-z0-9]+\/[A-Za-z0-9]+ GitCommit: {{ .FinchCommit }} + OS\/Arch: [A-Za-z0-9]+\/[A-Za-z0-9]+ nerdctl: Version: {{ .NerdctlVersion }} GitCommit: [a-z0-9]{40} diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go new file mode 100644 index 000000000..8e6c8ea0d --- /dev/null +++ b/pkg/templates/templates.go @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package templates provides functionality for working with templates, +// including JSON formatting for output. +package templates + +import ( + "bytes" + "encoding/json" + "strings" + "text/template" +) + +// Key and format strings in the --format option. +const ( + JSONFormatKey = "json" + JSONFormat = "{{json .}}" +) + +var basicFunctions = template.FuncMap{ + "json": func(v any) string { + buf := &bytes.Buffer{} + + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + err := enc.Encode(v) + if err != nil { + panic(err) + } + + return strings.TrimSpace(buf.String()) + }, +} + +// New creates a new empty template with the provided tag and built-in template functions. +func New(tag string) *template.Template { + return template.New(tag).Funcs(basicFunctions) +} diff --git a/pkg/templates/templates_test.go b/pkg/templates/templates_test.go new file mode 100644 index 000000000..7a9ff5829 --- /dev/null +++ b/pkg/templates/templates_test.go @@ -0,0 +1,25 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package templates + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + t.Parallel() + + tag := "TestNew" + tmpl, err := New(tag).Parse(`{{json .Os}}`) + assert.Nil(t, err) + + var b bytes.Buffer + assert.Nil(t, tmpl.Execute(&b, map[string]string{"Os": "linux"})) + + want := "\"linux\"" + assert.Equal(t, want, b.String()) +}