k8s.io/kubernetes@v1.29.3/test/cmd/apply.sh (about) 1 #!/usr/bin/env bash 2 3 # Copyright 2018 The Kubernetes Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 set -o errexit 18 set -o nounset 19 set -o pipefail 20 21 # Runs tests related to kubectl apply. 22 run_kubectl_apply_tests() { 23 set -o nounset 24 set -o errexit 25 26 create_and_use_new_namespace 27 kube::log::status "Testing kubectl apply" 28 ## kubectl apply should create the resource that doesn't exist yet 29 # Pre-Condition: no POD exists 30 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 31 # Command: apply a pod "test-pod" (doesn't exist) should create this pod 32 kubectl apply -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 33 # Post-Condition: pod "test-pod" is created 34 kube::test::get_object_assert 'pods test-pod' "{{${labels_field:?}.name}}" 'test-pod-label' 35 # Post-Condition: pod "test-pod" has configuration annotation 36 grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get pods test-pod -o yaml "${kube_flags[@]:?}")" 37 # pod has field manager for kubectl client-side apply 38 output_message=$(kubectl get --show-managed-fields -f hack/testdata/pod.yaml -o=jsonpath='{.metadata.managedFields[*].manager}' "${kube_flags[@]:?}" 2>&1) 39 kube::test::if_has_string "${output_message}" 'kubectl-client-side-apply' 40 # Clean up 41 kubectl delete pods test-pod "${kube_flags[@]:?}" 42 43 ### set-last-applied 44 # Pre-Condition: no POD exists 45 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 46 # Command: create "test-pod" (doesn't exist) should create this pod without last-applied annotation 47 kubectl create -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 48 # Post-Condition: pod "test-pod" is created 49 kube::test::get_object_assert 'pods test-pod' "{{${labels_field:?}.name}}" 'test-pod-label' 50 # Pre-Condition: pod "test-pod" does not have configuration annotation 51 ! grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get pods test-pod -o yaml "${kube_flags[@]:?}")" || exit 1 52 # Dry-run set-last-applied 53 kubectl apply set-last-applied --dry-run=client -f hack/testdata/pod.yaml --create-annotation=true "${kube_flags[@]:?}" 54 kubectl apply set-last-applied --dry-run=server -f hack/testdata/pod.yaml --create-annotation=true "${kube_flags[@]:?}" 55 ! grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get pods test-pod -o yaml "${kube_flags[@]:?}")" || exit 1 56 # Command 57 kubectl apply set-last-applied -f hack/testdata/pod.yaml --create-annotation=true "${kube_flags[@]:?}" 58 # Post-Condition: pod "test-pod" has configuration annotation 59 grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get pods test-pod -o yaml "${kube_flags[@]:?}")" 60 # Clean up 61 kubectl delete pods test-pod "${kube_flags[@]:?}" 62 63 ## kubectl apply should be able to clear defaulted fields. 64 # Pre-Condition: no deployment exists 65 kube::test::get_object_assert deployments "{{range.items}}{{${id_field:?}}}:{{end}}" '' 66 # Command: apply a deployment "test-deployment-retainkeys" (doesn't exist) should create this deployment 67 kubectl apply -f hack/testdata/retainKeys/deployment/deployment-before.yaml "${kube_flags[@]:?}" 68 # Post-Condition: deployment "test-deployment-retainkeys" created 69 kube::test::get_object_assert deployments "{{range.items}}{{${id_field:?}}}{{end}}" 'test-deployment-retainkeys' 70 # Post-Condition: deployment "test-deployment-retainkeys" has defaulted fields 71 grep -q RollingUpdate <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" 72 grep -q maxSurge <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" 73 grep -q maxUnavailable <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" 74 grep -q emptyDir <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" 75 # Command: apply a deployment "test-deployment-retainkeys" should clear 76 # defaulted fields and successfully update the deployment 77 [[ "$(kubectl apply -f hack/testdata/retainKeys/deployment/deployment-after.yaml "${kube_flags[@]:?}")" ]] 78 # Post-Condition: deployment "test-deployment-retainkeys" has updated fields 79 grep -q Recreate <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" 80 ! grep -q RollingUpdate <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" || exit 1 81 grep -q hostPath <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" 82 ! grep -q emptyDir <<< "$(kubectl get deployments test-deployment-retainkeys -o yaml "${kube_flags[@]:?}")" || exit 1 83 # Clean up 84 kubectl delete deployments test-deployment-retainkeys "${kube_flags[@]:?}" 85 86 ## kubectl apply -f with label selector should only apply matching objects 87 # Pre-Condition: no POD exists 88 kube::test::wait_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 89 # apply 90 kubectl apply -l unique-label=bingbang -f hack/testdata/filter "${kube_flags[@]:?}" 91 # check right pod exists 92 kube::test::get_object_assert 'pods selector-test-pod' "{{${labels_field:?}.name}}" 'selector-test-pod' 93 # check wrong pod doesn't exist 94 output_message=$(! kubectl get pods selector-test-pod-dont-apply 2>&1 "${kube_flags[@]:?}") 95 kube::test::if_has_string "${output_message}" 'pods "selector-test-pod-dont-apply" not found' 96 # cleanup 97 kubectl delete pods selector-test-pod 98 99 ## kubectl apply --dry-run=server 100 # Pre-Condition: no POD exists 101 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 102 103 # apply dry-run 104 kubectl apply --dry-run=client -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 105 kubectl apply --dry-run=server -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 106 # No pod exists 107 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 108 # apply non dry-run creates the pod 109 kubectl apply -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 110 initialResourceVersion=$(kubectl get "${kube_flags[@]:?}" -f hack/testdata/pod.yaml -o go-template='{{ .metadata.resourceVersion }}') 111 # apply changes 112 kubectl apply --dry-run=client -f hack/testdata/pod-apply.yaml "${kube_flags[@]:?}" 113 kubectl apply --dry-run=server -f hack/testdata/pod-apply.yaml "${kube_flags[@]:?}" 114 # Post-Condition: label still has initial value 115 kube::test::get_object_assert 'pods test-pod' "{{${labels_field:?}.name}}" 'test-pod-label' 116 # Ensure dry-run doesn't persist change 117 resourceVersion=$(kubectl get "${kube_flags[@]:?}" -f hack/testdata/pod.yaml -o go-template='{{ .metadata.resourceVersion }}') 118 kube::test::if_has_string "${resourceVersion}" "${initialResourceVersion}" 119 120 # clean-up 121 kubectl delete -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 122 123 ## kubectl apply dry-run on CR 124 # Create CRD 125 kubectl "${kube_flags_with_token[@]:?}" create -f - << __EOF__ 126 { 127 "kind": "CustomResourceDefinition", 128 "apiVersion": "apiextensions.k8s.io/v1", 129 "metadata": { 130 "name": "resources.mygroup.example.com" 131 }, 132 "spec": { 133 "group": "mygroup.example.com", 134 "scope": "Namespaced", 135 "names": { 136 "plural": "resources", 137 "singular": "resource", 138 "kind": "Kind", 139 "listKind": "KindList" 140 }, 141 "versions": [ 142 { 143 "name": "v1alpha1", 144 "served": true, 145 "storage": true, 146 "schema": { 147 "openAPIV3Schema": { 148 "x-kubernetes-preserve-unknown-fields": true, 149 "type": "object" 150 } 151 } 152 } 153 ] 154 } 155 } 156 __EOF__ 157 158 # Ensure the API server has recognized and started serving the associated CR API 159 local tries=5 160 for i in $(seq 1 $tries); do 161 local output 162 output=$(kubectl "${kube_flags[@]:?}" api-resources --api-group mygroup.example.com -oname || true) 163 if kube::test::if_has_string "$output" resources.mygroup.example.com; then 164 break 165 fi 166 echo "${i}: Waiting for CR API to be available" 167 sleep "$i" 168 done 169 170 # Dry-run create the CR 171 kubectl "${kube_flags[@]:?}" apply --dry-run=server -f hack/testdata/CRD/resource.yaml "${kube_flags[@]:?}" 172 # Make sure that the CR doesn't exist 173 ! kubectl "${kube_flags[@]:?}" get resource/myobj 2>/dev/null || exit 1 174 175 # clean-up 176 kubectl "${kube_flags[@]:?}" delete customresourcedefinition resources.mygroup.example.com 177 178 ## kubectl apply --prune 179 # Pre-Condition: namespace nsb exists; no POD exists 180 kubectl create ns nsb 181 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 182 # apply a into namespace nsb 183 kubectl apply --namespace nsb -l prune-group=true -f hack/testdata/prune/a.yaml "${kube_flags[@]:?}" 184 kube::test::get_object_assert 'pods a -n nsb' "{{${id_field:?}}}" 'a' 185 # apply b with namespace 186 kubectl apply --namespace nsb --prune -l prune-group=true -f hack/testdata/prune/b.yaml "${kube_flags[@]:?}" 187 # check right pod exists and wrong pod doesn't exist 188 kube::test::wait_object_assert 'pods -n nsb' "{{range.items}}{{${id_field:?}}}:{{end}}" 'b:' 189 190 # cleanup 191 kubectl delete pods b -n nsb 192 193 # same thing without prune for a sanity check 194 # Pre-Condition: no POD exists 195 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 196 197 # apply a 198 kubectl apply -l prune-group=true -f hack/testdata/prune/a.yaml "${kube_flags[@]:?}" 199 # check right pod exists 200 kube::test::get_object_assert 'pods a' "{{${id_field:?}}}" 'a' 201 # check wrong pod doesn't exist 202 kube::test::wait_object_assert 'pods -n nsb' "{{range.items}}{{${id_field:?}}}:{{end}}" '' 203 204 # apply b 205 kubectl apply -l prune-group=true -f hack/testdata/prune/b.yaml "${kube_flags[@]:?}" 206 # check both pods exist 207 kube::test::get_object_assert 'pods a' "{{${id_field:?}}}" 'a' 208 kube::test::get_object_assert 'pods b -n nsb' "{{${id_field:?}}}" 'b' 209 210 # cleanup 211 kubectl delete pod/a 212 kubectl delete pod/b -n nsb 213 214 ## kubectl apply --prune requires a --all flag to select everything 215 output_message=$(! kubectl apply --prune -f hack/testdata/prune 2>&1 "${kube_flags[@]:?}") 216 kube::test::if_has_string "${output_message}" \ 217 'all resources selected for prune without explicitly passing --all' 218 # should apply everything 219 kubectl apply --all --prune -f hack/testdata/prune 220 kube::test::get_object_assert 'pods a' "{{${id_field:?}}}" 'a' 221 kube::test::get_object_assert 'pods b -n nsb' "{{${id_field:?}}}" 'b' 222 kubectl delete pod/a 223 kubectl delete pod/b -n nsb 224 kubectl delete ns nsb 225 226 ## kubectl apply --prune should fallback to delete for non reapable types 227 kubectl apply --all --prune -f hack/testdata/prune-reap/a.yml 2>&1 "${kube_flags[@]:?}" 228 kube::test::get_object_assert 'pvc a-pvc' "{{${id_field:?}}}" 'a-pvc' 229 kubectl apply --all --prune -f hack/testdata/prune-reap/b.yml 2>&1 "${kube_flags[@]:?}" 230 kube::test::get_object_assert 'pvc b-pvc' "{{${id_field:?}}}" 'b-pvc' 231 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 232 kubectl delete pvc b-pvc 2>&1 "${kube_flags[@]:?}" 233 234 ## kubectl apply --prune --prune-whitelist 235 # Pre-Condition: no POD exists 236 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 237 # apply pod a 238 kubectl apply --prune -l prune-group=true -f hack/testdata/prune/a.yaml "${kube_flags[@]:?}" 239 # check right pod exists 240 kube::test::get_object_assert 'pods a' "{{${id_field:?}}}" 'a' 241 # apply svc and don't prune pod a by overwriting whitelist 242 kubectl apply --prune -l prune-group=true -f hack/testdata/prune/svc.yaml --prune-whitelist core/v1/Service 2>&1 "${kube_flags[@]:?}" 243 kube::test::get_object_assert 'service prune-svc' "{{${id_field:?}}}" 'prune-svc' 244 kube::test::get_object_assert 'pods a' "{{${id_field:?}}}" 'a' 245 # apply svc and prune pod a with default whitelist 246 kubectl apply --prune -l prune-group=true -f hack/testdata/prune/svc.yaml 2>&1 "${kube_flags[@]:?}" 247 kube::test::get_object_assert 'service prune-svc' "{{${id_field:?}}}" 'prune-svc' 248 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 249 # cleanup 250 kubectl delete svc prune-svc 2>&1 "${kube_flags[@]:?}" 251 252 ## kubectl apply --prune can prune resources not in the defaulted namespace 253 # Pre-Condition: namespace nsb exists; no POD exists 254 kubectl create ns nsb 255 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 256 # apply a into namespace nsb 257 kubectl apply --namespace nsb -f hack/testdata/prune/a.yaml "${kube_flags[@]:?}" 258 kube::test::get_object_assert 'pods a -n nsb' "{{${id_field:?}}}" 'a' 259 # apply b with namespace 260 kubectl apply --namespace nsb -f hack/testdata/prune/b.yaml "${kube_flags[@]:?}" 261 kube::test::get_object_assert 'pods b -n nsb' "{{${id_field:?}}}" 'b' 262 # apply --prune must prune a 263 kubectl apply --prune --all -f hack/testdata/prune/b.yaml 264 # check wrong pod doesn't exist and right pod exists 265 kube::test::wait_object_assert 'pods -n nsb' "{{range.items}}{{${id_field:?}}}:{{end}}" 'b:' 266 267 # cleanup 268 kubectl delete ns nsb 269 270 ## kubectl apply -n must fail if input file contains namespace other than the one given in -n 271 output_message=$(! kubectl apply -n foo -f hack/testdata/prune/b.yaml 2>&1 "${kube_flags[@]:?}") 272 kube::test::if_has_string "${output_message}" 'the namespace from the provided object "nsb" does not match the namespace "foo".' 273 274 ## kubectl apply -f some.yml --force 275 # Pre-condition: no service exists 276 kube::test::get_object_assert services "{{range.items}}{{${id_field:?}}}:{{end}}" '' 277 # apply service a 278 kubectl apply -f hack/testdata/service-revision1.yaml "${kube_flags[@]:?}" 279 # check right service exists 280 kube::test::get_object_assert 'services a' "{{${id_field:?}}}" 'a' 281 # change immutable field and apply service a 282 output_message=$(! kubectl apply -f hack/testdata/service-revision2.yaml 2>&1 "${kube_flags[@]:?}") 283 kube::test::if_has_string "${output_message}" 'may not change once set' 284 # apply --force to recreate resources for immutable fields 285 kubectl apply -f hack/testdata/service-revision2.yaml --force "${kube_flags[@]:?}" 286 # check immutable field exists 287 kube::test::get_object_assert 'services a' "{{.spec.clusterIP}}" '10.0.0.12' 288 # cleanup 289 kubectl delete -f hack/testdata/service-revision2.yaml "${kube_flags[@]:?}" 290 291 ## kubectl apply -k somedir 292 kubectl apply -k hack/testdata/kustomize 293 kube::test::get_object_assert 'configmap test-the-map' "{{${id_field}}}" 'test-the-map' 294 kube::test::get_object_assert 'deployment test-the-deployment' "{{${id_field}}}" 'test-the-deployment' 295 kube::test::get_object_assert 'service test-the-service' "{{${id_field}}}" 'test-the-service' 296 # cleanup 297 kubectl delete -k hack/testdata/kustomize 298 299 ## kubectl apply --kustomize somedir 300 kubectl apply --kustomize hack/testdata/kustomize 301 kube::test::get_object_assert 'configmap test-the-map' "{{${id_field}}}" 'test-the-map' 302 kube::test::get_object_assert 'deployment test-the-deployment' "{{${id_field}}}" 'test-the-deployment' 303 kube::test::get_object_assert 'service test-the-service' "{{${id_field}}}" 'test-the-service' 304 # cleanup 305 kubectl delete --kustomize hack/testdata/kustomize 306 307 ## kubectl apply multiple resources with one failure during apply phase. 308 # Pre-Condition: namespace does not exist and no POD exists 309 output_message=$(! kubectl get namespace multi-resource-ns 2>&1 "${kube_flags[@]:?}") 310 kube::test::if_has_string "${output_message}" 'namespaces "multi-resource-ns" not found' 311 kube::test::wait_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 312 # First pass, namespace is created, but pod is not (since namespace does not exist yet). 313 output_message=$(! kubectl apply -f hack/testdata/multi-resource-1.yaml 2>&1 "${kube_flags[@]:?}") 314 kube::test::if_has_string "${output_message}" 'namespaces "multi-resource-ns" not found' 315 output_message=$(! kubectl get pods test-pod -n multi-resource-ns 2>&1 "${kube_flags[@]:?}") 316 kube::test::if_has_string "${output_message}" 'pods "test-pod" not found' 317 # Second pass, pod is created (now that namespace exists). 318 kubectl apply -f hack/testdata/multi-resource-1.yaml "${kube_flags[@]:?}" 319 kube::test::get_object_assert 'pods test-pod -n multi-resource-ns' "{{${id_field}}}" 'test-pod' 320 # cleanup 321 kubectl delete -f hack/testdata/multi-resource-1.yaml "${kube_flags[@]:?}" 322 323 ## kubectl apply multiple resources with one failure during builder phase. 324 # Pre-Condition: No configmaps with name=foo 325 kube::test::get_object_assert 'configmaps --field-selector=metadata.name=foo' "{{range.items}}{{${id_field:?}}}:{{end}}" '' 326 # Apply a configmap and a bogus custom resource. 327 output_message=$(! kubectl apply -f hack/testdata/multi-resource-2.yaml 2>&1 "${kube_flags[@]:?}") 328 # Should be error message from bogus custom resource. 329 kube::test::if_has_string "${output_message}" 'no matches for kind "Bogus" in version "example.com/v1"' 330 # ConfigMap should have been created even with custom resource error. 331 kube::test::get_object_assert 'configmaps foo' "{{${id_field}}}" 'foo' 332 # cleanup 333 kubectl delete configmaps foo "${kube_flags[@]:?}" 334 335 ## kubectl apply multiple resources with one failure during builder phase. 336 # Pre-Condition: No pods exist. 337 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 338 # Applies three pods, one of which is invalid (POD-B), two succeed (pod-a, pod-c). 339 output_message=$(! kubectl apply -f hack/testdata/multi-resource-3.yaml 2>&1 "${kube_flags[@]:?}") 340 kube::test::if_has_string "${output_message}" 'The Pod "POD-B" is invalid' 341 kube::test::get_object_assert 'pods pod-a' "{{${id_field}}}" 'pod-a' 342 kube::test::get_object_assert 'pods pod-c' "{{${id_field}}}" 'pod-c' 343 # cleanup 344 kubectl delete pod pod-a pod-c "${kube_flags[@]:?}" 345 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 346 347 ## kubectl apply multiple resources with one failure during apply phase. 348 # Pre-Condition: crd does not exist, and custom resource does not exist. 349 kube::test::get_object_assert crds "{{range.items}}{{${id_field:?}}}:{{end}}" '' 350 # First pass, custom resource fails, but crd apply succeeds. 351 output_message=$(! kubectl apply -f hack/testdata/multi-resource-4.yaml 2>&1 "${kube_flags[@]:?}") 352 kube::test::if_has_string "${output_message}" 'no matches for kind "Widget" in version "example.com/v1"' 353 kubectl wait --timeout=2s --for=condition=Established=true crd/widgets.example.com 354 output_message=$(! kubectl get widgets foo 2>&1 "${kube_flags[@]:?}") 355 kube::test::if_has_string "${output_message}" 'widgets.example.com "foo" not found' 356 kube::test::get_object_assert 'crds widgets.example.com' "{{${id_field}}}" 'widgets.example.com' 357 # Second pass, custom resource is created (now that crd exists). 358 kubectl apply -f hack/testdata/multi-resource-4.yaml "${kube_flags[@]:?}" 359 kube::test::get_object_assert 'widget foo' "{{${id_field}}}" 'foo' 360 # cleanup 361 kubectl delete -f hack/testdata/multi-resource-4.yaml "${kube_flags[@]:?}" 362 363 set +o nounset 364 set +o errexit 365 } 366 367 # Runs tests related to kubectl apply (server-side) 368 run_kubectl_server_side_apply_tests() { 369 set -o nounset 370 set -o errexit 371 372 create_and_use_new_namespace 373 kube::log::status "Testing kubectl apply --server-side" 374 ## kubectl apply should create the resource that doesn't exist yet 375 # Pre-Condition: no POD exists 376 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 377 # Command: apply a pod "test-pod" (doesn't exist) should create this pod 378 kubectl apply --server-side -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 379 # Post-Condition: pod "test-pod" is created 380 kube::test::get_object_assert 'pods test-pod' "{{${labels_field:?}.name}}" 'test-pod-label' 381 # pod has field manager for kubectl server-side apply 382 output_message=$(kubectl get --show-managed-fields -f hack/testdata/pod.yaml -o=jsonpath='{.metadata.managedFields[*].manager}' "${kube_flags[@]:?}" 2>&1) 383 kube::test::if_has_string "${output_message}" 'kubectl' 384 # pod has custom field manager 385 kubectl apply --server-side --field-manager=my-field-manager --force-conflicts -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 386 output_message=$(kubectl get -f hack/testdata/pod.yaml -o=jsonpath='{.metadata.managedFields[*].manager}' "${kube_flags[@]:?}" 2>&1) 387 kube::test::if_has_string "${output_message}" 'my-field-manager' 388 # Clean up 389 kubectl delete pods test-pod "${kube_flags[@]:?}" 390 391 ## kubectl apply --dry-run=server 392 # Pre-Condition: no POD exists 393 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 394 395 # apply dry-run 396 kubectl apply --server-side --dry-run=server -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 397 # No pod exists 398 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 399 # apply non dry-run creates the pod 400 kubectl apply --server-side -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 401 initialResourceVersion=$(kubectl get "${kube_flags[@]:?}" -f hack/testdata/pod.yaml -o go-template='{{ .metadata.resourceVersion }}') 402 # apply changes 403 kubectl apply --server-side --dry-run=server -f hack/testdata/pod-apply.yaml "${kube_flags[@]:?}" 404 # Post-Condition: label still has initial value 405 kube::test::get_object_assert 'pods test-pod' "{{${labels_field:?}.name}}" 'test-pod-label' 406 # Ensure dry-run doesn't persist change 407 resourceVersion=$(kubectl get "${kube_flags[@]:?}" -f hack/testdata/pod.yaml -o go-template='{{ .metadata.resourceVersion }}') 408 kube::test::if_has_string "${resourceVersion}" "${initialResourceVersion}" 409 410 # clean-up 411 kubectl delete -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 412 413 ## kubectl apply upgrade 414 # Pre-Condition: no POD exists 415 kube::test::get_object_assert pods "{{range.items}}{{${id_field:?}}}:{{end}}" '' 416 417 kube::log::status "Testing upgrade kubectl client-side apply to server-side apply" 418 # run client-side apply 419 kubectl apply -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 420 # test upgrade does not work with non-standard server-side apply field manager 421 ! kubectl apply --server-side --field-manager="not-kubectl" -f hack/testdata/pod-apply.yaml "${kube_flags[@]:?}" || exit 1 422 # test upgrade from client-side apply to server-side apply 423 kubectl apply --server-side -f hack/testdata/pod-apply.yaml "${kube_flags[@]:?}" 424 # Post-Condition: pod "test-pod" has configuration annotation 425 grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get pods test-pod -o yaml "${kube_flags[@]:?}")" 426 output_message=$(kubectl apply view-last-applied pod/test-pod -o json 2>&1 "${kube_flags[@]:?}") 427 kube::test::if_has_string "${output_message}" '"name": "test-pod-applied"' 428 429 kube::log::status "Testing downgrade kubectl server-side apply to client-side apply" 430 # test downgrade from server-side apply to client-side apply 431 kubectl apply --server-side -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 432 # Post-Condition: pod "test-pod" has configuration annotation 433 grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get pods test-pod -o yaml "${kube_flags[@]:?}")" 434 output_message=$(kubectl apply view-last-applied pod/test-pod -o json 2>&1 "${kube_flags[@]:?}") 435 kube::test::if_has_string "${output_message}" '"name": "test-pod-label"' 436 kubectl apply -f hack/testdata/pod-apply.yaml "${kube_flags[@]:?}" 437 438 # clean-up 439 kubectl delete -f hack/testdata/pod.yaml "${kube_flags[@]:?}" 440 441 # Test apply migration 442 443 # Create a configmap in the cluster with client-side apply: 444 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side=false -f - << __EOF__ 445 apiVersion: v1 446 kind: ConfigMap 447 metadata: 448 name: test 449 data: 450 key: value 451 legacy: unused 452 __EOF__ 453 ) 454 455 kube::test::if_has_string "${output_message}" 'configmap/test created' 456 457 # Apply the same manifest with --server-side flag, as per server-side-apply migration instructions: 458 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side -f - << __EOF__ 459 apiVersion: v1 460 kind: ConfigMap 461 metadata: 462 name: test 463 data: 464 key: value 465 legacy: unused 466 __EOF__ 467 ) 468 469 kube::test::if_has_string "${output_message}" 'configmap/test serverside-applied' 470 471 # Apply the object a third time using server-side-apply, but this time removing 472 # a field and adding a field. Old versions of kubectl would not allow the field 473 # to be removed 474 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side -f - << __EOF__ 475 apiVersion: v1 476 kind: ConfigMap 477 metadata: 478 name: test 479 data: 480 key: value 481 ssaKey: ssaValue 482 __EOF__ 483 ) 484 485 kube::test::if_has_string "${output_message}" 'configmap/test serverside-applied' 486 487 # Fetch the object and check to see that it does not have a field 'legacy' 488 kube::test::get_object_assert "configmap test" "{{ .data.key }}" 'value' 489 kube::test::get_object_assert "configmap test" "{{ .data.legacy }}" '<no value>' 490 kube::test::get_object_assert "configmap test" "{{ .data.ssaKey }}" 'ssaValue' 491 492 # CSA the object after it has been server-side-applied and had a field removed 493 # Add new key with client-side-apply. Also removes the field from server-side-apply 494 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side=false -f - << __EOF__ 495 apiVersion: v1 496 kind: ConfigMap 497 metadata: 498 name: test 499 data: 500 key: value 501 newKey: newValue 502 __EOF__ 503 ) 504 505 kube::test::get_object_assert "configmap test" "{{ .data.key }}" 'value' 506 kube::test::get_object_assert "configmap test" "{{ .data.newKey }}" 'newValue' 507 kube::test::get_object_assert "configmap test" "{{ .data.ssaKey }}" '<no value>' 508 509 # SSA the object without the field added above by CSA. Show that the object 510 # on the server has had the field removed 511 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side -f - << __EOF__ 512 apiVersion: v1 513 kind: ConfigMap 514 metadata: 515 name: test 516 data: 517 key: value 518 ssaKey: ssaValue 519 __EOF__ 520 ) 521 522 # Fetch the object and check to see that it does not have a field 'newKey' 523 kube::test::get_object_assert "configmap test" "{{ .data.key }}" 'value' 524 kube::test::get_object_assert "configmap test" "{{ .data.newKey }}" '<no value>' 525 kube::test::get_object_assert "configmap test" "{{ .data.ssaKey }}" 'ssaValue' 526 527 # Show that kubectl diff --server-side also functions after a migration 528 output_message=$(kubectl diff "${kube_flags[@]:?}" --server-side -f - << __EOF__ || test $? -eq 1 529 apiVersion: v1 530 kind: ConfigMap 531 metadata: 532 name: test 533 annotations: 534 newAnnotation: newValue 535 data: 536 key: value 537 newKey: newValue 538 __EOF__ 539 ) 540 kube::test::if_has_string "${output_message}" '+ newKey: newValue' 541 kube::test::if_has_string "${output_message}" '+ newAnnotation: newValue' 542 543 # clean-up 544 kubectl "${kube_flags[@]:?}" delete configmap test 545 546 ## Test to show that supplying a custom field manager to kubectl apply 547 # does not prevent migration from client-side-apply to server-side-apply 548 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side=false --field-manager=myfm -f - << __EOF__ 549 apiVersion: v1 550 data: 551 key: value1 552 legacy: value2 553 kind: ConfigMap 554 metadata: 555 name: ssa-test 556 __EOF__ 557 ) 558 kube::test::if_has_string "$output_message" "configmap/ssa-test created" 559 kube::test::get_object_assert "configmap ssa-test" "{{ .data.key }}" 'value1' 560 561 # show that after client-side applying with a custom field manager, the 562 # last-applied-annotation is present 563 grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get configmap ssa-test -o yaml "${kube_flags[@]:?}")" 564 565 # Migrate to server-side-apply by applying the same object 566 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side=true --field-manager=myfm -f - << __EOF__ 567 apiVersion: v1 568 data: 569 key: value1 570 legacy: value2 571 kind: ConfigMap 572 metadata: 573 name: ssa-test 574 __EOF__ 575 ) 576 kube::test::if_has_string "$output_message" "configmap/ssa-test serverside-applied" 577 kube::test::get_object_assert "configmap ssa-test" "{{ .data.key }}" 'value1' 578 579 # show that after migrating to SSA with a custom field manager, the 580 # last-applied-annotation is dropped 581 ! grep -q kubectl.kubernetes.io/last-applied-configuration <<< "$(kubectl get configmap ssa-test -o yaml "${kube_flags[@]:?}")" || exit 1 582 583 # Change a field without having any conflict and also drop a field in the same patch 584 output_message=$(kubectl "${kube_flags[@]:?}" apply --server-side=true --field-manager=myfm -f - << __EOF__ 585 apiVersion: v1 586 data: 587 key: value2 588 kind: ConfigMap 589 metadata: 590 name: ssa-test 591 __EOF__ 592 ) 593 kube::test::if_has_string "$output_message" "configmap/ssa-test serverside-applied" 594 kube::test::get_object_assert "configmap ssa-test" "{{ .data.key }}" 'value2' 595 kube::test::get_object_assert "configmap ssa-test" "{{ .data.legacy }}" '<no value>' 596 597 # Clean up 598 kubectl delete configmap ssa-test 599 600 ## kubectl apply dry-run on CR 601 # Create CRD 602 kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ 603 { 604 "kind": "CustomResourceDefinition", 605 "apiVersion": "apiextensions.k8s.io/v1", 606 "metadata": { 607 "name": "resources.mygroup.example.com" 608 }, 609 "spec": { 610 "group": "mygroup.example.com", 611 "scope": "Namespaced", 612 "names": { 613 "plural": "resources", 614 "singular": "resource", 615 "kind": "Kind", 616 "listKind": "KindList" 617 }, 618 "versions": [ 619 { 620 "name": "v1alpha1", 621 "served": true, 622 "storage": true, 623 "schema": { 624 "openAPIV3Schema": { 625 "x-kubernetes-preserve-unknown-fields": true, 626 "type": "object" 627 } 628 } 629 } 630 ] 631 } 632 } 633 __EOF__ 634 635 # Ensure the API server has recognized and started serving the associated CR API 636 local tries=5 637 for i in $(seq 1 $tries); do 638 local output 639 output=$(kubectl "${kube_flags[@]:?}" api-resources --api-group mygroup.example.com -oname || true) 640 if kube::test::if_has_string "$output" resources.mygroup.example.com; then 641 break 642 fi 643 echo "${i}: Waiting for CR API to be available" 644 sleep "$i" 645 done 646 647 # Dry-run create the CR 648 kubectl "${kube_flags[@]:?}" apply --server-side --dry-run=server -f hack/testdata/CRD/resource.yaml "${kube_flags[@]:?}" 649 # Make sure that the CR doesn't exist 650 ! kubectl "${kube_flags[@]:?}" get resource/myobj 2>/dev/null || exit 1 651 652 # clean-up 653 kubectl "${kube_flags[@]:?}" delete customresourcedefinition resources.mygroup.example.com 654 655 set +o nounset 656 set +o errexit 657 }