@@ -72,8 +72,173 @@ func (r *Reconciler) postProcessReconcile(ctx context.Context, req ctrl.Request,
7272 }
7373}
7474
75+ // shouldTakeOverGitRepository checks if this CustomPackage should take over an existing GitRepository.
76+ // Returns true if this package has higher priority than the current owner.
77+ func (r * Reconciler ) shouldTakeOverGitRepository (ctx context.Context , resource * v1alpha1.CustomPackage , existingRepo * v1alpha1.GitRepository ) (bool , error ) {
78+ logger := log .FromContext (ctx )
79+
80+ // Get this package's priority
81+ thisPriority , err := getPackagePriority (resource )
82+ if err != nil {
83+ // If no priority annotation, assume it's a legacy package - allow takeover for backward compat
84+ logger .V (1 ).Info ("no priority on this package, allowing takeover" , "package" , resource .Name )
85+ return true , nil
86+ }
87+
88+ // Check the existing repo's owner references
89+ for _ , ownerRef := range existingRepo .GetOwnerReferences () {
90+ if ownerRef .Kind == "CustomPackage" {
91+ // Fetch the owner CustomPackage to get its priority
92+ ownerPkg := & v1alpha1.CustomPackage {}
93+ err := r .Client .Get (ctx , client.ObjectKey {
94+ Name : ownerRef .Name ,
95+ Namespace : existingRepo .Namespace ,
96+ }, ownerPkg )
97+
98+ if err != nil {
99+ if errors .IsNotFound (err ) {
100+ // Owner doesn't exist anymore, we can take over
101+ logger .Info ("GitRepository owner not found, taking over" , "repo" , existingRepo .Name )
102+ return true , nil
103+ }
104+ return false , fmt .Errorf ("getting owner package: %w" , err )
105+ }
106+
107+ // Get owner's priority
108+ ownerPriority , err := getPackagePriority (ownerPkg )
109+ if err != nil {
110+ // Owner has no priority, we can take over
111+ logger .V (1 ).Info ("GitRepository owner has no priority, taking over" , "repo" , existingRepo .Name )
112+ return true , nil
113+ }
114+
115+ // Only take over if we have HIGHER priority (higher number wins)
116+ if thisPriority > ownerPriority {
117+ logger .Info ("Taking over GitRepository from lower priority owner" ,
118+ "repo" , existingRepo .Name ,
119+ "ourPriority" , thisPriority ,
120+ "ownerPriority" , ownerPriority ,
121+ "owner" , ownerPkg .Name )
122+ return true , nil
123+ } else {
124+ logger .Info ("Not taking over GitRepository owned by higher/equal priority package" ,
125+ "repo" , existingRepo .Name ,
126+ "ourPriority" , thisPriority ,
127+ "ownerPriority" , ownerPriority ,
128+ "owner" , ownerPkg .Name )
129+ return false , nil
130+ }
131+ }
132+ }
133+
134+ // No CustomPackage owner found, we can take over
135+ logger .V (1 ).Info ("GitRepository has no CustomPackage owner, taking over" , "repo" , existingRepo .Name )
136+ return true , nil
137+ }
138+
139+ // shouldReconcile checks if this CustomPackage should proceed with reconciliation.
140+ // It returns true only if this is the highest priority CustomPackage for the same app.
141+ func (r * Reconciler ) shouldReconcile (ctx context.Context , resource * v1alpha1.CustomPackage ) (bool , error ) {
142+ logger := log .FromContext (ctx )
143+
144+ // Get this package's priority
145+ thisPriority , err := getPackagePriority (resource )
146+ if err != nil {
147+ // If no priority annotation, assume it's a legacy package and allow it
148+ logger .V (1 ).Info ("no priority annotation found, assuming legacy package" , "name" , resource .Name )
149+ return true , nil
150+ }
151+
152+ // List all CustomPackages in the same namespace
153+ pkgList := & v1alpha1.CustomPackageList {}
154+ err = r .Client .List (ctx , pkgList , client .InNamespace (resource .Namespace ))
155+ if err != nil {
156+ return false , fmt .Errorf ("listing custom packages: %w" , err )
157+ }
158+
159+ // Check if any other CustomPackage has the same app name with higher priority
160+ for i := range pkgList .Items {
161+ pkg := & pkgList .Items [i ]
162+
163+ // Skip self
164+ if pkg .Name == resource .Name {
165+ continue
166+ }
167+
168+ // Skip if different app name
169+ if pkg .Spec .ArgoCD .Name != resource .Spec .ArgoCD .Name {
170+ continue
171+ }
172+
173+ // Get the other package's priority
174+ otherPriority , err := getPackagePriority (pkg )
175+ if err != nil {
176+ // If the other package has no priority, this one wins
177+ continue
178+ }
179+
180+ // If another package has higher priority, this one should not reconcile
181+ if otherPriority > thisPriority {
182+ thisSource := resource .ObjectMeta .Annotations [v1alpha1 .PackageSourcePathAnnotation ]
183+ otherSource := pkg .ObjectMeta .Annotations [v1alpha1 .PackageSourcePathAnnotation ]
184+ logger .Info ("Yielding to higher priority package - skipping all reconciliation" ,
185+ "appName" , resource .Spec .ArgoCD .Name ,
186+ "yieldingPackage" , thisSource ,
187+ "yieldingPriority" , thisPriority ,
188+ "activePackage" , otherSource ,
189+ "activePriority" , otherPriority )
190+ return false , nil
191+ }
192+ }
193+
194+ return true , nil
195+ }
196+
197+ // getPackagePriority extracts the priority from a CustomPackage's annotations
198+ func getPackagePriority (pkg * v1alpha1.CustomPackage ) (int , error ) {
199+ if pkg .ObjectMeta .Annotations == nil {
200+ return 0 , fmt .Errorf ("no annotations" )
201+ }
202+
203+ priorityStr , ok := pkg .ObjectMeta .Annotations [v1alpha1 .PackagePriorityAnnotation ]
204+ if ! ok {
205+ return 0 , fmt .Errorf ("no priority annotation" )
206+ }
207+
208+ var priority int
209+ _ , err := fmt .Sscanf (priorityStr , "%d" , & priority )
210+ if err != nil {
211+ return 0 , fmt .Errorf ("invalid priority format: %w" , err )
212+ }
213+
214+ return priority , nil
215+ }
216+
75217// create an in-cluster repository CR, update the application spec, then apply
76218func (r * Reconciler ) reconcileCustomPackage (ctx context.Context , resource * v1alpha1.CustomPackage ) (ctrl.Result , error ) {
219+ logger := log .FromContext (ctx )
220+
221+ // Check if this package should reconcile
222+ shouldReconcile , err := r .shouldReconcile (ctx , resource )
223+ if err != nil {
224+ return ctrl.Result {}, fmt .Errorf ("checking if should reconcile: %w" , err )
225+ }
226+
227+ if ! shouldReconcile {
228+ // This package has been superseded by a higher priority package
229+ // Mark as not synced and don't update resources, and don't requeue
230+ logger .Info ("package superseded by higher priority, skipping reconciliation" ,
231+ "name" , resource .Name ,
232+ "appName" , resource .Spec .ArgoCD .Name ,
233+ "sourcePath" , resource .ObjectMeta .Annotations [v1alpha1 .PackageSourcePathAnnotation ])
234+ resource .Status .Synced = false
235+ return ctrl.Result {}, nil
236+ }
237+
238+ logger .V (1 ).Info ("proceeding with reconciliation as highest priority package" ,
239+ "name" , resource .Name ,
240+ "appName" , resource .Spec .ArgoCD .Name )
241+
77242 b , err := r .getArgoCDAppFile (ctx , resource )
78243 if err != nil {
79244 return ctrl.Result {}, fmt .Errorf ("reading file %s: %w" , resource .Spec .ArgoCD .ApplicationFile , err )
@@ -219,7 +384,12 @@ func (r *Reconciler) reconcileArgoCDApp(ctx context.Context, resource *v1alpha1.
219384 }
220385 resource .Status .GitRepositoryRefs = repoRefs
221386 resource .Status .Synced = appSourcesSynced
222- return ctrl.Result {RequeueAfter : requeueTime }, nil
387+
388+ // Only requeue if not synced yet to avoid continuous reconciliation
389+ if ! appSourcesSynced {
390+ return ctrl.Result {RequeueAfter : requeueTime }, nil
391+ }
392+ return ctrl.Result {}, nil
223393}
224394
225395func (r * Reconciler ) reconcileArgoCDAppSet (ctx context.Context , resource * v1alpha1.CustomPackage , appSet * argov1alpha1.ApplicationSet ) (ctrl.Result , error ) {
@@ -270,7 +440,11 @@ func (r *Reconciler) reconcileArgoCDAppSet(ctx context.Context, resource *v1alph
270440
271441 resource .Status .Synced = resource .Status .Synced && gitGeneratorsSynced
272442
273- return ctrl.Result {RequeueAfter : requeueTime }, nil
443+ // Only requeue if not synced yet to avoid continuous reconciliation
444+ if ! resource .Status .Synced {
445+ return ctrl.Result {RequeueAfter : requeueTime }, nil
446+ }
447+ return ctrl.Result {}, nil
274448}
275449
276450// create a gitrepository custom resource, then let the git repository controller take care of the rest
@@ -285,6 +459,7 @@ func (r *Reconciler) reconcileArgoCDSource(ctx context.Context, resource *v1alph
285459}
286460
287461func (r * Reconciler ) reconcileArgoCDSourceFromRemote (ctx context.Context , resource * v1alpha1.CustomPackage , appName , repoURL string ) (ctrl.Result , * v1alpha1.GitRepository , error ) {
462+ logger := log .FromContext (ctx )
288463 relativePath := strings .TrimPrefix (repoURL , v1alpha1 .CNOEURIScheme )
289464 // no guarantee that this path exists
290465 dirPath := filepath .Join (resource .Spec .RemoteRepository .Path , relativePath )
@@ -296,9 +471,34 @@ func (r *Reconciler) reconcileArgoCDSourceFromRemote(ctx context.Context, resour
296471 },
297472 }
298473
474+ // Check if GitRepository already exists
475+ existingRepo := & v1alpha1.GitRepository {}
476+ getErr := r .Client .Get (ctx , client .ObjectKeyFromObject (repo ), existingRepo )
477+
478+ if getErr == nil {
479+ // GitRepository exists - check if we should take it over
480+ shouldTakeOver , checkErr := r .shouldTakeOverGitRepository (ctx , resource , existingRepo )
481+ if checkErr != nil {
482+ return ctrl.Result {}, nil , fmt .Errorf ("checking if should take over git repository: %w" , checkErr )
483+ }
484+
485+ if ! shouldTakeOver {
486+ // A higher/equal priority package owns this, just return it without updating
487+ logger .V (1 ).Info ("Using existing GitRepository owned by higher priority package" ,
488+ "repo" , repo .Name ,
489+ "ourPriority" , resource .ObjectMeta .Annotations [v1alpha1 .PackagePriorityAnnotation ])
490+ return ctrl.Result {}, existingRepo , nil
491+ }
492+ // We should take it over - proceed with CreateOrUpdate below
493+ } else if ! errors .IsNotFound (getErr ) {
494+ return ctrl.Result {}, nil , getErr
495+ }
496+ // GitRepository doesn't exist or we should take it over
497+
299498 cliStartTime , _ := util .GetCLIStartTimeAnnotationValue (resource .ObjectMeta .Annotations )
300499
301- _ , err := controllerutil .CreateOrUpdate (ctx , r .Client , repo , func () error {
500+ var err error
501+ _ , err = controllerutil .CreateOrUpdate (ctx , r .Client , repo , func () error {
302502 if err := controllerutil .SetControllerReference (resource , repo , r .Scheme ); err != nil {
303503 return err
304504 }
@@ -349,6 +549,30 @@ func (r *Reconciler) reconcileArgoCDSourceFromLocal(ctx context.Context, resourc
349549 },
350550 }
351551
552+ // Check if GitRepository already exists
553+ existingRepo := & v1alpha1.GitRepository {}
554+ getErr := r .Client .Get (ctx , client .ObjectKeyFromObject (repo ), existingRepo )
555+
556+ if getErr == nil {
557+ // GitRepository exists - check if we should take it over
558+ shouldTakeOver , checkErr := r .shouldTakeOverGitRepository (ctx , resource , existingRepo )
559+ if checkErr != nil {
560+ return ctrl.Result {}, nil , fmt .Errorf ("checking if should take over git repository: %w" , checkErr )
561+ }
562+
563+ if ! shouldTakeOver {
564+ // A higher/equal priority package owns this, just return it without updating
565+ logger .V (1 ).Info ("Using existing GitRepository owned by higher priority package" ,
566+ "repo" , repo .Name ,
567+ "ourPriority" , resource .ObjectMeta .Annotations [v1alpha1 .PackagePriorityAnnotation ])
568+ return ctrl.Result {}, existingRepo , nil
569+ }
570+ // We should take it over - proceed with CreateOrUpdate below
571+ } else if ! errors .IsNotFound (getErr ) {
572+ return ctrl.Result {}, nil , getErr
573+ }
574+ // GitRepository doesn't exist or we should take it over
575+
352576 cliStartTime , _ := util .GetCLIStartTimeAnnotationValue (resource .ObjectMeta .Annotations )
353577
354578 _ , err = controllerutil .CreateOrUpdate (ctx , r .Client , repo , func () error {
@@ -469,7 +693,8 @@ func (r *Reconciler) reconcileHelmValueObjectSource(ctx context.Context,
469693 val [k ] = v
470694 }
471695 }
472- return ctrl.Result {RequeueAfter : requeueTime }, nil
696+ // No need to requeue for helm value processing
697+ return ctrl.Result {}, nil
473698}
474699
475700func localRepoName (appName , dir string ) string {
0 commit comments