mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-09 22:47:27 +00:00
Some checks failed
Run the E2E test on kind / get-go-version (push) Failing after 1m5s
Run the E2E test on kind / build (push) Has been skipped
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / get-go-version (push) Successful in 11s
Main CI / Build (push) Failing after 25s
Close stale issues and PRs / stale (push) Successful in 14s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m36s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-aws, main) (push) Failing after 1m2s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-gcp, main) (push) Failing after 1m19s
Trivy Nightly Scan / Trivy nightly scan (velero-plugin-for-microsoft-azure, main) (push) Failing after 1m2s
* Add wildcard status fields Signed-off-by: Joseph <jvaikath@redhat.com> * Implement wildcard namespace expansion in item collector - Introduced methods to get active namespaces and expand wildcard includes/excludes in the item collector. - Updated getNamespacesToList to handle wildcard patterns and return expanded lists. - Added utility functions for setting includes and excludes in the IncludesExcludes struct. - Created a new package for wildcard handling, including functions to determine when to expand wildcards and to perform the expansion. This enhances the backup process by allowing more flexible namespace selection based on wildcard patterns. Signed-off-by: Joseph <jvaikath@redhat.com> * Enhance wildcard expansion logic and logging in item collector - Improved logging to include original includes and excludes when expanding wildcards. - Updated the ShouldExpandWildcards function to check for wildcard patterns in excludes. - Added comments for clarity in the expandWildcards function regarding pattern handling. These changes enhance the clarity and functionality of the wildcard expansion process in the backup system. Signed-off-by: Joseph <jvaikath@redhat.com> * Add wildcard namespace fields to Backup CRD and update deepcopy methods - Introduced `wildcardIncludedNamespaces` and `wildcardExcludedNamespaces` fields to the Backup CRD to support wildcard patterns for namespace inclusion and exclusion. - Updated deepcopy methods to handle the new fields, ensuring proper copying of data during object manipulation. These changes enhance the flexibility of namespace selection in backup operations, aligning with recent improvements in wildcard handling. Signed-off-by: Joseph <jvaikath@redhat.com> * Refactor Backup CRD to rename wildcard namespace fields - Updated `BackupStatus` struct to rename `WildcardIncludedNamespaces` to `WildcardExpandedIncludedNamespaces` and `WildcardExcludedNamespaces` to `WildcardExpandedExcludedNamespaces` for clarity. - Adjusted associated comments to reflect the new naming and ensure consistency in documentation. - Modified deepcopy methods to accommodate the renamed fields, ensuring proper data handling during object manipulation. These changes enhance the clarity and maintainability of the Backup CRD, aligning with recent improvements in wildcard handling. Signed-off-by: Joseph <jvaikath@redhat.com> * Fix Signed-off-by: Joseph <jvaikath@redhat.com> * Refactor where wildcard expansion happens Signed-off-by: Joseph <jvaikath@redhat.com> * Refactor Backup CRD and related components for expanded namespace handling - Updated `BackupStatus` struct to rename fields for clarity: `WildcardExpandedIncludedNamespaces` and `WildcardExpandedExcludedNamespaces` are now `ExpandedIncludedNamespaces` and `ExpandedExcludedNamespaces`, respectively. - Adjusted associated comments and deepcopy methods to reflect the new naming conventions. - Removed the `getActiveNamespaces` function from the item collector, streamlining the namespace handling process. - Enhanced logging during wildcard expansion to provide clearer insights into the process. These changes improve the clarity and maintainability of the Backup CRD and enhance the functionality of namespace selection in backup operations. Signed-off-by: Joseph <jvaikath@redhat.com> * Refactor wildcard expansion logic in item collector and enhance testing - Moved the wildcard expansion logic into a dedicated method, `expandNamespaceWildcards`, improving code organization and readability. - Updated logging to provide detailed insights during the wildcard expansion process. - Introduced comprehensive unit tests for wildcard handling, covering various scenarios and edge cases. - Enhanced the `ShouldExpandWildcards` function to better identify wildcard patterns and validate inputs. These changes improve the maintainability and robustness of the wildcard handling in the backup system. Signed-off-by: Joseph <jvaikath@redhat.com> * Enhance Restore CRD with expanded namespace fields and update logic - Added `ExpandedIncludedNamespaces` and `ExpandedExcludedNamespaces` fields to the `RestoreStatus` struct to support expanded wildcard namespace handling. - Updated the `DeepCopyInto` method to ensure proper copying of the new fields. - Implemented logic in the restore process to expand wildcard patterns for included and excluded namespaces, improving flexibility in namespace selection during restores. - Enhanced logging to provide insights into the expanded namespaces. These changes improve the functionality and maintainability of the restore process, aligning with recent enhancements in wildcard handling. Signed-off-by: Joseph <jvaikath@redhat.com> * Refactor Backup and Restore CRDs to enhance wildcard namespace handling - Renamed fields in `BackupStatus` and `RestoreStatus` from `ExpandedIncludedNamespaces` and `ExpandedExcludedNamespaces` to `IncludeWildcardMatches` and `ExcludeWildcardMatches` for clarity. - Introduced a new field `WildcardResult` to record the final namespaces after applying wildcard logic. - Updated the `DeepCopyInto` methods to accommodate the new field names and ensure proper data handling. - Enhanced comments to reflect the changes and improve documentation clarity. These updates improve the maintainability and clarity of the CRDs, aligning with recent enhancements in wildcard handling. Signed-off-by: Joseph <jvaikath@redhat.com> * Enhance wildcard namespace handling in Backup and Restore processes - Updated `BackupRequest` and `Restore` status structures to include a new field `WildcardResult`, which captures the final list of namespaces after applying wildcard logic. - Renamed existing fields to `IncludeWildcardMatches` and `ExcludeWildcardMatches` for improved clarity. - Enhanced logging to provide detailed insights into the expanded namespaces and final results during backup and restore operations. - Introduced a new utility function `GetWildcardResult` to streamline the selection of namespaces based on include/exclude criteria. These changes improve the clarity and functionality of namespace selection in both backup and restore processes, aligning with recent enhancements in wildcard handling. Signed-off-by: Joseph <jvaikath@redhat.com> * Refactor namespace wildcard expansion logic in restore process - Moved the wildcard expansion logic into a dedicated method, `expandNamespaceWildcards`, improving code organization and readability. - Enhanced error handling and logging to provide detailed insights into the expanded namespaces during the restore operation. - Updated the restore context with expanded namespace patterns and final results, ensuring clarity in the restore status. These changes improve the maintainability and clarity of the restore process, aligning with recent enhancements in wildcard handling. Signed-off-by: Joseph <jvaikath@redhat.com> * Add checks for "*" in exclude Signed-off-by: Joseph <jvaikath@redhat.com> * Rebase Signed-off-by: Joseph <jvaikath@redhat.com> * Create NamespaceIncludesExcludes to get full NS listing for backup w/ Signed-off-by: Scott Seago <sseago@redhat.com> Signed-off-by: Joseph <jvaikath@redhat.com> * Add new NamespaceIncludesExcludes struct Signed-off-by: Joseph <jvaikath@redhat.com> * Move namespace expansion logic Signed-off-by: Joseph <jvaikath@redhat.com> * Update backup status with expansion Signed-off-by: Joseph <jvaikath@redhat.com> * Wildcard status update Signed-off-by: Joseph <jvaikath@redhat.com> * Skip ns check if wildcard expansion Signed-off-by: Joseph <jvaikath@redhat.com> * Move wildcard expansion to getResourceItems Signed-off-by: Joseph <jvaikath@redhat.com> * lint Signed-off-by: Joseph <jvaikath@redhat.com> * Changelog Signed-off-by: Joseph <jvaikath@redhat.com> * linting issues Signed-off-by: Joseph <jvaikath@redhat.com> * Remove wildcard restore to check if tests pass Signed-off-by: Joseph <jvaikath@redhat.com> * Fix namespace mapping test bug from lint fix The previous commit (0a4aabcf4) attempted to fix linting issues by using strings.Builder, but incorrectly wrote commas to a separate builder and concatenated them at the end instead of between namespace mappings. This caused the namespace mapping string to be malformed: Before: ns-1:ns-1-mapped,ns-2:ns-2-mapped Bug: ns-1:ns-1-mappedns-2:ns-2-mapped,, The malformed string was parsed as a single mapping with an invalid namespace name containing a colon, causing Kubernetes to reject it: "ns-1-mappedns-2:ns-2-mapped" is invalid Fix by properly using strings.Builder to construct the mapping string with commas between entries, addressing both the linting concern and the functional bug. Fixes the MultiNamespacesMappingResticTest and MultiNamespacesMappingSnapshotTest failures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Signed-off-by: Joseph <jvaikath@redhat.com> * Fix wildcard namespace expansion edge cases This commit fixes two bugs in the wildcard namespace expansion feature: 1. Empty wildcard results: When a wildcard pattern (e.g., "invalid*") matched no namespaces, the backup would incorrectly back up ALL namespaces instead of backing up nothing. This was because the empty includes list was indistinguishable from "no filter specified". Fix: Added wildcardExpanded flag to NamespaceIncludesExcludes to track when wildcard expansion has occurred. When true and the includes list is empty, ShouldInclude now correctly returns false. 2. Premature namespace filtering: An earlier attempt to fix bug #1 filtered namespaces too early in collectNamespaces, breaking LabelSelector tests where namespaces should be included based on resources within them matching the label selector. Fix: Removed the premature filtering and rely on the existing filterNamespaces call at the end of getAllItems, which correctly handles both wildcard expansion and label selector scenarios. The fixes ensure: - Wildcard patterns matching nothing result in empty backups - Label selectors still work correctly (namespace included if any resource in it matches the selector) - State is preserved across multiple ResolveNamespaceList calls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Signed-off-by: Joseph <jvaikath@redhat.com> * Run wildcard expansion during backup processing Signed-off-by: Joseph <jvaikath@redhat.com> * Lint fix Signed-off-by: Joseph <jvaikath@redhat.com> * Improve coverage Signed-off-by: Joseph <jvaikath@redhat.com> * gofmt fix Signed-off-by: Joseph <jvaikath@redhat.com> * Add wildcard details to describe backup status Signed-off-by: Joseph <jvaikath@redhat.com> * Revert "Remove wildcard restore to check if tests pass" This reverts commit 4e22c2af855b71447762cb0a9fab7e7049f38a5f. Signed-off-by: Joseph <jvaikath@redhat.com> * Add restore describe for wildcard namespaces Revert restore wildcard removal Signed-off-by: Joseph <jvaikath@redhat.com> * Add coverage Signed-off-by: Joseph <jvaikath@redhat.com> * Lint Signed-off-by: Joseph <jvaikath@redhat.com> * Remove unintentional changes Signed-off-by: Joseph <jvaikath@redhat.com> * Remove wildcard status fields and mentionsRemove usage of wildcard fields for backup and restore status. Signed-off-by: Joseph <jvaikath@redhat.com> * Remove status update changelog line Signed-off-by: Joseph <jvaikath@redhat.com> * Rename getNamespaceIncludesExcludes Signed-off-by: Scott Seago <sseago@redhat.com> Signed-off-by: Scott Seago <sseago@redhat.com> * Rewrite brace pattern validation Signed-off-by: Joseph <jvaikath@redhat.com> * Different var for internal loop Signed-off-by: Joseph <jvaikath@redhat.com> --------- Signed-off-by: Joseph <jvaikath@redhat.com> Signed-off-by: Scott Seago <sseago@redhat.com> Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Co-authored-by: Scott Seago <sseago@redhat.com> Co-authored-by: Tiger Kaovilai <tkaovila@redhat.com> Co-authored-by: Claude <noreply@anthropic.com>
1400 lines
45 KiB
Go
1400 lines
45 KiB
Go
/*
|
|
Copyright The Velero Contributors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package collections
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
|
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
"github.com/vmware-tanzu/velero/pkg/builder"
|
|
"github.com/vmware-tanzu/velero/pkg/test"
|
|
)
|
|
|
|
func TestShouldInclude(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
item string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "empty string should include every item",
|
|
item: "foo",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "include * should include every item",
|
|
includes: []string{"*"},
|
|
item: "foo",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "item in includes list should include item",
|
|
includes: []string{"foo", "bar", "baz"},
|
|
item: "foo",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "item not in includes list should not include item",
|
|
includes: []string{"foo", "baz"},
|
|
item: "bar",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "include *, excluded item should not include item",
|
|
includes: []string{"*"},
|
|
excludes: []string{"foo"},
|
|
item: "foo",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "include *, exclude foo, bar should be included",
|
|
includes: []string{"*"},
|
|
excludes: []string{"foo"},
|
|
item: "bar",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "an item both included and excluded should not be included",
|
|
includes: []string{"foo"},
|
|
excludes: []string{"foo"},
|
|
item: "foo",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "wildcard should include item",
|
|
includes: []string{"*.bar"},
|
|
item: "foo.bar",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "wildcard mismatch should not include item",
|
|
includes: []string{"*.bar"},
|
|
item: "bar.foo",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "wildcard exclude should not include item",
|
|
includes: []string{"*"},
|
|
excludes: []string{"*.bar"},
|
|
item: "foo.bar",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "wildcard mismatch should include item",
|
|
includes: []string{"*"},
|
|
excludes: []string{"*.bar"},
|
|
item: "bar.foo",
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
includesExcludes := NewIncludesExcludes().Includes(tc.includes...).Excludes(tc.excludes...)
|
|
|
|
if got := includesExcludes.ShouldInclude((tc.item)); got != tc.want {
|
|
t.Errorf("want %t, got %t", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateIncludesExcludes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
want []error
|
|
}{
|
|
{
|
|
name: "empty includes (everything) is allowed",
|
|
includes: []string{},
|
|
},
|
|
{
|
|
name: "include everything",
|
|
includes: []string{"*"},
|
|
},
|
|
{
|
|
name: "include everything not allowed with other includes",
|
|
includes: []string{"*", "foo"},
|
|
want: []error{errors.New("includes list must either contain '*' only, or a non-empty list of items")},
|
|
},
|
|
{
|
|
name: "exclude everything not allowed",
|
|
includes: []string{"foo"},
|
|
excludes: []string{"*"},
|
|
want: []error{errors.New("excludes list cannot contain '*'")},
|
|
},
|
|
{
|
|
name: "excludes cannot contain items in includes",
|
|
includes: []string{"foo", "bar"},
|
|
excludes: []string{"bar"},
|
|
want: []error{errors.New("excludes list cannot contain an item in the includes list: bar")},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
errs := ValidateIncludesExcludes(tc.includes, tc.excludes)
|
|
|
|
require.Len(t, errs, len(tc.want))
|
|
|
|
for i := 0; i < len(tc.want); i++ {
|
|
assert.Equal(t, tc.want[i].Error(), errs[i].Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIncludeExcludeString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
wantIncludes string
|
|
wantExcludes string
|
|
}{
|
|
{
|
|
name: "unspecified includes/excludes should return '*'/'<none>'",
|
|
includes: nil,
|
|
excludes: nil,
|
|
wantIncludes: "*",
|
|
wantExcludes: "<none>",
|
|
},
|
|
{
|
|
name: "specific resources should result in sorted joined string",
|
|
includes: []string{"foo", "bar"},
|
|
excludes: []string{"baz", "xyz"},
|
|
wantIncludes: "bar, foo",
|
|
wantExcludes: "baz, xyz",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
includesExcludes := NewIncludesExcludes().Includes(tc.includes...).Excludes(tc.excludes...)
|
|
assert.Equal(t, tc.wantIncludes, includesExcludes.IncludesString())
|
|
assert.Equal(t, tc.wantExcludes, includesExcludes.ExcludesString())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateNamespaceIncludesExcludes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty slice doesn't return error",
|
|
includes: []string{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "asterisk by itself is valid",
|
|
includes: []string{"*"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "alphanumeric names with optional dash inside are valid",
|
|
includes: []string{"foobar", "bar-321", "foo123bar"},
|
|
excludes: []string{"123bar", "barfoo", "foo-321", "bar123foo"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "not starting or ending with an alphanumeric character is invalid",
|
|
includes: []string{"-123foo"},
|
|
excludes: []string{"foo321-", "foo321-"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "special characters in name is invalid",
|
|
includes: []string{"foo?", "foo.bar", "bar_321"},
|
|
excludes: []string{"$foo", "foo>bar", "bar=321"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty includes (everything) is valid",
|
|
includes: []string{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty string includes is valid (includes nothing)",
|
|
includes: []string{""},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty string excludes is valid (excludes nothing)",
|
|
excludes: []string{""},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "include everything using asterisk is valid",
|
|
includes: []string{"*"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "excludes can contain wildcard",
|
|
includes: []string{"foo", "bar"},
|
|
excludes: []string{"nginx-ingress-*", "*-bar", "*-ingress-*"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "includes can contain wildcard",
|
|
includes: []string{"*-foo", "kube-*", "*kube*"},
|
|
excludes: []string{"bar"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "include everything not allowed with other includes",
|
|
includes: []string{"*", "foo"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "exclude everything not allowed",
|
|
includes: []string{"foo"},
|
|
excludes: []string{"*"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "excludes cannot contain items in includes",
|
|
includes: []string{"foo", "bar"},
|
|
excludes: []string{"bar"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
errs := ValidateNamespaceIncludesExcludes(tc.includes, tc.excludes)
|
|
|
|
if tc.wantErr && len(errs) == 0 {
|
|
t.Errorf("%s: wanted errors but got none", tc.name)
|
|
}
|
|
|
|
if !tc.wantErr && len(errs) != 0 {
|
|
t.Errorf("%s: wanted no errors but got: %v", tc.name, errs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateScopedIncludesExcludes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
wantErr []error
|
|
}{
|
|
// includes testing
|
|
{
|
|
name: "empty includes is valid",
|
|
includes: []string{},
|
|
wantErr: []error{},
|
|
},
|
|
{
|
|
name: "asterisk includes is valid",
|
|
includes: []string{"*"},
|
|
wantErr: []error{},
|
|
},
|
|
{
|
|
name: "include everything not allowed with other includes",
|
|
includes: []string{"*", "foo"},
|
|
wantErr: []error{errors.New("includes list must either contain '*' only, or a non-empty list of items")},
|
|
},
|
|
// excludes testing
|
|
{
|
|
name: "empty excludes is valid",
|
|
excludes: []string{},
|
|
wantErr: []error{},
|
|
},
|
|
{
|
|
name: "asterisk excludes is valid",
|
|
excludes: []string{"*"},
|
|
wantErr: []error{},
|
|
},
|
|
{
|
|
name: "exclude everything not allowed with other excludes",
|
|
excludes: []string{"*", "foo"},
|
|
wantErr: []error{errors.New("excludes list must either contain '*' only, or a non-empty list of items")},
|
|
},
|
|
// includes and excludes combination testing
|
|
{
|
|
name: "asterisk excludes doesn't work with non-empty includes",
|
|
includes: []string{"foo"},
|
|
excludes: []string{"*"},
|
|
wantErr: []error{errors.New("when exclude is '*', include cannot have value")},
|
|
},
|
|
{
|
|
name: "excludes cannot contain items in includes",
|
|
includes: []string{"foo", "bar"},
|
|
excludes: []string{"bar"},
|
|
wantErr: []error{errors.New("excludes list cannot contain an item in the includes list: bar")},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
errs := ValidateScopedIncludesExcludes(tc.includes, tc.excludes)
|
|
|
|
require.Len(t, errs, len(tc.wantErr))
|
|
|
|
for i := 0; i < len(tc.wantErr); i++ {
|
|
assert.Equal(t, tc.wantErr[i].Error(), errs[i].Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNamespaceScopedShouldInclude(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
namespaceScopedIncludes []string
|
|
namespaceScopedExcludes []string
|
|
item string
|
|
want bool
|
|
apiResources []*test.APIResource
|
|
}{
|
|
{
|
|
name: "empty string should include every item",
|
|
item: "pods",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "include * should include every item",
|
|
namespaceScopedIncludes: []string{"*"},
|
|
item: "pods",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "item in includes list should include item",
|
|
namespaceScopedIncludes: []string{"foo", "bar", "pods"},
|
|
item: "pods",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "item not in includes list should not include item",
|
|
namespaceScopedIncludes: []string{"foo", "baz"},
|
|
item: "pods",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "include *, excluded item should not include item",
|
|
namespaceScopedIncludes: []string{"*"},
|
|
namespaceScopedExcludes: []string{"pods"},
|
|
item: "pods",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "include *, exclude foo, bar should be included",
|
|
namespaceScopedIncludes: []string{"*"},
|
|
namespaceScopedExcludes: []string{"foo"},
|
|
item: "pods",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "an item both included and excluded should not be included",
|
|
namespaceScopedIncludes: []string{"pods"},
|
|
namespaceScopedExcludes: []string{"pods"},
|
|
item: "pods",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard should include item",
|
|
namespaceScopedIncludes: []string{"*s"},
|
|
item: "pods",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard mismatch should not include item",
|
|
namespaceScopedIncludes: []string{"*.bar"},
|
|
item: "pods",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "exclude * should include nothing",
|
|
namespaceScopedExcludes: []string{"*"},
|
|
item: "pods",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard exclude should not include item",
|
|
namespaceScopedIncludes: []string{"*"},
|
|
namespaceScopedExcludes: []string{"*s"},
|
|
item: "pods",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard exclude mismatch should include item",
|
|
namespaceScopedExcludes: []string{"*.bar"},
|
|
item: "pods",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "resource cannot be found by discovery client should not be include",
|
|
item: "pods",
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
discoveryHelper := setupDiscoveryClientWithResources(tc.apiResources)
|
|
logger := logrus.StandardLogger()
|
|
scopeIncludesExcludes := GetScopeResourceIncludesExcludes(discoveryHelper, logger, tc.namespaceScopedIncludes, tc.namespaceScopedExcludes, []string{}, []string{}, *NewNamespaceIncludesExcludes())
|
|
|
|
if got := scopeIncludesExcludes.ShouldInclude((tc.item)); got != tc.want {
|
|
t.Errorf("want %t, got %t", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClusterScopedShouldInclude(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
clusterScopedIncludes []string
|
|
clusterScopedExcludes []string
|
|
nsIncludes []string
|
|
item string
|
|
want bool
|
|
apiResources []*test.APIResource
|
|
}{
|
|
{
|
|
name: "empty string should include nothing",
|
|
nsIncludes: []string{"default"},
|
|
item: "persistentvolumes",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
},
|
|
{
|
|
name: "include * should include every item",
|
|
clusterScopedIncludes: []string{"*"},
|
|
item: "persistentvolumes",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
},
|
|
{
|
|
name: "item in includes list should include item",
|
|
clusterScopedIncludes: []string{"namespaces", "bar", "baz"},
|
|
item: "namespaces",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "item not in includes list should not include item",
|
|
clusterScopedIncludes: []string{"foo", "baz"},
|
|
nsIncludes: []string{"default"},
|
|
item: "persistentvolumes",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
},
|
|
{
|
|
name: "include *, excluded item should not include item",
|
|
clusterScopedIncludes: []string{"*"},
|
|
clusterScopedExcludes: []string{"namespaces"},
|
|
item: "namespaces",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "include *, exclude foo, bar should be included",
|
|
clusterScopedIncludes: []string{"*"},
|
|
clusterScopedExcludes: []string{"foo"},
|
|
item: "namespaces",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "an item both included and excluded should not be included",
|
|
clusterScopedIncludes: []string{"namespaces"},
|
|
clusterScopedExcludes: []string{"namespaces"},
|
|
item: "namespaces",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard should include item",
|
|
clusterScopedIncludes: []string{"*spaces"},
|
|
item: "namespaces",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard mismatch should not include item",
|
|
clusterScopedIncludes: []string{"*.bar"},
|
|
nsIncludes: []string{"default"},
|
|
item: "persistentvolumes",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
},
|
|
{
|
|
name: "exclude * should include nothing",
|
|
clusterScopedExcludes: []string{"*"},
|
|
item: "namespaces",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard exclude should not include item",
|
|
clusterScopedIncludes: []string{"*"},
|
|
clusterScopedExcludes: []string{"*spaces"},
|
|
item: "namespaces",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "wildcard exclude mismatch should not include item",
|
|
clusterScopedExcludes: []string{"*spaces"},
|
|
item: "namespaces",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "resource cannot be found by discovery client should not be include",
|
|
item: "namespaces",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "even namespaces is not in the include list, it should also be involved.",
|
|
clusterScopedIncludes: []string{"foo", "baz"},
|
|
item: "namespaces",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.Namespaces(),
|
|
},
|
|
},
|
|
{
|
|
name: "When all namespaces and namespace scope resources are included, cluster resource should be included.",
|
|
clusterScopedIncludes: []string{},
|
|
nsIncludes: []string{"*"},
|
|
item: "persistentvolumes",
|
|
want: true,
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
},
|
|
{
|
|
name: "When all namespaces and namespace scope resources are included, but cluster resource is excluded.",
|
|
clusterScopedIncludes: []string{},
|
|
clusterScopedExcludes: []string{"persistentvolumes"},
|
|
nsIncludes: []string{"*"},
|
|
item: "persistentvolumes",
|
|
want: false,
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
discoveryHelper := setupDiscoveryClientWithResources(tc.apiResources)
|
|
logger := logrus.StandardLogger()
|
|
nsIncludeExclude := NewNamespaceIncludesExcludes().Includes(tc.nsIncludes...)
|
|
scopeIncludesExcludes := GetScopeResourceIncludesExcludes(discoveryHelper, logger, []string{}, []string{}, tc.clusterScopedIncludes, tc.clusterScopedExcludes, *nsIncludeExclude)
|
|
|
|
if got := scopeIncludesExcludes.ShouldInclude((tc.item)); got != tc.want {
|
|
t.Errorf("want %t, got %t", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetScopedResourceIncludesExcludes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
namespaceScopedIncludes []string
|
|
namespaceScopedExcludes []string
|
|
clusterScopedIncludes []string
|
|
clusterScopedExcludes []string
|
|
expectedNamespaceScopedIncludes []string
|
|
expectedNamespaceScopedExcludes []string
|
|
expectedClusterScopedIncludes []string
|
|
expectedClusterScopedExcludes []string
|
|
apiResources []*test.APIResource
|
|
}{
|
|
{
|
|
name: "only include namespace-scoped resources in IncludesExcludes",
|
|
namespaceScopedIncludes: []string{"deployments.apps", "persistentvolumes"},
|
|
namespaceScopedExcludes: []string{"pods", "persistentvolumes"},
|
|
expectedNamespaceScopedIncludes: []string{"deployments.apps"},
|
|
expectedNamespaceScopedExcludes: []string{"pods"},
|
|
expectedClusterScopedIncludes: []string{},
|
|
expectedClusterScopedExcludes: []string{},
|
|
apiResources: []*test.APIResource{
|
|
test.Deployments(),
|
|
test.PVs(),
|
|
test.Pods(),
|
|
},
|
|
},
|
|
{
|
|
name: "only include cluster-scoped resources in IncludesExcludes",
|
|
clusterScopedIncludes: []string{"deployments.apps", "persistentvolumes"},
|
|
clusterScopedExcludes: []string{"pods", "persistentvolumes"},
|
|
expectedNamespaceScopedIncludes: []string{},
|
|
expectedNamespaceScopedExcludes: []string{},
|
|
expectedClusterScopedIncludes: []string{"persistentvolumes"},
|
|
expectedClusterScopedExcludes: []string{"persistentvolumes"},
|
|
apiResources: []*test.APIResource{
|
|
test.Deployments(),
|
|
test.PVs(),
|
|
test.Pods(),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
logger := logrus.StandardLogger()
|
|
nsIncludeExclude := NewNamespaceIncludesExcludes()
|
|
resources := GetScopeResourceIncludesExcludes(setupDiscoveryClientWithResources(tc.apiResources), logger, tc.namespaceScopedIncludes, tc.namespaceScopedExcludes, tc.clusterScopedIncludes, tc.clusterScopedExcludes, *nsIncludeExclude)
|
|
|
|
assert.Equal(t, tc.expectedNamespaceScopedIncludes, resources.namespaceScopedResourceFilter.includes.List())
|
|
assert.Equal(t, tc.expectedNamespaceScopedExcludes, resources.namespaceScopedResourceFilter.excludes.List())
|
|
assert.Equal(t, tc.expectedClusterScopedIncludes, resources.clusterScopedResourceFilter.includes.List())
|
|
assert.Equal(t, tc.expectedClusterScopedExcludes, resources.clusterScopedResourceFilter.excludes.List())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScopeIncludesExcludes_CombineWithPolicy(t *testing.T) {
|
|
apiResources := []*test.APIResource{test.Deployments(), test.Pods(), test.ConfigMaps(), test.Secrets(), test.PVs(), test.CRDs(), test.ServiceAccounts()}
|
|
tests := []struct {
|
|
name string
|
|
namespaceScopedIncludes []string
|
|
namespaceScopedExcludes []string
|
|
clusterScopedIncludes []string
|
|
clusterScopedExcludes []string
|
|
policy *resourcepolicies.IncludeExcludePolicy
|
|
verify func(sie ScopeIncludesExcludes) bool
|
|
}{
|
|
{
|
|
name: "When policy is nil, the original includes excludes filters should not change",
|
|
namespaceScopedIncludes: []string{"deployments", "pods"},
|
|
namespaceScopedExcludes: []string{"configmaps"},
|
|
clusterScopedIncludes: []string{"persistentvolumes"},
|
|
clusterScopedExcludes: []string{"crds"},
|
|
policy: nil,
|
|
verify: func(sie ScopeIncludesExcludes) bool {
|
|
return sie.clusterScopedResourceFilter.ShouldInclude("persistentvolumes") &&
|
|
!sie.clusterScopedResourceFilter.ShouldInclude("crds") &&
|
|
sie.namespaceScopedResourceFilter.ShouldInclude("deployments") &&
|
|
!sie.namespaceScopedResourceFilter.ShouldInclude("configmaps")
|
|
},
|
|
},
|
|
{
|
|
name: "policy includes excludes should be merged to the original includes excludes when there's no conflict",
|
|
namespaceScopedIncludes: []string{"pods"},
|
|
namespaceScopedExcludes: []string{"configmaps"},
|
|
clusterScopedIncludes: []string{},
|
|
clusterScopedExcludes: []string{"crds"},
|
|
policy: &resourcepolicies.IncludeExcludePolicy{
|
|
IncludedNamespaceScopedResources: []string{"deployments"},
|
|
ExcludedNamespaceScopedResources: []string{"secrets"},
|
|
IncludedClusterScopedResources: []string{"persistentvolumes"},
|
|
ExcludedClusterScopedResources: []string{},
|
|
},
|
|
verify: func(sie ScopeIncludesExcludes) bool {
|
|
return sie.clusterScopedResourceFilter.ShouldInclude("persistentvolumes") &&
|
|
!sie.clusterScopedResourceFilter.ShouldInclude("crds") &&
|
|
sie.namespaceScopedResourceFilter.ShouldInclude("deployments") &&
|
|
!sie.namespaceScopedResourceFilter.ShouldInclude("configmaps") &&
|
|
!sie.namespaceScopedResourceFilter.ShouldInclude("secrets")
|
|
},
|
|
},
|
|
{
|
|
name: "when there are conflicts, the existing includes excludes filters have higher priorities",
|
|
namespaceScopedIncludes: []string{"pods", "deployments"},
|
|
namespaceScopedExcludes: []string{"configmaps"},
|
|
clusterScopedIncludes: []string{"crds"},
|
|
clusterScopedExcludes: []string{"persistentvolumes"},
|
|
policy: &resourcepolicies.IncludeExcludePolicy{
|
|
IncludedNamespaceScopedResources: []string{"configmaps"},
|
|
ExcludedNamespaceScopedResources: []string{"pods", "secrets"},
|
|
IncludedClusterScopedResources: []string{"persistentvolumes"},
|
|
ExcludedClusterScopedResources: []string{"crds"},
|
|
},
|
|
verify: func(sie ScopeIncludesExcludes) bool {
|
|
return sie.clusterScopedResourceFilter.ShouldInclude("crds") &&
|
|
!sie.clusterScopedResourceFilter.ShouldInclude("persistentvolumes") &&
|
|
sie.namespaceScopedResourceFilter.ShouldInclude("pods") &&
|
|
!sie.namespaceScopedResourceFilter.ShouldInclude("configmaps") &&
|
|
!sie.namespaceScopedResourceFilter.ShouldInclude("secrets")
|
|
},
|
|
},
|
|
{
|
|
name: "verify the case when there's '*' in the original include filter",
|
|
namespaceScopedIncludes: []string{"*"},
|
|
namespaceScopedExcludes: []string{},
|
|
clusterScopedIncludes: []string{},
|
|
clusterScopedExcludes: []string{},
|
|
policy: &resourcepolicies.IncludeExcludePolicy{
|
|
IncludedNamespaceScopedResources: []string{"deployments", "pods"},
|
|
ExcludedNamespaceScopedResources: []string{"configmaps", "secrets"},
|
|
IncludedClusterScopedResources: []string{},
|
|
ExcludedClusterScopedResources: []string{},
|
|
},
|
|
verify: func(sie ScopeIncludesExcludes) bool {
|
|
return sie.namespaceScopedResourceFilter.ShouldInclude("configmaps") &&
|
|
sie.namespaceScopedResourceFilter.ShouldInclude("secrets")
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
logger := logrus.StandardLogger()
|
|
discoveryHelper := setupDiscoveryClientWithResources(apiResources)
|
|
sie := GetScopeResourceIncludesExcludes(discoveryHelper, logger, tc.namespaceScopedIncludes, tc.namespaceScopedExcludes, tc.clusterScopedIncludes, tc.clusterScopedExcludes, *NewNamespaceIncludesExcludes())
|
|
sie.CombineWithPolicy(tc.policy)
|
|
assert.True(t, tc.verify(*sie))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUseOldResourceFilters(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
backup velerov1api.Backup
|
|
useOldResourceFilters bool
|
|
}{
|
|
{
|
|
name: "backup with no filters should use new filters",
|
|
backup: *defaultBackup().Result(),
|
|
useOldResourceFilters: false,
|
|
},
|
|
{
|
|
name: "backup with only old filters should use old filters",
|
|
backup: *defaultBackup().IncludeClusterResources(true).Result(),
|
|
useOldResourceFilters: true,
|
|
},
|
|
{
|
|
name: "backup with only new filters should use new filters",
|
|
backup: *defaultBackup().IncludedClusterScopedResources("StorageClass").Result(),
|
|
useOldResourceFilters: false,
|
|
},
|
|
{
|
|
// This case should not happen in Velero workflow, because filter validation not old and new
|
|
// filters used together. So this is only used for UT checking, and I assume old filters
|
|
// have higher priority, because old parameter should be the default one.
|
|
name: "backup with both old and new filters should use old filters",
|
|
backup: *defaultBackup().IncludeClusterResources(true).IncludedClusterScopedResources("StorageClass").Result(),
|
|
useOldResourceFilters: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
assert.Equal(t, test.useOldResourceFilters, UseOldResourceFilters(test.backup.Spec))
|
|
})
|
|
}
|
|
}
|
|
|
|
func defaultBackup() *builder.BackupBuilder {
|
|
return builder.ForBackup(velerov1api.DefaultNamespace, "backup-1").DefaultVolumesToFsBackup(false)
|
|
}
|
|
|
|
func TestShouldExcluded(t *testing.T) {
|
|
falseBoolean := false
|
|
trueBoolean := true
|
|
tests := []struct {
|
|
name string
|
|
clusterIncludes []string
|
|
clusterExcludes []string
|
|
includeClusterResources *bool
|
|
filterType string
|
|
resourceName string
|
|
apiResources []*test.APIResource
|
|
resourceIsExcluded bool
|
|
}{
|
|
{
|
|
name: "GlobalResourceIncludesExcludes: filters are all default",
|
|
clusterIncludes: []string{},
|
|
clusterExcludes: []string{},
|
|
includeClusterResources: nil,
|
|
filterType: "global",
|
|
resourceName: "persistentvolumes",
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
resourceIsExcluded: false,
|
|
},
|
|
{
|
|
name: "GlobalResourceIncludesExcludes: IncludeClusterResources is set to true",
|
|
clusterIncludes: []string{},
|
|
clusterExcludes: []string{},
|
|
includeClusterResources: &trueBoolean,
|
|
filterType: "global",
|
|
resourceName: "persistentvolumes",
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
resourceIsExcluded: false,
|
|
},
|
|
{
|
|
name: "GlobalResourceIncludesExcludes: IncludeClusterResources is set to false",
|
|
clusterIncludes: []string{"persistentvolumes"},
|
|
clusterExcludes: []string{},
|
|
includeClusterResources: &falseBoolean,
|
|
filterType: "global",
|
|
resourceName: "persistentvolumes",
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
resourceIsExcluded: true,
|
|
},
|
|
{
|
|
name: "GlobalResourceIncludesExcludes: resource is in the include list",
|
|
clusterIncludes: []string{"persistentvolumes"},
|
|
clusterExcludes: []string{},
|
|
includeClusterResources: nil,
|
|
filterType: "global",
|
|
resourceName: "persistentvolumes",
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
resourceIsExcluded: false,
|
|
},
|
|
{
|
|
name: "ScopeResourceIncludesExcludes: resource is in the include list",
|
|
clusterIncludes: []string{"persistentvolumes"},
|
|
clusterExcludes: []string{},
|
|
filterType: "scope",
|
|
resourceName: "persistentvolumes",
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
resourceIsExcluded: false,
|
|
},
|
|
{
|
|
name: "ScopeResourceIncludesExcludes: filters are all default",
|
|
clusterIncludes: []string{},
|
|
clusterExcludes: []string{},
|
|
filterType: "scope",
|
|
resourceName: "persistentvolumes",
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
resourceIsExcluded: false,
|
|
},
|
|
{
|
|
name: "ScopeResourceIncludesExcludes: resource is not in the exclude list",
|
|
clusterIncludes: []string{},
|
|
clusterExcludes: []string{"namespaces"},
|
|
filterType: "scope",
|
|
resourceName: "persistentvolumes",
|
|
apiResources: []*test.APIResource{
|
|
test.PVs(),
|
|
},
|
|
resourceIsExcluded: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
logger := logrus.StandardLogger()
|
|
|
|
var ie IncludesExcludesInterface
|
|
if tc.filterType == "global" {
|
|
ie = GetGlobalResourceIncludesExcludes(setupDiscoveryClientWithResources(tc.apiResources), logger, tc.clusterIncludes, tc.clusterExcludes, tc.includeClusterResources, *NewNamespaceIncludesExcludes())
|
|
} else if tc.filterType == "scope" {
|
|
ie = GetScopeResourceIncludesExcludes(setupDiscoveryClientWithResources(tc.apiResources), logger, []string{}, []string{}, tc.clusterIncludes, tc.clusterExcludes, *NewNamespaceIncludesExcludes())
|
|
}
|
|
assert.Equal(t, tc.resourceIsExcluded, ie.ShouldExclude(tc.resourceName))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpandIncludesExcludes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
activeNamespaces []string
|
|
expectedIncludes []string
|
|
expectedExcludes []string
|
|
expectedWildcardExpanded bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "no wildcards - should not expand",
|
|
includes: []string{"default", "kube-system"},
|
|
excludes: []string{"kube-public"},
|
|
activeNamespaces: []string{"default", "kube-system", "kube-public", "test"},
|
|
expectedIncludes: []string{"default", "kube-system"},
|
|
expectedExcludes: []string{"kube-public"},
|
|
expectedWildcardExpanded: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "asterisk alone - should not expand",
|
|
includes: []string{"*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "test"},
|
|
expectedIncludes: []string{"*"},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcard in includes - should expand",
|
|
includes: []string{"kube-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "kube-public", "test"},
|
|
expectedIncludes: []string{"kube-system", "kube-public"},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcard in excludes - should expand",
|
|
includes: []string{"default"},
|
|
excludes: []string{"*-test"},
|
|
activeNamespaces: []string{"default", "kube-test", "app-test", "prod"},
|
|
expectedIncludes: []string{"default"},
|
|
expectedExcludes: []string{"kube-test", "app-test"},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcards in both includes and excludes",
|
|
includes: []string{"kube-*", "app-*"},
|
|
excludes: []string{"*-test"},
|
|
activeNamespaces: []string{"kube-system", "kube-test", "app-prod", "app-test", "default"},
|
|
expectedIncludes: []string{"kube-system", "kube-test", "app-prod", "app-test"},
|
|
expectedExcludes: []string{"kube-test", "app-test"},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "wildcard pattern matches nothing",
|
|
includes: []string{"nonexistent-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system"},
|
|
expectedIncludes: []string{},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "mix of wildcards and non-wildcards in includes",
|
|
includes: []string{"default", "kube-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "kube-public", "test"},
|
|
expectedIncludes: []string{"default", "kube-system", "kube-public"},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "question mark wildcard",
|
|
includes: []string{"test-?"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"test-1", "test-2", "test-10", "default"},
|
|
expectedIncludes: []string{"test-1", "test-2"},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "brace wildcard pattern",
|
|
includes: []string{"app-{prod,dev}"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"app-prod", "app-dev", "app-test", "default"},
|
|
expectedIncludes: []string{"app-prod", "app-dev"},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "empty activeNamespaces with wildcards",
|
|
includes: []string{"kube-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{},
|
|
expectedIncludes: []string{},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid wildcard pattern - consecutive asterisks",
|
|
includes: []string{"kube-**"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default"},
|
|
expectedIncludes: []string{"kube-**"},
|
|
expectedExcludes: []string{},
|
|
expectedWildcardExpanded: false,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
nie := NewNamespaceIncludesExcludes().
|
|
ActiveNamespaces(tc.activeNamespaces).
|
|
Includes(tc.includes...).
|
|
Excludes(tc.excludes...)
|
|
|
|
err := nie.ExpandIncludesExcludes()
|
|
|
|
if tc.expectError {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.expectedWildcardExpanded, nie.IsWildcardExpanded())
|
|
|
|
// Check includes - convert to sets for order-independent comparison
|
|
actualIncludes := sets.NewString(nie.GetIncludes()...)
|
|
expectedIncludes := sets.NewString(tc.expectedIncludes...)
|
|
assert.True(t, actualIncludes.Equal(expectedIncludes),
|
|
"includes mismatch: expected %v, got %v", tc.expectedIncludes, nie.GetIncludes())
|
|
|
|
// Check excludes
|
|
actualExcludes := sets.NewString(nie.GetExcludes()...)
|
|
expectedExcludes := sets.NewString(tc.expectedExcludes...)
|
|
assert.True(t, actualExcludes.Equal(expectedExcludes),
|
|
"excludes mismatch: expected %v, got %v", tc.expectedExcludes, nie.GetExcludes())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveNamespaceList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
activeNamespaces []string
|
|
expectedNamespaces []string
|
|
preExpandWildcards bool
|
|
}{
|
|
{
|
|
name: "no includes/excludes - all active namespaces",
|
|
includes: []string{},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "test"},
|
|
expectedNamespaces: []string{"default", "kube-system", "test"},
|
|
},
|
|
{
|
|
name: "asterisk includes - all active namespaces",
|
|
includes: []string{"*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "test"},
|
|
expectedNamespaces: []string{"default", "kube-system", "test"},
|
|
},
|
|
{
|
|
name: "specific includes - only those namespaces",
|
|
includes: []string{"default", "test"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "test"},
|
|
expectedNamespaces: []string{"default", "test"},
|
|
},
|
|
{
|
|
name: "includes with excludes",
|
|
includes: []string{"*"},
|
|
excludes: []string{"kube-system"},
|
|
activeNamespaces: []string{"default", "kube-system", "test"},
|
|
expectedNamespaces: []string{"default", "test"},
|
|
},
|
|
{
|
|
name: "wildcard includes - expands and filters",
|
|
includes: []string{"kube-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "kube-public", "test"},
|
|
expectedNamespaces: []string{"kube-system", "kube-public"},
|
|
},
|
|
{
|
|
name: "wildcard includes with wildcard excludes",
|
|
includes: []string{"app-*"},
|
|
excludes: []string{"*-test"},
|
|
activeNamespaces: []string{"app-prod", "app-dev", "app-test", "default"},
|
|
expectedNamespaces: []string{"app-prod", "app-dev"},
|
|
},
|
|
{
|
|
name: "wildcard matches nothing - empty result",
|
|
includes: []string{"nonexistent-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system"},
|
|
expectedNamespaces: []string{},
|
|
},
|
|
{
|
|
name: "empty active namespaces",
|
|
includes: []string{"*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{},
|
|
expectedNamespaces: []string{},
|
|
},
|
|
{
|
|
name: "includes namespace not in active namespaces",
|
|
includes: []string{"default", "nonexistent"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "test"},
|
|
expectedNamespaces: []string{"default"},
|
|
},
|
|
{
|
|
name: "excludes all namespaces from includes",
|
|
includes: []string{"default", "test"},
|
|
excludes: []string{"default", "test"},
|
|
activeNamespaces: []string{"default", "test", "prod"},
|
|
expectedNamespaces: []string{},
|
|
},
|
|
{
|
|
name: "pre-expanded wildcards - should not expand again",
|
|
includes: []string{"kube-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "kube-public"},
|
|
expectedNamespaces: []string{"kube-system", "kube-public"},
|
|
preExpandWildcards: true,
|
|
},
|
|
{
|
|
name: "complex wildcard pattern",
|
|
includes: []string{"app-{prod,dev}", "kube-*"},
|
|
excludes: []string{"*-test"},
|
|
activeNamespaces: []string{"app-prod", "app-dev", "app-test", "kube-system", "kube-test", "default"},
|
|
expectedNamespaces: []string{"app-prod", "app-dev", "kube-system"},
|
|
},
|
|
{
|
|
name: "question mark wildcard pattern",
|
|
includes: []string{"ns-?"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"ns-1", "ns-2", "ns-10", "default"},
|
|
expectedNamespaces: []string{"ns-1", "ns-2"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
nie := NewNamespaceIncludesExcludes().
|
|
ActiveNamespaces(tc.activeNamespaces).
|
|
Includes(tc.includes...).
|
|
Excludes(tc.excludes...)
|
|
|
|
// Pre-expand wildcards if requested
|
|
if tc.preExpandWildcards {
|
|
err := nie.ExpandIncludesExcludes()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
namespaces, err := nie.ResolveNamespaceList()
|
|
require.NoError(t, err)
|
|
|
|
// Convert to sets for order-independent comparison
|
|
actualNs := sets.NewString(namespaces...)
|
|
expectedNs := sets.NewString(tc.expectedNamespaces...)
|
|
assert.True(t, actualNs.Equal(expectedNs),
|
|
"namespaces mismatch: expected %v, got %v", tc.expectedNamespaces, namespaces)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveNamespaceListError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
activeNamespaces []string
|
|
}{
|
|
{
|
|
name: "invalid wildcard pattern in includes",
|
|
includes: []string{"kube-**"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default"},
|
|
},
|
|
{
|
|
name: "invalid wildcard pattern in excludes",
|
|
includes: []string{"default"},
|
|
excludes: []string{"test-**"},
|
|
activeNamespaces: []string{"default"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
nie := NewNamespaceIncludesExcludes().
|
|
ActiveNamespaces(tc.activeNamespaces).
|
|
Includes(tc.includes...).
|
|
Excludes(tc.excludes...)
|
|
|
|
_, err := nie.ResolveNamespaceList()
|
|
assert.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNamespaceIncludesExcludesShouldIncludeAfterWildcardExpansion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includes []string
|
|
excludes []string
|
|
activeNamespaces []string
|
|
testNamespace string
|
|
expectedResult bool
|
|
}{
|
|
{
|
|
name: "wildcard expanded to empty includes - should not include anything",
|
|
includes: []string{"nonexistent-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system"},
|
|
testNamespace: "default",
|
|
expectedResult: false,
|
|
},
|
|
{
|
|
name: "wildcard expanded with matches - should include matched namespace",
|
|
includes: []string{"kube-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "kube-public"},
|
|
testNamespace: "kube-system",
|
|
expectedResult: true,
|
|
},
|
|
{
|
|
name: "wildcard expanded with matches - should not include unmatched namespace",
|
|
includes: []string{"kube-*"},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system", "kube-public"},
|
|
testNamespace: "default",
|
|
expectedResult: false,
|
|
},
|
|
{
|
|
name: "no wildcard expansion - empty includes means include all",
|
|
includes: []string{},
|
|
excludes: []string{},
|
|
activeNamespaces: []string{"default", "kube-system"},
|
|
testNamespace: "default",
|
|
expectedResult: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
nie := NewNamespaceIncludesExcludes().
|
|
ActiveNamespaces(tc.activeNamespaces).
|
|
Includes(tc.includes...).
|
|
Excludes(tc.excludes...)
|
|
|
|
err := nie.ExpandIncludesExcludes()
|
|
require.NoError(t, err)
|
|
|
|
result := nie.ShouldInclude(tc.testNamespace)
|
|
assert.Equal(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func setupDiscoveryClientWithResources(APIResources []*test.APIResource) *test.FakeDiscoveryHelper {
|
|
resourcesMap := make(map[schema.GroupVersionResource]schema.GroupVersionResource)
|
|
resourceList := make([]*metav1.APIResourceList, 0)
|
|
|
|
for _, resource := range APIResources {
|
|
gvr := schema.GroupVersionResource{
|
|
Group: resource.Group,
|
|
Version: resource.Version,
|
|
Resource: resource.Name,
|
|
}
|
|
resourcesMap[gvr] = gvr
|
|
|
|
resourceList = append(resourceList,
|
|
&metav1.APIResourceList{
|
|
GroupVersion: gvr.GroupVersion().String(),
|
|
APIResources: []metav1.APIResource{
|
|
{
|
|
Name: resource.Name,
|
|
Kind: resource.Name,
|
|
Namespaced: resource.Namespaced,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
discoveryHelper := test.NewFakeDiscoveryHelper(false, resourcesMap)
|
|
discoveryHelper.ResourceList = resourceList
|
|
return discoveryHelper
|
|
}
|