k8s.io/apiserver@v0.31.1/pkg/admission/config_test.go (about) 1 /* 2 Copyright 2017 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 admission 18 19 import ( 20 "io" 21 "os" 22 "reflect" 23 "testing" 24 25 "github.com/stretchr/testify/require" 26 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/util/json" 29 "k8s.io/apiserver/pkg/apis/apiserver" 30 apiserverapiv1 "k8s.io/apiserver/pkg/apis/apiserver/v1" 31 apiserverapiv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1" 32 ) 33 34 func TestReadAdmissionConfiguration(t *testing.T) { 35 // create a place holder file to hold per test config 36 configFile, err := os.CreateTemp("", "admission-plugin-config") 37 if err != nil { 38 t.Fatalf("unexpected err: %v", err) 39 } 40 defer os.Remove(configFile.Name()) 41 if err = configFile.Close(); err != nil { 42 t.Fatalf("unexpected err: %v", err) 43 } 44 configFileName := configFile.Name() 45 // the location that will be fixed up to be relative to the test config file. 46 imagePolicyWebhookFile, err := makeAbs("image-policy-webhook.json", os.TempDir()) 47 if err != nil { 48 t.Fatalf("unexpected err: %v", err) 49 } 50 // individual test scenarios 51 testCases := map[string]struct { 52 ConfigBody string 53 ExpectedAdmissionConfig *apiserver.AdmissionConfiguration 54 PluginNames []string 55 }{ 56 "v1alpha1 configuration - path fixup": { 57 ConfigBody: `{ 58 "apiVersion": "apiserver.k8s.io/v1alpha1", 59 "kind": "AdmissionConfiguration", 60 "plugins": [ 61 {"name": "ImagePolicyWebhook", "path": "image-policy-webhook.json"}, 62 {"name": "ResourceQuota"} 63 ]}`, 64 ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{ 65 Plugins: []apiserver.AdmissionPluginConfiguration{ 66 { 67 Name: "ImagePolicyWebhook", 68 Path: imagePolicyWebhookFile, 69 }, 70 { 71 Name: "ResourceQuota", 72 }, 73 }, 74 }, 75 PluginNames: []string{}, 76 }, 77 "v1alpha1 configuration - abspath": { 78 ConfigBody: `{ 79 "apiVersion": "apiserver.k8s.io/v1alpha1", 80 "kind": "AdmissionConfiguration", 81 "plugins": [ 82 {"name": "ImagePolicyWebhook", "path": "/tmp/image-policy-webhook.json"}, 83 {"name": "ResourceQuota"} 84 ]}`, 85 ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{ 86 Plugins: []apiserver.AdmissionPluginConfiguration{ 87 { 88 Name: "ImagePolicyWebhook", 89 Path: "/tmp/image-policy-webhook.json", 90 }, 91 { 92 Name: "ResourceQuota", 93 }, 94 }, 95 }, 96 PluginNames: []string{}, 97 }, 98 "v1 configuration - path fixup": { 99 ConfigBody: `{ 100 "apiVersion": "apiserver.config.k8s.io/v1", 101 "kind": "AdmissionConfiguration", 102 "plugins": [ 103 {"name": "ImagePolicyWebhook", "path": "image-policy-webhook.json"}, 104 {"name": "ResourceQuota"} 105 ]}`, 106 ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{ 107 Plugins: []apiserver.AdmissionPluginConfiguration{ 108 { 109 Name: "ImagePolicyWebhook", 110 Path: imagePolicyWebhookFile, 111 }, 112 { 113 Name: "ResourceQuota", 114 }, 115 }, 116 }, 117 PluginNames: []string{}, 118 }, 119 "v1 configuration - abspath": { 120 ConfigBody: `{ 121 "apiVersion": "apiserver.config.k8s.io/v1", 122 "kind": "AdmissionConfiguration", 123 "plugins": [ 124 {"name": "ImagePolicyWebhook", "path": "/tmp/image-policy-webhook.json"}, 125 {"name": "ResourceQuota"} 126 ]}`, 127 ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{ 128 Plugins: []apiserver.AdmissionPluginConfiguration{ 129 { 130 Name: "ImagePolicyWebhook", 131 Path: "/tmp/image-policy-webhook.json", 132 }, 133 { 134 Name: "ResourceQuota", 135 }, 136 }, 137 }, 138 PluginNames: []string{}, 139 }, 140 "legacy configuration with using legacy plugins": { 141 ConfigBody: `{ 142 "imagePolicy": { 143 "kubeConfigFile": "/home/user/.kube/config", 144 "allowTTL": 30, 145 "denyTTL": 30, 146 "retryBackoff": 500, 147 "defaultAllow": true 148 }, 149 "podNodeSelectorPluginConfig": { 150 "clusterDefaultNodeSelector": "" 151 } 152 }`, 153 ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{ 154 Plugins: []apiserver.AdmissionPluginConfiguration{ 155 { 156 Name: "ImagePolicyWebhook", 157 Path: configFileName, 158 }, 159 { 160 Name: "PodNodeSelector", 161 Path: configFileName, 162 }, 163 }, 164 }, 165 PluginNames: []string{"ImagePolicyWebhook", "PodNodeSelector"}, 166 }, 167 "legacy configuration not using legacy plugins": { 168 ConfigBody: `{ 169 "imagePolicy": { 170 "kubeConfigFile": "/home/user/.kube/config", 171 "allowTTL": 30, 172 "denyTTL": 30, 173 "retryBackoff": 500, 174 "defaultAllow": true 175 }, 176 "podNodeSelectorPluginConfig": { 177 "clusterDefaultNodeSelector": "" 178 } 179 }`, 180 ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{}, 181 PluginNames: []string{"NamespaceLifecycle", "InitialResources"}, 182 }, 183 } 184 185 scheme := runtime.NewScheme() 186 require.NoError(t, apiserver.AddToScheme(scheme)) 187 require.NoError(t, apiserverapiv1alpha1.AddToScheme(scheme)) 188 require.NoError(t, apiserverapiv1.AddToScheme(scheme)) 189 190 for testName, testCase := range testCases { 191 if err = os.WriteFile(configFileName, []byte(testCase.ConfigBody), 0644); err != nil { 192 t.Fatalf("unexpected err writing temp file: %v", err) 193 } 194 config, err := ReadAdmissionConfiguration(testCase.PluginNames, configFileName, scheme) 195 if err != nil { 196 t.Fatalf("unexpected err: %v", err) 197 } 198 if !reflect.DeepEqual(config.(configProvider).config, testCase.ExpectedAdmissionConfig) { 199 t.Errorf("%s: Expected:\n\t%#v\nGot:\n\t%#v", testName, testCase.ExpectedAdmissionConfig, config.(configProvider).config) 200 } 201 } 202 } 203 204 func TestEmbeddedConfiguration(t *testing.T) { 205 // create a place holder file to hold per test config 206 configFile, err := os.CreateTemp("", "admission-plugin-config") 207 if err != nil { 208 t.Fatalf("unexpected err: %v", err) 209 } 210 defer os.Remove(configFile.Name()) 211 if err = configFile.Close(); err != nil { 212 t.Fatalf("unexpected err: %v", err) 213 } 214 configFileName := configFile.Name() 215 216 testCases := map[string]struct { 217 ConfigBody string 218 ExpectedConfig string 219 }{ 220 "v1alpha1 versioned configuration": { 221 ConfigBody: `{ 222 "apiVersion": "apiserver.k8s.io/v1alpha1", 223 "kind": "AdmissionConfiguration", 224 "plugins": [ 225 { 226 "name": "Foo", 227 "configuration": { 228 "apiVersion": "foo.admission.k8s.io/v1alpha1", 229 "kind": "Configuration", 230 "foo": "bar" 231 } 232 } 233 ]}`, 234 ExpectedConfig: `{ 235 "apiVersion": "foo.admission.k8s.io/v1alpha1", 236 "kind": "Configuration", 237 "foo": "bar" 238 }`, 239 }, 240 "v1alpha1 legacy configuration": { 241 ConfigBody: `{ 242 "apiVersion": "apiserver.k8s.io/v1alpha1", 243 "kind": "AdmissionConfiguration", 244 "plugins": [ 245 { 246 "name": "Foo", 247 "configuration": { 248 "foo": "bar" 249 } 250 } 251 ]}`, 252 ExpectedConfig: `{ 253 "foo": "bar" 254 }`, 255 }, 256 "v1 versioned configuration": { 257 ConfigBody: `{ 258 "apiVersion": "apiserver.config.k8s.io/v1", 259 "kind": "AdmissionConfiguration", 260 "plugins": [ 261 { 262 "name": "Foo", 263 "configuration": { 264 "apiVersion": "foo.admission.k8s.io/v1alpha1", 265 "kind": "Configuration", 266 "foo": "bar" 267 } 268 } 269 ]}`, 270 ExpectedConfig: `{ 271 "apiVersion": "foo.admission.k8s.io/v1alpha1", 272 "kind": "Configuration", 273 "foo": "bar" 274 }`, 275 }, 276 "v1 legacy configuration": { 277 ConfigBody: `{ 278 "apiVersion": "apiserver.config.k8s.io/v1", 279 "kind": "AdmissionConfiguration", 280 "plugins": [ 281 { 282 "name": "Foo", 283 "configuration": { 284 "foo": "bar" 285 } 286 } 287 ]}`, 288 ExpectedConfig: `{ 289 "foo": "bar" 290 }`, 291 }, 292 } 293 294 for desc, test := range testCases { 295 scheme := runtime.NewScheme() 296 require.NoError(t, apiserver.AddToScheme(scheme)) 297 require.NoError(t, apiserverapiv1alpha1.AddToScheme(scheme)) 298 require.NoError(t, apiserverapiv1.AddToScheme(scheme)) 299 300 if err = os.WriteFile(configFileName, []byte(test.ConfigBody), 0644); err != nil { 301 t.Errorf("[%s] unexpected err writing temp file: %v", desc, err) 302 continue 303 } 304 config, err := ReadAdmissionConfiguration([]string{"Foo"}, configFileName, scheme) 305 if err != nil { 306 t.Errorf("[%s] unexpected err: %v", desc, err) 307 continue 308 } 309 r, err := config.ConfigFor("Foo") 310 if err != nil { 311 t.Errorf("[%s] Failed to get Foo config: %v", desc, err) 312 continue 313 } 314 bs, err := io.ReadAll(r) 315 if err != nil { 316 t.Errorf("[%s] Failed to read Foo config data: %v", desc, err) 317 continue 318 } 319 320 if !equalJSON(test.ExpectedConfig, string(bs)) { 321 t.Errorf("Unexpected config: expected=%q got=%q", test.ExpectedConfig, string(bs)) 322 } 323 } 324 } 325 326 func equalJSON(a, b string) bool { 327 var x, y interface{} 328 if err := json.Unmarshal([]byte(a), &x); err != nil { 329 return false 330 } 331 if err := json.Unmarshal([]byte(b), &y); err != nil { 332 return false 333 } 334 return reflect.DeepEqual(x, y) 335 }