validation_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package validation
  14. import (
  15. "path/filepath"
  16. . "github.com/onsi/ginkgo"
  17. . "github.com/onsi/gomega"
  18. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  19. "k8s.io/kube-openapi/pkg/util/proto/validation"
  20. // This dependency is needed to register API types.
  21. "k8s.io/kube-openapi/pkg/util/proto/testing"
  22. "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
  23. )
  24. var fakeSchema = testing.Fake{Path: filepath.Join("..", "..", "..", "..", "..", "..", "api", "openapi-spec", "swagger.json")}
  25. var _ = Describe("resource validation using OpenAPI Schema", func() {
  26. var validator *SchemaValidation
  27. BeforeEach(func() {
  28. s, err := fakeSchema.OpenAPISchema()
  29. Expect(err).To(BeNil())
  30. resources, err := openapi.NewOpenAPIData(s)
  31. Expect(err).To(BeNil())
  32. validator = NewSchemaValidation(resources)
  33. Expect(validator).ToNot(BeNil())
  34. })
  35. It("finds Deployment in Schema and validates it", func() {
  36. err := validator.ValidateBytes([]byte(`
  37. apiVersion: apps/v1
  38. kind: Deployment
  39. metadata:
  40. labels:
  41. name: redis-master
  42. name: name
  43. spec:
  44. replicas: 1
  45. selector:
  46. matchLabels:
  47. app: redis
  48. template:
  49. metadata:
  50. labels:
  51. app: redis
  52. spec:
  53. containers:
  54. - image: redis
  55. name: redis
  56. `))
  57. Expect(err).To(BeNil())
  58. })
  59. It("validates a valid pod", func() {
  60. err := validator.ValidateBytes([]byte(`
  61. apiVersion: v1
  62. kind: Pod
  63. metadata:
  64. labels:
  65. name: redis-master
  66. name: name
  67. spec:
  68. containers:
  69. - args:
  70. - this
  71. - is
  72. - an
  73. - ok
  74. - command
  75. image: gcr.io/fake_project/fake_image:fake_tag
  76. name: master
  77. `))
  78. Expect(err).To(BeNil())
  79. })
  80. It("finds invalid command (string instead of []string) in Json Pod", func() {
  81. err := validator.ValidateBytes([]byte(`
  82. {
  83. "kind": "Pod",
  84. "apiVersion": "v1",
  85. "metadata": {
  86. "name": "name",
  87. "labels": {
  88. "name": "redis-master"
  89. }
  90. },
  91. "spec": {
  92. "containers": [
  93. {
  94. "name": "master",
  95. "image": "gcr.io/fake_project/fake_image:fake_tag",
  96. "args": "this is a bad command"
  97. }
  98. ]
  99. }
  100. }
  101. `))
  102. Expect(err).To(Equal(utilerrors.NewAggregate([]error{
  103. validation.ValidationError{
  104. Path: "Pod.spec.containers[0].args",
  105. Err: validation.InvalidTypeError{
  106. Path: "io.k8s.api.core.v1.Container.args",
  107. Expected: "array",
  108. Actual: "string",
  109. },
  110. },
  111. })))
  112. })
  113. It("fails because hostPort is string instead of int", func() {
  114. err := validator.ValidateBytes([]byte(`
  115. {
  116. "kind": "Pod",
  117. "apiVersion": "v1",
  118. "metadata": {
  119. "name": "apache-php",
  120. "labels": {
  121. "name": "apache-php"
  122. }
  123. },
  124. "spec": {
  125. "volumes": [{
  126. "name": "shared-disk"
  127. }],
  128. "containers": [
  129. {
  130. "name": "apache-php",
  131. "image": "gcr.io/fake_project/fake_image:fake_tag",
  132. "ports": [
  133. {
  134. "name": "apache",
  135. "hostPort": "13380",
  136. "containerPort": 80,
  137. "protocol": "TCP"
  138. }
  139. ],
  140. "volumeMounts": [
  141. {
  142. "name": "shared-disk",
  143. "mountPath": "/var/www/html"
  144. }
  145. ]
  146. }
  147. ]
  148. }
  149. }
  150. `))
  151. Expect(err).To(Equal(utilerrors.NewAggregate([]error{
  152. validation.ValidationError{
  153. Path: "Pod.spec.containers[0].ports[0].hostPort",
  154. Err: validation.InvalidTypeError{
  155. Path: "io.k8s.api.core.v1.ContainerPort.hostPort",
  156. Expected: "integer",
  157. Actual: "string",
  158. },
  159. },
  160. })))
  161. })
  162. It("fails because volume is not an array of object", func() {
  163. err := validator.ValidateBytes([]byte(`
  164. {
  165. "kind": "Pod",
  166. "apiVersion": "v1",
  167. "metadata": {
  168. "name": "apache-php",
  169. "labels": {
  170. "name": "apache-php"
  171. }
  172. },
  173. "spec": {
  174. "volumes": [
  175. "name": "shared-disk"
  176. ],
  177. "containers": [
  178. {
  179. "name": "apache-php",
  180. "image": "gcr.io/fake_project/fake_image:fake_tag",
  181. "ports": [
  182. {
  183. "name": "apache",
  184. "hostPort": 13380,
  185. "containerPort": 80,
  186. "protocol": "TCP"
  187. }
  188. ],
  189. "volumeMounts": [
  190. {
  191. "name": "shared-disk",
  192. "mountPath": "/var/www/html"
  193. }
  194. ]
  195. }
  196. ]
  197. }
  198. }
  199. `))
  200. Expect(err.Error()).To(Equal("invalid character ':' after array element"))
  201. })
  202. It("fails because some string lists have empty strings", func() {
  203. err := validator.ValidateBytes([]byte(`
  204. apiVersion: v1
  205. kind: Pod
  206. metadata:
  207. labels:
  208. name: redis-master
  209. name: name
  210. spec:
  211. containers:
  212. - image: gcr.io/fake_project/fake_image:fake_tag
  213. name: master
  214. args:
  215. -
  216. command:
  217. -
  218. `))
  219. Expect(err).To(Equal(utilerrors.NewAggregate([]error{
  220. validation.ValidationError{
  221. Path: "Pod.spec.containers[0].args",
  222. Err: validation.InvalidObjectTypeError{
  223. Path: "Pod.spec.containers[0].args[0]",
  224. Type: "nil",
  225. },
  226. },
  227. validation.ValidationError{
  228. Path: "Pod.spec.containers[0].command",
  229. Err: validation.InvalidObjectTypeError{
  230. Path: "Pod.spec.containers[0].command[0]",
  231. Type: "nil",
  232. },
  233. },
  234. })))
  235. })
  236. It("fails if required fields are missing", func() {
  237. err := validator.ValidateBytes([]byte(`
  238. apiVersion: v1
  239. kind: Pod
  240. metadata:
  241. labels:
  242. name: redis-master
  243. name: name
  244. spec:
  245. containers:
  246. - command: ["my", "command"]
  247. `))
  248. Expect(err).To(Equal(utilerrors.NewAggregate([]error{
  249. validation.ValidationError{
  250. Path: "Pod.spec.containers[0]",
  251. Err: validation.MissingRequiredFieldError{
  252. Path: "io.k8s.api.core.v1.Container",
  253. Field: "name",
  254. },
  255. },
  256. })))
  257. })
  258. It("fails if required fields are empty", func() {
  259. err := validator.ValidateBytes([]byte(`
  260. apiVersion: v1
  261. kind: Pod
  262. metadata:
  263. labels:
  264. name: redis-master
  265. name: name
  266. spec:
  267. containers:
  268. - image:
  269. name:
  270. `))
  271. Expect(err).To(Equal(utilerrors.NewAggregate([]error{
  272. validation.ValidationError{
  273. Path: "Pod.spec.containers[0]",
  274. Err: validation.MissingRequiredFieldError{
  275. Path: "io.k8s.api.core.v1.Container",
  276. Field: "name",
  277. },
  278. },
  279. })))
  280. })
  281. It("is fine with empty non-mandatory fields", func() {
  282. err := validator.ValidateBytes([]byte(`
  283. apiVersion: v1
  284. kind: Pod
  285. metadata:
  286. labels:
  287. name: redis-master
  288. name: name
  289. spec:
  290. containers:
  291. - image: image
  292. name: name
  293. command:
  294. `))
  295. Expect(err).To(BeNil())
  296. })
  297. It("can validate lists", func() {
  298. err := validator.ValidateBytes([]byte(`
  299. apiVersion: v1
  300. kind: List
  301. items:
  302. - apiVersion: v1
  303. kind: Pod
  304. metadata:
  305. labels:
  306. name: redis-master
  307. name: name
  308. spec:
  309. containers:
  310. - name: name
  311. `))
  312. Expect(err).To(BeNil())
  313. })
  314. It("fails because apiVersion is not provided", func() {
  315. err := validator.ValidateBytes([]byte(`
  316. kind: Pod
  317. metadata:
  318. name: name
  319. spec:
  320. containers:
  321. - name: name
  322. image: image
  323. `))
  324. Expect(err.Error()).To(Equal("apiVersion not set"))
  325. })
  326. It("fails because apiVersion type is not string and kind is not provided", func() {
  327. err := validator.ValidateBytes([]byte(`
  328. apiVersion: 1
  329. metadata:
  330. name: name
  331. spec:
  332. containers:
  333. - name: name
  334. image: image
  335. `))
  336. Expect(err.Error()).To(Equal("[apiVersion isn't string type, kind not set]"))
  337. })
  338. It("fails because List first item is missing kind and second item is missing apiVersion", func() {
  339. err := validator.ValidateBytes([]byte(`
  340. apiVersion: v1
  341. kind: List
  342. items:
  343. - apiVersion: v1
  344. metadata:
  345. name: name
  346. spec:
  347. replicas: 1
  348. template:
  349. metadata:
  350. labels:
  351. name: name
  352. spec:
  353. containers:
  354. - name: name
  355. image: image
  356. - kind: Service
  357. metadata:
  358. name: name
  359. spec:
  360. type: NodePort
  361. ports:
  362. - port: 123
  363. targetPort: 1234
  364. name: name
  365. selector:
  366. name: name
  367. `))
  368. Expect(err.Error()).To(Equal("[kind not set, apiVersion not set]"))
  369. })
  370. It("is fine with crd resource with List as a suffix kind name, which may not be a list of resources", func() {
  371. err := validator.ValidateBytes([]byte(`
  372. apiVersion: fake.com/v1
  373. kind: FakeList
  374. metadata:
  375. name: fake
  376. spec:
  377. foo: bar
  378. `))
  379. Expect(err).To(BeNil())
  380. })
  381. })