diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/matching/matching.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/matching/matching.go new file mode 100644 index 00000000000..c4f7e64af2d --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/matching/matching.go @@ -0,0 +1,192 @@ +/* +Copyright 2022 The Kubernetes Authors. + +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 matching + +import ( + "fmt" + + v1 "k8s.io/api/admissionregistration/v1" + "k8s.io/api/admissionregistration/v1alpha1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + "k8s.io/client-go/kubernetes" + listersv1 "k8s.io/client-go/listers/core/v1" + + "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace" + "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object" + "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules" +) + +type MatchCriteria interface { + namespace.NamespaceSelectorProvider + object.ObjectSelectorProvider + + GetMatchResources() v1alpha1.MatchResources +} + +// Matcher decides if a request matches against matchCriteria +type Matcher struct { + namespaceMatcher *namespace.Matcher + objectMatcher *object.Matcher +} + +// NewMatcher initialize the matcher with dependencies requires +func NewMatcher( + namespaceLister listersv1.NamespaceLister, + client kubernetes.Interface, +) *Matcher { + return &Matcher{ + namespaceMatcher: &namespace.Matcher{ + NamespaceLister: namespaceLister, + Client: client, + }, + objectMatcher: &object.Matcher{}, + } +} + +// ValidateInitialization verify if the matcher is ready before use +func (m *Matcher) ValidateInitialization() error { + if err := m.namespaceMatcher.Validate(); err != nil { + return fmt.Errorf("namespaceMatcher is not properly setup: %v", err) + } + return nil +} + +func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionKind, error) { + matches, matchNsErr := m.namespaceMatcher.MatchNamespaceSelector(criteria, attr) + // Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario. + if !matches && matchNsErr == nil { + return false, schema.GroupVersionKind{}, nil + } + + matches, matchObjErr := m.objectMatcher.MatchObjectSelector(criteria, attr) + // Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario. + if !matches && matchObjErr == nil { + return false, schema.GroupVersionKind{}, nil + } + + matchResources := criteria.GetMatchResources() + matchPolicy := matchResources.MatchPolicy + if isExcluded, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil { + return false, schema.GroupVersionKind{}, err + } + + var ( + isMatch bool + matchKind schema.GroupVersionKind + matchErr error + ) + if len(matchResources.ResourceRules) == 0 { + isMatch = true + matchKind = attr.GetKind() + } else { + isMatch, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o) + } + if matchErr != nil { + return false, schema.GroupVersionKind{}, matchErr + } + if !isMatch { + return false, schema.GroupVersionKind{}, nil + } + + // now that we know this applies to this request otherwise, if there were selector errors, return them + if matchNsErr != nil { + return false, schema.GroupVersionKind{}, matchNsErr + } + if matchObjErr != nil { + return false, schema.GroupVersionKind{}, matchObjErr + } + + return true, matchKind, nil +} + +func matchesResourceRules(namedRules []v1alpha1.NamedRuleWithOperations, matchPolicy *v1alpha1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionKind, error) { + matchKind := attr.GetKind() + for _, namedRule := range namedRules { + rule := v1.RuleWithOperations(namedRule.RuleWithOperations) + ruleMatcher := rules.Matcher{ + Rule: rule, + Attr: attr, + } + if !ruleMatcher.Matches() { + continue + } + // an empty name list always matches + if len(namedRule.ResourceNames) == 0 { + return true, matchKind, nil + } + // TODO: GetName() can return an empty string if the user is relying on + // the API server to generate the name... figure out what to do for this edge case + name := attr.GetName() + for _, matchedName := range namedRule.ResourceNames { + if name == matchedName { + return true, matchKind, nil + } + } + } + + // if match policy is undefined or exact, don't perform fuzzy matching + // note that defaulting to fuzzy matching is set by the API + if matchPolicy == nil || *matchPolicy == v1alpha1.Exact { + return false, schema.GroupVersionKind{}, nil + } + + attrWithOverride := &attrWithResourceOverride{Attributes: attr} + equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource()) + for _, namedRule := range namedRules { + for _, equivalent := range equivalents { + if equivalent == attr.GetResource() { + // we have already checked the original resource + continue + } + attrWithOverride.resource = equivalent + rule := v1.RuleWithOperations(namedRule.RuleWithOperations) + m := rules.Matcher{ + Rule: rule, + Attr: attrWithOverride, + } + if !m.Matches() { + continue + } + matchKind = o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource()) + if matchKind.Empty() { + return false, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent) + } + // an empty name list always matches + if len(namedRule.ResourceNames) == 0 { + return true, matchKind, nil + } + + // TODO: GetName() can return an empty string if the user is relying on + // the API server to generate the name... figure out what to do for this edge case + name := attr.GetName() + for _, matchedName := range namedRule.ResourceNames { + if name == matchedName { + return true, matchKind, nil + } + } + } + } + return false, schema.GroupVersionKind{}, nil +} + +type attrWithResourceOverride struct { + admission.Attributes + resource schema.GroupVersionResource +} + +func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/matching/matching_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/matching/matching_test.go new file mode 100644 index 00000000000..d7fee8e9181 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/matching/matching_test.go @@ -0,0 +1,787 @@ +/* +Copyright 2022 The Kubernetes Authors. + +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 matching + +import ( + "fmt" + "strings" + "testing" + + v1 "k8s.io/api/admissionregistration/v1" + "k8s.io/api/admissionregistration/v1alpha1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace" + "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object" + "k8s.io/apiserver/pkg/apis/example" +) + +var _ MatchCriteria = &fakeCriteria{} + +type fakeCriteria struct { + matchResources v1alpha1.MatchResources +} + +func (fc *fakeCriteria) GetMatchResources() v1alpha1.MatchResources { + return fc.matchResources +} + +func (fc *fakeCriteria) GetParsedNamespaceSelector() (labels.Selector, error) { + return metav1.LabelSelectorAsSelector(fc.matchResources.NamespaceSelector) +} + +func (fc *fakeCriteria) GetParsedObjectSelector() (labels.Selector, error) { + return metav1.LabelSelectorAsSelector(fc.matchResources.ObjectSelector) +} + +func TestMatcher(t *testing.T) { + a := &Matcher{namespaceMatcher: &namespace.Matcher{}, objectMatcher: &object.Matcher{}} + + allScopes := v1.AllScopes + exactMatch := v1alpha1.Exact + equivalentMatch := v1alpha1.Equivalent + + mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { + if resource.Resource == "deployments" { + // co-locate deployments in all API groups + return "/deployments" + } + return "" + }) + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"extensions", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1alpha1", "Deployment"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"extensions", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", schema.GroupVersionKind{"autoscaling", "v1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1alpha1", "Scale"}) + + // register invalid kinds to trigger an error + mapper.RegisterKindFor(schema.GroupVersionResource{"example.com", "v1", "widgets"}, "", schema.GroupVersionKind{"", "", ""}) + mapper.RegisterKindFor(schema.GroupVersionResource{"example.com", "v2", "widgets"}, "", schema.GroupVersionKind{"", "", ""}) + + interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} + + // TODO write test cases for name matching and exclude matching + testcases := []struct { + name string + + criteria *v1alpha1.MatchResources + attrs admission.Attributes + + expectMatches bool + expectMatchKind *schema.GroupVersionKind + expectErr string + }{ + { + name: "no rules (just write)", + criteria: &v1alpha1.MatchResources{NamespaceSelector: &metav1.LabelSelector{}, ResourceRules: []v1alpha1.NamedRuleWithOperations{}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "wildcard rule, match as requested", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"apps", "v1", "Deployment"}, + }, + { + name: "specific rules, prefer exact match", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"apps", "v1", "Deployment"}, + }, + { + name: "specific rules, match miss", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "specific rules, exact match miss", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &exactMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "specific rules, equivalent match, prefer extensions", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"extensions", "v1beta1", "Deployment"}, + }, + { + name: "specific rules, equivalent match, prefer apps", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"apps", "v1beta1", "Deployment"}, + }, + + { + name: "specific rules, subresource prefer exact match", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, + }, + { + name: "specific rules, subresource match miss", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "specific rules, subresource exact match miss", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &exactMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "specific rules, subresource equivalent match, prefer extensions", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"extensions", "v1beta1", "Scale"}, + }, + { + name: "specific rules, subresource equivalent match, prefer apps", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"apps", "v1beta1", "Scale"}, + }, + { + name: "specific rules, prefer exact match and name match", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + ResourceNames: []string{"name"}, + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, + }, + { + name: "specific rules, prefer exact match and name match miss", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + ResourceNames: []string{"wrong-name"}, + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "specific rules, subresource equivalent match, prefer extensions and name match", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + ResourceNames: []string{"name"}, + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, + }, + { + name: "specific rules, subresource equivalent match, prefer extensions and name match miss", + criteria: &v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + ResourceNames: []string{"wrong-name"}, + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }}}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "exclude resource match on miss", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, + }, + }}, + ExcludeResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}, + }, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + expectMatchKind: &schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, + }, + { + name: "exclude resource miss on match", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, + }, + }}, + ExcludeResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}, + }, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "treat empty ResourceRules as match", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ExcludeResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, + }, + }}, + }, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: true, + }, + { + name: "treat non-empty ResourceRules as no match", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{}}, + }, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + }, + { + name: "erroring namespace selector on otherwise non-matching rule doesn't error", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key ", Operator: "In", Values: []string{"bad value"}}}}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Rule: v1alpha1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"deployments"}}, + Operations: []v1alpha1.OperationType{"*"}, + }, + }}, + }, + attrs: admission.NewAttributesRecord(&example.Pod{}, nil, schema.GroupVersionKind{"example.apiserver.k8s.io", "v1", "Pod"}, "ns", "name", schema.GroupVersionResource{"example.apiserver.k8s.io", "v1", "pods"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + expectErr: "", + }, + { + name: "erroring namespace selector on otherwise matching rule errors", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: "In", Values: []string{"bad value"}}}}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Rule: v1alpha1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"pods"}}, + Operations: []v1alpha1.OperationType{"*"}, + }, + }}, + }, + attrs: admission.NewAttributesRecord(&example.Pod{}, nil, schema.GroupVersionKind{"example.apiserver.k8s.io", "v1", "Pod"}, "ns", "name", schema.GroupVersionResource{"example.apiserver.k8s.io", "v1", "pods"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + expectErr: "bad value", + }, + { + name: "erroring object selector on otherwise non-matching rule doesn't error", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: "In", Values: []string{"bad value"}}}}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Rule: v1alpha1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"deployments"}}, + Operations: []v1alpha1.OperationType{"*"}, + }, + }}, + }, + attrs: admission.NewAttributesRecord(&example.Pod{}, nil, schema.GroupVersionKind{"example.apiserver.k8s.io", "v1", "Pod"}, "ns", "name", schema.GroupVersionResource{"example.apiserver.k8s.io", "v1", "pods"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + expectErr: "", + }, + { + name: "erroring object selector on otherwise matching rule errors", + criteria: &v1alpha1.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "key", Operator: "In", Values: []string{"bad value"}}}}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Rule: v1alpha1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"pods"}}, + Operations: []v1alpha1.OperationType{"*"}, + }, + }}, + }, + attrs: admission.NewAttributesRecord(&example.Pod{}, nil, schema.GroupVersionKind{"example.apiserver.k8s.io", "v1", "Pod"}, "ns", "name", schema.GroupVersionResource{"example.apiserver.k8s.io", "v1", "pods"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectMatches: false, + expectErr: "bad value", + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + matches, matchKind, err := a.Matches(testcase.attrs, interfaces, &fakeCriteria{matchResources: *testcase.criteria}) + if err != nil { + if len(testcase.expectErr) == 0 { + t.Fatal(err) + } + if !strings.Contains(err.Error(), testcase.expectErr) { + t.Fatalf("expected error containing %q, got %s", testcase.expectErr, err.Error()) + } + return + } else if len(testcase.expectErr) > 0 { + t.Fatalf("expected error %q, got no error", testcase.expectErr) + } + if testcase.expectMatchKind != nil { + if *testcase.expectMatchKind != matchKind { + t.Fatalf("expected matchKind %v, got %v", testcase.expectMatchKind, matchKind) + } + } + + if matches != testcase.expectMatches { + t.Fatalf("expected matches = %v; got %v", testcase.expectMatches, matches) + } + }) + } +} + +type fakeNamespaceLister struct { + namespaces map[string]*corev1.Namespace +} + +func (f fakeNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) { + return nil, nil +} +func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) { + ns, ok := f.namespaces[name] + if ok { + return ns, nil + } + return nil, errors.NewNotFound(corev1.Resource("namespaces"), name) +} + +func BenchmarkMatcher(b *testing.B) { + allScopes := v1.AllScopes + equivalentMatch := v1alpha1.Equivalent + + namespace1Labels := map[string]string{"ns": "ns1"} + namespace1 := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns1", + Labels: namespace1Labels, + }, + } + namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} + + mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { + if resource.Resource == "deployments" { + // co-locate deployments in all API groups + return "/deployments" + } + return "" + }) + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"extensions", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1alpha1", "Deployment"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"extensions", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", schema.GroupVersionKind{"autoscaling", "v1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1alpha1", "Scale"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1", "StatefulSet"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1beta1", "StatefulSet"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta2", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1beta2", "StatefulSet"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha2", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1beta2", "Scale"}) + + nsSelector := make(map[string]string) + for i := 0; i < 100; i++ { + nsSelector[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("val-%d", i) + } + + mr := v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: nsSelector}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{ + { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, + { + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, + }, + }, + }, + } + + criteria := &fakeCriteria{matchResources: mr} + attrs := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil) + interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} + matcher := &Matcher{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} + + for i := 0; i < b.N; i++ { + matcher.Matches(attrs, interfaces, criteria) + } +} + +func BenchmarkShouldCallHookWithComplexRule(b *testing.B) { + allScopes := v1.AllScopes + equivalentMatch := v1alpha1.Equivalent + + namespace1Labels := map[string]string{"ns": "ns1"} + namespace1 := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns1", + Labels: namespace1Labels, + }, + } + namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} + + mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { + if resource.Resource == "deployments" { + // co-locate deployments in all API groups + return "/deployments" + } + return "" + }) + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"extensions", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1alpha1", "Deployment"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"extensions", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", schema.GroupVersionKind{"autoscaling", "v1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1alpha1", "Scale"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1", "StatefulSet"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1beta1", "StatefulSet"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta2", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1beta2", "StatefulSet"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha2", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1beta2", "Scale"}) + + mr := v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{}, + } + + for i := 0; i < 100; i++ { + rule := v1alpha1.NamedRuleWithOperations{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{ + APIGroups: []string{fmt.Sprintf("app-%d", i)}, + APIVersions: []string{fmt.Sprintf("v%d", i)}, + Resources: []string{fmt.Sprintf("resource%d", i), fmt.Sprintf("resource%d/scale", i)}, + Scope: &allScopes, + }, + }, + } + mr.ResourceRules = append(mr.ResourceRules, rule) + } + + criteria := &fakeCriteria{matchResources: mr} + attrs := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil) + interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} + matcher := &Matcher{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} + + for i := 0; i < b.N; i++ { + matcher.Matches(attrs, interfaces, criteria) + } +} + +func BenchmarkShouldCallHookWithComplexSelectorAndRule(b *testing.B) { + allScopes := v1.AllScopes + equivalentMatch := v1alpha1.Equivalent + + namespace1Labels := map[string]string{"ns": "ns1"} + namespace1 := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns1", + Labels: namespace1Labels, + }, + } + namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} + + mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { + if resource.Resource == "deployments" { + // co-locate deployments in all API groups + return "/deployments" + } + return "" + }) + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"extensions", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1beta1", "Deployment"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "", schema.GroupVersionKind{"apps", "v1alpha1", "Deployment"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"extensions", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"extensions", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", schema.GroupVersionKind{"autoscaling", "v1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha1", "deployments"}, "scale", schema.GroupVersionKind{"apps", "v1alpha1", "Scale"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1", "StatefulSet"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1beta1", "StatefulSet"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta2", "statefulset"}, "", schema.GroupVersionKind{"apps", "v1beta2", "StatefulSet"}) + + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1beta1", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1beta1", "Scale"}) + mapper.RegisterKindFor(schema.GroupVersionResource{"apps", "v1alpha2", "statefulset"}, "scale", schema.GroupVersionKind{"apps", "v1beta2", "Scale"}) + + nsSelector := make(map[string]string) + for i := 0; i < 100; i++ { + nsSelector[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("val-%d", i) + } + + mr := v1alpha1.MatchResources{ + MatchPolicy: &equivalentMatch, + NamespaceSelector: &metav1.LabelSelector{MatchLabels: nsSelector}, + ObjectSelector: &metav1.LabelSelector{}, + ResourceRules: []v1alpha1.NamedRuleWithOperations{}, + } + + for i := 0; i < 100; i++ { + rule := v1alpha1.NamedRuleWithOperations{ + RuleWithOperations: v1alpha1.RuleWithOperations{ + Operations: []v1.OperationType{"*"}, + Rule: v1.Rule{ + APIGroups: []string{fmt.Sprintf("app-%d", i)}, + APIVersions: []string{fmt.Sprintf("v%d", i)}, + Resources: []string{fmt.Sprintf("resource%d", i), fmt.Sprintf("resource%d/scale", i)}, + Scope: &allScopes, + }, + }, + } + mr.ResourceRules = append(mr.ResourceRules, rule) + } + + criteria := &fakeCriteria{matchResources: mr} + attrs := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"autoscaling", "v1", "Scale"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "scale", admission.Create, &metav1.CreateOptions{}, false, nil) + interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} + matcher := &Matcher{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} + + for i := 0; i < b.N; i++ { + matcher.Matches(attrs, interfaces, criteria) + } +}