diff --git a/internal/controller/argo.go b/internal/controller/argo.go index a25fa0e20..793913b80 100644 --- a/internal/controller/argo.go +++ b/internal/controller/argo.go @@ -21,6 +21,7 @@ import ( "fmt" "log" "os" + "slices" "strconv" "strings" @@ -961,3 +962,25 @@ func updateHelmParameter(goal api.PatternParameter, actual []argoapi.HelmParamet } return false } + +// syncApplicationWithPrune syncs the application with prune and force options if such a sync is not already in progress. +// Returns true if a sync with prune and force is already in progress, false otherwise +func syncApplicationWithPrune(client argoclient.Interface, app *argoapi.Application, namespace string) (bool, error) { + if app.Operation != nil && app.Operation.Sync != nil && app.Operation.Sync.Prune && slices.Contains(app.Operation.Sync.SyncOptions, "Force=true") { + return true, nil + } + + app.Operation = &argoapi.Operation{ + Sync: &argoapi.SyncOperation{ + Prune: true, + SyncOptions: []string{"Force=true"}, + }, + } + + _, err := client.ArgoprojV1alpha1().Applications(namespace).Update(context.Background(), app, metav1.UpdateOptions{}) + if err != nil { + return false, fmt.Errorf("failed to sync application %q with prune: %w", app.Name, err) + } + + return true, nil +} diff --git a/internal/controller/pattern_controller.go b/internal/controller/pattern_controller.go index a7230d8b8..cd11898de 100644 --- a/internal/controller/pattern_controller.go +++ b/internal/controller/pattern_controller.go @@ -162,7 +162,14 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // -- GitOps Subscription targetSub, _ := newSubscriptionFromConfigMap(r.fullClient) - _ = controllerutil.SetOwnerReference(qualifiedInstance, targetSub, r.Scheme) + operatorConfigMap, err := GetOperatorConfigmap() + if err == nil { + if err := controllerutil.SetOwnerReference(operatorConfigMap, targetSub, r.Scheme); err != nil { + return r.actionPerformed(qualifiedInstance, "error setting owner of gitops subscription", err) + } + } else { + return r.actionPerformed(qualifiedInstance, "error getting operator configmap", err) + } sub, _ := getSubscription(r.olmClient, targetSub.Name) if sub == nil { @@ -176,7 +183,20 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return r.actionPerformed(qualifiedInstance, "update gitops subscription", errSub) } } else { - logOnce("The gitops subscription is not owned by us, leaving untouched") + // Historically the subscription was owned by the pattern, not the operator. If this is the case, + // we update the owner reference to the operator itself. + if err := controllerutil.RemoveOwnerReference(qualifiedInstance, sub, r.Scheme); err == nil { + if err := controllerutil.SetOwnerReference(operatorConfigMap, sub, r.Scheme); err != nil { + return r.actionPerformed(qualifiedInstance, "error setting patterns operator owner reference of gitops subscription", err) + } + // Persist the updated ownerReferences on the Subscription + if _, err := r.olmClient.OperatorsV1alpha1().Subscriptions(SubscriptionNamespace).Update(context.Background(), sub, metav1.UpdateOptions{}); err != nil { + return r.actionPerformed(qualifiedInstance, "error updating gitops subscription owner references", err) + } + return r.actionPerformed(qualifiedInstance, "updated patterns operator owner reference of gitops subscription", nil) + } else { + logOnce("The gitops subscription is not owned by us, leaving untouched") + } } logOnce("subscription found") @@ -528,6 +548,16 @@ func (r *PatternReconciler) finalizeObject(instance *api.Pattern) error { return fmt.Errorf("updated application %q for removal", app.Name) } + if app.Status.Sync.Status == argoapi.SyncStatusCodeOutOfSync { + inProgress, err := syncApplicationWithPrune(r.argoClient, app, ns) + if err != nil { + return err + } + if inProgress { + return fmt.Errorf("sync with prune and force is already in progress for application %q", app.Name) + } + } + if haveACMHub(r) { return fmt.Errorf("waiting for removal of that acm hub") } @@ -604,7 +634,8 @@ func (r *PatternReconciler) onReconcileErrorWithRequeue(p *api.Pattern, reason s } if duration != nil { log.Printf("Requeueing\n") - return reconcile.Result{RequeueAfter: *duration}, err + // Return nil error when we have a duration to avoid exponential backoff + return reconcile.Result{RequeueAfter: *duration}, nil } return reconcile.Result{}, err } diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 7ad533a10..4b98e8565 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -40,6 +40,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + ctrl "sigs.k8s.io/controller-runtime" configv1 "github.com/openshift/api/config/v1" ) @@ -404,3 +405,17 @@ func IsCommonSlimmed(patternPath string) bool { } return true } + +// Gets the configmap for the Patterns Operator. (Used as an owner reference for the operator itself.) +func GetOperatorConfigmap() (*corev1.ConfigMap, error) { + config, err := ctrl.GetConfig() + if err != nil { + return nil, fmt.Errorf("failed to get config: %s", err) + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to call NewForConfig: %s", err) + } + + return clientset.CoreV1().ConfigMaps(OperatorNamespace).Get(context.Background(), OperatorConfigMap, metav1.GetOptions{}) +}