mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-19 22:44:21 +00:00
[Cherry-pick]Set based label selectors for 1.18 (#10072)
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
e2e-test-kind.yaml / extract (push) Successful in 12s
Run the E2E test on kind / get-go-version (push) Successful in 14s
push.yml / extract (push) Successful in 13s
Main CI / get-go-version (push) Successful in 14s
Run the E2E test on kind / build (push) Failing after 28s
Run the E2E test on kind / run-e2e-test (push) Skipped
Main CI / Build (push) Failing after 30s
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
e2e-test-kind.yaml / extract (push) Successful in 12s
Run the E2E test on kind / get-go-version (push) Successful in 14s
push.yml / extract (push) Successful in 13s
Main CI / get-go-version (push) Successful in 14s
Run the E2E test on kind / build (push) Failing after 28s
Run the E2E test on kind / run-e2e-test (push) Skipped
Main CI / Build (push) Failing after 30s
* add documentation for backup filters (#9967) * add documentation for backup filters Add user guide for fine grained backup filters with examples from easy to advanced. Signed-off-by: Adam Zhang <adam.zhang@broadcom.com> * address review comments - enhanced example 3, explain how each item got excluded - enhanced example 8, explain the exact match rule, and how the ordering affecting namespace that has multiple match patterns - cross link to restore side design - fix the error msg to be consistent with implemenation Signed-off-by: Adam Zhang <adam.zhang@broadcom.com> --------- Signed-off-by: Adam Zhang <adam.zhang@broadcom.com> * update backup filters example 14 update the excludeNames to match example 3 for better consistency. Signed-off-by: Adam Zhang <adam.zhang@broadcom.com> * Support set-based filter label selectors (#10064) * Support set-based filter label selectors Use matchLabels/matchExpressions in fine-grained filters. Signed-off-by: Adam Zhang <adam.zhang@broadcom.com> * omit the details of resource policy for cli The reason to not resolve and display CLI is because it may go out of sync, we want to avoid display it to mislead users. We may consider to cpature those information and display it in later release. Signed-off-by: Adam Zhang <adam.zhang@broadcom.com> * update change log Signed-off-by: Adam Zhang <adam.zhang@broadcom.com> --------- Signed-off-by: Adam Zhang <adam.zhang@broadcom.com>
This commit is contained in:
@@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -31,7 +30,6 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fatih/color"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -94,9 +92,6 @@ func DescribeBackup(
|
||||
if backup.Spec.ResourcePolicy != nil {
|
||||
d.Println()
|
||||
DescribeResourcePolicies(d, backup.Spec.ResourcePolicy)
|
||||
|
||||
// Display fine-grained filter policies if they exist
|
||||
DescribeFineGrainedFilterPolicies(ctx, kbClient, d, backup)
|
||||
}
|
||||
|
||||
DescribeGlobalVolumePolicy(d, backup)
|
||||
@@ -151,119 +146,6 @@ func DescribeGlobalVolumePolicy(d *Describer, backup *velerov1api.Backup) {
|
||||
d.Printf("\tName:\t%s\n", name)
|
||||
}
|
||||
|
||||
// DescribeFineGrainedFilterPolicies describes cluster-scoped and namespace-scoped filter policies if present
|
||||
func DescribeFineGrainedFilterPolicies(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup) {
|
||||
if backup.Spec.ResourcePolicy == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a discard logger for the resource policies function since this is CLI output context
|
||||
discardLogger := logrus.New()
|
||||
discardLogger.Out = io.Discard
|
||||
|
||||
resourcePolicies, err := resourcepolicies.GetResourcePoliciesFromBackup(*backup, kbClient, discardLogger)
|
||||
if err != nil {
|
||||
// Don't fail the describe if we can't read policies, just skip
|
||||
return
|
||||
}
|
||||
|
||||
if resourcePolicies == nil {
|
||||
return
|
||||
}
|
||||
|
||||
clusterScopedFilterPolicy := resourcePolicies.GetClusterScopedFilterPolicy()
|
||||
if clusterScopedFilterPolicy != nil {
|
||||
d.Printf("\nCluster Scoped Filter Policy:\n")
|
||||
d.Printf(" Resource Filters:\n")
|
||||
for _, rf := range clusterScopedFilterPolicy.ResourceFilters {
|
||||
kindsStr := strings.Join(rf.Kinds, ", ")
|
||||
d.Printf(" %s:\n", kindsStr)
|
||||
|
||||
// Label selector
|
||||
if len(rf.LabelSelector) > 0 {
|
||||
selectorStr := formatLabelMap(rf.LabelSelector)
|
||||
d.Printf(" Label selector: %s\n", selectorStr)
|
||||
} else if len(rf.OrLabelSelectors) > 0 {
|
||||
var orStrs []string
|
||||
for _, ols := range rf.OrLabelSelectors {
|
||||
orStrs = append(orStrs, formatLabelMap(ols))
|
||||
}
|
||||
d.Printf(" OR label selectors: [%s]\n", strings.Join(orStrs, ", "))
|
||||
} else {
|
||||
d.Printf(" Label selector: <none>\n")
|
||||
}
|
||||
|
||||
// Name patterns
|
||||
if len(rf.Names) > 0 {
|
||||
d.Printf(" Included names: [%s]\n", strings.Join(rf.Names, ", "))
|
||||
} else {
|
||||
d.Printf(" Included names: <none>\n")
|
||||
}
|
||||
|
||||
if len(rf.ExcludedNames) > 0 {
|
||||
d.Printf(" Excluded names: [%s]\n", strings.Join(rf.ExcludedNames, ", "))
|
||||
} else {
|
||||
d.Printf(" Excluded names: <none>\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nfPolicies := resourcePolicies.GetNamespacedFilterPolicies()
|
||||
if len(nfPolicies) > 0 {
|
||||
d.Printf("\nNamespace-Scoped Filter Policies:\n")
|
||||
for _, policy := range nfPolicies {
|
||||
for _, ns := range policy.Namespaces {
|
||||
d.Printf(" %s:\n", ns)
|
||||
d.Printf(" Resource Filters:\n")
|
||||
for _, rf := range policy.ResourceFilters {
|
||||
var kindsStr string
|
||||
if rf.IsCatchAll() {
|
||||
kindsStr = "<catch-all> (all other kinds)"
|
||||
} else {
|
||||
kindsStr = strings.Join(rf.Kinds, ", ")
|
||||
}
|
||||
d.Printf(" %s:\n", kindsStr)
|
||||
|
||||
// Label selector
|
||||
if len(rf.LabelSelector) > 0 {
|
||||
selectorStr := formatLabelMap(rf.LabelSelector)
|
||||
d.Printf(" Label selector: %s\n", selectorStr)
|
||||
} else if len(rf.OrLabelSelectors) > 0 {
|
||||
var orStrs []string
|
||||
for _, ols := range rf.OrLabelSelectors {
|
||||
orStrs = append(orStrs, formatLabelMap(ols))
|
||||
}
|
||||
d.Printf(" OR label selectors: [%s]\n", strings.Join(orStrs, ", "))
|
||||
} else {
|
||||
d.Printf(" Label selector: <none>\n")
|
||||
}
|
||||
|
||||
// Name patterns
|
||||
if len(rf.Names) > 0 {
|
||||
d.Printf(" Included names: [%s]\n", strings.Join(rf.Names, ", "))
|
||||
} else {
|
||||
d.Printf(" Included names: <none>\n")
|
||||
}
|
||||
|
||||
if len(rf.ExcludedNames) > 0 {
|
||||
d.Printf(" Excluded names: [%s]\n", strings.Join(rf.ExcludedNames, ", "))
|
||||
} else {
|
||||
d.Printf(" Excluded names: <none>\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatLabelMap(labelMap map[string]string) string {
|
||||
var pairs []string
|
||||
for k, v := range labelMap {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
// DescribeUploaderConfigForBackup describes uploader config in human-readable format
|
||||
func DescribeUploaderConfigForBackup(d *Describer, spec velerov1api.BackupSpec) {
|
||||
d.Printf("Uploader config:\n")
|
||||
|
||||
@@ -18,7 +18,6 @@ package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
@@ -26,8 +25,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
@@ -897,85 +894,3 @@ func TestDescribeBackupItemOperation(t *testing.T) {
|
||||
d.out.Flush()
|
||||
assert.Equal(t, expected, d.buf.String())
|
||||
}
|
||||
|
||||
func TestDescribeFineGrainedFilterPolicies(t *testing.T) {
|
||||
yamlData := `
|
||||
version: v1
|
||||
clusterScopedFilterPolicy:
|
||||
resourceFilters:
|
||||
- kinds: ["StorageClass"]
|
||||
labelSelector: {"app": "velero"}
|
||||
- kinds: ["ClusterRole"]
|
||||
orLabelSelectors:
|
||||
- {"app": "velero"}
|
||||
- {"app": "test"}
|
||||
names: ["role1"]
|
||||
excludedNames: ["role2"]
|
||||
namespacedFilterPolicies:
|
||||
- namespaces: ["ns1", "ns2"]
|
||||
resourceFilters:
|
||||
- kinds: ["Pod", "ConfigMap"]
|
||||
labelSelector: {"app": "velero"}
|
||||
- kinds: ["*"]
|
||||
`
|
||||
cm := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-policy",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"policy.yaml": yamlData,
|
||||
},
|
||||
}
|
||||
|
||||
client := fake.NewClientBuilder().WithRuntimeObjects(cm).Build()
|
||||
|
||||
backup := builder.ForBackup("velero", "test-backup").
|
||||
ResourcePolicies("test-policy").Result()
|
||||
|
||||
d := &Describer{
|
||||
Prefix: "",
|
||||
out: &tabwriter.Writer{},
|
||||
buf: &bytes.Buffer{},
|
||||
}
|
||||
d.out.Init(d.buf, 0, 8, 2, ' ', 0)
|
||||
|
||||
DescribeFineGrainedFilterPolicies(context.Background(), client, d, backup)
|
||||
d.out.Flush()
|
||||
|
||||
expected := `
|
||||
Cluster Scoped Filter Policy:
|
||||
Resource Filters:
|
||||
StorageClass:
|
||||
Label selector: app=velero
|
||||
Included names: <none>
|
||||
Excluded names: <none>
|
||||
ClusterRole:
|
||||
OR label selectors: [app=velero, app=test]
|
||||
Included names: [role1]
|
||||
Excluded names: [role2]
|
||||
|
||||
Namespace-Scoped Filter Policies:
|
||||
ns1:
|
||||
Resource Filters:
|
||||
Pod, ConfigMap:
|
||||
Label selector: app=velero
|
||||
Included names: <none>
|
||||
Excluded names: <none>
|
||||
<catch-all> (all other kinds):
|
||||
Label selector: <none>
|
||||
Included names: <none>
|
||||
Excluded names: <none>
|
||||
ns2:
|
||||
Resource Filters:
|
||||
Pod, ConfigMap:
|
||||
Label selector: app=velero
|
||||
Included names: <none>
|
||||
Excluded names: <none>
|
||||
<catch-all> (all other kinds):
|
||||
Label selector: <none>
|
||||
Included names: <none>
|
||||
Excluded names: <none>
|
||||
`
|
||||
assert.Equal(t, expected, d.buf.String())
|
||||
}
|
||||
|
||||
@@ -21,10 +21,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
@@ -57,7 +55,6 @@ func DescribeBackupInSF(
|
||||
|
||||
if backup.Spec.ResourcePolicy != nil {
|
||||
DescribeResourcePoliciesInSF(d, backup.Spec.ResourcePolicy)
|
||||
DescribeFineGrainedFilterPoliciesInSF(ctx, kbClient, d, backup)
|
||||
}
|
||||
|
||||
DescribeGlobalVolumePolicyInSF(d, backup)
|
||||
@@ -228,88 +225,6 @@ func DescribeBackupSpecInSF(d *StructuredDescriber, spec velerov1api.BackupSpec)
|
||||
d.Describe("spec", backupSpecInfo)
|
||||
}
|
||||
|
||||
// DescribeFineGrainedFilterPoliciesInSF adds the clusterScopedFilterPolicy
|
||||
// and namespacedFilterPolicies sections to the structured describer output when present
|
||||
// in the ResourcePolicy ConfigMap referenced by the backup.
|
||||
func DescribeFineGrainedFilterPoliciesInSF(ctx context.Context, kbClient kbclient.Client, d *StructuredDescriber, backup *velerov1api.Backup) {
|
||||
if backup.Spec.ResourcePolicy == nil {
|
||||
return
|
||||
}
|
||||
|
||||
discardLogger := logrus.New()
|
||||
discardLogger.Out = io.Discard
|
||||
|
||||
resPolicies, err := resourcepolicies.GetResourcePoliciesFromBackup(*backup, kbClient, discardLogger)
|
||||
if err != nil || resPolicies == nil {
|
||||
return
|
||||
}
|
||||
|
||||
clusterScopedFilterPolicy := resPolicies.GetClusterScopedFilterPolicy()
|
||||
if clusterScopedFilterPolicy != nil {
|
||||
var clusterScopedFilters []map[string]any
|
||||
for _, rf := range clusterScopedFilterPolicy.ResourceFilters {
|
||||
entry := map[string]any{
|
||||
"kinds": rf.Kinds,
|
||||
}
|
||||
if len(rf.LabelSelector) > 0 {
|
||||
entry["labelSelector"] = rf.LabelSelector
|
||||
}
|
||||
if len(rf.OrLabelSelectors) > 0 {
|
||||
entry["orLabelSelectors"] = rf.OrLabelSelectors
|
||||
}
|
||||
if len(rf.Names) > 0 {
|
||||
entry["names"] = rf.Names
|
||||
}
|
||||
if len(rf.ExcludedNames) > 0 {
|
||||
entry["excludedNames"] = rf.ExcludedNames
|
||||
}
|
||||
clusterScopedFilters = append(clusterScopedFilters, entry)
|
||||
}
|
||||
d.Describe("clusterScopedFilterPolicy", map[string]any{
|
||||
"resourceFilters": clusterScopedFilters,
|
||||
})
|
||||
}
|
||||
|
||||
nfPolicies := resPolicies.GetNamespacedFilterPolicies()
|
||||
if len(nfPolicies) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var structuredPolicies []map[string]any
|
||||
for _, policy := range nfPolicies {
|
||||
for _, ns := range policy.Namespaces {
|
||||
var rfEntries []map[string]any
|
||||
for _, rf := range policy.ResourceFilters {
|
||||
entry := map[string]any{}
|
||||
if rf.IsCatchAll() {
|
||||
entry["kinds"] = []string{}
|
||||
entry["isCatchAll"] = true
|
||||
} else {
|
||||
entry["kinds"] = rf.Kinds
|
||||
}
|
||||
if len(rf.LabelSelector) > 0 {
|
||||
entry["labelSelector"] = rf.LabelSelector
|
||||
}
|
||||
if len(rf.OrLabelSelectors) > 0 {
|
||||
entry["orLabelSelectors"] = rf.OrLabelSelectors
|
||||
}
|
||||
if len(rf.Names) > 0 {
|
||||
entry["names"] = rf.Names
|
||||
}
|
||||
if len(rf.ExcludedNames) > 0 {
|
||||
entry["excludedNames"] = rf.ExcludedNames
|
||||
}
|
||||
rfEntries = append(rfEntries, entry)
|
||||
}
|
||||
structuredPolicies = append(structuredPolicies, map[string]any{
|
||||
"namespace": ns,
|
||||
"resourceFilters": rfEntries,
|
||||
})
|
||||
}
|
||||
}
|
||||
d.Describe("namespacedFilterPolicies", structuredPolicies)
|
||||
}
|
||||
|
||||
// DescribeBackupStatusInSF describes a backup status in structured format.
|
||||
func DescribeBackupStatusInSF(ctx context.Context, kbClient kbclient.Client, d *StructuredDescriber, backup *velerov1api.Backup, details bool,
|
||||
insecureSkipTLSVerify bool, caCertPath string, podVolumeBackups []velerov1api.PodVolumeBackup) {
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package output
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -25,8 +24,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
@@ -731,96 +728,3 @@ func TestDescribeDeleteBackupRequestsInSF(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeFineGrainedFilterPoliciesInSF(t *testing.T) {
|
||||
yamlData := `
|
||||
version: v1
|
||||
clusterScopedFilterPolicy:
|
||||
resourceFilters:
|
||||
- kinds: ["StorageClass"]
|
||||
labelSelector: {"app": "velero"}
|
||||
- kinds: ["ClusterRole"]
|
||||
orLabelSelectors:
|
||||
- {"app": "velero"}
|
||||
- {"app": "test"}
|
||||
names: ["role1"]
|
||||
excludedNames: ["role2"]
|
||||
namespacedFilterPolicies:
|
||||
- namespaces: ["ns1", "ns2"]
|
||||
resourceFilters:
|
||||
- kinds: ["Pod", "ConfigMap"]
|
||||
labelSelector: {"app": "velero"}
|
||||
- kinds: ["*"]
|
||||
`
|
||||
cm := &corev1api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-policy",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"policy.yaml": yamlData,
|
||||
},
|
||||
}
|
||||
|
||||
client := fake.NewClientBuilder().WithRuntimeObjects(cm).Build()
|
||||
|
||||
backup := builder.ForBackup("velero", "test-backup").
|
||||
ResourcePolicies("test-policy").Result()
|
||||
|
||||
sd := &StructuredDescriber{
|
||||
output: make(map[string]any),
|
||||
format: "",
|
||||
}
|
||||
|
||||
DescribeFineGrainedFilterPoliciesInSF(context.Background(), client, sd, backup)
|
||||
|
||||
expect := map[string]any{
|
||||
"clusterScopedFilterPolicy": map[string]any{
|
||||
"resourceFilters": []map[string]any{
|
||||
{
|
||||
"kinds": []string{"StorageClass"},
|
||||
"labelSelector": map[string]string{"app": "velero"},
|
||||
},
|
||||
{
|
||||
"kinds": []string{"ClusterRole"},
|
||||
"orLabelSelectors": []map[string]string{
|
||||
{"app": "velero"},
|
||||
{"app": "test"},
|
||||
},
|
||||
"names": []string{"role1"},
|
||||
"excludedNames": []string{"role2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"namespacedFilterPolicies": []map[string]any{
|
||||
{
|
||||
"namespace": "ns1",
|
||||
"resourceFilters": []map[string]any{
|
||||
{
|
||||
"kinds": []string{"Pod", "ConfigMap"},
|
||||
"labelSelector": map[string]string{"app": "velero"},
|
||||
},
|
||||
{
|
||||
"kinds": []string{},
|
||||
"isCatchAll": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"namespace": "ns2",
|
||||
"resourceFilters": []map[string]any{
|
||||
{
|
||||
"kinds": []string{"Pod", "ConfigMap"},
|
||||
"labelSelector": map[string]string{"app": "velero"},
|
||||
},
|
||||
{
|
||||
"kinds": []string{},
|
||||
"isCatchAll": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, reflect.DeepEqual(sd.output, expect))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user