github.com/oam-dev/kubevela@v1.9.11/pkg/appfile/template_test.go (about) 1 /* 2 Copyright 2021 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 appfile 18 19 import ( 20 "context" 21 "testing" 22 23 "cuelang.org/go/cue/cuecontext" 24 "github.com/crossplane/crossplane-runtime/pkg/test" 25 "github.com/google/go-cmp/cmp" 26 "github.com/stretchr/testify/assert" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "k8s.io/apimachinery/pkg/runtime" 29 ktypes "k8s.io/apimachinery/pkg/types" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 33 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 34 "github.com/oam-dev/kubevela/apis/types" 35 oamutil "github.com/oam-dev/kubevela/pkg/oam/util" 36 ) 37 38 func TestLoadComponentTemplate(t *testing.T) { 39 cueTemplate := ` 40 context: { 41 name: "test" 42 } 43 output: { 44 apiVersion: "apps/v1" 45 kind: "Deployment" 46 spec: { 47 selector: matchLabels: { 48 "app.oam.dev/component": context.name 49 } 50 51 template: { 52 metadata: labels: { 53 "app.oam.dev/component": context.name 54 } 55 56 spec: { 57 containers: [{ 58 name: context.name 59 image: parameter.image 60 61 if parameter["cmd"] != _|_ { 62 command: parameter.cmd 63 } 64 }] 65 } 66 } 67 68 selector: 69 matchLabels: 70 "app.oam.dev/component": context.name 71 } 72 } 73 74 parameter: { 75 // +usage=Which image would you like to use for your service 76 // +short=i 77 image: string 78 79 cmd?: [...string] 80 } 81 ` 82 83 var componentDefintion = ` 84 apiVersion: core.oam.dev/v1beta1 85 kind: ComponentDefinition 86 metadata: 87 name: worker 88 namespace: default 89 annotations: 90 definition.oam.dev/description: "Long-running scalable backend worker without network endpoint" 91 spec: 92 workload: 93 definition: 94 apiVersion: apps/v1 95 kind: Deployment 96 extension: 97 template: | 98 ` + cueTemplate 99 100 // Create mock client 101 tclient := test.MockClient{ 102 MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj client.Object) error { 103 switch o := obj.(type) { 104 case *v1beta1.ComponentDefinition: 105 cd, err := oamutil.UnMarshalStringToComponentDefinition(componentDefintion) 106 if err != nil { 107 return err 108 } 109 *o = *cd 110 } 111 return nil 112 }, 113 } 114 115 temp, err := LoadTemplate(context.TODO(), &tclient, "worker", types.TypeComponentDefinition) 116 117 if err != nil { 118 t.Error(err) 119 return 120 } 121 inst := cuecontext.New().CompileString(temp.TemplateStr) 122 instDest := cuecontext.New().CompileString(cueTemplate) 123 s1, _ := inst.Value().String() 124 s2, _ := instDest.Value().String() 125 if s1 != s2 { 126 t.Errorf("parsered template is not correct") 127 } 128 } 129 130 func TestLoadTraitTemplate(t *testing.T) { 131 cueTemplate := ` 132 parameter: { 133 domain: string 134 http: [string]: int 135 } 136 context: { 137 name: "test" 138 } 139 // trait template can have multiple outputs in one trait 140 outputs: service: { 141 apiVersion: "v1" 142 kind: "Service" 143 metadata: 144 name: context.name 145 spec: { 146 selector: 147 "app.oam.dev/component": context.name 148 ports: [ 149 for k, v in parameter.http { 150 port: v 151 targetPort: v 152 }, 153 ] 154 } 155 } 156 157 outputs: ingress: { 158 apiVersion: "networking.k8s.io/v1beta1" 159 kind: "Ingress" 160 metadata: 161 name: context.name 162 spec: { 163 rules: [{ 164 host: parameter.domain 165 http: { 166 paths: [ 167 for k, v in parameter.http { 168 path: k 169 backend: { 170 serviceName: context.name 171 servicePort: v 172 } 173 }, 174 ] 175 } 176 }] 177 } 178 } 179 ` 180 181 var traitDefintion = ` 182 apiVersion: core.oam.dev/v1beta1 183 kind: TraitDefinition 184 metadata: 185 annotations: 186 definition.oam.dev/description: "Configures K8s ingress and service to enable web traffic for your service. 187 Please use route trait in cap center for advanced usage." 188 name: ingress 189 namespace: default 190 spec: 191 status: 192 customStatus: |- 193 if len(context.outputs.ingress.status.loadBalancer.ingress) > 0 { 194 message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host + ", IP: " + context.outputs.ingress.status.loadBalancer.ingress[0].ip 195 } 196 if len(context.outputs.ingress.status.loadBalancer.ingress) == 0 { 197 message: "No loadBalancer found, visiting by using 'vela port-forward " + context.appName + " --route'\n" 198 } 199 healthPolicy: | 200 isHealth: len(context.outputs.service.spec.clusterIP) > 0 201 appliesToWorkloads: 202 - deployments.apps 203 schematic: 204 cue: 205 template: | 206 ` + cueTemplate 207 208 // Create mock client 209 tclient := test.MockClient{ 210 MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj client.Object) error { 211 switch o := obj.(type) { 212 case *v1beta1.TraitDefinition: 213 wd, err := oamutil.UnMarshalStringToTraitDefinition(traitDefintion) 214 if err != nil { 215 return err 216 } 217 *o = *wd 218 } 219 return nil 220 }, 221 } 222 223 temp, err := LoadTemplate(context.TODO(), &tclient, "ingress", types.TypeTrait) 224 225 if err != nil { 226 t.Error(err) 227 return 228 } 229 inst := cuecontext.New().CompileString(temp.TemplateStr) 230 instDest := cuecontext.New().CompileString(cueTemplate) 231 s1, _ := inst.Value().String() 232 s2, _ := instDest.Value().String() 233 if s1 != s2 { 234 t.Errorf("parsered template is not correct") 235 } 236 } 237 238 func TestLoadSchematicToTemplate(t *testing.T) { 239 testCases := map[string]struct { 240 schematic *common.Schematic 241 status *common.Status 242 ext *runtime.RawExtension 243 want *Template 244 }{ 245 "only tmp": { 246 schematic: &common.Schematic{CUE: &common.CUE{Template: "t1"}}, 247 want: &Template{ 248 TemplateStr: "t1", 249 CapabilityCategory: types.CUECategory, 250 }, 251 }, 252 "no tmp,but has extension": { 253 ext: &runtime.RawExtension{Raw: []byte(`{"template":"t1"}`)}, 254 want: &Template{ 255 TemplateStr: "t1", 256 CapabilityCategory: types.CUECategory, 257 }, 258 }, 259 "no tmp,but has extension without temp": { 260 ext: &runtime.RawExtension{Raw: []byte(`{"template":{"t1":"t2"}}`)}, 261 want: &Template{ 262 TemplateStr: "", 263 CapabilityCategory: types.CUECategory, 264 }, 265 }, 266 "tmp with status": { 267 schematic: &common.Schematic{CUE: &common.CUE{Template: "t1"}}, 268 status: &common.Status{ 269 CustomStatus: "s1", 270 HealthPolicy: "h1", 271 }, 272 want: &Template{ 273 TemplateStr: "t1", 274 CustomStatus: "s1", 275 Health: "h1", 276 CapabilityCategory: types.CUECategory, 277 }, 278 }, 279 "no tmp only status": { 280 status: &common.Status{ 281 CustomStatus: "s1", 282 HealthPolicy: "h1", 283 }, 284 want: &Template{ 285 CustomStatus: "s1", 286 Health: "h1", 287 }, 288 }, 289 "terraform schematic": { 290 schematic: &common.Schematic{Terraform: &common.Terraform{}}, 291 want: &Template{ 292 CapabilityCategory: types.TerraformCategory, 293 Terraform: &common.Terraform{}, 294 }, 295 }, 296 } 297 for reason, casei := range testCases { 298 gtmp := &Template{} 299 err := loadSchematicToTemplate(gtmp, casei.status, casei.schematic, casei.ext) 300 assert.NoError(t, err, reason) 301 assert.Equal(t, casei.want, gtmp, reason) 302 } 303 } 304 305 func TestDryRunTemplateLoader(t *testing.T) { 306 compDefStr := ` 307 apiVersion: core.oam.dev/v1beta1 308 kind: ComponentDefinition 309 metadata: 310 name: myworker 311 spec: 312 status: 313 customStatus: testCustomStatus 314 healthPolicy: testHealthPolicy 315 workload: 316 definition: 317 apiVersion: apps/v1 318 kind: Deployment 319 schematic: 320 cue: 321 template: testCUE ` 322 323 traitDefStr := ` 324 apiVersion: core.oam.dev/v1beta1 325 kind: TraitDefinition 326 metadata: 327 name: myingress 328 spec: 329 status: 330 customStatus: testCustomStatus 331 healthPolicy: testHealthPolicy 332 appliesToWorkloads: 333 - deployments.apps 334 schematic: 335 cue: 336 template: testCUE ` 337 338 compDef, _ := oamutil.UnMarshalStringToComponentDefinition(compDefStr) 339 traitDef, _ := oamutil.UnMarshalStringToTraitDefinition(traitDefStr) 340 unstrctCompDef, _ := oamutil.Object2Unstructured(compDef) 341 unstrctTraitDef, _ := oamutil.Object2Unstructured(traitDef) 342 343 expectedCompTmpl := &Template{ 344 TemplateStr: "testCUE", 345 Health: "testHealthPolicy", 346 CustomStatus: "testCustomStatus", 347 CapabilityCategory: types.CUECategory, 348 Reference: common.WorkloadTypeDescriptor{ 349 Definition: common.WorkloadGVK{ 350 APIVersion: "apps/v1", 351 Kind: "Deployment", 352 }, 353 }, 354 ComponentDefinition: compDef, 355 } 356 357 expectedTraitTmpl := &Template{ 358 TemplateStr: "testCUE", 359 Health: "testHealthPolicy", 360 CustomStatus: "testCustomStatus", 361 CapabilityCategory: types.CUECategory, 362 TraitDefinition: traitDef, 363 } 364 365 dryRunLoadTemplate := DryRunTemplateLoader([]*unstructured.Unstructured{unstrctCompDef, unstrctTraitDef}) 366 compTmpl, err := dryRunLoadTemplate(nil, nil, "myworker", types.TypeComponentDefinition) 367 if err != nil { 368 t.Error("failed load template of component defintion", err) 369 } 370 if diff := cmp.Diff(expectedCompTmpl, compTmpl); diff != "" { 371 t.Fatal("failed load template of component defintion", diff) 372 } 373 374 traitTmpl, err := dryRunLoadTemplate(nil, nil, "myingress", types.TypeTrait) 375 if err != nil { 376 t.Error("failed load template of component defintion", err) 377 } 378 if diff := cmp.Diff(expectedTraitTmpl, traitTmpl); diff != "" { 379 t.Fatal("failed load template of trait definition ", diff) 380 } 381 }