crd.sh 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. #!/usr/bin/env bash
  2. # Copyright 2018 The Kubernetes Authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. set -o errexit
  16. set -o nounset
  17. set -o pipefail
  18. run_crd_tests() {
  19. set -o nounset
  20. set -o errexit
  21. create_and_use_new_namespace
  22. kube::log::status "Testing kubectl crd"
  23. kubectl "${kube_flags_with_token[@]:?}" create -f - << __EOF__
  24. {
  25. "kind": "CustomResourceDefinition",
  26. "apiVersion": "apiextensions.k8s.io/v1beta1",
  27. "metadata": {
  28. "name": "foos.company.com"
  29. },
  30. "spec": {
  31. "group": "company.com",
  32. "version": "v1",
  33. "scope": "Namespaced",
  34. "names": {
  35. "plural": "foos",
  36. "kind": "Foo"
  37. }
  38. }
  39. }
  40. __EOF__
  41. # Post-Condition: assertion object exist
  42. kube::test::get_object_assert customresourcedefinitions "{{range.items}}{{if eq ${id_field:?} \\\"foos.company.com\\\"}}{{$id_field}}:{{end}}{{end}}" 'foos.company.com:'
  43. kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__
  44. {
  45. "kind": "CustomResourceDefinition",
  46. "apiVersion": "apiextensions.k8s.io/v1beta1",
  47. "metadata": {
  48. "name": "bars.company.com"
  49. },
  50. "spec": {
  51. "group": "company.com",
  52. "version": "v1",
  53. "scope": "Namespaced",
  54. "names": {
  55. "plural": "bars",
  56. "kind": "Bar"
  57. }
  58. }
  59. }
  60. __EOF__
  61. # Post-Condition: assertion object exist
  62. 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:'
  63. # This test ensures that the name printer is able to output a resource
  64. # in the proper "kind.group/resource_name" format, and that the
  65. # resource builder is able to resolve a GVK when a kind.group pair is given.
  66. kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__
  67. {
  68. "kind": "CustomResourceDefinition",
  69. "apiVersion": "apiextensions.k8s.io/v1beta1",
  70. "metadata": {
  71. "name": "resources.mygroup.example.com"
  72. },
  73. "spec": {
  74. "group": "mygroup.example.com",
  75. "version": "v1alpha1",
  76. "scope": "Namespaced",
  77. "names": {
  78. "plural": "resources",
  79. "singular": "resource",
  80. "kind": "Kind",
  81. "listKind": "KindList"
  82. }
  83. }
  84. }
  85. __EOF__
  86. # Post-Condition: assertion crd with non-matching kind and resource exists
  87. 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:'
  88. # This test ensures that we can create complex validation without client-side validation complaining
  89. kubectl "${kube_flags_with_token[@]}" create -f - << __EOF__
  90. {
  91. "kind": "CustomResourceDefinition",
  92. "apiVersion": "apiextensions.k8s.io/v1beta1",
  93. "metadata": {
  94. "name": "validfoos.company.com"
  95. },
  96. "spec": {
  97. "group": "company.com",
  98. "version": "v1",
  99. "scope": "Namespaced",
  100. "names": {
  101. "plural": "validfoos",
  102. "kind": "ValidFoo"
  103. },
  104. "validation": {
  105. "openAPIV3Schema": {
  106. "properties": {
  107. "spec": {
  108. "type": "array",
  109. "items": {
  110. "type": "number"
  111. }
  112. }
  113. }
  114. }
  115. }
  116. }
  117. }
  118. __EOF__
  119. # Post-Condition: assertion crd with non-matching kind and resource exists
  120. 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:'
  121. run_non_native_resource_tests
  122. # teardown
  123. kubectl delete customresourcedefinitions/foos.company.com "${kube_flags_with_token[@]}"
  124. kubectl delete customresourcedefinitions/bars.company.com "${kube_flags_with_token[@]}"
  125. kubectl delete customresourcedefinitions/resources.mygroup.example.com "${kube_flags_with_token[@]}"
  126. kubectl delete customresourcedefinitions/validfoos.company.com "${kube_flags_with_token[@]}"
  127. set +o nounset
  128. set +o errexit
  129. }
  130. kube::util::non_native_resources() {
  131. local times
  132. local wait
  133. local failed
  134. times=30
  135. wait=10
  136. for _ in $(seq 1 $times); do
  137. failed=""
  138. kubectl "${kube_flags[@]:?}" get --raw '/apis/company.com/v1' || failed=true
  139. kubectl "${kube_flags[@]}" get --raw '/apis/company.com/v1/foos' || failed=true
  140. kubectl "${kube_flags[@]}" get --raw '/apis/company.com/v1/bars' || failed=true
  141. if [ -z "${failed}" ]; then
  142. return 0
  143. fi
  144. sleep ${wait}
  145. done
  146. kube::log::error "Timed out waiting for non-native-resources; tried ${times} waiting ${wait}s between each"
  147. return 1
  148. }
  149. run_non_native_resource_tests() {
  150. set -o nounset
  151. set -o errexit
  152. create_and_use_new_namespace
  153. kube::log::status "Testing kubectl non-native resources"
  154. kube::util::non_native_resources
  155. # Test that we can list this new CustomResource (foos)
  156. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
  157. # Test that we can list this new CustomResource (bars)
  158. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
  159. # Test that we can list this new CustomResource (resources)
  160. kube::test::get_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" ''
  161. # Test that we can create a new resource of type Kind
  162. kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/resource.yaml "${kube_flags[@]}"
  163. # Test that -o name returns kind.group/resourcename
  164. output_message=$(kubectl "${kube_flags[@]}" get resource/myobj -o name)
  165. kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj'
  166. output_message=$(kubectl "${kube_flags[@]}" get resources/myobj -o name)
  167. kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj'
  168. output_message=$(kubectl "${kube_flags[@]}" get kind.mygroup.example.com/myobj -o name)
  169. kube::test::if_has_string "${output_message}" 'kind.mygroup.example.com/myobj'
  170. # Delete the resource with cascade.
  171. kubectl "${kube_flags[@]}" delete resources myobj --cascade=true
  172. # Make sure it's gone
  173. kube::test::wait_object_assert resources "{{range.items}}{{$id_field}}:{{end}}" ''
  174. # Test that we can create a new resource of type Foo
  175. kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/foo.yaml "${kube_flags[@]}"
  176. # Test that we can list this new custom resource
  177. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  178. # Test alternate forms
  179. kube::test::get_object_assert foo "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  180. kube::test::get_object_assert foos.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  181. kube::test::get_object_assert foos.v1.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  182. # Test all printers, with lists and individual items
  183. kube::log::status "Testing CustomResource printing"
  184. kubectl "${kube_flags[@]}" get foos
  185. kubectl "${kube_flags[@]}" get foos/test
  186. kubectl "${kube_flags[@]}" get foos -o name
  187. kubectl "${kube_flags[@]}" get foos/test -o name
  188. kubectl "${kube_flags[@]}" get foos -o wide
  189. kubectl "${kube_flags[@]}" get foos/test -o wide
  190. kubectl "${kube_flags[@]}" get foos -o json
  191. kubectl "${kube_flags[@]}" get foos/test -o json
  192. kubectl "${kube_flags[@]}" get foos -o yaml
  193. kubectl "${kube_flags[@]}" get foos/test -o yaml
  194. kubectl "${kube_flags[@]}" get foos -o "jsonpath={.items[*].someField}" --allow-missing-template-keys=false
  195. kubectl "${kube_flags[@]}" get foos/test -o "jsonpath={.someField}" --allow-missing-template-keys=false
  196. kubectl "${kube_flags[@]}" get foos -o "go-template={{range .items}}{{.someField}}{{end}}" --allow-missing-template-keys=false
  197. kubectl "${kube_flags[@]}" get foos/test -o "go-template={{.someField}}" --allow-missing-template-keys=false
  198. output_message=$(kubectl "${kube_flags[@]}" get foos/test -o name)
  199. kube::test::if_has_string "${output_message}" 'foo.company.com/test'
  200. # Test patching
  201. kube::log::status "Testing CustomResource patching"
  202. kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value1"}' --type=merge
  203. kube::test::get_object_assert foos/test "{{.patched}}" 'value1'
  204. kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value2"}' --type=merge --record
  205. kube::test::get_object_assert foos/test "{{.patched}}" 'value2'
  206. kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":null}' --type=merge --record
  207. kube::test::get_object_assert foos/test "{{.patched}}" '<no value>'
  208. # Get local version
  209. CRD_RESOURCE_FILE="${KUBE_TEMP}/crd-foos-test.json"
  210. kubectl "${kube_flags[@]}" get foos/test -o json > "${CRD_RESOURCE_FILE}"
  211. # cannot apply strategic patch locally
  212. CRD_PATCH_ERROR_FILE="${KUBE_TEMP}/crd-foos-test-error"
  213. ! kubectl "${kube_flags[@]}" patch --local -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' 2> "${CRD_PATCH_ERROR_FILE}" || exit 1
  214. if grep -q "try --type merge" "${CRD_PATCH_ERROR_FILE}"; then
  215. kube::log::status "\"kubectl patch --local\" returns error as expected for CustomResource: $(cat "${CRD_PATCH_ERROR_FILE}")"
  216. else
  217. kube::log::status "\"kubectl patch --local\" returns unexpected error or non-error: $(cat "${CRD_PATCH_ERROR_FILE}")"
  218. exit 1
  219. fi
  220. # can apply merge patch locally
  221. kubectl "${kube_flags[@]}" patch --local -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json
  222. # can apply merge patch remotely
  223. kubectl "${kube_flags[@]}" patch --record -f "${CRD_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json
  224. kube::test::get_object_assert foos/test "{{.patched}}" 'value3'
  225. rm "${CRD_RESOURCE_FILE}"
  226. rm "${CRD_PATCH_ERROR_FILE}"
  227. # Test labeling
  228. kube::log::status "Testing CustomResource labeling"
  229. kubectl "${kube_flags[@]}" label foos --all listlabel=true
  230. kubectl "${kube_flags[@]}" label foo/test itemlabel=true
  231. # Test annotating
  232. kube::log::status "Testing CustomResource annotating"
  233. kubectl "${kube_flags[@]}" annotate foos --all listannotation=true
  234. kubectl "${kube_flags[@]}" annotate foo/test itemannotation=true
  235. # Test describing
  236. kube::log::status "Testing CustomResource describing"
  237. kubectl "${kube_flags[@]}" describe foos
  238. kubectl "${kube_flags[@]}" describe foos/test
  239. kubectl "${kube_flags[@]}" describe foos | grep listlabel=true
  240. kubectl "${kube_flags[@]}" describe foos | grep itemlabel=true
  241. # Delete the resource with cascade.
  242. kubectl "${kube_flags[@]}" delete foos test --cascade=true
  243. # Make sure it's gone
  244. kube::test::wait_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
  245. # Test that we can create a new resource of type Bar
  246. kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/bar.yaml "${kube_flags[@]}"
  247. # Test that we can list this new custom resource
  248. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  249. # Test that we can watch the resource.
  250. # Start watcher in background with process substitution,
  251. # so we can read from stdout asynchronously.
  252. kube::log::status "Testing CustomResource watching"
  253. exec 3< <(kubectl "${kube_flags[@]}" get bars --request-timeout=1m --watch-only -o name & echo $! ; wait)
  254. local watch_pid
  255. read -r <&3 watch_pid
  256. # We can't be sure when the watch gets established,
  257. # so keep triggering events (in the background) until something comes through.
  258. local tries=0
  259. while [ ${tries} -lt 10 ]; do
  260. tries=$((tries+1))
  261. kubectl "${kube_flags[@]}" patch bars/test -p "{\"patched\":\"${tries}\"}" --type=merge
  262. sleep 1
  263. done &
  264. local patch_pid=$!
  265. # Wait up to 30s for a complete line of output.
  266. local watch_output
  267. read -r <&3 -t 30 watch_output
  268. # Stop the watcher and the patch loop.
  269. kill -9 "${watch_pid}"
  270. kill -9 "${patch_pid}"
  271. kube::test::if_has_string "${watch_output}" 'bar.company.com/test'
  272. # Delete the resource without cascade.
  273. kubectl "${kube_flags[@]}" delete bars test --cascade=false
  274. # Make sure it's gone
  275. kube::test::wait_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
  276. # Test that we can create single item via apply
  277. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo.yaml
  278. # Test that we have create a foo named test
  279. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  280. # Test that the field has the expected value
  281. kube::test::get_object_assert foos/test '{{.someField}}' 'field1'
  282. # Test that apply an empty patch doesn't change fields
  283. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo.yaml
  284. # Test that the field has the same value after re-apply
  285. kube::test::get_object_assert foos/test '{{.someField}}' 'field1'
  286. # Test that apply has updated the subfield
  287. kube::test::get_object_assert foos/test '{{.nestedField.someSubfield}}' 'subfield1'
  288. # Update a subfield and then apply the change
  289. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-updated-subfield.yaml
  290. # Test that apply has updated the subfield
  291. kube::test::get_object_assert foos/test '{{.nestedField.someSubfield}}' 'modifiedSubfield'
  292. # Test that the field has the expected value
  293. kube::test::get_object_assert foos/test '{{.nestedField.otherSubfield}}' 'subfield2'
  294. # Delete a subfield and then apply the change
  295. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-deleted-subfield.yaml
  296. # Test that apply has deleted the field
  297. kube::test::get_object_assert foos/test '{{.nestedField.otherSubfield}}' '<no value>'
  298. # Test that the field does not exist
  299. kube::test::get_object_assert foos/test '{{.nestedField.newSubfield}}' '<no value>'
  300. # Add a field and then apply the change
  301. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/foo-added-subfield.yaml
  302. # Test that apply has added the field
  303. kube::test::get_object_assert foos/test '{{.nestedField.newSubfield}}' 'subfield3'
  304. # Delete the resource
  305. kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/foo.yaml
  306. # Make sure it's gone
  307. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
  308. # Test that we can create list via apply
  309. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list.yaml
  310. # Test that we have create a foo and a bar from a list
  311. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test-list:'
  312. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test-list:'
  313. # Test that the field has the expected value
  314. kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1'
  315. kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1'
  316. # Test that re-apply an list doesn't change anything
  317. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list.yaml
  318. # Test that the field has the same value after re-apply
  319. kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1'
  320. kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1'
  321. # Test that the fields have the expected value
  322. kube::test::get_object_assert foos/test-list '{{.someField}}' 'field1'
  323. kube::test::get_object_assert bars/test-list '{{.someField}}' 'field1'
  324. # Update fields and then apply the change
  325. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-updated-field.yaml
  326. # Test that apply has updated the fields
  327. kube::test::get_object_assert foos/test-list '{{.someField}}' 'modifiedField'
  328. kube::test::get_object_assert bars/test-list '{{.someField}}' 'modifiedField'
  329. # Test that the field has the expected value
  330. kube::test::get_object_assert foos/test-list '{{.otherField}}' 'field2'
  331. kube::test::get_object_assert bars/test-list '{{.otherField}}' 'field2'
  332. # Delete fields and then apply the change
  333. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-deleted-field.yaml
  334. # Test that apply has deleted the fields
  335. kube::test::get_object_assert foos/test-list '{{.otherField}}' '<no value>'
  336. kube::test::get_object_assert bars/test-list '{{.otherField}}' '<no value>'
  337. # Test that the fields does not exist
  338. kube::test::get_object_assert foos/test-list '{{.newField}}' '<no value>'
  339. kube::test::get_object_assert bars/test-list '{{.newField}}' '<no value>'
  340. # Add a field and then apply the change
  341. kubectl "${kube_flags[@]}" apply -f hack/testdata/CRD/multi-crd-list-added-field.yaml
  342. # Test that apply has added the field
  343. kube::test::get_object_assert foos/test-list '{{.newField}}' 'field3'
  344. kube::test::get_object_assert bars/test-list '{{.newField}}' 'field3'
  345. # Delete the resource
  346. kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/multi-crd-list.yaml
  347. # Make sure it's gone
  348. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
  349. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
  350. ## kubectl apply --prune
  351. # Test that no foo or bar exist
  352. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
  353. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
  354. # apply --prune on foo.yaml that has foo/test
  355. 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
  356. # check right crds exist
  357. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  358. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
  359. # apply --prune on bar.yaml that has bar/test
  360. 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
  361. # check right crds exist
  362. kube::test::wait_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
  363. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test:'
  364. # Delete the resource
  365. kubectl "${kube_flags[@]}" delete -f hack/testdata/CRD/bar.yaml
  366. # Make sure it's gone
  367. kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
  368. kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
  369. # Test 'kubectl create' with namespace, and namespace cleanup.
  370. kubectl "${kube_flags[@]}" create namespace non-native-resources
  371. kubectl "${kube_flags[@]}" create -f hack/testdata/CRD/bar.yaml --namespace=non-native-resources
  372. kube::test::get_object_assert bars '{{len .items}}' '1' --namespace=non-native-resources
  373. kubectl "${kube_flags[@]}" delete namespace non-native-resources
  374. # Make sure objects go away.
  375. kube::test::wait_object_assert bars '{{len .items}}' '0' --namespace=non-native-resources
  376. # Make sure namespace goes away.
  377. local tries=0
  378. while kubectl "${kube_flags[@]}" get namespace non-native-resources && [ ${tries} -lt 10 ]; do
  379. tries=$((tries+1))
  380. sleep ${tries}
  381. done
  382. set +o nounset
  383. set +o errexit
  384. }