diff --git a/webhooks/component_webhook.go b/webhooks/component_webhook.go index 95c81d714..7778529b4 100644 --- a/webhooks/component_webhook.go +++ b/webhooks/component_webhook.go @@ -90,7 +90,14 @@ func (r *ComponentWebhook) Default(ctx context.Context, obj runtime.Object) erro Name: hasApplication.Name, UID: hasApplication.UID, } + + if isArgoCDManaged(&curComp, &hasApplication) { + componentlog.Info("component is managed by ArgoCD, skipping owner reference setting") + return nil + } + curComp.SetOwnerReferences(append(curComp.GetOwnerReferences(), ownerReference)) + err = r.client.Update(ctx, &curComp) return err }) @@ -300,3 +307,43 @@ func (r *ComponentWebhook) validateBuildNudgesRefGraph(ctx context.Context, nudg return nil } + +// isArgoCDManaged checks if a component or its Application is managed by ArgoCD +// by looking for ArgoCD tracking annotations or labels +func isArgoCDManaged(component *appstudiov1alpha1.Component, application *appstudiov1alpha1.Application) bool { + + // use a constant for the ArgoCD tracking ID key + const argocdtrackingId = "argocd.argoproj.io/tracking-id" + + // Check component annotations + if component.Annotations != nil { + if _, exists := component.Annotations[argocdtrackingId]; exists { + return true + } + } + + // Check component labels + if component.Labels != nil { + if _, exists := component.Labels[argocdtrackingId]; exists { + return true + } + } + + // Check Application annotations (to avoid race conditions) + if application != nil { + if application.Annotations != nil { + if _, exists := application.Annotations[argocdtrackingId]; exists { + return true + } + } + + // Check Application labels + if application.Labels != nil { + if _, exists := application.Labels[argocdtrackingId]; exists { + return true + } + } + } + + return false +} diff --git a/webhooks/component_webhook_unit_test.go b/webhooks/component_webhook_unit_test.go index 97949d4ca..0b9028ff4 100644 --- a/webhooks/component_webhook_unit_test.go +++ b/webhooks/component_webhook_unit_test.go @@ -734,6 +734,160 @@ func TestUpdateNudgedComponentStatus(t *testing.T) { } } +func TestIsArgoCDManaged(t *testing.T) { + // reusable application for tests with no ArgoCD tracking + normalApplication := &appstudiov1alpha1.Application{ + ObjectMeta: v1.ObjectMeta{ + Name: "application1", + Namespace: "default", + }, + } + + t.Run("should return true when component has ArgoCD annotation", func(t *testing.T) { + component := &appstudiov1alpha1.Component{ + ObjectMeta: v1.ObjectMeta{ + Name: "component1", + Namespace: "default", + Annotations: map[string]string{ + "argocd.argoproj.io/tracking-id": "123", + }, + }, + } + result := isArgoCDManaged(component, normalApplication) + + if !result { + t.Errorf("TestIsArgoCDManaged(): should return true when component has ArgoCD annotation, got false") + } + }) + + t.Run("should return true when component has ArgoCD label", func(t *testing.T) { + component := &appstudiov1alpha1.Component{ + ObjectMeta: v1.ObjectMeta{ + Name: "component1", + Namespace: "default", + Labels: map[string]string{ + "argocd.argoproj.io/tracking-id": "123", + }, + }, + } + + result := isArgoCDManaged(component, normalApplication) + + if !result { + t.Errorf("TestIsArgoCDManaged(): should return true when component has ArgoCD label, got false") + } + }) + + t.Run("should return true when application has ArgoCD annotation", func(t *testing.T) { + // Component without ArgoCD tracking + component := &appstudiov1alpha1.Component{ + ObjectMeta: v1.ObjectMeta{ + Name: "component1", + Namespace: "default", + // Component has no ArgoCD tracking - looks normal + }, + } + + // Application HAS ArgoCD annotation + application := &appstudiov1alpha1.Application{ + ObjectMeta: v1.ObjectMeta{ + Name: "application1", + Namespace: "default", + Annotations: map[string]string{ + "argocd.argoproj.io/tracking-id": "123", + }, + }, + } + + result := isArgoCDManaged(component, application) + + if !result { + t.Errorf("TestIsArgoCDManaged(): should return true when application has ArgoCD annotation, got false") + } + }) + + t.Run("should return true when application has ArgoCD label", func(t *testing.T) { + component := &appstudiov1alpha1.Component{ + ObjectMeta: v1.ObjectMeta{ + Name: "component1", + Namespace: "default", + // Component has no ArgoCD tracking + }, + } + + // Application HAS ArgoCD label (not annotation) + application := &appstudiov1alpha1.Application{ + ObjectMeta: v1.ObjectMeta{ + Name: "application1", + Namespace: "default", + Labels: map[string]string{ + "argocd.argoproj.io/tracking-id": "123", + }, + }, + } + + result := isArgoCDManaged(component, application) + + if !result { + t.Errorf("TestIsArgoCDManaged(): should return true when application has ArgoCD label, got false") + } + }) + + t.Run("should return false when component and application both have no ArgoCD tracking", func(t *testing.T) { + component := &appstudiov1alpha1.Component{ + ObjectMeta: v1.ObjectMeta{ + Name: "component1", + Namespace: "default", + // No annotations, no labels - completely normal component + }, + } + + result := isArgoCDManaged(component, normalApplication) + + if result { + t.Errorf("TestIsArgoCDManaged(): should return false when neither has ArgoCD tracking, got true") + } + }) + + t.Run("should return false when component has no ArgoCD and application is nil", func(t *testing.T) { + component := &appstudiov1alpha1.Component{ + ObjectMeta: v1.ObjectMeta{ + Name: "component1", + Namespace: "default", + // Component has no ArgoCD tracking + }, + } + + // Call with nil application - should not panic and should return false + result := isArgoCDManaged(component, nil) + + if result { + t.Errorf("TestIsArgoCDManaged(): should return false when component has no ArgoCD and application is nil, got true") + } + }) + + t.Run("should return true when component has ArgoCD annotation even with nil application", func(t *testing.T) { + component := &appstudiov1alpha1.Component{ + ObjectMeta: v1.ObjectMeta{ + Name: "component1", + Namespace: "default", + // Component HAS ArgoCD annotation + Annotations: map[string]string{ + "argocd.argoproj.io/tracking-id": "123", + }, + }, + } + + // Even with nil application, should return true because component has annotation + result := isArgoCDManaged(component, nil) + + if !result { + t.Errorf("TestIsArgoCDManaged(): should return true when component has ArgoCD annotation even with nil application, got false") + } + }) + +} + // setUpComponentsForFakeErrorClient creates a fake controller-runtime Kube client with components to test error scenarios func setUpComponentsForFakeErrorClient(t *testing.T) *FakeClient { fakeErrorClient := NewFakeErrorClient(t)