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  }