k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/alwayspullimages/admission_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package alwayspullimages 18 19 import ( 20 "context" 21 "testing" 22 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/runtime" 25 "k8s.io/apiserver/pkg/admission" 26 admissiontesting "k8s.io/apiserver/pkg/admission/testing" 27 api "k8s.io/kubernetes/pkg/apis/core" 28 ) 29 30 // TestAdmission verifies all create requests for pods result in every container's image pull policy 31 // set to Always 32 func TestAdmission(t *testing.T) { 33 namespace := "test" 34 handler := admissiontesting.WithReinvocationTesting(t, &AlwaysPullImages{}) 35 pod := api.Pod{ 36 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace}, 37 Spec: api.PodSpec{ 38 InitContainers: []api.Container{ 39 {Name: "init1", Image: "image"}, 40 {Name: "init2", Image: "image", ImagePullPolicy: api.PullNever}, 41 {Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, 42 {Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways}, 43 }, 44 Containers: []api.Container{ 45 {Name: "ctr1", Image: "image"}, 46 {Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever}, 47 {Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, 48 {Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways}, 49 }, 50 }, 51 } 52 err := handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 53 if err != nil { 54 t.Errorf("Unexpected error returned from admission handler") 55 } 56 for _, c := range pod.Spec.InitContainers { 57 if c.ImagePullPolicy != api.PullAlways { 58 t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy) 59 } 60 } 61 for _, c := range pod.Spec.Containers { 62 if c.ImagePullPolicy != api.PullAlways { 63 t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy) 64 } 65 } 66 } 67 68 func TestValidate(t *testing.T) { 69 namespace := "test" 70 handler := &AlwaysPullImages{} 71 pod := api.Pod{ 72 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace}, 73 Spec: api.PodSpec{ 74 InitContainers: []api.Container{ 75 {Name: "init1", Image: "image"}, 76 {Name: "init2", Image: "image", ImagePullPolicy: api.PullNever}, 77 {Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, 78 {Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways}, 79 }, 80 Containers: []api.Container{ 81 {Name: "ctr1", Image: "image"}, 82 {Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever}, 83 {Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, 84 {Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways}, 85 }, 86 }, 87 } 88 expectedError := `[` + 89 `pods "123" is forbidden: spec.initContainers[0].imagePullPolicy: Unsupported value: "": supported values: "Always", ` + 90 `pods "123" is forbidden: spec.initContainers[1].imagePullPolicy: Unsupported value: "Never": supported values: "Always", ` + 91 `pods "123" is forbidden: spec.initContainers[2].imagePullPolicy: Unsupported value: "IfNotPresent": supported values: "Always", ` + 92 `pods "123" is forbidden: spec.containers[0].imagePullPolicy: Unsupported value: "": supported values: "Always", ` + 93 `pods "123" is forbidden: spec.containers[1].imagePullPolicy: Unsupported value: "Never": supported values: "Always", ` + 94 `pods "123" is forbidden: spec.containers[2].imagePullPolicy: Unsupported value: "IfNotPresent": supported values: "Always"]` 95 err := handler.Validate(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 96 if err == nil { 97 t.Fatal("missing expected error") 98 } 99 if err.Error() != expectedError { 100 t.Fatal(err) 101 } 102 } 103 104 // TestOtherResources ensures that this admission controller is a no-op for other resources, 105 // subresources, and non-pods. 106 func TestOtherResources(t *testing.T) { 107 namespace := "testnamespace" 108 name := "testname" 109 pod := &api.Pod{ 110 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 111 Spec: api.PodSpec{ 112 Containers: []api.Container{ 113 {Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever}, 114 }, 115 }, 116 } 117 tests := []struct { 118 name string 119 kind string 120 resource string 121 subresource string 122 object runtime.Object 123 expectError bool 124 }{ 125 { 126 name: "non-pod resource", 127 kind: "Foo", 128 resource: "foos", 129 object: pod, 130 }, 131 { 132 name: "pod subresource", 133 kind: "Pod", 134 resource: "pods", 135 subresource: "exec", 136 object: pod, 137 }, 138 { 139 name: "non-pod object", 140 kind: "Pod", 141 resource: "pods", 142 object: &api.Service{}, 143 expectError: true, 144 }, 145 } 146 147 for _, tc := range tests { 148 handler := admissiontesting.WithReinvocationTesting(t, &AlwaysPullImages{}) 149 150 err := handler.Admit(context.TODO(), admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, &metav1.CreateOptions{}, false, nil), nil) 151 152 if tc.expectError { 153 if err == nil { 154 t.Errorf("%s: unexpected nil error", tc.name) 155 } 156 continue 157 } 158 159 if err != nil { 160 t.Errorf("%s: unexpected error: %v", tc.name, err) 161 continue 162 } 163 164 if e, a := api.PullNever, pod.Spec.Containers[0].ImagePullPolicy; e != a { 165 t.Errorf("%s: image pull policy was changed to %s", tc.name, a) 166 } 167 } 168 169 } 170 171 // TestUpdatePod ensures that this admission controller is a no-op for update pod if no 172 // images were changed in the new pod spec. 173 func TestUpdatePod(t *testing.T) { 174 namespace := "testnamespace" 175 name := "testname" 176 oldPod := &api.Pod{ 177 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 178 Spec: api.PodSpec{ 179 Containers: []api.Container{ 180 {Name: "ctr2", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, 181 }, 182 }, 183 } 184 // only add new annotation 185 pod := &api.Pod{ 186 ObjectMeta: metav1.ObjectMeta{ 187 Name: name, 188 Namespace: namespace, 189 Annotations: map[string]string{ 190 "test": "test", 191 }, 192 }, 193 Spec: api.PodSpec{ 194 Containers: []api.Container{ 195 {Name: "ctr2", Image: "image", ImagePullPolicy: api.PullIfNotPresent}, 196 }, 197 }, 198 } 199 // add new label and change image 200 podWithNewImage := &api.Pod{ 201 ObjectMeta: metav1.ObjectMeta{ 202 Name: name, 203 Namespace: namespace, 204 Annotations: map[string]string{ 205 "test": "test", 206 }, 207 }, 208 Spec: api.PodSpec{ 209 Containers: []api.Container{ 210 {Name: "ctr2", Image: "image2", ImagePullPolicy: api.PullIfNotPresent}, 211 }, 212 }, 213 } 214 tests := []struct { 215 name string 216 kind string 217 resource string 218 subresource string 219 object runtime.Object 220 oldObject runtime.Object 221 expectError bool 222 expectIgnore bool 223 }{ 224 { 225 name: "update IfNotPresent pod annotations", 226 kind: "Pod", 227 resource: "pods", 228 subresource: "finalizers", 229 object: pod, 230 oldObject: oldPod, 231 expectIgnore: true, 232 }, 233 { 234 name: "update IfNotPresent pod image", 235 kind: "Pod", 236 resource: "pods", 237 subresource: "finalizers", 238 object: podWithNewImage, 239 oldObject: oldPod, 240 }, 241 } 242 243 for _, tc := range tests { 244 handler := admissiontesting.WithReinvocationTesting(t, &AlwaysPullImages{}) 245 246 err := handler.Admit(context.TODO(), admission.NewAttributesRecord(tc.object, tc.oldObject, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, &metav1.UpdateOptions{}, false, nil), nil) 247 248 if tc.expectError { 249 if err == nil { 250 t.Errorf("%s: unexpected nil error", tc.name) 251 } 252 continue 253 } 254 if tc.expectIgnore { 255 if e, a := api.PullIfNotPresent, pod.Spec.Containers[0].ImagePullPolicy; e != a { 256 t.Errorf("%s: image pull policy was changed to %s", tc.name, a) 257 } 258 continue 259 } 260 261 if err != nil { 262 t.Errorf("%s: unexpected error: %v", tc.name, err) 263 continue 264 } 265 266 } 267 268 }