diff --git a/Makefile b/Makefile index 412c45fb0..14c0b638e 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,7 @@ reviewable: manifests fmt vet lint staticcheck # Execute auto-gen code commands and ensure branch is clean. check-diff: reviewable + git --no-pager diff git diff --quiet || ($(ERR) please run 'make reviewable' to include all changes && false) @$(OK) branch is clean diff --git a/apis/core.oam.dev/v1beta1/componentdefinition_types.go b/apis/core.oam.dev/v1beta1/componentdefinition_types.go index e279e6a13..57a8bd69a 100644 --- a/apis/core.oam.dev/v1beta1/componentdefinition_types.go +++ b/apis/core.oam.dev/v1beta1/componentdefinition_types.go @@ -67,7 +67,7 @@ type ComponentDefinitionStatus struct { // +kubebuilder:object:root=true // ComponentDefinition is the Schema for the componentdefinitions API -// +kubebuilder:resource:scope=Namespaced,categories={crossplane,oam} +// +kubebuilder:resource:scope=Namespaced,categories={oam} // +kubebuilder:storageversion // +kubebuilder:subresource:status type ComponentDefinition struct { diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index 983b6a331..702325606 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -163,8 +163,7 @@ func (p *Parser) GenerateAppFile(ctx context.Context, name string, app *v1alpha2 func (p *Parser) parseWorkload(ctx context.Context, comp v1alpha2.ApplicationComponent) (*Workload, error) { - // TODO: pass in p.dm - templ, err := util.LoadTemplate(ctx, p.client, comp.WorkloadType, types.TypeComponentDefinition) + templ, err := util.LoadTemplate(ctx, p.dm, p.client, comp.WorkloadType, types.TypeComponentDefinition) if err != nil && !kerrors.IsNotFound(err) { return nil, errors.WithMessagef(err, "fetch type of %s", comp.Name) } @@ -212,8 +211,7 @@ func (p *Parser) parseWorkload(ctx context.Context, comp v1alpha2.ApplicationCom } func (p *Parser) parseTrait(ctx context.Context, name string, properties map[string]interface{}) (*Trait, error) { - // TODO: pass in p.dm - templ, err := util.LoadTemplate(ctx, p.client, name, types.TypeTrait) + templ, err := util.LoadTemplate(ctx, p.dm, p.client, name, types.TypeTrait) if kerrors.IsNotFound(err) { return nil, errors.Errorf("trait definition of %s not found", name) } diff --git a/pkg/dsl/definition/package_suit_test.go b/pkg/dsl/definition/package_suit_test.go index 07a2fffb5..d48bf96e1 100644 --- a/pkg/dsl/definition/package_suit_test.go +++ b/pkg/dsl/definition/package_suit_test.go @@ -261,17 +261,18 @@ parameter: { return err }, time.Second*2, time.Millisecond*300).Should(BeNil()) - time.Sleep(2 * time.Second) Expect(pd.Exist(metav1.GroupVersionKind{ Group: "example.com", Version: "v1", Kind: "Foo", })).Should(Equal(false)) - Expect(pd.RefreshKubePackagesFromCluster()).ShouldNot(HaveOccurred()) - By("test new added CRD in kube package") - bi = build.NewContext().NewInstance("", nil) - pd.ImportBuiltinPackagesFor(bi) - bi.AddFile("-", ` + + Eventually(func() error { + Expect(pd.RefreshKubePackagesFromCluster()).ShouldNot(HaveOccurred()) + By("test new added CRD in kube package") + bi = build.NewContext().NewInstance("", nil) + pd.ImportBuiltinPackagesFor(bi) + bi.AddFile("-", ` import ("kube/example.com/v1") output: v1.#Foo @@ -280,7 +281,10 @@ output: { status: key: "test2" } `) - inst, err = r.Build(bi) + inst, err = r.Build(bi) + return err + }, time.Second*5, time.Millisecond*300).Should(BeNil()) + Expect(err).Should(BeNil()) base, err = model.NewBase(inst.Lookup("output")) Expect(err).Should(BeNil()) diff --git a/pkg/oam/mock/mocks.go b/pkg/oam/mock/mocks.go index 7e8ec0ea7..20e0193ea 100644 --- a/pkg/oam/mock/mocks.go +++ b/pkg/oam/mock/mocks.go @@ -168,3 +168,27 @@ func SchemeWith(o ...runtime.Object) *runtime.Scheme { s.AddKnownTypes(GV, o...) return s } + +// NotFoundErr describes NotFound Resource error +type NotFoundErr struct { + NotFoundStatus metav1.Status +} + +// NewMockNotFoundErr return a mock NotFoundErr +func NewMockNotFoundErr() NotFoundErr { + return NotFoundErr{ + NotFoundStatus: metav1.Status{ + Reason: metav1.StatusReasonNotFound, + }, + } +} + +// Status returns the Status Reason +func (mock NotFoundErr) Status() metav1.Status { + return mock.NotFoundStatus +} + +// Error return error info +func (mock NotFoundErr) Error() string { + return "Not Found Resource" +} diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go index 739da29e6..77546c192 100644 --- a/pkg/oam/util/template.go +++ b/pkg/oam/util/template.go @@ -45,7 +45,7 @@ func GetScopeGVK(ctx context.Context, cli client.Reader, dm discoverymapper.Disc } // LoadTemplate Get template according to key -func LoadTemplate(ctx context.Context, cli client.Reader, key string, kd types.CapType) (*Template, error) { +func LoadTemplate(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli client.Reader, key string, kd types.CapType) (*Template, error) { // Application Controller only load template from ComponentDefinition and TraitDefinition switch kd { case types.TypeComponentDefinition: @@ -70,9 +70,14 @@ func LoadTemplate(ctx context.Context, cli client.Reader, key string, kd types.C tmpl.CapabilityCategory = types.TerraformCategory } tmpl.WorkloadDefinition = wd - // GetGVKFromDefinition - // TODO: need to pass in a discoverMapper in order to get the tmpl reference - // from the workloadDefinition reference + gvk, err := GetGVKFromDefinition(dm, wd.Spec.Reference) + if err != nil { + return nil, errors.WithMessagef(err, "Get GVK from workload definition [%s]", key) + } + tmpl.Reference = common.WorkloadGVK{ + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, + } return tmpl, nil } return nil, errors.WithMessagef(err, "LoadTemplate from ComponentDefinition [%s] ", key) diff --git a/pkg/oam/util/template_test.go b/pkg/oam/util/template_test.go index d50e443fb..7e0648a46 100644 --- a/pkg/oam/util/template_test.go +++ b/pkg/oam/util/template_test.go @@ -13,6 +13,7 @@ import ( "github.com/oam-dev/kubevela/apis/core.oam.dev/common" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" "github.com/oam-dev/kubevela/apis/types" + "github.com/oam-dev/kubevela/pkg/oam/mock" ) func TestLoadComponentTemplate(t *testing.T) { @@ -91,8 +92,114 @@ spec: return nil }, } + tdm := mock.NewMockDiscoveryMapper() + tdm.MockKindsFor = mock.NewMockKindsFor("Deployment", "v1") + temp, err := LoadTemplate(context.TODO(), tdm, &tclient, "worker", types.TypeComponentDefinition) - temp, err := LoadTemplate(context.TODO(), &tclient, "worker", types.TypeComponentDefinition) + if err != nil { + t.Error(err) + return + } + var r cue.Runtime + inst, err := r.Compile("-", temp.TemplateStr) + if err != nil { + t.Error(err) + return + } + instDest, err := r.Compile("-", cueTemplate) + if err != nil { + t.Error(err) + return + } + s1, _ := inst.Value().String() + s2, _ := instDest.Value().String() + if s1 != s2 { + t.Errorf("parsered template is not correct") + } +} + +func TestLoadWorkloadTemplate(t *testing.T) { + cueTemplate := ` + context: { + name: "test" + } + output: { + apiVersion: "apps/v1" + kind: "Deployment" + spec: { + selector: matchLabels: { + "app.oam.dev/component": context.name + } + + template: { + metadata: labels: { + "app.oam.dev/component": context.name + } + + spec: { + containers: [{ + name: context.name + image: parameter.image + + if parameter["cmd"] != _|_ { + command: parameter.cmd + } + }] + } + } + + selector: + matchLabels: + "app.oam.dev/component": context.name + } + } + + parameter: { + // +usage=Which image would you like to use for your service + // +short=i + image: string + + cmd?: [...string] + } + ` + + var workloadDefintion = ` +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: worker + namespace: default + annotations: + definition.oam.dev/description: "Long-running scalable backend worker without network endpoint" +spec: + workload: + definition: + apiVersion: apps/v1 + kind: Deployment + extension: + template: | +` + cueTemplate + + // Create mock client + tclient := test.MockClient{ + MockGet: func(ctx context.Context, key ktypes.NamespacedName, obj runtime.Object) error { + switch o := obj.(type) { + case *v1alpha2.WorkloadDefinition: + cd, err := UnMarshalStringToWorkloadDefinition(workloadDefintion) + if err != nil { + return err + } + *o = *cd + case *v1alpha2.ComponentDefinition: + err := mock.NewMockNotFoundErr() + return err + } + return nil + }, + } + tdm := mock.NewMockDiscoveryMapper() + tdm.MockKindsFor = mock.NewMockKindsFor("Deployment", "v1") + temp, err := LoadTemplate(context.TODO(), tdm, &tclient, "worker", types.TypeComponentDefinition) if err != nil { t.Error(err) @@ -210,7 +317,9 @@ spec: }, } - temp, err := LoadTemplate(context.TODO(), &tclient, "ingress", types.TypeTrait) + tdm := mock.NewMockDiscoveryMapper() + tdm.MockKindsFor = mock.NewMockKindsFor("Deployment", "v1") + temp, err := LoadTemplate(context.TODO(), tdm, &tclient, "ingress", types.TypeTrait) if err != nil { t.Error(err) diff --git a/pkg/oam/util/test_utils.go b/pkg/oam/util/test_utils.go index a5549d063..92b4af6ec 100644 --- a/pkg/oam/util/test_utils.go +++ b/pkg/oam/util/test_utils.go @@ -145,6 +145,19 @@ func UnMarshalStringToComponentDefinition(s string) (*v1alpha2.ComponentDefiniti return obj, nil } +// UnMarshalStringToWorkloadDefinition parse a string to a workloadDefinition object +func UnMarshalStringToWorkloadDefinition(s string) (*v1alpha2.WorkloadDefinition, error) { + obj := &v1alpha2.WorkloadDefinition{} + _body, err := yaml.YAMLToJSON([]byte(s)) + if err != nil { + return nil, err + } + if err := json.Unmarshal(_body, obj); err != nil { + return nil, err + } + return obj, nil +} + // UnMarshalStringToTraitDefinition parse a string to a traitDefinition object func UnMarshalStringToTraitDefinition(s string) (*v1alpha2.TraitDefinition, error) { obj := &v1alpha2.TraitDefinition{} diff --git a/test/e2e-test/helm_app_test.go b/test/e2e-test/helm_app_test.go index 23ce78d11..a3fd6897e 100644 --- a/test/e2e-test/helm_app_test.go +++ b/test/e2e-test/helm_app_test.go @@ -27,6 +27,7 @@ var _ = Describe("Test application containing helm module", func() { appName = "test-app" compName = "test-comp" cdName = "webapp-chart" + wdName = "webapp-chart-wd" tdName = "virtualgroup" ) var app v1alpha2.Application @@ -73,7 +74,6 @@ var _ = Describe("Test application containing helm module", func() { }), }, } - Expect(k8sClient.Create(ctx, &cd)).Should(Succeed()) By("Install a patch trait used to test CUE module") @@ -289,6 +289,68 @@ var _ = Describe("Test application containing helm module", func() { }, 60*time.Second, 10*time.Second).Should(BeTrue()) }) + It("Test deploy an application containing helm module defined by workloadDefinition", func() { + + workloaddef := v1alpha2.WorkloadDefinition{} + workloaddef.SetName(wdName) + workloaddef.SetNamespace(namespace) + workloaddef.Spec.Reference = common.DefinitionReference{Name: "deployments.apps", Version: "v1"} + workloaddef.Spec.Schematic = &common.Schematic{ + HELM: &common.Helm{ + Release: util.Object2RawExtension(map[string]interface{}{ + "chart": map[string]interface{}{ + "spec": map[string]interface{}{ + "chart": "podinfo", + "version": "5.1.4", + }, + }, + }), + Repository: util.Object2RawExtension(map[string]interface{}{ + "url": "http://oam.dev/catalog/", + }), + }, + } + By("register workloadDefinition") + Expect(k8sClient.Create(ctx, &workloaddef)).Should(Succeed()) + + appTestName := "test-app-refer-to-workloaddef" + appTest := v1alpha2.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: appTestName, + Namespace: namespace, + }, + Spec: v1alpha2.ApplicationSpec{ + Components: []v1alpha2.ApplicationComponent{ + { + Name: compName, + WorkloadType: wdName, + Settings: util.Object2RawExtension(map[string]interface{}{ + "image": map[string]interface{}{ + "tag": "5.1.2", + }, + }), + }, + }, + }, + } + By("Create application") + Expect(k8sClient.Create(ctx, &appTest)).Should(Succeed()) + + ac := &v1alpha2.ApplicationContext{} + acName := appTestName + By("Verify the AppConfig is created successfully") + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac) + }, 30*time.Second, time.Second).Should(Succeed()) + + By("Verify the workload(deployment) is created successfully by Helm") + deploy := &appsv1.Deployment{} + deployName := fmt.Sprintf("%s-%s-podinfo", appTestName, compName) + Eventually(func() error { + return k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy) + }, 240*time.Second, 5*time.Second).Should(Succeed()) + }) + It("Test store JSON schema of Helm Chart in ConfigMap", func() { By("Get the ConfigMap") cmName := fmt.Sprintf("schema-%s", cdName)