Skip to content

Commit

Permalink
k8sClient get resources across all namespaces (#601) (#854)
Browse files Browse the repository at this point in the history
Signed-off-by: Jort Koopmans <[email protected]>
  • Loading branch information
jortkoopmans authored Oct 14, 2024
1 parent 2e631b0 commit ad9648f
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 57 deletions.
9 changes: 9 additions & 0 deletions manifests/base/rbac/argocd-image-updater-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ rules:
- events
verbs:
- create
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- update
- patch
9 changes: 0 additions & 9 deletions manifests/base/rbac/argocd-image-updater-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,3 @@ rules:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- update
- patch
18 changes: 9 additions & 9 deletions manifests/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ rules:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- update
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
Expand All @@ -50,6 +41,15 @@ rules:
- events
verbs:
- create
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- update
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
Expand Down
80 changes: 69 additions & 11 deletions pkg/argocd/argocd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/argoproj-labs/argocd-image-updater/pkg/common"
"github.com/argoproj-labs/argocd-image-updater/pkg/image"
Expand All @@ -25,40 +26,97 @@ type k8sClient struct {
kubeClient *kube.KubernetesClient
}

// GetApplication retrieves an application by name across all namespaces.
func (client *k8sClient) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) {
return client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Get(ctx, appName, v1.GetOptions{})
log.Debugf("Getting application %s across all namespaces", appName)

// List all applications across all namespaces (using empty labelSelector)
appList, err := client.ListApplications("")
if err != nil {
return nil, fmt.Errorf("error listing applications: %w", err)
}

// Filter applications by name using nameMatchesPattern
app, err := findApplicationByName(appList, appName)
if err != nil {
log.Errorf("error getting application: %v", err)
return nil, fmt.Errorf("error getting application: %w", err)
}

// Retrieve the application in the specified namespace
return client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(ctx, app.Name, v1.GetOptions{})
}

// ListApplications lists all applications across all namespaces.
func (client *k8sClient) ListApplications(labelSelector string) ([]v1alpha1.Application, error) {
list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).List(context.TODO(), v1.ListOptions{LabelSelector: labelSelector})
list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(v1.NamespaceAll).List(context.TODO(), v1.ListOptions{LabelSelector: labelSelector})
if err != nil {
return nil, err
return nil, fmt.Errorf("error listing applications: %w", err)
}
log.Debugf("Applications listed: %d", len(list.Items))
return list.Items, nil
}

// findApplicationByName filters the list of applications by name using nameMatchesPattern.
func findApplicationByName(appList []v1alpha1.Application, appName string) (*v1alpha1.Application, error) {
var matchedApps []*v1alpha1.Application

for _, app := range appList {
log.Debugf("Found application: %s in namespace %s", app.Name, app.Namespace)
if nameMatchesPattern(app.Name, []string{appName}) {
log.Debugf("Application %s matches the pattern", app.Name)
matchedApps = append(matchedApps, &app)
}
}

if len(matchedApps) == 0 {
return nil, fmt.Errorf("application %s not found", appName)
}

if len(matchedApps) > 1 {
return nil, fmt.Errorf("multiple applications found matching %s", appName)
}

return matchedApps[0], nil
}

func (client *k8sClient) UpdateSpec(ctx context.Context, spec *application.ApplicationUpdateSpecRequest) (*v1alpha1.ApplicationSpec, error) {
for {
app, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Get(ctx, spec.GetName(), v1.GetOptions{})
const defaultMaxRetries = 7
const baseDelay = 100 * time.Millisecond // Initial delay before retrying

// Allow overriding max retries for testing purposes
maxRetries := defaultMaxRetries
if overrideRetries, ok := os.LookupEnv("OVERRIDE_MAX_RETRIES"); ok {
var retries int
if _, err := fmt.Sscanf(overrideRetries, "%d", &retries); err == nil {
maxRetries = retries
}
}

for attempts := 0; attempts < maxRetries; attempts++ {
app, err := client.GetApplication(ctx, spec.GetName())
if err != nil {
return nil, err
log.Errorf("could not get application: %s, error: %v", spec.GetName(), err)
return nil, fmt.Errorf("error getting application: %w", err)
}
app.Spec = *spec.Spec

updatedApp, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Update(ctx, app, v1.UpdateOptions{})
updatedApp, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(app.Namespace).Update(ctx, app, v1.UpdateOptions{})
if err != nil {
if errors.IsConflict(err) {
log.Warnf("conflict occurred while updating application: %s, retrying... (%d/%d)", spec.GetName(), attempts+1, maxRetries)
time.Sleep(baseDelay * (1 << attempts)) // Exponential backoff, multiply baseDelay by 2^attempts
continue
}
return nil, err
log.Errorf("could not update application: %s, error: %v", spec.GetName(), err)
return nil, fmt.Errorf("error updating application: %w", err)
}
return &updatedApp.Spec, nil
}

return nil, fmt.Errorf("max retries(%d) reached while updating application: %s", maxRetries, spec.GetName())
}

// NewAPIClient creates a new API client for ArgoCD and connects to the ArgoCD
// API server.
// NewK8SClient creates a new kubernetes client to interact with kubernetes api-server.
func NewK8SClient(kubeClient *kube.KubernetesClient) (ArgoCD, error) {
return &k8sClient{kubeClient: kubeClient}, nil
}
Expand Down
Loading

0 comments on commit ad9648f

Please sign in to comment.