diff --git a/cmd/main.go b/cmd/main.go index 3a4019dd..5fbc20b1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -45,6 +45,7 @@ type ImageUpdaterConfig struct { GitCommitMail string GitCommitMessage *template.Template DisableKubeEvents bool + Namespaced bool } // newRootCommand implements the root command of argocd-image-updater diff --git a/cmd/run.go b/cmd/run.go index 305863db..a2e2d286 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -19,6 +19,8 @@ import ( "github.com/argoproj-labs/argocd-image-updater/pkg/registry" "github.com/argoproj-labs/argocd-image-updater/pkg/version" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/spf13/cobra" "golang.org/x/sync/semaphore" @@ -108,7 +110,11 @@ func newRunCommand() *cobra.Command { var err error if !disableKubernetes { ctx := context.Background() - cfg.KubeClient, err = getKubeConfig(ctx, cfg.ArgocdNamespace, kubeConfig) + ns := cfg.ArgocdNamespace + if !cfg.Namespaced { + ns = v1.NamespaceAll + } + cfg.KubeClient, err = getKubeConfig(ctx, ns, cfg.Namespaced, kubeConfig) if err != nil { log.Fatalf("could not create K8s client: %v", err) } @@ -125,13 +131,15 @@ func newRunCommand() *cobra.Command { cfg.ClientOpts.AuthToken = token } - log.Infof("ArgoCD configuration: [apiKind=%s, server=%s, auth_token=%v, insecure=%v, grpc_web=%v, plaintext=%v]", + log.Infof("ArgoCD configuration: [apiKind=%s, server=%s, auth_token=%v, insecure=%v, grpc_web=%v, plaintext=%v, namespaced=%v, namespace=%s]", cfg.ApplicationsAPIKind, cfg.ClientOpts.ServerAddr, cfg.ClientOpts.AuthToken != "", cfg.ClientOpts.Insecure, cfg.ClientOpts.GRPCWeb, cfg.ClientOpts.Plaintext, + cfg.Namespaced, + cfg.ArgocdNamespace, ) // Health server will start in a go routine and run asynchronously @@ -223,6 +231,7 @@ func newRunCommand() *cobra.Command { runCmd.Flags().StringVar(&cfg.GitCommitMail, "git-commit-email", env.GetStringVal("GIT_COMMIT_EMAIL", "noreply@argoproj.io"), "E-Mail address to use for Git commits") runCmd.Flags().StringVar(&commitMessagePath, "git-commit-message-path", defaultCommitTemplatePath, "Path to a template to use for Git commit messages") runCmd.Flags().BoolVar(&cfg.DisableKubeEvents, "disable-kube-events", env.GetBoolVal("IMAGE_UPDATER_KUBE_EVENTS", false), "Disable kubernetes events") + runCmd.Flags().BoolVar(&cfg.Namespaced, "namespaced", env.GetBoolVal("IMAGE_UPDATER_NAMESPACED", true), "Scope to only the provided namespace") return runCmd } diff --git a/cmd/test.go b/cmd/test.go index afc9cfbe..202fbde4 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -68,7 +68,7 @@ argocd-image-updater test nginx --allow-tags '^1.19.\d+(\-.*)*$' --update-strate var err error if !disableKubernetes { ctx := context.Background() - kubeClient, err = getKubeConfig(ctx, "", kubeConfig) + kubeClient, err = getKubeConfig(ctx, "", true, kubeConfig) if err != nil { log.Fatalf("could not create K8s client: %v", err) } diff --git a/cmd/util.go b/cmd/util.go index 7b6a944d..bddd6608 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -26,7 +26,7 @@ func getPrintableHealthPort(port int) string { } } -func getKubeConfig(ctx context.Context, namespace string, kubeConfig string) (*kube.KubernetesClient, error) { +func getKubeConfig(ctx context.Context, namespace string, namespaced bool, kubeConfig string) (*kube.KubernetesClient, error) { var fullKubeConfigPath string var kubeClient *kube.KubernetesClient var err error @@ -39,12 +39,12 @@ func getKubeConfig(ctx context.Context, namespace string, kubeConfig string) (*k } if fullKubeConfigPath != "" { - log.Debugf("Creating Kubernetes client from %s", fullKubeConfigPath) + log.Debugf("Creating Kubernetes client from %s for namespace '%s'", fullKubeConfigPath, namespace) } else { - log.Debugf("Creating in-cluster Kubernetes client") + log.Debugf("Creating in-cluster Kubernetes client for namespace '%s'", namespace) } - kubeClient, err = kube.NewKubernetesClientFromConfig(ctx, namespace, fullKubeConfigPath) + kubeClient, err = kube.NewKubernetesClientFromConfig(ctx, namespace, namespaced, fullKubeConfigPath) if err != nil { return nil, err } diff --git a/pkg/argocd/argocd.go b/pkg/argocd/argocd.go index 4584dbe0..72d70542 100644 --- a/pkg/argocd/argocd.go +++ b/pkg/argocd/argocd.go @@ -176,41 +176,40 @@ func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string, var appsForUpdate = make(map[string]ApplicationImages) for _, app := range apps { - logCtx := log.WithContext().AddField("application", app.GetName()) - + logCtx := log.WithContext().AddField("application", app.GetName()).AddField("namespace", app.GetNamespace()) sourceType := getApplicationSourceType(&app) // Check whether application has our annotation set annotations := app.GetAnnotations() if _, ok := annotations[common.ImageUpdaterAnnotation]; !ok { - logCtx.Tracef("skipping app '%s' of type '%s' because required annotation is missing", app.GetName(), sourceType) + logCtx.Tracef("skipping app '%s' of type '%s' because required annotation is missing", app.QualifiedName(), sourceType) continue } // Check for valid application type if !IsValidApplicationType(&app) { - logCtx.Warnf("skipping app '%s' of type '%s' because it's not of supported source type", app.GetName(), sourceType) + logCtx.Warnf("skipping app '%s' of type '%s' because it's not of supported source type", app.QualifiedName(), sourceType) continue } // Check if application name matches requested patterns if !nameMatchesPattern(app.GetName(), patterns) { - logCtx.Debugf("Skipping app '%s' because it does not match requested patterns", app.GetName()) + logCtx.Debugf("Skipping app '%s' because it does not match requested patterns", app.QualifiedName()) continue } // Check if application carries requested label if !matchAppLabels(app.GetName(), app.GetLabels(), appLabel) { - logCtx.Debugf("Skipping app '%s' because it does not carry requested label", app.GetName()) + logCtx.Debugf("Skipping app '%s' because it does not carry requested label", app.QualifiedName()) continue } - logCtx.Tracef("processing app '%s' of type '%v'", app.GetName(), sourceType) + logCtx.Tracef("processing app '%s' of type '%v'", app.QualifiedName(), sourceType) imageList := parseImageList(annotations) appImages := ApplicationImages{} appImages.Application = app appImages.Images = *imageList - appsForUpdate[app.GetName()] = appImages + appsForUpdate[app.QualifiedName()] = appImages } return appsForUpdate, nil @@ -388,6 +387,7 @@ func SetHelmImage(app *v1alpha1.Application, newImage *image.ContainerImage) err } appName := app.GetName() + appNamespace := app.GetNamespace() var hpImageName, hpImageTag, hpImageSpec string @@ -407,6 +407,7 @@ func SetHelmImage(app *v1alpha1.Application, newImage *image.ContainerImage) err log.WithContext(). AddField("application", appName). AddField("image", newImage.GetFullNameWithoutTag()). + AddField("namespace", appNamespace). Debugf("target parameters: image-spec=%s image-name=%s, image-tag=%s", hpImageSpec, hpImageName, hpImageTag) mergeParams := make([]v1alpha1.HelmParameter, 0) diff --git a/pkg/argocd/argocd_test.go b/pkg/argocd/argocd_test.go index 95553570..c6ff4525 100644 --- a/pkg/argocd/argocd_test.go +++ b/pkg/argocd/argocd_test.go @@ -435,8 +435,8 @@ func Test_FilterApplicationsForUpdate(t *testing.T) { filtered, err := FilterApplicationsForUpdate(applicationList, []string{}, "") require.NoError(t, err) require.Len(t, filtered, 1) - require.Contains(t, filtered, "app1") - assert.Len(t, filtered["app1"].Images, 2) + require.Contains(t, filtered, "argocd/app1") + assert.Len(t, filtered["argocd/app1"].Images, 2) }) t.Run("Filter for applications with patterns", func(t *testing.T) { @@ -487,9 +487,9 @@ func Test_FilterApplicationsForUpdate(t *testing.T) { filtered, err := FilterApplicationsForUpdate(applicationList, []string{"app*"}, "") require.NoError(t, err) require.Len(t, filtered, 2) - require.Contains(t, filtered, "app1") - require.Contains(t, filtered, "app2") - assert.Len(t, filtered["app1"].Images, 2) + require.Contains(t, filtered, "argocd/app1") + require.Contains(t, filtered, "argocd/app2") + assert.Len(t, filtered["argocd/app1"].Images, 2) }) t.Run("Filter for applications with label", func(t *testing.T) { @@ -529,8 +529,8 @@ func Test_FilterApplicationsForUpdate(t *testing.T) { filtered, err := FilterApplicationsForUpdate(applicationList, []string{}, "custom.label/name=xyz") require.NoError(t, err) require.Len(t, filtered, 1) - require.Contains(t, filtered, "app1") - assert.Len(t, filtered["app1"].Images, 2) + require.Contains(t, filtered, "argocd/app1") + assert.Len(t, filtered["argocd/app1"].Images, 2) }) } diff --git a/pkg/argocd/update.go b/pkg/argocd/update.go index 855bbdd0..baea2391 100644 --- a/pkg/argocd/update.go +++ b/pkg/argocd/update.go @@ -141,6 +141,7 @@ func UpdateApplication(updateConf *UpdateConfiguration, state *SyncIterationStat result := ImageUpdaterResult{} app := updateConf.UpdateApp.Application.GetName() + namespace := updateConf.UpdateApp.Application.GetNamespace() changeList := make([]ChangeEntry, 0) // Get all images that are deployed with the current application @@ -157,7 +158,10 @@ func UpdateApplication(updateConf *UpdateConfiguration, state *SyncIterationStat for _, applicationImage := range updateConf.UpdateApp.Images { updateableImage := applicationImages.ContainsImage(applicationImage, false) if updateableImage == nil { - log.WithContext().AddField("application", app).Debugf("Image '%s' seems not to be live in this application, skipping", applicationImage.ImageName) + log.WithContext(). + AddField("application", app). + AddField("namespace", namespace). + Debugf("Image '%s' seems not to be live in this application, skipping", applicationImage.ImageName) result.NumSkipped += 1 continue } @@ -173,6 +177,7 @@ func UpdateApplication(updateConf *UpdateConfiguration, state *SyncIterationStat imgCtx := log.WithContext(). AddField("application", app). + AddField("namespace", namespace). AddField("registry", updateableImage.RegistryURL). AddField("image_name", updateableImage.ImageName). AddField("image_tag", updateableImage.ImageTag). diff --git a/pkg/kube/kubernetes.go b/pkg/kube/kubernetes.go index 3ec66cfd..723aa4b6 100644 --- a/pkg/kube/kubernetes.go +++ b/pkg/kube/kubernetes.go @@ -8,6 +8,7 @@ import ( "os" "time" + "github.com/argoproj-labs/argocd-image-updater/pkg/log" "github.com/argoproj-labs/argocd-image-updater/pkg/metrics" appv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" @@ -38,7 +39,7 @@ func NewKubernetesClient(ctx context.Context, client kubernetes.Interface, appli // NewKubernetesClient creates a new Kubernetes client object from given // configuration file. If configuration file is the empty string, in-cluster // client will be created. -func NewKubernetesClientFromConfig(ctx context.Context, namespace string, kubeconfig string) (*KubernetesClient, error) { +func NewKubernetesClientFromConfig(ctx context.Context, namespace string, namespaced bool, kubeconfig string) (*KubernetesClient, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig loadingRules.ExplicitPath = kubeconfig @@ -50,7 +51,7 @@ func NewKubernetesClientFromConfig(ctx context.Context, namespace string, kubeco return nil, err } - if namespace == "" { + if namespace == "" && namespaced { namespace, _, err = clientConfig.Namespace() if err != nil { return nil, err @@ -67,6 +68,7 @@ func NewKubernetesClientFromConfig(ctx context.Context, namespace string, kubeco return nil, err } + log.Debugf("Creating kubernetes client for ns '%s'", namespace) return NewKubernetesClient(ctx, clientset, applicationsClientset, namespace), nil } diff --git a/pkg/kube/kubernetes_test.go b/pkg/kube/kubernetes_test.go index 56ca6204..87f9181c 100644 --- a/pkg/kube/kubernetes_test.go +++ b/pkg/kube/kubernetes_test.go @@ -16,14 +16,14 @@ import ( func Test_NewKubernetesClient(t *testing.T) { t.Run("Get new K8s client for remote cluster instance", func(t *testing.T) { - client, err := NewKubernetesClientFromConfig(context.TODO(), "", "../../test/testdata/kubernetes/config") + client, err := NewKubernetesClientFromConfig(context.TODO(), "", true, "../../test/testdata/kubernetes/config") require.NoError(t, err) assert.NotNil(t, client) assert.Equal(t, "default", client.Namespace) }) t.Run("Get new K8s client for remote cluster instance specified namespace", func(t *testing.T) { - client, err := NewKubernetesClientFromConfig(context.TODO(), "argocd", "../../test/testdata/kubernetes/config") + client, err := NewKubernetesClientFromConfig(context.TODO(), "argocd", true, "../../test/testdata/kubernetes/config") require.NoError(t, err) assert.NotNil(t, client) assert.Equal(t, "argocd", client.Namespace)