diff --git a/cmd/main.go b/cmd/main.go index 8f5c0c6b..ae36650b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -31,6 +31,7 @@ type ImageUpdaterConfig struct { ApplicationsAPIKind string ClientOpts argocd.ClientOptions ArgocdNamespace string + AppNamespace string DryRun bool CheckInterval time.Duration ArgoClient argocd.ArgoCD diff --git a/cmd/run.go b/cmd/run.go index 947a29b7..d3e9ea96 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -24,6 +24,8 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/semaphore" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // newRunCommand implements "run" command @@ -235,6 +237,7 @@ func newRunCommand() *cobra.Command { runCmd.Flags().BoolVar(&disableKubernetes, "disable-kubernetes", false, "do not create and use a Kubernetes client") runCmd.Flags().IntVar(&cfg.MaxConcurrency, "max-concurrency", 10, "maximum number of update threads to run concurrently") runCmd.Flags().StringVar(&cfg.ArgocdNamespace, "argocd-namespace", "", "namespace where ArgoCD runs in (current namespace by default)") + runCmd.Flags().StringVar(&cfg.AppNamespace, "application-namespace", v1.NamespaceAll, "namespace where Argo Image Updater will manage applications (all namespaces by default)") runCmd.Flags().StringSliceVar(&cfg.AppNamePatterns, "match-application-name", nil, "patterns to match application name against") runCmd.Flags().StringVar(&cfg.AppLabel, "match-application-label", "", "label selector to match application labels against") runCmd.Flags().BoolVar(&warmUpCache, "warmup-cache", true, "whether to perform a cache warm-up on startup") @@ -256,7 +259,7 @@ func runImageUpdater(cfg *ImageUpdaterConfig, warmUp bool) (argocd.ImageUpdaterR var argoClient argocd.ArgoCD switch cfg.ApplicationsAPIKind { case applicationsAPIKindK8S: - argoClient, err = argocd.NewK8SClient(cfg.KubeClient) + argoClient, err = argocd.NewK8SClient(cfg.KubeClient, &argocd.K8SClientOptions{AppNamespace: cfg.AppNamespace}) case applicationsAPIKindArgoCD: argoClient, err = argocd.NewAPIClient(&cfg.ClientOpts) default: diff --git a/docs/install/reference.md b/docs/install/reference.md index 28301569..7c0603c0 100644 --- a/docs/install/reference.md +++ b/docs/install/reference.md @@ -26,6 +26,10 @@ Runs the Argo CD Image Updater, possibly in an endless loop. ### Flags +**--application-namespace *namespace*** + +Specifies the Kubernetes namespace in which Argo CD Image Updater will manage Argo CD Applications when using the Kubernetes-based Application API. By default, applications in all namespaces are considered. This flag can be used to limit scope to a single namespace for performance, security, or organizational reasons. + **--argocd-auth-token *token*** Use *token* for authenticating to the Argo CD API. This token must be a base64 diff --git a/pkg/argocd/argocd.go b/pkg/argocd/argocd.go index de540f68..b7d053d4 100644 --- a/pkg/argocd/argocd.go +++ b/pkg/argocd/argocd.go @@ -25,13 +25,21 @@ import ( // Kubernetes based client type k8sClient struct { - kubeClient *kube.ImageUpdaterKubernetesClient + kubeClient *kube.ImageUpdaterKubernetesClient + appNamespace *string } -// GetApplication retrieves an application by name across all namespaces. +// GetApplication retrieves an application by name, either in a specific namespace or all namespaces depending on client configuration. func (client *k8sClient) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) { - // List all applications across all namespaces (using empty labelSelector) - appList, err := client.ListApplications(v1.NamespaceAll) + // List all applications across configured namespace or all namespaces (using empty labelSelector) + if *client.appNamespace != v1.NamespaceAll { + return client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(*client.appNamespace).Get(ctx, appName, v1.GetOptions{}) + } + return client.getApplicationInAllNamespaces(appName) +} + +func (client *k8sClient) getApplicationInAllNamespaces(appName string) (*v1alpha1.Application, error) { + appList, err := client.ListApplications("") if err != nil { return nil, fmt.Errorf("error listing applications: %w", err) } @@ -61,7 +69,7 @@ func (client *k8sClient) GetApplication(ctx context.Context, appName string) (*v // ListApplications lists all applications across all namespaces. func (client *k8sClient) ListApplications(labelSelector string) ([]v1alpha1.Application, error) { - list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(v1.NamespaceAll).List(context.TODO(), v1.ListOptions{LabelSelector: labelSelector}) + list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(*client.appNamespace).List(context.TODO(), v1.ListOptions{LabelSelector: labelSelector}) if err != nil { return nil, fmt.Errorf("error listing applications: %w", err) } @@ -99,9 +107,23 @@ func (client *k8sClient) UpdateSpec(ctx context.Context, spec *application.Appli return nil, fmt.Errorf("max retries(%d) reached while updating application: %s", maxRetries, spec.GetName()) } +type K8SClientOptions struct { + AppNamespace string +} + // NewK8SClient creates a new kubernetes client to interact with kubernetes api-server. -func NewK8SClient(kubeClient *kube.ImageUpdaterKubernetesClient) (ArgoCD, error) { - return &k8sClient{kubeClient: kubeClient}, nil +func NewK8SClient(kubeClient *kube.ImageUpdaterKubernetesClient, opts *K8SClientOptions) (ArgoCD, error) { + // Provide default options if nil + if opts == nil { + opts = &K8SClientOptions{ + AppNamespace: v1.NamespaceAll, + } + } + + return &k8sClient{ + kubeClient: kubeClient, + appNamespace: &opts.AppNamespace, + }, nil } // Native diff --git a/pkg/argocd/argocd_test.go b/pkg/argocd/argocd_test.go index 1c9b9445..b19a0278 100644 --- a/pkg/argocd/argocd_test.go +++ b/pkg/argocd/argocd_test.go @@ -1023,7 +1023,7 @@ func TestKubernetesClient(t *testing.T) { Namespace: "testns1", }, ApplicationsClientset: fake.NewSimpleClientset(app1, app2), - }) + }, nil) require.NoError(t, err) @@ -1064,7 +1064,7 @@ func TestKubernetesClient(t *testing.T) { // Create the Kubernetes client client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{ ApplicationsClientset: clientset, - }) + }, nil) require.NoError(t, err) // Test ListApplications error handling @@ -1094,7 +1094,7 @@ func TestKubernetesClient(t *testing.T) { // Create the Kubernetes client client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{ ApplicationsClientset: clientset, - }) + }, nil) require.NoError(t, err) // Test GetApplication with multiple matching applications @@ -1124,7 +1124,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) { client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{ ApplicationsClientset: clientset, - }) + }, nil) require.NoError(t, err) appName := "test-app" @@ -1145,7 +1145,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) { client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{ ApplicationsClientset: clientset, - }) + }, nil) require.NoError(t, err) appName := "test-app" @@ -1169,7 +1169,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) { client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{ ApplicationsClientset: clientset, - }) + }, nil) require.NoError(t, err) clientset.PrependReactor("update", "applications", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { @@ -1198,7 +1198,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) { client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{ ApplicationsClientset: clientset, - }) + }, nil) require.NoError(t, err) clientset.PrependReactor("update", "applications", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {