k8s.io/kubernetes@v1.29.3/test/cmd/crd.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 run_crd_tests() { 22 set -o nounset 23 set -o errexit 24 25 create_and_use_new_namespace 26 kube::log::status "Testing kubectl crd" 27 kubectl "${kube_flags_with_token[@]:?}" create -f - << __EOF__ 28 { 29 "kind": "CustomResourceDefinition", 30 "apiVersion": "apiextensions.k8s.io/v1", 31 "metadata": { 32 "name": "foos.company.com" 33 }, 34 "spec": { 35 "group": "company.com", 36 "scope": "Namespaced", 37 "names": { 38 "plural": "foos", 39 "kind": "Foo" 40 }, 41 "versions": [ 42 { 43 "name": "v1", 44 "served": true, 45 "storage": true, 46 "schema": { 47 "openAPIV3Schema": { 48 "type": "object", 49 "properties": { 50 "metadata": {"type": "object"}, 51 "nestedField": { 52 "type": "object", 53 "properties": { 54 "someSubfield": {"type": "string"}, 55 "otherSubfield": {"type": "string"}, 56 "newSubfield": {"type": "string"} 57 } 58 }, 59 "otherField": {"type": "string"}, 60 "someField": {"type": "string"}, 61 "newField": {"type": "string"}, 62 "patched": {"type": "string"} 63 } 64 } 65 } 66 } 67 ] 68 } 69 } 70 __EOF__ 71 72 # Post-Condition: assertion object exist 73 kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{if eq ${id_field:?} \"foos.company.com\"}}{{$id_field}}:{{end}}{{end}}" 'foos.company.com:' 74 75 kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ 76 { 77 "kind": "CustomResourceDefinition", 78 "apiVersion": "apiextensions.k8s.io/v1", 79 "metadata": { 80 "name": "bars.company.com" 81 }, 82 "spec": { 83 "group": "company.com", 84 "scope": "Namespaced", 85 "names": { 86 "plural": "bars", 87 "kind": "Bar" 88 }, 89 "versions": [ 90 { 91 "name": "v1", 92 "served": true, 93 "storage": true, 94 "schema": { 95 "openAPIV3Schema": { 96 "x-kubernetes-preserve-unknown-fields": true, 97 "type": "object" 98 } 99 } 100 } 101 ] 102 } 103 } 104 __EOF__ 105 106 # Post-Condition: assertion object exist 107 kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{if eq $id_field \"foos.company.com\" \"bars.company.com\"}}{{$id_field}}:{{end}}{{end}}" 'bars.company.com:foos.company.com:' 108 109 # This test ensures that the name printer is able to output a resource 110 # in the proper "kind.group/resource_name" format, and that the 111 # resource builder is able to resolve a GVK when a kind.group pair is given. 112 kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ 113 { 114 "kind": "CustomResourceDefinition", 115 "apiVersion": "apiextensions.k8s.io/v1", 116 "metadata": { 117 "name": "resources.mygroup.example.com" 118 }, 119 "spec": { 120 "group": "mygroup.example.com", 121 "scope": "Namespaced", 122 "names": { 123 "plural": "resources", 124 "singular": "resource", 125 "kind": "Kind", 126 "listKind": "KindList" 127 }, 128 "versions": [ 129 { 130 "name": "v1alpha1", 131 "served": true, 132 "storage": true, 133 "schema": { 134 "openAPIV3Schema": { 135 "x-kubernetes-preserve-unknown-fields": true, 136 "type": "object" 137 } 138 } 139 } 140 ] 141 } 142 } 143 __EOF__ 144 145 # Post-Condition: assertion crd with non-matching kind and resource exists 146 kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{if eq $id_field \"foos.company.com\" \"bars.company.com\" \"resources.mygroup.example.com\"}}{{$id_field}}:{{end}}{{end}}" 'bars.company.com:foos.company.com:resources.mygroup.example.com:' 147 148 # This test ensures that we can create complex validation without client-side validation complaining 149 kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ 150 { 151 "kind": "CustomResourceDefinition", 152 "apiVersion": "apiextensions.k8s.io/v1", 153 "metadata": { 154 "name": "validfoos.company.com" 155 }, 156 "spec": { 157 "group": "company.com", 158 "scope": "Namespaced", 159 "names": { 160 "plural": "validfoos", 161 "kind": "ValidFoo" 162 }, 163 "versions": [ 164 { 165 "name": "v1", 166 "served": true, 167 "storage": true, 168 "schema": { 169 "openAPIV3Schema": { 170 "type": "object", 171 "properties": { 172 "spec": { 173 "type": "array", 174 "items": { 175 "type": "number" 176 } 177 } 178 } 179 } 180 } 181 } 182 ] 183 } 184 } 185 __EOF__ 186 187 # Post-Condition: assertion crd with non-matching kind and resource exists 188 kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{if eq $id_field \"foos.company.com\" \"bars.company.com\" \"resources.mygroup.example.com\" \"validfoos.company.com\"}}{{$id_field}}:{{end}}{{end}}" 'bars.company.com:foos.company.com:resources.mygroup.example.com:validfoos.company.com:' 189 190 run_non_native_resource_tests 191 192 # teardown 193 kubectl delete customresourcedefinitions/foos.company.com "${kube_flags_with_token[@]}" 194 kubectl delete customresourcedefinitions/bars.company.com "${kube_flags_with_token[@]}" 195 kubectl delete customresourcedefinitions/resources.mygroup.example.com "${kube_flags_with_token[@]}" 196 kubectl delete customresourcedefinitions/validfoos.company.com "${kube_flags_with_token[@]}" 197 198 set +o nounset 199 set +o errexit 200 } 201 202 kube::util::non_native_resources() { 203 local times 204 local wait 205 local failed 206 times=30 207 wait=10 208 for _ in $(seq 1 $times); do 209 failed="" 210 kubectl "${kube_flags[@]:?}" get --raw '/apis/company.com/v1' || failed=true 211 kubectl "${kube_flags[@]}" get --raw '/apis/company.com/v1/foos' || failed=true 212 kubectl "${kube_flags[@]}" get --raw '/apis/company.com/v1/bars' || failed=true 213 214 if [ -z "${failed}" ]; then 215 return 0 216 fi 217 sleep ${wait} 218 done 219 220 kube::log::error "Timed out waiting for non-native-resources; tried ${times} waiting ${wait}s between each" 221 return 1 222 } 223 224 run_non_native_resource_tests() { 225 set -o nounset 226 set -o errexit 227 228 create_and_use_new_namespace 229 kube::log::status "Testing kubectl non-native resources" 230 kube::util::non_native_resources 231 232 # Test that we can list this new CustomResource (foos) 233 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' 234 235 # Test that we can list this new CustomResource (bars) 236 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' 237 238 # Test that we can list this new CustomResource (resources) 239 kube::test::get_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" '' 240 241 # Test that we can create a new resource of type Kind 242 kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/resource.yaml "${kube_flags[@]}" 243 244 # Test that -o name returns kind.group/resourcename 245 output_message=$(kubectl "${kube_flags[@]}" get resource/myobj -o name) 246 kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' 247 248 output_message=$(kubectl "${kube_flags[@]}" get resources/myobj -o name) 249 kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' 250 251 output_message=$(kubectl "${kube_flags[@]}" get kind.mygroup.example.com/myobj -o name) 252 kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' 253 254 # Delete the resource with cascading strategy background. 255 kubectl "${kube_flags[@]}" delete resources myobj --cascade=background 256 257 # Make sure it's gone 258 kube::test::wait_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" '' 259 260 # Test that we can create a new resource of type Foo 261 kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/foo.yaml "${kube_flags[@]}" 262 kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/foo-2.yaml "${kube_flags[@]}" 263 264 # Test that we can list this new custom resource 265 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' 266 267 # Test alternate forms 268 kube::test::get_object_assert foo "{{range.items}}{{$id_field}}:{{end}}" 'test:' 269 kube::test::get_object_assert foos.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:' 270 kube::test::get_object_assert foos.v1.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:' 271 272 # Test all printers, with lists and individual items 273 kube::log::status "Testing CustomResource printing" 274 kubectl "${kube_flags[@]}" get foos 275 kubectl "${kube_flags[@]}" get foos/test 276 kubectl "${kube_flags[@]}" get foos -o name 277 kubectl "${kube_flags[@]}" get foos/test -o name 278 kubectl "${kube_flags[@]}" get foos -o wide 279 kubectl "${kube_flags[@]}" get foos/test -o wide 280 kubectl "${kube_flags[@]}" get foos -o json 281 kubectl "${kube_flags[@]}" get foos/test -o json 282 kubectl "${kube_flags[@]}" get foos -o yaml 283 kubectl "${kube_flags[@]}" get foos/test -o yaml 284 kubectl "${kube_flags[@]}" get foos -o "jsonpath={.items[*].someField}" --allow-missing-template-keys=false 285 kubectl "${kube_flags[@]}" get foos/test -o "jsonpath={.someField}" --allow-missing-template-keys=false 286 kubectl "${kube_flags[@]}" get foos -o "go-template={{range .items}}{{.someField}}{{end}}" --allow-missing-template-keys=false 287 kubectl "${kube_flags[@]}" get foos/test -o "go-template={{.someField}}" --allow-missing-template-keys=false 288 output_message=$(kubectl "${kube_flags[@]}" get foos/test -o name) 289 kube::test::if_has_string "${output_message}" 'foo.company.com/test' 290 291 # Test patching 292 kube::log::status "Testing CustomResource patching" 293 kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value1"}' --type=merge 294 kube::test::get_object_assert foos/test "{{.patched}}" 'value1' 295 kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value2"}' --type=merge --record 296 kube::test::get_object_assert foos/test "{{.patched}}" 'value2' 297 kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":null}' --type=merge --record 298 kube::test::get_object_assert foos/test "{{.patched}}" '<no value>' 299 # Get local version 300 CRD_RESOURCE_FILE="${KUBE_TEMP}/crd-foos-test.json" 301 kubectl "${kube_flags[@]}" get foos/test -o json > "${CRD_RESOURCE_FILE}" 302 # cannot apply strategic patch locally 303 CRD_PATCH_ERROR_FILE="${KUBE_TEMP}/crd-foos-test-error" 304 ! kubectl "${kube_flags[@]}" patch --local -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' 2> "${CRD_PATCH_ERROR_FILE}" || exit 1 305 if grep -q "try --type merge" "${CRD_PATCH_ERROR_FILE}"; then 306 kube::log::status "\"kubectl patch --local\" returns error as expected for CustomResource: $(cat "${CRD_PATCH_ERROR_FILE}")" 307 else 308 kube::log::status "\"kubectl patch --local\" returns unexpected error or non-error: $(cat "${CRD_PATCH_ERROR_FILE}")" 309 exit 1 310 fi 311 # can apply merge patch locally 312 kubectl "${kube_flags[@]}" patch --local -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json 313 # can apply merge patch remotely 314 kubectl "${kube_flags[@]}" patch --record -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json 315 kube::test::get_object_assert foos/test "{{.patched}}" 'value3' 316 rm "${CRD_RESOURCE_FILE}" 317 rm "${CRD_PATCH_ERROR_FILE}" 318 319 # Test labeling 320 kube::log::status "Testing CustomResource labeling" 321 kubectl "${kube_flags[@]}" label foos --all listlabel=true 322 kubectl "${kube_flags[@]}" label foo/test itemlabel=true 323 kubectl "${kube_flags[@]}" label --all --all-namespaces foo allnsLabel=true 324 # make sure all instances in different namespaces got the annotation 325 kubectl "${kube_flags[@]}" get foo/test -oyaml | grep allnsLabel 326 kubectl "${kube_flags[@]}" get -n default foo -oyaml | grep allnsLabel 327 328 # Test annotating 329 kube::log::status "Testing CustomResource annotating" 330 kubectl "${kube_flags[@]}" annotate foos --all listannotation=true 331 kubectl "${kube_flags[@]}" annotate foo/test itemannotation=true 332 kubectl "${kube_flags[@]}" annotate --all --all-namespaces foo allnsannotation=true 333 # make sure all instances in different namespaces got the annotation 334 kubectl "${kube_flags[@]}" get foo/test -oyaml | grep allnsannotation 335 kubectl "${kube_flags[@]}" get -n default foo -oyaml | grep allnsannotation 336 337 # Test describing 338 kube::log::status "Testing CustomResource describing" 339 kubectl "${kube_flags[@]}" describe foos 340 kubectl "${kube_flags[@]}" describe foos/test 341 kubectl "${kube_flags[@]}" describe foos | grep listlabel=true 342 kubectl "${kube_flags[@]}" describe foos | grep itemlabel=true 343 # Describe command should respect the chunk size parameter 344 kube::test::describe_resource_chunk_size_assert customresourcedefinitions events 345 kube::test::describe_resource_chunk_size_assert foos events 346 347 # Delete the resource with cascading strategy background. 348 kubectl "${kube_flags[@]}" delete foos test --cascade=background 349 350 # Make sure it's gone 351 kube::test::wait_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' 352 353 # Test that we can create a new resource of type Bar 354 kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/bar.yaml "${kube_flags[@]}" 355 356 # Test that we can list this new custom resource 357 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test:' 358 359 # Test that we can watch the resource. 360 # Start watcher in background with process substitution, 361 # so we can read from stdout asynchronously. 362 kube::log::status "Testing CustomResource watching" 363 exec 3< <(kubectl "${kube_flags[@]}" get bars --request-timeout=1m --watch-only -o name & echo $! ; wait) 364 local watch_pid 365 read -r <&3 watch_pid 366 367 # We can't be sure when the watch gets established, 368 # so keep triggering events (in the background) until something comes through. 369 local tries=0 370 while [ ${tries} -lt 10 ]; do 371 tries=$((tries+1)) 372 kubectl "${kube_flags[@]}" patch bars/test -p "{\"patched\":\"${tries}\"}" --type=merge 373 sleep 1 374 done & 375 local patch_pid=$! 376 377 # Wait up to 30s for a complete line of output. 378 local watch_output 379 read -r <&3 -t 30 watch_output 380 # Stop the watcher and the patch loop. 381 kill -9 "${watch_pid}" 382 kill -9 "${patch_pid}" 383 kube::test::if_has_string "${watch_output}" 'bar.company.com/test' 384 385 # Delete the resource with cascading strategy orphan. 386 kubectl "${kube_flags[@]}" delete bars test --cascade=orphan 387 388 # Make sure it's gone 389 kube::test::wait_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' 390 391 # Test that we can create single item via apply 392 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo.yaml 393 394 # Test that we have create a foo named test 395 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' 396 397 # Test that the field has the expected value 398 kube::test::get_object_assert foos/test '{{.someField}}' 'field1' 399 400 # Test that apply an empty patch doesn't change fields 401 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo.yaml 402 403 # Test that the field has the same value after re-apply 404 kube::test::get_object_assert foos/test '{{.someField}}' 'field1' 405 406 # Test that apply has updated the subfield 407 kube::test::get_object_assert foos/test '{{.nestedField.someSubfield}}' 'subfield1' 408 409 # Update a subfield and then apply the change 410 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-updated-subfield.yaml 411 412 # Test that apply has updated the subfield 413 kube::test::get_object_assert foos/test '{{.nestedField.someSubfield}}' 'modifiedSubfield' 414 415 # Test that the field has the expected value 416 kube::test::get_object_assert foos/test '{{.nestedField.otherSubfield}}' 'subfield2' 417 418 # Delete a subfield and then apply the change 419 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-deleted-subfield.yaml 420 421 # Test that apply has deleted the field 422 kube::test::get_object_assert foos/test '{{.nestedField.otherSubfield}}' '<no value>' 423 424 # Test that the field does not exist 425 kube::test::get_object_assert foos/test '{{.nestedField.newSubfield}}' '<no value>' 426 427 # Add a field and then apply the change 428 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-added-subfield.yaml 429 430 # Test that apply has added the field 431 kube::test::get_object_assert foos/test '{{.nestedField.newSubfield}}' 'subfield3' 432 433 # Delete the resource 434 kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/foo.yaml 435 436 # Make sure it's gone 437 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' 438 439 # Test that we can create list via apply 440 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list.yaml 441 442 # Test that we have create a foo and a bar from a list 443 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test-list:' 444 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test-list:' 445 446 # Test that the field has the expected value 447 kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1' 448 kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1' 449 450 # Test that re-apply an list doesn't change anything 451 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list.yaml 452 453 # Test that the field has the same value after re-apply 454 kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1' 455 kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1' 456 457 # Test that the fields have the expected value 458 kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1' 459 kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1' 460 461 # Update fields and then apply the change 462 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-updated-field.yaml 463 464 # Test that apply has updated the fields 465 kube::test::get_object_assert foos/test-list '{{.someField}}' 'modifiedField' 466 kube::test::get_object_assert bars/test-list '{{.someField}}' 'modifiedField' 467 468 # Test that the field has the expected value 469 kube::test::get_object_assert foos/test-list '{{.otherField}}' 'field2' 470 kube::test::get_object_assert bars/test-list '{{.otherField}}' 'field2' 471 472 # Delete fields and then apply the change 473 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-deleted-field.yaml 474 475 # Test that apply has deleted the fields 476 kube::test::get_object_assert foos/test-list '{{.otherField}}' '<no value>' 477 kube::test::get_object_assert bars/test-list '{{.otherField}}' '<no value>' 478 479 # Test that the fields does not exist 480 kube::test::get_object_assert foos/test-list '{{.newField}}' '<no value>' 481 kube::test::get_object_assert bars/test-list '{{.newField}}' '<no value>' 482 483 # Add a field and then apply the change 484 kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-added-field.yaml 485 486 # Test that apply has added the field 487 kube::test::get_object_assert foos/test-list '{{.newField}}' 'field3' 488 kube::test::get_object_assert bars/test-list '{{.newField}}' 'field3' 489 490 # Delete the resource 491 kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/multi-crd-list.yaml 492 493 # Make sure it's gone 494 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' 495 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' 496 497 ## kubectl apply --prune 498 # Test that no foo or bar exist 499 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' 500 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' 501 502 # apply --prune on foo.yaml that has foo/test 503 kubectl apply --prune -l pruneGroup=true -f hack/testdata/CRD/foo.yaml "${kube_flags[@]}" --prune-whitelist=company.com/v1/Foo --prune-whitelist=company.com/v1/Bar 504 # check right crds exist 505 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' 506 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' 507 508 # apply --prune on bar.yaml that has bar/test 509 kubectl apply --prune -l pruneGroup=true -f hack/testdata/CRD/bar.yaml "${kube_flags[@]}" --prune-whitelist=company.com/v1/Foo --prune-whitelist=company.com/v1/Bar 510 # check right crds exist 511 kube::test::wait_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' 512 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test:' 513 514 # Delete the resource 515 kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/bar.yaml 516 517 # Make sure it's gone 518 kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' 519 kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' 520 521 # Test 'kubectl create' with namespace, and namespace cleanup. 522 kubectl "${kube_flags[@]}" create namespace non-native-resources 523 kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/bar.yaml --namespace=non-native-resources 524 kube::test::get_object_assert bars '{{len .items}}' '1' --namespace=non-native-resources 525 kubectl "${kube_flags[@]}" delete namespace non-native-resources 526 # Make sure objects go away. 527 kube::test::wait_object_assert bars '{{len .items}}' '0' --namespace=non-native-resources 528 # Make sure namespace goes away. 529 local tries=0 530 while kubectl "${kube_flags[@]}" get namespace non-native-resources && [ ${tries} -lt 10 ]; do 531 tries=$((tries+1)) 532 sleep ${tries} 533 done 534 535 set +o nounset 536 set +o errexit 537 }