github.com/oam-dev/kubevela@v1.9.11/references/appfile/api/appfile_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 api 18 19 import ( 20 "os" 21 "testing" 22 23 "github.com/stretchr/testify/assert" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 "sigs.k8s.io/yaml" 27 28 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 29 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 30 "github.com/oam-dev/kubevela/apis/types" 31 "github.com/oam-dev/kubevela/pkg/oam" 32 "github.com/oam-dev/kubevela/pkg/oam/util" 33 cmdutil "github.com/oam-dev/kubevela/pkg/utils/util" 34 "github.com/oam-dev/kubevela/references/appfile/template" 35 ) 36 37 func TestBuildOAMApplication2(t *testing.T) { 38 expectNs := "test-ns" 39 40 tm := template.NewFakeTemplateManager() 41 tm.Templates = map[string]*template.Template{ 42 "containerWorkload": { 43 Captype: types.TypeWorkload, 44 Raw: `{parameters : {image: string} }`, 45 }, 46 "scaler": { 47 Captype: types.TypeTrait, 48 Raw: `{parameters : {relicas: int} }`, 49 }, 50 } 51 52 testCases := []struct { 53 appFile *AppFile 54 expectApp *v1beta1.Application 55 }{ 56 { 57 appFile: &AppFile{ 58 Name: "test", 59 Services: map[string]Service{ 60 "webapp": map[string]interface{}{ 61 "type": "containerWorkload", 62 "image": "busybox", 63 }, 64 }, 65 }, 66 expectApp: &v1beta1.Application{ 67 TypeMeta: metav1.TypeMeta{ 68 Kind: "Application", 69 APIVersion: "core.oam.dev/v1beta1", 70 }, ObjectMeta: metav1.ObjectMeta{ 71 Name: "test", 72 }, 73 Spec: v1beta1.ApplicationSpec{ 74 Components: []common.ApplicationComponent{ 75 { 76 Name: "webapp", 77 Type: "containerWorkload", 78 Properties: &runtime.RawExtension{ 79 Raw: []byte("{\"image\":\"busybox\"}"), 80 }, 81 }, 82 }, 83 }, 84 }, 85 }, 86 { 87 appFile: &AppFile{ 88 Name: "test", 89 Services: map[string]Service{ 90 "webapp": map[string]interface{}{ 91 "type": "containerWorkload", 92 "image": "busybox", 93 "scaler": map[string]interface{}{ 94 "replicas": 10, 95 }, 96 }, 97 }, 98 }, 99 expectApp: &v1beta1.Application{ 100 TypeMeta: metav1.TypeMeta{ 101 Kind: "Application", 102 APIVersion: "core.oam.dev/v1beta1", 103 }, ObjectMeta: metav1.ObjectMeta{ 104 Name: "test", 105 }, 106 Spec: v1beta1.ApplicationSpec{ 107 Components: []common.ApplicationComponent{ 108 { 109 Name: "webapp", 110 Type: "containerWorkload", 111 Properties: &runtime.RawExtension{ 112 Raw: []byte("{\"image\":\"busybox\"}"), 113 }, 114 Traits: []common.ApplicationTrait{ 115 { 116 Type: "scaler", 117 Properties: &runtime.RawExtension{ 118 Raw: []byte("{\"replicas\":10}"), 119 }, 120 }, 121 }, 122 }, 123 }, 124 }, 125 }, 126 }, 127 } 128 129 for _, tcase := range testCases { 130 tcase.expectApp.Namespace = expectNs 131 o, err := tcase.appFile.ConvertToApplication(expectNs, cmdutil.IOStreams{ 132 In: os.Stdin, 133 Out: os.Stdout, 134 }, tm, false) 135 assert.NoError(t, err) 136 assert.Equal(t, tcase.expectApp, o) 137 } 138 } 139 140 func TestBuildOAMApplication(t *testing.T) { 141 yamlOneService := `name: myapp 142 services: 143 express-server: 144 image: oamdev/testapp:v1 145 cmd: ["node", "server.js"] 146 route: 147 domain: example.com 148 http: 149 "/": 8080 150 ` 151 yamlTwoServices := yamlOneService + ` 152 mongodb: 153 type: backend 154 image: bitnami/mongodb:3.6.20 155 cmd: ["mongodb"] 156 ` 157 yamlNoImage := `name: myapp 158 services: 159 bad-server: 160 build: 161 docker: 162 file: Dockerfile 163 cmd: ["node", "server.js"] 164 ` 165 166 templateWebservice := `parameter: #webservice 167 #webservice: { 168 cmd: [...string] 169 image: string 170 } 171 172 output: { 173 apiVersion: "test.oam.dev/v1" 174 kind: "WebService" 175 metadata: { 176 name: context.name 177 } 178 spec: { 179 image: parameter.image 180 command: parameter.cmd 181 } 182 } 183 ` 184 templateBackend := `parameter: #backend 185 #backend: { 186 cmd: [...string] 187 image: string 188 } 189 190 output: { 191 apiVersion: "test.oam.dev/v1" 192 kind: "Worker" 193 metadata: { 194 name: context.name 195 } 196 spec: { 197 image: parameter.image 198 command: parameter.cmd 199 } 200 }` 201 templateRoute := `parameter: #route 202 #route: { 203 domain: string 204 http: [string]: int 205 } 206 207 // trait template can have multiple outputs and they are all traits 208 outputs: service: { 209 apiVersion: "v1" 210 kind: "Service" 211 metadata: 212 name: context.name 213 spec: { 214 selector: 215 app: context.name 216 ports: [ 217 for k, v in parameter.http { 218 port: v 219 targetPort: v 220 } 221 ] 222 } 223 } 224 225 outputs: ingress: { 226 apiVersion: "networking.k8s.io/v1beta1" 227 kind: "Ingress" 228 spec: { 229 rules: [{ 230 host: parameter.domain 231 http: { 232 paths: [ 233 for k, v in parameter.http { 234 path: k 235 backend: { 236 serviceName: context.name 237 servicePort: v 238 } 239 } 240 ] 241 } 242 }] 243 } 244 } 245 ` 246 ac1 := &v1beta1.Application{ 247 TypeMeta: metav1.TypeMeta{ 248 Kind: "Application", 249 APIVersion: "core.oam.dev/v1beta1", 250 }, 251 ObjectMeta: metav1.ObjectMeta{ 252 Name: "myapp", 253 Namespace: "default", 254 }, 255 Spec: v1beta1.ApplicationSpec{ 256 Components: []common.ApplicationComponent{{ 257 Type: "webservice", 258 Name: "express-server", 259 Properties: &runtime.RawExtension{ 260 Raw: []byte(`{"image": "oamdev/testapp:v1", "cmd": ["node", "server.js"]}`), 261 }, 262 Traits: []common.ApplicationTrait{{ 263 Type: "route", 264 Properties: &runtime.RawExtension{ 265 Raw: []byte(`{"domain": "example.com", "http":{"/": 8080}}`), 266 }, 267 }, 268 }, 269 }}, 270 }, 271 } 272 ac2 := ac1.DeepCopy() 273 ac2.Spec.Components = append(ac2.Spec.Components, common.ApplicationComponent{ 274 Name: "mongodb", 275 Type: "backend", 276 Properties: &runtime.RawExtension{ 277 Raw: []byte(`{"image":"bitnami/mongodb:3.6.20","cmd": ["mongodb"]}`), 278 }, 279 Traits: []common.ApplicationTrait{}, 280 }) 281 282 ac3 := ac1.DeepCopy() 283 ac3.Spec.Components[0].Type = "withconfig" 284 285 // TODO application 那边补测试: 286 // 2. 1对多的情况,多对1 的情况 287 288 type args struct { 289 appfileData string 290 workloadTemplates map[string]string 291 traitTemplates map[string]string 292 } 293 type want struct { 294 objs []oam.Object 295 app *v1beta1.Application 296 err error 297 } 298 cases := map[string]struct { 299 args args 300 want want 301 }{ 302 "one service should generate one component and one appconfig": { 303 args: args{ 304 appfileData: yamlOneService, 305 workloadTemplates: map[string]string{ 306 "webservice": templateWebservice, 307 }, 308 traitTemplates: map[string]string{ 309 "route": templateRoute, 310 }, 311 }, 312 want: want{ 313 app: ac1, 314 objs: []oam.Object{}, 315 }, 316 }, 317 "two services should generate two components and one appconfig": { 318 args: args{ 319 appfileData: yamlTwoServices, 320 workloadTemplates: map[string]string{ 321 "webservice": templateWebservice, 322 "backend": templateBackend, 323 }, 324 traitTemplates: map[string]string{ 325 "route": templateRoute, 326 }, 327 }, 328 want: want{ 329 app: ac2, 330 objs: []oam.Object{}, 331 }, 332 }, 333 "no image should fail": { 334 args: args{ 335 appfileData: yamlNoImage, 336 }, 337 want: want{ 338 err: ErrImageNotDefined, 339 }, 340 }, 341 } 342 343 io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} 344 for caseName, c := range cases { 345 t.Run(caseName, func(t *testing.T) { 346 347 app := NewAppFile() 348 err := yaml.Unmarshal([]byte(c.args.appfileData), app) 349 if err != nil { 350 t.Fatal(err) 351 } 352 tm := template.NewFakeTemplateManager() 353 for k, v := range c.args.traitTemplates { 354 tm.Templates[k] = &template.Template{ 355 Captype: types.TypeTrait, 356 Raw: v, 357 } 358 } 359 for k, v := range c.args.workloadTemplates { 360 tm.Templates[k] = &template.Template{ 361 Captype: types.TypeWorkload, 362 Raw: v, 363 } 364 } 365 366 application, err := app.ConvertToApplication("default", io, tm, false) 367 if c.want.err != nil { 368 assert.Equal(t, c.want.err, err) 369 return 370 } 371 assert.Equal(t, c.want.app.ObjectMeta, application.ObjectMeta) 372 for _, comp := range application.Spec.Components { 373 var found bool 374 for idx, expcomp := range c.want.app.Spec.Components { 375 if comp.Name != expcomp.Name { 376 continue 377 } 378 found = true 379 assert.Equal(t, comp.Type, c.want.app.Spec.Components[idx].Type) 380 assert.Equal(t, comp.Name, c.want.app.Spec.Components[idx].Name) 381 assert.Equal(t, comp.Scopes, c.want.app.Spec.Components[idx].Scopes) 382 383 got, err := util.RawExtension2Map(comp.Properties) 384 assert.NoError(t, err) 385 exp, err := util.RawExtension2Map(c.want.app.Spec.Components[idx].Properties) 386 assert.NoError(t, err) 387 assert.Equal(t, exp, got) 388 for tidx, tr := range comp.Traits { 389 assert.Equal(t, tr.Type, c.want.app.Spec.Components[idx].Traits[tidx].Type) 390 391 got, err := util.RawExtension2Map(tr.Properties) 392 assert.NoError(t, err) 393 exp, err := util.RawExtension2Map(c.want.app.Spec.Components[idx].Traits[tidx].Properties) 394 assert.NoError(t, err) 395 assert.Equal(t, exp, got) 396 } 397 } 398 assert.Equal(t, true, found, "no component found for %s", comp.Name) 399 } 400 401 }) 402 } 403 }