mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-19 14:34:17 +00:00
add label selector in Resource Modifiers (#6704)
* add label selector in resource modifier Signed-off-by: lou <alex1988@outlook.com> * add ut Signed-off-by: lou <alex1988@outlook.com> * update after review Signed-off-by: lou <alex1988@outlook.com> * update after review Signed-off-by: lou <alex1988@outlook.com> --------- Signed-off-by: lou <alex1988@outlook.com> Signed-off-by: Anshul Ahuja <anshulahuja@microsoft.com>
This commit is contained in:
committed by
Anshul Ahuja
parent
53530130a5
commit
45165503ba
@@ -2,7 +2,6 @@ package resourcemodifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -10,9 +9,11 @@ import (
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
)
|
||||
@@ -23,26 +24,27 @@ const (
|
||||
)
|
||||
|
||||
type JSONPatch struct {
|
||||
Operation string `yaml:"operation"`
|
||||
From string `yaml:"from,omitempty"`
|
||||
Path string `yaml:"path"`
|
||||
Value string `yaml:"value,omitempty"`
|
||||
Operation string `json:"operation"`
|
||||
From string `json:"from,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Conditions struct {
|
||||
Namespaces []string `yaml:"namespaces,omitempty"`
|
||||
GroupKind string `yaml:"groupKind"`
|
||||
ResourceNameRegex string `yaml:"resourceNameRegex"`
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
GroupResource string `json:"groupResource"`
|
||||
ResourceNameRegex string `json:"resourceNameRegex,omitempty"`
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
}
|
||||
|
||||
type ResourceModifierRule struct {
|
||||
Conditions Conditions `yaml:"conditions"`
|
||||
Patches []JSONPatch `yaml:"patches"`
|
||||
Conditions Conditions `json:"conditions"`
|
||||
Patches []JSONPatch `json:"patches"`
|
||||
}
|
||||
|
||||
type ResourceModifiers struct {
|
||||
Version string `yaml:"version"`
|
||||
ResourceModifierRules []ResourceModifierRule `yaml:"resourceModifierRules"`
|
||||
Version string `json:"version"`
|
||||
ResourceModifierRules []ResourceModifierRule `json:"resourceModifierRules"`
|
||||
}
|
||||
|
||||
func GetResourceModifiersFromConfig(cm *v1.ConfigMap) (*ResourceModifiers, error) {
|
||||
@@ -58,7 +60,7 @@ func GetResourceModifiersFromConfig(cm *v1.ConfigMap) (*ResourceModifiers, error
|
||||
yamlData = v
|
||||
}
|
||||
|
||||
resModifiers, err := unmarshalResourceModifiers(&yamlData)
|
||||
resModifiers, err := unmarshalResourceModifiers([]byte(yamlData))
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@@ -83,9 +85,11 @@ func (r *ResourceModifierRule) Apply(obj *unstructured.Unstructured, groupResour
|
||||
if !namespaceInclusion.ShouldInclude(obj.GetNamespace()) {
|
||||
return nil
|
||||
}
|
||||
if !strings.EqualFold(groupResource, r.Conditions.GroupKind) {
|
||||
|
||||
if r.Conditions.GroupResource != groupResource {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.Conditions.ResourceNameRegex != "" {
|
||||
match, err := regexp.MatchString(r.Conditions.ResourceNameRegex, obj.GetName())
|
||||
if err != nil {
|
||||
@@ -95,6 +99,17 @@ func (r *ResourceModifierRule) Apply(obj *unstructured.Unstructured, groupResour
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if r.Conditions.LabelSelector != nil {
|
||||
selector, err := metav1.LabelSelectorAsSelector(r.Conditions.LabelSelector)
|
||||
if err != nil {
|
||||
return errors.Errorf("error in creating label selector %s", err.Error())
|
||||
}
|
||||
if !selector.Matches(labels.Set(obj.GetLabels())) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
patches, err := r.PatchArrayToByteArray()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -107,7 +122,7 @@ func (r *ResourceModifierRule) Apply(obj *unstructured.Unstructured, groupResour
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert all JsonPatch to string array with the format of jsonpatch.Patch and then convert it to byte array
|
||||
// PatchArrayToByteArray converts all JsonPatch to string array with the format of jsonpatch.Patch and then convert it to byte array
|
||||
func (r *ResourceModifierRule) PatchArrayToByteArray() ([]byte, error) {
|
||||
var patches []string
|
||||
for _, patch := range r.Patches {
|
||||
@@ -148,22 +163,15 @@ func ApplyPatch(patch []byte, obj *unstructured.Unstructured, log logrus.FieldLo
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalResourceModifiers(yamlData *string) (*ResourceModifiers, error) {
|
||||
func unmarshalResourceModifiers(yamlData []byte) (*ResourceModifiers, error) {
|
||||
resModifiers := &ResourceModifiers{}
|
||||
err := decodeStruct(strings.NewReader(*yamlData), resModifiers)
|
||||
err := yaml.UnmarshalStrict(yamlData, resModifiers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode yaml data into resource modifiers %v", err)
|
||||
}
|
||||
return resModifiers, nil
|
||||
}
|
||||
|
||||
// decodeStruct restrict validate the keys in decoded mappings to exist as fields in the struct being decoded into
|
||||
func decodeStruct(r io.Reader, s interface{}) error {
|
||||
dec := yaml.NewDecoder(r)
|
||||
dec.KnownFields(true)
|
||||
return dec.Decode(s)
|
||||
}
|
||||
|
||||
func addQuotes(value string) bool {
|
||||
if value == "" {
|
||||
return true
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupKind: persistentvolumeclaims\n resourceNameRegex: \".*\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: replace\n path: \"/spec/storageClassName\"\n value: \"premium\"\n - operation: remove\n path: \"/metadata/labels/test\"\n\n\n",
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: persistentvolumeclaims\n resourceNameRegex: \".*\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: replace\n path: \"/spec/storageClassName\"\n value: \"premium\"\n - operation: remove\n path: \"/metadata/labels/test\"\n\n\n",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "persistentvolumeclaims",
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Namespaces: []string{"bar", "foo"},
|
||||
},
|
||||
@@ -51,7 +51,7 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupKind: deployments.apps\n resourceNameRegex: \"^test-.*$\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: add\n path: \"/spec/template/spec/containers/0\"\n value: \"{\\\"name\\\": \\\"nginx\\\", \\\"image\\\": \\\"nginx:1.14.2\\\", \\\"ports\\\": [{\\\"containerPort\\\": 80}]}\"\n - operation: copy\n from: \"/spec/template/spec/containers/0\"\n path: \"/spec/template/spec/containers/1\"\n\n\n",
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: deployments.apps\n resourceNameRegex: \"^test-.*$\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: add\n path: \"/spec/template/spec/containers/0\"\n value: \"{\\\"name\\\": \\\"nginx\\\", \\\"image\\\": \\\"nginx:1.14.2\\\", \\\"ports\\\": [{\\\"containerPort\\\": 80}]}\"\n - operation: copy\n from: \"/spec/template/spec/containers/0\"\n path: \"/spec/template/spec/containers/1\"\n\n\n",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
GroupResource: "deployments.apps",
|
||||
ResourceNameRegex: "^test-.*$",
|
||||
Namespaces: []string{"bar", "foo"},
|
||||
},
|
||||
@@ -86,7 +86,33 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version1: v1\nresourceModifierRules:\n- conditions:\n groupKind: deployments.apps\n resourceNameRegex: \"^test-.*$\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: add\n path: \"/spec/template/spec/containers/0\"\n value: \"{\\\"name\\\": \\\"nginx\\\", \\\"image\\\": \\\"nginx:1.14.2\\\", \\\"ports\\\": [{\\\"containerPort\\\": 80}]}\"\n - operation: copy\n from: \"/spec/template/spec/containers/0\"\n path: \"/spec/template/spec/containers/1\"\n\n\n",
|
||||
"sub.yml": "version1: v1\nresourceModifierRules:\n- conditions:\n groupResource: deployments.apps\n resourceNameRegex: \"^test-.*$\"\n namespaces:\n - bar\n - foo\n patches:\n - operation: add\n path: \"/spec/template/spec/containers/0\"\n value: \"{\\\"name\\\": \\\"nginx\\\", \\\"image\\\": \\\"nginx:1.14.2\\\", \\\"ports\\\": [{\\\"containerPort\\\": 80}]}\"\n - operation: copy\n from: \"/spec/template/spec/containers/0\"\n path: \"/spec/template/spec/containers/1\"\n\n\n",
|
||||
},
|
||||
}
|
||||
|
||||
cm4 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-configmap",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"sub.yml": "version: v1\nresourceModifierRules:\n- conditions:\n groupResource: deployments.apps\n labelSelector:\n matchLabels:\n a: b\n",
|
||||
},
|
||||
}
|
||||
|
||||
rules4 := &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "deployments.apps",
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -123,6 +149,14 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "match labels",
|
||||
args: args{
|
||||
cm: cm4,
|
||||
},
|
||||
want: rules4,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nil configmap",
|
||||
args: args{
|
||||
@@ -183,6 +217,9 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-deployment",
|
||||
"namespace": "foo",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(1),
|
||||
@@ -211,6 +248,9 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-deployment",
|
||||
"namespace": "foo",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(2),
|
||||
@@ -239,6 +279,9 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-deployment",
|
||||
"namespace": "foo",
|
||||
"labels": map[string]interface{}{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": int64(1),
|
||||
@@ -287,7 +330,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "persistentvolumeclaims",
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: "[a-z",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
@@ -320,7 +363,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "persistentvolumeclaims",
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
@@ -353,7 +396,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
GroupResource: "deployments.apps",
|
||||
ResourceNameRegex: "^test-.*$",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
@@ -386,7 +429,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
GroupResource: "deployments.apps",
|
||||
ResourceNameRegex: "^test-.*$",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
@@ -419,8 +462,8 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
Namespaces: []string{"foo"},
|
||||
GroupResource: "deployments.apps",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
@@ -451,7 +494,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
GroupResource: "deployments.apps",
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
@@ -482,7 +525,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
GroupResource: "deployments.apps",
|
||||
ResourceNameRegex: ".*",
|
||||
Namespaces: []string{"bar"},
|
||||
},
|
||||
@@ -515,7 +558,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
GroupResource: "deployments.apps",
|
||||
ResourceNameRegex: "^test-.*$",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
@@ -543,7 +586,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "deployments.apps",
|
||||
GroupResource: "deployments.apps",
|
||||
ResourceNameRegex: "^test-.*$",
|
||||
Namespaces: []string{"foo"},
|
||||
},
|
||||
@@ -579,6 +622,80 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantObj: deployNginxMysql.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "nginx deployment: match label selector",
|
||||
fields: fields{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "deployments.apps",
|
||||
Namespaces: []string{"foo"},
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "test",
|
||||
Path: "/spec/replicas",
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/spec/replicas",
|
||||
Value: "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
obj: deployNginxOneReplica.DeepCopy(),
|
||||
groupResource: "deployments.apps",
|
||||
},
|
||||
wantErr: false,
|
||||
wantObj: deployNginxTwoReplica.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "nginx deployment: mismatch label selector",
|
||||
fields: fields{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "deployments.apps",
|
||||
Namespaces: []string{"foo"},
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "nginx-mismatch",
|
||||
},
|
||||
},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "test",
|
||||
Path: "/spec/replicas",
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/spec/replicas",
|
||||
Value: "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
obj: deployNginxOneReplica.DeepCopy(),
|
||||
groupResource: "deployments.apps",
|
||||
},
|
||||
wantErr: false,
|
||||
wantObj: deployNginxOneReplica.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (r *ResourceModifierRule) Validate() error {
|
||||
@@ -48,8 +47,8 @@ func (p *JSONPatch) Validate() error {
|
||||
}
|
||||
|
||||
func (c *Conditions) Validate() error {
|
||||
if c.GroupKind == "" {
|
||||
return fmt.Errorf("groupkind cannot be empty")
|
||||
if c.GroupResource == "" {
|
||||
return fmt.Errorf("groupkResource cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestResourceModifiers_Validate(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "persistentvolumeclaims",
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Namespaces: []string{"bar", "foo"},
|
||||
},
|
||||
@@ -44,7 +44,7 @@ func TestResourceModifiers_Validate(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "persistentvolumeclaims",
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Namespaces: []string{"bar", "foo"},
|
||||
},
|
||||
@@ -75,7 +75,7 @@ func TestResourceModifiers_Validate(t *testing.T) {
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "persistentvolumeclaims",
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Namespaces: []string{"bar", "foo"},
|
||||
},
|
||||
@@ -92,13 +92,13 @@ func TestResourceModifiers_Validate(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Condition has empty GroupKind",
|
||||
name: "Condition has empty GroupResource",
|
||||
fields: fields{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupKind: "",
|
||||
GroupResource: "",
|
||||
ResourceNameRegex: ".*",
|
||||
Namespaces: []string{"bar", "foo"},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user