Skip to content

Commit 70f1dee

Browse files
jortkoopmansTchoupinax
authored andcommitted
k8sClient get resources across all namespaces (argoproj-labs#601) (argoproj-labs#854)
Signed-off-by: Jort Koopmans <[email protected]> Signed-off-by: Tchoupinax <[email protected]>
1 parent e099c5c commit 70f1dee

File tree

5 files changed

+250
-57
lines changed

5 files changed

+250
-57
lines changed

manifests/base/rbac/argocd-image-updater-clusterrole.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@ rules:
1313
- events
1414
verbs:
1515
- create
16+
- apiGroups:
17+
- argoproj.io
18+
resources:
19+
- applications
20+
verbs:
21+
- get
22+
- list
23+
- update
24+
- patch

manifests/base/rbac/argocd-image-updater-role.yaml

-9
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,3 @@ rules:
1616
- get
1717
- list
1818
- watch
19-
- apiGroups:
20-
- argoproj.io
21-
resources:
22-
- applications
23-
verbs:
24-
- get
25-
- list
26-
- update
27-
- patch

manifests/install.yaml

+9-9
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,6 @@ rules:
2525
- get
2626
- list
2727
- watch
28-
- apiGroups:
29-
- argoproj.io
30-
resources:
31-
- applications
32-
verbs:
33-
- get
34-
- list
35-
- update
36-
- patch
3728
---
3829
apiVersion: rbac.authorization.k8s.io/v1
3930
kind: ClusterRole
@@ -50,6 +41,15 @@ rules:
5041
- events
5142
verbs:
5243
- create
44+
- apiGroups:
45+
- argoproj.io
46+
resources:
47+
- applications
48+
verbs:
49+
- get
50+
- list
51+
- update
52+
- patch
5353
---
5454
apiVersion: rbac.authorization.k8s.io/v1
5555
kind: RoleBinding

pkg/argocd/argocd.go

+69-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
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.
2830
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{})
3048
}
3149

50+
// ListApplications lists all applications across all namespaces.
3251
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})
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+
4083
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())
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.
62120
func NewK8SClient(kubeClient *kube.KubernetesClient) (ArgoCD, error) {
63121
return &k8sClient{kubeClient: kubeClient}, nil
64122
}

0 commit comments

Comments
 (0)