#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail run_crd_tests() { set -o nounset set -o errexit create_and_use_new_namespace kube::log::status "Testing kubectl crd" kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ { "kind": "CustomResourceDefinition", "apiVersion": "apiextensions.k8s.io/v1beta1", "metadata": { "name": "foos.company.com" }, "spec": { "group": "company.com", "version": "v1", "scope": "Namespaced", "names": { "plural": "foos", "kind": "Foo" } } } __EOF__ # Post-Condition: assertion object exist kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{if eq $id_field \\\"foos.company.com\\\"}}{{$id_field}}:{{end}}{{end}}" 'foos.company.com:' kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ { "kind": "CustomResourceDefinition", "apiVersion": "apiextensions.k8s.io/v1beta1", "metadata": { "name": "bars.company.com" }, "spec": { "group": "company.com", "version": "v1", "scope": "Namespaced", "names": { "plural": "bars", "kind": "Bar" } } } __EOF__ # Post-Condition: assertion object exist 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:' # This test ensures that the name printer is able to output a resource # in the proper "kind.group/resource_name" format, and that the # resource builder is able to resolve a GVK when a kind.group pair is given. kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ { "kind": "CustomResourceDefinition", "apiVersion": "apiextensions.k8s.io/v1beta1", "metadata": { "name": "resources.mygroup.example.com" }, "spec": { "group": "mygroup.example.com", "version": "v1alpha1", "scope": "Namespaced", "names": { "plural": "resources", "singular": "resource", "kind": "Kind", "listKind": "KindList" } } } __EOF__ # Post-Condition: assertion crd with non-matching kind and resource exists 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:' # This test ensures that we can create complex validation without client-side validation complaining kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__ { "kind": "CustomResourceDefinition", "apiVersion": "apiextensions.k8s.io/v1beta1", "metadata": { "name": "validfoos.company.com" }, "spec": { "group": "company.com", "version": "v1", "scope": "Namespaced", "names": { "plural": "validfoos", "kind": "ValidFoo" }, "validation": { "openAPIV3Schema": { "properties": { "spec": { "type": "array", "items": { "type": "number" } } } } } } } __EOF__ # Post-Condition: assertion crd with non-matching kind and resource exists 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:' run_non_native_resource_tests # teardown kubectl delete customresourcedefinitions/foos.company.com "${kube_flags_with_token[@]}" kubectl delete customresourcedefinitions/bars.company.com "${kube_flags_with_token[@]}" kubectl delete customresourcedefinitions/resources.mygroup.example.com "${kube_flags_with_token[@]}" kubectl delete customresourcedefinitions/validfoos.company.com "${kube_flags_with_token[@]}" set +o nounset set +o errexit } kube::util::non_native_resources() { local times local wait local failed times=30 wait=10 local i for i in $(seq 1 $times); do failed="" kubectl "${kube_flags[@]}" get --raw '/apis/company.com/v1' || failed=true kubectl "${kube_flags[@]}" get --raw '/apis/company.com/v1/foos' || failed=true kubectl "${kube_flags[@]}" get --raw '/apis/company.com/v1/bars' || failed=true if [ -z "${failed}" ]; then return 0 fi sleep ${wait} done kube::log::error "Timed out waiting for non-native-resources; tried ${times} waiting ${wait}s between each" return 1 } run_non_native_resource_tests() { set -o nounset set -o errexit create_and_use_new_namespace kube::log::status "Testing kubectl non-native resources" kube::util::non_native_resources # Test that we can list this new CustomResource (foos) kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' # Test that we can list this new CustomResource (bars) kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' # Test that we can list this new CustomResource (resources) kube::test::get_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" '' # Test that we can create a new resource of type Kind kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/resource.yaml "${kube_flags[@]}" # Test that -o name returns kind.group/resourcename output_message=$(kubectl "${kube_flags[@]}" get resource/myobj -o name) kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' output_message=$(kubectl "${kube_flags[@]}" get resources/myobj -o name) kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' output_message=$(kubectl "${kube_flags[@]}" get kind.mygroup.example.com/myobj -o name) kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj' # Delete the resource with cascade. kubectl "${kube_flags[@]}" delete resources myobj --cascade=true # Make sure it's gone kube::test::wait_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" '' # Test that we can create a new resource of type Foo kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/foo.yaml "${kube_flags[@]}" # Test that we can list this new custom resource kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' # Test alternate forms kube::test::get_object_assert foo "{{range.items}}{{$id_field}}:{{end}}" 'test:' kube::test::get_object_assert foos.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:' kube::test::get_object_assert foos.v1.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:' # Test all printers, with lists and individual items kube::log::status "Testing CustomResource printing" kubectl "${kube_flags[@]}" get foos kubectl "${kube_flags[@]}" get foos/test kubectl "${kube_flags[@]}" get foos -o name kubectl "${kube_flags[@]}" get foos/test -o name kubectl "${kube_flags[@]}" get foos -o wide kubectl "${kube_flags[@]}" get foos/test -o wide kubectl "${kube_flags[@]}" get foos -o json kubectl "${kube_flags[@]}" get foos/test -o json kubectl "${kube_flags[@]}" get foos -o yaml kubectl "${kube_flags[@]}" get foos/test -o yaml kubectl "${kube_flags[@]}" get foos -o "jsonpath={.items[*].someField}" --allow-missing-template-keys=false kubectl "${kube_flags[@]}" get foos/test -o "jsonpath={.someField}" --allow-missing-template-keys=false kubectl "${kube_flags[@]}" get foos -o "go-template={{range .items}}{{.someField}}{{end}}" --allow-missing-template-keys=false kubectl "${kube_flags[@]}" get foos/test -o "go-template={{.someField}}" --allow-missing-template-keys=false output_message=$(kubectl "${kube_flags[@]}" get foos/test -o name) kube::test::if_has_string "${output_message}" 'foo.company.com/test' # Test patching kube::log::status "Testing CustomResource patching" kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value1"}' --type=merge kube::test::get_object_assert foos/test "{{.patched}}" 'value1' kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value2"}' --type=merge --record kube::test::get_object_assert foos/test "{{.patched}}" 'value2' kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":null}' --type=merge --record kube::test::get_object_assert foos/test "{{.patched}}" '' # Get local version CRD_RESOURCE_FILE="${KUBE_TEMP}/crd-foos-test.json" kubectl "${kube_flags[@]}" get foos/test -o json > "${CRD_RESOURCE_FILE}" # cannot apply strategic patch locally CRD_PATCH_ERROR_FILE="${KUBE_TEMP}/crd-foos-test-error" ! kubectl "${kube_flags[@]}" patch --local -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' 2> "${CRD_PATCH_ERROR_FILE}" if grep -q "try --type merge" "${CRD_PATCH_ERROR_FILE}"; then kube::log::status "\"kubectl patch --local\" returns error as expected for CustomResource: $(cat ${CRD_PATCH_ERROR_FILE})" else kube::log::status "\"kubectl patch --local\" returns unexpected error or non-error: $(cat ${CRD_PATCH_ERROR_FILE})" exit 1 fi # can apply merge patch locally kubectl "${kube_flags[@]}" patch --local -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json # can apply merge patch remotely kubectl "${kube_flags[@]}" patch --record -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json kube::test::get_object_assert foos/test "{{.patched}}" 'value3' rm "${CRD_RESOURCE_FILE}" rm "${CRD_PATCH_ERROR_FILE}" # Test labeling kube::log::status "Testing CustomResource labeling" kubectl "${kube_flags[@]}" label foos --all listlabel=true kubectl "${kube_flags[@]}" label foo/test itemlabel=true # Test annotating kube::log::status "Testing CustomResource annotating" kubectl "${kube_flags[@]}" annotate foos --all listannotation=true kubectl "${kube_flags[@]}" annotate foo/test itemannotation=true # Test describing kube::log::status "Testing CustomResource describing" kubectl "${kube_flags[@]}" describe foos kubectl "${kube_flags[@]}" describe foos/test kubectl "${kube_flags[@]}" describe foos | grep listlabel=true kubectl "${kube_flags[@]}" describe foos | grep itemlabel=true # Delete the resource with cascade. kubectl "${kube_flags[@]}" delete foos test --cascade=true # Make sure it's gone kube::test::wait_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' # Test that we can create a new resource of type Bar kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/bar.yaml "${kube_flags[@]}" # Test that we can list this new custom resource kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test:' # Test that we can watch the resource. # Start watcher in background with process substitution, # so we can read from stdout asynchronously. kube::log::status "Testing CustomResource watching" exec 3< <(kubectl "${kube_flags[@]}" get bars --request-timeout=1m --watch-only -o name & echo $! ; wait) local watch_pid read <&3 watch_pid # We can't be sure when the watch gets established, # so keep triggering events (in the background) until something comes through. local tries=0 while [ ${tries} -lt 10 ]; do tries=$((tries+1)) kubectl "${kube_flags[@]}" patch bars/test -p "{\"patched\":\"${tries}\"}" --type=merge sleep 1 done & local patch_pid=$! # Wait up to 30s for a complete line of output. local watch_output read <&3 -t 30 watch_output # Stop the watcher and the patch loop. kill -9 ${watch_pid} kill -9 ${patch_pid} kube::test::if_has_string "${watch_output}" 'bar.company.com/test' # Delete the resource without cascade. kubectl "${kube_flags[@]}" delete bars test --cascade=false # Make sure it's gone kube::test::wait_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' # Test that we can create single item via apply kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo.yaml # Test that we have create a foo named test kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' # Test that the field has the expected value kube::test::get_object_assert foos/test '{{.someField}}' 'field1' # Test that apply an empty patch doesn't change fields kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo.yaml # Test that the field has the same value after re-apply kube::test::get_object_assert foos/test '{{.someField}}' 'field1' # Test that apply has updated the subfield kube::test::get_object_assert foos/test '{{.nestedField.someSubfield}}' 'subfield1' # Update a subfield and then apply the change kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-updated-subfield.yaml # Test that apply has updated the subfield kube::test::get_object_assert foos/test '{{.nestedField.someSubfield}}' 'modifiedSubfield' # Test that the field has the expected value kube::test::get_object_assert foos/test '{{.nestedField.otherSubfield}}' 'subfield2' # Delete a subfield and then apply the change kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-deleted-subfield.yaml # Test that apply has deleted the field kube::test::get_object_assert foos/test '{{.nestedField.otherSubfield}}' '' # Test that the field does not exist kube::test::get_object_assert foos/test '{{.nestedField.newSubfield}}' '' # Add a field and then apply the change kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-added-subfield.yaml # Test that apply has added the field kube::test::get_object_assert foos/test '{{.nestedField.newSubfield}}' 'subfield3' # Delete the resource kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/foo.yaml # Make sure it's gone kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' # Test that we can create list via apply kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list.yaml # Test that we have create a foo and a bar from a list kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test-list:' kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test-list:' # Test that the field has the expected value kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1' kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1' # Test that re-apply an list doesn't change anything kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list.yaml # Test that the field has the same value after re-apply kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1' kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1' # Test that the fields have the expected value kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1' kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1' # Update fields and then apply the change kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-updated-field.yaml # Test that apply has updated the fields kube::test::get_object_assert foos/test-list '{{.someField}}' 'modifiedField' kube::test::get_object_assert bars/test-list '{{.someField}}' 'modifiedField' # Test that the field has the expected value kube::test::get_object_assert foos/test-list '{{.otherField}}' 'field2' kube::test::get_object_assert bars/test-list '{{.otherField}}' 'field2' # Delete fields and then apply the change kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-deleted-field.yaml # Test that apply has deleted the fields kube::test::get_object_assert foos/test-list '{{.otherField}}' '' kube::test::get_object_assert bars/test-list '{{.otherField}}' '' # Test that the fields does not exist kube::test::get_object_assert foos/test-list '{{.newField}}' '' kube::test::get_object_assert bars/test-list '{{.newField}}' '' # Add a field and then apply the change kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-added-field.yaml # Test that apply has added the field kube::test::get_object_assert foos/test-list '{{.newField}}' 'field3' kube::test::get_object_assert bars/test-list '{{.newField}}' 'field3' # Delete the resource kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/multi-crd-list.yaml # Make sure it's gone kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' ## kubectl apply --prune # Test that no foo or bar exist kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' # apply --prune on foo.yaml that has foo/test 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 # check right crds exist kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' # apply --prune on bar.yaml that has bar/test 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 # check right crds exist kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test:' # Delete the resource kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/bar.yaml # Make sure it's gone kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" '' # Test 'kubectl create' with namespace, and namespace cleanup. kubectl "${kube_flags[@]}" create namespace non-native-resources kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/bar.yaml --namespace=non-native-resources kube::test::get_object_assert bars '{{len .items}}' '1' --namespace=non-native-resources kubectl "${kube_flags[@]}" delete namespace non-native-resources # Make sure objects go away. kube::test::wait_object_assert bars '{{len .items}}' '0' --namespace=non-native-resources # Make sure namespace goes away. local tries=0 while kubectl "${kube_flags[@]}" get namespace non-native-resources && [ ${tries} -lt 10 ]; do tries=$((tries+1)) sleep ${tries} done set +o nounset set +o errexit }