66 "os"
77 "path/filepath"
88 "strings"
9+ "time"
910
1011 "github.com/argoproj-labs/argocd-image-updater/pkg/common"
1112 "github.com/argoproj-labs/argocd-image-updater/pkg/image"
@@ -25,40 +26,97 @@ type k8sClient struct {
2526 kubeClient * kube.KubernetesClient
2627}
2728
29+ // GetApplication retrieves an application by name across all namespaces.
2830func (client * k8sClient ) GetApplication (ctx context.Context , appName string ) (* v1alpha1.Application , error ) {
29- return client .kubeClient .ApplicationsClientset .ArgoprojV1alpha1 ().Applications (client .kubeClient .Namespace ).Get (ctx , appName , v1.GetOptions {})
31+ log .Debugf ("Getting application %s across all namespaces" , appName )
32+
33+ // List all applications across all namespaces (using empty labelSelector)
34+ appList , err := client .ListApplications ("" )
35+ if err != nil {
36+ return nil , fmt .Errorf ("error listing applications: %w" , err )
37+ }
38+
39+ // Filter applications by name using nameMatchesPattern
40+ app , err := findApplicationByName (appList , appName )
41+ if err != nil {
42+ log .Errorf ("error getting application: %v" , err )
43+ return nil , fmt .Errorf ("error getting application: %w" , err )
44+ }
45+
46+ // Retrieve the application in the specified namespace
47+ return client .kubeClient .ApplicationsClientset .ArgoprojV1alpha1 ().Applications (app .Namespace ).Get (ctx , app .Name , v1.GetOptions {})
3048}
3149
50+ // ListApplications lists all applications across all namespaces.
3251func (client * k8sClient ) ListApplications (labelSelector string ) ([]v1alpha1.Application , error ) {
33- list , err := client .kubeClient .ApplicationsClientset .ArgoprojV1alpha1 ().Applications (client . kubeClient . Namespace ).List (context .TODO (), v1.ListOptions {LabelSelector : labelSelector })
52+ list , err := client .kubeClient .ApplicationsClientset .ArgoprojV1alpha1 ().Applications (v1 . NamespaceAll ).List (context .TODO (), v1.ListOptions {LabelSelector : labelSelector })
3453 if err != nil {
35- return nil , err
54+ return nil , fmt . Errorf ( "error listing applications: %w" , err )
3655 }
56+ log .Debugf ("Applications listed: %d" , len (list .Items ))
3757 return list .Items , nil
3858}
3959
60+ // findApplicationByName filters the list of applications by name using nameMatchesPattern.
61+ func findApplicationByName (appList []v1alpha1.Application , appName string ) (* v1alpha1.Application , error ) {
62+ var matchedApps []* v1alpha1.Application
63+
64+ for _ , app := range appList {
65+ log .Debugf ("Found application: %s in namespace %s" , app .Name , app .Namespace )
66+ if nameMatchesPattern (app .Name , []string {appName }) {
67+ log .Debugf ("Application %s matches the pattern" , app .Name )
68+ matchedApps = append (matchedApps , & app )
69+ }
70+ }
71+
72+ if len (matchedApps ) == 0 {
73+ return nil , fmt .Errorf ("application %s not found" , appName )
74+ }
75+
76+ if len (matchedApps ) > 1 {
77+ return nil , fmt .Errorf ("multiple applications found matching %s" , appName )
78+ }
79+
80+ return matchedApps [0 ], nil
81+ }
82+
4083func (client * k8sClient ) UpdateSpec (ctx context.Context , spec * application.ApplicationUpdateSpecRequest ) (* v1alpha1.ApplicationSpec , error ) {
41- for {
42- app , err := client .kubeClient .ApplicationsClientset .ArgoprojV1alpha1 ().Applications (client .kubeClient .Namespace ).Get (ctx , spec .GetName (), v1.GetOptions {})
84+ const defaultMaxRetries = 7
85+ const baseDelay = 100 * time .Millisecond // Initial delay before retrying
86+
87+ // Allow overriding max retries for testing purposes
88+ maxRetries := defaultMaxRetries
89+ if overrideRetries , ok := os .LookupEnv ("OVERRIDE_MAX_RETRIES" ); ok {
90+ var retries int
91+ if _ , err := fmt .Sscanf (overrideRetries , "%d" , & retries ); err == nil {
92+ maxRetries = retries
93+ }
94+ }
95+
96+ for attempts := 0 ; attempts < maxRetries ; attempts ++ {
97+ app , err := client .GetApplication (ctx , spec .GetName ())
4398 if err != nil {
44- return nil , err
99+ log .Errorf ("could not get application: %s, error: %v" , spec .GetName (), err )
100+ return nil , fmt .Errorf ("error getting application: %w" , err )
45101 }
46102 app .Spec = * spec .Spec
47103
48- updatedApp , err := client .kubeClient .ApplicationsClientset .ArgoprojV1alpha1 ().Applications (client . kubeClient .Namespace ).Update (ctx , app , v1.UpdateOptions {})
104+ updatedApp , err := client .kubeClient .ApplicationsClientset .ArgoprojV1alpha1 ().Applications (app .Namespace ).Update (ctx , app , v1.UpdateOptions {})
49105 if err != nil {
50106 if errors .IsConflict (err ) {
107+ log .Warnf ("conflict occurred while updating application: %s, retrying... (%d/%d)" , spec .GetName (), attempts + 1 , maxRetries )
108+ time .Sleep (baseDelay * (1 << attempts )) // Exponential backoff, multiply baseDelay by 2^attempts
51109 continue
52110 }
53- return nil , err
111+ log .Errorf ("could not update application: %s, error: %v" , spec .GetName (), err )
112+ return nil , fmt .Errorf ("error updating application: %w" , err )
54113 }
55114 return & updatedApp .Spec , nil
56115 }
57-
116+ return nil , fmt . Errorf ( "max retries(%d) reached while updating application: %s" , maxRetries , spec . GetName ())
58117}
59118
60- // NewAPIClient creates a new API client for ArgoCD and connects to the ArgoCD
61- // API server.
119+ // NewK8SClient creates a new kubernetes client to interact with kubernetes api-server.
62120func NewK8SClient (kubeClient * kube.KubernetesClient ) (ArgoCD , error ) {
63121 return & k8sClient {kubeClient : kubeClient }, nil
64122}
0 commit comments