github.com/oam-dev/kubevela@v1.9.11/pkg/addon/render_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 addon 18 19 import ( 20 "encoding/json" 21 "strings" 22 "testing" 23 24 "github.com/stretchr/testify/assert" 25 26 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 27 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 28 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 29 ) 30 31 func TestRenderAppTemplate(t *testing.T) { 32 paraDefined := `parameter: { 33 // +usage=The clusters to install 34 clusters?: [...string] 35 namespace: string 36 }` 37 resourceComponent1 := ` 38 myref: { 39 type: "ref-objects" 40 properties: { 41 urls: ["https://hello.yaml"] 42 } 43 } 44 ` 45 appTemplate := `output: { 46 apiVersion: "core.oam.dev/v1beta1" 47 kind: "Application" 48 metadata: { 49 name: "velaux" 50 namespace: "vela-system" 51 } 52 spec: { 53 components: [{ 54 type: "k8s-objects" 55 name: "vela-namespace" 56 properties: objects: [{ 57 apiVersion: "v1" 58 kind: "Namespace" 59 metadata: name: parameter.namespace 60 }] 61 },myref] 62 policies: [{ 63 type: "shared-resource" 64 name: "namespace" 65 properties: rules: [{selector: resourceTypes: ["Namespace"]}] 66 }, { 67 type: "topology" 68 name: "deploy-topology" 69 properties: { 70 if parameter.clusters != _|_ { 71 clusters: parameter.clusters 72 } 73 if parameter.clusters == _|_ { 74 clusterLabelSelector: {} 75 } 76 namespace: parameter.namespace 77 } 78 }] 79 } 80 }` 81 addon := &InstallPackage{ 82 Meta: Meta{ 83 Name: "velaux", 84 DeployTo: &DeployTo{ 85 RuntimeCluster: true, 86 }, 87 }, 88 Parameters: paraDefined, 89 CUETemplates: []ElementFile{{Data: resourceComponent1}}, 90 AppCueTemplate: ElementFile{Data: appTemplate}, 91 } 92 93 render := addonCueTemplateRender{ 94 addon: addon, 95 inputArgs: map[string]interface{}{ 96 "namespace": "vela-system", 97 }, 98 } 99 app, _, err := render.renderApp() 100 assert.Equal(t, err.Error(), `load app template with CUE files: output.spec.components: reference "myref" not found`) 101 assert.Nil(t, app) 102 103 addon.CUETemplates = []ElementFile{{Data: "package main\n" + resourceComponent1}} 104 app, _, err = render.renderApp() 105 assert.NoError(t, err) 106 assert.Equal(t, len(app.Spec.Components), 2) 107 str, err := json.Marshal(app.Spec.Components[0].Properties) 108 assert.NoError(t, err) 109 assert.True(t, strings.Contains(string(str), `{"name":"vela-system"}`)) 110 str2, err := json.Marshal(app.Spec.Components[1].Properties) 111 assert.NoError(t, err) 112 assert.True(t, strings.Contains(string(str2), `{"urls":["https://hello.yaml"]}`)) 113 114 assert.Equal(t, len(app.Spec.Policies), 2) 115 str, err = json.Marshal(app.Spec.Policies) 116 assert.NoError(t, err) 117 assert.Contains(t, string(str), `"clusterLabelSelector":{}`) 118 119 addon.Parameters = "package newp\n" + paraDefined 120 addon.CUETemplates = []ElementFile{{Data: "package newp\n" + resourceComponent1}} 121 addon.AppCueTemplate = ElementFile{Data: "package newp\n" + appTemplate} 122 app, _, err = render.renderApp() 123 assert.NoError(t, err) 124 assert.Equal(t, len(app.Spec.Components), 2) 125 126 addon.CUETemplates = []ElementFile{{Data: "package main\n" + resourceComponent1}} 127 addon.Parameters = paraDefined 128 addon.AppCueTemplate = ElementFile{Data: appTemplate} 129 app, _, err = render.renderApp() 130 assert.NoError(t, err) 131 assert.Equal(t, len(app.Spec.Components), 2) 132 133 addon.CUETemplates = []ElementFile{{Data: "package hello\n" + resourceComponent1}} 134 addon.AppCueTemplate = ElementFile{Data: "package main\n" + appTemplate} 135 _, _, err = render.renderApp() 136 assert.Equal(t, err.Error(), `load app template with CUE files: output.spec.components: reference "myref" not found`) 137 138 addon.CUETemplates = []ElementFile{{Data: "package hello\n" + resourceComponent1}} 139 addon.Parameters = paraDefined 140 addon.AppCueTemplate = ElementFile{Data: appTemplate} 141 _, _, err = render.renderApp() 142 assert.Equal(t, err.Error(), `load app template with CUE files: output.spec.components: reference "myref" not found`) 143 144 } 145 146 func TestOutputsRender(t *testing.T) { 147 appTemplate := `output: { 148 apiVersion: "core.oam.dev/v1beta1" 149 kind: "Application" 150 metadata: { 151 name: "velaux" 152 namespace: "vela-system" 153 } 154 spec: { 155 components: [{ 156 type: "k8s-objects" 157 name: "vela-namespace" 158 properties: objects: [{ 159 apiVersion: "v1" 160 kind: "Namespace" 161 metadata: name: parameter.namespace 162 }] 163 }] 164 policies: [{ 165 type: "shared-resource" 166 name: "namespace" 167 properties: rules: [{selector: resourceTypes: ["Namespace"]}] 168 }, { 169 type: "topology" 170 name: "deploy-topology" 171 properties: { 172 if parameter.clusters != _|_ { 173 clusters: parameter.clusters 174 } 175 if parameter.clusters == _|_ { 176 clusterLabelSelector: {} 177 } 178 namespace: parameter.namespace 179 } 180 }] 181 } 182 }, 183 outputs: configmap: { 184 apiVersion: "v1" 185 kind: "Configmap" 186 metadata: { 187 name: "test-cm" 188 namespace: "default" 189 } 190 data: parameter.data 191 } 192 ` 193 paraDefined := `parameter: { 194 // +usage=The clusters to install 195 data: "myData" 196 }` 197 appTemplateNoOutputs := `output: { 198 apiVersion: "core.oam.dev/v1beta1" 199 kind: "Application" 200 metadata: { 201 name: "velaux" 202 namespace: "vela-system" 203 } 204 spec: { 205 components: [{ 206 type: "k8s-objects" 207 name: "vela-namespace" 208 properties: objects: [{ 209 apiVersion: "v1" 210 kind: "Namespace" 211 metadata: name: parameter.namespace 212 }] 213 }] 214 policies: [{ 215 type: "shared-resource" 216 name: "namespace" 217 properties: rules: [{selector: resourceTypes: ["Namespace"]}] 218 }, { 219 type: "topology" 220 name: "deploy-topology" 221 properties: { 222 if parameter.clusters != _|_ { 223 clusters: parameter.clusters 224 } 225 if parameter.clusters == _|_ { 226 clusterLabelSelector: {} 227 } 228 namespace: parameter.namespace 229 } 230 }] 231 } 232 }, 233 ` 234 235 addon := &InstallPackage{ 236 Meta: Meta{ 237 Name: "velaux", 238 DeployTo: &DeployTo{ 239 RuntimeCluster: true, 240 }, 241 }, 242 Parameters: paraDefined, 243 AppCueTemplate: ElementFile{Data: appTemplate}, 244 } 245 render := addonCueTemplateRender{ 246 addon: addon, 247 inputArgs: map[string]interface{}{ 248 "namespace": "vela-system", 249 }, 250 } 251 app, auxdata, err := render.renderApp() 252 assert.NoError(t, err) 253 assert.Equal(t, len(app.Spec.Components), 1) 254 str, err := json.Marshal(app.Spec.Components[0].Properties) 255 assert.NoError(t, err) 256 assert.True(t, strings.Contains(string(str), `{"name":"vela-system"}`)) 257 assert.Equal(t, len(auxdata), 1) 258 auxStr, err := json.Marshal(auxdata[0]) 259 assert.NoError(t, err) 260 assert.True(t, strings.Contains(string(auxStr), "myData")) 261 assert.True(t, strings.Contains(string(auxStr), "addons.oam.dev/auxiliary-name")) 262 assert.True(t, strings.Contains(string(auxStr), "configmap")) 263 264 // test no error when no outputs 265 addon.AppCueTemplate = ElementFile{Data: appTemplateNoOutputs} 266 _, _, err = render.renderApp() 267 assert.NoError(t, err) 268 } 269 270 func TestAppComponentRender(t *testing.T) { 271 paraDefined := `parameter: { 272 image: string 273 }` 274 compTemplate := `output: { 275 type: "webservice" 276 name: "velaux" 277 properties: { 278 image: parameter.image} 279 }` 280 addon := &InstallPackage{ 281 Meta: Meta{ 282 Name: "velaux", 283 DeployTo: &DeployTo{ 284 RuntimeCluster: true, 285 }, 286 }, 287 Parameters: paraDefined, 288 } 289 290 render := addonCueTemplateRender{ 291 addon: addon, 292 inputArgs: map[string]interface{}{ 293 "image": "1.4.1", 294 }, 295 } 296 comp := common.ApplicationComponent{} 297 err := render.toObject(compTemplate, renderOutputCuePath, &comp) 298 assert.NoError(t, err) 299 assert.Equal(t, comp.Name, "velaux") 300 assert.Equal(t, comp.Type, "webservice") 301 str, err := json.Marshal(comp.Properties) 302 assert.NoError(t, err) 303 assert.Equal(t, `{"image":"1.4.1"}`, string(str)) 304 } 305 306 func TestCheckNeedAttachTopologyPolicy(t *testing.T) { 307 addon := &InstallPackage{ 308 AppCueTemplate: ElementFile{ 309 Data: "not empty", 310 Name: "template.cue", 311 }, 312 } 313 assert.Equal(t, checkNeedAttachTopologyPolicy(&v1beta1.Application{Spec: v1beta1.ApplicationSpec{Policies: []v1beta1.AppPolicy{{ 314 Type: v1alpha1.SharedResourcePolicyType, 315 }}}}, addon), false) 316 317 addon0 := &InstallPackage{ 318 AppCueTemplate: ElementFile{ 319 Data: "", 320 Name: "template.cue", 321 }, 322 Meta: Meta{ 323 DeployTo: &DeployTo{RuntimeCluster: true}, 324 }, 325 } 326 assert.Equal(t, checkNeedAttachTopologyPolicy(&v1beta1.Application{Spec: v1beta1.ApplicationSpec{Policies: []v1beta1.AppPolicy{{ 327 Type: v1alpha1.SharedResourcePolicyType, 328 }}}}, addon0), true) 329 330 addon1 := &InstallPackage{ 331 Meta: Meta{ 332 DeployTo: nil, 333 }, 334 } 335 assert.Equal(t, checkNeedAttachTopologyPolicy(nil, addon1), false) 336 337 addon2 := &InstallPackage{ 338 Meta: Meta{ 339 DeployTo: &DeployTo{RuntimeCluster: false}, 340 }, 341 } 342 assert.Equal(t, checkNeedAttachTopologyPolicy(nil, addon2), false) 343 344 addon3 := &InstallPackage{ 345 Meta: Meta{ 346 DeployTo: &DeployTo{RuntimeCluster: true}, 347 }, 348 } 349 assert.Equal(t, checkNeedAttachTopologyPolicy(&v1beta1.Application{Spec: v1beta1.ApplicationSpec{Policies: []v1beta1.AppPolicy{{ 350 Type: v1alpha1.TopologyPolicyType, 351 }}}}, addon3), false) 352 353 addon4 := &InstallPackage{ 354 Meta: Meta{ 355 DeployTo: &DeployTo{RuntimeCluster: true}, 356 }, 357 } 358 assert.Equal(t, checkNeedAttachTopologyPolicy(&v1beta1.Application{Spec: v1beta1.ApplicationSpec{Policies: []v1beta1.AppPolicy{{ 359 Type: v1alpha1.SharedResourcePolicyType, 360 }}}}, addon4), true) 361 362 } 363 364 func TestGenerateAppFrameworkWithCue(t *testing.T) { 365 paraDefined := `parameter: { 366 // +usage=The clusters to install 367 clusters?: [...string] 368 namespace: string 369 }` 370 cueTemplate := `output: { 371 apiVersion: "core.oam.dev/v1beta1" 372 kind: "Application" 373 metadata: { 374 name: "velaux" 375 namespace: "vela-system" 376 } 377 spec: { 378 components: [{ 379 type: "k8s-objects" 380 name: "vela-namespace" 381 properties: objects: [{ 382 apiVersion: "v1" 383 kind: "Namespace" 384 metadata: name: parameter.namespace 385 }] 386 }] 387 policies: [{ 388 type: "shared-resource" 389 name: "namespace" 390 properties: rules: [{selector: resourceTypes: ["Namespace"]}] 391 }, { 392 type: "topology" 393 name: "deploy-topology" 394 properties: { 395 if parameter.clusters != _|_ { 396 clusters: parameter.clusters 397 } 398 if parameter.clusters == _|_ { 399 clusterLabelSelector: {} 400 } 401 namespace: parameter.namespace 402 } 403 }] 404 } 405 }` 406 cueAddon := &InstallPackage{ 407 Meta: Meta{Name: "velaux", DeployTo: &DeployTo{RuntimeCluster: true}}, 408 AppCueTemplate: ElementFile{Data: cueTemplate}, 409 Parameters: paraDefined, 410 } 411 app, _, err := generateAppFramework(cueAddon, map[string]interface{}{ 412 "namespace": "vela-system", 413 }) 414 assert.NoError(t, err) 415 assert.Equal(t, len(app.Spec.Components), 1) 416 str, err := json.Marshal(app.Spec.Components[0].Properties) 417 assert.NoError(t, err) 418 assert.True(t, strings.Contains(string(str), `{"name":"vela-system"}`)) 419 assert.Equal(t, len(app.Spec.Policies), 2) 420 str, err = json.Marshal(app.Spec.Policies) 421 assert.NoError(t, err) 422 assert.True(t, strings.Contains(string(str), `"clusterLabelSelector":{}`)) 423 assert.Equal(t, len(app.Labels), 2) 424 } 425 426 func TestGenerateAppFrameworkWithYamlTemplate(t *testing.T) { 427 yamlAddon := &InstallPackage{ 428 Meta: Meta{Name: "velaux"}, 429 AppTemplate: nil, 430 } 431 app, _, err := generateAppFramework(yamlAddon, nil) 432 assert.NoError(t, err) 433 assert.Equal(t, app.Spec.Components != nil, true) 434 assert.Equal(t, len(app.Labels), 2) 435 436 noCompAddon := &InstallPackage{ 437 Meta: Meta{Name: "velaux"}, 438 AppTemplate: &v1beta1.Application{}, 439 } 440 app, _, err = generateAppFramework(noCompAddon, nil) 441 assert.NoError(t, err) 442 assert.Equal(t, app.Spec.Components != nil, true) 443 assert.Equal(t, len(app.Labels), 2) 444 } 445 446 func TestRenderCueResourceError(t *testing.T) { 447 cueTemplate1 := `output: { 448 type: "webservice" 449 name: "velaux" 450 }` 451 cueTemplate2 := `output: { 452 type: "webservice" 453 name: "velaux2" 454 }` 455 cueTemplate3 := `nooutput: { 456 type: "webservice" 457 name: "velaux3" 458 }` 459 comp, err := renderResources(&InstallPackage{ 460 CUETemplates: []ElementFile{ 461 { 462 Data: cueTemplate1, 463 Name: "tmplaate1.cue", 464 }, 465 { 466 Data: cueTemplate2, 467 Name: "tmplaate2.cue", 468 }, 469 { 470 Data: cueTemplate3, 471 Name: "tmplaate3.cue", 472 }, 473 }, 474 }, nil) 475 assert.NoError(t, err) 476 assert.Equal(t, len(comp), 2) 477 } 478 479 func TestCheckCueFileHasPackageHeader(t *testing.T) { 480 testCueTemplateWithPkg := ` 481 package main 482 483 kustomizeController: { 484 // About this name, refer to #429 for details. 485 name: "fluxcd-kustomize-controller" 486 type: "webservice" 487 dependsOn: ["fluxcd-ns"] 488 properties: { 489 imagePullPolicy: "IfNotPresent" 490 image: _base + "fluxcd/kustomize-controller:v0.26.0" 491 env: [ 492 { 493 name: "RUNTIME_NAMESPACE" 494 value: _targetNamespace 495 }, 496 ] 497 livenessProbe: { 498 httpGet: { 499 path: "/healthz" 500 port: 9440 501 } 502 timeoutSeconds: 5 503 } 504 readinessProbe: { 505 httpGet: { 506 path: "/readyz" 507 port: 9440 508 } 509 timeoutSeconds: 5 510 } 511 volumeMounts: { 512 emptyDir: [ 513 { 514 name: "temp" 515 mountPath: "/tmp" 516 }, 517 ] 518 } 519 } 520 traits: [ 521 { 522 type: "service-account" 523 properties: { 524 name: "sa-kustomize-controller" 525 create: true 526 privileges: _rules 527 } 528 }, 529 { 530 type: "labels" 531 properties: { 532 "control-plane": "controller" 533 // This label is kept to avoid breaking existing 534 // KubeVela e2e tests (makefile e2e-setup). 535 "app": "kustomize-controller" 536 } 537 }, 538 { 539 type: "command" 540 properties: { 541 args: controllerArgs 542 } 543 }, 544 ] 545 } 546 ` 547 548 testCueTemplateWithoutPkg := ` 549 output: { 550 type: "helm" 551 name: "nginx-ingress" 552 properties: { 553 repoType: "helm" 554 url: "https://kubernetes.github.io/ingress-nginx" 555 chart: "ingress-nginx" 556 version: "4.2.0" 557 values: { 558 controller: service: type: parameter["serviceType"] 559 } 560 } 561 } 562 ` 563 564 cueTemplate := ElementFile{Name: "test-file.cue", Data: testCueTemplateWithPkg} 565 ok, err := checkCueFileHasPackageHeader(cueTemplate) 566 assert.NoError(t, err) 567 assert.Equal(t, true, ok) 568 569 cueTemplate = ElementFile{Name: "test-file-without-pkg.cue", Data: testCueTemplateWithoutPkg} 570 ok, err = checkCueFileHasPackageHeader(cueTemplate) 571 assert.NoError(t, err) 572 assert.Equal(t, false, ok) 573 }