github.com/kubevela/workflow@v0.6.0/pkg/cue/package_suit_test.go (about) 1 /* 2 Copyright 2022 The KubeVela 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 */ 16 17 package cue 18 19 import ( 20 "context" 21 "errors" 22 "strings" 23 "time" 24 25 . "github.com/onsi/ginkgo" 26 . "github.com/onsi/gomega" 27 28 "cuelang.org/go/cue/build" 29 "github.com/google/go-cmp/cmp" 30 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 31 appsv1 "k8s.io/api/apps/v1" 32 batchv1 "k8s.io/api/batch/v1" 33 certificatesv1beta1 "k8s.io/api/certificates/v1beta1" 34 coordinationv1 "k8s.io/api/coordination/v1" 35 corev1 "k8s.io/api/core/v1" 36 discoveryv1beta1 "k8s.io/api/discovery/v1beta1" 37 networkingv1 "k8s.io/api/networking/v1" 38 policyv1beta1 "k8s.io/api/policy/v1beta1" 39 rbacv1 "k8s.io/api/rbac/v1" 40 crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 41 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 42 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 43 "k8s.io/apimachinery/pkg/runtime" 44 "k8s.io/utils/pointer" 45 46 "github.com/kubevela/workflow/pkg/cue/model" 47 "github.com/kubevela/workflow/pkg/utils" 48 ) 49 50 var _ = Describe("Package discovery resources for definition from K8s APIServer", func() { 51 PIt("check that all built-in k8s resource are registered", func() { 52 var localSchemeBuilder = runtime.SchemeBuilder{ 53 admissionregistrationv1.AddToScheme, 54 appsv1.AddToScheme, 55 batchv1.AddToScheme, 56 certificatesv1beta1.AddToScheme, 57 coordinationv1.AddToScheme, 58 corev1.AddToScheme, 59 discoveryv1beta1.AddToScheme, 60 networkingv1.AddToScheme, 61 policyv1beta1.AddToScheme, 62 rbacv1.AddToScheme, 63 } 64 65 var localScheme = runtime.NewScheme() 66 err := localSchemeBuilder.AddToScheme(localScheme) 67 Expect(err).Should(BeNil()) 68 types := localScheme.AllKnownTypes() 69 for typ := range types { 70 if strings.HasSuffix(typ.Kind, "List") { 71 continue 72 } 73 if strings.HasSuffix(typ.Kind, "Options") { 74 continue 75 } 76 switch typ.Kind { 77 case "WatchEvent": 78 continue 79 case "APIGroup", "APIVersions": 80 continue 81 case "RangeAllocation", "ComponentStatus", "Status": 82 continue 83 case "SerializedReference", "EndpointSlice": 84 continue 85 case "PodStatusResult", "EphemeralContainers": 86 continue 87 } 88 89 Expect(pd.Exist(metav1.GroupVersionKind{ 90 Group: typ.Group, 91 Version: typ.Version, 92 Kind: typ.Kind, 93 })).Should(BeTrue(), typ.String()) 94 } 95 }) 96 97 // nolint:staticcheck 98 PIt("discovery built-in k8s resource with kube prefix", func() { 99 100 By("test ingress in kube package") 101 bi := build.NewContext().NewInstance("", nil) 102 err := bi.AddFile("-", ` 103 import ( 104 kube "kube/networking.k8s.io/v1beta1" 105 ) 106 output: kube.#Ingress 107 output: { 108 apiVersion: "networking.k8s.io/v1beta1" 109 kind: "Ingress" 110 metadata: name: "myapp" 111 spec: { 112 rules: [{ 113 host: parameter.domain 114 http: { 115 paths: [ 116 for k, v in parameter.http { 117 path: k 118 backend: { 119 serviceName: "myname" 120 servicePort: v 121 } 122 }, 123 ] 124 } 125 }] 126 } 127 } 128 parameter: { 129 domain: "abc.com" 130 http: { 131 "/": 80 132 } 133 }`) 134 Expect(err).ToNot(HaveOccurred()) 135 inst, err := pd.ImportPackagesAndBuildInstance(bi) 136 Expect(err).Should(BeNil()) 137 base, err := model.NewBase(inst.Lookup("output")) 138 Expect(err).Should(BeNil()) 139 data, err := base.Unstructured() 140 Expect(err).Should(BeNil()) 141 142 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 143 "kind": "Ingress", 144 "apiVersion": "networking.k8s.io/v1beta1", 145 "metadata": map[string]interface{}{"name": "myapp"}, 146 "spec": map[string]interface{}{ 147 "rules": []interface{}{ 148 map[string]interface{}{ 149 "host": "abc.com", 150 "http": map[string]interface{}{ 151 "paths": []interface{}{ 152 map[string]interface{}{ 153 "path": "/", 154 "backend": map[string]interface{}{ 155 "serviceName": "myname", 156 "servicePort": int64(80), 157 }}}}}}}}, 158 })).Should(BeEquivalentTo("")) 159 By("test Invalid Import path") 160 bi = build.NewContext().NewInstance("", nil) 161 err = bi.AddFile("-", ` 162 import ( 163 kube "kube/networking.k8s.io/v1" 164 ) 165 output: kube.#Deployment 166 output: { 167 metadata: { 168 "name": parameter.name 169 } 170 spec: template: spec: { 171 containers: [{ 172 name:"invalid-path", 173 image: parameter.image 174 }] 175 } 176 } 177 178 parameter: { 179 name: "myapp" 180 image: "nginx" 181 }`) 182 Expect(err).Should(BeNil()) 183 inst, err = pd.ImportPackagesAndBuildInstance(bi) 184 Expect(err).Should(BeNil()) 185 _, err = model.NewBase(inst.Lookup("output")) 186 Expect(err).ShouldNot(BeNil()) 187 Expect(err.Error()).Should(Equal("_|_ // undefined field \"#Deployment\"")) 188 189 By("test Deployment in kube package") 190 bi = build.NewContext().NewInstance("", nil) 191 err = bi.AddFile("-", ` 192 import ( 193 kube "kube/apps/v1" 194 ) 195 output: kube.#Deployment 196 output: { 197 metadata: { 198 "name": parameter.name 199 } 200 spec: template: spec: { 201 containers: [{ 202 name:"test", 203 image: parameter.image 204 }] 205 } 206 } 207 parameter: { 208 name: "myapp" 209 image: "nginx" 210 }`) 211 Expect(err).ShouldNot(BeNil()) 212 inst, err = pd.ImportPackagesAndBuildInstance(bi) 213 Expect(err).Should(BeNil()) 214 base, err = model.NewBase(inst.Lookup("output")) 215 Expect(err).Should(BeNil()) 216 data, err = base.Unstructured() 217 Expect(err).Should(BeNil()) 218 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 219 "kind": "Deployment", 220 "apiVersion": "apps/v1", 221 "metadata": map[string]interface{}{"name": "myapp"}, 222 "spec": map[string]interface{}{ 223 "selector": map[string]interface{}{}, 224 "template": map[string]interface{}{ 225 "spec": map[string]interface{}{ 226 "containers": []interface{}{ 227 map[string]interface{}{ 228 "name": "test", 229 "image": "nginx"}}}}}}, 230 })).Should(BeEquivalentTo("")) 231 232 By("test Secret in kube package") 233 bi = build.NewContext().NewInstance("", nil) 234 err = bi.AddFile("-", ` 235 import ( 236 kube "kube/v1" 237 ) 238 output: kube.#Secret 239 output: { 240 metadata: { 241 "name": parameter.name 242 } 243 type:"kubevela" 244 } 245 parameter: { 246 name: "myapp" 247 }`) 248 Expect(err).Should(BeNil()) 249 inst, err = pd.ImportPackagesAndBuildInstance(bi) 250 Expect(err).Should(BeNil()) 251 base, err = model.NewBase(inst.Lookup("output")) 252 Expect(err).Should(BeNil()) 253 data, err = base.Unstructured() 254 Expect(err).Should(BeNil()) 255 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 256 "kind": "Secret", 257 "apiVersion": "v1", 258 "metadata": map[string]interface{}{"name": "myapp"}, 259 "type": "kubevela"}})).Should(BeEquivalentTo("")) 260 261 By("test Service in kube package") 262 bi = build.NewContext().NewInstance("", nil) 263 err = bi.AddFile("-", ` 264 import ( 265 kube "kube/v1" 266 ) 267 output: kube.#Service 268 output: { 269 metadata: { 270 "name": parameter.name 271 } 272 spec: type: "ClusterIP", 273 } 274 parameter: { 275 name: "myapp" 276 }`) 277 Expect(err).Should(BeNil()) 278 inst, err = pd.ImportPackagesAndBuildInstance(bi) 279 Expect(err).Should(BeNil()) 280 base, err = model.NewBase(inst.Lookup("output")) 281 Expect(err).Should(BeNil()) 282 data, err = base.Unstructured() 283 Expect(err).Should(BeNil()) 284 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 285 "kind": "Service", 286 "apiVersion": "v1", 287 "metadata": map[string]interface{}{"name": "myapp"}, 288 "spec": map[string]interface{}{ 289 "type": "ClusterIP"}}, 290 })).Should(BeEquivalentTo("")) 291 Expect(pd.Exist(metav1.GroupVersionKind{ 292 Group: "", 293 Version: "v1", 294 Kind: "Service", 295 })).Should(Equal(true)) 296 297 By("Check newly added CRD refreshed and could be used in CUE package") 298 crd1 := crdv1.CustomResourceDefinition{ 299 ObjectMeta: metav1.ObjectMeta{ 300 Name: "foo.example.com", 301 }, 302 Spec: crdv1.CustomResourceDefinitionSpec{ 303 Group: "example.com", 304 Names: crdv1.CustomResourceDefinitionNames{ 305 Kind: "Foo", 306 ListKind: "FooList", 307 Plural: "foo", 308 Singular: "foo", 309 }, 310 Versions: []crdv1.CustomResourceDefinitionVersion{{ 311 Name: "v1", 312 Served: true, 313 Storage: true, 314 Subresources: &crdv1.CustomResourceSubresources{Status: &crdv1.CustomResourceSubresourceStatus{}}, 315 Schema: &crdv1.CustomResourceValidation{ 316 OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 317 Type: "object", 318 Properties: map[string]crdv1.JSONSchemaProps{ 319 "spec": { 320 Type: "object", 321 XPreserveUnknownFields: pointer.BoolPtr(true), 322 Properties: map[string]crdv1.JSONSchemaProps{ 323 "key": {Type: "string"}, 324 }}, 325 "status": { 326 Type: "object", 327 XPreserveUnknownFields: pointer.BoolPtr(true), 328 Properties: map[string]crdv1.JSONSchemaProps{ 329 "key": {Type: "string"}, 330 "app-hash": {Type: "string"}, 331 }}}}}}, 332 }, 333 Scope: crdv1.NamespaceScoped, 334 }, 335 } 336 Expect(k8sClient.Create(context.Background(), &crd1)).Should(SatisfyAny(BeNil(), &utils.AlreadyExistMatcher{})) 337 338 Expect(pd.Exist(metav1.GroupVersionKind{ 339 Group: "example.com", 340 Version: "v1", 341 Kind: "Foo", 342 })).Should(Equal(false)) 343 344 By("test new added CRD in kube package") 345 Eventually(func() error { 346 if err := pd.RefreshKubePackagesFromCluster(); err != nil { 347 return err 348 } 349 if !pd.Exist(metav1.GroupVersionKind{ 350 Group: "example.com", 351 Version: "v1", 352 Kind: "Foo", 353 }) { 354 return errors.New("crd(example.com/v1.Foo) not register to openAPI") 355 } 356 return nil 357 }, time.Second*30, time.Millisecond*300).Should(BeNil()) 358 359 bi = build.NewContext().NewInstance("", nil) 360 err = bi.AddFile("-", ` 361 import ( 362 kv1 "kube/example.com/v1" 363 ) 364 output: kv1.#Foo 365 output: { 366 spec: key: "test1" 367 status: key: "test2" 368 } 369 `) 370 Expect(err).Should(BeNil()) 371 inst, err = pd.ImportPackagesAndBuildInstance(bi) 372 Expect(err).Should(BeNil()) 373 base, err = model.NewBase(inst.Lookup("output")) 374 Expect(err).Should(BeNil()) 375 data, err = base.Unstructured() 376 Expect(err).Should(BeNil()) 377 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 378 "kind": "Foo", 379 "apiVersion": "example.com/v1", 380 "spec": map[string]interface{}{ 381 "key": "test1"}, 382 "status": map[string]interface{}{ 383 "key": "test2"}}, 384 })).Should(BeEquivalentTo("")) 385 386 }) 387 388 // nolint:staticcheck 389 PIt("discovery built-in k8s resource with third-party path", func() { 390 391 By("test ingress in kube package") 392 bi := build.NewContext().NewInstance("", nil) 393 err := bi.AddFile("-", ` 394 import ( 395 network "k8s.io/networking/v1beta1" 396 ) 397 output: network.#Ingress 398 output: { 399 metadata: name: "myapp" 400 spec: { 401 rules: [{ 402 host: parameter.domain 403 http: { 404 paths: [ 405 for k, v in parameter.http { 406 path: k 407 backend: { 408 serviceName: "myname" 409 servicePort: v 410 } 411 }, 412 ] 413 } 414 }] 415 } 416 } 417 parameter: { 418 domain: "abc.com" 419 http: { 420 "/": 80 421 } 422 }`) 423 Expect(err).ToNot(HaveOccurred()) 424 inst, err := pd.ImportPackagesAndBuildInstance(bi) 425 Expect(err).Should(BeNil()) 426 base, err := model.NewBase(inst.Lookup("output")) 427 Expect(err).Should(BeNil()) 428 data, err := base.Unstructured() 429 Expect(err).Should(BeNil()) 430 431 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 432 "kind": "Ingress", 433 "apiVersion": "networking.k8s.io/v1beta1", 434 "metadata": map[string]interface{}{"name": "myapp"}, 435 "spec": map[string]interface{}{ 436 "rules": []interface{}{ 437 map[string]interface{}{ 438 "host": "abc.com", 439 "http": map[string]interface{}{ 440 "paths": []interface{}{ 441 map[string]interface{}{ 442 "path": "/", 443 "backend": map[string]interface{}{ 444 "serviceName": "myname", 445 "servicePort": int64(80), 446 }}}}}}}}, 447 })).Should(BeEquivalentTo("")) 448 By("test Invalid Import path") 449 bi = build.NewContext().NewInstance("", nil) 450 err = bi.AddFile("-", ` 451 import ( 452 "k8s.io/networking/v1" 453 ) 454 output: v1.#Deployment 455 output: { 456 metadata: { 457 "name": parameter.name 458 } 459 spec: template: spec: { 460 containers: [{ 461 name:"invalid-path", 462 image: parameter.image 463 }] 464 } 465 } 466 467 parameter: { 468 name: "myapp" 469 image: "nginx" 470 }`) 471 Expect(err).Should(BeNil()) 472 inst, err = pd.ImportPackagesAndBuildInstance(bi) 473 Expect(err).Should(BeNil()) 474 _, err = model.NewBase(inst.Lookup("output")) 475 Expect(err).ShouldNot(BeNil()) 476 Expect(err.Error()).Should(Equal("_|_ // undefined field \"#Deployment\"")) 477 478 By("test Deployment in kube package") 479 bi = build.NewContext().NewInstance("", nil) 480 err = bi.AddFile("-", ` 481 import ( 482 apps "k8s.io/apps/v1" 483 ) 484 output: apps.#Deployment 485 output: { 486 metadata: { 487 "name": parameter.name 488 } 489 spec: template: spec: { 490 containers: [{ 491 name:"test", 492 image: parameter.image 493 }] 494 } 495 } 496 parameter: { 497 name: "myapp" 498 image: "nginx" 499 }`) 500 Expect(err).Should(BeNil()) 501 inst, err = pd.ImportPackagesAndBuildInstance(bi) 502 Expect(err).Should(BeNil()) 503 base, err = model.NewBase(inst.Lookup("output")) 504 Expect(err).Should(BeNil()) 505 data, err = base.Unstructured() 506 Expect(err).Should(BeNil()) 507 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 508 "kind": "Deployment", 509 "apiVersion": "apps/v1", 510 "metadata": map[string]interface{}{"name": "myapp"}, 511 "spec": map[string]interface{}{ 512 "selector": map[string]interface{}{}, 513 "template": map[string]interface{}{ 514 "spec": map[string]interface{}{ 515 "containers": []interface{}{ 516 map[string]interface{}{ 517 "name": "test", 518 "image": "nginx"}}}}}}, 519 })).Should(BeEquivalentTo("")) 520 521 By("test Secret in kube package") 522 bi = build.NewContext().NewInstance("", nil) 523 err = bi.AddFile("-", ` 524 import ( 525 "k8s.io/core/v1" 526 ) 527 output: v1.#Secret 528 output: { 529 metadata: { 530 "name": parameter.name 531 } 532 type:"kubevela" 533 } 534 parameter: { 535 name: "myapp" 536 }`) 537 Expect(err).Should(BeNil()) 538 inst, err = pd.ImportPackagesAndBuildInstance(bi) 539 Expect(err).Should(BeNil()) 540 base, err = model.NewBase(inst.Lookup("output")) 541 Expect(err).Should(BeNil()) 542 data, err = base.Unstructured() 543 Expect(err).Should(BeNil()) 544 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 545 "kind": "Secret", 546 "apiVersion": "v1", 547 "metadata": map[string]interface{}{"name": "myapp"}, 548 "type": "kubevela"}})).Should(BeEquivalentTo("")) 549 550 By("test Service in kube package") 551 bi = build.NewContext().NewInstance("", nil) 552 err = bi.AddFile("-", ` 553 import ( 554 "k8s.io/core/v1" 555 ) 556 output: v1.#Service 557 output: { 558 metadata: { 559 "name": parameter.name 560 } 561 spec: type: "ClusterIP", 562 } 563 parameter: { 564 name: "myapp" 565 }`) 566 Expect(err).Should(BeNil()) 567 inst, err = pd.ImportPackagesAndBuildInstance(bi) 568 Expect(err).Should(BeNil()) 569 base, err = model.NewBase(inst.Lookup("output")) 570 Expect(err).Should(BeNil()) 571 data, err = base.Unstructured() 572 Expect(err).Should(BeNil()) 573 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 574 "kind": "Service", 575 "apiVersion": "v1", 576 "metadata": map[string]interface{}{"name": "myapp"}, 577 "spec": map[string]interface{}{ 578 "type": "ClusterIP"}}, 579 })).Should(BeEquivalentTo("")) 580 Expect(pd.Exist(metav1.GroupVersionKind{ 581 Group: "", 582 Version: "v1", 583 Kind: "Service", 584 })).Should(Equal(true)) 585 586 By("Check newly added CRD refreshed and could be used in CUE package") 587 crd1 := crdv1.CustomResourceDefinition{ 588 ObjectMeta: metav1.ObjectMeta{ 589 Name: "bar.example.com", 590 }, 591 Spec: crdv1.CustomResourceDefinitionSpec{ 592 Group: "example.com", 593 Names: crdv1.CustomResourceDefinitionNames{ 594 Kind: "Bar", 595 ListKind: "BarList", 596 Plural: "bar", 597 Singular: "bar", 598 }, 599 Versions: []crdv1.CustomResourceDefinitionVersion{{ 600 Name: "v1", 601 Served: true, 602 Storage: true, 603 Subresources: &crdv1.CustomResourceSubresources{Status: &crdv1.CustomResourceSubresourceStatus{}}, 604 Schema: &crdv1.CustomResourceValidation{ 605 OpenAPIV3Schema: &crdv1.JSONSchemaProps{ 606 Type: "object", 607 Properties: map[string]crdv1.JSONSchemaProps{ 608 "spec": { 609 Type: "object", 610 XPreserveUnknownFields: pointer.BoolPtr(true), 611 Properties: map[string]crdv1.JSONSchemaProps{ 612 "key": {Type: "string"}, 613 }}, 614 "status": { 615 Type: "object", 616 XPreserveUnknownFields: pointer.BoolPtr(true), 617 Properties: map[string]crdv1.JSONSchemaProps{ 618 "key": {Type: "string"}, 619 "app-hash": {Type: "string"}, 620 }}}}}}, 621 }, 622 Scope: crdv1.NamespaceScoped, 623 }, 624 } 625 Expect(k8sClient.Create(context.Background(), &crd1)).Should(SatisfyAny(BeNil(), &utils.AlreadyExistMatcher{})) 626 627 Expect(pd.Exist(metav1.GroupVersionKind{ 628 Group: "example.com", 629 Version: "v1", 630 Kind: "Bar", 631 })).Should(Equal(false)) 632 633 By("test new added CRD in kube package") 634 Eventually(func() error { 635 if err := pd.RefreshKubePackagesFromCluster(); err != nil { 636 return err 637 } 638 if !pd.Exist(metav1.GroupVersionKind{ 639 Group: "example.com", 640 Version: "v1", 641 Kind: "Bar", 642 }) { 643 return errors.New("crd(example.com/v1.Bar) not register to openAPI") 644 } 645 return nil 646 }, time.Second*30, time.Millisecond*300).Should(BeNil()) 647 648 bi = build.NewContext().NewInstance("", nil) 649 err = bi.AddFile("-", ` 650 import ( 651 ev1 "example.com/v1" 652 ) 653 output: ev1.#Bar 654 output: { 655 spec: key: "test1" 656 status: key: "test2" 657 } 658 `) 659 Expect(err).Should(BeNil()) 660 inst, err = pd.ImportPackagesAndBuildInstance(bi) 661 Expect(err).Should(BeNil()) 662 base, err = model.NewBase(inst.Lookup("output")) 663 Expect(err).Should(BeNil()) 664 data, err = base.Unstructured() 665 Expect(err).Should(BeNil()) 666 Expect(cmp.Diff(data, &unstructured.Unstructured{Object: map[string]interface{}{ 667 "kind": "Bar", 668 "apiVersion": "example.com/v1", 669 "spec": map[string]interface{}{ 670 "key": "test1"}, 671 "status": map[string]interface{}{ 672 "key": "test2"}}, 673 })).Should(BeEquivalentTo("")) 674 }) 675 })