From 68d195495ecad21458c2bd2441d0c8e80fabe7eb Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Wed, 31 Jul 2024 12:26:41 -0500 Subject: [PATCH 1/6] Renaming "./kubestr browse" command to "./kubestr browse pvc" (#275) * Adding the kubestr browse pvc command. Handling kubestr browse support for backward compatibility. * Adding Deprecated msg to the 'browse' command * Updating Deprecated msg for 'browse' command * Making namespace, runAsUser & localport flags persistent --- cmd/rootCmd.go | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/cmd/rootCmd.go b/cmd/rootCmd.go index 8855912..4f36fd7 100644 --- a/cmd/rootCmd.go +++ b/cmd/rootCmd.go @@ -83,9 +83,20 @@ var ( }, } - pvcBrowseLocalPort int - pvcBrowseCmd = &cobra.Command{ - Use: "browse [PVC name]", + browseLocalPort int + browseCmd = &cobra.Command{ + Use: "browse", + Short: "Browse the contents of PVC or VolumeSnapshot", + Long: "Browse the contents of a CSI provisioned PVC or a CSI provisioned VolumeSnapshot.", + Deprecated: "use 'browse pvc' instead", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return browsePvcCmd.RunE(cmd, args) + }, + } + + browsePvcCmd = &cobra.Command{ + Use: "pvc [PVC name]", Short: "Browse the contents of a CSI PVC via file browser", Long: "Browse the contents of a CSI provisioned PVC by cloning the volume and mounting it with a file browser.", Args: cobra.ExactArgs(1), @@ -94,7 +105,7 @@ var ( namespace, csiCheckVolumeSnapshotClass, csiCheckRunAsUser, - pvcBrowseLocalPort, + browseLocalPort, ) }, } @@ -164,12 +175,16 @@ func init() { csiCheckCmd.Flags().Int64VarP(&csiCheckRunAsUser, "runAsUser", "u", 0, "Runs the CSI check pod with the specified user ID (int)") csiCheckCmd.Flags().BoolVarP(&csiCheckSkipCFSCheck, "skipCFScheck", "k", false, "Use this flag to skip validating the ability to clone a snapshot.") - rootCmd.AddCommand(pvcBrowseCmd) - pvcBrowseCmd.Flags().StringVarP(&csiCheckVolumeSnapshotClass, "volumesnapshotclass", "v", "", "The name of a VolumeSnapshotClass. (Required)") - _ = pvcBrowseCmd.MarkFlagRequired("volumesnapshotclass") - pvcBrowseCmd.Flags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace of the PersistentVolumeClaim.") - pvcBrowseCmd.Flags().Int64VarP(&csiCheckRunAsUser, "runAsUser", "u", 0, "Runs the inspector pod as a user (int)") - pvcBrowseCmd.Flags().IntVarP(&pvcBrowseLocalPort, "localport", "l", 8080, "The local port to expose the inspector") + rootCmd.AddCommand(browseCmd) + browseCmd.Flags().StringVarP(&csiCheckVolumeSnapshotClass, "volumesnapshotclass", "v", "", "The name of a VolumeSnapshotClass. (Required)") + _ = browseCmd.MarkFlagRequired("volumesnapshotclass") + browseCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace of the PersistentVolumeClaim.") + browseCmd.PersistentFlags().Int64VarP(&csiCheckRunAsUser, "runAsUser", "u", 0, "Runs the inspector pod as a user (int)") + browseCmd.PersistentFlags().IntVarP(&browseLocalPort, "localport", "l", 8080, "The local port to expose the inspector") + + browseCmd.AddCommand(browsePvcCmd) + browsePvcCmd.Flags().StringVarP(&csiCheckVolumeSnapshotClass, "volumesnapshotclass", "v", "", "The name of a VolumeSnapshotClass. (Required)") + _ = browsePvcCmd.MarkFlagRequired("volumesnapshotclass") rootCmd.AddCommand(blockMountCmd) blockMountCmd.Flags().StringVarP(&storageClass, "storageclass", "s", "", "The name of a Storageclass. (Required)") From 2cf1758a0c082a8c5a234ed2eb26f24a90d2ef84 Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Thu, 1 Aug 2024 21:06:34 -0500 Subject: [PATCH 2/6] Adding "./kubestr browse snapshot" command (#277) * Adding the kubestr browse pvc command. Handling kubestr browse support for backward compatibility. * Adding browse snapshot command. Updating browse command to browse pvc command. * chore(deps): bump github/codeql-action in the github-actions group (#272) Bumps the github-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.25.12 to 3.25.13 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/4fa2a7953630fd2f3fb380f21be14ede0169dd4f...2d790406f505036ef40ecba973cc774a50395aac) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump docker/build-push-action in the docker group (#273) Bumps the docker group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.3.0 to 6.4.1 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/1a162644f9a7e87d8f4b053101d1d9a712edc18c...1ca370b3a9802c92e886402e0dd88098a2533b12) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Removing unused snapshot function parameter in cleanup * Adding mock tests for SnapshotBrowserStepper * Adding Deprecated msg to the 'browse' command * Adding fake tests for snapshot_inspector.go * Renamed testcase CSITestSuite.TestCreateInspectorApplication to TestCreateInspectorApplicationForPVC * Adding snapshot_inspector_steps_test.go * Updating Deprecated msg for 'browse' command * Making namespace, runAsUser & localport flags persistent * Removing namespace, runAsUser & localport flags for browse snapshot because we made those persistent * Removing storage class flag * Update cmd/rootCmd.go Co-authored-by: Sirish Bathina --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sirish Bathina --- cmd/rootCmd.go | 52 +- pkg/csi/csi_ops.go | 49 ++ pkg/csi/mocks/mock_argument_validator.go | 36 +- .../mocks/mock_snapshot_browser_stepper.go | 97 +++ pkg/csi/mocks/mock_snapshot_fetcher.go | 68 +++ pkg/csi/pvc_inspector_steps_test.go | 2 +- pkg/csi/snapshot_inspector.go | 239 ++++++++ pkg/csi/snapshot_inspector_steps_test.go | 566 ++++++++++++++++++ pkg/csi/snapshot_inspector_test.go | 192 ++++++ pkg/csi/types/csi_types.go | 26 + 10 files changed, 1314 insertions(+), 13 deletions(-) create mode 100644 pkg/csi/mocks/mock_snapshot_browser_stepper.go create mode 100644 pkg/csi/mocks/mock_snapshot_fetcher.go create mode 100644 pkg/csi/snapshot_inspector.go create mode 100644 pkg/csi/snapshot_inspector_steps_test.go create mode 100644 pkg/csi/snapshot_inspector_test.go diff --git a/cmd/rootCmd.go b/cmd/rootCmd.go index 4f36fd7..d53356e 100644 --- a/cmd/rootCmd.go +++ b/cmd/rootCmd.go @@ -110,6 +110,20 @@ var ( }, } + browseSnapshotCmd = &cobra.Command{ + Use: "snapshot [Snapshot name]", + Short: "Browse the contents of a CSI VolumeSnapshot via file browser", + Long: "Browse the contents of a CSI provisioned VolumeSnapshot by cloning the volume and mounting it with a file browser.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return CsiSnapshotBrowse(context.Background(), args[0], + namespace, + csiCheckRunAsUser, + browseLocalPort, + ) + }, + } + blockMountRunAsUser int64 blockMountCleanup bool blockMountCleanupOnly bool @@ -178,7 +192,7 @@ func init() { rootCmd.AddCommand(browseCmd) browseCmd.Flags().StringVarP(&csiCheckVolumeSnapshotClass, "volumesnapshotclass", "v", "", "The name of a VolumeSnapshotClass. (Required)") _ = browseCmd.MarkFlagRequired("volumesnapshotclass") - browseCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace of the PersistentVolumeClaim.") + browseCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace of the resource to browse.") browseCmd.PersistentFlags().Int64VarP(&csiCheckRunAsUser, "runAsUser", "u", 0, "Runs the inspector pod as a user (int)") browseCmd.PersistentFlags().IntVarP(&browseLocalPort, "localport", "l", 8080, "The local port to expose the inspector") @@ -186,8 +200,10 @@ func init() { browsePvcCmd.Flags().StringVarP(&csiCheckVolumeSnapshotClass, "volumesnapshotclass", "v", "", "The name of a VolumeSnapshotClass. (Required)") _ = browsePvcCmd.MarkFlagRequired("volumesnapshotclass") + browseCmd.AddCommand(browseSnapshotCmd) + rootCmd.AddCommand(blockMountCmd) - blockMountCmd.Flags().StringVarP(&storageClass, "storageclass", "s", "", "The name of a Storageclass. (Required)") + blockMountCmd.Flags().StringVarP(&storageClass, "storageclass", "s", "", "The name of a StorageClass. (Required)") _ = blockMountCmd.MarkFlagRequired("storageclass") blockMountCmd.Flags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace used to run the check.") blockMountCmd.Flags().StringVarP(&containerImage, "image", "i", "", "The container image used to create a pod.") @@ -373,6 +389,38 @@ func CsiPvcBrowse(ctx context.Context, return err } +func CsiSnapshotBrowse(ctx context.Context, + snapshotName string, + namespace string, + runAsUser int64, + localPort int, +) error { + kubecli, err := kubestr.LoadKubeCli() + if err != nil { + fmt.Printf("Failed to load kubeCli (%s)", err.Error()) + return err + } + dyncli, err := kubestr.LoadDynCli() + if err != nil { + fmt.Printf("Failed to load dynCli (%s)", err.Error()) + return err + } + browseRunner := &csi.SnapshotBrowseRunner{ + KubeCli: kubecli, + DynCli: dyncli, + } + err = browseRunner.RunSnapshotBrowse(ctx, &csitypes.SnapshotBrowseArgs{ + SnapshotName: snapshotName, + Namespace: namespace, + RunAsUser: runAsUser, + LocalPort: localPort, + }) + if err != nil { + fmt.Printf("Failed to run Snapshot browser (%s)\n", err.Error()) + } + return err +} + func BlockMountCheck(ctx context.Context, output, outfile string, cleanupOnly bool, checkerArgs block.BlockMountCheckerArgs) error { kubecli, err := kubestr.LoadKubeCli() if err != nil { diff --git a/pkg/csi/csi_ops.go b/pkg/csi/csi_ops.go index 7316731..280a8d2 100644 --- a/pkg/csi/csi_ops.go +++ b/pkg/csi/csi_ops.go @@ -5,6 +5,8 @@ package csi import ( "context" "fmt" + "k8s.io/apimachinery/pkg/runtime" + "log" "net/http" "net/url" "strings" @@ -44,6 +46,7 @@ type ArgumentValidator interface { //Rename ValidatePVC(ctx context.Context, pvcName, namespace string) (*v1.PersistentVolumeClaim, error) FetchPV(ctx context.Context, pvName string) (*v1.PersistentVolume, error) + ValidateVolumeSnapshot(ctx context.Context, snapshotName, namespace string, groupVersion *metav1.GroupVersionForDiscovery) (*snapv1.VolumeSnapshot, error) ValidateNamespace(ctx context.Context, namespace string) error ValidateStorageClass(ctx context.Context, storageClass string) (*sv1.StorageClass, error) ValidateVolumeSnapshotClass(ctx context.Context, volumeSnapshotClass string, groupVersion *metav1.GroupVersionForDiscovery) (*unstructured.Unstructured, error) @@ -68,6 +71,17 @@ func (o *validateOperations) ValidatePVC(ctx context.Context, pvcName, namespace return o.kubeCli.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}) } +func (o *validateOperations) ValidateVolumeSnapshot(ctx context.Context, snapshotName, namespace string, groupVersion *metav1.GroupVersionForDiscovery) (*snapv1.VolumeSnapshot, error) { + VolSnapGVR := schema.GroupVersionResource{Group: snapv1.GroupName, Version: groupVersion.Version, Resource: common.VolumeSnapshotResourcePlural} + uVS, err := o.dynCli.Resource(VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) + if err != nil { + log.Fatalf("Failed to get VolumeSnapshot: %v", err) + } + volumeSnapshot := &snapv1.VolumeSnapshot{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured(uVS.UnstructuredContent(), volumeSnapshot) + return volumeSnapshot, err +} + func (o *validateOperations) FetchPV(ctx context.Context, pvName string) (*v1.PersistentVolume, error) { if o.kubeCli == nil { return nil, fmt.Errorf("kubeCli not initialized") @@ -317,6 +331,41 @@ func (c *applicationCreate) getErrorFromEvents(ctx context.Context, namespace, n return nil } +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_snapshot_fetcher.go -package=mocks . SnapshotFetcher +type SnapshotFetcher interface { + NewSnapshotter() (kansnapshot.Snapshotter, error) + GetVolumeSnapshot(ctx context.Context, snapshotter kansnapshot.Snapshotter, args *types.FetchSnapshotArgs) (*snapv1.VolumeSnapshot, error) +} + +type snapshotFetch struct { + kubeCli kubernetes.Interface + dynCli dynamic.Interface +} + +func (f *snapshotFetch) NewSnapshotter() (kansnapshot.Snapshotter, error) { + if f.kubeCli == nil { + return nil, fmt.Errorf("kubeCli not initialized") + } + if f.dynCli == nil { + return nil, fmt.Errorf("dynCli not initialized") + } + return kansnapshot.NewSnapshotter(f.kubeCli, f.dynCli) +} + +func (f *snapshotFetch) GetVolumeSnapshot(ctx context.Context, snapshotter kansnapshot.Snapshotter, args *types.FetchSnapshotArgs) (*snapv1.VolumeSnapshot, error) { + if snapshotter == nil || args == nil { + return nil, fmt.Errorf("snapshotter or args are empty") + } + if err := args.Validate(); err != nil { + return nil, err + } + snap, err := snapshotter.Get(ctx, args.SnapshotName, args.Namespace) + if err != nil { + return nil, errors.Wrapf(err, "Failed to get CSI snapshot (%s) in Namespace (%s)", args.SnapshotName, args.Namespace) + } + return snap, nil +} + //go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_snapshot_creator.go -package=mocks . SnapshotCreator type SnapshotCreator interface { NewSnapshotter() (kansnapshot.Snapshotter, error) diff --git a/pkg/csi/mocks/mock_argument_validator.go b/pkg/csi/mocks/mock_argument_validator.go index 22553a0..86af720 100644 --- a/pkg/csi/mocks/mock_argument_validator.go +++ b/pkg/csi/mocks/mock_argument_validator.go @@ -9,9 +9,10 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - v1 "k8s.io/api/core/v1" - v10 "k8s.io/api/storage/v1" - v11 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + v10 "k8s.io/api/core/v1" + v11 "k8s.io/api/storage/v1" + v12 "k8s.io/apimachinery/pkg/apis/meta/v1" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -39,10 +40,10 @@ func (m *MockArgumentValidator) EXPECT() *MockArgumentValidatorMockRecorder { } // FetchPV mocks base method. -func (m *MockArgumentValidator) FetchPV(arg0 context.Context, arg1 string) (*v1.PersistentVolume, error) { +func (m *MockArgumentValidator) FetchPV(arg0 context.Context, arg1 string) (*v10.PersistentVolume, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FetchPV", arg0, arg1) - ret0, _ := ret[0].(*v1.PersistentVolume) + ret0, _ := ret[0].(*v10.PersistentVolume) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -68,10 +69,10 @@ func (mr *MockArgumentValidatorMockRecorder) ValidateNamespace(arg0, arg1 interf } // ValidatePVC mocks base method. -func (m *MockArgumentValidator) ValidatePVC(arg0 context.Context, arg1, arg2 string) (*v1.PersistentVolumeClaim, error) { +func (m *MockArgumentValidator) ValidatePVC(arg0 context.Context, arg1, arg2 string) (*v10.PersistentVolumeClaim, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidatePVC", arg0, arg1, arg2) - ret0, _ := ret[0].(*v1.PersistentVolumeClaim) + ret0, _ := ret[0].(*v10.PersistentVolumeClaim) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -83,10 +84,10 @@ func (mr *MockArgumentValidatorMockRecorder) ValidatePVC(arg0, arg1, arg2 interf } // ValidateStorageClass mocks base method. -func (m *MockArgumentValidator) ValidateStorageClass(arg0 context.Context, arg1 string) (*v10.StorageClass, error) { +func (m *MockArgumentValidator) ValidateStorageClass(arg0 context.Context, arg1 string) (*v11.StorageClass, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidateStorageClass", arg0, arg1) - ret0, _ := ret[0].(*v10.StorageClass) + ret0, _ := ret[0].(*v11.StorageClass) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -97,8 +98,23 @@ func (mr *MockArgumentValidatorMockRecorder) ValidateStorageClass(arg0, arg1 int return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateStorageClass", reflect.TypeOf((*MockArgumentValidator)(nil).ValidateStorageClass), arg0, arg1) } +// ValidateVolumeSnapshot mocks base method. +func (m *MockArgumentValidator) ValidateVolumeSnapshot(arg0 context.Context, arg1, arg2 string, arg3 *v12.GroupVersionForDiscovery) (*v1.VolumeSnapshot, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateVolumeSnapshot", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*v1.VolumeSnapshot) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ValidateVolumeSnapshot indicates an expected call of ValidateVolumeSnapshot. +func (mr *MockArgumentValidatorMockRecorder) ValidateVolumeSnapshot(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateVolumeSnapshot", reflect.TypeOf((*MockArgumentValidator)(nil).ValidateVolumeSnapshot), arg0, arg1, arg2, arg3) +} + // ValidateVolumeSnapshotClass mocks base method. -func (m *MockArgumentValidator) ValidateVolumeSnapshotClass(arg0 context.Context, arg1 string, arg2 *v11.GroupVersionForDiscovery) (*unstructured.Unstructured, error) { +func (m *MockArgumentValidator) ValidateVolumeSnapshotClass(arg0 context.Context, arg1 string, arg2 *v12.GroupVersionForDiscovery) (*unstructured.Unstructured, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidateVolumeSnapshotClass", arg0, arg1, arg2) ret0, _ := ret[0].(*unstructured.Unstructured) diff --git a/pkg/csi/mocks/mock_snapshot_browser_stepper.go b/pkg/csi/mocks/mock_snapshot_browser_stepper.go new file mode 100644 index 0000000..4c5bb02 --- /dev/null +++ b/pkg/csi/mocks/mock_snapshot_browser_stepper.go @@ -0,0 +1,97 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: SnapshotBrowserStepper) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + types "github.com/kastenhq/kubestr/pkg/csi/types" + v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + v10 "k8s.io/api/core/v1" + v11 "k8s.io/api/storage/v1" +) + +// MockSnapshotBrowserStepper is a mock of SnapshotBrowserStepper interface. +type MockSnapshotBrowserStepper struct { + ctrl *gomock.Controller + recorder *MockSnapshotBrowserStepperMockRecorder +} + +// MockSnapshotBrowserStepperMockRecorder is the mock recorder for MockSnapshotBrowserStepper. +type MockSnapshotBrowserStepperMockRecorder struct { + mock *MockSnapshotBrowserStepper +} + +// NewMockSnapshotBrowserStepper creates a new mock instance. +func NewMockSnapshotBrowserStepper(ctrl *gomock.Controller) *MockSnapshotBrowserStepper { + mock := &MockSnapshotBrowserStepper{ctrl: ctrl} + mock.recorder = &MockSnapshotBrowserStepperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSnapshotBrowserStepper) EXPECT() *MockSnapshotBrowserStepperMockRecorder { + return m.recorder +} + +// Cleanup mocks base method. +func (m *MockSnapshotBrowserStepper) Cleanup(arg0 context.Context, arg1 *v10.PersistentVolumeClaim, arg2 *v10.Pod) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Cleanup", arg0, arg1, arg2) +} + +// Cleanup indicates an expected call of Cleanup. +func (mr *MockSnapshotBrowserStepperMockRecorder) Cleanup(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cleanup", reflect.TypeOf((*MockSnapshotBrowserStepper)(nil).Cleanup), arg0, arg1, arg2) +} + +// CreateInspectorApplication mocks base method. +func (m *MockSnapshotBrowserStepper) CreateInspectorApplication(arg0 context.Context, arg1 *types.SnapshotBrowseArgs, arg2 *v1.VolumeSnapshot, arg3 *v11.StorageClass) (*v10.Pod, *v10.PersistentVolumeClaim, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateInspectorApplication", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*v10.Pod) + ret1, _ := ret[1].(*v10.PersistentVolumeClaim) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// CreateInspectorApplication indicates an expected call of CreateInspectorApplication. +func (mr *MockSnapshotBrowserStepperMockRecorder) CreateInspectorApplication(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInspectorApplication", reflect.TypeOf((*MockSnapshotBrowserStepper)(nil).CreateInspectorApplication), arg0, arg1, arg2, arg3) +} + +// PortForwardAPod mocks base method. +func (m *MockSnapshotBrowserStepper) PortForwardAPod(arg0 context.Context, arg1 *v10.Pod, arg2 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortForwardAPod", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// PortForwardAPod indicates an expected call of PortForwardAPod. +func (mr *MockSnapshotBrowserStepperMockRecorder) PortForwardAPod(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortForwardAPod", reflect.TypeOf((*MockSnapshotBrowserStepper)(nil).PortForwardAPod), arg0, arg1, arg2) +} + +// ValidateArgs mocks base method. +func (m *MockSnapshotBrowserStepper) ValidateArgs(arg0 context.Context, arg1 *types.SnapshotBrowseArgs) (*v1.VolumeSnapshot, *v11.StorageClass, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateArgs", arg0, arg1) + ret0, _ := ret[0].(*v1.VolumeSnapshot) + ret1, _ := ret[1].(*v11.StorageClass) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ValidateArgs indicates an expected call of ValidateArgs. +func (mr *MockSnapshotBrowserStepperMockRecorder) ValidateArgs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateArgs", reflect.TypeOf((*MockSnapshotBrowserStepper)(nil).ValidateArgs), arg0, arg1) +} diff --git a/pkg/csi/mocks/mock_snapshot_fetcher.go b/pkg/csi/mocks/mock_snapshot_fetcher.go new file mode 100644 index 0000000..8bd55e8 --- /dev/null +++ b/pkg/csi/mocks/mock_snapshot_fetcher.go @@ -0,0 +1,68 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: SnapshotFetcher) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + snapshot "github.com/kanisterio/kanister/pkg/kube/snapshot" + types "github.com/kastenhq/kubestr/pkg/csi/types" + v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" +) + +// MockSnapshotFetcher is a mock of SnapshotFetcher interface. +type MockSnapshotFetcher struct { + ctrl *gomock.Controller + recorder *MockSnapshotFetcherMockRecorder +} + +// MockSnapshotFetcherMockRecorder is the mock recorder for MockSnapshotFetcher. +type MockSnapshotFetcherMockRecorder struct { + mock *MockSnapshotFetcher +} + +// NewMockSnapshotFetcher creates a new mock instance. +func NewMockSnapshotFetcher(ctrl *gomock.Controller) *MockSnapshotFetcher { + mock := &MockSnapshotFetcher{ctrl: ctrl} + mock.recorder = &MockSnapshotFetcherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSnapshotFetcher) EXPECT() *MockSnapshotFetcherMockRecorder { + return m.recorder +} + +// GetVolumeSnapshot mocks base method. +func (m *MockSnapshotFetcher) GetVolumeSnapshot(arg0 context.Context, arg1 snapshot.Snapshotter, arg2 *types.FetchSnapshotArgs) (*v1.VolumeSnapshot, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVolumeSnapshot", arg0, arg1, arg2) + ret0, _ := ret[0].(*v1.VolumeSnapshot) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetVolumeSnapshot indicates an expected call of GetVolumeSnapshot. +func (mr *MockSnapshotFetcherMockRecorder) GetVolumeSnapshot(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolumeSnapshot", reflect.TypeOf((*MockSnapshotFetcher)(nil).GetVolumeSnapshot), arg0, arg1, arg2) +} + +// NewSnapshotter mocks base method. +func (m *MockSnapshotFetcher) NewSnapshotter() (snapshot.Snapshotter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewSnapshotter") + ret0, _ := ret[0].(snapshot.Snapshotter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewSnapshotter indicates an expected call of NewSnapshotter. +func (mr *MockSnapshotFetcherMockRecorder) NewSnapshotter() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSnapshotter", reflect.TypeOf((*MockSnapshotFetcher)(nil).NewSnapshotter)) +} diff --git a/pkg/csi/pvc_inspector_steps_test.go b/pkg/csi/pvc_inspector_steps_test.go index 8b53069..a78649c 100644 --- a/pkg/csi/pvc_inspector_steps_test.go +++ b/pkg/csi/pvc_inspector_steps_test.go @@ -486,7 +486,7 @@ func (s *CSITestSuite) TestPvcBrowseSnapshotPVC(c *C) { } } -func (s *CSITestSuite) TestCreateInspectorApplication(c *C) { +func (s *CSITestSuite) TestCreateInspectorApplicationForPVC(c *C) { ctx := context.Background() resourceQuantity := resource.MustParse("1Gi") snapshotAPIGroup := "snapshot.storage.k8s.io" diff --git a/pkg/csi/snapshot_inspector.go b/pkg/csi/snapshot_inspector.go new file mode 100644 index 0000000..9e2645a --- /dev/null +++ b/pkg/csi/snapshot_inspector.go @@ -0,0 +1,239 @@ +package csi + +import ( + "bytes" + "context" + "fmt" + "github.com/kastenhq/kubestr/pkg/csi/types" + snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + sv1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "os" + "os/signal" + "sync" + "syscall" +) + +type SnapshotBrowseRunner struct { + KubeCli kubernetes.Interface + DynCli dynamic.Interface + browserSteps SnapshotBrowserStepper + pvc *v1.PersistentVolumeClaim + pod *v1.Pod + snapshot *snapv1.VolumeSnapshot +} + +func (r *SnapshotBrowseRunner) RunSnapshotBrowse(ctx context.Context, args *types.SnapshotBrowseArgs) error { + r.browserSteps = &snapshotBrowserSteps{ + validateOps: &validateOperations{ + kubeCli: r.KubeCli, + dynCli: r.DynCli, + }, + versionFetchOps: &apiVersionFetch{ + kubeCli: r.KubeCli, + }, + createAppOps: &applicationCreate{ + kubeCli: r.KubeCli, + }, + snapshotFetchOps: &snapshotFetch{ + kubeCli: r.KubeCli, + dynCli: r.DynCli, + }, + portForwardOps: &portforward{}, + cleanerOps: &cleanse{ + kubeCli: r.KubeCli, + dynCli: r.DynCli, + }, + } + return r.RunSnapshotBrowseHelper(ctx, args) +} + +func (r *SnapshotBrowseRunner) RunSnapshotBrowseHelper(ctx context.Context, args *types.SnapshotBrowseArgs) error { + defer func() { + fmt.Println("Cleaning up resources.") + r.browserSteps.Cleanup(ctx, r.pvc, r.pod) + }() + + if r.KubeCli == nil || r.DynCli == nil { + return fmt.Errorf("cli uninitialized") + } + + fmt.Println("Fetching the snapshot.") + vs, sc, err := r.browserSteps.ValidateArgs(ctx, args) + if err != nil { + return errors.Wrap(err, "Failed to validate arguments.") + } + r.snapshot = vs + + fmt.Println("Creating the file browser application.") + r.pod, r.pvc, err = r.browserSteps.CreateInspectorApplication(ctx, args, r.snapshot, sc) + if err != nil { + return errors.Wrap(err, "Failed to create inspector application.") + } + + fmt.Println("Forwarding the port.") + err = r.browserSteps.PortForwardAPod(ctx, r.pod, args.LocalPort) + if err != nil { + return errors.Wrap(err, "Failed to port forward Pod.") + } + + return nil +} + +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_snapshot_browser_stepper.go -package=mocks . SnapshotBrowserStepper +type SnapshotBrowserStepper interface { + ValidateArgs(ctx context.Context, args *types.SnapshotBrowseArgs) (*snapv1.VolumeSnapshot, *sv1.StorageClass, error) + CreateInspectorApplication(ctx context.Context, args *types.SnapshotBrowseArgs, snapshot *snapv1.VolumeSnapshot, storageClass *sv1.StorageClass) (*v1.Pod, *v1.PersistentVolumeClaim, error) + PortForwardAPod(ctx context.Context, pod *v1.Pod, localPort int) error + Cleanup(ctx context.Context, pvc *v1.PersistentVolumeClaim, pod *v1.Pod) +} + +type snapshotBrowserSteps struct { + validateOps ArgumentValidator + versionFetchOps ApiVersionFetcher + snapshotFetchOps SnapshotFetcher + createAppOps ApplicationCreator + portForwardOps PortForwarder + cleanerOps Cleaner + SnapshotGroupVersion *metav1.GroupVersionForDiscovery +} + +func (s *snapshotBrowserSteps) ValidateArgs(ctx context.Context, args *types.SnapshotBrowseArgs) (*snapv1.VolumeSnapshot, *sv1.StorageClass, error) { + if err := args.Validate(); err != nil { + return nil, nil, errors.Wrap(err, "Failed to validate input arguments") + } + if err := s.validateOps.ValidateNamespace(ctx, args.Namespace); err != nil { + return nil, nil, errors.Wrap(err, "Failed to validate Namespace") + } + groupVersion, err := s.versionFetchOps.GetCSISnapshotGroupVersion() + if err != nil { + return nil, nil, errors.Wrap(err, "Failed to fetch groupVersion") + } + s.SnapshotGroupVersion = groupVersion + snapshot, err := s.validateOps.ValidateVolumeSnapshot(ctx, args.SnapshotName, args.Namespace, groupVersion) + if err != nil { + return nil, nil, errors.Wrap(err, "Failed to validate VolumeSnapshot") + } + pvc, err := s.validateOps.ValidatePVC(ctx, *snapshot.Spec.Source.PersistentVolumeClaimName, args.Namespace) + if err != nil { + return nil, nil, errors.Wrap(err, "Failed to validate source PVC") + } + sc, err := s.validateOps.ValidateStorageClass(ctx, *pvc.Spec.StorageClassName) + if err != nil { + return nil, nil, errors.Wrap(err, "Failed to validate SC") + } + uVSC, err := s.validateOps.ValidateVolumeSnapshotClass(ctx, *snapshot.Spec.VolumeSnapshotClassName, groupVersion) + if err != nil { + return nil, nil, errors.Wrap(err, "Failed to validate VolumeSnapshotClass") + } + vscDriver := getDriverNameFromUVSC(*uVSC, groupVersion.GroupVersion) + if sc.Provisioner != vscDriver { + return nil, nil, fmt.Errorf("StorageClass provisioner (%s) and VolumeSnapshotClass driver (%s) are different.", sc.Provisioner, vscDriver) + } + return snapshot, sc, nil +} + +func (s *snapshotBrowserSteps) CreateInspectorApplication(ctx context.Context, args *types.SnapshotBrowseArgs, snapshot *snapv1.VolumeSnapshot, storageClass *sv1.StorageClass) (*v1.Pod, *v1.PersistentVolumeClaim, error) { + snapshotAPIGroup := "snapshot.storage.k8s.io" + snapshotKind := "VolumeSnapshot" + dataSource := &v1.TypedLocalObjectReference{ + APIGroup: &snapshotAPIGroup, + Kind: snapshotKind, + Name: snapshot.Name, + } + pvcArgs := &types.CreatePVCArgs{ + GenerateName: clonedPVCGenerateName, + StorageClass: storageClass.Name, + Namespace: args.Namespace, + DataSource: dataSource, + RestoreSize: snapshot.Status.RestoreSize, + } + pvc, err := s.createAppOps.CreatePVC(ctx, pvcArgs) + if err != nil { + return nil, nil, errors.Wrap(err, "Failed to restore PVC") + } + podArgs := &types.CreatePodArgs{ + GenerateName: clonedPodGenerateName, + PVCName: pvc.Name, + Namespace: args.Namespace, + RunAsUser: args.RunAsUser, + ContainerImage: "filebrowser/filebrowser:v2", + ContainerArgs: []string{"--noauth", "-r", "/data"}, + MountPath: "/data", + } + pod, err := s.createAppOps.CreatePod(ctx, podArgs) + if err != nil { + return nil, pvc, errors.Wrap(err, "Failed to create restored Pod") + } + if err = s.createAppOps.WaitForPodReady(ctx, args.Namespace, pod.Name); err != nil { + return pod, pvc, errors.Wrap(err, "Pod failed to become ready") + } + return pod, pvc, nil +} + +func (s *snapshotBrowserSteps) PortForwardAPod(ctx context.Context, pod *v1.Pod, localPort int) error { + var wg sync.WaitGroup + wg.Add(1) + stopChan, readyChan, errChan := make(chan struct{}, 1), make(chan struct{}, 1), make(chan string) + out, errOut := new(bytes.Buffer), new(bytes.Buffer) + cfg, err := s.portForwardOps.FetchRestConfig() + if err != nil { + return errors.New("Failed to fetch rest config") + } + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigs + fmt.Println("Stopping port forward") + close(stopChan) + wg.Done() + }() + + go func() { + pfArgs := &types.PortForwardAPodRequest{ + RestConfig: cfg, + Pod: pod, + LocalPort: localPort, + PodPort: 80, + OutStream: bytes.Buffer(*out), + ErrOutStream: bytes.Buffer(*errOut), + StopCh: stopChan, + ReadyCh: readyChan, + } + err = s.portForwardOps.PortForwardAPod(pfArgs) + if err != nil { + errChan <- fmt.Sprintf("Failed to port forward (%s)", err.Error()) + } + }() + + select { + case <-readyChan: + url := fmt.Sprintf("http://localhost:%d/", localPort) + fmt.Printf("Port forwarding is ready to get traffic. visit %s\n", url) + openbrowser(url) + wg.Wait() + case msg := <-errChan: + return errors.New(msg) + } + + return nil +} + +func (s *snapshotBrowserSteps) Cleanup(ctx context.Context, pvc *v1.PersistentVolumeClaim, pod *v1.Pod) { + if pvc != nil { + err := s.cleanerOps.DeletePVC(ctx, pvc.Name, pvc.Namespace) + if err != nil { + fmt.Println("Failed to delete PVC", pvc) + } + } + if pod != nil { + err := s.cleanerOps.DeletePod(ctx, pod.Name, pod.Namespace) + if err != nil { + fmt.Println("Failed to delete Pod", pod) + } + } +} diff --git a/pkg/csi/snapshot_inspector_steps_test.go b/pkg/csi/snapshot_inspector_steps_test.go new file mode 100644 index 0000000..1f6e767 --- /dev/null +++ b/pkg/csi/snapshot_inspector_steps_test.go @@ -0,0 +1,566 @@ +package csi + +import ( + "context" + "fmt" + + "github.com/golang/mock/gomock" + "github.com/kastenhq/kubestr/pkg/common" + "github.com/kastenhq/kubestr/pkg/csi/mocks" + "github.com/kastenhq/kubestr/pkg/csi/types" + snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + . "gopkg.in/check.v1" + v1 "k8s.io/api/core/v1" + sv1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func (s *CSITestSuite) TestSnapshotBrowseValidateArgs(c *C) { + ctx := context.Background() + scName := "sc" + vscName := "vsc" + pvcName := "pvc" + type fields struct { + validateOps *mocks.MockArgumentValidator + versionOps *mocks.MockApiVersionFetcher + } + for _, tc := range []struct { + args *types.SnapshotBrowseArgs + prepare func(f *fields) + errChecker Checker + }{ + { // valid args + args: &types.SnapshotBrowseArgs{ + SnapshotName: "vs", + Namespace: "ns", + }, + prepare: func(f *fields) { + gomock.InOrder( + f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil), + f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return( + &metav1.GroupVersionForDiscovery{ + GroupVersion: common.SnapshotAlphaVersion, + }, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshot(gomock.Any(), "vs", "ns", gomock.Any()).Return( + &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + Namespace: "ns", + }, + Spec: snapv1.VolumeSnapshotSpec{ + Source: snapv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: &pvcName, + }, + VolumeSnapshotClassName: &vscName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidatePVC(gomock.Any(), "pvc", "ns").Return( + &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + Namespace: "ns", + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "vol", + StorageClassName: &scName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), scName).Return( + &sv1.StorageClass{ + Provisioner: "p1", + }, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshotClass(gomock.Any(), "vsc", &metav1.GroupVersionForDiscovery{ + GroupVersion: common.SnapshotAlphaVersion, + }).Return(&unstructured.Unstructured{ + Object: map[string]interface{}{ + common.VolSnapClassAlphaDriverKey: "p1", + }, + }, nil), + ) + }, + errChecker: IsNil, + }, + { // driver mismatch + args: &types.SnapshotBrowseArgs{ + SnapshotName: "vs", + Namespace: "ns", + }, + prepare: func(f *fields) { + gomock.InOrder( + f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil), + f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return( + &metav1.GroupVersionForDiscovery{ + GroupVersion: common.SnapshotAlphaVersion, + }, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshot(gomock.Any(), "vs", "ns", gomock.Any()).Return( + &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + Namespace: "ns", + }, + Spec: snapv1.VolumeSnapshotSpec{ + Source: snapv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: &pvcName, + }, + VolumeSnapshotClassName: &vscName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidatePVC(gomock.Any(), "pvc", "ns").Return( + &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + Namespace: "ns", + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "vol", + StorageClassName: &scName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), gomock.Any()).Return( + &sv1.StorageClass{ + Provisioner: "p1", + }, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshotClass(gomock.Any(), "vsc", &metav1.GroupVersionForDiscovery{ + GroupVersion: common.SnapshotAlphaVersion, + }).Return(&unstructured.Unstructured{ + Object: map[string]interface{}{ + common.VolSnapClassAlphaDriverKey: "p2", + }, + }, nil), + ) + }, + errChecker: NotNil, + }, + { // vsc error + args: &types.SnapshotBrowseArgs{ + SnapshotName: "vs", + Namespace: "ns", + }, + prepare: func(f *fields) { + gomock.InOrder( + f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil), + f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(nil, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshot(gomock.Any(), "vs", "ns", gomock.Any()).Return( + &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + Namespace: "ns", + }, + Spec: snapv1.VolumeSnapshotSpec{ + Source: snapv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: &pvcName, + }, + VolumeSnapshotClassName: &vscName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidatePVC(gomock.Any(), "pvc", "ns").Return( + &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + Namespace: "ns", + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "vol", + StorageClassName: &scName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), gomock.Any()).Return(nil, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshotClass(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("vsc error")), + ) + }, + errChecker: NotNil, + }, + { // get driver versionn error + args: &types.SnapshotBrowseArgs{ + SnapshotName: "vs", + Namespace: "ns", + }, + prepare: func(f *fields) { + gomock.InOrder( + f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil), + f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(nil, fmt.Errorf("driver version error")), + ) + }, + errChecker: NotNil, + }, + { // sc error + args: &types.SnapshotBrowseArgs{ + SnapshotName: "vs", + Namespace: "ns", + }, + prepare: func(f *fields) { + gomock.InOrder( + f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil), + f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(nil, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshot(gomock.Any(), "vs", "ns", gomock.Any()).Return( + &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + Namespace: "ns", + }, + Spec: snapv1.VolumeSnapshotSpec{ + Source: snapv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: &pvcName, + }, + VolumeSnapshotClassName: &vscName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidatePVC(gomock.Any(), "pvc", "ns").Return( + &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + Namespace: "ns", + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: "vol", + StorageClassName: &scName, + }, + }, nil, + ), + f.validateOps.EXPECT().ValidateStorageClass(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("sc error")), + ) + }, + errChecker: NotNil, + }, + { // validate vs error + args: &types.SnapshotBrowseArgs{ + SnapshotName: "vs", + Namespace: "ns", + }, + prepare: func(f *fields) { + gomock.InOrder( + f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(nil), + f.versionOps.EXPECT().GetCSISnapshotGroupVersion().Return(nil, nil), + f.validateOps.EXPECT().ValidateVolumeSnapshot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("validate vs error")), + ) + }, + errChecker: NotNil, + }, + { // validate ns error + args: &types.SnapshotBrowseArgs{ + SnapshotName: "vs", + Namespace: "ns", + }, + prepare: func(f *fields) { + gomock.InOrder( + f.validateOps.EXPECT().ValidateNamespace(gomock.Any(), "ns").Return(fmt.Errorf("validate ns error")), + ) + }, + errChecker: NotNil, + }, + { // validate vs error + args: &types.SnapshotBrowseArgs{ + SnapshotName: "", + Namespace: "ns", + }, + errChecker: NotNil, + }, + { // validate ns error + args: &types.SnapshotBrowseArgs{ + SnapshotName: "dfd", + Namespace: "", + }, + errChecker: NotNil, + }, + } { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + f := fields{ + validateOps: mocks.NewMockArgumentValidator(ctrl), + versionOps: mocks.NewMockApiVersionFetcher(ctrl), + } + if tc.prepare != nil { + tc.prepare(&f) + } + stepper := &snapshotBrowserSteps{ + validateOps: f.validateOps, + versionFetchOps: f.versionOps, + } + _, _, err := stepper.ValidateArgs(ctx, tc.args) + c.Check(err, tc.errChecker) + } +} + +func (s *CSITestSuite) TestCreateInspectorApplicationForSnapshot(c *C) { + ctx := context.Background() + resourceQuantity := resource.MustParse("1Gi") + snapshotAPIGroup := "snapshot.storage.k8s.io" + type fields struct { + createAppOps *mocks.MockApplicationCreator + } + for _, tc := range []struct { + args *types.SnapshotBrowseArgs + snapshot *snapv1.VolumeSnapshot + sc *sv1.StorageClass + prepare func(f *fields) + errChecker Checker + podChecker Checker + pvcChecker Checker + }{ + { + args: &types.SnapshotBrowseArgs{ + Namespace: "ns", + RunAsUser: 100, + }, + sc: &sv1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sc", + }, + }, + snapshot: &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + }, + Status: &snapv1.VolumeSnapshotStatus{ + RestoreSize: &resourceQuantity, + }, + }, + prepare: func(f *fields) { + gomock.InOrder( + f.createAppOps.EXPECT().CreatePVC(gomock.Any(), &types.CreatePVCArgs{ + GenerateName: clonedPVCGenerateName, + StorageClass: "sc", + Namespace: "ns", + DataSource: &v1.TypedLocalObjectReference{ + APIGroup: &snapshotAPIGroup, + Kind: "VolumeSnapshot", + Name: "vs", + }, + RestoreSize: &resourceQuantity, + }).Return(&v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + }, + }, nil), + f.createAppOps.EXPECT().CreatePod(gomock.Any(), &types.CreatePodArgs{ + GenerateName: clonedPodGenerateName, + PVCName: "pvc", + Namespace: "ns", + ContainerArgs: []string{"--noauth", "-r", "/data"}, + MountPath: "/data", + RunAsUser: 100, + ContainerImage: "filebrowser/filebrowser:v2", + }).Return(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + }, + }, nil), + f.createAppOps.EXPECT().WaitForPodReady(gomock.Any(), "ns", "pod").Return(nil), + ) + }, + errChecker: IsNil, + podChecker: NotNil, + pvcChecker: NotNil, + }, + { + args: &types.SnapshotBrowseArgs{ + Namespace: "ns", + RunAsUser: 100, + }, + sc: &sv1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sc", + }, + }, + snapshot: &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + }, + Status: &snapv1.VolumeSnapshotStatus{ + RestoreSize: &resourceQuantity, + }, + }, + prepare: func(f *fields) { + gomock.InOrder( + f.createAppOps.EXPECT().CreatePVC(gomock.Any(), &types.CreatePVCArgs{ + GenerateName: clonedPVCGenerateName, + StorageClass: "sc", + Namespace: "ns", + DataSource: &v1.TypedLocalObjectReference{ + APIGroup: &snapshotAPIGroup, + Kind: "VolumeSnapshot", + Name: "vs", + }, + RestoreSize: &resourceQuantity, + }).Return(&v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + }, + }, nil), + f.createAppOps.EXPECT().CreatePod(gomock.Any(), &types.CreatePodArgs{ + GenerateName: clonedPodGenerateName, + PVCName: "pvc", + Namespace: "ns", + ContainerArgs: []string{"--noauth", "-r", "/data"}, + MountPath: "/data", + RunAsUser: 100, + ContainerImage: "filebrowser/filebrowser:v2", + }).Return(&v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + }, + }, nil), + f.createAppOps.EXPECT().WaitForPodReady(gomock.Any(), "ns", "pod").Return(fmt.Errorf("pod ready error")), + ) + }, + errChecker: NotNil, + podChecker: NotNil, + pvcChecker: NotNil, + }, + { + args: &types.SnapshotBrowseArgs{ + Namespace: "ns", + RunAsUser: 100, + }, + sc: &sv1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sc", + }, + }, + snapshot: &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + }, + Status: &snapv1.VolumeSnapshotStatus{ + RestoreSize: &resourceQuantity, + }, + }, + prepare: func(f *fields) { + gomock.InOrder( + f.createAppOps.EXPECT().CreatePVC(gomock.Any(), gomock.Any()).Return(&v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + }, + }, nil), + f.createAppOps.EXPECT().CreatePod(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("pod error")), + ) + }, + errChecker: NotNil, + podChecker: IsNil, + pvcChecker: NotNil, + }, + { + args: &types.SnapshotBrowseArgs{ + Namespace: "ns", + RunAsUser: 100, + }, + sc: &sv1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sc", + }, + }, + snapshot: &snapv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vs", + }, + Status: &snapv1.VolumeSnapshotStatus{ + RestoreSize: &resourceQuantity, + }, + }, + prepare: func(f *fields) { + gomock.InOrder( + f.createAppOps.EXPECT().CreatePVC(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")), + ) + }, + errChecker: NotNil, + podChecker: IsNil, + pvcChecker: IsNil, + }, + } { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + f := fields{ + createAppOps: mocks.NewMockApplicationCreator(ctrl), + } + if tc.prepare != nil { + tc.prepare(&f) + } + stepper := &snapshotBrowserSteps{ + createAppOps: f.createAppOps, + } + pod, pvc, err := stepper.CreateInspectorApplication(ctx, tc.args, tc.snapshot, tc.sc) + c.Check(err, tc.errChecker) + c.Check(pod, tc.podChecker) + c.Check(pvc, tc.pvcChecker) + } +} + +func (s *CSITestSuite) TestSnapshotBrowseCleanup(c *C) { + ctx := context.Background() + groupversion := &metav1.GroupVersionForDiscovery{ + GroupVersion: "gv", + Version: "v", + } + type fields struct { + cleanerOps *mocks.MockCleaner + } + for _, tc := range []struct { + pvc *v1.PersistentVolumeClaim + pod *v1.Pod + prepare func(f *fields) + }{ + { + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + Namespace: "ns", + }, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "ns", + }, + }, + prepare: func(f *fields) { + gomock.InOrder( + f.cleanerOps.EXPECT().DeletePVC(ctx, "pvc", "ns").Return(nil), + f.cleanerOps.EXPECT().DeletePod(ctx, "pod", "ns").Return(nil), + ) + }, + }, + { + pvc: &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc", + Namespace: "ns", + }, + }, + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "ns", + }, + }, + prepare: func(f *fields) { + gomock.InOrder( + f.cleanerOps.EXPECT().DeletePVC(ctx, "pvc", "ns").Return(fmt.Errorf("err")), + f.cleanerOps.EXPECT().DeletePod(ctx, "pod", "ns").Return(fmt.Errorf("err")), + ) + }, + }, + } { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + f := fields{ + cleanerOps: mocks.NewMockCleaner(ctrl), + } + if tc.prepare != nil { + tc.prepare(&f) + } + stepper := &snapshotBrowserSteps{ + cleanerOps: f.cleanerOps, + SnapshotGroupVersion: groupversion, + } + stepper.Cleanup(ctx, tc.pvc, tc.pod) + } +} diff --git a/pkg/csi/snapshot_inspector_test.go b/pkg/csi/snapshot_inspector_test.go new file mode 100644 index 0000000..22a0232 --- /dev/null +++ b/pkg/csi/snapshot_inspector_test.go @@ -0,0 +1,192 @@ +package csi + +import ( + "context" + "fmt" + + "github.com/golang/mock/gomock" + "github.com/kastenhq/kubestr/pkg/csi/mocks" + "github.com/kastenhq/kubestr/pkg/csi/types" + snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" + . "gopkg.in/check.v1" + v1 "k8s.io/api/core/v1" + sv1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" + fakedynamic "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" +) + +func (s *CSITestSuite) TestRunSnapshotBrowseHelper(c *C) { + ctx := context.Background() + type fields struct { + stepperOps *mocks.MockSnapshotBrowserStepper + } + for _, tc := range []struct { + kubeCli kubernetes.Interface + dynCli dynamic.Interface + args *types.SnapshotBrowseArgs + prepare func(f *fields) + errChecker Checker + }{ + { + // success + kubeCli: fake.NewSimpleClientset(), + dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()), + args: &types.SnapshotBrowseArgs{}, + prepare: func(f *fields) { + gomock.InOrder( + f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return( + &snapv1.VolumeSnapshot{}, &sv1.StorageClass{}, nil, + ), + f.stepperOps.EXPECT().CreateInspectorApplication(gomock.Any(), gomock.Any(), + &snapv1.VolumeSnapshot{}, &sv1.StorageClass{}, + ).Return( + &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "ns", + }, + }, + &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc1", + Namespace: "ns", + }, + }, + nil, + ), + f.stepperOps.EXPECT().PortForwardAPod(gomock.Any(), + &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "ns", + }, + }, gomock.Any(), + ).Return(nil), + f.stepperOps.EXPECT().Cleanup(gomock.Any(), + &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc1", + Namespace: "ns", + }, + }, + &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "ns", + }, + }, + ), + ) + }, + errChecker: IsNil, + }, + { + // portforward failure + kubeCli: fake.NewSimpleClientset(), + dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()), + args: &types.SnapshotBrowseArgs{}, + prepare: func(f *fields) { + gomock.InOrder( + f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil, nil, nil), + f.stepperOps.EXPECT().CreateInspectorApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, nil), + f.stepperOps.EXPECT().PortForwardAPod(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf("portforward error")), + f.stepperOps.EXPECT().Cleanup(gomock.Any(), gomock.Any(), gomock.Any()), + ) + }, + errChecker: NotNil, + }, + { + // createapp failure + kubeCli: fake.NewSimpleClientset(), + dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()), + args: &types.SnapshotBrowseArgs{}, + prepare: func(f *fields) { + gomock.InOrder( + f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil, nil, nil), + f.stepperOps.EXPECT().CreateInspectorApplication(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("createapp error")), + f.stepperOps.EXPECT().Cleanup(gomock.Any(), gomock.Any(), gomock.Any()), + ) + }, + errChecker: NotNil, + }, + { + // fetch snapshot failure + kubeCli: fake.NewSimpleClientset(), + dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()), + args: &types.SnapshotBrowseArgs{}, + prepare: func(f *fields) { + gomock.InOrder( + f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("snapshot error")), + f.stepperOps.EXPECT().Cleanup(gomock.Any(), gomock.Any(), gomock.Any()), + ) + }, + errChecker: NotNil, + }, + { + // validate failure + kubeCli: fake.NewSimpleClientset(), + dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()), + args: &types.SnapshotBrowseArgs{}, + prepare: func(f *fields) { + gomock.InOrder( + f.stepperOps.EXPECT().ValidateArgs(gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("validate error")), + f.stepperOps.EXPECT().Cleanup(gomock.Any(), gomock.Any(), gomock.Any()), + ) + }, + errChecker: NotNil, + }, + { + // emptycli failure + kubeCli: nil, + dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()), + args: &types.SnapshotBrowseArgs{}, + prepare: func(f *fields) { + gomock.InOrder( + f.stepperOps.EXPECT().Cleanup(gomock.Any(), gomock.Any(), gomock.Any()), + ) + }, + errChecker: NotNil, + }, + { + // emptydyncli failure + kubeCli: fake.NewSimpleClientset(), + dynCli: nil, + args: &types.SnapshotBrowseArgs{}, + prepare: func(f *fields) { + gomock.InOrder( + f.stepperOps.EXPECT().Cleanup(gomock.Any(), gomock.Any(), gomock.Any()), + ) + }, + errChecker: NotNil, + }, + } { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + f := fields{ + stepperOps: mocks.NewMockSnapshotBrowserStepper(ctrl), + } + if tc.prepare != nil { + tc.prepare(&f) + } + runner := &SnapshotBrowseRunner{ + KubeCli: tc.kubeCli, + DynCli: tc.dynCli, + browserSteps: f.stepperOps, + } + err := runner.RunSnapshotBrowseHelper(ctx, tc.args) + c.Check(err, tc.errChecker) + } +} + +func (s *CSITestSuite) TestSnapshotBrowseRunner(c *C) { + ctx := context.Background() + r := &SnapshotBrowseRunner{ + browserSteps: &snapshotBrowserSteps{}, + } + err := r.RunSnapshotBrowseHelper(ctx, nil) + c.Check(err, NotNil) +} diff --git a/pkg/csi/types/csi_types.go b/pkg/csi/types/csi_types.go index d09a745..da29d65 100644 --- a/pkg/csi/types/csi_types.go +++ b/pkg/csi/types/csi_types.go @@ -94,6 +94,18 @@ func (c *CreateSnapshotArgs) Validate() error { return nil } +type FetchSnapshotArgs struct { + Namespace string + SnapshotName string +} + +func (c *FetchSnapshotArgs) Validate() error { + if c.Namespace == "" || c.SnapshotName == "" { + return fmt.Errorf("Invalid FetchSnapshotArgs (%v)", c) + } + return nil +} + type CreateFromSourceCheckArgs struct { VolumeSnapshotClass string SnapshotName string @@ -122,6 +134,20 @@ func (p *PVCBrowseArgs) Validate() error { return nil } +type SnapshotBrowseArgs struct { + SnapshotName string + Namespace string + RunAsUser int64 + LocalPort int +} + +func (p *SnapshotBrowseArgs) Validate() error { + if p.SnapshotName == "" || p.Namespace == "" { + return fmt.Errorf("Invalid SnapshotBrowseArgs (%v)", p) + } + return nil +} + type PortForwardAPodRequest struct { // RestConfig is the kubernetes config RestConfig *rest.Config From 8037d9456d0c32221ee48bff4abe80acc38cf446 Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Fri, 2 Aug 2024 15:27:02 -0500 Subject: [PATCH 3/6] Adding --show-tree flag to both "./kubestr browse pvc" & "./kubestr browse snapshot" commands (#278) * Adding the kubestr browse pvc command. Handling kubestr browse support for backward compatibility. * Adding browse snapshot command. Updating browse command to browse pvc command. * chore(deps): bump github/codeql-action in the github-actions group (#272) Bumps the github-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.25.12 to 3.25.13 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/4fa2a7953630fd2f3fb380f21be14ede0169dd4f...2d790406f505036ef40ecba973cc774a50395aac) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump docker/build-push-action in the docker group (#273) Bumps the docker group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.3.0 to 6.4.1 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/1a162644f9a7e87d8f4b053101d1d9a712edc18c...1ca370b3a9802c92e886402e0dd88098a2533b12) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Adding --show-tree flag for browse snapshot & browse pvc commands * Removing unused snapshot function parameter in cleanup * Adding KubeExecutor Exec helper function to execute tree command * Adding --show-tree logic in pvc_inspector.go * Adding --show-tree logic in snapshot_inspector.go * Printing out the tree structure for --show-tree * Updating mock tests for new code changes * Updating mount path in container args for creating a browse pod * Updating the CSITestSuite.TestCreateInspectorApplication for changes in the mount path * Adding Deprecated msg to the 'browse' command * Adding mock tests for SnapshotBrowserStepper * Adding fake tests for snapshot_inspector.go * Renamed testcase CSITestSuite.TestCreateInspectorApplication to TestCreateInspectorApplicationForPVC * Adding snapshot_inspector_steps_test.go * Updating mock tests for new code changes * Updating the mount paths in CSITestSuite.TestCreateInspectorApplicationForSnapshot * Updating Deprecated msg for 'browse' command * Making namespace, runAsUser & localport flags persistent * Removing namespace, runAsUser & localport flags for browse snapshot because we made those persistent * Adding --show-tree flag for browse snapshot & browse pvc commands * Updating namespace flag usage for better understanding * Removing storage class flag * Adding --show-tree logic in snapshot_inspector.go * Updating mock objects for SnapshotBrowserStepper * Adding --show-tree flag for browse snapshot & browse pvc commands * Removing storage class flag * Adding --show-tree flag for browse snapshot & browse pvc commands * Adding --show-tree logic in snapshot_inspector.go * Passing showTree var as function argument * Making --show-tree a persistent flag * Removing ShowTree dummy condition * Removing duplicate browseSnapshotCmd * Adding --show-tree flag for browse snapshot & browse pvc commands * Making --show-tree a persistent flag * Adding --show-tree flag for browse snapshot & browse pvc commands * Making --show-tree a persistent flag --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cmd/rootCmd.go | 9 ++++ pkg/csi/csi_ops.go | 17 +++++++ pkg/csi/mocks/mock_kube_executor.go | 50 +++++++++++++++++++ pkg/csi/mocks/mock_pvc_browser_stepper.go | 15 ++++++ .../mocks/mock_snapshot_browser_stepper.go | 15 ++++++ pkg/csi/pvc_inspector.go | 50 +++++++++++++++++-- pkg/csi/pvc_inspector_steps_test.go | 8 +-- pkg/csi/snapshot_inspector.go | 48 ++++++++++++++++-- pkg/csi/snapshot_inspector_steps_test.go | 8 +-- pkg/csi/types/csi_types.go | 2 + 10 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 pkg/csi/mocks/mock_kube_executor.go diff --git a/cmd/rootCmd.go b/cmd/rootCmd.go index d53356e..023b900 100644 --- a/cmd/rootCmd.go +++ b/cmd/rootCmd.go @@ -95,6 +95,8 @@ var ( }, } + showTree bool + browsePvcCmd = &cobra.Command{ Use: "pvc [PVC name]", Short: "Browse the contents of a CSI PVC via file browser", @@ -106,6 +108,7 @@ var ( csiCheckVolumeSnapshotClass, csiCheckRunAsUser, browseLocalPort, + showTree, ) }, } @@ -120,6 +123,7 @@ var ( namespace, csiCheckRunAsUser, browseLocalPort, + showTree, ) }, } @@ -195,6 +199,7 @@ func init() { browseCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", fio.DefaultNS, "The namespace of the resource to browse.") browseCmd.PersistentFlags().Int64VarP(&csiCheckRunAsUser, "runAsUser", "u", 0, "Runs the inspector pod as a user (int)") browseCmd.PersistentFlags().IntVarP(&browseLocalPort, "localport", "l", 8080, "The local port to expose the inspector") + browseCmd.PersistentFlags().BoolVarP(&showTree, "show-tree", "t", false, "Prints the contents of given PVC or VolumeSnapshot") browseCmd.AddCommand(browsePvcCmd) browsePvcCmd.Flags().StringVarP(&csiCheckVolumeSnapshotClass, "volumesnapshotclass", "v", "", "The name of a VolumeSnapshotClass. (Required)") @@ -361,6 +366,7 @@ func CsiPvcBrowse(ctx context.Context, volumeSnapshotClass string, runAsUser int64, localPort int, + showTree bool, ) error { kubecli, err := kubestr.LoadKubeCli() if err != nil { @@ -382,6 +388,7 @@ func CsiPvcBrowse(ctx context.Context, VolumeSnapshotClass: volumeSnapshotClass, RunAsUser: runAsUser, LocalPort: localPort, + ShowTree: showTree, }) if err != nil { fmt.Printf("Failed to run PVC browser (%s)\n", err.Error()) @@ -394,6 +401,7 @@ func CsiSnapshotBrowse(ctx context.Context, namespace string, runAsUser int64, localPort int, + showTree bool, ) error { kubecli, err := kubestr.LoadKubeCli() if err != nil { @@ -414,6 +422,7 @@ func CsiSnapshotBrowse(ctx context.Context, Namespace: namespace, RunAsUser: runAsUser, LocalPort: localPort, + ShowTree: showTree, }) if err != nil { fmt.Printf("Failed to run Snapshot browser (%s)\n", err.Error()) diff --git a/pkg/csi/csi_ops.go b/pkg/csi/csi_ops.go index 280a8d2..cbca361 100644 --- a/pkg/csi/csi_ops.go +++ b/pkg/csi/csi_ops.go @@ -568,3 +568,20 @@ func (p *portforward) PortForwardAPod(req *types.PortForwardAPodRequest) error { func (p *portforward) FetchRestConfig() (*rest.Config, error) { return kube.LoadConfig() } + +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_kube_executor.go -package=mocks . KubeExecutor +type KubeExecutor interface { + Exec(ctx context.Context, namespace string, podName string, ContainerName string, command []string) (string, error) +} + +type kubeExec struct { + kubeCli kubernetes.Interface +} + +func (k *kubeExec) Exec(ctx context.Context, namespace string, podName string, ContainerName string, command []string) (string, error) { + if k.kubeCli == nil { + return "", fmt.Errorf("kubeCli not initialized") + } + stdout, _, err := kankube.Exec(ctx, k.kubeCli, namespace, podName, ContainerName, command, nil) + return stdout, err +} diff --git a/pkg/csi/mocks/mock_kube_executor.go b/pkg/csi/mocks/mock_kube_executor.go new file mode 100644 index 0000000..aabcbb2 --- /dev/null +++ b/pkg/csi/mocks/mock_kube_executor.go @@ -0,0 +1,50 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/kastenhq/kubestr/pkg/csi (interfaces: KubeExecutor) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockKubeExecutor is a mock of KubeExecutor interface. +type MockKubeExecutor struct { + ctrl *gomock.Controller + recorder *MockKubeExecutorMockRecorder +} + +// MockKubeExecutorMockRecorder is the mock recorder for MockKubeExecutor. +type MockKubeExecutorMockRecorder struct { + mock *MockKubeExecutor +} + +// NewMockKubeExecutor creates a new mock instance. +func NewMockKubeExecutor(ctrl *gomock.Controller) *MockKubeExecutor { + mock := &MockKubeExecutor{ctrl: ctrl} + mock.recorder = &MockKubeExecutorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockKubeExecutor) EXPECT() *MockKubeExecutorMockRecorder { + return m.recorder +} + +// Exec mocks base method. +func (m *MockKubeExecutor) Exec(arg0 context.Context, arg1, arg2, arg3 string, arg4 []string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exec", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Exec indicates an expected call of Exec. +func (mr *MockKubeExecutorMockRecorder) Exec(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exec", reflect.TypeOf((*MockKubeExecutor)(nil).Exec), arg0, arg1, arg2, arg3, arg4) +} diff --git a/pkg/csi/mocks/mock_pvc_browser_stepper.go b/pkg/csi/mocks/mock_pvc_browser_stepper.go index a26efc5..3dade31 100644 --- a/pkg/csi/mocks/mock_pvc_browser_stepper.go +++ b/pkg/csi/mocks/mock_pvc_browser_stepper.go @@ -66,6 +66,21 @@ func (mr *MockPVCBrowserStepperMockRecorder) CreateInspectorApplication(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInspectorApplication", reflect.TypeOf((*MockPVCBrowserStepper)(nil).CreateInspectorApplication), arg0, arg1, arg2, arg3) } +// ExecuteTreeCommand mocks base method. +func (m *MockPVCBrowserStepper) ExecuteTreeCommand(arg0 context.Context, arg1 *types.PVCBrowseArgs, arg2 *v10.Pod) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteTreeCommand", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteTreeCommand indicates an expected call of ExecuteTreeCommand. +func (mr *MockPVCBrowserStepperMockRecorder) ExecuteTreeCommand(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteTreeCommand", reflect.TypeOf((*MockPVCBrowserStepper)(nil).ExecuteTreeCommand), arg0, arg1, arg2) +} + // PortForwardAPod mocks base method. func (m *MockPVCBrowserStepper) PortForwardAPod(arg0 context.Context, arg1 *v10.Pod, arg2 int) error { m.ctrl.T.Helper() diff --git a/pkg/csi/mocks/mock_snapshot_browser_stepper.go b/pkg/csi/mocks/mock_snapshot_browser_stepper.go index 4c5bb02..26b2349 100644 --- a/pkg/csi/mocks/mock_snapshot_browser_stepper.go +++ b/pkg/csi/mocks/mock_snapshot_browser_stepper.go @@ -66,6 +66,21 @@ func (mr *MockSnapshotBrowserStepperMockRecorder) CreateInspectorApplication(arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInspectorApplication", reflect.TypeOf((*MockSnapshotBrowserStepper)(nil).CreateInspectorApplication), arg0, arg1, arg2, arg3) } +// ExecuteTreeCommand mocks base method. +func (m *MockSnapshotBrowserStepper) ExecuteTreeCommand(arg0 context.Context, arg1 *types.SnapshotBrowseArgs, arg2 *v10.Pod) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteTreeCommand", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecuteTreeCommand indicates an expected call of ExecuteTreeCommand. +func (mr *MockSnapshotBrowserStepperMockRecorder) ExecuteTreeCommand(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteTreeCommand", reflect.TypeOf((*MockSnapshotBrowserStepper)(nil).ExecuteTreeCommand), arg0, arg1, arg2) +} + // PortForwardAPod mocks base method. func (m *MockSnapshotBrowserStepper) PortForwardAPod(arg0 context.Context, arg1 *v10.Pod, arg2 int) error { m.ctrl.T.Helper() diff --git a/pkg/csi/pvc_inspector.go b/pkg/csi/pvc_inspector.go index 82fa838..7ef5be7 100644 --- a/pkg/csi/pvc_inspector.go +++ b/pkg/csi/pvc_inspector.go @@ -49,11 +49,18 @@ func (r *PVCBrowseRunner) RunPVCBrowse(ctx context.Context, args *types.PVCBrows dynCli: r.DynCli, }, portForwardOps: &portforward{}, + kubeExecutor: &kubeExec{ + kubeCli: r.KubeCli, + }, cleanerOps: &cleanse{ kubeCli: r.KubeCli, dynCli: r.DynCli, }, } + if args.ShowTree { + fmt.Println("Show Tree works for PVC!") + return nil + } return r.RunPVCBrowseHelper(ctx, args) } @@ -70,19 +77,29 @@ func (r *PVCBrowseRunner) RunPVCBrowseHelper(ctx context.Context, args *types.PV return errors.Wrap(err, "Failed to validate arguments.") } - fmt.Println("Taking a snapshot") + fmt.Println("Taking a snapshot.") snapName := snapshotPrefix + time.Now().Format("20060102150405") r.snapshot, err = r.browserSteps.SnapshotPVC(ctx, args, snapName) if err != nil { return errors.Wrap(err, "Failed to snapshot PVC.") } - fmt.Println("Creating the file browser application.") + fmt.Println("Creating the browser pod.") r.pod, r.pvc, err = r.browserSteps.CreateInspectorApplication(ctx, args, r.snapshot, sc) if err != nil { return errors.Wrap(err, "Failed to create inspector application.") } + if args.ShowTree { + fmt.Println("Printing the tree structure from root directory.") + stdout, err := r.browserSteps.ExecuteTreeCommand(ctx, args, r.pod) + if err != nil { + return errors.Wrap(err, "Failed to execute tree command in pod.") + } + fmt.Printf("\n%s\n\n", stdout) + return nil + } + fmt.Println("Forwarding the port.") err = r.browserSteps.PortForwardAPod(ctx, r.pod, args.LocalPort) if err != nil { @@ -97,6 +114,7 @@ type PVCBrowserStepper interface { ValidateArgs(ctx context.Context, args *types.PVCBrowseArgs) (*sv1.StorageClass, error) SnapshotPVC(ctx context.Context, args *types.PVCBrowseArgs, snapshotName string) (*snapv1.VolumeSnapshot, error) CreateInspectorApplication(ctx context.Context, args *types.PVCBrowseArgs, snapshot *snapv1.VolumeSnapshot, storageClass *sv1.StorageClass) (*v1.Pod, *v1.PersistentVolumeClaim, error) + ExecuteTreeCommand(ctx context.Context, args *types.PVCBrowseArgs, pod *v1.Pod) (string, error) PortForwardAPod(ctx context.Context, pod *v1.Pod, localPort int) error Cleanup(ctx context.Context, pvc *v1.PersistentVolumeClaim, pod *v1.Pod, snapshot *snapv1.VolumeSnapshot) } @@ -108,6 +126,7 @@ type pvcBrowserSteps struct { snapshotCreateOps SnapshotCreator portForwardOps PortForwarder cleanerOps Cleaner + kubeExecutor KubeExecutor SnapshotGroupVersion *metav1.GroupVersionForDiscovery } @@ -194,12 +213,24 @@ func (p *pvcBrowserSteps) CreateInspectorApplication(ctx context.Context, args * Namespace: args.Namespace, RunAsUser: args.RunAsUser, ContainerImage: "filebrowser/filebrowser:v2", - ContainerArgs: []string{"--noauth", "-r", "/data"}, - MountPath: "/data", + ContainerArgs: []string{"--noauth", "-r", "/pvc-data"}, + MountPath: "/pvc-data", + } + if args.ShowTree { + podArgs = &types.CreatePodArgs{ + GenerateName: clonedPodGenerateName, + PVCName: pvc.Name, + Namespace: args.Namespace, + RunAsUser: args.RunAsUser, + ContainerImage: "alpine:3.19", + Command: []string{"/bin/sh"}, + ContainerArgs: []string{"-c", "while true; do sleep 3600; done"}, + MountPath: "/pvc-data", + } } pod, err := p.createAppOps.CreatePod(ctx, podArgs) if err != nil { - return nil, pvc, errors.Wrap(err, "Failed to create restored Pod") + return nil, pvc, errors.Wrap(err, "Failed to create browse Pod") } if err = p.createAppOps.WaitForPodReady(ctx, args.Namespace, pod.Name); err != nil { return pod, pvc, errors.Wrap(err, "Pod failed to become ready") @@ -207,6 +238,15 @@ func (p *pvcBrowserSteps) CreateInspectorApplication(ctx context.Context, args * return pod, pvc, nil } +func (p *pvcBrowserSteps) ExecuteTreeCommand(ctx context.Context, args *types.PVCBrowseArgs, pod *v1.Pod) (string, error) { + command := []string{"tree", "/pvc-data"} + stdout, err := p.kubeExecutor.Exec(ctx, args.Namespace, pod.Name, pod.Spec.Containers[0].Name, command) + if err != nil { + return "", errors.Wrapf(err, "Error running command:(%v)", command) + } + return stdout, nil +} + func (p *pvcBrowserSteps) PortForwardAPod(ctx context.Context, pod *v1.Pod, localPort int) error { var wg sync.WaitGroup wg.Add(1) diff --git a/pkg/csi/pvc_inspector_steps_test.go b/pkg/csi/pvc_inspector_steps_test.go index a78649c..25d6a62 100644 --- a/pkg/csi/pvc_inspector_steps_test.go +++ b/pkg/csi/pvc_inspector_steps_test.go @@ -541,8 +541,8 @@ func (s *CSITestSuite) TestCreateInspectorApplicationForPVC(c *C) { GenerateName: clonedPodGenerateName, PVCName: "pvc1", Namespace: "ns", - ContainerArgs: []string{"--noauth", "-r", "/data"}, - MountPath: "/data", + ContainerArgs: []string{"--noauth", "-r", "/pvc-data"}, + MountPath: "/pvc-data", RunAsUser: 100, ContainerImage: "filebrowser/filebrowser:v2", }).Return(&v1.Pod{ @@ -596,8 +596,8 @@ func (s *CSITestSuite) TestCreateInspectorApplicationForPVC(c *C) { GenerateName: clonedPodGenerateName, PVCName: "pvc1", Namespace: "ns", - ContainerArgs: []string{"--noauth", "-r", "/data"}, - MountPath: "/data", + ContainerArgs: []string{"--noauth", "-r", "/pvc-data"}, + MountPath: "/pvc-data", RunAsUser: 100, ContainerImage: "filebrowser/filebrowser:v2", }).Return(&v1.Pod{ diff --git a/pkg/csi/snapshot_inspector.go b/pkg/csi/snapshot_inspector.go index 9e2645a..442b579 100644 --- a/pkg/csi/snapshot_inspector.go +++ b/pkg/csi/snapshot_inspector.go @@ -44,11 +44,18 @@ func (r *SnapshotBrowseRunner) RunSnapshotBrowse(ctx context.Context, args *type dynCli: r.DynCli, }, portForwardOps: &portforward{}, + kubeExecutor: &kubeExec{ + kubeCli: r.KubeCli, + }, cleanerOps: &cleanse{ kubeCli: r.KubeCli, dynCli: r.DynCli, }, } + if args.ShowTree { + fmt.Println("Show Tree works for VS!") + return nil + } return r.RunSnapshotBrowseHelper(ctx, args) } @@ -69,12 +76,22 @@ func (r *SnapshotBrowseRunner) RunSnapshotBrowseHelper(ctx context.Context, args } r.snapshot = vs - fmt.Println("Creating the file browser application.") + fmt.Println("Creating the browser pod.") r.pod, r.pvc, err = r.browserSteps.CreateInspectorApplication(ctx, args, r.snapshot, sc) if err != nil { return errors.Wrap(err, "Failed to create inspector application.") } + if args.ShowTree { + fmt.Println("Printing the tree structure from root directory.") + stdout, err := r.browserSteps.ExecuteTreeCommand(ctx, args, r.pod) + if err != nil { + return errors.Wrap(err, "Failed to execute tree command in pod.") + } + fmt.Printf("\n%s\n\n", stdout) + return nil + } + fmt.Println("Forwarding the port.") err = r.browserSteps.PortForwardAPod(ctx, r.pod, args.LocalPort) if err != nil { @@ -88,6 +105,7 @@ func (r *SnapshotBrowseRunner) RunSnapshotBrowseHelper(ctx context.Context, args type SnapshotBrowserStepper interface { ValidateArgs(ctx context.Context, args *types.SnapshotBrowseArgs) (*snapv1.VolumeSnapshot, *sv1.StorageClass, error) CreateInspectorApplication(ctx context.Context, args *types.SnapshotBrowseArgs, snapshot *snapv1.VolumeSnapshot, storageClass *sv1.StorageClass) (*v1.Pod, *v1.PersistentVolumeClaim, error) + ExecuteTreeCommand(ctx context.Context, args *types.SnapshotBrowseArgs, pod *v1.Pod) (string, error) PortForwardAPod(ctx context.Context, pod *v1.Pod, localPort int) error Cleanup(ctx context.Context, pvc *v1.PersistentVolumeClaim, pod *v1.Pod) } @@ -99,6 +117,7 @@ type snapshotBrowserSteps struct { createAppOps ApplicationCreator portForwardOps PortForwarder cleanerOps Cleaner + kubeExecutor KubeExecutor SnapshotGroupVersion *metav1.GroupVersionForDiscovery } @@ -162,12 +181,24 @@ func (s *snapshotBrowserSteps) CreateInspectorApplication(ctx context.Context, a Namespace: args.Namespace, RunAsUser: args.RunAsUser, ContainerImage: "filebrowser/filebrowser:v2", - ContainerArgs: []string{"--noauth", "-r", "/data"}, - MountPath: "/data", + ContainerArgs: []string{"--noauth", "-r", "/snapshot-data"}, + MountPath: "/snapshot-data", + } + if args.ShowTree { + podArgs = &types.CreatePodArgs{ + GenerateName: clonedPodGenerateName, + PVCName: pvc.Name, + Namespace: args.Namespace, + RunAsUser: args.RunAsUser, + ContainerImage: "alpine:3.19", + Command: []string{"/bin/sh"}, + ContainerArgs: []string{"-c", "while true; do sleep 3600; done"}, + MountPath: "/snapshot-data", + } } pod, err := s.createAppOps.CreatePod(ctx, podArgs) if err != nil { - return nil, pvc, errors.Wrap(err, "Failed to create restored Pod") + return nil, pvc, errors.Wrap(err, "Failed to create browse Pod") } if err = s.createAppOps.WaitForPodReady(ctx, args.Namespace, pod.Name); err != nil { return pod, pvc, errors.Wrap(err, "Pod failed to become ready") @@ -175,6 +206,15 @@ func (s *snapshotBrowserSteps) CreateInspectorApplication(ctx context.Context, a return pod, pvc, nil } +func (s *snapshotBrowserSteps) ExecuteTreeCommand(ctx context.Context, args *types.SnapshotBrowseArgs, pod *v1.Pod) (string, error) { + command := []string{"tree", "/snapshot-data"} + stdout, err := s.kubeExecutor.Exec(ctx, args.Namespace, pod.Name, pod.Spec.Containers[0].Name, command) + if err != nil { + return "", errors.Wrapf(err, "Error running command:(%v)", command) + } + return stdout, nil +} + func (s *snapshotBrowserSteps) PortForwardAPod(ctx context.Context, pod *v1.Pod, localPort int) error { var wg sync.WaitGroup wg.Add(1) diff --git a/pkg/csi/snapshot_inspector_steps_test.go b/pkg/csi/snapshot_inspector_steps_test.go index 1f6e767..423c8ac 100644 --- a/pkg/csi/snapshot_inspector_steps_test.go +++ b/pkg/csi/snapshot_inspector_steps_test.go @@ -345,8 +345,8 @@ func (s *CSITestSuite) TestCreateInspectorApplicationForSnapshot(c *C) { GenerateName: clonedPodGenerateName, PVCName: "pvc", Namespace: "ns", - ContainerArgs: []string{"--noauth", "-r", "/data"}, - MountPath: "/data", + ContainerArgs: []string{"--noauth", "-r", "/snapshot-data"}, + MountPath: "/snapshot-data", RunAsUser: 100, ContainerImage: "filebrowser/filebrowser:v2", }).Return(&v1.Pod{ @@ -400,8 +400,8 @@ func (s *CSITestSuite) TestCreateInspectorApplicationForSnapshot(c *C) { GenerateName: clonedPodGenerateName, PVCName: "pvc", Namespace: "ns", - ContainerArgs: []string{"--noauth", "-r", "/data"}, - MountPath: "/data", + ContainerArgs: []string{"--noauth", "-r", "/snapshot-data"}, + MountPath: "/snapshot-data", RunAsUser: 100, ContainerImage: "filebrowser/filebrowser:v2", }).Return(&v1.Pod{ diff --git a/pkg/csi/types/csi_types.go b/pkg/csi/types/csi_types.go index da29d65..2755582 100644 --- a/pkg/csi/types/csi_types.go +++ b/pkg/csi/types/csi_types.go @@ -125,6 +125,7 @@ type PVCBrowseArgs struct { VolumeSnapshotClass string RunAsUser int64 LocalPort int + ShowTree bool } func (p *PVCBrowseArgs) Validate() error { @@ -139,6 +140,7 @@ type SnapshotBrowseArgs struct { Namespace string RunAsUser int64 LocalPort int + ShowTree bool } func (p *SnapshotBrowseArgs) Validate() error { From 8811100a762a782d3b8866636d26e69d15e39773 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:23:35 -0700 Subject: [PATCH 4/6] chore(deps): bump the github-actions group across 1 directory with 2 updates (#282) Bumps the github-actions group with 2 updates in the / directory: [github/codeql-action](https://github.com/github/codeql-action) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `github/codeql-action` from 3.25.13 to 3.25.15 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2d790406f505036ef40ecba973cc774a50395aac...afb54ba388a7dca6ecae48f608c4ff05ff4cc77a) Updates `actions/upload-artifact` from 4.3.4 to 4.3.5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/0b2256b8c012f0828dc542b3febcab082c67f72b...89ef406dd8d7e03cfd12d9e0a4a378f454709029) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 2f71fea..811131a 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -45,12 +45,12 @@ jobs: - # Upload the results to GitHub's code scanning dashboard. name: "Upload to results to dashboard" - uses: github/codeql-action/upload-sarif@2d790406f505036ef40ecba973cc774a50395aac # v3.25.13 + uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 with: sarif_file: results.sarif - name: "Upload analysis results as 'Job Artifact'" - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 with: name: SARIF file path: results.sarif From dfb68da33c3fc63a699a8045cde03d57f6980a90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:24:06 -0700 Subject: [PATCH 5/6] chore(deps): bump the docker group across 1 directory with 4 updates (#283) Bumps the docker group with 4 updates in the / directory: [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action), [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/login-action](https://github.com/docker/login-action) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/setup-qemu-action` from 3.1.0 to 3.2.0 - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/5927c834f5b4fdf503fca6f4c7eccda82949e1ee...49b3bc8e6bdd4a60e6116a5414239cba5943d3cf) Updates `docker/setup-buildx-action` from 3.4.0 to 3.6.1 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/4fd812986e6c8c2a69e18311145f9371337f27d4...988b5a0280414f521da01fcc63a27aeeb4b104db) Updates `docker/login-action` from 3.2.0 to 3.3.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/0d4c9c5ea7693da7b068278f7b52bda2a190a446...9780b0c442fbb1117ed29e0efdff1e18412f7567) Updates `docker/build-push-action` from 6.4.1 to 6.5.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/1ca370b3a9802c92e886402e0dd88098a2533b12...5176d81f87c23d6fc96624dfdbcd9f3830bbe445) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 1c625e6..c11bf27 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -40,17 +40,17 @@ jobs: # This action can be useful if you want to add emulation # support with QEMU to be able to build against more platforms. - name: Set up QEMU - uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v3.1.0 + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 # This action will create and boot a builder using # by default the docker-container builder driver. # Recommended for build multi-platform images, export cache, etc. - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Log into ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -59,7 +59,7 @@ jobs: # Build and push Docker image with Buildx (don't push on PR) # https://github.com/docker/build-push-action - name: Build and push Docker image - uses: docker/build-push-action@1ca370b3a9802c92e886402e0dd88098a2533b12 # v6.4.1 + uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 with: context: . push: ${{ github.event_name != 'pull_request' }} From 9a28659ba859b0aea5b5d76cce1e289b114efa17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:24:33 -0700 Subject: [PATCH 6/6] chore(deps): bump ossf/scorecard-action from 2.3.3 to 2.4.0 (#281) Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.3 to 2.4.0. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/dc50aa9510b46c811795eb24b2f1ba02a914e534...62b2cac7ed8198b15735ed49ab1e5cf35480ba46) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 811131a..78ff23e 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif