summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaideep Raghunath Rao <jaideep.r97@gmail.com>2022-01-24 11:07:39 -0500
committerGitHub <noreply@github.com>2022-01-24 17:07:39 +0100
commit89daab684fcdac4c4d3a0683fe4764a9c1abd0b4 (patch)
treee0d92ae386319625aca886b0495085cfe9e70a79
parent527aecb345a6fdabec71ee22c1ea367c4c589850 (diff)
feat: support filtering apps by label in CLI (#368)
-rw-r--r--cmd/main.go1
-rw-r--r--cmd/run.go3
-rw-r--r--docs/install/running.md10
-rw-r--r--pkg/argocd/argocd.go46
-rw-r--r--pkg/argocd/argocd_test.go45
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
diff --git a/cmd/run.go b/cmd/run.go
index 8016c8f..581bc4b 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -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) {