/* Copyright 2017 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 initialization import ( "reflect" "strings" "testing" "k8s.io/api/admissionregistration/v1alpha1" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authorization/authorizer" ) func newInitializer(name string, rules ...v1alpha1.Rule) *v1alpha1.InitializerConfiguration { return addInitializer(&v1alpha1.InitializerConfiguration{}, name, rules...) } func addInitializer(base *v1alpha1.InitializerConfiguration, name string, rules ...v1alpha1.Rule) *v1alpha1.InitializerConfiguration { base.Initializers = append(base.Initializers, v1alpha1.Initializer{ Name: name, Rules: rules, }) return base } func TestFindInitializers(t *testing.T) { type args struct { initializers *v1alpha1.InitializerConfiguration gvr schema.GroupVersionResource } tests := []struct { name string args args want []string }{ { name: "empty", args: args{ gvr: schema.GroupVersionResource{}, initializers: newInitializer("1"), }, }, { name: "everything", args: args{ gvr: schema.GroupVersionResource{}, initializers: newInitializer("1", v1alpha1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}}), }, want: []string{"1"}, }, { name: "empty group", args: args{ gvr: schema.GroupVersionResource{}, initializers: newInitializer("1", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"*"}}), }, want: []string{"1"}, }, { name: "pod", args: args{ gvr: schema.GroupVersionResource{Resource: "pods"}, initializers: addInitializer( newInitializer("1", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"pods"}}), "2", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"pods"}}, ), }, want: []string{"1", "2"}, }, { name: "multiple matches", args: args{ gvr: schema.GroupVersionResource{Resource: "pods"}, initializers: newInitializer("1", v1alpha1.Rule{APIGroups: []string{""}, APIVersions: []string{"*"}, Resources: []string{"pods"}}), }, want: []string{"1"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := findInitializers(tt.args.initializers, tt.args.gvr); !reflect.DeepEqual(got, tt.want) { t.Errorf("findInitializers() = %v, want %v", got, tt.want) } }) } } type fakeAuthorizer struct { accept bool } func (f *fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) { if f.accept { return authorizer.DecisionAllow, "", nil } return authorizer.DecisionNoOpinion, "denied", nil } func TestAdmitUpdate(t *testing.T) { tests := []struct { name string oldInitializers *metav1.Initializers newInitializers *metav1.Initializers verifyUpdatedObj func(runtime.Object) (pass bool, reason string) err string }{ { name: "updates on initialized resources are allowed", oldInitializers: nil, newInitializers: nil, err: "", }, { name: "updates on initialized resources are allowed", oldInitializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}}, newInitializers: &metav1.Initializers{}, verifyUpdatedObj: func(obj runtime.Object) (bool, string) { accessor, err := meta.Accessor(obj) if err != nil { return false, "cannot get accessor" } if accessor.GetInitializers() != nil { return false, "expect nil initializers" } return true, "" }, err: "", }, { name: "initializers may not be set once initialized", oldInitializers: nil, newInitializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init.k8s.io"}}}, err: "field is immutable once initialization has completed", }, { name: "empty initializer list is treated as nil initializer", oldInitializers: nil, newInitializers: &metav1.Initializers{}, verifyUpdatedObj: func(obj runtime.Object) (bool, string) { accessor, err := meta.Accessor(obj) if err != nil { return false, "cannot get accessor" } if accessor.GetInitializers() != nil { return false, "expect nil initializers" } return true, "" }, err: "", }, } plugin := initializer{ config: nil, authorizer: &fakeAuthorizer{true}, } for _, tc := range tests { oldObj := &v1.Pod{} oldObj.Initializers = tc.oldInitializers newObj := &v1.Pod{} newObj.Initializers = tc.newInitializers a := admission.NewAttributesRecord(newObj, oldObj, schema.GroupVersionKind{}, "", "foo", schema.GroupVersionResource{}, "", admission.Update, false, nil) err := plugin.Admit(a) switch { case tc.err == "" && err != nil: t.Errorf("%q: unexpected error: %v", tc.name, err) case tc.err != "" && err == nil: t.Errorf("%q: unexpected no error, expected %s", tc.name, tc.err) case tc.err != "" && err != nil && !strings.Contains(err.Error(), tc.err): t.Errorf("%q: expected %s, got %v", tc.name, tc.err, err) } } }