From 5898b40dd1e3e345d2d77ee94c9055a1412a2879 Mon Sep 17 00:00:00 2001 From: Trey West Date: Fri, 21 Jun 2024 11:29:23 -0400 Subject: [PATCH] ibu mgmt: add missing dpa test --- .../internal/safeapirequest/safeapirequest.go | 4 + .../mgmt/negative/internal/tsparams/const.go | 4 + .../mgmt/negative/negative_suite_test.go | 45 +++ .../negative/tests/ibu-invalid-next-stage.go | 18 - .../negative/tests/immutable-seedimage.go | 37 -- .../negative/tests/missing-backup-location.go | 129 ++++++ .../eco-goinfra/pkg/oadp/const.go | 10 + .../pkg/oadp/dataprotectionapplication.go | 367 ++++++++++++++++++ .../pkg/oadp/dataprotectionapplicationlist.go | 77 ++++ .../eco-goinfra/pkg/velero/backup.go | 353 +++++++++++++++++ .../pkg/velero/backupstoragelocation.go | 332 ++++++++++++++++ .../pkg/velero/backupstoragelocationlist.go | 63 +++ .../eco-goinfra/pkg/velero/restore.go | 232 +++++++++++ vendor/modules.txt | 2 + 14 files changed, 1618 insertions(+), 55 deletions(-) create mode 100644 tests/lca/imagebasedupgrade/mgmt/negative/tests/missing-backup-location.go create mode 100644 vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/const.go create mode 100644 vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplication.go create mode 100644 vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplicationlist.go create mode 100644 vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backup.go create mode 100644 vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocation.go create mode 100644 vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocationlist.go create mode 100644 vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/restore.go diff --git a/tests/lca/imagebasedupgrade/internal/safeapirequest/safeapirequest.go b/tests/lca/imagebasedupgrade/internal/safeapirequest/safeapirequest.go index 521c63574..6b12e256b 100644 --- a/tests/lca/imagebasedupgrade/internal/safeapirequest/safeapirequest.go +++ b/tests/lca/imagebasedupgrade/internal/safeapirequest/safeapirequest.go @@ -34,6 +34,10 @@ func Do(req Request) error { continue case strings.Contains(errorStatus, "connection refused"): continue + case strings.Contains(errorStatus, + "Operation cannot be fulfilled on imagebasedupgrades.lca.openshift.io \"upgrade\": "+ + "the object has been modified; please apply your changes to the latest version and try again"): + continue default: return err } diff --git a/tests/lca/imagebasedupgrade/mgmt/negative/internal/tsparams/const.go b/tests/lca/imagebasedupgrade/mgmt/negative/internal/tsparams/const.go index 2de7d5024..83d26c212 100644 --- a/tests/lca/imagebasedupgrade/mgmt/negative/internal/tsparams/const.go +++ b/tests/lca/imagebasedupgrade/mgmt/negative/internal/tsparams/const.go @@ -9,4 +9,8 @@ const ( // LabelStageTransition represents stage-transition label that can be used for test cases selection. LabelStageTransition = "stage-transition" + + // LabelMissingBackupLocation missing-backup-location represents immutable-seed-image + // label that can be used for test cases selection. + LabelMissingBackupLocation = "missing-backup-location" ) diff --git a/tests/lca/imagebasedupgrade/mgmt/negative/negative_suite_test.go b/tests/lca/imagebasedupgrade/mgmt/negative/negative_suite_test.go index 347d27ea4..3fca5bf63 100644 --- a/tests/lca/imagebasedupgrade/mgmt/negative/negative_suite_test.go +++ b/tests/lca/imagebasedupgrade/mgmt/negative/negative_suite_test.go @@ -3,16 +3,19 @@ package negative_test import ( "runtime" "testing" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/openshift-kni/eco-goinfra/pkg/lca" "github.com/openshift-kni/eco-goinfra/pkg/reportxml" "github.com/openshift-kni/eco-gotests/tests/internal/reporter" . "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/internal/mgmtinittools" "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/negative/internal/tsparams" + "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/internal/safeapirequest" "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/internal/seedimage" _ "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/negative/tests" ) @@ -35,6 +38,48 @@ var _ = BeforeSuite(func() { MGMTConfig.SeedClusterInfo = seedClusterInfo }) +var _ = AfterEach(func() { + By("Pull the imagebasedupgrade from the cluster") + ibu, err := lca.PullImageBasedUpgrade(APIClient) + Expect(err).NotTo(HaveOccurred(), "error pulling imagebasedupgrade resource") + + if ibu.Object.Spec.Stage != "Idle" { + err = safeapirequest.Do(func() error { + ibu, err = lca.PullImageBasedUpgrade(APIClient) + if err != nil { + return err + } + + _, err = ibu.WithStage("Idle").Update() + if err != nil { + return err + } + + return nil + }) + + Expect(err).NotTo(HaveOccurred(), "error setting ibu to idle stage") + + By("Wait until IBU has become Idle") + _, err = ibu.WaitUntilStageComplete("Idle") + Expect(err).NotTo(HaveOccurred(), "error waiting for idle stage to complete") + } + + Eventually(func() (bool, error) { + ibu.Object, err = ibu.Get() + if err != nil { + return false, err + } + + return len(ibu.Object.Status.Conditions) == 1 && + ibu.Object.Status.Conditions[0].Type == "Idle" && + ibu.Object.Status.Conditions[0].Status == "True", nil + }).WithTimeout(time.Second*60).WithPolling(time.Second*2).Should( + BeTrue(), "error waiting for image based upgrade to become idle") + + Expect(string(ibu.Object.Spec.Stage)).To(Equal("Idle"), "error: ibu resource contains unexpected state") +}) + var _ = ReportAfterSuite("", func(report Report) { reportxml.Create( report, MGMTConfig.GetReportPath(), MGMTConfig.TCPrefix) diff --git a/tests/lca/imagebasedupgrade/mgmt/negative/tests/ibu-invalid-next-stage.go b/tests/lca/imagebasedupgrade/mgmt/negative/tests/ibu-invalid-next-stage.go index 13df0ba82..a5127c121 100644 --- a/tests/lca/imagebasedupgrade/mgmt/negative/tests/ibu-invalid-next-stage.go +++ b/tests/lca/imagebasedupgrade/mgmt/negative/tests/ibu-invalid-next-stage.go @@ -31,24 +31,6 @@ var _ = Describe( Expect(err).NotTo(HaveOccurred(), "error updating ibu with image and version") }) - AfterEach(func() { - By("Pull the imagebasedupgrade from the cluster") - ibu, err = lca.PullImageBasedUpgrade(APIClient) - Expect(err).NotTo(HaveOccurred(), "error pulling imagebasedupgrade resource") - - if ibu.Object.Spec.Stage != "Idle" { - By("Set IBU stage to Idle") - _, err = ibu.WithStage("Idle").Update() - Expect(err).NotTo(HaveOccurred(), "error setting ibu to idle stage") - - By("Wait until IBU has become Idle") - _, err = ibu.WaitUntilStageComplete("Idle") - Expect(err).NotTo(HaveOccurred(), "error waiting for idle stage to complete") - } - - Expect(string(ibu.Object.Spec.Stage)).To(Equal("Idle"), "error: ibu resource contains unexpected state") - }) - It("fails because from Idle it's not possible to move to Rollback stage", reportxml.ID("71738"), func() { By("Setting the IBU stage to Rollback") diff --git a/tests/lca/imagebasedupgrade/mgmt/negative/tests/immutable-seedimage.go b/tests/lca/imagebasedupgrade/mgmt/negative/tests/immutable-seedimage.go index 82b618655..3821833d0 100644 --- a/tests/lca/imagebasedupgrade/mgmt/negative/tests/immutable-seedimage.go +++ b/tests/lca/imagebasedupgrade/mgmt/negative/tests/immutable-seedimage.go @@ -1,17 +1,13 @@ package negative_test import ( - "time" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/openshift-kni/eco-goinfra/pkg/lca" "github.com/openshift-kni/eco-goinfra/pkg/reportxml" - "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/internal/nodestate" . "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/internal/mgmtinittools" "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/negative/internal/tsparams" lcav1 "github.com/openshift-kni/lifecycle-agent/api/imagebasedupgrade/v1" - "golang.org/x/exp/slices" ) var _ = Describe( @@ -38,39 +34,6 @@ var _ = Describe( Expect(err).NotTo(HaveOccurred(), "error updating ibu resource with empty values") }) - AfterAll(func() { - By("Revert IBU resource back to Idle stage") - ibu, err = lca.PullImageBasedUpgrade(APIClient) - Expect(err).NotTo(HaveOccurred(), "error pulling imagebasedupgrade resource") - - if ibu.Object.Spec.Stage == "Upgrade" { - By("Set IBU stage to Rollback") - _, err = ibu.WithStage("Rollback").Update() - Expect(err).NotTo(HaveOccurred(), "error setting ibu to rollback stage") - - By("Wait for IBU resource to be available") - err = nodestate.WaitForIBUToBeAvailable(APIClient, ibu, time.Minute*10) - Expect(err).NotTo(HaveOccurred(), "error waiting for ibu resource to become available") - - By("Wait until Rollback stage has completed") - _, err = ibu.WaitUntilStageComplete("Rollback") - Expect(err).NotTo(HaveOccurred(), "error waiting for rollback stage to complete") - } - - if slices.Contains([]string{"Prep", "Rollback"}, string(ibu.Object.Spec.Stage)) { - By("Set IBU stage to Idle") - _, err = ibu.WithStage("Idle").Update() - Expect(err).NotTo(HaveOccurred(), "error setting ibu to idle stage") - - By("Wait until IBU has become Idle") - _, err = ibu.WaitUntilStageComplete("Idle") - Expect(err).NotTo(HaveOccurred(), "error waiting for idle stage to complete") - } - - Expect(string(ibu.Object.Spec.Stage)).To(Equal("Idle"), "error: ibu resource contains unexpected state") - - }) - It("fails because seedImageRef is immutable while progressing", reportxml.ID("71383"), func() { ibu, err = ibu.WithSeedImage(MGMTConfig.SeedImage). WithSeedImageVersion(MGMTConfig.SeedClusterInfo.SeedClusterOCPVersion).Update() diff --git a/tests/lca/imagebasedupgrade/mgmt/negative/tests/missing-backup-location.go b/tests/lca/imagebasedupgrade/mgmt/negative/tests/missing-backup-location.go new file mode 100644 index 000000000..a58a381a7 --- /dev/null +++ b/tests/lca/imagebasedupgrade/mgmt/negative/tests/missing-backup-location.go @@ -0,0 +1,129 @@ +package negative_test + +import ( + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/openshift-kni/eco-goinfra/pkg/configmap" + "github.com/openshift-kni/eco-goinfra/pkg/lca" + "github.com/openshift-kni/eco-goinfra/pkg/oadp" + "github.com/openshift-kni/eco-goinfra/pkg/reportxml" + "github.com/openshift-kni/eco-goinfra/pkg/velero" + "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/internal/brutil" + . "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/internal/mgmtinittools" + "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/internal/mgmtparams" + "github.com/openshift-kni/eco-gotests/tests/lca/imagebasedupgrade/mgmt/negative/internal/tsparams" + lcav1 "github.com/openshift-kni/lifecycle-agent/api/imagebasedupgrade/v1" +) + +var _ = Describe( + "Starting imagebasedupgrade with missing dataprotectionlocation", + Ordered, + Label(tsparams.LabelMissingBackupLocation), func() { + var ( + ibu *lca.ImageBasedUpgradeBuilder + err error + + originalDPA *oadp.DPABuilder + oadpConfigmap *configmap.Builder + ) + + BeforeAll(func() { + By("Pull the imagebasedupgrade from the cluster") + ibu, err = lca.PullImageBasedUpgrade(APIClient) + Expect(err).NotTo(HaveOccurred(), "error pulling ibu resource from cluster") + + By("Ensure that imagebasedupgrade values are empty") + ibu.Definition.Spec.ExtraManifests = []lcav1.ConfigMapRef{} + ibu.Definition.Spec.OADPContent = []lcav1.ConfigMapRef{} + _, err = ibu.Update() + Expect(err).NotTo(HaveOccurred(), "error updating ibu resource with empty values") + + By("Get configured dataprotection application") + dpaBuilders, err := oadp.ListDataProtectionApplication(APIClient, mgmtparams.LCAOADPNamespace) + Expect(err).NotTo(HaveOccurred(), "error listing dataprotectionapplications") + Expect(len(dpaBuilders)).To(Equal(1), "error: receieved multiple dataprotectionapplication resources") + + originalDPA = dpaBuilders[0] + + err = originalDPA.Delete() + Expect(err).NotTo(HaveOccurred(), "error deleting original dataprotectionapplication") + + By("Get klusterlet backup string") + klusterletBackup, err := brutil.KlusterletBackup.String() + Expect(err).NotTo(HaveOccurred(), "error creating configmap data for klusterlet backup") + + By("Get klusterlet restore string") + klusterletRestore, err := brutil.KlusterletRestore.String() + Expect(err).NotTo(HaveOccurred(), "error creating configmap data for klusterlet restore") + + oadpConfigmap, err = configmap.NewBuilder( + APIClient, "oadp-configmap", mgmtparams.LCAOADPNamespace).WithData(map[string]string{ + "klusterlet_backup.yaml": klusterletBackup, + "klusterlet_restore.yaml": klusterletRestore, + }).Create() + Expect(err).NotTo(HaveOccurred(), "error creating oadp configmap") + }) + + AfterAll(func() { + + if originalDPA != nil && !originalDPA.Exists() { + By("Restoring data protection application") + originalDPA.Definition.ResourceVersion = "" + _, err := originalDPA.Create() + Expect(err).NotTo(HaveOccurred(), "error restoring original dataprotection application") + } + + var backupStorageLocations []*velero.BackupStorageLocationBuilder + + Eventually(func() (bool, error) { + backupStorageLocations, err = velero.ListBackupStorageLocationBuilder(APIClient, mgmtparams.LCAOADPNamespace) + if err != nil { + return false, err + } + + if len(backupStorageLocations) > 0 { + return backupStorageLocations[0].Object.Status.Phase == "Available", nil + } + + return false, nil + }).WithTimeout(time.Second*60).WithPolling(time.Second*2).Should( + BeTrue(), "error waiting for backupstoragelocation to be created") + + }) + + It("fails oadp operator availability check", reportxml.ID("71478"), func() { + ibu, err = ibu.WithSeedImage(MGMTConfig.SeedImage). + WithSeedImageVersion(MGMTConfig.SeedClusterInfo.SeedClusterOCPVersion).WithOadpContent( + oadpConfigmap.Definition.Name, + oadpConfigmap.Definition.Namespace).Update() + Expect(err).NotTo(HaveOccurred(), "error updating ibu with image and version") + + By("Setting the IBU stage to Prep") + _, err = ibu.WithStage("Prep").Update() + Expect(err).NotTo(HaveOccurred(), "error setting ibu to prep stage") + + ibu.Object, err = ibu.Get() + Expect(err).To(BeNil(), "error: getting updated ibu") + + Eventually(func() (string, error) { + ibu.Object, err = ibu.Get() + if err != nil { + return "", err + } + + for _, condition := range ibu.Object.Status.Conditions { + if condition.Type == "PrepInProgress" { + return condition.Message, nil + } + } + + return "", nil + }).WithTimeout(time.Second * 30).WithPolling(time.Second * 2).Should( + Equal(fmt.Sprintf("failed to validate IBU spec: failed to check oadp operator availability: "+ + "No DataProtectionApplication CR found in the %s", + mgmtparams.LCAOADPNamespace))) + }) + }) diff --git a/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/const.go b/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/const.go new file mode 100644 index 000000000..7180eddc3 --- /dev/null +++ b/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/const.go @@ -0,0 +1,10 @@ +package oadp + +const ( + // APIGroup represents oadp api group. + APIGroup = "oadp.openshift.io" + // V1Alpha1Version represents v1alpha1 version of oadp api. + V1Alpha1Version = "v1alpha1" + // DPAKind represents the kind for dataprotectionapplication resources. + DPAKind = "DataProtectionApplication" +) diff --git a/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplication.go b/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplication.go new file mode 100644 index 000000000..c450365df --- /dev/null +++ b/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplication.go @@ -0,0 +1,367 @@ +package oadp + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/openshift-kni/eco-goinfra/pkg/msg" + "github.com/openshift-kni/eco-goinfra/pkg/oadp/oadptypes" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// DPABuilder provides a struct for backup object from the cluster and a backup definition. +type DPABuilder struct { + // Backup definition, used to create the backup object. + Definition *oadptypes.DataProtectionApplication + // Created backup object. + Object *oadptypes.DataProtectionApplication + // Used to store latest error message upon defining or mutating backup definition. + errorMsg string + // api client to interact with the cluster. + apiClient *clients.Settings +} + +// GetDataProtectionApplicationGVR returns dataprotectionapplication's GroupVersionResource. +func GetDataProtectionApplicationGVR() schema.GroupVersionResource { + return schema.GroupVersionResource{ + Group: APIGroup, Version: V1Alpha1Version, Resource: "dataprotectionapplications", + } +} + +// NewDPABuilder creates a new instance of DPABuilder. +func NewDPABuilder( + apiClient *clients.Settings, name, namespace string, config oadptypes.ApplicationConfig) *DPABuilder { + glog.V(100).Infof( + "Initializing new dataprotectionapplication structure with the following params: "+ + "name: %s, namespace: %s, config %v", + name, namespace, config, config) + + if apiClient == nil { + glog.V(100).Infof("apiClient is nil") + + return nil + } + + builder := &DPABuilder{ + apiClient: apiClient, + Definition: &oadptypes.DataProtectionApplication{ + TypeMeta: metav1.TypeMeta{ + Kind: DPAKind, + APIVersion: V1Alpha1Version, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: oadptypes.DataProtectionApplicationSpec{ + Configuration: &config, + }, + }, + } + + if name == "" { + glog.V(100).Infof("The name of the dataprotectionapplication is empty") + + builder.errorMsg = "dataprotectionapplication 'name' cannot be empty" + } + + if namespace == "" { + glog.V(100).Infof("The namespace of the dataprotectionapplication is empty") + + builder.errorMsg = "dataprotectionapplication 'namespace' cannot be empty" + } + + if config.Velero == nil { + glog.V(100).Infof("The velero config of the dataprotectionapplication is empty") + + builder.errorMsg = "dataprotectionapplication velero config cannot be empty" + } + + return builder +} + +// PullDPA pulls existing dataprotectionapplication from cluster. +func PullDPA(apiClient *clients.Settings, name, nsname string) (*DPABuilder, error) { + glog.V(100).Infof("Pulling existing dataprotectionapplication name %s under namespace %s from cluster", name, nsname) + + if apiClient == nil { + glog.V(100).Infof("The apiClient cannot be nil") + + return nil, fmt.Errorf("the apiClient is nil") + } + + builder := DPABuilder{ + apiClient: apiClient, + Definition: &oadptypes.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + }, + } + + if name == "" { + glog.V(100).Infof("The name of the dataprotectionapplication is empty") + + return nil, fmt.Errorf("dataprotectionapplication 'name' cannot be empty") + } + + if nsname == "" { + glog.V(100).Infof("The namespace of the dataprotectionapplication is empty") + + return nil, fmt.Errorf("dataprotectionapplication 'namespace' cannot be empty") + } + + if !builder.Exists() { + return nil, fmt.Errorf("dataprotectionapplication object %s does not exist in namespace %s", name, nsname) + } + + builder.Definition = builder.Object + + return &builder, nil +} + +// WithBackupLocation configures the dataprotectionapplication with the specified backup location. +func (builder *DPABuilder) WithBackupLocation(backupLocation oadptypes.BackupLocation) *DPABuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding backuplocation to dataprotectionapplication %s in namespace %s: %v", + builder.Definition.Name, builder.Definition.Namespace, backupLocation) + + if backupLocation.Velero == nil { + glog.V(100).Infof("The backuplocation velero config of the dataprotectionapplication is empty") + + builder.errorMsg = "dataprotectionapplication backuplocation cannot have empty velero config" + + return builder + } + + builder.Definition.Spec.BackupLocations = append(builder.Definition.Spec.BackupLocations, backupLocation) + + return builder +} + +// Get fetches the defined dataprotectionapplication from the cluster. +func (builder *DPABuilder) Get() (*oadptypes.DataProtectionApplication, error) { + if valid, err := builder.validate(); !valid { + return nil, err + } + + glog.V(100).Infof("Getting dataprotectionapplication %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + unsObject, err := builder.apiClient.Resource(GetDataProtectionApplicationGVR()).Namespace( + builder.Definition.Namespace).Get(context.TODO(), builder.Definition.Name, metav1.GetOptions{}) + + if err != nil { + return nil, err + } + + return builder.convertToStructured(unsObject) +} + +// Exists checks whether the given dataprotectionapplication exists. +func (builder *DPABuilder) Exists() bool { + if valid, _ := builder.validate(); !valid { + return false + } + + glog.V(100).Infof("Checking if dataprotectionapplication %s exists in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + builder.Object, err = builder.Get() + + return err == nil || !k8serrors.IsNotFound(err) +} + +// Create makes a dataprotectionapplication according to the dataprotectionapplication +// definition and stores the created object in the dataprotectionapplication builder. +func (builder *DPABuilder) Create() (*DPABuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Creating the dataprotectionapplication %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + if !builder.Exists() { + unstructuredDPA, err := runtime.DefaultUnstructuredConverter.ToUnstructured(builder.Definition) + + if err != nil { + glog.V(100).Infof("Failed to convert structured DataProtectionApplication to unstructured object") + + return nil, err + } + + unsObject, err := builder.apiClient.Resource( + GetDataProtectionApplicationGVR()).Namespace(builder.Definition.Namespace).Create( + context.TODO(), &unstructured.Unstructured{Object: unstructuredDPA}, metav1.CreateOptions{}) + + if err != nil { + glog.V(100).Infof("Failed to create dataprotectionapplication") + + return nil, err + } + + builder.Object, err = builder.convertToStructured(unsObject) + + if err != nil { + return nil, err + } + } + + return builder, err +} + +// Update renovates the existing dataprotectionapplication object with +// the dataprotectionapplication definition in builder. +func (builder *DPABuilder) Update(force bool) (*DPABuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + if !builder.Exists() { + return nil, fmt.Errorf("failed to update dataprotectionapplication, object does not exist on cluster") + } + + glog.V(100).Infof("Updating the dataprotectionapplication object %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace, + ) + + if builder.errorMsg != "" { + return nil, fmt.Errorf(builder.errorMsg) + } + + builder.Definition.ResourceVersion = builder.Object.ResourceVersion + builder.Definition.ObjectMeta.ResourceVersion = builder.Object.ObjectMeta.ResourceVersion + + unstructuredDPA, err := runtime.DefaultUnstructuredConverter.ToUnstructured(builder.Definition) + if err != nil { + glog.V(100).Infof("Failed to convert structured DataProtectionApplication to unstructured object") + + return nil, err + } + + unstructObj, err := builder.apiClient.Resource( + GetDataProtectionApplicationGVR()).Namespace(builder.Definition.Namespace).Update( + context.TODO(), &unstructured.Unstructured{Object: unstructuredDPA}, metav1.UpdateOptions{}) + + if err != nil { + if force { + glog.V(100).Infof( + msg.FailToUpdateNotification("dataprotectionapplication", builder.Definition.Name, builder.Definition.Namespace)) + + err := builder.Delete() + + if err != nil { + glog.V(100).Infof( + msg.FailToUpdateError("dataprotectionapplication", builder.Definition.Name, builder.Definition.Namespace)) + + return nil, err + } + + return builder.Create() + } + } + + if err == nil { + structuredDPA, err := builder.convertToStructured(unstructObj) + if err != nil { + glog.V(100).Infof("Failed to convert unstructured dataprotectionapplication into structured object") + + return nil, err + } + + builder.Object = structuredDPA + } + + return builder, err +} + +// Delete removes the dataprotectionapplication object and resets the builder object. +func (builder *DPABuilder) Delete() error { + if valid, err := builder.validate(); !valid { + return err + } + + glog.V(100).Infof("Deleting the dataprotectionapplication object %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace, + ) + + if !builder.Exists() { + glog.V(100).Infof("Dataprotectionapplication %s in namespace %s cannot be deleted because it does not exist", + builder.Definition.Name, builder.Definition.Namespace) + + return nil + } + + err := builder.apiClient.Resource( + GetDataProtectionApplicationGVR()).Namespace(builder.Definition.Namespace).Delete( + context.TODO(), builder.Definition.Name, metav1.DeleteOptions{}) + + if err != nil { + return fmt.Errorf("can not delete dataprotectionapplication: %w", err) + } + + builder.Object = nil + + return nil +} + +func (builder *DPABuilder) convertToStructured( + unsObject *unstructured.Unstructured) (*oadptypes.DataProtectionApplication, error) { + dpaBuilder := &oadptypes.DataProtectionApplication{} + + err := runtime.DefaultUnstructuredConverter.FromUnstructured(unsObject.Object, dpaBuilder) + if err != nil { + glog.V(100).Infof( + "Failed to convert from unstructured to DataProtectionApplication object in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + return nil, err + } + + return dpaBuilder, err +} + +// validate will check that the builder and builder definition are properly initialized before +// accessing any member fields. +func (builder *DPABuilder) validate() (bool, error) { + resourceCRD := "DataProtectionApplication" + + if builder == nil { + glog.V(100).Infof("The %s builder is uninitialized", resourceCRD) + + return false, fmt.Errorf("error: received nil %s builder", resourceCRD) + } + + if builder.Definition == nil { + glog.V(100).Infof("The %s is undefined", resourceCRD) + + builder.errorMsg = msg.UndefinedCrdObjectErrString(resourceCRD) + } + + if builder.apiClient == nil { + glog.V(100).Infof("The %s builder apiclient is nil", resourceCRD) + + builder.errorMsg = fmt.Sprintf("%s builder cannot have nil apiClient", resourceCRD) + } + + if builder.errorMsg != "" { + glog.V(100).Infof("The %s builder has error message: %s", resourceCRD, builder.errorMsg) + + return false, fmt.Errorf(builder.errorMsg) + } + + return true, nil +} diff --git a/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplicationlist.go b/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplicationlist.go new file mode 100644 index 000000000..06b6955dd --- /dev/null +++ b/vendor/github.com/openshift-kni/eco-goinfra/pkg/oadp/dataprotectionapplicationlist.go @@ -0,0 +1,77 @@ +package oadp + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/openshift-kni/eco-goinfra/pkg/oadp/oadptypes" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// ListDataProtectionApplication returns dataprotectionapplication inventory in the given namespace. +func ListDataProtectionApplication( + apiClient *clients.Settings, nsname string, options ...metav1.ListOptions) ([]*DPABuilder, error) { + glog.V(100).Infof("Listing dataprotectionapplications in namespace %s", nsname) + + if apiClient == nil { + glog.V(100).Infof("The apiClient cannot be nil") + + return nil, fmt.Errorf("the apiClient is nil") + } + + if nsname == "" { + glog.V(100).Infof("dataprotectionapplication 'nsname' parameter can not be empty") + + return nil, fmt.Errorf("failed to list dataprotectionapplications, 'nsname' parameter is empty") + } + + logMessage := fmt.Sprintf("Listing dataprotectionapplications in the nsname %s", nsname) + passedOptions := metav1.ListOptions{} + + if len(options) == 1 { + passedOptions = options[0] + logMessage += fmt.Sprintf(" with the options %v", passedOptions) + } else if len(options) > 1 { + glog.V(100).Infof("'options' parameter must be empty or single-valued") + + return nil, fmt.Errorf("error: more than one ListOptions was passed") + } + + glog.V(100).Infof(logMessage) + + unstructObjList, err := apiClient.Resource(GetDataProtectionApplicationGVR()). + Namespace(nsname).List(context.TODO(), passedOptions) + if err != nil { + glog.V(100).Infof("Failed to list dataprotectionapplications in the nsname %s due to %s", nsname, err.Error()) + + return nil, err + } + + var dpaObjects []*DPABuilder + + for _, runningDPA := range unstructObjList.Items { + dataprotectionapplication := &oadptypes.DataProtectionApplication{} + + err = runtime.DefaultUnstructuredConverter.FromUnstructured(runningDPA.Object, dataprotectionapplication) + if err != nil { + glog.V(100).Infof( + "Failed to convert from unstructured list to DataProtectionApplicationList object in namespace %s", + nsname) + + return nil, err + } + + dpaBuilder := &DPABuilder{ + apiClient: apiClient, + Object: dataprotectionapplication, + Definition: dataprotectionapplication, + } + + dpaObjects = append(dpaObjects, dpaBuilder) + } + + return dpaObjects, nil +} diff --git a/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backup.go b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backup.go new file mode 100644 index 000000000..3450eb70e --- /dev/null +++ b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backup.go @@ -0,0 +1,353 @@ +package velero + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/openshift-kni/eco-goinfra/pkg/msg" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + veleroClient "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BackupBuilder provides a struct for backup object from the cluster and a backup definition. +type BackupBuilder struct { + // Backup definition, used to create the backup object. + Definition *velerov1.Backup + // Created backup object. + Object *velerov1.Backup + // Used to store latest error message upon defining or mutating backup definition. + errorMsg string + // api client to interact with the cluster. + apiClient veleroClient.Interface +} + +// NewBackupBuilder creates a new instance of BackupBuilder. +func NewBackupBuilder(apiClient *clients.Settings, name, nsname string) *BackupBuilder { + glog.V(100).Infof( + "Initializing new backup structure with the following params: "+ + "name: %s, namespace: %s", name, nsname) + + builder := &BackupBuilder{ + apiClient: apiClient.VeleroClient, + Definition: &velerov1.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + }, + } + + if name == "" { + glog.V(100).Infof("The name of the backup is empty") + + builder.errorMsg = "backup name cannot be an empty string" + } + + if nsname == "" { + glog.V(100).Infof("The namespace of the backup is empty") + + builder.errorMsg = "backup namespace cannot be an empty string" + } + + return builder +} + +// PullBackup loads an existing backup into BackupBuilder struct. +func PullBackup(apiClient *clients.Settings, name, nsname string) (*BackupBuilder, error) { + glog.V(100).Infof("Pulling existing backup name: %s under namespace: %s", name, nsname) + + builder := BackupBuilder{ + apiClient: apiClient.VeleroClient, + Definition: &velerov1.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + }, + } + + if name == "" { + return nil, fmt.Errorf("backup name cannot be empty") + } + + if nsname == "" { + return nil, fmt.Errorf("backup namespace cannot be empty") + } + + if !builder.Exists() { + return nil, fmt.Errorf("backup object %s does not exist in namespace %s", name, nsname) + } + + builder.Definition = builder.Object + + return &builder, nil +} + +// WithStorageLocation adds a storage location to the backup. +func (builder *BackupBuilder) WithStorageLocation(location string) *BackupBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof( + "Adding storage location %s to backup %s in namespace %s", + location, builder.Definition.Name, builder.Definition.Namespace) + + if location == "" { + glog.V(100).Infof("Backup storage location is empty") + + builder.errorMsg = "backup storage location cannot be an empty string" + } + + if builder.errorMsg != "" { + return builder + } + + if builder.Definition.ObjectMeta.Labels == nil { + builder.Definition.ObjectMeta.Labels = make(map[string]string) + } + + builder.Definition.ObjectMeta.Labels["velero.io/storage-location"] = location + + return builder +} + +// WithIncludedNamespace adds the specified namespace for inclusion when performing a backup. +func (builder *BackupBuilder) WithIncludedNamespace(namespace string) *BackupBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof( + "Adding namespace %s to backup %s in namespace %s includedNamespaces field", + namespace, builder.Definition.Name, builder.Definition.Namespace) + + if namespace == "" { + glog.V(100).Infof("Backup includedNamespace is empty") + + builder.errorMsg = "backup includedNamespace cannot be an empty string" + } + + if builder.errorMsg != "" { + return builder + } + + builder.Definition.Spec.IncludedNamespaces = append(builder.Definition.Spec.IncludedNamespaces, namespace) + + return builder +} + +// WithIncludedClusterScopedResource adds the specified cluster-scoped crd for inclusion when performing a backup. +func (builder *BackupBuilder) WithIncludedClusterScopedResource(crd string) *BackupBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof( + "Adding custom resource %s to backup %s in namespace %s includedClusterScopedResources field", + crd, builder.Definition.Name, builder.Definition.Namespace) + + if crd == "" { + glog.V(100).Infof("Backup includedClusterScopedResource is empty") + + builder.errorMsg = "backup includedClusterScopedResource cannot be an empty string" + } + + if builder.errorMsg != "" { + return builder + } + + builder.Definition.Spec.IncludedClusterScopedResources = + append(builder.Definition.Spec.IncludedClusterScopedResources, crd) + + return builder +} + +// WithIncludedNamespaceScopedResource adds the specified namespace-scoped crd for inclusion when performing a backup. +func (builder *BackupBuilder) WithIncludedNamespaceScopedResource(crd string) *BackupBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof( + "Adding custom resource %s to backup %s in namespace %s includedNamespaceScopedResources field", + crd, builder.Definition.Name, builder.Definition.Namespace) + + if crd == "" { + glog.V(100).Infof("Backup includedNamespaceScopedResource is empty") + + builder.errorMsg = "backup includedNamespaceScopedResource cannot be an empty string" + } + + if builder.errorMsg != "" { + return builder + } + + builder.Definition.Spec.IncludedNamespaceScopedResources = + append(builder.Definition.Spec.IncludedNamespaceScopedResources, crd) + + return builder +} + +// WithExcludedClusterScopedResource adds the specified cluster-scoped crd for exclusion when performing a backup. +func (builder *BackupBuilder) WithExcludedClusterScopedResource(crd string) *BackupBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof( + "Adding custom resource %s to backup %s in namespace %s excludedClusterScopedResources field", + crd, builder.Definition.Name, builder.Definition.Namespace) + + if crd == "" { + glog.V(100).Infof("Backup excludedClusterScopedResource is empty") + + builder.errorMsg = "backup excludedClusterScopedResource cannot be an empty string" + } + + if builder.errorMsg != "" { + return builder + } + + builder.Definition.Spec.ExcludedClusterScopedResources = + append(builder.Definition.Spec.ExcludedClusterScopedResources, crd) + + return builder +} + +// WithExcludedNamespaceScopedResources adds the specified namespace-scoped crd for exclusion when performing a backup. +func (builder *BackupBuilder) WithExcludedNamespaceScopedResources(crd string) *BackupBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof( + "Adding custom resource %s to backup %s in namespace %s excludedNamespaceScopedResources field", + crd, builder.Definition.Name, builder.Definition.Namespace) + + if crd == "" { + glog.V(100).Infof("Backup excludedNamespaceScopedResource is empty") + + builder.errorMsg = "backup excludedNamespaceScopedResource cannot be an empty string" + } + + if builder.errorMsg != "" { + return builder + } + + builder.Definition.Spec.ExcludedNamespaceScopedResources = + append(builder.Definition.Spec.ExcludedNamespaceScopedResources, crd) + + return builder +} + +// Exists checks whether the given backup exists. +func (builder *BackupBuilder) Exists() bool { + if valid, _ := builder.validate(); !valid { + return false + } + + glog.V(100).Infof("Checking if backup %s exists in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + builder.Object, err = builder.apiClient.VeleroV1().Backups(builder.Definition.Namespace).Get( + context.TODO(), builder.Definition.Name, metav1.GetOptions{}) + + return err == nil || !k8serrors.IsNotFound(err) +} + +// Create makes a backup according to the backup definition and stores the created object in the backup builder. +func (builder *BackupBuilder) Create() (*BackupBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Creating backup %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + if !builder.Exists() { + builder.Object, err = builder.apiClient.VeleroV1().Backups(builder.Definition.Namespace).Create( + context.TODO(), builder.Definition, metav1.CreateOptions{}) + } + + return builder, err +} + +// Update renovates the existing backup object with the backup definition in builder. +func (builder *BackupBuilder) Update() (*BackupBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Updating backup %s in namespace %s", builder.Definition.Name, builder.Definition.Namespace) + + var err error + builder.Object, err = builder.apiClient.VeleroV1().Backups(builder.Definition.Namespace).Update( + context.TODO(), builder.Definition, metav1.UpdateOptions{}) + + return builder, err +} + +// Delete removes the backup object and resets the builder object. +func (builder *BackupBuilder) Delete() (*BackupBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Deleting backup %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + if !builder.Exists() { + return builder, fmt.Errorf("backup cannot be deleted because it does not exist") + } + + err := builder.apiClient.VeleroV1().Backups(builder.Definition.Namespace).Delete( + context.TODO(), builder.Object.Name, metav1.DeleteOptions{}) + + if err != nil { + return builder, fmt.Errorf("can not delete backup: %w", err) + } + + builder.Object = nil + + return builder, nil +} + +// validate will check that the builder and builder definition are properly initialized before +// accessing any member fields. +func (builder *BackupBuilder) validate() (bool, error) { + resourceCRD := "Backup" + + if builder == nil { + glog.V(100).Infof("The %s builder is uninitialized", resourceCRD) + + return false, fmt.Errorf("error: received nil %s builder", resourceCRD) + } + + if builder.Definition == nil { + glog.V(100).Infof("The %s is undefined", resourceCRD) + + builder.errorMsg = msg.UndefinedCrdObjectErrString(resourceCRD) + } + + if builder.apiClient == nil { + glog.V(100).Infof("The %s builder apiclient is nil", resourceCRD) + + builder.errorMsg = fmt.Sprintf("%s builder cannot have nil apiClient", resourceCRD) + } + + if builder.errorMsg != "" { + glog.V(100).Infof("The %s builder has error message: %s", resourceCRD, builder.errorMsg) + + return false, fmt.Errorf(builder.errorMsg) + } + + return true, nil +} diff --git a/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocation.go b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocation.go new file mode 100644 index 000000000..6ba5c0fcd --- /dev/null +++ b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocation.go @@ -0,0 +1,332 @@ +package velero + +import ( + "context" + "fmt" + "time" + + "github.com/golang/glog" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/openshift-kni/eco-goinfra/pkg/msg" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + veleroClient "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" +) + +// BackupStorageLocationBuilder provides a struct for backupstoragelocation +// object from the cluster and a backupstoragelocation definition. +type BackupStorageLocationBuilder struct { + // BackupStorageLocation definition, used to create the backupstoragelocation object. + Definition *velerov1.BackupStorageLocation + // Created backupstoragelocation object. + Object *velerov1.BackupStorageLocation + // Used to store latest error message upon defining or mutating backupstoragelocation definition. + errorMsg string + // api client to interact with the cluster. + apiClient veleroClient.Interface +} + +// NewBackupStorageLocationBuilder creates a new instance of BackupStorageLocationBuilder. +func NewBackupStorageLocationBuilder( + apiClient *clients.Settings, + name string, + namespace string, + provider string, + objectStorage velerov1.ObjectStorageLocation) *BackupStorageLocationBuilder { + glog.V(100).Infof( + "Initializing new backupstoragelocation structure with the following params: "+ + "name: %s, namespace: %s, provider: %s, objectStorage: %v", name, namespace, provider, objectStorage) + + if apiClient == nil { + glog.V(100).Infof("The apiClient cannot be nil") + + return nil + } + + builder := &BackupStorageLocationBuilder{ + apiClient: apiClient.VeleroClient, + Definition: &velerov1.BackupStorageLocation{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: velerov1.BackupStorageLocationSpec{ + Provider: provider, + StorageType: velerov1.StorageType{ + ObjectStorage: &objectStorage, + }, + }, + }, + } + + if name == "" { + glog.V(100).Infof("The name of the backupstoragelocation is empty") + + builder.errorMsg = "backupstoragelocation name cannot be empty" + } + + if namespace == "" { + glog.V(100).Infof("The namespace of the backupstoragelocation is empty") + + builder.errorMsg = "backupstoragelocation namespace cannot be empty" + } + + if provider == "" { + glog.V(100).Infof("The provider of the backupstoragelocation is empty") + + builder.errorMsg = "backupstoragelocation provider cannot be empty" + } + + if objectStorage.Bucket == "" { + glog.V(100).Infof("The objectstorage bucket of the backupstoragelocation is empty") + + builder.errorMsg = "backupstoragelocation objectstorage bucket cannot be empty" + } + + return builder +} + +// PullBackupStorageLocationBuilder pulls existing backupstoragelocation from cluster. +func PullBackupStorageLocationBuilder( + apiClient *clients.Settings, name, namespace string) (*BackupStorageLocationBuilder, error) { + glog.V(100).Infof("Pulling existing backupstoragelocation name: %s under namespace: %s", name, namespace) + + if apiClient == nil { + glog.V(100).Infof("The apiClient cannot be nil") + + return nil, fmt.Errorf("the apiClient is nil") + } + + builder := BackupStorageLocationBuilder{ + apiClient: apiClient.VeleroClient, + Definition: &velerov1.BackupStorageLocation{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + }, + } + + if name == "" { + glog.V(100).Infof("The name of the backupstoragelocation is empty") + + return nil, fmt.Errorf("backupstoragelocation name cannot be empty") + } + + if namespace == "" { + glog.V(100).Infof("The namespace of the backupstoragelocation is empty") + + return nil, fmt.Errorf("backupstoragelocation namespace cannot be empty") + } + + if !builder.Exists() { + return nil, fmt.Errorf("backupstoragelocation object %s does not exist in namespace %s", name, namespace) + } + + builder.Definition = builder.Object + + return &builder, nil +} + +// WithConfig includes the provided configuration to the backupstoragelocation. +func (builder *BackupStorageLocationBuilder) WithConfig(config map[string]string) *BackupStorageLocationBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + if len(config) == 0 { + glog.V(100).Infof("The config of the backupstoragelocation is empty") + + builder.errorMsg = "backupstoragelocation cannot have empty config" + + return builder + } + + builder.Definition.Spec.Config = config + + return builder +} + +// WaitUntilAvailable waits the specified timeout for the backupstoragelocation to become available. +func (builder *BackupStorageLocationBuilder) WaitUntilAvailable( + timeout time.Duration) (*BackupStorageLocationBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + if !builder.Exists() { + return builder, fmt.Errorf("cannot wait for backupstoragelocation that does not exist") + } + + var err error + err = wait.PollUntilContextTimeout( + context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + glog.V(100).Infof("Waiting for the backupstoragelocation %s in %s to become available", + builder.Definition.Name, builder.Definition.Namespace) + + builder.Object, err = builder.apiClient.VeleroV1(). + BackupStorageLocations(builder.Definition.Namespace).Get( + context.TODO(), builder.Definition.Name, metav1.GetOptions{}) + + if err != nil { + return false, err + } + + return builder.Object.Status.Phase == velerov1.BackupStorageLocationPhaseAvailable, nil + }) + + if err == nil { + return builder, nil + } + + return nil, fmt.Errorf("error waiting for backupstoragelocation to become available: %w", err) +} + +// WaitUntilUnavailable waits the specified timeout for the backupstoragelocation to become unavailable. +func (builder *BackupStorageLocationBuilder) WaitUntilUnavailable( + timeout time.Duration) (*BackupStorageLocationBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + if !builder.Exists() { + return builder, fmt.Errorf("cannot wait for backupstoragelocation that does not exist") + } + + var err error + err = wait.PollUntilContextTimeout( + context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + glog.V(100).Infof("Waiting for the backupstoragelocation %s in %s to become unavailable", + builder.Definition.Name, builder.Definition.Namespace) + + builder.Object, err = builder.apiClient.VeleroV1(). + BackupStorageLocations(builder.Definition.Namespace).Get( + context.TODO(), builder.Definition.Name, metav1.GetOptions{}) + + if err != nil { + return false, err + } + + return builder.Object.Status.Phase == velerov1.BackupStorageLocationPhaseUnavailable, nil + }) + + if err == nil { + return builder, nil + } + + return nil, fmt.Errorf("error waiting for backupstoragelocation to become unavailable: %w", err) +} + +// Exists checks whether the given backupstoragelocation exists. +func (builder *BackupStorageLocationBuilder) Exists() bool { + if valid, _ := builder.validate(); !valid { + return false + } + + glog.V(100).Infof("Checking if backupstoragelocation %s exists in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + builder.Object, err = builder.apiClient.VeleroV1().BackupStorageLocations(builder.Definition.Namespace).Get( + context.TODO(), builder.Definition.Name, metav1.GetOptions{}) + + return err == nil || !k8serrors.IsNotFound(err) +} + +// Create makes a backupstoragelocation according to the backupstoragelocation +// definition and stores the created object in the backupstoragelocation builder. +func (builder *BackupStorageLocationBuilder) Create() (*BackupStorageLocationBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Creating backupstoragelocation %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + if !builder.Exists() { + builder.Object, err = builder.apiClient.VeleroV1().BackupStorageLocations(builder.Definition.Namespace).Create( + context.TODO(), builder.Definition, metav1.CreateOptions{}) + } + + return builder, err +} + +// Update renovates the existing backupstoragelocation object with the backupstoragelocation definition in builder. +func (builder *BackupStorageLocationBuilder) Update() (*BackupStorageLocationBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Updating backupstoragelocation %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + if !builder.Exists() { + return builder, fmt.Errorf("cannot update non-existent backupstoragelocation") + } + + var err error + builder.Object, err = builder.apiClient.VeleroV1().BackupStorageLocations(builder.Definition.Namespace).Update( + context.TODO(), builder.Definition, metav1.UpdateOptions{}) + + return builder, err +} + +// Delete removes the backupstoragelocation object and resets the builder object. +func (builder *BackupStorageLocationBuilder) Delete() error { + if valid, err := builder.validate(); !valid { + return err + } + + glog.V(100).Infof("Deleting backupstoragelocation %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + if !builder.Exists() { + return fmt.Errorf("backupstoragelocation cannot be deleted because it does not exist") + } + + err := builder.apiClient.VeleroV1().BackupStorageLocations(builder.Definition.Namespace).Delete( + context.TODO(), builder.Object.Name, metav1.DeleteOptions{}) + + if err != nil { + return fmt.Errorf("can not delete backupstoragelocation: %w", err) + } + + builder.Object = nil + + return nil +} + +// validate will check that the builder and builder definition are properly initialized before +// accessing any member fields. +func (builder *BackupStorageLocationBuilder) validate() (bool, error) { + resourceCRD := "BackupStorageLocation" + + if builder == nil { + glog.V(100).Infof("The %s builder is uninitialized", resourceCRD) + + return false, fmt.Errorf("error: received nil %s builder", resourceCRD) + } + + if builder.Definition == nil { + glog.V(100).Infof("The %s is undefined", resourceCRD) + + builder.errorMsg = msg.UndefinedCrdObjectErrString(resourceCRD) + } + + if builder.apiClient == nil { + glog.V(100).Infof("The %s builder apiclient is nil", resourceCRD) + + builder.errorMsg = fmt.Sprintf("%s builder cannot have nil apiClient", resourceCRD) + } + + if builder.errorMsg != "" { + glog.V(100).Infof("The %s builder has error message: %s", resourceCRD, builder.errorMsg) + + return false, fmt.Errorf(builder.errorMsg) + } + + return true, nil +} diff --git a/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocationlist.go b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocationlist.go new file mode 100644 index 000000000..117382261 --- /dev/null +++ b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/backupstoragelocationlist.go @@ -0,0 +1,63 @@ +package velero + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ListBackupStorageLocationBuilder returns backupstoragelocation inventory in the given namespace. +func ListBackupStorageLocationBuilder( + apiClient *clients.Settings, nsname string, options ...metav1.ListOptions) ([]*BackupStorageLocationBuilder, error) { + if apiClient == nil { + glog.V(100).Infof("The apiClient cannot be nil") + + return nil, fmt.Errorf("the apiClient is nil") + } + + if nsname == "" { + glog.V(100).Infof("backupstoragelocation 'nsname' parameter can not be empty") + + return nil, fmt.Errorf("failed to list backupstoragelocations, 'nsname' parameter is empty") + } + + logMessage := fmt.Sprintf("Listing backupstoragelocations in the nsname %s", nsname) + passedOptions := metav1.ListOptions{} + + if len(options) == 1 { + passedOptions = options[0] + logMessage += fmt.Sprintf(" with the options %v", passedOptions) + } else if len(options) > 1 { + glog.V(100).Infof("'options' parameter must be empty or single-valued") + + return nil, fmt.Errorf("error: more than one ListOptions was passed") + } + + glog.V(100).Infof(logMessage) + + bslList, err := apiClient.BackupStorageLocations(nsname).List(context.TODO(), passedOptions) + + if err != nil { + glog.V(100).Infof("Failed to list backupstoragelocations in the nsname %s due to %s", nsname, err.Error()) + + return nil, err + } + + var bslObjects []*BackupStorageLocationBuilder + + for _, runningBsl := range bslList.Items { + copiedBsl := runningBsl + bslBuilder := &BackupStorageLocationBuilder{ + apiClient: apiClient.VeleroClient, + Object: &copiedBsl, + Definition: &copiedBsl, + } + + bslObjects = append(bslObjects, bslBuilder) + } + + return bslObjects, nil +} diff --git a/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/restore.go b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/restore.go new file mode 100644 index 000000000..771365a19 --- /dev/null +++ b/vendor/github.com/openshift-kni/eco-goinfra/pkg/velero/restore.go @@ -0,0 +1,232 @@ +package velero + +import ( + "context" + "fmt" + + "github.com/golang/glog" + "github.com/openshift-kni/eco-goinfra/pkg/clients" + "github.com/openshift-kni/eco-goinfra/pkg/msg" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + veleroClient "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RestoreBuilder provides a struct for restore object from the cluster and a restore definition. +type RestoreBuilder struct { + // Restore definition, used to create the restore object. + Definition *velerov1.Restore + // Created restore object. + Object *velerov1.Restore + // Used to store latest error message upon defining or mutating restore definition. + errorMsg string + // api client to interact with the cluster. + apiClient veleroClient.Interface +} + +// NewRestoreBuilder creates a new instance of RestoreBuilder. +func NewRestoreBuilder(apiClient *clients.Settings, name, nsname, backupName string) *RestoreBuilder { + glog.V(100).Infof( + "Initializing new restore structure with the following params: "+ + "name: %s, namespace: %s, restoreName: %s", name, nsname, backupName) + + builder := &RestoreBuilder{ + apiClient: apiClient.VeleroClient, + Definition: &velerov1.Restore{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + Spec: velerov1.RestoreSpec{ + BackupName: backupName, + }, + }, + } + + if name == "" { + glog.V(100).Infof("The name of the restore is empty") + + builder.errorMsg = "restore name cannot be an empty string" + } + + if nsname == "" { + glog.V(100).Infof("The namespace of the restore is empty") + + builder.errorMsg = "restore namespace cannot be an empty string" + } + + if backupName == "" { + glog.V(100).Infof("The backupName of the restore is empty") + + builder.errorMsg = "restore backupName cannot be an empty string" + } + + return builder +} + +// PullRestore loads an existing restore into RestoreBuilder struct. +func PullRestore(apiClient *clients.Settings, name, nsname string) (*RestoreBuilder, error) { + glog.V(100).Infof("Pulling existing restore name: %s under namespace: %s", name, nsname) + + builder := RestoreBuilder{ + apiClient: apiClient.VeleroClient, + Definition: &velerov1.Restore{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: nsname, + }, + }, + } + + if name == "" { + return nil, fmt.Errorf("restore name cannot be empty") + } + + if nsname == "" { + return nil, fmt.Errorf("restore namespace cannot be empty") + } + + if !builder.Exists() { + return nil, fmt.Errorf("restore object %s does not exist in namespace %s", name, nsname) + } + + builder.Definition = builder.Object + + return &builder, nil +} + +// WithStorageLocation adds a storage location to the restore. +func (builder *RestoreBuilder) WithStorageLocation(location string) *RestoreBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof( + "Adding storage location %s to restore %s in namespace %s", + location, builder.Definition.Name, builder.Definition.Namespace) + + if location == "" { + glog.V(100).Infof("Backup storage location is empty") + + builder.errorMsg = "restore storage location cannot be an empty string" + } + + if builder.errorMsg != "" { + return builder + } + + if builder.Definition.ObjectMeta.Labels == nil { + builder.Definition.ObjectMeta.Labels = make(map[string]string) + } + + builder.Definition.ObjectMeta.Labels["velero.io/storage-location"] = location + + return builder +} + +// Exists checks whether the given restore object exists. +func (builder *RestoreBuilder) Exists() bool { + if valid, _ := builder.validate(); !valid { + return false + } + + glog.V(100).Infof("Checking if restore %s exists in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + builder.Object, err = builder.apiClient.VeleroV1().Restores(builder.Definition.Namespace).Get( + context.TODO(), builder.Definition.Name, metav1.GetOptions{}) + + return err == nil || !k8serrors.IsNotFound(err) +} + +// Create makes a restore according to the restore definition and stores the created object in the restore builder. +func (builder *RestoreBuilder) Create() (*RestoreBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Creating restore %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + var err error + if !builder.Exists() { + builder.Object, err = builder.apiClient.VeleroV1().Restores(builder.Definition.Namespace).Create( + context.TODO(), builder.Definition, metav1.CreateOptions{}) + } + + return builder, err +} + +// Update renovates the existing restore object with the restore definition in builder. +func (builder *RestoreBuilder) Update() (*RestoreBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Updating restore %s in namespace %s", builder.Definition.Name, builder.Definition.Namespace) + + var err error + builder.Object, err = builder.apiClient.VeleroV1().Restores(builder.Definition.Namespace).Update( + context.TODO(), builder.Definition, metav1.UpdateOptions{}) + + return builder, err +} + +// Delete removes the restore object and resets the builder object. +func (builder *RestoreBuilder) Delete() (*RestoreBuilder, error) { + if valid, err := builder.validate(); !valid { + return builder, err + } + + glog.V(100).Infof("Deleting restore %s in namespace %s", + builder.Definition.Name, builder.Definition.Namespace) + + if !builder.Exists() { + return builder, fmt.Errorf("restore cannot be deleted because it does not exist") + } + + err := builder.apiClient.VeleroV1().Restores(builder.Definition.Namespace).Delete( + context.TODO(), builder.Object.Name, metav1.DeleteOptions{}) + + if err != nil { + return builder, fmt.Errorf("can not delete restore: %w", err) + } + + builder.Object = nil + + return builder, nil +} + +// validate will check that the builder and builder definition are properly initialized before +// accessing any member fields. +func (builder *RestoreBuilder) validate() (bool, error) { + resourceCRD := "Restore" + + if builder == nil { + glog.V(100).Infof("The %s builder is uninitialized", resourceCRD) + + return false, fmt.Errorf("error: received nil %s builder", resourceCRD) + } + + if builder.Definition == nil { + glog.V(100).Infof("The %s is undefined", resourceCRD) + + builder.errorMsg = msg.UndefinedCrdObjectErrString(resourceCRD) + } + + if builder.apiClient == nil { + glog.V(100).Infof("The %s builder apiclient is nil", resourceCRD) + + builder.errorMsg = fmt.Sprintf("%s builder cannot have nil apiClient", resourceCRD) + } + + if builder.errorMsg != "" { + glog.V(100).Infof("The %s builder has error message: %s", resourceCRD, builder.errorMsg) + + return false, fmt.Errorf(builder.errorMsg) + } + + return true, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4d01922e6..dad92d5d3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -677,6 +677,7 @@ github.com/openshift-kni/eco-goinfra/pkg/nodes github.com/openshift-kni/eco-goinfra/pkg/nodesconfig github.com/openshift-kni/eco-goinfra/pkg/nto github.com/openshift-kni/eco-goinfra/pkg/nvidiagpu +github.com/openshift-kni/eco-goinfra/pkg/oadp github.com/openshift-kni/eco-goinfra/pkg/oadp/oadptypes github.com/openshift-kni/eco-goinfra/pkg/ocm github.com/openshift-kni/eco-goinfra/pkg/olm @@ -695,6 +696,7 @@ github.com/openshift-kni/eco-goinfra/pkg/sriov-fec github.com/openshift-kni/eco-goinfra/pkg/sriov-fec/sriovfectypes github.com/openshift-kni/eco-goinfra/pkg/statefulset github.com/openshift-kni/eco-goinfra/pkg/storage +github.com/openshift-kni/eco-goinfra/pkg/velero github.com/openshift-kni/eco-goinfra/pkg/webhook # github.com/openshift-kni/k8sreporter v1.0.5 ## explicit; go 1.20