github.com/kubevela/workflow@v0.6.0/pkg/cue/packages/package_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 packages 18 19 import ( 20 "fmt" 21 "testing" 22 23 "cuelang.org/go/cue" 24 "cuelang.org/go/cue/build" 25 "cuelang.org/go/cue/cuecontext" 26 "cuelang.org/go/cue/parser" 27 "cuelang.org/go/cue/token" 28 "github.com/stretchr/testify/require" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 31 "github.com/kubevela/workflow/pkg/cue/model" 32 ) 33 34 func TestPackage(t *testing.T) { 35 var openAPISchema = ` 36 { 37 "paths": { 38 "paths...": { 39 "post":{ 40 "x-kubernetes-group-version-kind": { 41 "group": "apps.test.io", 42 "kind": "Bucket", 43 "version": "v1" 44 } 45 } 46 } 47 }, 48 "definitions":{ 49 "io.test.apps.v1.Bucket":{ 50 "properties":{ 51 "apiVersion": {"type": "string"} 52 "kind": {"type": "string"} 53 "acl":{ 54 "default":"private", 55 "enum":[ 56 "public-read-write", 57 "public-read", 58 "private" 59 ], 60 "type":"string" 61 }, 62 "dataRedundancyType":{ 63 "default":"LRS", 64 "enum":[ 65 "LRS", 66 "ZRS" 67 ], 68 "type":"string" 69 }, 70 "dataSourceRef":{ 71 "properties":{ 72 "dsPath":{ 73 "type":"string" 74 } 75 }, 76 "required":[ 77 "dsPath" 78 ], 79 "type":"object" 80 }, 81 "importRef":{ 82 "properties":{ 83 "importKey":{ 84 "type":"string" 85 } 86 }, 87 "required":[ 88 "importKey" 89 ], 90 "type":"object" 91 }, 92 "output":{ 93 "additionalProperties":{ 94 "oneOf":[ 95 { 96 "properties":{ 97 "outRef":{ 98 "type":"string" 99 } 100 }, 101 "required":[ 102 "outRef" 103 ] 104 }, 105 { 106 "properties":{ 107 "valueRef":{ 108 "description":"Example: demoVpc.vpcId", 109 "type":"string" 110 } 111 }, 112 "required":[ 113 "valueRef" 114 ] 115 } 116 ], 117 "type":"object" 118 }, 119 "properties":{ 120 "bucketName":{ 121 "properties":{ 122 "outRef":{ 123 "enum":[ 124 "self.name" 125 ], 126 "type":"string" 127 } 128 }, 129 "required":[ 130 "outRef" 131 ], 132 "type":"object" 133 }, 134 "extranetEndpoint":{ 135 "properties":{ 136 "outRef":{ 137 "enum":[ 138 "self.state.extranetEndpoint" 139 ], 140 "type":"string" 141 } 142 }, 143 "required":[ 144 "outRef" 145 ], 146 "type":"object" 147 }, 148 "intranetEndpoint":{ 149 "properties":{ 150 "outRef":{ 151 "enum":[ 152 "self.state.intranetEndpoint" 153 ], 154 "type":"string" 155 } 156 }, 157 "required":[ 158 "outRef" 159 ], 160 "type":"object" 161 }, 162 "masterUserId":{ 163 "properties":{ 164 "outRef":{ 165 "enum":[ 166 "self.state.masterUserId" 167 ], 168 "type":"string" 169 } 170 }, 171 "required":[ 172 "outRef" 173 ], 174 "type":"object" 175 } 176 }, 177 "required":[ 178 "bucketName", 179 "extranetEndpoint", 180 "intranetEndpoint", 181 "masterUserId" 182 ], 183 "type":"object" 184 }, 185 "profile":{ 186 "properties":{ 187 "baasRepo":{ 188 "oneOf":[ 189 { 190 "type":"string" 191 }, 192 { 193 "properties":{ 194 "valueRef":{ 195 "description":"Example: demoVpc.vpcId", 196 "type":"string" 197 } 198 }, 199 "required":[ 200 "valueRef" 201 ], 202 "type":"object" 203 } 204 ] 205 }, 206 "cloudProduct":{ 207 "enum":[ 208 "AliCloudOSS" 209 ], 210 "type":"string" 211 }, 212 "endpoint":{ 213 "oneOf":[ 214 { 215 "type":"string" 216 }, 217 { 218 "properties":{ 219 "valueRef":{ 220 "description":"Example: demoVpc.vpcId", 221 "type":"string" 222 } 223 }, 224 "required":[ 225 "valueRef" 226 ], 227 "type":"object" 228 } 229 ] 230 }, 231 "envType":{ 232 "oneOf":[ 233 { 234 "enum":[ 235 "testing", 236 "product" 237 ] 238 }, 239 { 240 "properties":{ 241 "valueRef":{ 242 "description":"Example: demoVpc.vpcId", 243 "type":"string" 244 } 245 }, 246 "required":[ 247 "valueRef" 248 ], 249 "type":"object" 250 } 251 ] 252 }, 253 "provider":{ 254 "enum":[ 255 "alicloud" 256 ], 257 "type":"string" 258 }, 259 "region":{ 260 "oneOf":[ 261 { 262 "type":"string" 263 }, 264 { 265 "properties":{ 266 "valueRef":{ 267 "description":"Example: demoVpc.vpcId", 268 "type":"string" 269 } 270 }, 271 "required":[ 272 "valueRef" 273 ], 274 "type":"object" 275 } 276 ] 277 }, 278 "serviceAccount":{ 279 "oneOf":[ 280 { 281 "type":"string" 282 }, 283 { 284 "properties":{ 285 "valueRef":{ 286 "description":"Example: demoVpc.vpcId", 287 "type":"string" 288 } 289 }, 290 "required":[ 291 "valueRef" 292 ], 293 "type":"object" 294 } 295 ] 296 } 297 }, 298 "required":[ 299 "cloudProduct", 300 "provider", 301 "baasRepo", 302 "region" 303 ], 304 "type":"object" 305 }, 306 "storageClass":{ 307 "default":"Standard", 308 "enum":[ 309 "Standard", 310 "IA", 311 "Archive", 312 "ColdArchive" 313 ], 314 "type":"string" 315 }, 316 "type":{ 317 "enum":[ 318 "alicloud_oss_bucket" 319 ], 320 "type":"string" 321 } 322 }, 323 "required":[ 324 "type", 325 "output", 326 "profile", 327 "acl" 328 ], 329 "type":"object" 330 } 331 } 332 } 333 ` 334 r := require.New(t) 335 mypd := &PackageDiscover{pkgKinds: make(map[string][]VersionKind)} 336 err := mypd.addKubeCUEPackagesFromCluster(openAPISchema) 337 r.NoError(err) 338 expectPkgKinds := map[string][]VersionKind{ 339 "test.io/apps/v1": []VersionKind{{ 340 DefinitionName: "#Bucket", 341 APIVersion: "apps.test.io/v1", 342 Kind: "Bucket", 343 }}, 344 "kube/apps.test.io/v1": []VersionKind{{ 345 DefinitionName: "#Bucket", 346 APIVersion: "apps.test.io/v1", 347 Kind: "Bucket", 348 }}, 349 } 350 r.Equal(mypd.ListPackageKinds(), expectPkgKinds) 351 352 // TODO: fix losing close struct in cue 353 exceptObj := `output: { 354 apiVersion: "apps.test.io/v1" 355 kind: "Bucket" 356 acl: *"private" | "public-read" | "public-read-write" 357 dataRedundancyType?: "LRS" | "ZRS" | *"LRS" 358 dataSourceRef?: { 359 dsPath: string 360 } 361 importRef?: { 362 importKey: string 363 } 364 output: { 365 bucketName: { 366 outRef: "self.name" 367 } 368 extranetEndpoint: { 369 outRef: "self.state.extranetEndpoint" 370 } 371 intranetEndpoint: { 372 outRef: "self.state.intranetEndpoint" 373 } 374 masterUserId: { 375 outRef: "self.state.masterUserId" 376 } 377 } 378 profile: { 379 baasRepo: string | { 380 // Example: demoVpc.vpcId 381 valueRef: string 382 } 383 cloudProduct: "AliCloudOSS" 384 endpoint?: string | { 385 // Example: demoVpc.vpcId 386 valueRef: string 387 } 388 envType?: "testing" | "product" | { 389 // Example: demoVpc.vpcId 390 valueRef: string 391 } 392 provider: "alicloud" 393 region: string | { 394 // Example: demoVpc.vpcId 395 valueRef: string 396 } 397 serviceAccount?: string | { 398 // Example: demoVpc.vpcId 399 valueRef: string 400 } 401 } 402 storageClass?: "Standard" | "IA" | "Archive" | "ColdArchive" | *"Standard" 403 type: "alicloud_oss_bucket" 404 } 405 ` 406 bi := build.NewContext().NewInstance("", nil) 407 file, err := parser.ParseFile("-", ` 408 import "test.io/apps/v1" 409 output: v1.#Bucket 410 `) 411 r.NoError(err) 412 err = bi.AddSyntax(file) 413 r.NoError(err) 414 inst, err := mypd.ImportPackagesAndBuildValue(bi) 415 r.NoError(err) 416 base, err := model.NewBase(inst) 417 r.NoError(err) 418 s, err := base.String() 419 r.NoError(err) 420 r.Equal(s, exceptObj) 421 422 bi = build.NewContext().NewInstance("", nil) 423 file, err = parser.ParseFile("-", ` 424 import "kube/apps.test.io/v1" 425 output: v1.#Bucket 426 `) 427 r.NoError(err) 428 err = bi.AddSyntax(file) 429 r.NoError(err) 430 inst, err = mypd.ImportPackagesAndBuildValue(bi) 431 r.NoError(err) 432 base, err = model.NewBase(inst) 433 r.NoError(err) 434 s, err = base.String() 435 r.NoError(err) 436 r.Equal(s, exceptObj) 437 } 438 439 func TestProcessFile(t *testing.T) { 440 srcTmpl := ` 441 #Definition: { 442 kind?: string 443 apiVersion?: string 444 metadata: { 445 name: string 446 ... 447 } 448 ... 449 } 450 ` 451 r := require.New(t) 452 file, err := parser.ParseFile("-", srcTmpl) 453 r.NoError(err) 454 testPkg := newPackage("foo") 455 testPkg.processOpenAPIFile(file) 456 cuectx := cuecontext.New() 457 inst := cuectx.BuildFile(file) 458 459 testCasesInst := cuectx.CompileString(` 460 #Definition: {} 461 case1: #Definition & {additionalProperty: "test"} 462 463 case2: #Definition & { 464 metadata: { 465 additionalProperty: "test" 466 } 467 } 468 `) 469 retInst := inst.FillPath(cue.ParsePath(""), testCasesInst.Value()) 470 r.Error(retInst.LookupPath(cue.ParsePath("case1")).Err(), "case1.additionalProperty: field not allowed") 471 r.Error(retInst.LookupPath(cue.ParsePath("case2.metadata")).Err(), "case2.metadata.additionalProperty: field not allowed") 472 } 473 474 func TestMount(t *testing.T) { 475 mypd := &PackageDiscover{pkgKinds: make(map[string][]VersionKind)} 476 testPkg := newPackage("foo") 477 mypd.mount(testPkg, []VersionKind{}) 478 r := require.New(t) 479 r.Equal(len(mypd.velaBuiltinPackages), 1) 480 mypd.mount(testPkg, []VersionKind{}) 481 r.Equal(len(mypd.velaBuiltinPackages), 1) 482 r.Equal(mypd.velaBuiltinPackages[0], testPkg.Instance) 483 } 484 485 func TestGetDGVK(t *testing.T) { 486 srcTmpl := ` 487 { 488 "x-kubernetes-group-version-kind": { 489 "group": "apps.test.io", 490 "kind": "Foo", 491 "version": "v1" 492 } 493 } 494 ` 495 r := require.New(t) 496 file, err := parser.ParseFile("-", srcTmpl) 497 r.NoError(err) 498 inst := cuecontext.New().BuildFile(file) 499 gvk, err := getDGVK(inst.Value().LookupPath(cue.ParsePath("\"x-kubernetes-group-version-kind\""))) 500 r.NoError(err) 501 r.Equal(gvk, domainGroupVersionKind{ 502 Domain: "test.io", 503 Group: "apps", 504 Version: "v1", 505 Kind: "Foo", 506 APIVersion: "apps.test.io/v1", 507 }) 508 509 srcTmpl = ` 510 { 511 "x-kubernetes-group-version-kind": { 512 "group": "test.io", 513 "kind": "Foo", 514 "version": "v1" 515 } 516 } 517 ` 518 inst = cuecontext.New().CompileString(srcTmpl) 519 gvk, err = getDGVK(inst.LookupPath(cue.ParsePath("\"x-kubernetes-group-version-kind\""))) 520 r.NoError(err) 521 r.Equal(gvk, domainGroupVersionKind{ 522 Group: "test.io", 523 Version: "v1", 524 Kind: "Foo", 525 APIVersion: "test.io/v1", 526 }) 527 } 528 529 func TestOpenAPIMapping(t *testing.T) { 530 testCases := []struct { 531 input []string 532 pos token.Pos 533 result string 534 errMsg string 535 }{ 536 { 537 input: []string{"definitions", "io.k8s.api.discovery.v1beta1.Endpoint"}, 538 pos: token.NoPos, 539 result: "[io_k8s_api_discovery_v1beta1_Endpoint]", 540 }, 541 { 542 input: []string{"definitions", "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"}, 543 pos: token.NoPos.Add(1), 544 result: "[_]", 545 }, 546 { 547 input: []string{"definitions", "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"}, 548 pos: token.NoPos, 549 result: "[io_k8s_apiextensions_apiserver_pkg_apis_apiextensions_v1_JSONSchemaProps]", 550 }, 551 { 552 input: []string{"definitions"}, 553 pos: token.NoPos, 554 errMsg: "openAPIMapping format invalid", 555 }, 556 } 557 558 emptyMapper := make(map[string]domainGroupVersionKind) 559 for _, tCase := range testCases { 560 r := require.New(t) 561 labels, err := openAPIMapping(emptyMapper)(tCase.pos, tCase.input) 562 if tCase.errMsg != "" { 563 r.Error(err, tCase.errMsg) 564 continue 565 } 566 r.NoError(err) 567 r.Equal(len(labels), 1) 568 r.Equal(tCase.result, fmt.Sprint(labels)) 569 } 570 } 571 572 func TestGeneratePkgName(t *testing.T) { 573 testCases := []struct { 574 dgvk domainGroupVersionKind 575 sdPkgName string 576 openPkgName string 577 }{ 578 { 579 dgvk: domainGroupVersionKind{ 580 Domain: "k8s.io", 581 Group: "networking", 582 Version: "v1", 583 Kind: "Ingress", 584 }, 585 sdPkgName: "k8s.io/networking/v1", 586 openPkgName: "kube/networking.k8s.io", 587 }, 588 { 589 dgvk: domainGroupVersionKind{ 590 Group: "example.com", 591 Version: "v1", 592 Kind: "Sls", 593 }, 594 sdPkgName: "example.com/v1", 595 openPkgName: "kube/example.com/v1", 596 }, 597 } 598 599 for _, tCase := range testCases { 600 r := require.New(t) 601 r.Equal(genStandardPkgName(tCase.dgvk), tCase.sdPkgName) 602 } 603 } 604 605 func TestReverseString(t *testing.T) { 606 testCases := []struct { 607 gvr metav1.GroupVersionKind 608 reverseString string 609 }{ 610 { 611 gvr: metav1.GroupVersionKind{ 612 Group: "networking.k8s.io", 613 Version: "v1", 614 Kind: "NetworkPolicy", 615 }, 616 reverseString: "io_k8s_api_networking_v1_NetworkPolicy", 617 }, 618 { 619 gvr: metav1.GroupVersionKind{ 620 Group: "example.com", 621 Version: "v1", 622 Kind: "Sls", 623 }, 624 reverseString: "com_example_v1_Sls", 625 }, 626 { 627 gvr: metav1.GroupVersionKind{ 628 Version: "v1", 629 Kind: "Pod", 630 }, 631 reverseString: "io_k8s_api_core_v1_Pod", 632 }, 633 } 634 635 for _, tCase := range testCases { 636 r := require.New(t) 637 r.Equal(convert2DGVK(tCase.gvr).reverseString(), tCase.reverseString) 638 } 639 }