github.com/oam-dev/kubevela@v1.9.11/pkg/appfile/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 appfile
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"testing"
    23  
    24  	"cuelang.org/go/cue/cuecontext"
    25  	"github.com/crossplane/crossplane-runtime/pkg/test"
    26  	"github.com/google/go-cmp/cmp"
    27  	terraformtypes "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
    28  	terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2"
    29  	. "github.com/onsi/ginkgo/v2"
    30  	. "github.com/onsi/gomega"
    31  	"github.com/pkg/errors"
    32  	"github.com/stretchr/testify/assert"
    33  	appsv1 "k8s.io/api/apps/v1"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"sigs.k8s.io/yaml"
    38  
    39  	"github.com/kubevela/workflow/pkg/cue/model"
    40  
    41  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    42  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    43  	oamtypes "github.com/oam-dev/kubevela/apis/types"
    44  	"github.com/oam-dev/kubevela/pkg/cue/definition"
    45  	"github.com/oam-dev/kubevela/pkg/cue/process"
    46  	"github.com/oam-dev/kubevela/pkg/oam"
    47  	"github.com/oam-dev/kubevela/pkg/oam/util"
    48  )
    49  
    50  var _ = Describe("Test Workflow", func() {
    51  	It("generate workflow task runners", func() {
    52  		workflowStepDef := v1beta1.WorkflowStepDefinition{
    53  			Spec: v1beta1.WorkflowStepDefinitionSpec{
    54  				Schematic: &common.Schematic{
    55  					CUE: &common.CUE{
    56  						Template: `
    57  wait: op.#ConditionalWait & {
    58    continue: true
    59  }
    60  `,
    61  					},
    62  				},
    63  			},
    64  		}
    65  		workflowStepDef.Name = "test-wait"
    66  		workflowStepDef.Namespace = "default"
    67  		err := k8sClient.Create(context.Background(), &workflowStepDef)
    68  		Expect(err).To(BeNil())
    69  
    70  		notCueStepDef := v1beta1.WorkflowStepDefinition{
    71  			Spec: v1beta1.WorkflowStepDefinitionSpec{
    72  				Schematic: &common.Schematic{},
    73  			},
    74  		}
    75  
    76  		notCueStepDef.Name = "not-cue"
    77  		notCueStepDef.Namespace = "default"
    78  		err = k8sClient.Create(context.Background(), &notCueStepDef)
    79  		Expect(err).To(BeNil())
    80  	})
    81  })
    82  
    83  var _ = Describe("Test Terraform schematic appfile", func() {
    84  	It("workload capability is Terraform", func() {
    85  		var (
    86  			ns            = "default"
    87  			compName      = "sample-db"
    88  			appName       = "webapp"
    89  			revision      = "v1"
    90  			configuration = `
    91  module "rds" {
    92    source = "terraform-alicloud-modules/rds/alicloud"
    93    engine = "MySQL"
    94    engine_version = "8.0"
    95    instance_type = "rds.mysql.c1.large"
    96    instance_storage = "20"
    97    instance_name = var.instance_name
    98    account_name = var.account_name
    99    password = var.password
   100  }
   101  
   102  output "DB_NAME" {
   103    value = module.rds.this_db_instance_name
   104  }
   105  output "DB_USER" {
   106    value = module.rds.this_db_database_account
   107  }
   108  output "DB_PORT" {
   109    value = module.rds.this_db_instance_port
   110  }
   111  output "DB_HOST" {
   112    value = module.rds.this_db_instance_connection_string
   113  }
   114  output "DB_PASSWORD" {
   115    value = module.rds.this_db_instance_port
   116  }
   117  
   118  variable "instance_name" {
   119    description = "RDS instance name"
   120    type = string
   121    default = "poc"
   122  }
   123  
   124  variable "account_name" {
   125    description = "RDS instance user account name"
   126    type = "string"
   127    default = "oam"
   128  }
   129  
   130  variable "password" {
   131    description = "RDS instance account password"
   132    type = "string"
   133    default = "Xyfff83jfewGGfaked"
   134  }
   135  `
   136  		)
   137  
   138  		wl := &Component{
   139  			Name: "sample-db",
   140  			FullTemplate: &Template{
   141  				Terraform: &common.Terraform{
   142  					Configuration: configuration,
   143  					Type:          "hcl",
   144  				},
   145  				ComponentDefinition: &v1beta1.ComponentDefinition{
   146  					Spec: v1beta1.ComponentDefinitionSpec{
   147  						Schematic: &common.Schematic{
   148  							Terraform: &common.Terraform{},
   149  						},
   150  					},
   151  				},
   152  			},
   153  			CapabilityCategory: oamtypes.TerraformCategory,
   154  			Params: map[string]interface{}{
   155  				"account_name": "oamtest",
   156  				"writeConnectionSecretToRef": map[string]interface{}{
   157  					"name": "db",
   158  				},
   159  				process.OutputSecretName: "db-conn",
   160  			},
   161  		}
   162  
   163  		af := &Appfile{
   164  			ParsedComponents: []*Component{wl},
   165  			Name:             appName,
   166  			AppRevisionName:  revision,
   167  			Namespace:        ns,
   168  		}
   169  
   170  		variable := map[string]interface{}{"account_name": "oamtest"}
   171  		data, _ := json.Marshal(variable)
   172  		raw := &runtime.RawExtension{}
   173  		raw.Raw = data
   174  
   175  		workload := terraformapi.Configuration{
   176  			ObjectMeta: metav1.ObjectMeta{
   177  				Labels: map[string]string{
   178  					"app.oam.dev/appRevision": "v1",
   179  					"app.oam.dev/component":   "sample-db",
   180  					"app.oam.dev/name":        "webapp",
   181  					"workload.oam.dev/type":   "",
   182  				},
   183  				Name:      "sample-db",
   184  				Namespace: "default",
   185  			},
   186  
   187  			Spec: terraformapi.ConfigurationSpec{
   188  				HCL:      configuration,
   189  				Variable: raw,
   190  			},
   191  			Status: terraformapi.ConfigurationStatus{},
   192  		}
   193  		workload.Spec.WriteConnectionSecretToReference = &terraformtypes.SecretReference{Name: "db", Namespace: "default"}
   194  
   195  		expectCompManifest := &oamtypes.ComponentManifest{
   196  			Name: compName,
   197  			ComponentOutput: func() *unstructured.Unstructured {
   198  				r, _ := util.Object2Unstructured(workload)
   199  				return r
   200  			}(),
   201  		}
   202  
   203  		comps, err := af.GenerateComponentManifests()
   204  		diff := cmp.Diff(comps[0], expectCompManifest)
   205  		Expect(diff).ShouldNot(BeEmpty())
   206  		Expect(err).Should(BeNil())
   207  	})
   208  })
   209  
   210  func TestSetParameterValuesToKubeObj(t *testing.T) {
   211  	tests := map[string]struct {
   212  		reason  string
   213  		obj     unstructured.Unstructured
   214  		values  paramValueSettings
   215  		wantObj unstructured.Unstructured
   216  		wantErr error
   217  	}{
   218  		"InvalidStringType": {
   219  			reason: "An error should be returned",
   220  			values: paramValueSettings{
   221  				"strParam": paramValueSetting{
   222  					Value:      int32(100),
   223  					ValueType:  common.StringType,
   224  					FieldPaths: []string{"spec.test"},
   225  				},
   226  			},
   227  			wantErr: errors.Errorf(errInvalidValueType, common.StringType),
   228  		},
   229  		"InvalidNumberType": {
   230  			reason: "An error should be returned",
   231  			values: paramValueSettings{
   232  				"intParam": paramValueSetting{
   233  					Value:      "test",
   234  					ValueType:  common.NumberType,
   235  					FieldPaths: []string{"spec.test"},
   236  				},
   237  			},
   238  			wantErr: errors.Errorf(errInvalidValueType, common.NumberType),
   239  		},
   240  		"InvalidBoolType": {
   241  			reason: "An error should be returned",
   242  			values: paramValueSettings{
   243  				"boolParam": paramValueSetting{
   244  					Value:      "test",
   245  					ValueType:  common.BooleanType,
   246  					FieldPaths: []string{"spec.test"},
   247  				},
   248  			},
   249  			wantErr: errors.Errorf(errInvalidValueType, common.BooleanType),
   250  		},
   251  		"InvalidFieldPath": {
   252  			reason: "An error should be returned",
   253  			values: paramValueSettings{
   254  				"strParam": paramValueSetting{
   255  					Value:      "test",
   256  					ValueType:  common.StringType,
   257  					FieldPaths: []string{"spec[.test"}, // a invalid field path
   258  				},
   259  			},
   260  			wantErr: errors.Wrap(errors.New(`cannot parse path "spec[.test": unterminated '[' at position 4`),
   261  				`cannot set parameter "strParam" to field "spec[.test"`),
   262  		},
   263  		"Succeed": {
   264  			reason: "No error should be returned",
   265  			obj:    unstructured.Unstructured{Object: make(map[string]interface{})},
   266  			values: paramValueSettings{
   267  				"strParam": paramValueSetting{
   268  					Value:      "test",
   269  					ValueType:  common.StringType,
   270  					FieldPaths: []string{"spec.strField"},
   271  				},
   272  				"intParam": paramValueSetting{
   273  					Value:      10,
   274  					ValueType:  common.NumberType,
   275  					FieldPaths: []string{"spec.intField"},
   276  				},
   277  				"floatParam": paramValueSetting{
   278  					Value:      float64(10.01),
   279  					ValueType:  common.NumberType,
   280  					FieldPaths: []string{"spec.floatField"},
   281  				},
   282  				"boolParam": paramValueSetting{
   283  					Value:      true,
   284  					ValueType:  common.BooleanType,
   285  					FieldPaths: []string{"spec.boolField"},
   286  				},
   287  			},
   288  			wantObj: unstructured.Unstructured{Object: map[string]interface{}{
   289  				"spec": map[string]interface{}{
   290  					"strField":   "test",
   291  					"intField":   int64(10),
   292  					"floatField": float64(10.01),
   293  					"boolField":  true,
   294  				},
   295  			}},
   296  		},
   297  	}
   298  
   299  	for tcName, tc := range tests {
   300  		t.Run(tcName, func(t *testing.T) {
   301  			obj := tc.obj.DeepCopy()
   302  			err := setParameterValuesToKubeObj(obj, tc.values)
   303  			if diff := cmp.Diff(tc.wantObj, *obj); diff != "" {
   304  				t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
   305  			}
   306  			if diff := cmp.Diff(tc.wantErr, err, test.EquateErrors()); diff != "" {
   307  				t.Errorf("\nsetParameterValuesToKubeObj(...)error -want +get \nreason:%s\n%s\n", tc.reason, diff)
   308  			}
   309  		})
   310  	}
   311  
   312  }
   313  
   314  var _ = Describe("Test evalWorkloadWithContext", func() {
   315  	It("workload capability is Terraform", func() {
   316  		var (
   317  			ns       = "default"
   318  			compName = "sample-db"
   319  			err      error
   320  		)
   321  		type appArgs struct {
   322  			wl       *Component
   323  			appName  string
   324  			revision string
   325  		}
   326  
   327  		args := appArgs{
   328  			wl: &Component{
   329  				Name: compName,
   330  				FullTemplate: &Template{
   331  					Terraform: &common.Terraform{
   332  						Configuration: `
   333  module "rds" {
   334    source = "terraform-alicloud-modules/rds/alicloud"
   335    engine = "MySQL"
   336    engine_version = "8.0"
   337    instance_type = "rds.mysql.c1.large"
   338    instance_storage = "20"
   339    instance_name = var.instance_name
   340    account_name = var.account_name
   341    password = var.password
   342  }
   343  
   344  output "DB_NAME" {
   345    value = module.rds.this_db_instance_name
   346  }
   347  output "DB_USER" {
   348    value = module.rds.this_db_database_account
   349  }
   350  output "DB_PORT" {
   351    value = module.rds.this_db_instance_port
   352  }
   353  output "DB_HOST" {
   354    value = module.rds.this_db_instance_connection_string
   355  }
   356  output "DB_PASSWORD" {
   357    value = module.rds.this_db_instance_port
   358  }
   359  
   360  variable "instance_name" {
   361    description = "RDS instance name"
   362    type = string
   363    default = "poc"
   364  }
   365  
   366  variable "account_name" {
   367    description = "RDS instance user account name"
   368    type = "string"
   369    default = "oam"
   370  }
   371  
   372  variable "password" {
   373    description = "RDS instance account password"
   374    type = "string"
   375    default = "Xyfff83jfewGGfaked"
   376  }
   377  `,
   378  						Type: "hcl",
   379  					},
   380  					ComponentDefinition: &v1beta1.ComponentDefinition{
   381  						Spec: v1beta1.ComponentDefinitionSpec{
   382  							Schematic: &common.Schematic{
   383  								Terraform: &common.Terraform{},
   384  							},
   385  						},
   386  					},
   387  				},
   388  				CapabilityCategory: oamtypes.TerraformCategory,
   389  				engine:             definition.NewWorkloadAbstractEngine(compName, pd),
   390  				Params: map[string]interface{}{
   391  					"variable": map[string]interface{}{
   392  						"account_name": "oamtest",
   393  					},
   394  				},
   395  			},
   396  			appName:  "webapp",
   397  			revision: "v1",
   398  		}
   399  
   400  		ctxData := GenerateContextDataFromAppFile(&Appfile{
   401  			Name:            args.appName,
   402  			Namespace:       ns,
   403  			AppRevisionName: args.revision,
   404  		}, args.wl.Name)
   405  		pCtx := NewBasicContext(ctxData, args.wl.Params)
   406  		comp, err := evalWorkloadWithContext(pCtx, args.wl, ns, args.appName)
   407  		Expect(comp.ComponentOutput).ShouldNot(BeNil())
   408  		Expect(comp.Name).Should(Equal(""))
   409  		Expect(err).Should(BeNil())
   410  	})
   411  })
   412  
   413  func TestGenerateTerraformConfigurationWorkload(t *testing.T) {
   414  	var (
   415  		name     = "oss"
   416  		ns       = "default"
   417  		variable = map[string]interface{}{"acl": "private"}
   418  	)
   419  
   420  	ch := make(chan string)
   421  	badParam := map[string]interface{}{"abc": ch}
   422  	_, badParamMarshalError := json.Marshal(badParam)
   423  
   424  	type args struct {
   425  		writeConnectionSecretToRef *terraformtypes.SecretReference
   426  		hcl                        string
   427  		remote                     string
   428  		params                     map[string]interface{}
   429  		providerRef                *terraformtypes.Reference
   430  	}
   431  
   432  	type want struct {
   433  		err error
   434  	}
   435  
   436  	testcases := map[string]struct {
   437  		args args
   438  		want want
   439  	}{
   440  		"invalid ComponentDefinition": {
   441  			args: args{
   442  				hcl: "abc",
   443  				params: map[string]interface{}{"acl": "private",
   444  					"writeConnectionSecretToRef": map[string]interface{}{"name": "oss", "namespace": "default"}},
   445  			},
   446  			want: want{err: errors.New("terraform component definition is not valid")},
   447  		},
   448  		"valid hcl workload": {
   449  			args: args{
   450  				hcl: "abc",
   451  				params: map[string]interface{}{"acl": "private",
   452  					"writeConnectionSecretToRef": map[string]interface{}{"name": "oss", "namespace": "default"}},
   453  				writeConnectionSecretToRef: &terraformtypes.SecretReference{Name: "oss", Namespace: "default"},
   454  			},
   455  			want: want{err: nil}},
   456  		"valid hcl workload, and there are some custom params compared to ComponentDefinition": {
   457  			args: args{
   458  				hcl: "def",
   459  				params: map[string]interface{}{"acl": "private",
   460  					"writeConnectionSecretToRef": map[string]interface{}{"name": "oss2", "namespace": "default2"},
   461  					"providerRef":                map[string]interface{}{"name": "aws2", "namespace": "default2"}},
   462  				writeConnectionSecretToRef: &terraformtypes.SecretReference{Name: "oss", Namespace: "default"},
   463  				providerRef:                &terraformtypes.Reference{Name: "aws", Namespace: "default"},
   464  			},
   465  			want: want{err: nil},
   466  		},
   467  		"valid hcl workload, but the namespace of WriteConnectionSecretToReference is empty": {
   468  			args: args{
   469  				hcl: "abc",
   470  				params: map[string]interface{}{"acl": "private",
   471  					"writeConnectionSecretToRef": map[string]interface{}{"name": "oss", "namespace": "default"}},
   472  				writeConnectionSecretToRef: &terraformtypes.SecretReference{Name: "oss"},
   473  			},
   474  			want: want{err: nil}},
   475  
   476  		"remote hcl workload": {
   477  			args: args{
   478  				remote: "https://xxx/a.git",
   479  				params: map[string]interface{}{"acl": "private",
   480  					"writeConnectionSecretToRef": map[string]interface{}{"name": "oss", "namespace": "default"}},
   481  				writeConnectionSecretToRef: &terraformtypes.SecretReference{Name: "oss", Namespace: "default"},
   482  			},
   483  			want: want{err: nil}},
   484  
   485  		"workload's TF configuration is empty": {
   486  			args: args{
   487  				params: variable,
   488  			},
   489  			want: want{err: errors.New(errTerraformConfigurationIsNotSet)},
   490  		},
   491  
   492  		"workload's params is bad": {
   493  			args: args{
   494  				params: badParam,
   495  				hcl:    "abc",
   496  			},
   497  			want: want{err: errors.Wrap(badParamMarshalError, errFailToConvertTerraformComponentProperties)},
   498  		},
   499  
   500  		"terraform workload has a provider reference, but parameters are bad": {
   501  			args: args{
   502  				params:      badParam,
   503  				hcl:         "abc",
   504  				providerRef: &terraformtypes.Reference{Name: "azure", Namespace: "default"},
   505  			},
   506  			want: want{err: errors.Wrap(badParamMarshalError, errFailToConvertTerraformComponentProperties)},
   507  		},
   508  		"terraform workload has a provider reference": {
   509  			args: args{
   510  				params:      variable,
   511  				hcl:         "variable \"name\" {\n      description = \"Name to be used on all resources as prefix. Default to 'TF-Module-EIP'.\"\n      default = \"TF-Module-EIP\"\n      type = string\n    }",
   512  				providerRef: &terraformtypes.Reference{Name: "aws", Namespace: "default"},
   513  			},
   514  			want: want{err: nil},
   515  		},
   516  	}
   517  
   518  	for tcName, tc := range testcases {
   519  		t.Run(tcName, func(t *testing.T) {
   520  
   521  			var (
   522  				template   *Template
   523  				configSpec terraformapi.ConfigurationSpec
   524  			)
   525  			data, _ := json.Marshal(variable)
   526  			raw := &runtime.RawExtension{}
   527  			raw.Raw = data
   528  			if tc.args.hcl != "" {
   529  				template = &Template{
   530  					Terraform: &common.Terraform{
   531  						Configuration: tc.args.hcl,
   532  						Type:          "hcl",
   533  					},
   534  				}
   535  				configSpec = terraformapi.ConfigurationSpec{
   536  					HCL:      tc.args.hcl,
   537  					Variable: raw,
   538  				}
   539  				configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
   540  			}
   541  			if tc.args.remote != "" {
   542  				template = &Template{
   543  					Terraform: &common.Terraform{
   544  						Configuration: tc.args.remote,
   545  						Type:          "remote",
   546  					},
   547  				}
   548  				configSpec = terraformapi.ConfigurationSpec{
   549  					Remote:   tc.args.remote,
   550  					Variable: raw,
   551  				}
   552  				configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
   553  			}
   554  			if tc.args.hcl == "" && tc.args.remote == "" {
   555  				template = &Template{
   556  					Terraform: &common.Terraform{},
   557  				}
   558  
   559  				configSpec = terraformapi.ConfigurationSpec{
   560  					Variable: raw,
   561  				}
   562  				configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
   563  			}
   564  			tf := &common.Terraform{}
   565  			if tc.args.providerRef != nil {
   566  				tf.ProviderReference = tc.args.providerRef
   567  				configSpec.ProviderReference = tc.args.providerRef
   568  			}
   569  			if tc.args.writeConnectionSecretToRef != nil {
   570  				tf.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
   571  				configSpec.WriteConnectionSecretToReference = tc.args.writeConnectionSecretToRef
   572  				if tc.args.writeConnectionSecretToRef.Namespace == "" {
   573  					configSpec.WriteConnectionSecretToReference.Namespace = ns
   574  				}
   575  			}
   576  
   577  			if tc.args.providerRef != nil || tc.args.writeConnectionSecretToRef != nil {
   578  				template.ComponentDefinition = &v1beta1.ComponentDefinition{
   579  					Spec: v1beta1.ComponentDefinitionSpec{
   580  						Schematic: &common.Schematic{
   581  							Terraform: tf,
   582  						},
   583  					},
   584  				}
   585  			}
   586  
   587  			if tc.args.hcl == "def" {
   588  				configSpec.WriteConnectionSecretToReference = &terraformtypes.SecretReference{
   589  					Name:      "oss2",
   590  					Namespace: "default2",
   591  				}
   592  				configSpec.ProviderReference = &terraformtypes.Reference{
   593  					Name:      "aws2",
   594  					Namespace: "default2",
   595  				}
   596  			}
   597  
   598  			wl := &Component{
   599  				FullTemplate: template,
   600  				Name:         name,
   601  				Params:       tc.args.params,
   602  			}
   603  
   604  			got, err := generateTerraformConfigurationWorkload(wl, ns)
   605  			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
   606  				t.Errorf("\n%s\ngenerateTerraformConfigurationWorkload(...): -want error, +got error:\n%s\n", tcName, diff)
   607  			}
   608  
   609  			if err == nil {
   610  				tfConfiguration := terraformapi.Configuration{
   611  					TypeMeta:   metav1.TypeMeta{APIVersion: "terraform.core.oam.dev/v1beta2", Kind: "Configuration"},
   612  					ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
   613  					Spec:       configSpec,
   614  				}
   615  				rawConf := util.Object2RawExtension(tfConfiguration)
   616  				wantWL, _ := util.RawExtension2Unstructured(rawConf)
   617  
   618  				if diff := cmp.Diff(wantWL, got); diff != "" {
   619  					t.Errorf("\n%s\ngenerateTerraformConfigurationWorkload(...): -want, +got:\n%s\n", tcName, diff)
   620  				}
   621  			}
   622  		})
   623  	}
   624  }
   625  
   626  func TestPrepareArtifactsData(t *testing.T) {
   627  	compManifests := []*oamtypes.ComponentManifest{
   628  		{
   629  			Name:         "readyComp",
   630  			Namespace:    "ns",
   631  			RevisionName: "readyComp-v1",
   632  			ComponentOutput: &unstructured.Unstructured{Object: map[string]interface{}{
   633  				"fake": "workload",
   634  			}},
   635  			ComponentOutputsAndTraits: func() []*unstructured.Unstructured {
   636  				ingressYAML := `apiVersion: networking.k8s.io/v1
   637  kind: Ingress
   638  metadata:
   639    labels:
   640      trait.oam.dev/resource: ingress
   641      trait.oam.dev/type: ingress
   642    namespace: default
   643  spec:
   644    rules:
   645    - host: testsvc.example.com`
   646  				ingress := &unstructured.Unstructured{}
   647  				_ = yaml.Unmarshal([]byte(ingressYAML), ingress)
   648  				svcYAML := `apiVersion: v1
   649  kind: Service
   650  metadata:
   651    labels:
   652      trait.oam.dev/resource: service
   653      trait.oam.dev/type: ingress
   654    namespace: default
   655  spec:
   656    clusterIP: 10.96.185.119
   657    selector:
   658      app.oam.dev/component: express-server
   659    type: ClusterIP`
   660  				svc := &unstructured.Unstructured{}
   661  				_ = yaml.Unmarshal([]byte(svcYAML), svc)
   662  				return []*unstructured.Unstructured{ingress, svc}
   663  			}(),
   664  		},
   665  	}
   666  
   667  	gotArtifacts := prepareArtifactsData(compManifests)
   668  	gotWorkload, _, err := unstructured.NestedMap(gotArtifacts, "readyComp", "workload")
   669  	assert.NoError(t, err)
   670  	diff := cmp.Diff(gotWorkload, map[string]interface{}{"fake": string("workload")})
   671  	assert.Equal(t, diff, "")
   672  
   673  	_, gotIngress, err := unstructured.NestedMap(gotArtifacts, "readyComp", "traits", "ingress", "ingress")
   674  	assert.NoError(t, err)
   675  	if !gotIngress {
   676  		t.Fatalf("cannot get ingress trait")
   677  	}
   678  	_, gotSvc, err := unstructured.NestedMap(gotArtifacts, "readyComp", "traits", "ingress", "service")
   679  	assert.NoError(t, err)
   680  	if !gotSvc {
   681  		t.Fatalf("cannot get service trait")
   682  	}
   683  
   684  }
   685  
   686  func TestBaseGenerateComponent(t *testing.T) {
   687  	var appName = "test-app"
   688  	var ns = "test-ns"
   689  	var traitName = "mytrait"
   690  	var wlName = "my-wl-1"
   691  	var workflowName = "my-wf"
   692  	var publishVersion = "123"
   693  	ctxData := GenerateContextDataFromAppFile(&Appfile{
   694  		Name:      appName,
   695  		Namespace: ns,
   696  		AppAnnotations: map[string]string{
   697  			oam.AnnotationWorkflowName:   workflowName,
   698  			oam.AnnotationPublishVersion: publishVersion,
   699  		},
   700  	}, wlName)
   701  	pContext := NewBasicContext(ctxData, nil)
   702  	base := `
   703  	apiVersion: "apps/v1"
   704  	kind:       "Deployment"
   705  	spec: {
   706  		template: {
   707  			spec: containers: [{
   708  				image: "nginx"
   709  			}]
   710  		}
   711  	}
   712  `
   713  	inst := cuecontext.New().CompileString(base)
   714  	bs, _ := model.NewBase(inst.Value())
   715  	err := pContext.SetBase(bs)
   716  	assert.NoError(t, err)
   717  	tr := &Trait{
   718  		Name:   traitName,
   719  		engine: definition.NewTraitAbstractEngine(traitName, nil),
   720  		Template: `outputs:mytrait:{
   721  if context.componentType == "stateless" {
   722               kind:  			"Deployment"
   723  	}
   724  	if context.componentType  == "stateful" {
   725               kind:  			"StatefulSet"
   726  	}
   727  	name:                   context.name
   728  	envSourceContainerName: context.name
   729    workflowName:           context.workflowName
   730    publishVersion:         context.publishVersion
   731  }`,
   732  	}
   733  	wl := &Component{Type: "stateful", Traits: []*Trait{tr}}
   734  	cm, err := baseGenerateComponent(pContext, wl, appName, ns)
   735  	assert.NoError(t, err)
   736  	assert.Equal(t, cm.ComponentOutputsAndTraits[0].Object["kind"], "StatefulSet")
   737  	assert.Equal(t, cm.ComponentOutputsAndTraits[0].Object["workflowName"], workflowName)
   738  	assert.Equal(t, cm.ComponentOutputsAndTraits[0].Object["publishVersion"], publishVersion)
   739  }
   740  
   741  var _ = Describe("Test use context.appLabels& context.appAnnotations in componentDefinition ", func() {
   742  	It("Test generate AppConfig resources from ", func() {
   743  		af := &Appfile{
   744  			Name:      "app",
   745  			Namespace: "ns",
   746  			AppLabels: map[string]string{
   747  				"lk1": "lv1",
   748  				"lk2": "lv2",
   749  			},
   750  			AppAnnotations: map[string]string{
   751  				"ak1": "av1",
   752  				"ak2": "av2",
   753  			},
   754  			ParsedComponents: []*Component{
   755  				{
   756  					Name: "comp1",
   757  					Type: "deployment",
   758  					Params: map[string]interface{}{
   759  						"image": "busybox",
   760  						"cmd":   []interface{}{"sleep", "1000"},
   761  					},
   762  					engine: definition.NewWorkloadAbstractEngine("myweb", pd),
   763  					FullTemplate: &Template{
   764  						TemplateStr: `
   765  						  output: {
   766  							apiVersion: "apps/v1"
   767  							kind:       "Deployment"
   768  							spec: {
   769  								selector: matchLabels: {
   770  									"app.oam.dev/component": context.name
   771  								}
   772  						  
   773  								template: {
   774  									metadata: {
   775  										labels: {
   776  											if context.appLabels != _|_ {
   777  												context.appLabels
   778  											}
   779  										}
   780  										annotations: {
   781  											if context.appAnnotations != _|_ {
   782  												context.appAnnotations
   783  											}
   784  										}
   785  									}
   786  						  
   787  									spec: {
   788  										containers: [{
   789  											name:  context.name
   790  											image: parameter.image
   791  						  
   792  											if parameter["cmd"] != _|_ {
   793  												command: parameter.cmd
   794  											}
   795  										}]
   796  									}
   797  								}
   798  						  
   799  								selector:
   800  									matchLabels:
   801  										"app.oam.dev/component": context.name
   802  							}
   803  						  }
   804  						  
   805  						  parameter: {
   806  							// +usage=Which image would you like to use for your service
   807  							// +short=i
   808  							image: string
   809  						  
   810  							cmd?: [...string]
   811  						  }`},
   812  				},
   813  			},
   814  		}
   815  		By("Generate ComponentManifests")
   816  		componentManifests, err := af.GenerateComponentManifests()
   817  		Expect(err).To(BeNil())
   818  		By("Verify expected ComponentManifest")
   819  		deployment := &appsv1.Deployment{}
   820  		runtime.DefaultUnstructuredConverter.FromUnstructured(componentManifests[0].ComponentOutput.Object, deployment)
   821  		labels := deployment.Spec.Template.Labels
   822  		annotations := deployment.Spec.Template.Annotations
   823  		Expect(cmp.Diff(len(labels), 2)).Should(BeEmpty())
   824  		Expect(cmp.Diff(len(annotations), 2)).Should(BeEmpty())
   825  		Expect(cmp.Diff(labels["lk1"], "lv1")).Should(BeEmpty())
   826  		Expect(cmp.Diff(annotations["ak1"], "av1")).Should(BeEmpty())
   827  	})
   828  
   829  })