diff --git a/pkg/function/checkRepository.go b/pkg/function/checkRepository.go new file mode 100644 index 0000000000..7bb65b1089 --- /dev/null +++ b/pkg/function/checkRepository.go @@ -0,0 +1,125 @@ +package function + +import ( + "context" + "strings" + + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + + kanister "github.com/kanisterio/kanister/pkg" + crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1" + "github.com/kanisterio/kanister/pkg/kube" + "github.com/kanisterio/kanister/pkg/param" + "github.com/kanisterio/kanister/pkg/restic" +) + +const ( + // CheckRepositoryArtifactPrefixArg provides the path to restore backed up data + CheckRepositoryArtifactPrefixArg = "backupArtifactPrefix" + // CheckRepositoryEncryptionKeyArg provides the encryption key to be used for deletes + CheckRepositoryEncryptionKeyArg = "encryptionKey" + // CheckRepositoryPodOverrideArg contains pod specs to override default pod specs + CheckRepositoryPodOverrideArg = "podOverride" + CheckRepositoryJobPrefix = "check-repository-" + CheckRepositoryPasswordIncorrect = "passwordIncorrect" + CheckRepositoryRepoDoesNotExist = "repoUnavailable" +) + +func init() { + kanister.Register(&CheckRepositoryFunc{}) +} + +var _ kanister.Func = (*CheckRepositoryFunc)(nil) + +type CheckRepositoryFunc struct{} + +func (*CheckRepositoryFunc) Name() string { + return "CheckRepository" +} + +func CheckRepository(ctx context.Context, cli kubernetes.Interface, tp param.TemplateParams, encryptionKey, targetPaths, jobPrefix string, podOverride crv1alpha1.JSONMap) (map[string]interface{}, error) { + namespace, err := kube.GetControllerNamespace() + if err != nil { + return nil, errors.Wrapf(err, "Failed to get controller namespace") + } + options := &kube.PodOptions{ + Namespace: namespace, + GenerateName: jobPrefix, + Image: kanisterToolsImage, + Command: []string{"sh", "-c", "tail -f /dev/null"}, + PodOverride: podOverride, + } + pr := kube.NewPodRunner(cli, options) + podFunc := CheckRepositoryPodFunc(cli, tp, namespace, encryptionKey, targetPaths) + return pr.Run(ctx, podFunc) +} + +func CheckRepositoryPodFunc(cli kubernetes.Interface, tp param.TemplateParams, namespace, encryptionKey, targetPath string) func(ctx context.Context, pod *v1.Pod) (map[string]interface{}, error) { + return func(ctx context.Context, pod *v1.Pod) (map[string]interface{}, error) { + // Wait for pod to reach running state + if err := kube.WaitForPodReady(ctx, cli, pod.Namespace, pod.Name); err != nil { + return nil, errors.Wrapf(err, "Failed while waiting for Pod %s to be ready", pod.Name) + } + pw, err := GetPodWriter(cli, ctx, pod.Namespace, pod.Name, pod.Spec.Containers[0].Name, tp.Profile) + if err != nil { + return nil, err + } + defer CleanUpCredsFile(ctx, pw, pod.Namespace, pod.Name, pod.Spec.Containers[0].Name) + err = restic.CheckIfRepoIsReachable(tp.Profile, targetPath, encryptionKey, cli, namespace, pod.Name, pod.Spec.Containers[0].Name) + switch { + case err == nil: + break + case strings.Contains(err.Error(), restic.PasswordIncorrect): + return map[string]interface{}{ + CheckRepositoryPasswordIncorrect: "true", + CheckRepositoryRepoDoesNotExist: "false", + }, + nil + + case strings.Contains(err.Error(), restic.RepoDoesNotExist): + return map[string]interface{}{ + + CheckRepositoryPasswordIncorrect: "false", + CheckRepositoryRepoDoesNotExist: "true", + }, + nil + default: + return nil, err + + } + return map[string]interface{}{ + CheckRepositoryPasswordIncorrect: "false", + CheckRepositoryRepoDoesNotExist: "false", + }, + nil + } +} + +func (*CheckRepositoryFunc) Exec(ctx context.Context, tp param.TemplateParams, args map[string]interface{}) (map[string]interface{}, error) { + var getCheckRepositoryArtifactPrefix, encryptionKey string + if err := Arg(args, CheckRepositoryArtifactPrefixArg, &getCheckRepositoryArtifactPrefix); err != nil { + return nil, err + } + if err := OptArg(args, CheckRepositoryEncryptionKeyArg, &encryptionKey, restic.GeneratePassword()); err != nil { + return nil, err + } + podOverride, err := GetPodSpecOverride(tp, args, CheckRepositoryPodOverrideArg) + if err != nil { + return nil, err + } + + if err = ValidateProfile(tp.Profile); err != nil { + return nil, err + } + cli, err := kube.NewClient() + if err != nil { + return nil, errors.Wrapf(err, "Failed to create Kubernetes client") + } + return CheckRepository(ctx, cli, tp, encryptionKey, getCheckRepositoryArtifactPrefix, CheckRepositoryJobPrefix, podOverride) +} + +func (*CheckRepositoryFunc) RequiredArgs() []string { + return []string{CheckRepositoryArtifactPrefixArg} +} diff --git a/pkg/function/data_test.go b/pkg/function/data_test.go index a0b3ee7d4d..b998f4de00 100644 --- a/pkg/function/data_test.go +++ b/pkg/function/data_test.go @@ -189,6 +189,26 @@ func newDescribeBackupsBlueprint() *crv1alpha1.Blueprint { } } +func newCheckRepositoryBlueprint() *crv1alpha1.Blueprint { + return &crv1alpha1.Blueprint{ + Actions: map[string]*crv1alpha1.BlueprintAction{ + "checkRepository": &crv1alpha1.BlueprintAction{ + Kind: param.StatefulSetKind, + Phases: []crv1alpha1.BlueprintPhase{ + crv1alpha1.BlueprintPhase{ + Name: "testCheckRepository", + Func: "CheckRepository", + Args: map[string]interface{}{ + CheckRepositoryArtifactPrefixArg: "{{ .Profile.Location.Bucket }}/{{ .Profile.Location.Prefix }}", + CheckRepositoryEncryptionKeyArg: "{{ .Secrets.backupKey.Data.password | toString }}", + }, + }, + }, + }, + }, + } +} + func newLocationDeleteBlueprint() *crv1alpha1.Blueprint { return &crv1alpha1.Blueprint{ Actions: map[string]*crv1alpha1.BlueprintAction{ @@ -614,3 +634,53 @@ func (s *DataSuite) TestDescribeBackupsRepoNotAvailable(c *C) { out2 := runAction(c, bp2, "describeBackups", tp) c.Assert(out2[DescribeBackupsRepoDoesNotExist].(string), Equals, "true") } + +func (s *DataSuite) TestCheckRepository(c *C) { + tp, _ := s.getTemplateParamsAndPVCName(c, 1) + + // Test backup + bp := *newBackupDataBlueprint() + out := runAction(c, bp, "backup", tp) + c.Assert(out[BackupDataOutputBackupID].(string), Not(Equals), "") + c.Assert(out[BackupDataOutputBackupTag].(string), Not(Equals), "") + + // Test CheckRepository + bp2 := *newCheckRepositoryBlueprint() + out2 := runAction(c, bp2, "checkRepository", tp) + c.Assert(out2[CheckRepositoryPasswordIncorrect].(string), Equals, "false") + c.Assert(out2[CheckRepositoryRepoDoesNotExist].(string), Equals, "false") +} + +func (s *DataSuite) TestCheckRepositoryWrongPassword(c *C) { + tp, _ := s.getTemplateParamsAndPVCName(c, 1) + + // Test backup + bp := *newBackupDataBlueprint() + bp.Actions["backup"].Phases[0].Args[BackupDataBackupArtifactPrefixArg] = fmt.Sprintf("%s/%s", bp.Actions["backup"].Phases[0].Args[BackupDataBackupArtifactPrefixArg], "abcdef") + bp.Actions["backup"].Phases[0].Args[BackupDataEncryptionKeyArg] = "foobar" + out := runAction(c, bp, "backup", tp) + c.Assert(out[BackupDataOutputBackupID].(string), Not(Equals), "") + c.Assert(out[BackupDataOutputBackupTag].(string), Not(Equals), "") + + // Test CheckRepository + bp2 := *newCheckRepositoryBlueprint() + bp2.Actions["checkRepository"].Phases[0].Args[CheckRepositoryArtifactPrefixArg] = fmt.Sprintf("%s/%s", bp2.Actions["checkRepository"].Phases[0].Args[CheckRepositoryArtifactPrefixArg], "abcdef") + out2 := runAction(c, bp2, "checkRepository", tp) + c.Assert(out2[CheckRepositoryPasswordIncorrect].(string), Equals, "true") +} + +func (s *DataSuite) TestCheckRepositoryRepoNotAvailable(c *C) { + tp, _ := s.getTemplateParamsAndPVCName(c, 1) + + // Test backup + bp := *newBackupDataBlueprint() + out := runAction(c, bp, "backup", tp) + c.Assert(out[BackupDataOutputBackupID].(string), Not(Equals), "") + c.Assert(out[BackupDataOutputBackupTag].(string), Not(Equals), "") + + // Test CheckRepository + bp2 := *newCheckRepositoryBlueprint() + bp2.Actions["checkRepository"].Phases[0].Args[CheckRepositoryArtifactPrefixArg] = fmt.Sprintf("%s/%s", bp2.Actions["checkRepository"].Phases[0].Args[CheckRepositoryArtifactPrefixArg], c.TestName()) + out2 := runAction(c, bp2, "checkRepository", tp) + c.Assert(out2[CheckRepositoryRepoDoesNotExist].(string), Equals, "true") +}