From 0d2301544ee2f749eec1aa438c8b6a596216f47e Mon Sep 17 00:00:00 2001 From: Swagat Bora Date: Thu, 3 Oct 2024 19:48:25 +0000 Subject: [PATCH] feat: add finch version output to support-bundle Signed-off-by: Swagat Bora --- cmd/finch/main_native.go | 1 + cmd/finch/main_remote.go | 1 + pkg/mocks/pkg_support.go | 52 +++++++++++ pkg/support/support.go | 75 +++++++++++++--- pkg/support/support_test.go | 168 ++++++++++++++++++++++++++++++++---- 5 files changed, 267 insertions(+), 30 deletions(-) create mode 100644 pkg/mocks/pkg_support.go diff --git a/cmd/finch/main_native.go b/cmd/finch/main_native.go index 9c8443f12..91c289e47 100644 --- a/cmd/finch/main_native.go +++ b/cmd/finch/main_native.go @@ -104,6 +104,7 @@ var newApp = func( ecc, ncc, lima, + system.NewStdLib(), ) // append nerdctl commands diff --git a/cmd/finch/main_remote.go b/cmd/finch/main_remote.go index 2f42e318f..4e23400bb 100644 --- a/cmd/finch/main_remote.go +++ b/cmd/finch/main_remote.go @@ -114,6 +114,7 @@ var newApp = func( ecc, ncc, lima, + system.NewStdLib(), ) // append nerdctl commands diff --git a/pkg/mocks/pkg_support.go b/pkg/mocks/pkg_support.go new file mode 100644 index 000000000..495a7d3ee --- /dev/null +++ b/pkg/mocks/pkg_support.go @@ -0,0 +1,52 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/runfinch/finch/pkg/support (interfaces: SystemDeps) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// SupportSystemDeps is a mock of SystemDeps interface. +type SupportSystemDeps struct { + ctrl *gomock.Controller + recorder *SupportSystemDepsMockRecorder +} + +// SupportSystemDepsMockRecorder is the mock recorder for SupportSystemDeps. +type SupportSystemDepsMockRecorder struct { + mock *SupportSystemDeps +} + +// NewSupportSystemDeps creates a new mock instance. +func NewSupportSystemDeps(ctrl *gomock.Controller) *SupportSystemDeps { + mock := &SupportSystemDeps{ctrl: ctrl} + mock.recorder = &SupportSystemDepsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *SupportSystemDeps) EXPECT() *SupportSystemDepsMockRecorder { + return m.recorder +} + +// Executable mocks base method. +func (m *SupportSystemDeps) Executable() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Executable") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Executable indicates an expected call of Executable. +func (mr *SupportSystemDepsMockRecorder) Executable() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Executable", reflect.TypeOf((*SupportSystemDeps)(nil).Executable)) +} diff --git a/pkg/support/support.go b/pkg/support/support.go index 4219dd79c..c98378867 100644 --- a/pkg/support/support.go +++ b/pkg/support/support.go @@ -31,6 +31,7 @@ import ( const ( bundlePrefix = "finch-support" platformFileName = "platform.yaml" + versionFileName = "version-output.txt" logPrefix = "logs" configPrefix = "configs" additionalPrefix = "misc" @@ -48,14 +49,22 @@ type BundleBuilder interface { GenerateSupportBundle([]string, []string) (string, error) } +// SystemDeps provides methods to get system dependencies. +// +//go:generate mockgen -copyright_file=../../copyright_header -destination=../mocks/pkg_support.go -package=mocks -mock_names SystemDeps=SupportSystemDeps . SystemDeps +type SystemDeps interface { + system.ExecutableFinder +} + type bundleBuilder struct { - logger flog.Logger - fs afero.Fs - config BundleConfig - finch fpath.Finch - ecc command.Creator - ncc command.NerdctlCmdCreator - lima wrapper.LimaWrapper + logger flog.Logger + fs afero.Fs + config BundleConfig + finch fpath.Finch + ecc command.Creator + ncc command.NerdctlCmdCreator + lima wrapper.LimaWrapper + systemDeps SystemDeps } // NewBundleBuilder produces a new BundleBuilder. @@ -67,15 +76,17 @@ func NewBundleBuilder( ecc command.Creator, ncc command.NerdctlCmdCreator, lima wrapper.LimaWrapper, + systemDeps SystemDeps, ) BundleBuilder { return &bundleBuilder{ - logger: logger, - fs: fs, - config: config, - finch: finch, - ecc: ecc, - ncc: ncc, - lima: lima, + logger: logger, + fs: fs, + config: config, + finch: finch, + ecc: ecc, + ncc: ncc, + lima: lima, + systemDeps: systemDeps, } } @@ -108,6 +119,13 @@ func (bb *bundleBuilder) GenerateSupportBundle(additionalFiles []string, exclude return "", err } + bb.logger.Debugln("Collecting finch version output...") + version := bb.getFinchVersion() + err = writeVersionOutput(writer, version, zipPrefix) + if err != nil { + return "", err + } + bb.logger.Debugln("Copying in log files...") for _, file := range bb.config.LogFiles() { if fileShouldBeExcluded(file, excludeFiles) { @@ -320,6 +338,21 @@ func getFinchVersion() string { return version.Version } +func (bb *bundleBuilder) getFinchVersion() string { + // get current finch executable + executable, err := bb.systemDeps.Executable() + if err != nil { + return "" + } + cmd := bb.ecc.Create(executable, "version") + out, err := cmd.Output() + if err != nil { + return "" + } + output := string(out) + return output +} + func writePlatformData(writer *zip.Writer, platform *PlatformData, prefix string) error { platformFile, err := writer.Create(path.Join(prefix, platformFileName)) if err != nil { @@ -371,3 +404,17 @@ func fileShouldBeExcluded(filename string, exclude []string) bool { func isFileFromVM(filename string) bool { return strings.HasPrefix(filename, "vm:") } + +func writeVersionOutput(writer *zip.Writer, version, prefix string) error { + versionFile, err := writer.Create(path.Join(prefix, versionFileName)) + if err != nil { + return err + } + + _, err = versionFile.Write([]byte(version)) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/support/support_test.go b/pkg/support/support_test.go index b5e27b2bb..c1d52eb6d 100644 --- a/pkg/support/support_test.go +++ b/pkg/support/support_test.go @@ -30,9 +30,10 @@ func TestSupport_NewBundleBuilder(t *testing.T) { fs := afero.NewMemMapFs() finch := fpath.Finch("mockfinch") lima := mocks.NewMockLimaWrapper(ctrl) + systemDeps := mocks.NewSupportSystemDeps(ctrl) config := NewBundleConfig(finch, "mockhome") - NewBundleBuilder(logger, fs, config, finch, ecc, ncc, lima) + NewBundleBuilder(logger, fs, config, finch, ecc, ncc, lima, systemDeps) } func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { @@ -51,6 +52,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { *mocks.NerdctlCmdCreator, *mocks.Command, *mocks.MockLimaWrapper, + *mocks.SupportSystemDeps, afero.Fs, ) include []string @@ -67,6 +69,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, + systemDeps *mocks.SupportSystemDeps, _ afero.Fs, ) { logger.EXPECT().Debugf("Creating %s...", gomock.Any()) @@ -83,7 +86,11 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { cmd = nil } - cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil) + cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil).AnyTimes() + + logger.EXPECT().Debugln("Collecting finch version output...") + systemDeps.EXPECT().Executable().Return("/bin/path", nil) + ecc.EXPECT().Create("/bin/path", "version").Return(cmd) config.EXPECT().LogFiles().Return([]string{ "log1", @@ -117,6 +124,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, + systemDeps *mocks.SupportSystemDeps, _ afero.Fs, ) { logger.EXPECT().Debugf("Creating %s...", gomock.Any()) @@ -133,7 +141,11 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { cmd = nil } - cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil) + cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil).AnyTimes() + + logger.EXPECT().Debugln("Collecting finch version output...") + systemDeps.EXPECT().Executable().Return("/bin/path", nil) + ecc.EXPECT().Create("/bin/path", "version").Return(cmd) config.EXPECT().LogFiles().Return([]string{ "log1", @@ -164,6 +176,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, + systemDeps *mocks.SupportSystemDeps, _ afero.Fs, ) { logger.EXPECT().Debugf("Creating %s...", gomock.Any()) @@ -180,7 +193,11 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { cmd = nil } - cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil) + cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil).AnyTimes() + + logger.EXPECT().Debugln("Collecting finch version output...") + systemDeps.EXPECT().Executable().Return("/bin/path", nil) + ecc.EXPECT().Create("/bin/path", "version").Return(cmd) config.EXPECT().LogFiles().Return([]string{ "log1", @@ -210,6 +227,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, + systemDeps *mocks.SupportSystemDeps, _ afero.Fs, ) { logger.EXPECT().Debugf("Creating %s...", gomock.Any()) @@ -226,7 +244,11 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { cmd = nil } - cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil) + cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil).AnyTimes() + + logger.EXPECT().Debugln("Collecting finch version output...") + systemDeps.EXPECT().Executable().Return("/bin/path", nil) + ecc.EXPECT().Create("/bin/path", "version").Return(cmd) config.EXPECT().LogFiles().Return([]string{ "log1", @@ -256,6 +278,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, + systemDeps *mocks.SupportSystemDeps, _ afero.Fs, ) { logger.EXPECT().Debugf("Creating %s...", gomock.Any()) @@ -272,7 +295,11 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { cmd = nil } - cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil) + cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil).AnyTimes() + + logger.EXPECT().Debugln("Collecting finch version output...") + systemDeps.EXPECT().Executable().Return("/bin/path", nil) + ecc.EXPECT().Create("/bin/path", "version").Return(cmd) config.EXPECT().LogFiles().Return([]string{ "log1", @@ -306,6 +333,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { ncc *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, + systemDeps *mocks.SupportSystemDeps, _ afero.Fs, ) { logger.EXPECT().Debugf("Creating %s...", gomock.Any()) @@ -322,7 +350,11 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { cmd = nil } - cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil) + cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil).AnyTimes() + + logger.EXPECT().Debugln("Collecting finch version output...") + systemDeps.EXPECT().Executable().Return("/bin/path", nil) + ecc.EXPECT().Create("finch", "version").Return(cmd) config.EXPECT().LogFiles().Return([]string{ "log1", @@ -375,6 +407,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { _ *mocks.NerdctlCmdCreator, cmd *mocks.Command, lima *mocks.MockLimaWrapper, + systemDeps *mocks.SupportSystemDeps, _ afero.Fs, ) { logger.EXPECT().Debugf("Creating %s...", gomock.Any()) @@ -391,7 +424,11 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { cmd = nil } - cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil) + cmd.EXPECT().Output().Return([]byte("1.2.3\n"), nil).AnyTimes() + + logger.EXPECT().Debugln("Collecting finch version output...") + systemDeps.EXPECT().Executable().Return("/bin/path", nil) + ecc.EXPECT().Create("/bin/path", "version").Return(cmd) config.EXPECT().LogFiles().Return([]string{ "log1", @@ -437,15 +474,17 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { ncc := mocks.NewNerdctlCmdCreator(ctrl) lima := mocks.NewMockLimaWrapper(ctrl) cmd := mocks.NewCommand(ctrl) + systemDeps := mocks.NewSupportSystemDeps(ctrl) builder := &bundleBuilder{ - logger: logger, - fs: fs, - config: config, - finch: finch, - ecc: ecc, - ncc: ncc, - lima: lima, + logger: logger, + fs: fs, + config: config, + finch: finch, + ecc: ecc, + ncc: ncc, + lima: lima, + systemDeps: systemDeps, } testFiles := []string{ @@ -465,7 +504,7 @@ func TestSupportBundleBuilder_GenerateSupportBundle(t *testing.T) { require.NoError(t, err) } - tc.mockSvc(logger, config, ecc, ncc, cmd, lima, fs) + tc.mockSvc(logger, config, ecc, ncc, cmd, lima, systemDeps, fs) zipFile, err := builder.GenerateSupportBundle(tc.include, tc.exclude) assert.NoError(t, err) @@ -593,3 +632,100 @@ func TestSupport_fileShouldBeExcluded(t *testing.T) { }) } } + +func TestSupport_writeVersionOutput(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + versionOutput string + prefix string + expectedPath string + expectedContent string + expectError bool + }{ + { + name: "Write version output successfully", + versionOutput: "1.2.3", + prefix: "test_prefix", + expectedPath: "test_prefix/version-output.txt", + expectedContent: "1.2.3", + expectError: false, + }, + { + name: "Write version output with empty prefix", + versionOutput: "1.2.3", + prefix: "", + expectedPath: "version-output.txt", + expectedContent: "1.2.3", + expectError: false, + }, + { + name: "Write empty output", + versionOutput: "", + prefix: "empty_version", + expectedPath: "empty_version/version-output.txt", + expectedContent: "", + expectError: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + fs := afero.NewMemMapFs() + + file, err := fs.Create("testFile") + require.NoError(t, err) + + writer := zip.NewWriter(file) + + err = writeVersionOutput(writer, tc.versionOutput, tc.prefix) + + // Check if we expected an error + if tc.expectError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + err = writer.Close() + assert.NoError(t, err) + + err = file.Close() + assert.NoError(t, err) + + // Open the zip archive for reading + readFile, err := fs.Open("testFile") + require.NoError(t, err) + defer readFile.Close() //nolint:errcheck // closing the file + + fileInfo, err := readFile.Stat() + require.NoError(t, err) + + reader, err := zip.NewReader(readFile, fileInfo.Size()) + require.NoError(t, err) + + // Check if the file exists in the zip + var found bool + for _, f := range reader.File { + if f.Name == tc.expectedPath { + found = true + rc, err := f.Open() + require.NoError(t, err) + defer rc.Close() //nolint:errcheck // closing the file + + content, err := io.ReadAll(rc) + require.NoError(t, err) + + // Check the file content + assert.Equal(t, tc.expectedContent, string(content)) + break + } + } + assert.True(t, found, "Expected file not found in zip archive") + }) + } +}