Skip to content

Commit 0ad5b94

Browse files
authored
feat!: Filter labels on the server instead of client to allow more label filtering options (#832)
Signed-off-by: Joshua Novick <[email protected]>
1 parent 65698c5 commit 0ad5b94

File tree

5 files changed

+22
-105
lines changed

5 files changed

+22
-105
lines changed

cmd/run.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ func newRunCommand() *cobra.Command {
236236
runCmd.Flags().IntVar(&cfg.MaxConcurrency, "max-concurrency", 10, "maximum number of update threads to run concurrently")
237237
runCmd.Flags().StringVar(&cfg.ArgocdNamespace, "argocd-namespace", "", "namespace where ArgoCD runs in (current namespace by default)")
238238
runCmd.Flags().StringSliceVar(&cfg.AppNamePatterns, "match-application-name", nil, "patterns to match application name against")
239-
runCmd.Flags().StringVar(&cfg.AppLabel, "match-application-label", "", "label to match application labels against")
239+
runCmd.Flags().StringVar(&cfg.AppLabel, "match-application-label", "", "label selector to match application labels against")
240240
runCmd.Flags().BoolVar(&warmUpCache, "warmup-cache", true, "whether to perform a cache warm-up on startup")
241241
runCmd.Flags().StringVar(&cfg.GitCommitUser, "git-commit-user", env.GetStringVal("GIT_COMMIT_USER", "argocd-image-updater"), "Username to use for Git commits")
242242
runCmd.Flags().StringVar(&cfg.GitCommitMail, "git-commit-email", env.GetStringVal("GIT_COMMIT_EMAIL", "[email protected]"), "E-Mail address to use for Git commits")
@@ -267,7 +267,7 @@ func runImageUpdater(cfg *ImageUpdaterConfig, warmUp bool) (argocd.ImageUpdaterR
267267
}
268268
cfg.ArgoClient = argoClient
269269

270-
apps, err := cfg.ArgoClient.ListApplications()
270+
apps, err := cfg.ArgoClient.ListApplications(cfg.AppLabel)
271271
if err != nil {
272272
log.WithContext().
273273
AddField("argocd_server", cfg.ClientOpts.ServerAddr).
@@ -281,7 +281,7 @@ func runImageUpdater(cfg *ImageUpdaterConfig, warmUp bool) (argocd.ImageUpdaterR
281281

282282
// Get the list of applications that are allowed for updates, that is, those
283283
// applications which have correct annotation.
284-
appList, err := argocd.FilterApplicationsForUpdate(apps, cfg.AppNamePatterns, cfg.AppLabel)
284+
appList, err := argocd.FilterApplicationsForUpdate(apps, cfg.AppNamePatterns)
285285
if err != nil {
286286
return result, err
287287
}

docs/install/reference.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,16 @@ style wildcards, i.e. `*-staging` would match any application name with a
116116
suffix of `-staging`. Can be specified multiple times to define more than
117117
one pattern, from which at least one has to match.
118118

119-
**--match-application-label *label* **
119+
**--match-application-label *selector* **
120120

121121
Only process applications that have a valid annotation and match the given
122-
*label*. The *label* is a string that matches the standard kubernetes label
123-
syntax of `key=value`. For e.g, `custom.label/name=xyz` would be a valid label
124-
that can be supplied through this parameter. Any applications carrying this
122+
*label* selector. The *selector* is a string that matches the standard kubernetes label
123+
[label selector syntax][]. For e.g, `custom.label/name=xyz` would be a valid label
124+
that can be supplied through this parameter. Any applications carrying this
125125
exact label will be considered as candidates for image updates. This parameter
126-
currently does not support pattern matching on label values (e.g `customer.label/name=*-staging`)
127-
and only accepts a single label to match applications against.
126+
currently does not support pattern matching on label values (e.g `customer.label/name=*-staging`).
127+
You can specify equality, inequality, or set based requirements or a combination.
128+
For e.g., `app,app!=foo,custom.label/name=xyz,customer in (a,b,c)`
128129

129130
**--max-concurrency *number* **
130131

@@ -142,3 +143,5 @@ Load the registry configuration from file at *path*. Defaults to the path
142143
`/app/config/registries.conf`. If no configuration should be loaded, and the
143144
default configuration should be used instead, specify the empty string, i.e.
144145
`--registries-conf-path=""`.
146+
147+
[label selector syntax]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors

pkg/argocd/argocd.go

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ func (client *k8sClient) GetApplication(ctx context.Context, appName string) (*v
2929
return client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Get(ctx, appName, v1.GetOptions{})
3030
}
3131

32-
func (client *k8sClient) ListApplications() ([]v1alpha1.Application, error) {
33-
list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).List(context.TODO(), v1.ListOptions{})
32+
func (client *k8sClient) ListApplications(labelSelector string) ([]v1alpha1.Application, error) {
33+
list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).List(context.TODO(), v1.ListOptions{LabelSelector: labelSelector})
3434
if err != nil {
3535
return nil, err
3636
}
@@ -71,7 +71,7 @@ type argoCD struct {
7171
// ArgoCD is the interface for accessing Argo CD functions we need
7272
type ArgoCD interface {
7373
GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error)
74-
ListApplications() ([]v1alpha1.Application, error)
74+
ListApplications(labelSelector string) ([]v1alpha1.Application, error)
7575
UpdateSpec(ctx context.Context, spec *application.ApplicationUpdateSpecRequest) (*v1alpha1.ApplicationSpec, error)
7676
}
7777

@@ -145,34 +145,10 @@ func nameMatchesPattern(name string, patterns []string) bool {
145145
return false
146146
}
147147

148-
// Match app labels against provided filter label
149-
func matchAppLabels(appName string, appLabels map[string]string, filterLabel string) bool {
150-
151-
if filterLabel == "" {
152-
return true
153-
}
154-
155-
filterLabelMap, err := parseLabel(filterLabel)
156-
if err != nil {
157-
log.Errorf("Unable match app labels against %s: %s", filterLabel, err)
158-
return false
159-
}
160-
161-
for filterLabelKey, filterLabelValue := range filterLabelMap {
162-
log.Tracef("Matching application name %s against label %s", appName, filterLabel)
163-
if appLabelValue, ok := appLabels[filterLabelKey]; ok {
164-
if appLabelValue == filterLabelValue {
165-
return true
166-
}
167-
}
168-
}
169-
return false
170-
}
171-
172148
// Retrieve a list of applications from ArgoCD that qualify for image updates
173149
// Application needs either to be of type Kustomize or Helm and must have the
174150
// correct annotation in order to be considered.
175-
func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string, appLabel string) (map[string]ApplicationImages, error) {
151+
func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string) (map[string]ApplicationImages, error) {
176152
var appsForUpdate = make(map[string]ApplicationImages)
177153

178154
for _, app := range apps {
@@ -199,12 +175,6 @@ func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string,
199175
continue
200176
}
201177

202-
// Check if application carries requested label
203-
if !matchAppLabels(app.GetName(), app.GetLabels(), appLabel) {
204-
logCtx.Debugf("Skipping app '%s' because it does not carry requested label", appNSName)
205-
continue
206-
}
207-
208178
logCtx.Tracef("processing app '%s' of type '%v'", appNSName, sourceType)
209179
imageList := parseImageList(annotations)
210180
appImages := ApplicationImages{}
@@ -231,20 +201,6 @@ func parseImageList(annotations map[string]string) *image.ContainerImageList {
231201
return &results
232202
}
233203

234-
func parseLabel(inputLabel string) (map[string]string, error) {
235-
var selectedLabels map[string]string
236-
const labelFieldDelimiter = "="
237-
if inputLabel != "" {
238-
selectedLabels = map[string]string{}
239-
fields := strings.Split(inputLabel, labelFieldDelimiter)
240-
if len(fields) != 2 {
241-
return nil, fmt.Errorf("labels should have key%svalue, but instead got: %s", labelFieldDelimiter, inputLabel)
242-
}
243-
selectedLabels[fields[0]] = fields[1]
244-
}
245-
return selectedLabels, nil
246-
}
247-
248204
// GetApplication gets the application named appName from Argo CD API
249205
func (client *argoCD) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) {
250206
conn, appClient, err := client.Client.NewApplicationClient()
@@ -267,7 +223,7 @@ func (client *argoCD) GetApplication(ctx context.Context, appName string) (*v1al
267223

268224
// ListApplications returns a list of all application names that the API user
269225
// has access to.
270-
func (client *argoCD) ListApplications() ([]v1alpha1.Application, error) {
226+
func (client *argoCD) ListApplications(labelSelector string) ([]v1alpha1.Application, error) {
271227
conn, appClient, err := client.Client.NewApplicationClient()
272228
metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
273229
if err != nil {
@@ -277,7 +233,7 @@ func (client *argoCD) ListApplications() ([]v1alpha1.Application, error) {
277233
defer conn.Close()
278234

279235
metrics.Clients().IncreaseArgoCDClientRequest(client.Client.ClientOptions().ServerAddr, 1)
280-
apps, err := appClient.List(context.TODO(), &application.ApplicationQuery{})
236+
apps, err := appClient.List(context.TODO(), &application.ApplicationQuery{Selector: &labelSelector})
281237
if err != nil {
282238
metrics.Clients().IncreaseArgoCDClientError(client.Client.ClientOptions().ServerAddr, 1)
283239
return nil, err

pkg/argocd/argocd_test.go

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ func Test_FilterApplicationsForUpdate(t *testing.T) {
491491
},
492492
},
493493
}
494-
filtered, err := FilterApplicationsForUpdate(applicationList, []string{}, "")
494+
filtered, err := FilterApplicationsForUpdate(applicationList, []string{})
495495
require.NoError(t, err)
496496
require.Len(t, filtered, 1)
497497
require.Contains(t, filtered, "argocd/app1")
@@ -543,55 +543,13 @@ func Test_FilterApplicationsForUpdate(t *testing.T) {
543543
},
544544
},
545545
}
546-
filtered, err := FilterApplicationsForUpdate(applicationList, []string{"app*"}, "")
546+
filtered, err := FilterApplicationsForUpdate(applicationList, []string{"app*"})
547547
require.NoError(t, err)
548548
require.Len(t, filtered, 2)
549549
require.Contains(t, filtered, "argocd/app1")
550550
require.Contains(t, filtered, "argocd/app2")
551551
assert.Len(t, filtered["argocd/app1"].Images, 2)
552552
})
553-
554-
t.Run("Filter for applications with label", func(t *testing.T) {
555-
applicationList := []v1alpha1.Application{
556-
// Annotated and carries required label
557-
{
558-
ObjectMeta: v1.ObjectMeta{
559-
Name: "app1",
560-
Namespace: "argocd",
561-
Annotations: map[string]string{
562-
common.ImageUpdaterAnnotation: "nginx, quay.io/dexidp/dex:v1.23.0",
563-
},
564-
Labels: map[string]string{
565-
"custom.label/name": "xyz",
566-
},
567-
},
568-
Spec: v1alpha1.ApplicationSpec{},
569-
Status: v1alpha1.ApplicationStatus{
570-
SourceType: v1alpha1.ApplicationSourceTypeKustomize,
571-
},
572-
},
573-
// Annotated but does not carry required label
574-
{
575-
ObjectMeta: v1.ObjectMeta{
576-
Name: "app2",
577-
Namespace: "argocd",
578-
Annotations: map[string]string{
579-
common.ImageUpdaterAnnotation: "nginx, quay.io/dexidp/dex:v1.23.0",
580-
},
581-
},
582-
Spec: v1alpha1.ApplicationSpec{},
583-
Status: v1alpha1.ApplicationStatus{
584-
SourceType: v1alpha1.ApplicationSourceTypeHelm,
585-
},
586-
},
587-
}
588-
filtered, err := FilterApplicationsForUpdate(applicationList, []string{}, "custom.label/name=xyz")
589-
require.NoError(t, err)
590-
require.Len(t, filtered, 1)
591-
require.Contains(t, filtered, "argocd/app1")
592-
assert.Len(t, filtered["argocd/app1"].Images, 2)
593-
})
594-
595553
}
596554

597555
func Test_GetHelmParamAnnotations(t *testing.T) {
@@ -1064,7 +1022,7 @@ func TestKubernetesClient(t *testing.T) {
10641022
require.NoError(t, err)
10651023

10661024
t.Run("List applications", func(t *testing.T) {
1067-
apps, err := client.ListApplications()
1025+
apps, err := client.ListApplications("")
10681026
require.NoError(t, err)
10691027
require.Len(t, apps, 1)
10701028

pkg/argocd/mocks/ArgoCD.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)