From 932553ac83259ac6f2bd2efa3af0d25ddc819392 Mon Sep 17 00:00:00 2001 From: Jianbo Sun Date: Tue, 26 Jul 2022 15:22:37 +0800 Subject: [PATCH] Chore: refactor addon enable with package (#4467) Signed-off-by: Jianbo Sun --- pkg/addon/addon.go | 8 +-- pkg/addon/addon_suite_test.go | 4 +- pkg/addon/addon_test.go | 10 ++-- pkg/addon/helper.go | 2 +- pkg/addon/init.go | 7 ++- pkg/addon/render.go | 103 +++++++++++++++++++--------------- pkg/addon/render_test.go | 63 +++++++++++++-------- pkg/cue/model/value/value.go | 20 ++++--- pkg/oam/labels.go | 3 + 9 files changed, 127 insertions(+), 93 deletions(-) diff --git a/pkg/addon/addon.go b/pkg/addon/addon.go index 295e1bb9a..139478076 100644 --- a/pkg/addon/addon.go +++ b/pkg/addon/addon.go @@ -1052,7 +1052,7 @@ func (h *Installer) createOrUpdate(app *v1beta1.Application) error { } func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { - app, err := RenderApp(h.ctx, addon, h.cli, h.args) + app, auxiliaryOutputs, err := RenderApp(h.ctx, addon, h.cli, h.args) if err != nil { return errors.Wrap(err, "render addon application fail") } @@ -1083,12 +1083,6 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { return errors.Wrapf(err, "cannot pass definition to addon app's annotation") } - var auxiliaryOutputs []*unstructured.Unstructured - auxiliaryOutputs, err = renderOutputs(addon, h.args) - if err != nil { - return err - } - if err = h.createOrUpdate(app); err != nil { return err } diff --git a/pkg/addon/addon_suite_test.go b/pkg/addon/addon_suite_test.go index f366b7882..210ad50d4 100644 --- a/pkg/addon/addon_suite_test.go +++ b/pkg/addon/addon_suite_test.go @@ -307,7 +307,7 @@ var _ = Describe("Test render addon with specified clusters", func() { args := map[string]interface{}{ "clusters": []string{"add-c1", "ne"}, } - _, err := RenderApp(ctx, i, k8sClient, args) + _, _, err := RenderApp(ctx, i, k8sClient, args) Expect(err.Error()).Should(BeEquivalentTo("cluster ne not exist")) }) It("test render normal addon with specified clusters", func() { @@ -317,7 +317,7 @@ var _ = Describe("Test render addon with specified clusters", func() { args := map[string]interface{}{ "clusters": []string{"add-c1", "add-c2"}, } - ap, err := RenderApp(ctx, i, k8sClient, args) + ap, _, err := RenderApp(ctx, i, k8sClient, args) Expect(err).Should(BeNil()) Expect(ap.Spec.Policies).Should(BeEquivalentTo([]v1beta1.AppPolicy{{Name: specifyAddonClustersTopologyPolicy, Type: v1alpha12.TopologyPolicyType, diff --git a/pkg/addon/addon_test.go b/pkg/addon/addon_test.go index 1ef8e5614..9cec6358f 100644 --- a/pkg/addon/addon_test.go +++ b/pkg/addon/addon_test.go @@ -281,7 +281,7 @@ func TestRender(t *testing.T) { func TestRenderApp(t *testing.T) { addon := baseAddon - app, err := RenderApp(ctx, &addon, nil, map[string]interface{}{}) + app, _, err := RenderApp(ctx, &addon, nil, map[string]interface{}{}) assert.NoError(t, err, "render app fail") // definition should not be rendered assert.Equal(t, len(app.Spec.Components), 1) @@ -290,7 +290,7 @@ func TestRenderApp(t *testing.T) { func TestRenderAppWithNeedNamespace(t *testing.T) { addon := baseAddon addon.NeedNamespace = append(addon.NeedNamespace, types.DefaultKubeVelaNS, "test-ns2") - app, err := RenderApp(ctx, &addon, nil, map[string]interface{}{}) + app, _, err := RenderApp(ctx, &addon, nil, map[string]interface{}{}) assert.NoError(t, err, "render app fail") assert.Equal(t, len(app.Spec.Components), 2) for _, c := range app.Spec.Components { @@ -311,7 +311,7 @@ func TestRenderDeploy2RuntimeAddon(t *testing.T) { assert.Equal(t, def.GetAPIVersion(), "core.oam.dev/v1beta1") assert.Equal(t, def.GetKind(), "TraitDefinition") - app, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{}) + app, _, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{}) assert.NoError(t, err) policies := app.Spec.Policies assert.True(t, len(policies) == 1) @@ -331,7 +331,7 @@ func TestRenderDefinitions(t *testing.T) { assert.Equal(t, def.GetAPIVersion(), "core.oam.dev/v1beta1") assert.Equal(t, def.GetKind(), "TraitDefinition") - app, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{}) + app, _, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{}) assert.NoError(t, err) // addon which app work on no-runtime-cluster mode workflow is nil assert.Nil(t, app.Spec.Workflow) @@ -368,7 +368,7 @@ func TestRenderK8sObjects(t *testing.T) { RuntimeCluster: false, } - app, err := RenderApp(ctx, &addonMultiYaml, nil, map[string]interface{}{}) + app, _, err := RenderApp(ctx, &addonMultiYaml, nil, map[string]interface{}{}) assert.NoError(t, err) assert.Equal(t, len(app.Spec.Components), 1) comp := app.Spec.Components[0] diff --git a/pkg/addon/helper.go b/pkg/addon/helper.go index 623c4a747..e93dc122e 100644 --- a/pkg/addon/helper.go +++ b/pkg/addon/helper.go @@ -50,7 +50,7 @@ const ( enabling = "enabling" // disabling indicates the addon related app is deleting disabling = "disabling" - // suspend indicates the addon related app is suspend + // suspend indicates the addon related app is suspended suspend = "suspend" ) diff --git a/pkg/addon/init.go b/pkg/addon/init.go index 21a1e2074..d3048e934 100644 --- a/pkg/addon/init.go +++ b/pkg/addon/init.go @@ -503,9 +503,14 @@ parameter: { validate: required: true ` - appTemplate = `output: { + appTemplate = `package main +output: { apiVersion: "core.oam.dev/v1beta1" kind: "Application" + metadata: { + name: "my-addon-name" + namespace: "vela-system" + } spec: { components: [] policies: [] diff --git a/pkg/addon/render.go b/pkg/addon/render.go index bcf6eb9c7..227617907 100644 --- a/pkg/addon/render.go +++ b/pkg/addon/render.go @@ -23,6 +23,7 @@ import ( "path" "strings" + "cuelang.org/go/cue/parser" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -37,6 +38,7 @@ import ( "github.com/oam-dev/kubevela/pkg/cue/model/value" "github.com/oam-dev/kubevela/pkg/multicluster" "github.com/oam-dev/kubevela/pkg/oam" + "github.com/oam-dev/kubevela/pkg/oam/util" addonutil "github.com/oam-dev/kubevela/pkg/utils/addon" ) @@ -103,46 +105,77 @@ func (a addonCueTemplateRender) toObject(cueTemplate string, path string, object } // renderApp will render Application from CUE files -func (a addonCueTemplateRender) renderApp() (*v1beta1.Application, error) { +func (a addonCueTemplateRender) renderApp() (*v1beta1.Application, []*unstructured.Unstructured, error) { var app v1beta1.Application + var outputs = map[string]interface{}{} + var res []*unstructured.Unstructured contextFile, err := a.formatContext() if err != nil { - return nil, errors.Wrap(err, "format context for app render") + return nil, nil, errors.Wrap(err, "format context for app render") } + contextCue, err := parser.ParseFile("parameter.cue", contextFile, parser.ParseComments) + if err != nil { + return nil, nil, errors.Wrap(err, "parse parameter context") + } + if contextCue.PackageName() == "" { + contextFile = value.DefaultPackageHeader + contextFile + } + var files = []string{contextFile} for _, cuef := range a.addon.CUETemplates { files = append(files, cuef.Data) } // TODO(wonderflow): add package discover to support vela own packages if needed - v, err := value.NewValueWithFiles(a.addon.AppCueTemplate.Data, files, nil, "") + v, err := value.NewValueWithMainAndFiles(a.addon.AppCueTemplate.Data, files, nil, "") if err != nil { - return nil, errors.Wrap(err, "load app template with CUE files") + return nil, nil, errors.Wrap(err, "load app template with CUE files") } outputContent, err := v.LookupValue(renderOutputCuePath) if err != nil { - return nil, errors.Wrap(err, "render app from output field from CUE") + return nil, nil, errors.Wrap(err, "render app from output field from CUE") } err = outputContent.UnmarshalTo(&app) if err != nil { - return nil, errors.Wrap(err, "decode app from CUE") + return nil, nil, errors.Wrap(err, "decode app from CUE") } - return &app, nil + auxiliaryContent, err := v.LookupValue(renderAuxiliaryOutputsPath) + if err != nil { + // no outputs defined in app template, return normal data + if isErrorCueRenderPathNotFound(err, renderAuxiliaryOutputsPath) { + return &app, res, nil + } + return nil, nil, errors.Wrap(err, "render app from output field from CUE") + } + + err = auxiliaryContent.UnmarshalTo(&outputs) + if err != nil { + return nil, nil, errors.Wrap(err, "decode app from CUE") + } + for k, o := range outputs { + if ao, ok := o.(map[string]interface{}); ok { + auxO := &unstructured.Unstructured{Object: ao} + auxO.SetLabels(util.MergeMapOverrideWithDst(auxO.GetLabels(), map[string]string{oam.LabelAddonAuxiliaryName: k})) + res = append(res, auxO) + } + } + return &app, res, nil } // generateAppFramework generate application from yaml defined by template.yaml or cue file from template.cue -func generateAppFramework(addon *InstallPackage, parameters map[string]interface{}) (*v1beta1.Application, error) { +func generateAppFramework(addon *InstallPackage, parameters map[string]interface{}) (*v1beta1.Application, []*unstructured.Unstructured, error) { if len(addon.AppCueTemplate.Data) != 0 && addon.AppTemplate != nil { - return nil, ErrBothCueAndYamlTmpl + return nil, nil, ErrBothCueAndYamlTmpl } var app *v1beta1.Application + var auxiliaryObjects []*unstructured.Unstructured var err error if len(addon.AppCueTemplate.Data) != 0 { - app, err = renderAppAccordingToCueTemplate(addon, parameters) + app, auxiliaryObjects, err = renderAppAccordingToCueTemplate(addon, parameters) if err != nil { - return nil, err + return nil, nil, err } } else { app = addon.AppTemplate @@ -164,19 +197,20 @@ func generateAppFramework(addon *InstallPackage, parameters map[string]interface } app.Labels[oam.LabelAddonName] = addon.Name app.Labels[oam.LabelAddonVersion] = addon.Version - return app, nil + + for _, aux := range auxiliaryObjects { + aux.SetLabels(util.MergeMapOverrideWithDst(aux.GetLabels(), map[string]string{oam.LabelAddonName: addon.Name, oam.LabelAddonVersion: addon.Version})) + } + + return app, auxiliaryObjects, nil } -func renderAppAccordingToCueTemplate(addon *InstallPackage, args map[string]interface{}) (*v1beta1.Application, error) { +func renderAppAccordingToCueTemplate(addon *InstallPackage, args map[string]interface{}) (*v1beta1.Application, []*unstructured.Unstructured, error) { r := addonCueTemplateRender{ addon: addon, inputArgs: args, } - app, err := r.renderApp() - if err != nil { - return nil, err - } - return app, nil + return r.renderApp() } // renderCompAccordingCUETemplate will return a component from cue template @@ -199,19 +233,19 @@ func renderCompAccordingCUETemplate(cueTemplate ElementFile, addon *InstallPacka } // RenderApp render a K8s application -func RenderApp(ctx context.Context, addon *InstallPackage, k8sClient client.Client, args map[string]interface{}) (*v1beta1.Application, error) { +func RenderApp(ctx context.Context, addon *InstallPackage, k8sClient client.Client, args map[string]interface{}) (*v1beta1.Application, []*unstructured.Unstructured, error) { if args == nil { args = map[string]interface{}{} } - app, err := generateAppFramework(addon, args) + app, auxiliaryObjects, err := generateAppFramework(addon, args) if err != nil { - return nil, err + return nil, nil, err } app.Spec.Components = append(app.Spec.Components, renderNeededNamespaceAsComps(addon)...) resources, err := renderResources(addon, args) if err != nil { - return nil, err + return nil, nil, err } app.Spec.Components = append(app.Spec.Components, resources...) @@ -219,10 +253,10 @@ func RenderApp(ctx context.Context, addon *InstallPackage, k8sClient client.Clie // attach topology policy to application. if checkNeedAttachTopologyPolicy(app, addon) { if err := attachPolicyForLegacyAddon(ctx, app, addon, args, k8sClient); err != nil { - return nil, err + return nil, nil, err } } - return app, nil + return app, auxiliaryObjects, nil } func attachPolicyForLegacyAddon(ctx context.Context, app *v1beta1.Application, addon *InstallPackage, args map[string]interface{}, k8sClient client.Client) error { @@ -317,27 +351,6 @@ func checkNeedAttachTopologyPolicy(app *v1beta1.Application, addon *InstallPacka return true } -func renderOutputs(addon *InstallPackage, args map[string]interface{}) ([]*unstructured.Unstructured, error) { - outputs := map[string]interface{}{} - r := addonCueTemplateRender{ - addon: addon, - inputArgs: args, - } - if err := r.toObject(addon.AppCueTemplate.Data, renderAuxiliaryOutputsPath, &outputs); err != nil { - if isErrorCueRenderPathNotFound(err, renderAuxiliaryOutputsPath) { - return nil, nil - } - return nil, err - } - var res []*unstructured.Unstructured - for _, o := range outputs { - if ao, ok := o.(map[string]interface{}); ok { - res = append(res, &unstructured.Unstructured{Object: ao}) - } - } - return res, nil -} - func isDeployToRuntime(addon *InstallPackage) bool { if addon.DeployTo == nil { return false diff --git a/pkg/addon/render_test.go b/pkg/addon/render_test.go index 5c8af62a0..d277cd882 100644 --- a/pkg/addon/render_test.go +++ b/pkg/addon/render_test.go @@ -96,7 +96,12 @@ myref: { "namespace": "vela-system", }, } - app, err := render.renderApp() + app, _, err := render.renderApp() + assert.Equal(t, err.Error(), `load app template with CUE files: reference "myref" not found`) + assert.Nil(t, app) + + addon.CUETemplates = []ElementFile{{Data: "package main\n" + resourceComponent1}} + app, _, err = render.renderApp() assert.NoError(t, err) assert.Equal(t, len(app.Spec.Components), 2) str, err := json.Marshal(app.Spec.Components[0].Properties) @@ -111,26 +116,29 @@ myref: { assert.NoError(t, err) assert.True(t, strings.Contains(string(str), `"clusterLabelSelector":{}`)) - addon.CUETemplates = []ElementFile{{Data: resourceComponent1}} - addon.AppCueTemplate = ElementFile{Data: "package main\n" + appTemplate} - app, err = render.renderApp() + addon.Parameters = "package newp\n" + paraDefined + addon.CUETemplates = []ElementFile{{Data: "package newp\n" + resourceComponent1}} + addon.AppCueTemplate = ElementFile{Data: "package newp\n" + appTemplate} + app, _, err = render.renderApp() assert.NoError(t, err) assert.Equal(t, len(app.Spec.Components), 2) - addon.Parameters = "package main\n" + paraDefined - app, err = render.renderApp() + addon.CUETemplates = []ElementFile{{Data: "package main\n" + resourceComponent1}} + addon.Parameters = paraDefined + addon.AppCueTemplate = ElementFile{Data: appTemplate} + app, _, err = render.renderApp() assert.NoError(t, err) assert.Equal(t, len(app.Spec.Components), 2) addon.CUETemplates = []ElementFile{{Data: "package hello\n" + resourceComponent1}} addon.AppCueTemplate = ElementFile{Data: "package main\n" + appTemplate} - _, err = render.renderApp() + _, _, err = render.renderApp() assert.Equal(t, err.Error(), `load app template with CUE files: reference "myref" not found`) addon.CUETemplates = []ElementFile{{Data: "package hello\n" + resourceComponent1}} addon.Parameters = paraDefined addon.AppCueTemplate = ElementFile{Data: appTemplate} - _, err = render.renderApp() + _, _, err = render.renderApp() assert.Equal(t, err.Error(), `load app template with CUE files: reference "myref" not found`) } @@ -234,21 +242,28 @@ func TestOutputsRender(t *testing.T) { Parameters: paraDefined, AppCueTemplate: ElementFile{Data: appTemplate}, } - list, err := renderOutputs(addon, nil) - assert.NoError(t, err) - assert.Equal(t, true, len(list) == 1) - - addon = &InstallPackage{ - Meta: Meta{ - Name: "velaux", - DeployTo: &DeployTo{ - RuntimeCluster: true, - }, + render := addonCueTemplateRender{ + addon: addon, + inputArgs: map[string]interface{}{ + "namespace": "vela-system", }, - Parameters: paraDefined, - AppCueTemplate: ElementFile{Data: appTemplateNoOutputs}, } - _, err = renderOutputs(addon, nil) + app, auxdata, err := render.renderApp() + assert.NoError(t, err) + assert.Equal(t, len(app.Spec.Components), 1) + str, err := json.Marshal(app.Spec.Components[0].Properties) + assert.NoError(t, err) + assert.True(t, strings.Contains(string(str), `{"name":"vela-system"}`)) + assert.Equal(t, len(auxdata), 1) + auxStr, err := json.Marshal(auxdata[0]) + assert.NoError(t, err) + assert.True(t, strings.Contains(string(auxStr), "myData")) + assert.True(t, strings.Contains(string(auxStr), "addons.oam.dev/auxiliary-name")) + assert.True(t, strings.Contains(string(auxStr), "configmap")) + + // test no error when no outputs + addon.AppCueTemplate = ElementFile{Data: appTemplateNoOutputs} + _, _, err = render.renderApp() assert.NoError(t, err) } @@ -369,7 +384,7 @@ func TestGenerateAppFrameworkWithCue(t *testing.T) { AppCueTemplate: ElementFile{Data: cueTemplate}, Parameters: paraDefined, } - app, err := generateAppFramework(cueAddon, map[string]interface{}{ + app, _, err := generateAppFramework(cueAddon, map[string]interface{}{ "namespace": "vela-system", }) assert.NoError(t, err) @@ -389,7 +404,7 @@ func TestGenerateAppFrameworkWithYamlTemplate(t *testing.T) { Meta: Meta{Name: "velaux"}, AppTemplate: nil, } - app, err := generateAppFramework(yamlAddon, nil) + app, _, err := generateAppFramework(yamlAddon, nil) assert.NoError(t, err) assert.Equal(t, app.Spec.Components != nil, true) assert.Equal(t, len(app.Labels), 2) @@ -398,7 +413,7 @@ func TestGenerateAppFrameworkWithYamlTemplate(t *testing.T) { Meta: Meta{Name: "velaux"}, AppTemplate: &v1beta1.Application{}, } - app, err = generateAppFramework(noCompAddon, nil) + app, _, err = generateAppFramework(noCompAddon, nil) assert.NoError(t, err) assert.Equal(t, app.Spec.Components != nil, true) assert.Equal(t, len(app.Labels), 2) diff --git a/pkg/cue/model/value/value.go b/pkg/cue/model/value/value.go index 2ea3e0309..436252e35 100644 --- a/pkg/cue/model/value/value.go +++ b/pkg/cue/model/value/value.go @@ -36,6 +36,9 @@ import ( "github.com/oam-dev/kubevela/pkg/stdlib" ) +// DefaultPackageHeader describes the default package header for CUE files. +const DefaultPackageHeader = "package main\n" + // Value is an object with cue.runtime and vendors type Value struct { v cue.Value @@ -75,14 +78,21 @@ func (val *Value) UnmarshalTo(x interface{}) error { return json.Unmarshal(data, x) } -// NewValueWithFiles new a value from main and appendix files -func NewValueWithFiles(main string, slaveFiles []string, pd *packages.PackageDiscover, tagTempl string, opts ...func(*ast.File) error) (*Value, error) { +// NewValueWithMainAndFiles new a value from main and appendix files +func NewValueWithMainAndFiles(main string, slaveFiles []string, pd *packages.PackageDiscover, tagTempl string, opts ...func(*ast.File) error) (*Value, error) { builder := &build.Instance{} mainFile, err := parser.ParseFile("main.cue", main, parser.ParseComments) if err != nil { return nil, errors.Wrap(err, "parse main file") } + if mainFile.PackageName() == "" { + // add a default package main if not exist + mainFile, err = parser.ParseFile("main.cue", DefaultPackageHeader+main, parser.ParseComments) + if err != nil { + return nil, errors.Wrap(err, "parse main file with added package main header") + } + } for _, opt := range opts { if err := opt(mainFile); err != nil { return nil, errors.Wrap(err, "run option func for main file") @@ -97,12 +107,6 @@ func NewValueWithFiles(main string, slaveFiles []string, pd *packages.PackageDis if err != nil { return nil, errors.Wrap(err, "parse added file "+strconv.Itoa(idx)+" \n"+sf) } - if mainFile.PackageName() != "" && cueSF.PackageName() == "" { - cueSF, err = parser.ParseFile("sf-"+strconv.Itoa(idx)+".cue", "package "+mainFile.PackageName()+"\n"+sf, parser.ParseComments) - if err != nil { - return nil, errors.Wrap(err, "add package for added file") - } - } if cueSF.PackageName() != mainFile.PackageName() { continue } diff --git a/pkg/oam/labels.go b/pkg/oam/labels.go index d208d3fd1..312aa2adb 100644 --- a/pkg/oam/labels.go +++ b/pkg/oam/labels.go @@ -69,6 +69,9 @@ const ( // LabelAddonName indicates the name of the corresponding Addon LabelAddonName = "addons.oam.dev/name" + // LabelAddonAuxiliaryName indicates the name of the auxiliary resource in addon app template + LabelAddonAuxiliaryName = "addons.oam.dev/auxiliary-name" + // LabelAddonVersion indicates the version of the corresponding installed Addon LabelAddonVersion = "addons.oam.dev/version"