Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

k8sClient get resources across all namespaces (#601) #854

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to retrieve the application again, as it should be already retrieved as part of appList, err := client.ListApplications("") ?

}

// 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
}
}
Comment on lines +89 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be helpful to move this logic to env.go and follow the same pattern as this to read numeric values.


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