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  }