6
6
"os"
7
7
"path/filepath"
8
8
"strings"
9
+ "time"
9
10
10
11
"github.com/argoproj-labs/argocd-image-updater/pkg/common"
11
12
"github.com/argoproj-labs/argocd-image-updater/pkg/image"
@@ -25,40 +26,97 @@ type k8sClient struct {
25
26
kubeClient * kube.KubernetesClient
26
27
}
27
28
29
+ // GetApplication retrieves an application by name across all namespaces.
28
30
func (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 {})
30
48
}
31
49
50
+ // ListApplications lists all applications across all namespaces.
32
51
func (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 })
34
53
if err != nil {
35
- return nil , err
54
+ return nil , fmt . Errorf ( "error listing applications: %w" , err )
36
55
}
56
+ log .Debugf ("Applications listed: %d" , len (list .Items ))
37
57
return list .Items , nil
38
58
}
39
59
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
+
40
83
func (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 ())
43
98
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 )
45
101
}
46
102
app .Spec = * spec .Spec
47
103
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 {})
49
105
if err != nil {
50
106
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
51
109
continue
52
110
}
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 )
54
113
}
55
114
return & updatedApp .Spec , nil
56
115
}
57
-
116
+ return nil , fmt . Errorf ( "max retries(%d) reached while updating application: %s" , maxRetries , spec . GetName ())
58
117
}
59
118
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.
62
120
func NewK8SClient (kubeClient * kube.KubernetesClient ) (ArgoCD , error ) {
63
121
return & k8sClient {kubeClient : kubeClient }, nil
64
122
}
0 commit comments