1
0
Fork 0
mirror of https://github.com/kastenhq/kubestr.git synced 2024-12-15 17:50:57 +00:00
kastenhq-kubestr/pkg/csi/snapshot_inspector.go

240 lines
7.7 KiB
Go
Raw Normal View History

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] <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](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] <support@github.com> 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 <sirish@kasten.io> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sirish Bathina <sirish@kasten.io>
2024-08-02 02:06:34 +00:00
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)
}
}
}