1
0
Fork 0
mirror of https://github.com/kastenhq/kubestr.git synced 2024-12-14 11:57:56 +00:00

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](4fa2a79536...2d790406f5)

---
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] <support@github.com>
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](1a162644f9...1ca370b3a9)

---
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] <support@github.com>
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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Shlok Chaudhari 2024-08-02 15:27:02 -05:00 committed by GitHub
parent 2cf1758a0c
commit 8037d9456d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 205 additions and 17 deletions

View file

@ -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())

View file

@ -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
}

View file

@ -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)
}

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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{

View file

@ -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)

View file

@ -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{

View file

@ -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 {