diff --git a/Makefile b/Makefile index 819c36cb..e02cccf8 100644 --- a/Makefile +++ b/Makefile @@ -200,7 +200,7 @@ manifests: controller-gen ## Generate manifests e.g. CRD, RBAC etc. generate: controller-gen ## Generate code e.g. API etc. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -bundle-manifests: +bundle-manifests: yq $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle \ -q --overwrite --version $(OPERATOR_VERSION) $(BUNDLE_METADATA_OPTS) $(OPERATOR_SDK) bundle validate ./bundle diff --git a/api/v1alpha1/operandregistry_types.go b/api/v1alpha1/operandregistry_types.go index 94955f3b..becab46e 100644 --- a/api/v1alpha1/operandregistry_types.go +++ b/api/v1alpha1/operandregistry_types.go @@ -76,6 +76,8 @@ type Operator struct { // OperatorConfig is the name of the OperatorConfig // +optional OperatorConfig string `json:"operatorConfig,omitempty"` + // UserManaged is a flag to indicate whether operator is managed by user + UserManaged bool `json:"userManaged,omitempty"` } // +kubebuilder:validation:Enum=public;private diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 26f36494..d8720dec 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated // // Copyright 2022 IBM Corporation diff --git a/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml b/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml index db3acfe8..0aaee96d 100644 --- a/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml +++ b/bundle/manifests/operand-deployment-lifecycle-manager.clusterserviceversion.yaml @@ -129,7 +129,7 @@ metadata: categories: Developer Tools, Monitoring, Logging & Tracing, Security certified: "false" containerImage: icr.io/cpopen/odlm:latest - createdAt: "2024-04-04T03:40:45Z" + createdAt: "2024-08-27T18:18:40Z" description: The Operand Deployment Lifecycle Manager provides a Kubernetes CRD-based API to manage the lifecycle of operands. nss.operator.ibm.com/managed-operators: ibm-odlm olm.skipRange: '>=1.2.0 <4.3.5' @@ -664,8 +664,6 @@ spec: ephemeral-storage: 256Mi memory: 200Mi securityContext: - seccompProfile: - type: RuntimeDefault allowPrivilegeEscalation: false capabilities: drop: @@ -673,6 +671,8 @@ spec: privileged: false readOnlyRootFilesystem: true runAsNonRoot: true + seccompProfile: + type: RuntimeDefault serviceAccount: operand-deployment-lifecycle-manager serviceAccountName: operand-deployment-lifecycle-manager terminationGracePeriodSeconds: 10 diff --git a/bundle/manifests/operator.ibm.com_operandregistries.yaml b/bundle/manifests/operator.ibm.com_operandregistries.yaml index a8f57f4e..8618ce06 100644 --- a/bundle/manifests/operator.ibm.com_operandregistries.yaml +++ b/bundle/manifests/operator.ibm.com_operandregistries.yaml @@ -2055,6 +2055,10 @@ spec: items: type: string type: array + userManaged: + description: UserManaged is a flag to indicate whether operator + is managed by user + type: boolean required: - channel - name diff --git a/common/Makefile.common.mk b/common/Makefile.common.mk index 4574e06a..fcc2796f 100644 --- a/common/Makefile.common.mk +++ b/common/Makefile.common.mk @@ -86,7 +86,7 @@ fetch-test-crds: CONTROLLER_GEN ?= $(shell pwd)/common/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. - $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1) + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0) KIND ?= $(shell pwd)/common/bin/kind kind: ## Download kind locally if necessary. diff --git a/common/scripts/lint_go.sh b/common/scripts/lint_go.sh index 3fd5d346..e3163d8f 100755 --- a/common/scripts/lint_go.sh +++ b/common/scripts/lint_go.sh @@ -15,4 +15,4 @@ # limitations under the License. # -GOGC=25 golangci-lint run -c ./common/config/.golangci.yml --timeout=180s +GOGC=25 golangci-lint run -c ./common/config/.golangci.yml --timeout=300s diff --git a/config/crd/bases/operator.ibm.com_operandregistries.yaml b/config/crd/bases/operator.ibm.com_operandregistries.yaml index b452d559..54197b1b 100644 --- a/config/crd/bases/operator.ibm.com_operandregistries.yaml +++ b/config/crd/bases/operator.ibm.com_operandregistries.yaml @@ -2051,6 +2051,10 @@ spec: items: type: string type: array + userManaged: + description: UserManaged is a flag to indicate whether operator + is managed by user + type: boolean required: - channel - name diff --git a/controllers/operandrequest/operandrequest_controller.go b/controllers/operandrequest/operandrequest_controller.go index b00fee48..0f82ae56 100644 --- a/controllers/operandrequest/operandrequest_controller.go +++ b/controllers/operandrequest/operandrequest_controller.go @@ -238,18 +238,19 @@ func (r *Reconciler) checkFinalizer(ctx context.Context, requestInstance *operat for _, m := range requestInstance.Status.Members { remainingOperands.Add(m.Name) } - existingSub := &olmv1alpha1.SubscriptionList{} - - opts := []client.ListOption{ - client.MatchingLabels(map[string]string{constant.OpreqLabel: "true"}), - } - - if err := r.Client.List(ctx, existingSub, opts...); err != nil { - return err - } - if len(existingSub.Items) == 0 { - return nil - } + // TODO: update to check OperandRequest status to see if member is user managed or not + // existingSub := &olmv1alpha1.SubscriptionList{} + + // opts := []client.ListOption{ + // client.MatchingLabels(map[string]string{constant.OpreqLabel: "true"}), + // } + + // if err := r.Client.List(ctx, existingSub, opts...); err != nil { + // return err + // } + // if len(existingSub.Items) == 0 { + // return nil + // } // Delete all the subscriptions that created by current request if err := r.absentOperatorsAndOperands(ctx, requestInstance, &remainingOperands); err != nil { return err diff --git a/controllers/operandrequest/reconcile_operand.go b/controllers/operandrequest/reconcile_operand.go index a23c7076..45e09967 100644 --- a/controllers/operandrequest/reconcile_operand.go +++ b/controllers/operandrequest/reconcile_operand.go @@ -99,53 +99,67 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper namespace := r.GetOperatorNamespace(opdRegistry.InstallMode, opdRegistry.Namespace) sub, err := r.GetSubscription(ctx, operatorName, namespace, registryInstance.Namespace, opdRegistry.PackageName) - - if sub == nil && err == nil { - klog.Warningf("There is no Subscription %s or %s in the namespace %s and %s", operatorName, opdRegistry.PackageName, namespace, registryInstance.Namespace) - continue - - } else if err != nil { + if err != nil { merr.Add(errors.Wrapf(err, "failed to get the Subscription %s in the namespace %s and %s", operatorName, namespace, registryInstance.Namespace)) return merr } - if _, ok := sub.Labels[constant.OpreqLabel]; !ok { - // Subscription existing and not managed by OperandRequest controller - klog.Warningf("Subscription %s in the namespace %s isn't created by ODLM", sub.Name, sub.Namespace) - } + if !opdRegistry.UserManaged { + if sub == nil { + klog.Warningf("There is no Subscription %s or %s in the namespace %s and %s", operatorName, opdRegistry.PackageName, namespace, registryInstance.Namespace) + continue + } - // It the installplan is not created yet, ODLM will try later - if sub.Status.Install == nil || sub.Status.InstallPlanRef.Name == "" { - klog.Warningf("The Installplan for Subscription %s is not ready. Will check it again", sub.Name) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorInstalling, "", &r.Mutex) - continue - } + if _, ok := sub.Labels[constant.OpreqLabel]; !ok { + // Subscription existing and not managed by OperandRequest controller + klog.Warningf("Subscription %s in the namespace %s isn't created by ODLM", sub.Name, sub.Namespace) + } - // If the installplan is deleted after is completed, ODLM won't block the CR update. - ipName := sub.Status.InstallPlanRef.Name - ipNamespace := sub.Namespace - ip := &olmv1alpha1.InstallPlan{} - ipKey := types.NamespacedName{ - Name: ipName, - Namespace: ipNamespace, - } - if err := r.Client.Get(ctx, ipKey, ip); err != nil { - if !apierrors.IsNotFound(err) { - merr.Add(errors.Wrapf(err, "failed to get Installplan")) + // It the installplan is not created yet, ODLM will try later + if sub.Status.Install == nil || sub.Status.InstallPlanRef.Name == "" { + klog.Warningf("The Installplan for Subscription %s is not ready. Will check it again", sub.Name) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorInstalling, "", &r.Mutex) + continue } - } else if ip.Status.Phase == olmv1alpha1.InstallPlanPhaseFailed { - klog.Errorf("installplan %s/%s is failed", ipNamespace, ipName) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) - continue + + // If the installplan is deleted after is completed, ODLM won't block the CR update. + ipName := sub.Status.InstallPlanRef.Name + ipNamespace := sub.Namespace + ip := &olmv1alpha1.InstallPlan{} + ipKey := types.NamespacedName{ + Name: ipName, + Namespace: ipNamespace, + } + if err := r.Client.Get(ctx, ipKey, ip); err != nil { + if !apierrors.IsNotFound(err) { + merr.Add(errors.Wrapf(err, "failed to get Installplan")) + } + } else if ip.Status.Phase == olmv1alpha1.InstallPlanPhaseFailed { + klog.Errorf("installplan %s/%s is failed", ipNamespace, ipName) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } + } - csv, err := r.GetClusterServiceVersion(ctx, sub) + var csv *olmv1alpha1.ClusterServiceVersion - // If can't get CSV, requeue the request - if err != nil { - merr.Add(err) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) - continue + if opdRegistry.UserManaged { + csvList, err := r.GetClusterServiceVersionListFromPackage(ctx, opdRegistry.PackageName, opdRegistry.Namespace) + if err != nil { + merr.Add(err) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } + csv = csvList[0] + } else { + csv, err = r.GetClusterServiceVersion(ctx, sub) + // If can't get CSV, requeue the request + if err != nil { + merr.Add(err) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } } if csv == nil { @@ -160,20 +174,22 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper continue } - // find the OperandRequest which has the same operator's channel version as existing subscription. - // ODLM will only reconcile Operand based on OperandConfig for this OperandRequest - var requestList []string - reg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/request`) - for anno, version := range sub.Annotations { - if reg.MatchString(anno) && version == sub.Spec.Channel { - requestList = append(requestList, anno) + if !opdRegistry.UserManaged { + // find the OperandRequest which has the same operator's channel version as existing subscription. + // ODLM will only reconcile Operand based on OperandConfig for this OperandRequest + var requestList []string + reg, _ := regexp.Compile(`^(.*)\.(.*)\.(.*)\/request`) + for anno, version := range sub.Annotations { + if reg.MatchString(anno) && version == sub.Spec.Channel { + requestList = append(requestList, anno) + } } - } - if len(requestList) == 0 || !util.Contains(requestList, requestInstance.Namespace+"."+requestInstance.Name+"."+operand.Name+"/request") { - klog.Infof("Subscription %s in the namespace %s is NOT managed by %s/%s, Skip reconciling Operands", sub.Name, sub.Namespace, requestInstance.Namespace, requestInstance.Name) - requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) - continue + if len(requestList) == 0 || !util.Contains(requestList, requestInstance.Namespace+"."+requestInstance.Name+"."+operand.Name+"/request") { + klog.Infof("Subscription %s in the namespace %s is NOT managed by %s/%s, Skip reconciling Operands", sub.Name, sub.Namespace, requestInstance.Namespace, requestInstance.Name) + requestInstance.SetMemberStatus(operand.Name, operatorv1alpha1.OperatorFailed, "", &r.Mutex) + continue + } } klog.V(3).Info("Generating customresource base on ClusterServiceVersion: ", csv.GetName()) @@ -185,11 +201,11 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper if err == nil { // Check the requested Service Config if exist in specific OperandConfig opdConfig := configInstance.GetService(operand.Name) - if opdConfig == nil { + if opdConfig == nil && !opdRegistry.UserManaged { klog.V(2).Infof("There is no service: %s from the OperandConfig instance: %s/%s, Skip reconciling Operands", operand.Name, registryKey.Namespace, req.Registry) continue } - err = r.reconcileCRwithConfig(ctx, opdConfig, configInstance.Name, configInstance.Namespace, csv, requestInstance, operand.Name, sub.Namespace, &r.Mutex) + err = r.reconcileCRwithConfig(ctx, opdConfig, configInstance.Name, configInstance.Namespace, csv, requestInstance, operand.Name, csv.Namespace, &r.Mutex) if err != nil { merr.Add(err) requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceFailed, &r.Mutex) @@ -203,7 +219,7 @@ func (r *Reconciler) reconcileOperand(ctx context.Context, requestInstance *oper } } else { - err = r.reconcileCRwithRequest(ctx, requestInstance, operand, types.NamespacedName{Name: requestInstance.Name, Namespace: requestInstance.Namespace}, i, sub.Namespace, &r.Mutex) + err = r.reconcileCRwithRequest(ctx, requestInstance, operand, types.NamespacedName{Name: requestInstance.Name, Namespace: requestInstance.Namespace}, i, csv.Namespace, &r.Mutex) if err != nil { merr.Add(err) requestInstance.SetMemberStatus(operand.Name, "", operatorv1alpha1.ServiceFailed, &r.Mutex) diff --git a/controllers/operandrequest/reconcile_operator.go b/controllers/operandrequest/reconcile_operator.go index bbd0d5d5..e714274a 100644 --- a/controllers/operandrequest/reconcile_operator.go +++ b/controllers/operandrequest/reconcile_operator.go @@ -160,6 +160,19 @@ func (r *Reconciler) reconcileSubscription(ctx context.Context, requestInstance namespace := r.GetOperatorNamespace(opt.InstallMode, opt.Namespace) sub, err := r.GetSubscription(ctx, opt.Name, namespace, registryInstance.Namespace, opt.PackageName) + if opt.UserManaged { + klog.Infof("Skip installing operator %s because it is managed by user", opt.PackageName) + csvList, err := r.GetClusterServiceVersionListFromPackage(ctx, opt.PackageName, namespace) + if err != nil { + return errors.Wrapf(err, "failed to get CSV from package %s/%s", namespace, opt.PackageName) + } + if len(csvList) == 0 { + return errors.New("operator " + opt.Name + " is user managed, but no CSV exists, waiting...") + } + requestInstance.SetMemberStatus(opt.Name, operatorv1alpha1.OperatorUpdating, "", mu) + return nil + } + if sub == nil && err == nil { if opt.InstallMode == operatorv1alpha1.InstallModeNoop { requestInstance.SetNoSuitableRegistryCondition(registryKey.String(), opt.Name+" is in maintenance status", operatorv1alpha1.ResourceTypeOperandRegistry, corev1.ConditionTrue, &r.Mutex) @@ -403,9 +416,10 @@ func (r *Reconciler) uninstallOperatorsAndOperands(ctx context.Context, operandN } if _, ok := sub.Labels[constant.OpreqLabel]; !ok { - // Subscription existing and not managed by OperandRequest controller - klog.V(2).Infof("Subscription %s in the namespace %s isn't created by ODLM", sub.Name, sub.Namespace) - return nil + if !op.UserManaged { + klog.V(2).Infof("Subscription %s in the namespace %s isn't created by ODLM and isn't user managed", sub.Name, sub.Namespace) + return nil + } } uninstallOperator, uninstallOperand := checkSubAnnotationsForUninstall(requestInstance.ObjectMeta.Name, requestInstance.ObjectMeta.Namespace, op.Name, sub) @@ -480,6 +494,54 @@ func (r *Reconciler) uninstallOperatorsAndOperands(ctx context.Context, operandN return nil } +func (r *Reconciler) uninstallOperands(ctx context.Context, operandName string, requestInstance *operatorv1alpha1.OperandRequest, registryInstance *operatorv1alpha1.OperandRegistry, configInstance *operatorv1alpha1.OperandConfig) error { + // No error handling for un-installation step in case Catalog has been deleted + op, _ := r.GetOperandFromRegistry(ctx, registryInstance, operandName) + if op == nil { + klog.Warningf("Operand %s not found", operandName) + return nil + } + + namespace := r.GetOperatorNamespace(op.InstallMode, op.Namespace) + uninstallOperand := false + operatorStatus, ok := registryInstance.Status.OperatorsStatus[op.Name] + if !ok { + return nil + } + if operatorStatus.ReconcileRequests == nil { + return nil + } + if len(operatorStatus.ReconcileRequests) > 1 { + return nil + } + if operatorStatus.ReconcileRequests[0].Name == requestInstance.Name { + uninstallOperand = true + } + + // get list reconcileRequests + // ignore the name which triggered reconcile + // if list is empty then uninstallOperand = true + + if csvList, err := r.GetClusterServiceVersionListFromPackage(ctx, op.PackageName, namespace); err != nil { + // If can't get CSV, requeue the request + return err + } else if csvList != nil { + klog.Infof("Found %d ClusterServiceVersions for package %s/%s", len(csvList), op.Name, namespace) + if uninstallOperand { + klog.V(2).Infof("Deleting all the Custom Resources for CSV, Namespace: %s, Name: %s", csvList[0].Namespace, csvList[0].Name) + if err := r.deleteAllCustomResource(ctx, csvList[0], requestInstance, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + klog.V(2).Infof("Deleting all the k8s Resources for CSV, Namespace: %s, Name: %s", csvList[0].Namespace, csvList[0].Name) + if err := r.deleteAllK8sResource(ctx, configInstance, operandName, configInstance.Namespace); err != nil { + return err + } + } + } + + return nil +} + func (r *Reconciler) absentOperatorsAndOperands(ctx context.Context, requestInstance *operatorv1alpha1.OperandRequest, remainingOperands *gset.Set) error { needDeletedOperands := r.getNeedDeletedOperands(requestInstance) @@ -510,11 +572,24 @@ func (r *Reconciler) absentOperatorsAndOperands(ctx context.Context, requestInst wg.Add(1) go func() { defer wg.Done() - if err := r.uninstallOperatorsAndOperands(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { - r.Mutex.Lock() - defer r.Mutex.Unlock() - merr.Add(err) - return // return here to avoid removing the operand from remainingOperands + op, _ := r.GetOperandFromRegistry(ctx, registryInstance, fmt.Sprintf("%v", o)) + if op == nil { + klog.Warningf("Operand %s not found", fmt.Sprintf("%v", o)) + } + if op != nil && !op.UserManaged { + if err := r.uninstallOperatorsAndOperands(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return // return here to avoid removing the operand from remainingOperands + } + } else { + if err := r.uninstallOperands(ctx, fmt.Sprintf("%v", o), requestInstance, registryInstance, configInstance); err != nil { + r.Mutex.Lock() + defer r.Mutex.Unlock() + merr.Add(err) + return // return here to avoid removing the operand from remainingOperands + } } requestInstance.RemoveServiceStatus(fmt.Sprintf("%v", o), &r.Mutex) (*remainingOperands).Remove(o) @@ -694,6 +769,10 @@ func checkSubAnnotationsForUninstall(reqName, reqNs, opName string, sub *olmv1al uninstallOperator = false } + if _, ok := sub.Labels[constant.OpreqLabel]; !ok { + uninstallOperator = false + } + // If the removed/uninstalled /request annotation's value is NOT the same as all other /request annotation's values. // or the operator namespace in one of remaining annotation is the same as the operator name in removed/uninstalled /request // the operand should NOT be uninstalled. diff --git a/controllers/operandrequest/reconcile_operator_test.go b/controllers/operandrequest/reconcile_operator_test.go index baf63408..c141c48f 100644 --- a/controllers/operandrequest/reconcile_operator_test.go +++ b/controllers/operandrequest/reconcile_operator_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/types" operatorv1alpha1 "github.com/IBM/operand-deployment-lifecycle-manager/v4/api/v1alpha1" + "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant" ) func TestGenerateClusterObjects(t *testing.T) { @@ -125,6 +126,9 @@ func TestCheckSubAnnotationsForUninstall(t *testing.T) { reqNsA + "." + reqNameA + "." + opNameA + "/request": opChannelA, reqNsA + "." + reqNameA + "." + opNameA + "/operatorNamespace": reqNsA, }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, }, } @@ -147,6 +151,9 @@ func TestCheckSubAnnotationsForUninstall(t *testing.T) { reqNsB + "." + reqNameB + "." + opNameB + "/request": opChannelB, reqNsB + "." + reqNameB + "." + opNameB + "/operatorNamespace": reqNsB, }, + Labels: map[string]string{ + constant.OpreqLabel: "true", + }, }, } diff --git a/controllers/operator/manager.go b/controllers/operator/manager.go index 810987ec..ff54761d 100644 --- a/controllers/operator/manager.go +++ b/controllers/operator/manager.go @@ -429,6 +429,47 @@ func (m *ODLMOperator) GetClusterServiceVersionList(ctx context.Context, sub *ol return csvs, nil } +// GetClusterServiceVersionList gets a list of ClusterServiceVersions from the subscription +func (m *ODLMOperator) GetClusterServiceVersionListFromPackage(ctx context.Context, name, namespace string) ([]*olmv1alpha1.ClusterServiceVersion, error) { + packageName := name + csvNamespace := namespace + + csvList := &olmv1alpha1.ClusterServiceVersionList{} + + opts := []client.ListOption{ + client.InNamespace(csvNamespace), + } + + if err := m.Reader.List(ctx, csvList, opts...); err != nil { + if apierrors.IsNotFound(err) || len(csvList.Items) == 0 { + klog.V(3).Infof("No ClusterServiceVersion found") + return nil, nil + } + return nil, errors.Wrapf(err, "failed to list ClusterServiceVersions") + } + + var csvs []*olmv1alpha1.ClusterServiceVersion + // filter csvList to find one(s) that contain packageName + for _, v := range csvList.Items { + csv := v + if csv.Annotations == nil { + continue + } + if _, ok := csv.Annotations["operatorframework.io/properties"]; !ok { + continue + } + annotation := fmt.Sprintf("\"packageName\":\"%s\"", packageName) + if !strings.Contains(csv.Annotations["operatorframework.io/properties"], annotation) { + continue + } + klog.V(3).Infof("Get ClusterServiceVersion %s in the namespace %s", csv.Name, csvNamespace) + csvs = append(csvs, &csv) + } + + klog.V(3).Infof("Get %v ClusterServiceVersions in the namespace %s", len(csvs), csvNamespace) + return csvs, nil +} + func (m *ODLMOperator) DeleteRedundantCSV(ctx context.Context, csvName, operatorNs, serviceNs, packageName string) error { // Get the csv by its name and namespace csv := &olmv1alpha1.ClusterServiceVersion{} @@ -450,7 +491,7 @@ func (m *ODLMOperator) DeleteRedundantCSV(ctx context.Context, csvName, operator } // Delete the CSV if the csv does not contain label operators.coreos.com/packageName.operatorNs='' AND does not contains label olm.copiedFrom: operatorNs if _, ok := csv.GetLabels()[fmt.Sprintf("operators.coreos.com/%s.%s", packageName, operatorNs)]; !ok { - if csv.GetLabels()["olm.copiedFrom"] != operatorNs { + if _, ok := csv.Labels["olm.copiedFrom"]; !ok { klog.Infof("Delete the redundant ClusterServiceVersion %s in the namespace %s", csvName, serviceNs) if err := m.Client.Delete(ctx, csv); err != nil { return errors.Wrapf(err, "failed to delete ClusterServiceVersion %s/%s", serviceNs, csvName)