diff --git a/docs/operator-manual/argocd-cm.yaml b/docs/operator-manual/argocd-cm.yaml index 4cf8c35d58a28..61b8c3faa5d4f 100644 --- a/docs/operator-manual/argocd-cm.yaml +++ b/docs/operator-manual/argocd-cm.yaml @@ -221,12 +221,15 @@ data: # An optional comma-separated list of metadata.labels to observe in the UI. resource.customLabels: tier - # An optional comma-separated list of metadata.labels keys to add onto k8s events generated for Applications. - # The keys are compared against Application and it's AppProject, if matched, - # the corresponding labels are added on the generated event. - # In case of conflict between labels on Application and AppProject, - # the Application label values are prioritized and added to the event. - resource.eventLabelKeys: team,env + # An optional comma-separated list of metadata.labels keys to add to Kubernetes events generated for Applications. + # The keys are compared against the Application and its AppProject. If matched, + # the corresponding labels are added to the generated event. + # In case of a conflict between labels on the Application and AppProject, + # the Application label values are prioritized and added to the event. Supports wildcards. + resource.includeEventLabelKeys: team,env* + # An optional comma-separated list of metadata.labels keys to exclude from Kubernetes events generated for Applications. Supports wildcards. + resource.excludeEventLabelKeys: environment,bu + resource.compareoptions: | # if ignoreAggregatedRoles set to true then differences caused by aggregated roles in RBAC resources are ignored. diff --git a/docs/operator-manual/declarative-setup.md b/docs/operator-manual/declarative-setup.md index 3830cb610796a..5053102f7c00f 100644 --- a/docs/operator-manual/declarative-setup.md +++ b/docs/operator-manual/declarative-setup.md @@ -1130,6 +1130,22 @@ data: Custom Labels configured with `resource.customLabels` (comma separated string) will be displayed in the UI (for any resource that defines them). +## Labels on Application Events + +An optional comma-separated list of `metadata.labels` keys can be configured with `resource.includeEventLabelKeys` to add to Kubernetes events generated for Argo CD Applications. When events are generated for Applications containing the specified labels, the controller adds the matching labels to the event. This establishes an easy link between the event and the application, allowing for filtering using labels. In case of conflict between labels on the Application and AppProject, the Application label values are prioritized and added to the event. + +```yaml + resource.includeEventLabelKeys: team,env* +``` + +To exclude certain labels from events, use the `resource.excludeEventLabelKeys` key, which takes a comma-separated list of `metadata.labels` keys. + +```yaml + resource.excludeEventLabelKeys: environment,bu +``` + +Both `resource.includeEventLabelKeys` and `resource.excludeEventLabelKeys` support wildcards. + ## SSO & RBAC * SSO configuration details: [SSO](./user-management/index.md) diff --git a/test/e2e/app_k8s_events_test.go b/test/e2e/app_k8s_events_test.go index f67ea89f1e6fb..e306e2ddf0d5a 100644 --- a/test/e2e/app_k8s_events_test.go +++ b/test/e2e/app_k8s_events_test.go @@ -13,16 +13,17 @@ import ( . "github.com/argoproj/argo-cd/v2/test/e2e/fixture/app" ) -// resource.eventLabelKeys keys set in argocd-cm +// resource.includeEventLabelKeys keys set in argocd-cm func TestLabelsOnAppK8sEvents(t *testing.T) { - expectedLabels := map[string]string{"app": "test", "env": "dev"} + expectedLabels := map[string]string{"app": "test", "environment": "dev"} Given(t). Timeout(60). Path("two-nice-pods"). When(). - SetParamInSettingConfigMap("resource.eventLabelKeys", "app,env"). - CreateApp("--label=app=test", "--label=env=dev", "--label=tier=ui"). + SetParamInSettingConfigMap("resource.includeEventLabelKeys", "app,team,env*"). + SetParamInSettingConfigMap("resource.excludeEventLabelKeys", "team"). + CreateApp("--label=app=test", "--label=environment=dev", "--label=team=A", "--label=tier=ui"). Sync(). Then(). Expect(SyncStatusIs(SyncStatusCodeSynced)). @@ -41,13 +42,13 @@ func TestLabelsOnAppK8sEvents(t *testing.T) { }) } -// resource.eventLabelKeys keys not set in argocd-cm +// resource.includeEventLabelKeys keys not set in argocd-cm func TestNoLabelsOnAppK8sEvents(t *testing.T) { Given(t). Timeout(60). Path("two-nice-pods"). When(). - CreateApp("--label=app=test", "--label=env=dev", "--label=tier=ui"). + CreateApp("--label=app=test", "--label=environment=dev", "--label=team=A", "--label=tier=ui"). Sync(). Then(). Expect(SyncStatusIs(SyncStatusCodeSynced)). diff --git a/util/argo/argo.go b/util/argo/argo.go index 516c7955a81f1..70f962dd8ef3a 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -27,6 +27,7 @@ import ( applicationsv1 "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1" "github.com/argoproj/argo-cd/v2/reposerver/apiclient" "github.com/argoproj/argo-cd/v2/util/db" + "github.com/argoproj/argo-cd/v2/util/glob" "github.com/argoproj/argo-cd/v2/util/io" "github.com/argoproj/argo-cd/v2/util/settings" ) @@ -1111,13 +1112,15 @@ func IsValidContainerName(name string) bool { return len(validationErrors) == 0 } -// GetAppEventLabels returns a map of labels to add to k8s event. -// The Application and it's AppProject labels are compared against `resource.eventLabelKeys` key in argocd-cm, -// if matched, the corresponding labels are returned to add on the generated event. In case of conflict -// between labels on Application and AppProject, the Application label values are prioritized and added to the event. +// GetAppEventLabels returns a map of labels to add to a K8s event. +// The Application and its AppProject labels are compared against the `resource.includeEventLabelKeys` key in argocd-cm. +// If matched, the corresponding labels are returned to be added to the generated event. In case of a conflict +// between labels on the Application and AppProject, the Application label values are prioritized and added to the event. +// Furthermore, labels specified in `resource.excludeEventLabelKeys` in argocd-cm are removed from the event labels, if they were included. func GetAppEventLabels(app *argoappv1.Application, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager, db db.ArgoDB, ctx context.Context) map[string]string { eventLabels := make(map[string]string) + // Get all app & app-project labels labels := app.Labels if labels == nil { labels = make(map[string]string) @@ -1134,13 +1137,23 @@ func GetAppEventLabels(app *argoappv1.Application, projLister applicationsv1.App log.Warn(err) } - keys := settingsManager.GetEventLabelKeys() - for _, k := range keys { - v, found := labels[k] + // Filter out event labels to include + inKeys := settingsManager.GetIncludeEventLabelKeys() + for k, v := range labels { + found := glob.MatchStringInList(inKeys, k, false) if found { eventLabels[k] = v } } + // Remove excluded event labels + exKeys := settingsManager.GetExcludeEventLabelKeys() + for k := range eventLabels { + found := glob.MatchStringInList(exKeys, k, false) + if found { + delete(eventLabels, k) + } + } + return eventLabels } diff --git a/util/argo/argo_test.go b/util/argo/argo_test.go index 002f92483c203..030577e59d45a 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -1569,59 +1569,61 @@ func TestAugmentSyncMsg(t *testing.T) { func TestGetAppEventLabels(t *testing.T) { tests := []struct { name string - cmEventLabelKeys string + cmInEventLabelKeys string + cmExEventLabelKeys string appLabels map[string]string projLabels map[string]string expectedEventLabels map[string]string }{ { - name: "no label keys in cm", - cmEventLabelKeys: "", - appLabels: nil, - projLabels: nil, - expectedEventLabels: nil, - }, - { - name: "no label keys in cm", - cmEventLabelKeys: "", + name: "no label keys in cm - no event labels", + cmInEventLabelKeys: "", appLabels: map[string]string{"team": "A", "tier": "frontend"}, - projLabels: map[string]string{"env": "dev"}, + projLabels: map[string]string{"environment": "dev"}, expectedEventLabels: nil, }, { - name: "label keys in cm, no labels on app & proj", - cmEventLabelKeys: "team,env", + name: "label keys in cm, no labels on app & proj - no event labels", + cmInEventLabelKeys: "team, environment", appLabels: nil, projLabels: nil, expectedEventLabels: nil, }, { - name: "label keys in cm, labels on app, no labels on proj", - cmEventLabelKeys: "team,env", + name: "labels on app, no labels on proj - event labels matched on app only", + cmInEventLabelKeys: "team, environment", appLabels: map[string]string{"team": "A", "tier": "frontend"}, projLabels: nil, expectedEventLabels: map[string]string{"team": "A"}, }, { - name: "label keys in cm, no labels on app, labels on proj", - cmEventLabelKeys: "team,env", + name: "no labels on app, labels on proj - event labels matched on proj only", + cmInEventLabelKeys: "team, environment", appLabels: nil, - projLabels: map[string]string{"env": "dev"}, - expectedEventLabels: map[string]string{"env": "dev"}, + projLabels: map[string]string{"environment": "dev"}, + expectedEventLabels: map[string]string{"environment": "dev"}, + }, + { + name: "labels on app & proj with conflicts - event labels matched on both app & proj and app labels prioritized on conflict", + cmInEventLabelKeys: "team, environment", + appLabels: map[string]string{"team": "A", "environment": "stage", "tier": "frontend"}, + projLabels: map[string]string{"environment": "dev"}, + expectedEventLabels: map[string]string{"team": "A", "environment": "stage"}, }, { - name: "label keys in cm, labels on app & proj", - cmEventLabelKeys: "team,env", + name: "wildcard support - matched all labels", + cmInEventLabelKeys: "*", appLabels: map[string]string{"team": "A", "tier": "frontend"}, - projLabels: map[string]string{"env": "dev"}, - expectedEventLabels: map[string]string{"team": "A", "env": "dev"}, + projLabels: map[string]string{"environment": "dev"}, + expectedEventLabels: map[string]string{"team": "A", "tier": "frontend", "environment": "dev"}, }, { - name: "app & proj label conflicts", - cmEventLabelKeys: "example.com/team,env", + name: "exlcude event labels", + cmInEventLabelKeys: "example.com/team,tier,env*", + cmExEventLabelKeys: "tie*", appLabels: map[string]string{"example.com/team": "A", "tier": "frontend"}, - projLabels: map[string]string{"example.com/team": "B", "env": "dev"}, - expectedEventLabels: map[string]string{"example.com/team": "A", "env": "dev"}, + projLabels: map[string]string{"environment": "dev"}, + expectedEventLabels: map[string]string{"example.com/team": "A", "environment": "dev"}, }, } for _, tt := range tests { @@ -1635,7 +1637,8 @@ func TestGetAppEventLabels(t *testing.T) { }, }, Data: map[string]string{ - "resource.eventLabelKeys": tt.cmEventLabelKeys, + "resource.includeEventLabelKeys": tt.cmInEventLabelKeys, + "resource.excludeEventLabelKeys": tt.cmExEventLabelKeys, }, } @@ -1664,7 +1667,7 @@ func TestGetAppEventLabels(t *testing.T) { argoDB := db.NewDB("default", settingsMgr, kubeClient) eventLabels := GetAppEventLabels(&app, applisters.NewAppProjectLister(informer.GetIndexer()), test.FakeArgoCDNamespace, settingsMgr, argoDB, ctx) - assert.Equal(t, len(eventLabels), len(tt.expectedEventLabels)) + assert.Equal(t, len(tt.expectedEventLabels), len(eventLabels)) for ek, ev := range tt.expectedEventLabels { v, found := eventLabels[ek] assert.True(t, found) diff --git a/util/settings/settings.go b/util/settings/settings.go index b37161f483a1f..a425aaad5237a 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -445,8 +445,10 @@ const ( resourceIgnoreResourceUpdatesEnabledKey = "resource.ignoreResourceUpdatesEnabled" // resourceCustomLabelKey is the key to a custom label to show in node info, if present resourceCustomLabelsKey = "resource.customLabels" - // resourceEventLabelKeys is the key to labels to be added onto Application k8s events if present on an Application or it's AppProject - resourceEventLabelKeys = "resource.eventLabelKeys" + // resourceIncludeEventLabelKeys is the key to labels to be added onto Application k8s events if present on an Application or it's AppProject. Supports wildcard. + resourceIncludeEventLabelKeys = "resource.includeEventLabelKeys" + // resourceExcludeEventLabelKeys is the key to labels to be excluded from adding onto Application's k8s events. Supports wildcard. + resourceExcludeEventLabelKeys = "resource.excludeEventLabelKeys" // kustomizeBuildOptionsKey is a string of kustomize build parameters kustomizeBuildOptionsKey = "kustomize.buildOptions" // kustomizeVersionKeyPrefix is a kustomize version key prefix @@ -2224,14 +2226,30 @@ func (mgr *SettingsManager) GetResourceCustomLabels() ([]string, error) { return []string{}, nil } -func (mgr *SettingsManager) GetEventLabelKeys() []string { +func (mgr *SettingsManager) GetIncludeEventLabelKeys() []string { labelKeys := []string{} argoCDCM, err := mgr.getConfigMap() if err != nil { log.Error(fmt.Errorf("failed getting configmap: %v", err)) return labelKeys } - if value, ok := argoCDCM.Data[resourceEventLabelKeys]; ok { + if value, ok := argoCDCM.Data[resourceIncludeEventLabelKeys]; ok { + if value != "" { + value = strings.ReplaceAll(value, " ", "") + labelKeys = strings.Split(value, ",") + } + } + return labelKeys +} + +func (mgr *SettingsManager) GetExcludeEventLabelKeys() []string { + labelKeys := []string{} + argoCDCM, err := mgr.getConfigMap() + if err != nil { + log.Error(fmt.Errorf("failed getting configmap: %v", err)) + return labelKeys + } + if value, ok := argoCDCM.Data[resourceExcludeEventLabelKeys]; ok { if value != "" { value = strings.ReplaceAll(value, " ", "") labelKeys = strings.Split(value, ",") diff --git a/util/settings/settings_test.go b/util/settings/settings_test.go index 0cfbee1989046..3284e8475467c 100644 --- a/util/settings/settings_test.go +++ b/util/settings/settings_test.go @@ -719,25 +719,33 @@ func TestSettingsManager_GetEventLabelKeys(t *testing.T) { }{ { name: "Comma separated data", - data: "app,env, tier, example.com/team", - expectedKeys: []string{"app", "env", "tier", "example.com/team"}, + data: "app,env, tier, example.com/team-*, *", + expectedKeys: []string{"app", "env", "tier", "example.com/team-*", "*"}, }, { name: "Empty data", - data: "", expectedKeys: []string{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, settingsManager := fixtures(map[string]string{ - resourceEventLabelKeys: tt.data, - }) - keys := settingsManager.GetEventLabelKeys() - assert.Equal(t, len(tt.expectedKeys), len(keys)) + _, settingsManager := fixtures(map[string]string{}) + if tt.data != "" { + _, settingsManager = fixtures(map[string]string{ + resourceIncludeEventLabelKeys: tt.data, + resourceExcludeEventLabelKeys: tt.data, + }) + } + + inKeys := settingsManager.GetIncludeEventLabelKeys() + assert.Equal(t, len(tt.expectedKeys), len(inKeys)) + + exKeys := settingsManager.GetExcludeEventLabelKeys() + assert.Equal(t, len(tt.expectedKeys), len(exKeys)) for i := range tt.expectedKeys { - assert.Equal(t, tt.expectedKeys[i], keys[i]) + assert.Equal(t, tt.expectedKeys[i], inKeys[i]) + assert.Equal(t, tt.expectedKeys[i], exKeys[i]) } }) }