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  }