Merge branch 'release-1.18' into 1.18-9508

This commit is contained in:
Xun Jiang/Bruce Jiang
2026-02-12 11:32:06 +08:00
committed by GitHub
19 changed files with 438 additions and 314 deletions

View File

@@ -0,0 +1 @@
Support all glob wildcard characters in namespace validation

View File

@@ -21,9 +21,11 @@ ENV GO111MODULE=on
ENV GOPROXY=${GOPROXY}
# kubebuilder test bundle is separated from kubebuilder. Need to setup it for CI test.
RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/$(go env GOARCH) && \
mkdir /usr/local/kubebuilder && \
tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz
# Using setup-envtest to download envtest binaries
RUN go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest && \
mkdir -p /usr/local/kubebuilder/bin && \
ENVTEST_ASSETS_DIR=$(setup-envtest use 1.33.0 --bin-dir /usr/local/kubebuilder/bin -p path) && \
cp -r ${ENVTEST_ASSETS_DIR}/* /usr/local/kubebuilder/bin/
RUN wget --quiet https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.2.0/kubebuilder_linux_$(go env GOARCH) && \
mv kubebuilder_linux_$(go env GOARCH) /usr/local/kubebuilder/bin/kubebuilder && \

View File

@@ -666,10 +666,22 @@ func validateNamespaceName(ns string) []error {
return nil
}
// Kubernetes does not allow asterisks in namespaces but Velero uses them as
// wildcards. Replace asterisks with an arbitrary letter to pass Kubernetes
// validation.
tmpNamespace := strings.ReplaceAll(ns, "*", "x")
// Validate the namespace name to ensure it is a valid wildcard pattern
if err := wildcard.ValidateNamespaceName(ns); err != nil {
return []error{err}
}
// Kubernetes does not allow wildcard characters in namespaces but Velero uses them
// for glob patterns. Replace wildcard characters with valid characters to pass
// Kubernetes validation.
tmpNamespace := ns
// Replace glob wildcard characters with valid alphanumeric characters
// Note: Validation of wildcard patterns is handled by the wildcard package.
tmpNamespace = strings.ReplaceAll(tmpNamespace, "*", "x") // matches any sequence
tmpNamespace = strings.ReplaceAll(tmpNamespace, "?", "x") // matches single character
tmpNamespace = strings.ReplaceAll(tmpNamespace, "[", "x") // character class start
tmpNamespace = strings.ReplaceAll(tmpNamespace, "]", "x") // character class end
if errMsgs := validation.ValidateNamespaceName(tmpNamespace, false); errMsgs != nil {
for _, msg := range errMsgs {

View File

@@ -289,6 +289,54 @@ func TestValidateNamespaceIncludesExcludes(t *testing.T) {
excludes: []string{"bar"},
wantErr: true,
},
{
name: "glob characters in includes should not error",
includes: []string{"kube-*", "test-?", "ns-[0-9]"},
excludes: []string{},
wantErr: false,
},
{
name: "glob characters in excludes should not error",
includes: []string{"default"},
excludes: []string{"test-*", "app-?", "ns-[1-5]"},
wantErr: false,
},
{
name: "character class in includes should not error",
includes: []string{"ns-[abc]", "test-[0-9]"},
excludes: []string{},
wantErr: false,
},
{
name: "mixed glob patterns should not error",
includes: []string{"kube-*", "test-?"},
excludes: []string{"*-test", "debug-[0-9]"},
wantErr: false,
},
{
name: "pipe character in includes should error",
includes: []string{"namespace|other"},
excludes: []string{},
wantErr: true,
},
{
name: "parentheses in includes should error",
includes: []string{"namespace(prod)", "test-(dev)"},
excludes: []string{},
wantErr: true,
},
{
name: "exclamation mark in includes should error",
includes: []string{"!namespace", "test!"},
excludes: []string{},
wantErr: true,
},
{
name: "unsupported characters in excludes should error",
includes: []string{"default"},
excludes: []string{"test|prod", "app(staging)"},
wantErr: true,
},
}
for _, tc := range tests {
@@ -1082,16 +1130,6 @@ func TestExpandIncludesExcludes(t *testing.T) {
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-*"},
@@ -1233,13 +1271,6 @@ func TestResolveNamespaceList(t *testing.T) {
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-?"},

View File

@@ -31,70 +31,77 @@ func ShouldExpandWildcards(includes []string, excludes []string) bool {
}
// containsWildcardPattern checks if a pattern contains any wildcard symbols
// Supported patterns: *, ?, [abc], {a,b,c}
// Supported patterns: *, ?, [abc]
// Note: . and + are treated as literal characters (not wildcards)
// Note: ** and consecutive asterisks are NOT supported (will cause validation error)
func containsWildcardPattern(pattern string) bool {
return strings.ContainsAny(pattern, "*?[{")
return strings.ContainsAny(pattern, "*?[")
}
func validateWildcardPatterns(patterns []string) error {
for _, pattern := range patterns {
// Check for invalid regex-only patterns that we don't support
if strings.ContainsAny(pattern, "|()") {
return errors.New("wildcard pattern contains unsupported regex symbols: |, (, )")
}
// Check for consecutive asterisks (2 or more)
if strings.Contains(pattern, "**") {
return errors.New("wildcard pattern contains consecutive asterisks (only single * allowed)")
}
// Check for malformed brace patterns
if err := validateBracePatterns(pattern); err != nil {
if err := ValidateNamespaceName(pattern); err != nil {
return err
}
}
return nil
}
func ValidateNamespaceName(pattern string) error {
// Check for invalid characters that are not supported in glob patterns
if strings.ContainsAny(pattern, "|()!{},") {
return errors.New("wildcard pattern contains unsupported characters: |, (, ), !, {, }, ,")
}
// Check for consecutive asterisks (2 or more)
if strings.Contains(pattern, "**") {
return errors.New("wildcard pattern contains consecutive asterisks (only single * allowed)")
}
// Check for malformed brace patterns
if err := validateBracePatterns(pattern); err != nil {
return err
}
return nil
}
// validateBracePatterns checks for malformed brace patterns like unclosed braces or empty braces
// Also validates bracket patterns [] for character classes
func validateBracePatterns(pattern string) error {
depth := 0
bracketDepth := 0
for i := 0; i < len(pattern); i++ {
if pattern[i] == '{' {
braceStart := i
depth++
if pattern[i] == '[' {
bracketStart := i
bracketDepth++
// Scan ahead to find the matching closing brace and validate content
for j := i + 1; j < len(pattern) && depth > 0; j++ {
if pattern[j] == '{' {
depth++
} else if pattern[j] == '}' {
depth--
if depth == 0 {
// Found matching closing brace - validate content
content := pattern[braceStart+1 : j]
if strings.Trim(content, ", \t") == "" {
return errors.New("wildcard pattern contains empty brace pattern '{}'")
// Scan ahead to find the matching closing bracket and validate content
for j := i + 1; j < len(pattern) && bracketDepth > 0; j++ {
if pattern[j] == ']' {
bracketDepth--
if bracketDepth == 0 {
// Found matching closing bracket - validate content
content := pattern[bracketStart+1 : j]
if content == "" {
return errors.New("wildcard pattern contains empty bracket pattern '[]'")
}
// Skip to the closing brace
// Skip to the closing bracket
i = j
break
}
}
}
// If we exited the loop without finding a match (depth > 0), brace is unclosed
if depth > 0 {
return errors.New("wildcard pattern contains unclosed brace '{'")
// If we exited the loop without finding a match (bracketDepth > 0), bracket is unclosed
if bracketDepth > 0 {
return errors.New("wildcard pattern contains unclosed bracket '['")
}
// i is now positioned at the closing brace; the outer loop will increment it
} else if pattern[i] == '}' {
// Found a closing brace without a matching opening brace
return errors.New("wildcard pattern contains unmatched closing brace '}'")
// i is now positioned at the closing bracket; the outer loop will increment it
} else if pattern[i] == ']' {
// Found a closing bracket without a matching opening bracket
return errors.New("wildcard pattern contains unmatched closing bracket ']'")
}
}

View File

@@ -90,7 +90,7 @@ func TestShouldExpandWildcards(t *testing.T) {
name: "brace alternatives wildcard",
includes: []string{"ns{prod,staging}"},
excludes: []string{},
expected: true, // brace alternatives are considered wildcard
expected: false, // brace alternatives are not supported
},
{
name: "dot is literal - not wildcard",
@@ -237,9 +237,9 @@ func TestExpandWildcards(t *testing.T) {
activeNamespaces: []string{"app-prod", "app-staging", "app-dev", "db-prod"},
includes: []string{"app-{prod,staging}"},
excludes: []string{},
expectedIncludes: []string{"app-prod", "app-staging"}, // {prod,staging} matches either
expectedIncludes: nil,
expectedExcludes: nil,
expectError: false,
expectError: true,
},
{
name: "literal dot and plus patterns",
@@ -259,33 +259,6 @@ func TestExpandWildcards(t *testing.T) {
expectedExcludes: nil,
expectError: true, // |, (, ) are not supported
},
{
name: "unclosed brace patterns should error",
activeNamespaces: []string{"app-prod"},
includes: []string{"app-{prod,staging"},
excludes: []string{},
expectedIncludes: nil,
expectedExcludes: nil,
expectError: true, // unclosed brace
},
{
name: "empty brace patterns should error",
activeNamespaces: []string{"app-prod"},
includes: []string{"app-{}"},
excludes: []string{},
expectedIncludes: nil,
expectedExcludes: nil,
expectError: true, // empty braces
},
{
name: "unmatched closing brace should error",
activeNamespaces: []string{"app-prod"},
includes: []string{"app-prod}"},
excludes: []string{},
expectedIncludes: nil,
expectedExcludes: nil,
expectError: true, // unmatched closing brace
},
}
for _, tt := range tests {
@@ -354,13 +327,6 @@ func TestExpandWildcardsPrivate(t *testing.T) {
expected: []string{}, // returns empty slice, not nil
expectError: false,
},
{
name: "brace patterns work correctly",
patterns: []string{"app-{prod,staging}"},
activeNamespaces: []string{"app-prod", "app-staging", "app-dev", "app-{prod,staging}"},
expected: []string{"app-prod", "app-staging"}, // brace patterns do expand
expectError: false,
},
{
name: "duplicate matches from multiple patterns",
patterns: []string{"app-*", "*-prod"},
@@ -389,20 +355,6 @@ func TestExpandWildcardsPrivate(t *testing.T) {
expected: []string{"nsa", "nsb", "nsc"}, // [a-c] matches a to c
expectError: false,
},
{
name: "negated character class",
patterns: []string{"ns[!abc]"},
activeNamespaces: []string{"nsa", "nsb", "nsc", "nsd", "ns1"},
expected: []string{"nsd", "ns1"}, // [!abc] matches anything except a, b, c
expectError: false,
},
{
name: "brace alternatives",
patterns: []string{"app-{prod,test}"},
activeNamespaces: []string{"app-prod", "app-test", "app-staging", "db-prod"},
expected: []string{"app-prod", "app-test"}, // {prod,test} matches either
expectError: false,
},
{
name: "double asterisk should error",
patterns: []string{"**"},
@@ -410,13 +362,6 @@ func TestExpandWildcardsPrivate(t *testing.T) {
expected: nil,
expectError: true, // ** is not allowed
},
{
name: "literal dot and plus",
patterns: []string{"app.prod", "service+"},
activeNamespaces: []string{"app.prod", "appXprod", "service+", "service"},
expected: []string{"app.prod", "service+"}, // . and + are literal
expectError: false,
},
{
name: "unsupported regex symbols should error",
patterns: []string{"ns(1|2)"},
@@ -468,153 +413,101 @@ func TestValidateBracePatterns(t *testing.T) {
expectError bool
errorMsg string
}{
// Valid patterns
// Valid square bracket patterns
{
name: "valid single brace pattern",
pattern: "app-{prod,staging}",
name: "valid square bracket pattern",
pattern: "ns[abc]",
expectError: false,
},
{
name: "valid brace with single option",
pattern: "app-{prod}",
name: "valid square bracket pattern with range",
pattern: "ns[a-z]",
expectError: false,
},
{
name: "valid brace with three options",
pattern: "app-{prod,staging,dev}",
name: "valid square bracket pattern with numbers",
pattern: "ns[0-9]",
expectError: false,
},
{
name: "valid pattern with text before and after brace",
pattern: "prefix-{a,b}-suffix",
name: "valid square bracket pattern with mixed",
pattern: "ns[a-z0-9]",
expectError: false,
},
{
name: "valid pattern with no braces",
pattern: "app-prod",
name: "valid square bracket pattern with single character",
pattern: "ns[a]",
expectError: false,
},
{
name: "valid pattern with asterisk",
pattern: "app-*",
name: "valid square bracket pattern with text before and after",
pattern: "prefix-[abc]-suffix",
expectError: false,
},
// Unclosed opening brackets
{
name: "valid brace with spaces around content",
pattern: "app-{ prod , staging }",
expectError: false,
name: "unclosed opening bracket at end",
pattern: "ns[abc",
expectError: true,
errorMsg: "unclosed bracket",
},
{
name: "valid brace with numbers",
pattern: "ns-{1,2,3}",
expectError: false,
name: "unclosed opening bracket at start",
pattern: "[abc",
expectError: true,
errorMsg: "unclosed bracket",
},
{
name: "valid brace with hyphens in options",
pattern: "{app-prod,db-staging}",
expectError: false,
name: "unclosed opening bracket in middle",
pattern: "ns[abc-test",
expectError: true,
errorMsg: "unclosed bracket",
},
// Unclosed opening braces
// Unmatched closing brackets
{
name: "unclosed opening brace at end",
pattern: "app-{prod,staging",
name: "unmatched closing bracket at end",
pattern: "ns-abc]",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
{
name: "unclosed opening brace at start",
pattern: "{prod,staging",
name: "unmatched closing bracket at start",
pattern: "]ns-abc",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
{
name: "unclosed opening brace in middle",
pattern: "app-{prod-test",
name: "unmatched closing bracket in middle",
pattern: "ns-]abc",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
{
name: "multiple unclosed braces",
pattern: "app-{prod-{staging",
name: "extra closing bracket after valid pair",
pattern: "ns[abc]]",
expectError: true,
errorMsg: "unclosed brace",
errorMsg: "unmatched closing bracket",
},
// Unmatched closing braces
// Empty bracket patterns
{
name: "unmatched closing brace at end",
pattern: "app-prod}",
name: "completely empty brackets",
pattern: "ns[]",
expectError: true,
errorMsg: "unmatched closing brace",
errorMsg: "empty bracket pattern",
},
{
name: "unmatched closing brace at start",
pattern: "}app-prod",
name: "empty brackets at start",
pattern: "[]ns",
expectError: true,
errorMsg: "unmatched closing brace",
errorMsg: "empty bracket pattern",
},
{
name: "unmatched closing brace in middle",
pattern: "app-}prod",
name: "empty brackets standalone",
pattern: "[]",
expectError: true,
errorMsg: "unmatched closing brace",
},
{
name: "extra closing brace after valid pair",
pattern: "app-{prod,staging}}",
expectError: true,
errorMsg: "unmatched closing brace",
},
// Empty brace patterns
{
name: "completely empty braces",
pattern: "app-{}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with only spaces",
pattern: "app-{ }",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with only comma",
pattern: "app-{,}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with only commas",
pattern: "app-{,,,}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with commas and spaces",
pattern: "app-{ , , }",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "braces with tabs and commas",
pattern: "app-{\t,\t}",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "empty braces at start",
pattern: "{}app-prod",
expectError: true,
errorMsg: "empty brace pattern",
},
{
name: "empty braces standalone",
pattern: "{}",
expectError: true,
errorMsg: "empty brace pattern",
errorMsg: "empty bracket pattern",
},
// Edge cases
@@ -623,58 +516,6 @@ func TestValidateBracePatterns(t *testing.T) {
pattern: "",
expectError: false,
},
{
name: "pattern with only opening brace",
pattern: "{",
expectError: true,
errorMsg: "unclosed brace",
},
{
name: "pattern with only closing brace",
pattern: "}",
expectError: true,
errorMsg: "unmatched closing brace",
},
{
name: "valid brace with special characters inside",
pattern: "app-{prod-1,staging_2,dev.3}",
expectError: false,
},
{
name: "brace with asterisk inside option",
pattern: "app-{prod*,staging}",
expectError: false,
},
{
name: "multiple valid brace patterns",
pattern: "{app,db}-{prod,staging}",
expectError: false,
},
{
name: "brace with single character",
pattern: "app-{a}",
expectError: false,
},
{
name: "brace with trailing comma but has content",
pattern: "app-{prod,staging,}",
expectError: false, // Has content, so it's valid
},
{
name: "brace with leading comma but has content",
pattern: "app-{,prod,staging}",
expectError: false, // Has content, so it's valid
},
{
name: "brace with leading comma but has content",
pattern: "app-{{,prod,staging}",
expectError: true, // unclosed brace
},
{
name: "brace with leading comma but has content",
pattern: "app-{,prod,staging}}",
expectError: true, // unmatched closing brace
},
}
for _, tt := range tests {
@@ -723,20 +564,6 @@ func TestExpandWildcardsEdgeCases(t *testing.T) {
assert.ElementsMatch(t, []string{"ns-1", "ns_2", "ns.3", "ns@4"}, result)
})
t.Run("complex glob combinations", func(t *testing.T) {
activeNamespaces := []string{"app1-prod", "app2-prod", "app1-test", "db-prod", "service"}
result, err := expandWildcards([]string{"app?-{prod,test}"}, activeNamespaces)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"app1-prod", "app2-prod", "app1-test"}, result)
})
t.Run("escaped characters", func(t *testing.T) {
activeNamespaces := []string{"app*", "app-prod", "app?test", "app-test"}
result, err := expandWildcards([]string{"app\\*"}, activeNamespaces)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"app*"}, result)
})
t.Run("mixed literal and wildcard patterns", func(t *testing.T) {
activeNamespaces := []string{"app.prod", "app-prod", "app_prod", "test.ns"}
result, err := expandWildcards([]string{"app.prod", "app?prod"}, activeNamespaces)
@@ -777,12 +604,8 @@ func TestExpandWildcardsEdgeCases(t *testing.T) {
shouldError bool
}{
{"unclosed bracket", "ns[abc", true},
{"unclosed brace", "app-{prod,staging", true},
{"nested unclosed", "ns[a{bc", true},
{"valid bracket", "ns[abc]", false},
{"valid brace", "app-{prod,staging}", false},
{"empty bracket", "ns[]", true}, // empty brackets are invalid
{"empty brace", "app-{}", true}, // empty braces are invalid
}
for _, tt := range tests {

View File

@@ -16,6 +16,8 @@ Backup belongs to the API group version `velero.io/v1`.
Here is a sample `Backup` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -42,11 +44,12 @@ spec:
resourcePolicy:
kind: configmap
name: resource-policy-configmap
# Array of namespaces to include in the backup. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the backup. Accepts glob patterns (*, ?, [abc]).
# Note: '*' alone is reserved for empty fields, which means all namespaces.
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the backup. Optional.
# Array of namespaces to exclude from the backup. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the backup. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -16,6 +16,8 @@ Restore belongs to the API group version `velero.io/v1`.
Here is a sample `Restore` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -45,11 +47,11 @@ spec:
writeSparseFiles: true
# ParallelFilesDownload is the concurrency number setting for restore
parallelFilesDownload: 10
# Array of namespaces to include in the restore. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the restore. Accepts glob patterns (*, ?, [abc]).
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the restore. Optional.
# Array of namespaces to exclude from the restore. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the restore. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -0,0 +1,71 @@
---
title: "Namespace Glob Patterns"
layout: docs
---
When using `--include-namespaces` and `--exclude-namespaces` flags with backup and restore commands, you can use glob patterns to match multiple namespaces.
## Supported Patterns
Velero supports the following glob pattern characters:
- `*` - Matches any sequence of characters
```bash
velero backup create my-backup --include-namespaces "app-*"
# Matches: app-prod, app-staging, app-dev, etc.
```
- `?` - Matches exactly one character
```bash
velero backup create my-backup --include-namespaces "ns?"
# Matches: ns1, ns2, nsa, but NOT ns10
```
- `[abc]` - Matches any single character in the brackets
```bash
velero backup create my-backup --include-namespaces "ns[123]"
# Matches: ns1, ns2, ns3
```
- `[a-z]` - Matches any single character in the range
```bash
velero backup create my-backup --include-namespaces "ns[a-c]"
# Matches: nsa, nsb, nsc
```
## Unsupported Patterns
The following patterns are **not supported** and will cause validation errors:
- `**` - Consecutive asterisks
- `|` - Alternation (regex operator)
- `()` - Grouping (regex operators)
- `!` - Negation
- `{}` - Brace expansion
- `,` - Comma (used in brace expansion)
## Special Cases
- `*` alone means "all namespaces" and is not expanded
- Empty brackets `[]` are invalid
- Unmatched or unclosed brackets will cause validation errors
## Examples
Combine patterns with include and exclude flags:
```bash
# Backup all production namespaces except test
velero backup create prod-backup \
--include-namespaces "*-prod" \
--exclude-namespaces "test-*"
# Backup specific numbered namespaces
velero backup create numbered-backup \
--include-namespaces "app-[0-9]"
# Restore namespaces matching multiple patterns
velero restore create my-restore \
--from-backup my-backup \
--include-namespaces "frontend-*,backend-*"
```

View File

@@ -17,7 +17,11 @@ Wildcard takes precedence when both a wildcard and specific resource are include
### --include-namespaces
Namespaces to include. Default is `*`, all namespaces.
Namespaces to include. Accepts glob patterns (`*`, `?`, `[abc]`). Default is `*`, all namespaces.
See [Namespace Glob Patterns](namespace-glob-patterns) for more details on supported patterns.
Note: `*` alone is reserved for empty fields, which means all namespaces.
* Backup a namespace and it's objects.
@@ -158,7 +162,9 @@ Wildcard excludes are ignored.
### --exclude-namespaces
Namespaces to exclude.
Namespaces to exclude. Accepts glob patterns (`*`, `?`, `[abc]`).
See [Namespace Glob Patterns](namespace-glob-patterns.md) for more details on supported patterns.
* Exclude kube-system from the cluster backup.

View File

@@ -16,6 +16,8 @@ Backup belongs to the API group version `velero.io/v1`.
Here is a sample `Backup` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -42,11 +44,11 @@ spec:
resourcePolicy:
kind: configmap
name: resource-policy-configmap
# Array of namespaces to include in the backup. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the backup. Accepts glob patterns (*, ?, [abc]).
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the backup. Optional.
# Array of namespaces to exclude from the backup. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the backup. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -16,6 +16,8 @@ Restore belongs to the API group version `velero.io/v1`.
Here is a sample `Restore` object with each of the fields documented:
**Note:** Namespace includes/excludes support glob patterns (`*`, `?`, `[abc]`). See [Namespace Glob Patterns](../namespace-glob-patterns) for more details.
```yaml
# Standard Kubernetes API Version declaration. Required.
apiVersion: velero.io/v1
@@ -45,11 +47,11 @@ spec:
writeSparseFiles: true
# ParallelFilesDownload is the concurrency number setting for restore
parallelFilesDownload: 10
# Array of namespaces to include in the restore. If unspecified, all namespaces are included.
# Optional.
# Array of namespaces to include in the restore. Accepts glob patterns (*, ?, [abc]).
# If unspecified, all namespaces are included. Optional.
includedNamespaces:
- '*'
# Array of namespaces to exclude from the restore. Optional.
# Array of namespaces to exclude from the restore. Accepts glob patterns (*, ?, [abc]). Optional.
excludedNamespaces:
- some-namespace
# Array of resources to include in the restore. Resources may be shortcuts (for example 'po' for 'pods')

View File

@@ -0,0 +1,71 @@
---
title: "Namespace Glob Patterns"
layout: docs
---
When using `--include-namespaces` and `--exclude-namespaces` flags with backup and restore commands, you can use glob patterns to match multiple namespaces.
## Supported Patterns
Velero supports the following glob pattern characters:
- `*` - Matches any sequence of characters
```bash
velero backup create my-backup --include-namespaces "app-*"
# Matches: app-prod, app-staging, app-dev, etc.
```
- `?` - Matches exactly one character
```bash
velero backup create my-backup --include-namespaces "ns?"
# Matches: ns1, ns2, nsa, but NOT ns10
```
- `[abc]` - Matches any single character in the brackets
```bash
velero backup create my-backup --include-namespaces "ns[123]"
# Matches: ns1, ns2, ns3
```
- `[a-z]` - Matches any single character in the range
```bash
velero backup create my-backup --include-namespaces "ns[a-c]"
# Matches: nsa, nsb, nsc
```
## Unsupported Patterns
The following patterns are **not supported** and will cause validation errors:
- `**` - Consecutive asterisks
- `|` - Alternation (regex operator)
- `()` - Grouping (regex operators)
- `!` - Negation
- `{}` - Brace expansion
- `,` - Comma (used in brace expansion)
## Special Cases
- `*` alone means "all namespaces" and is not expanded
- Empty brackets `[]` are invalid
- Unmatched or unclosed brackets will cause validation errors
## Examples
Combine patterns with include and exclude flags:
```bash
# Backup all production namespaces except test
velero backup create prod-backup \
--include-namespaces "*-prod" \
--exclude-namespaces "test-*"
# Backup specific numbered namespaces
velero backup create numbered-backup \
--include-namespaces "app-[0-9]"
# Restore namespaces matching multiple patterns
velero restore create my-restore \
--from-backup my-backup \
--include-namespaces "frontend-*,backend-*"
```

View File

@@ -17,7 +17,11 @@ Wildcard takes precedence when both a wildcard and specific resource are include
### --include-namespaces
Namespaces to include. Default is `*`, all namespaces.
Namespaces to include. Accepts glob patterns (`*`, `?`, `[abc]`). Default is `*`, all namespaces.
See [Namespace Glob Patterns](namespace-glob-patterns) for more details on supported patterns.
Note: `*` alone is reserved for empty fields, which means all namespaces.
* Backup a namespace and it's objects.
@@ -158,7 +162,9 @@ Wildcard excludes are ignored.
### --exclude-namespaces
Namespaces to exclude.
Namespaces to exclude. Accepts glob patterns (`*`, `?`, `[abc]`).
See [Namespace Glob Patterns](namespace-glob-patterns) for more details on supported patterns.
* Exclude kube-system from the cluster backup.

View File

@@ -33,6 +33,8 @@ toc:
url: /enable-api-group-versions-feature
- page: Resource filtering
url: /resource-filtering
- page: Namespace glob patterns
url: /namespace-glob-patterns
- page: Backup reference
url: /backup-reference
- page: Backup hooks

View File

@@ -33,6 +33,8 @@ toc:
url: /enable-api-group-versions-feature
- page: Resource filtering
url: /resource-filtering
- page: Namespace glob patterns
url: /namespace-glob-patterns
- page: Backup reference
url: /backup-reference
- page: Backup hooks

View File

@@ -76,7 +76,7 @@ HAS_VSPHERE_PLUGIN ?= false
RESTORE_HELPER_IMAGE ?=
#Released version only
UPGRADE_FROM_VELERO_VERSION ?= v1.15.2,v1.16.2
UPGRADE_FROM_VELERO_VERSION ?= v1.16.2,v1.17.2
# UPGRADE_FROM_VELERO_CLI can has the same format(a list divided by comma) with UPGRADE_FROM_VELERO_VERSION
# Upgrade tests will be executed sequently according to the list by UPGRADE_FROM_VELERO_VERSION
@@ -85,7 +85,7 @@ UPGRADE_FROM_VELERO_VERSION ?= v1.15.2,v1.16.2
# to the end, nil string will be set if UPGRADE_FROM_VELERO_CLI is shorter than UPGRADE_FROM_VELERO_VERSION
UPGRADE_FROM_VELERO_CLI ?=
MIGRATE_FROM_VELERO_VERSION ?= v1.16.2,$(VERSION)
MIGRATE_FROM_VELERO_VERSION ?= v1.17.2,$(VERSION)
MIGRATE_FROM_VELERO_CLI ?=
VELERO_NAMESPACE ?= velero

View File

@@ -99,6 +99,15 @@ var ImagesMatrix = map[string]map[string][]string{
"velero": {"velero/velero:v1.16.2"},
"velero-restore-helper": {"velero/velero:v1.16.2"},
},
"v1.17": {
"aws": {"velero/velero-plugin-for-aws:v1.13.2"},
"azure": {"velero/velero-plugin-for-microsoft-azure:v1.13.2"},
"vsphere": {"vsphereveleroplugin/velero-plugin-for-vsphere:v1.5.2"},
"gcp": {"velero/velero-plugin-for-gcp:v1.13.2"},
"datamover": {"velero/velero-plugin-for-aws:v1.13.2"},
"velero": {"velero/velero:v1.17.2"},
"velero-restore-helper": {"velero/velero:v1.17.2"},
},
"main": {
"aws": {"velero/velero-plugin-for-aws:main"},
"azure": {"velero/velero-plugin-for-microsoft-azure:main"},
@@ -128,16 +137,13 @@ func SetImagesToDefaultValues(config VeleroConfig, version string) (VeleroConfig
ret.Plugins = ""
versionWithoutPatch := "main"
if version != "main" {
versionWithoutPatch = semver.MajorMinor(version)
}
versionWithoutPatch := getVersionWithoutPatch(version)
// Read migration case needs images from the PluginsMatrix map.
images, ok := ImagesMatrix[versionWithoutPatch]
if !ok {
return config, fmt.Errorf("fail to read the images for version %s from the ImagesMatrix",
versionWithoutPatch)
fmt.Printf("Cannot read the images for version %s from the ImagesMatrix. Use the original values.\n", versionWithoutPatch)
return config, nil
}
ret.VeleroImage = images[Velero][0]
@@ -164,6 +170,27 @@ func SetImagesToDefaultValues(config VeleroConfig, version string) (VeleroConfig
return ret, nil
}
func getVersionWithoutPatch(version string) string {
versionWithoutPatch := ""
mainRe := regexp.MustCompile(`^main$`)
releaseRe := regexp.MustCompile(`^release-(\d+)\.(\d+)(-dev)?$`)
switch {
case mainRe.MatchString(version):
versionWithoutPatch = "main"
case releaseRe.MatchString(version):
matches := releaseRe.FindStringSubmatch(version)
versionWithoutPatch = fmt.Sprintf("v%s.%s", matches[1], matches[2])
default:
versionWithoutPatch = semver.MajorMinor(version)
}
fmt.Println("The version is ", versionWithoutPatch)
return versionWithoutPatch
}
func getPluginsByVersion(version string, cloudProvider string, needDataMoverPlugin bool) ([]string, error) {
var cloudMap map[string][]string
arr := strings.Split(version, ".")

View File

@@ -0,0 +1,54 @@
/*
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 velero
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_getVersionWithoutPatch(t *testing.T) {
versionTests := []struct {
caseName string
version string
result string
}{
{
caseName: "main version",
version: "main",
result: "main",
},
{
caseName: "release version",
version: "release-1.18-dev",
result: "v1.18",
},
{
caseName: "tag version",
version: "v1.17.2",
result: "v1.17",
},
}
for _, test := range versionTests {
t.Run(test.caseName, func(t *testing.T) {
res := getVersionWithoutPatch(test.version)
require.Equal(t, test.result, res)
})
}
}