mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-28 10:45:44 +00:00
Implement namespace selection by label in resource policy
Add includedNamespacesByLabel, excludedNamespacesByLabel, and
labelSelectorLogic to IncludeExcludePolicy in the ResourcePolicy
ConfigMap (realizes design in velero-io/velero#9772), letting a backup
select or exclude namespaces by label instead of (or in addition to)
name/wildcard.
The backup controller resolves label selectors against the live
namespace list once per backup, merges the results into
spec.includedNamespaces/excludedNamespaces, then proceeds through the
existing name-based filtering unchanged. A defaulted "*" include list
is replaced by the resolved set; an explicitly-configured include list
(including an explicit "*") is unioned with it instead, and stays
canonical rather than widening. Namespaces matching an exclude
selector are always subtracted from the merged includes, regardless of
how the includes were populated.
Because Velero's namespace-includes/excludes model requires at least
one name (an empty list means "match everything"), a selector that
resolves to zero namespaces is represented with a sentinel glob
pattern ("[-]*") guaranteed to match no real namespace, rather than an
empty list that would silently fall back to including/excluding
everything.
labelSelectorLogic ("AND"/"OR", case-insensitive) controls whether
multiple included/excluded label selectors are combined by
intersection or union; it is validated up front, including inside
ResolveNamespacesByLabel itself, so an invalid value fails fast instead
of silently falling through to OR semantics.
Namespace-selection-by-label and resource-selection-by-label act as
independent axes and do not affect each other, matching the design
discussion in #9772.
Known limitations:
- Selectors are evaluated once per backup against the namespace list
at that point in time; namespaces created or relabeled mid-backup
are not picked up.
- Backup-only for now; restore-side namespace mapping is unaffected.
Testing:
- Unit coverage in internal/resourcepolicies for validation, selector
resolution (including AND/OR logic, case-insensitivity, and
malformed-selector/invalid-logic error paths), and the no-match
sentinel.
- Unit coverage in pkg/controller for the merge logic between resolved
label selections and explicit/defaulted includes and excludes.
- End-to-end coverage in pkg/backup exercising the full backup
pipeline with label-selected namespaces, including the
velero.io/exclude-from-backup hard-exclusion interaction and the
zero-match/fully-excluded sentinel path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d074047b06
commit
40af5efdd0
@@ -354,6 +354,417 @@ func TestPrepareBackupRequest_EmptyIncludedNamespacesNormalizedToWildcard(t *tes
|
||||
assert.Equal(t, []string{"*"}, res.Spec.IncludedNamespaces)
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_IncludedNamespacesByLabel_ReplacesWildcardBaseline verifies that
|
||||
// when includedNamespacesByLabel is configured and BackupSpec.IncludedNamespaces was left
|
||||
// empty (normalized to the ["*"] wildcard), the label-resolved namespace set REPLACES the
|
||||
// wildcard baseline rather than being unioned into it - otherwise "all" unioned with anything
|
||||
// is still "all", defeating the feature.
|
||||
func TestPrepareBackupRequest_IncludedNamespacesByLabel_ReplacesWildcardBaseline(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||
|
||||
policyYAML := `version: v1
|
||||
includeExcludePolicy:
|
||||
includedNamespacesByLabel:
|
||||
- "team=platform"
|
||||
`
|
||||
policyConfigMap := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ns-label-policy", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{"policy": policyYAML},
|
||||
}
|
||||
|
||||
backupLocation := builder.ForBackupStorageLocation("velero", "loc-1").Phase(velerov1api.BackupStorageLocationPhaseAvailable).Result()
|
||||
nsPlatform := builder.ForNamespace("platform-ns").ObjectMeta(builder.WithLabels("team", "platform")).Result()
|
||||
nsOther := builder.ForNamespace("other-ns").ObjectMeta(builder.WithLabels("team", "infra")).Result()
|
||||
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, policyConfigMap, backupLocation, nsPlatform, nsOther)
|
||||
|
||||
apiServer := velerotest.NewAPIServer(t)
|
||||
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &backupReconciler{
|
||||
discoveryHelper: discoveryHelper,
|
||||
kbClient: fakeClient,
|
||||
defaultBackupLocation: backupLocation.Name,
|
||||
clock: &clock.RealClock{},
|
||||
formatFlag: formatFlag,
|
||||
}
|
||||
|
||||
backup := defaultBackup().Result()
|
||||
backup.Spec.IncludedNamespaces = nil
|
||||
backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{Kind: "configmap", Name: "ns-label-policy"}
|
||||
|
||||
res := c.prepareBackupRequest(ctx, backup, logger)
|
||||
defer res.WorkerPool.Stop()
|
||||
|
||||
assert.Empty(t, res.Status.ValidationErrors)
|
||||
assert.Equal(t, []string{"platform-ns"}, res.Spec.IncludedNamespaces)
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_IncludedNamespacesByLabel_UnionsWithExplicitIncludes verifies that
|
||||
// when BackupSpec.IncludedNamespaces already has explicit names, the label-resolved set is
|
||||
// additive (unioned), not a replacement.
|
||||
func TestPrepareBackupRequest_IncludedNamespacesByLabel_UnionsWithExplicitIncludes(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||
|
||||
policyYAML := `version: v1
|
||||
includeExcludePolicy:
|
||||
includedNamespacesByLabel:
|
||||
- "team=platform"
|
||||
`
|
||||
policyConfigMap := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ns-label-policy", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{"policy": policyYAML},
|
||||
}
|
||||
|
||||
backupLocation := builder.ForBackupStorageLocation("velero", "loc-1").Phase(velerov1api.BackupStorageLocationPhaseAvailable).Result()
|
||||
nsPlatform := builder.ForNamespace("platform-ns").ObjectMeta(builder.WithLabels("team", "platform")).Result()
|
||||
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, policyConfigMap, backupLocation, nsPlatform)
|
||||
|
||||
apiServer := velerotest.NewAPIServer(t)
|
||||
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &backupReconciler{
|
||||
discoveryHelper: discoveryHelper,
|
||||
kbClient: fakeClient,
|
||||
defaultBackupLocation: backupLocation.Name,
|
||||
clock: &clock.RealClock{},
|
||||
formatFlag: formatFlag,
|
||||
}
|
||||
|
||||
backup := defaultBackup().IncludedNamespaces("explicit-ns").Result()
|
||||
backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{Kind: "configmap", Name: "ns-label-policy"}
|
||||
|
||||
res := c.prepareBackupRequest(ctx, backup, logger)
|
||||
defer res.WorkerPool.Stop()
|
||||
|
||||
assert.Empty(t, res.Status.ValidationErrors)
|
||||
assert.ElementsMatch(t, []string{"explicit-ns", "platform-ns"}, res.Spec.IncludedNamespaces)
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_IncludedNamespacesByLabel_ZeroMatchesResolvesToNoMatchSentinel
|
||||
// verifies that a configured includedNamespacesByLabel selector matching zero namespaces
|
||||
// resolves to resourcepolicies.NoNamespaceMatchesPattern, not a fall-through to "all
|
||||
// namespaces" - this is deliberate fail-safe behavior, not a bug.
|
||||
func TestPrepareBackupRequest_IncludedNamespacesByLabel_ZeroMatchesResolvesToNoMatchSentinel(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||
|
||||
policyYAML := `version: v1
|
||||
includeExcludePolicy:
|
||||
includedNamespacesByLabel:
|
||||
- "team=nonexistent"
|
||||
`
|
||||
policyConfigMap := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ns-label-policy", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{"policy": policyYAML},
|
||||
}
|
||||
|
||||
backupLocation := builder.ForBackupStorageLocation("velero", "loc-1").Phase(velerov1api.BackupStorageLocationPhaseAvailable).Result()
|
||||
nsOther := builder.ForNamespace("other-ns").ObjectMeta(builder.WithLabels("team", "infra")).Result()
|
||||
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, policyConfigMap, backupLocation, nsOther)
|
||||
|
||||
apiServer := velerotest.NewAPIServer(t)
|
||||
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &backupReconciler{
|
||||
discoveryHelper: discoveryHelper,
|
||||
kbClient: fakeClient,
|
||||
defaultBackupLocation: backupLocation.Name,
|
||||
clock: &clock.RealClock{},
|
||||
formatFlag: formatFlag,
|
||||
}
|
||||
|
||||
backup := defaultBackup().Result()
|
||||
backup.Spec.IncludedNamespaces = nil
|
||||
backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{Kind: "configmap", Name: "ns-label-policy"}
|
||||
|
||||
res := c.prepareBackupRequest(ctx, backup, logger)
|
||||
defer res.WorkerPool.Stop()
|
||||
|
||||
assert.Empty(t, res.Status.ValidationErrors)
|
||||
// Not a plain empty slice - see resourcepolicies.NoNamespaceMatchesPattern's doc comment
|
||||
// for why a bare empty list here would be silently reinterpreted downstream as "include
|
||||
// everything" instead of the intended "include nothing".
|
||||
assert.Equal(t, []string{resourcepolicies.NoNamespaceMatchesPattern}, res.Spec.IncludedNamespaces)
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_IncludedNamespacesByLabel_ExplicitWildcardCanonicalized verifies
|
||||
// that when BackupSpec.IncludedNamespaces was explicitly set to ["*"] (as opposed to left
|
||||
// empty and normalized to ["*"]), includedNamespacesByLabel does not narrow the backup down
|
||||
// to only the label matches - the two must not be conflated (see mergeNamespacesByLabel's doc
|
||||
// comment). The result stays canonicalized to ["*"] rather than widened to ["*", "platform-ns"]:
|
||||
// both are equivalent at match time, but only the former satisfies
|
||||
// collections.ValidateIncludesExcludes' "'*' must be alone in includes" invariant.
|
||||
func TestPrepareBackupRequest_IncludedNamespacesByLabel_ExplicitWildcardCanonicalized(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||
|
||||
policyYAML := `version: v1
|
||||
includeExcludePolicy:
|
||||
includedNamespacesByLabel:
|
||||
- "team=platform"
|
||||
`
|
||||
policyConfigMap := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ns-label-policy", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{"policy": policyYAML},
|
||||
}
|
||||
|
||||
backupLocation := builder.ForBackupStorageLocation("velero", "loc-1").Phase(velerov1api.BackupStorageLocationPhaseAvailable).Result()
|
||||
nsPlatform := builder.ForNamespace("platform-ns").ObjectMeta(builder.WithLabels("team", "platform")).Result()
|
||||
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, policyConfigMap, backupLocation, nsPlatform)
|
||||
|
||||
apiServer := velerotest.NewAPIServer(t)
|
||||
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &backupReconciler{
|
||||
discoveryHelper: discoveryHelper,
|
||||
kbClient: fakeClient,
|
||||
defaultBackupLocation: backupLocation.Name,
|
||||
clock: &clock.RealClock{},
|
||||
formatFlag: formatFlag,
|
||||
}
|
||||
|
||||
backup := defaultBackup().IncludedNamespaces("*").Result()
|
||||
backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{Kind: "configmap", Name: "ns-label-policy"}
|
||||
|
||||
res := c.prepareBackupRequest(ctx, backup, logger)
|
||||
defer res.WorkerPool.Stop()
|
||||
|
||||
assert.Empty(t, res.Status.ValidationErrors)
|
||||
assert.ElementsMatch(t, []string{"*"}, res.Spec.IncludedNamespaces)
|
||||
}
|
||||
|
||||
// TestPrepareBackupRequest_ExcludedNamespacesByLabel_Subtracted verifies excludedNamespacesByLabel
|
||||
// resolves independently and is merged into BackupSpec.ExcludedNamespaces, regardless of whether
|
||||
// includedNamespacesByLabel is configured.
|
||||
func TestPrepareBackupRequest_ExcludedNamespacesByLabel_Subtracted(t *testing.T) {
|
||||
formatFlag := logging.FormatText
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, formatFlag)
|
||||
|
||||
policyYAML := `version: v1
|
||||
includeExcludePolicy:
|
||||
excludedNamespacesByLabel:
|
||||
- "confidential=true"
|
||||
`
|
||||
policyConfigMap := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "ns-label-policy", Namespace: velerov1api.DefaultNamespace},
|
||||
Data: map[string]string{"policy": policyYAML},
|
||||
}
|
||||
|
||||
backupLocation := builder.ForBackupStorageLocation("velero", "loc-1").Phase(velerov1api.BackupStorageLocationPhaseAvailable).Result()
|
||||
nsConfidential := builder.ForNamespace("secret-ns").ObjectMeta(builder.WithLabels("confidential", "true")).Result()
|
||||
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, policyConfigMap, backupLocation, nsConfidential)
|
||||
|
||||
apiServer := velerotest.NewAPIServer(t)
|
||||
discoveryHelper, err := discovery.NewHelper(apiServer.DiscoveryClient, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
c := &backupReconciler{
|
||||
discoveryHelper: discoveryHelper,
|
||||
kbClient: fakeClient,
|
||||
defaultBackupLocation: backupLocation.Name,
|
||||
clock: &clock.RealClock{},
|
||||
formatFlag: formatFlag,
|
||||
}
|
||||
|
||||
backup := defaultBackup().Result()
|
||||
backup.Spec.IncludedNamespaces = nil
|
||||
backup.Spec.ResourcePolicy = &corev1api.TypedLocalObjectReference{Kind: "configmap", Name: "ns-label-policy"}
|
||||
|
||||
res := c.prepareBackupRequest(ctx, backup, logger)
|
||||
defer res.WorkerPool.Stop()
|
||||
|
||||
assert.Empty(t, res.Status.ValidationErrors)
|
||||
// includedNamespacesByLabel not configured, so baseline stays the wildcard.
|
||||
assert.Equal(t, []string{"*"}, res.Spec.IncludedNamespaces)
|
||||
assert.Equal(t, []string{"secret-ns"}, res.Spec.ExcludedNamespaces)
|
||||
}
|
||||
|
||||
// TestMergeNamespacesByLabel exercises the union/replacement decision directly, without
|
||||
// the full prepareBackupRequest scaffold (fake client, discovery helper, resource policy
|
||||
// ConfigMap, etc.) - see mergeNamespacesByLabel's doc comment for the precedence rules.
|
||||
func TestMergeNamespacesByLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
includedNamespaces []string
|
||||
excludedNamespaces []string
|
||||
labelIncludeActive bool
|
||||
includedNamespacesWereDefaulted bool
|
||||
resolvedIncluded []string
|
||||
resolvedExcluded []string
|
||||
wantIncluded []string
|
||||
wantExcluded []string
|
||||
}{
|
||||
{
|
||||
name: "defaulted wildcard baseline is replaced",
|
||||
includedNamespaces: []string{"*"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: true,
|
||||
resolvedIncluded: []string{"platform-ns"},
|
||||
wantIncluded: []string{"platform-ns"},
|
||||
wantExcluded: nil,
|
||||
},
|
||||
{
|
||||
name: "explicit includes union additively",
|
||||
includedNamespaces: []string{"explicit-ns"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: false,
|
||||
resolvedIncluded: []string{"platform-ns"},
|
||||
wantIncluded: []string{"explicit-ns", "platform-ns"},
|
||||
wantExcluded: nil,
|
||||
},
|
||||
{
|
||||
// Explicit includes can themselves be a non-"*" wildcard glob (e.g. from
|
||||
// --include-namespaces 'app-*'), not just concrete names - the union branch must
|
||||
// pass that through unchanged alongside the resolved concrete names; downstream
|
||||
// wildcard.ShouldExpandWildcards still expands it normally since it isn't the bare
|
||||
// "*" special case.
|
||||
name: "explicit glob-pattern include unions with resolved names unchanged",
|
||||
includedNamespaces: []string{"app-*"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: false,
|
||||
resolvedIncluded: []string{"platform-ns"},
|
||||
wantIncluded: []string{"app-*", "platform-ns"},
|
||||
wantExcluded: nil,
|
||||
},
|
||||
{
|
||||
// A bare empty []string here would be interpreted downstream by
|
||||
// wildcard.ShouldExpandWildcards as "match everything" (its own documented
|
||||
// behavior), silently defeating the fail-safe. Must come back as
|
||||
// resourcepolicies.NoNamespaceMatchesPattern instead - see
|
||||
// mergeNamespacesByLabel's doc comment.
|
||||
name: "defaulted wildcard matching zero namespaces resolves to the no-match sentinel, not empty",
|
||||
includedNamespaces: []string{"*"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: true,
|
||||
resolvedIncluded: nil,
|
||||
wantIncluded: []string{resourcepolicies.NoNamespaceMatchesPattern},
|
||||
wantExcluded: nil,
|
||||
},
|
||||
{
|
||||
// The code must not re-derive "was this defaulted" by checking
|
||||
// includedNamespaces == ["*"], since an explicitly-configured wildcard looks
|
||||
// identical to the normalized default by this point. An explicit ["*"] must stay
|
||||
// "everything", never narrow to just the label matches - canonicalized back to
|
||||
// ["*"] rather than widened to ["*", "platform-ns"], since the latter is
|
||||
// semantically identical at match time but would violate
|
||||
// collections.ValidateIncludesExcludes' "'*' must be alone in includes" invariant.
|
||||
name: "explicitly-configured wildcard canonicalizes to itself instead of widening",
|
||||
includedNamespaces: []string{"*"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: false,
|
||||
resolvedIncluded: []string{"platform-ns"},
|
||||
wantIncluded: []string{"*"},
|
||||
wantExcluded: nil,
|
||||
},
|
||||
{
|
||||
name: "explicitly-configured wildcard stays everything even on zero matches",
|
||||
includedNamespaces: []string{"*"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: false,
|
||||
resolvedIncluded: nil,
|
||||
wantIncluded: []string{"*"},
|
||||
wantExcluded: nil,
|
||||
},
|
||||
{
|
||||
name: "not labelIncludeActive leaves includedNamespaces untouched",
|
||||
includedNamespaces: []string{"*"},
|
||||
labelIncludeActive: false,
|
||||
includedNamespacesWereDefaulted: true,
|
||||
resolvedIncluded: []string{"platform-ns"}, // should be ignored
|
||||
wantIncluded: []string{"*"},
|
||||
wantExcluded: nil,
|
||||
},
|
||||
{
|
||||
name: "resolvedExcluded unions in regardless of labelIncludeActive",
|
||||
includedNamespaces: []string{"*"},
|
||||
excludedNamespaces: []string{"legacy-ns"},
|
||||
labelIncludeActive: false,
|
||||
includedNamespacesWereDefaulted: true,
|
||||
resolvedExcluded: []string{"secret-ns"},
|
||||
wantIncluded: []string{"*"},
|
||||
wantExcluded: []string{"legacy-ns", "secret-ns"},
|
||||
},
|
||||
{
|
||||
name: "empty resolvedExcluded leaves excludedNamespaces untouched",
|
||||
includedNamespaces: []string{"*"},
|
||||
excludedNamespaces: []string{"legacy-ns"},
|
||||
labelIncludeActive: false,
|
||||
includedNamespacesWereDefaulted: true,
|
||||
resolvedExcluded: nil,
|
||||
wantIncluded: []string{"*"},
|
||||
wantExcluded: []string{"legacy-ns"},
|
||||
},
|
||||
{
|
||||
// A resolved-include name overlapping an already-excluded name violates
|
||||
// collections.ValidateIncludesExcludes' "excludes list cannot contain an item in
|
||||
// the includes list" invariant if the merged result were ever re-validated.
|
||||
// Exclusion already wins at match time regardless (IncludesExcludes.ShouldInclude
|
||||
// checks excludes first), so dropping the overlap from mergedIncluded changes only
|
||||
// the returned representation, not resolved backup behavior.
|
||||
name: "a resolved include overlapping an existing exclude is dropped from the merged includes",
|
||||
includedNamespaces: []string{"explicit-ns"},
|
||||
excludedNamespaces: []string{"both-ns"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: false,
|
||||
resolvedIncluded: []string{"both-ns", "platform-ns"},
|
||||
wantIncluded: []string{"explicit-ns", "platform-ns"},
|
||||
wantExcluded: []string{"both-ns"},
|
||||
},
|
||||
{
|
||||
// The exclude-subtraction step itself can produce a bare empty []string when
|
||||
// every explicit include is also excluded - the same "empty means include
|
||||
// everything" hazard the zero-match sentinel exists for, reached through a
|
||||
// different path.
|
||||
name: "an explicit include fully removed by exclusion resolves to the no-match sentinel, not empty",
|
||||
includedNamespaces: []string{"explicit-ns"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: false,
|
||||
resolvedExcluded: []string{"explicit-ns"},
|
||||
wantIncluded: []string{resourcepolicies.NoNamespaceMatchesPattern},
|
||||
wantExcluded: []string{"explicit-ns"},
|
||||
},
|
||||
{
|
||||
// Same hazard, but for a defaulted-wildcard-replaced resolved include (rather
|
||||
// than an explicit one) that a same-namespace excludedNamespacesByLabel match
|
||||
// fully removes.
|
||||
name: "a resolved include fully removed by exclusion resolves to the no-match sentinel, not empty",
|
||||
includedNamespaces: []string{"*"},
|
||||
labelIncludeActive: true,
|
||||
includedNamespacesWereDefaulted: true,
|
||||
resolvedIncluded: []string{"both-ns"},
|
||||
resolvedExcluded: []string{"both-ns"},
|
||||
wantIncluded: []string{resourcepolicies.NoNamespaceMatchesPattern},
|
||||
wantExcluded: []string{"both-ns"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotIncluded, gotExcluded := mergeNamespacesByLabel(
|
||||
tc.includedNamespaces,
|
||||
tc.excludedNamespaces,
|
||||
tc.labelIncludeActive,
|
||||
tc.includedNamespacesWereDefaulted,
|
||||
tc.resolvedIncluded,
|
||||
tc.resolvedExcluded,
|
||||
)
|
||||
assert.ElementsMatch(t, tc.wantIncluded, gotIncluded)
|
||||
assert.ElementsMatch(t, tc.wantExcluded, gotExcluded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_prepareBackupRequest_BackupStorageLocation(t *testing.T) {
|
||||
var (
|
||||
defaultBackupTTL = metav1.Duration{Duration: 24 * 30 * time.Hour}
|
||||
|
||||
Reference in New Issue
Block a user