diff options
| author | Jaideep Raghunath Rao <jaideep.r97@gmail.com> | 2022-01-24 11:07:39 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-01-24 17:07:39 +0100 |
| commit | 89daab684fcdac4c4d3a0683fe4764a9c1abd0b4 (patch) | |
| tree | e0d92ae386319625aca886b0495085cfe9e70a79 | |
| parent | 527aecb345a6fdabec71ee22c1ea367c4c589850 (diff) | |
feat: support filtering apps by label in CLI (#368)
| -rw-r--r-- | cmd/main.go | 1 | ||||
| -rw-r--r-- | cmd/run.go | 3 | ||||
| -rw-r--r-- | docs/install/running.md | 10 | ||||
| -rw-r--r-- | pkg/argocd/argocd.go | 46 | ||||
| -rw-r--r-- | pkg/argocd/argocd_test.go | 45 |
5 files changed, 101 insertions, 4 deletions
diff --git a/cmd/main.go b/cmd/main.go index 74009cb..3a4019d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -40,6 +40,7 @@ type ImageUpdaterConfig struct { MetricsPort int RegistriesConf string AppNamePatterns []string + AppLabel string GitCommitUser string GitCommitMail string GitCommitMessage *template.Template @@ -218,6 +218,7 @@ func newRunCommand() *cobra.Command { 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().StringSliceVar(&cfg.AppNamePatterns, "match-application-name", nil, "patterns to match application name against") + runCmd.Flags().StringVar(&cfg.AppLabel, "match-application-label", "", "label to match application labels against") runCmd.Flags().BoolVar(&warmUpCache, "warmup-cache", true, "whether to perform a cache warm-up on startup") runCmd.Flags().StringVar(&cfg.GitCommitUser, "git-commit-user", env.GetStringVal("GIT_COMMIT_USER", "argocd-image-updater"), "Username to use for Git commits") runCmd.Flags().StringVar(&cfg.GitCommitMail, "git-commit-email", env.GetStringVal("GIT_COMMIT_EMAIL", "noreply@argoproj.io"), "E-Mail address to use for Git commits") @@ -259,7 +260,7 @@ func runImageUpdater(cfg *ImageUpdaterConfig, warmUp bool) (argocd.ImageUpdaterR // Get the list of applications that are allowed for updates, that is, those // applications which have correct annotation. - appList, err := argocd.FilterApplicationsForUpdate(apps, cfg.AppNamePatterns) + appList, err := argocd.FilterApplicationsForUpdate(apps, cfg.AppNamePatterns, cfg.AppLabel) if err != nil { return result, err } diff --git a/docs/install/running.md b/docs/install/running.md index 6f05e5c..e5556c1 100644 --- a/docs/install/running.md +++ b/docs/install/running.md @@ -116,6 +116,16 @@ style wildcards, i.e. `*-staging` would match any application name with a suffix of `-staging`. Can be specified multiple times to define more than one pattern, from which at least one has to match. +**--match-application-label *label* ** + +Only process applications that have a valid annotation and match the given +*label*. The *label* is a string that matches the standard kubernetes label +syntax of `key=value`. For e.g, `custom.label/name=xyz` would be a valid label +that can be supplied through this parameter. Any applications carrying this +exact label will be considered as candidates for image updates. This parameter +currently does not support patten matching on label values (e.g `customer.label/name=*-staging`) +and only accepts a single label to match applications against. + **--max-concurrency *number* ** Process a maximum of *number* applications concurrently. To disable concurrent diff --git a/pkg/argocd/argocd.go b/pkg/argocd/argocd.go index 10ed390..66e3f0c 100644 --- a/pkg/argocd/argocd.go +++ b/pkg/argocd/argocd.go @@ -145,10 +145,34 @@ func nameMatchesPattern(name string, patterns []string) bool { return false } +// Match app labels against provided filter label +func matchAppLabels(appName string, appLabels map[string]string, filterLabel string) bool { + + if filterLabel == "" { + return true + } + + filterLabelMap, err := parseLabel(filterLabel) + if err != nil { + log.Errorf("Unable match app labels against %s: %s", filterLabel, err) + return false + } + + for filterLabelKey, filterLabelValue := range filterLabelMap { + log.Tracef("Matching application name %s against label %s", appName, filterLabel) + if appLabelValue, ok := appLabels[filterLabelKey]; ok { + if appLabelValue == filterLabelValue { + return true + } + } + } + return false +} + // Retrieve a list of applications from ArgoCD that qualify for image updates // Application needs either to be of type Kustomize or Helm and must have the // correct annotation in order to be considered. -func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string) (map[string]ApplicationImages, error) { +func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string, appLabel string) (map[string]ApplicationImages, error) { var appsForUpdate = make(map[string]ApplicationImages) for _, app := range apps { @@ -172,6 +196,12 @@ func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string) 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()) + continue + } + logCtx.Tracef("processing app '%s' of type '%v'", app.GetName(), app.Status.SourceType) imageList := parseImageList(annotations) appImages := ApplicationImages{} @@ -198,6 +228,20 @@ func parseImageList(annotations map[string]string) *image.ContainerImageList { return &results } +func parseLabel(inputLabel string) (map[string]string, error) { + var selectedLabels map[string]string + const labelFieldDelimiter = "=" + if inputLabel != "" { + selectedLabels = map[string]string{} + fields := strings.Split(inputLabel, labelFieldDelimiter) + if len(fields) != 2 { + return nil, fmt.Errorf("labels should have key%svalue, but instead got: %s", labelFieldDelimiter, inputLabel) + } + selectedLabels[fields[0]] = fields[1] + } + return selectedLabels, nil +} + // GetApplication gets the application named appName from Argo CD API func (client *argoCD) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) { conn, appClient, err := client.Client.NewApplicationClient() diff --git a/pkg/argocd/argocd_test.go b/pkg/argocd/argocd_test.go index 4fc61bf..c6b4be4 100644 --- a/pkg/argocd/argocd_test.go +++ b/pkg/argocd/argocd_test.go @@ -203,7 +203,7 @@ func Test_FilterApplicationsForUpdate(t *testing.T) { }, }, } - filtered, err := FilterApplicationsForUpdate(applicationList, []string{}) + filtered, err := FilterApplicationsForUpdate(applicationList, []string{}, "") require.NoError(t, err) require.Len(t, filtered, 1) require.Contains(t, filtered, "app1") @@ -255,7 +255,7 @@ func Test_FilterApplicationsForUpdate(t *testing.T) { }, }, } - filtered, err := FilterApplicationsForUpdate(applicationList, []string{"app*"}) + filtered, err := FilterApplicationsForUpdate(applicationList, []string{"app*"}, "") require.NoError(t, err) require.Len(t, filtered, 2) require.Contains(t, filtered, "app1") @@ -263,6 +263,47 @@ func Test_FilterApplicationsForUpdate(t *testing.T) { assert.Len(t, filtered["app1"].Images, 2) }) + t.Run("Filter for applications with label", func(t *testing.T) { + applicationList := []v1alpha1.Application{ + // Annotated and carries required label + { + ObjectMeta: v1.ObjectMeta{ + Name: "app1", + Namespace: "argocd", + Annotations: map[string]string{ + common.ImageUpdaterAnnotation: "nginx, quay.io/dexidp/dex:v1.23.0", + }, + Labels: map[string]string{ + "custom.label/name": "xyz", + }, + }, + Spec: v1alpha1.ApplicationSpec{}, + Status: v1alpha1.ApplicationStatus{ + SourceType: v1alpha1.ApplicationSourceTypeKustomize, + }, + }, + // Annotated but does not carry required label + { + ObjectMeta: v1.ObjectMeta{ + Name: "app2", + Namespace: "argocd", + Annotations: map[string]string{ + common.ImageUpdaterAnnotation: "nginx, quay.io/dexidp/dex:v1.23.0", + }, + }, + Spec: v1alpha1.ApplicationSpec{}, + Status: v1alpha1.ApplicationStatus{ + SourceType: v1alpha1.ApplicationSourceTypeHelm, + }, + }, + } + 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) + }) + } func Test_GetHelmParamAnnotations(t *testing.T) { |
