diff --git a/controller/state.go b/controller/state.go index 5b59f411dafb1..d402f3f87e98b 100644 --- a/controller/state.go +++ b/controller/state.go @@ -533,7 +533,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1 for i := len(targetObjs) - 1; i >= 0; i-- { targetObj := targetObjs[i] gvk := targetObj.GroupVersionKind() - if resFilter.IsExcludedResource(gvk.Group, gvk.Kind, app.Spec.Destination.Server) { + if resFilter.IsExcludedResource(gvk.Group, gvk.Kind, app.Spec.Destination.Server, app.Spec.Destination.Namespace) { targetObjs = append(targetObjs[:i], targetObjs[i+1:]...) conditions = append(conditions, v1alpha1.ApplicationCondition{ Type: v1alpha1.ApplicationConditionExcludedResourceWarning, diff --git a/util/settings/filtered_resource.go b/util/settings/filtered_resource.go index 29c5d5cef51c9..dcc94006165e5 100644 --- a/util/settings/filtered_resource.go +++ b/util/settings/filtered_resource.go @@ -3,9 +3,10 @@ package settings import "github.com/argoproj/argo-cd/v2/util/glob" type FilteredResource struct { - APIGroups []string `json:"apiGroups,omitempty"` - Kinds []string `json:"kinds,omitempty"` - Clusters []string `json:"clusters,omitempty"` + APIGroups []string `json:"apiGroups,omitempty"` + Kinds []string `json:"kinds,omitempty"` + Clusters []string `json:"clusters,omitempty"` + Namespaces []string `json:"namespaces,omitempty"` } func (r FilteredResource) matchGroup(apiGroup string) bool { @@ -35,6 +36,15 @@ func (r FilteredResource) MatchCluster(cluster string) bool { return len(r.Clusters) == 0 } -func (r FilteredResource) Match(apiGroup, kind, cluster string) bool { - return r.matchGroup(apiGroup) && r.matchKind(kind) && r.MatchCluster(cluster) +func (r FilteredResource) MatchNamespace(namespace string) bool { + for _, excludedNamespace := range r.Namespaces { + if glob.Match(excludedNamespace, namespace) { + return true + } + } + return len(r.Namespaces) == 0 +} + +func (r FilteredResource) Match(apiGroup, kind, cluster, namespace string) bool { + return r.matchGroup(apiGroup) && r.matchKind(kind) && r.MatchCluster(cluster) && r.MatchNamespace(namespace) } diff --git a/util/settings/filtered_resource_test.go b/util/settings/filtered_resource_test.go index bb6a6d737526b..6ff8eb509c363 100644 --- a/util/settings/filtered_resource_test.go +++ b/util/settings/filtered_resource_test.go @@ -10,30 +10,36 @@ func TestExcludeResource(t *testing.T) { apiGroup := "foo.com" kind := "bar" cluster := "baz.com" + namespace := "qux" // matches with missing values - assert.True(t, FilteredResource{Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{"*"}}.Match(apiGroup, kind, cluster)) + assert.True(t, FilteredResource{Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster, namespace)) // simple matches - assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.True(t, FilteredResource{APIGroups: []string{"*.com"}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{"*"}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{"*.com"}}.Match(apiGroup, kind, cluster)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{"*.com"}, Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{"*"}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{"*.com"}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{"*"}}.Match(apiGroup, kind, cluster, namespace)) // negative matches - assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.False(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{""}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.False(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{""}}.Match(apiGroup, kind, cluster)) + assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.False(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{""}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.False(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{""}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.False(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{""}}.Match(apiGroup, kind, cluster, namespace)) // complex matches - assert.True(t, FilteredResource{APIGroups: []string{apiGroup, apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind, kind}, Clusters: []string{cluster}}.Match(apiGroup, kind, cluster)) - assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster, cluster}}.Match(apiGroup, kind, cluster)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup, apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind, kind}, Clusters: []string{cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster, cluster}, Namespaces: []string{namespace}}.Match(apiGroup, kind, cluster, namespace)) + assert.True(t, FilteredResource{APIGroups: []string{apiGroup}, Kinds: []string{kind}, Clusters: []string{cluster}, Namespaces: []string{namespace, namespace}}.Match(apiGroup, kind, cluster, namespace)) // rubbish patterns - assert.False(t, FilteredResource{APIGroups: []string{"["}, Kinds: []string{""}, Clusters: []string{""}}.Match("", "", "")) - assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{"["}, Clusters: []string{""}}.Match("", "", "")) - assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{""}, Clusters: []string{"["}}.Match("", "", "")) + assert.False(t, FilteredResource{APIGroups: []string{"["}, Kinds: []string{""}, Clusters: []string{""}, Namespaces: []string{""}}.Match("", "", "", "")) + assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{"["}, Clusters: []string{""}, Namespaces: []string{""}}.Match("", "", "", "")) + assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{""}, Clusters: []string{"["}, Namespaces: []string{""}}.Match("", "", "", "")) + assert.False(t, FilteredResource{APIGroups: []string{""}, Kinds: []string{""}, Clusters: []string{""}, Namespaces: []string{"["}}.Match("", "", "", "")) } diff --git a/util/settings/resources_filter.go b/util/settings/resources_filter.go index d6a08c5a2341c..8d0c6267a3abd 100644 --- a/util/settings/resources_filter.go +++ b/util/settings/resources_filter.go @@ -21,9 +21,9 @@ func (rf *ResourcesFilter) getExcludedResources() []FilteredResource { return append(coreExcludedResources, rf.ResourceExclusions...) } -func (rf *ResourcesFilter) checkResourcePresence(apiGroup, kind, cluster string, filteredResources []FilteredResource) bool { +func (rf *ResourcesFilter) checkResourcePresence(apiGroup, kind, cluster, namespace string, filteredResources []FilteredResource) bool { for _, includedResource := range filteredResources { - if includedResource.Match(apiGroup, kind, cluster) { + if includedResource.Match(apiGroup, kind, cluster, namespace) { return true } } @@ -31,12 +31,12 @@ func (rf *ResourcesFilter) checkResourcePresence(apiGroup, kind, cluster string, return false } -func (rf *ResourcesFilter) isIncludedResource(apiGroup, kind, cluster string) bool { - return rf.checkResourcePresence(apiGroup, kind, cluster, rf.ResourceInclusions) +func (rf *ResourcesFilter) isIncludedResource(apiGroup, kind, cluster, namespace string) bool { + return rf.checkResourcePresence(apiGroup, kind, cluster, namespace, rf.ResourceInclusions) } -func (rf *ResourcesFilter) isExcludedResource(apiGroup, kind, cluster string) bool { - return rf.checkResourcePresence(apiGroup, kind, cluster, rf.getExcludedResources()) +func (rf *ResourcesFilter) isExcludedResource(apiGroup, kind, cluster, namespace string) bool { + return rf.checkResourcePresence(apiGroup, kind, cluster, namespace, rf.getExcludedResources()) } // Behavior of this function is as follows: @@ -61,14 +61,14 @@ func (rf *ResourcesFilter) isExcludedResource(apiGroup, kind, cluster string) bo // +-------------+-------------+-------------+ // | Present | Present | Not Allowed | // +-------------+-------------+-------------+ -func (rf *ResourcesFilter) IsExcludedResource(apiGroup, kind, cluster string) bool { +func (rf *ResourcesFilter) IsExcludedResource(apiGroup, kind, cluster, namespace string) bool { // if excluded, do not allow - if rf.isExcludedResource(apiGroup, kind, cluster) { + if rf.isExcludedResource(apiGroup, kind, cluster, namespace) { return true } // if included, do allow - if rf.isIncludedResource(apiGroup, kind, cluster) { + if rf.isIncludedResource(apiGroup, kind, cluster, namespace) { return false } diff --git a/util/settings/resources_filter_test.go b/util/settings/resources_filter_test.go index 6bb6e9a74ced2..c94e2f6d14e59 100644 --- a/util/settings/resources_filter_test.go +++ b/util/settings/resources_filter_test.go @@ -8,9 +8,9 @@ import ( func TestIsExcludedResource(t *testing.T) { settings := &ResourcesFilter{} - assert.True(t, settings.IsExcludedResource("events.k8s.io", "", "")) - assert.True(t, settings.IsExcludedResource("metrics.k8s.io", "", "")) - assert.False(t, settings.IsExcludedResource("rubbish.io", "", "")) + assert.True(t, settings.IsExcludedResource("events.k8s.io", "", "", "")) + assert.True(t, settings.IsExcludedResource("metrics.k8s.io", "", "", "")) + assert.False(t, settings.IsExcludedResource("rubbish.io", "", "", "")) } func TestResourceInclusions(t *testing.T) { @@ -18,8 +18,8 @@ func TestResourceInclusions(t *testing.T) { ResourceInclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}}}, } - assert.True(t, filter.IsExcludedResource("non-whitelisted-resource", "", "")) - assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "")) + assert.True(t, filter.IsExcludedResource("non-whitelisted-resource", "", "", "")) + assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "", "")) } func TestResourceInclusionsExclusionNonMutex(t *testing.T) { @@ -28,26 +28,26 @@ func TestResourceInclusionsExclusionNonMutex(t *testing.T) { ResourceExclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}, Kinds: []string{"blacklisted-kind"}}}, } - assert.True(t, filter.IsExcludedResource("whitelisted-resource", "blacklisted-kind", "")) - assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "")) - assert.False(t, filter.IsExcludedResource("whitelisted-resource", "non-blacklisted-kind", "")) + assert.True(t, filter.IsExcludedResource("whitelisted-resource", "blacklisted-kind", "", "")) + assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "", "")) + assert.False(t, filter.IsExcludedResource("whitelisted-resource", "non-blacklisted-kind", "", "")) filter = ResourcesFilter{ ResourceInclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}, Kinds: []string{"whitelisted-kind"}}}, ResourceExclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}}}, } - assert.True(t, filter.IsExcludedResource("whitelisted-resource", "whitelisted-kind", "")) - assert.True(t, filter.IsExcludedResource("whitelisted-resource", "", "")) - assert.True(t, filter.IsExcludedResource("whitelisted-resource", "non-whitelisted-kind", "")) + assert.True(t, filter.IsExcludedResource("whitelisted-resource", "whitelisted-kind", "", "")) + assert.True(t, filter.IsExcludedResource("whitelisted-resource", "", "", "")) + assert.True(t, filter.IsExcludedResource("whitelisted-resource", "non-whitelisted-kind", "", "")) filter = ResourcesFilter{ ResourceInclusions: []FilteredResource{{APIGroups: []string{"foo-bar"}, Kinds: []string{"whitelisted-kind"}}}, ResourceExclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}}}, } - assert.True(t, filter.IsExcludedResource("not-whitelisted-resource", "whitelisted-kind", "")) - assert.True(t, filter.IsExcludedResource("not-whitelisted-resource", "", "")) + assert.True(t, filter.IsExcludedResource("not-whitelisted-resource", "whitelisted-kind", "", "")) + assert.True(t, filter.IsExcludedResource("not-whitelisted-resource", "", "", "")) } func TestResourceInclusionsExclusionMultiCluster(t *testing.T) { @@ -56,7 +56,17 @@ func TestResourceInclusionsExclusionMultiCluster(t *testing.T) { ResourceExclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}, Clusters: []string{"cluster-two"}}}, } - assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "cluster-one")) - assert.True(t, filter.IsExcludedResource("whitelisted-resource", "", "cluster-two")) - assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "cluster-three")) + assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "cluster-one", "")) + assert.True(t, filter.IsExcludedResource("whitelisted-resource", "", "cluster-two", "")) + assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "cluster-three", "")) +} + +func TestResourceInclusionsExclusionsNamespaces(t *testing.T) { + filter := ResourcesFilter{ + ResourceInclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}, Namespaces: []string{"whitelisted-namespace"}}}, + ResourceExclusions: []FilteredResource{{APIGroups: []string{"whitelisted-resource"}, Namespaces: []string{"blacklisted-namespace"}}}, + } + + assert.True(t, filter.IsExcludedResource("whitelisted-resource", "", "", "blacklisted-namespace")) + assert.False(t, filter.IsExcludedResource("whitelisted-resource", "", "", "whitelisted-namespace")) } diff --git a/util/settings/settings_test.go b/util/settings/settings_test.go index 9391ac5458137..36f483b512956 100644 --- a/util/settings/settings_test.go +++ b/util/settings/settings_test.go @@ -123,15 +123,15 @@ func TestGetRepositoryCredentials(t *testing.T) { func TestGetResourceFilter(t *testing.T) { data := map[string]string{ - "resource.exclusions": "\n - apiGroups: [\"group1\"]\n kinds: [\"kind1\"]\n clusters: [\"cluster1\"]\n", - "resource.inclusions": "\n - apiGroups: [\"group2\"]\n kinds: [\"kind2\"]\n clusters: [\"cluster2\"]\n", + "resource.exclusions": "\n - apiGroups: [\"group1\"]\n kinds: [\"kind1\"]\n clusters: [\"cluster1\"]\n namespaces: [\"namespace1\"]\n", + "resource.inclusions": "\n - apiGroups: [\"group2\"]\n kinds: [\"kind2\"]\n clusters: [\"cluster2\"]\n namespaces: [\"namespace2\"]\n", } _, settingsManager := fixtures(data) filter, err := settingsManager.GetResourcesFilter() require.NoError(t, err) assert.Equal(t, &ResourcesFilter{ - ResourceExclusions: []FilteredResource{{APIGroups: []string{"group1"}, Kinds: []string{"kind1"}, Clusters: []string{"cluster1"}}}, - ResourceInclusions: []FilteredResource{{APIGroups: []string{"group2"}, Kinds: []string{"kind2"}, Clusters: []string{"cluster2"}}}, + ResourceExclusions: []FilteredResource{{APIGroups: []string{"group1"}, Kinds: []string{"kind1"}, Clusters: []string{"cluster1"}, Namespaces: []string{"namespace1"}}}, + ResourceInclusions: []FilteredResource{{APIGroups: []string{"group2"}, Kinds: []string{"kind2"}, Clusters: []string{"cluster2"}, Namespaces: []string{"namespace2"}}}, }, filter) }