From bb8f4e426aec21824cea51951647b64bfafc7ec0 Mon Sep 17 00:00:00 2001 From: Charlie Chiang Date: Mon, 25 Jul 2022 21:32:01 +0800 Subject: [PATCH] Feat: make addon init use the latest CUE addon template (#4434) * Feat: make addon init use the latest CUE addon template Signed-off-by: Charlie Chiang * Refactor: simplify init cmd Signed-off-by: Charlie Chiang * Feat: ignore metadata Signed-off-by: Charlie Chiang * Feat: remove status Signed-off-by: Charlie Chiang * do not marshal to application Signed-off-by: Charlie Chiang * Feat: only look for output field Signed-off-by: Charlie Chiang * Feat: use global constant Signed-off-by: Charlie Chiang * Test: update tests according to changes Signed-off-by: Charlie Chiang --- pkg/addon/init.go | 68 ++++++++++++++++++----------------------- pkg/addon/utils.go | 52 +++++++++++++++++++++---------- pkg/addon/utils_test.go | 18 ++++++----- references/cli/addon.go | 35 ++++++--------------- 4 files changed, 87 insertions(+), 86 deletions(-) diff --git a/pkg/addon/init.go b/pkg/addon/init.go index ae4047230..21a1e2074 100644 --- a/pkg/addon/init.go +++ b/pkg/addon/init.go @@ -28,19 +28,16 @@ import ( "cuelang.org/go/cue/format" "cuelang.org/go/encoding/gocode/gocodec" "github.com/fatih/color" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog/v2" "sigs.k8s.io/yaml" - "github.com/oam-dev/kubevela/apis/core.oam.dev/common" - "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" - "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/utils" ) const ( // AddonNameRegex is the regex to validate addon names - AddonNameRegex = `^[a-z\d]+(-[a-z\d]+)*$` + AddonNameRegex = `^[a-z\d]+(-[a-z\d]+)*$` + // helmComponentDependency is the dependent addon of Helm Component helmComponentDependency = "fluxcd" ) @@ -54,13 +51,16 @@ type InitCmd struct { Path string Overwrite bool RefObjURLs []string - AppTmpl v1beta1.Application - Metadata Meta - Readme string - Resources []ElementFile - Schemas []ElementFile - Views []ElementFile - Definitions []ElementFile + // We use string instead of v1beta1.Application is because + // the cue formatter is having some problems: it will keep + // TypeMeta (instead of inlined). + AppTmpl string + Metadata Meta + Readme string + Resources []ElementFile + Schemas []ElementFile + Views []ElementFile + Definitions []ElementFile } // CreateScaffold creates an addon scaffold @@ -152,9 +152,6 @@ func (cmd *InitCmd) createSamples() { cmd.Resources = append(cmd.Resources, ElementFile{ Data: resourceTemplate, Name: "myresource.cue", - }, ElementFile{ - Data: parameterTemplate, - Name: "parameter.cue", }) // Sample schema cmd.Schemas = append(cmd.Schemas, ElementFile{ @@ -173,21 +170,8 @@ func (cmd *InitCmd) createRequiredFiles() { // README.md cmd.Readme = strings.ReplaceAll(readmeTemplate, "ADDON_NAME", cmd.AddonName) - // template.yaml - cmd.AppTmpl = v1beta1.Application{ - TypeMeta: v1.TypeMeta{ - APIVersion: v1beta1.SchemeGroupVersion.String(), - Kind: "Application", - }, - ObjectMeta: v1.ObjectMeta{ - Name: cmd.AddonName, - Namespace: types.DefaultKubeVelaNS, - }, - Spec: v1beta1.ApplicationSpec{ - // Prevent nulls after serialization - Components: []common.ApplicationComponent{}, - }, - } + // template.cue + cmd.AppTmpl = appTemplate // metadata.yaml cmd.Metadata = Meta{ @@ -341,6 +325,9 @@ func (cmd *InitCmd) writeFiles() error { files = append(files, ElementFile{ Name: ReadmeFileName, Data: cmd.Readme, + }, ElementFile{ + Data: parameterTemplate, + Name: GlobalParameterFileName, }) for _, v := range cmd.Resources { @@ -368,14 +355,10 @@ func (cmd *InitCmd) writeFiles() error { }) } - // Prepare template.yaml - tmplBytes, err := yaml.Marshal(cmd.AppTmpl) - if err != nil { - return err - } + // Prepare template.cue files = append(files, ElementFile{ - Data: string(tmplBytes), - Name: TemplateFileName, + Data: cmd.AppTmpl, + Name: AppTemplateCueFileName, }) // Prepare metadata.yaml @@ -502,7 +485,7 @@ output: { ` parameterTemplate = `// parameter.cue is used to store addon parameters. // -// You can use these parameters in other resources by 'parameter.myparam' +// You can use these parameters in template.cue or in resources/ by 'parameter.myparam' // // For example, you can use parameters to allow the user to customize // container images, ports, and etc. @@ -519,5 +502,14 @@ parameter: { label: MyParam validate: required: true +` + appTemplate = `output: { + apiVersion: "core.oam.dev/v1beta1" + kind: "Application" + spec: { + components: [] + policies: [] + } +} ` ) diff --git a/pkg/addon/utils.go b/pkg/addon/utils.go index aa51770e1..42eb01caf 100644 --- a/pkg/addon/utils.go +++ b/pkg/addon/utils.go @@ -24,16 +24,13 @@ import ( "path/filepath" "strings" + errors "github.com/pkg/errors" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" - "sigs.k8s.io/yaml" - - errors "github.com/pkg/errors" - - "sigs.k8s.io/controller-runtime/pkg/client" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" rest "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" "github.com/oam-dev/kubevela/pkg/definition" @@ -279,23 +276,46 @@ func IsAddonDir(dirName string) (bool, error) { return false, errors.Errorf("addon version is empty") } - // Load template.yaml - templateYaml := filepath.Join(dirName, TemplateFileName) - if _, err := os.Stat(templateYaml); os.IsNotExist(err) { - return false, errors.Errorf("no %s exists in directory %q", TemplateFileName, dirName) + // Load template.yaml/cue + var errYAML error + var errCUE error + templateYAML := filepath.Join(dirName, TemplateFileName) + templateCUE := filepath.Join(dirName, AppTemplateCueFileName) + _, errYAML = os.Stat(templateYAML) + _, errCUE = os.Stat(templateCUE) + if os.IsNotExist(errYAML) && os.IsNotExist(errCUE) { + return false, fmt.Errorf("no %s or %s exists in directory %q", TemplateFileName, AppTemplateCueFileName, dirName) } - templateYamlContent, err := ioutil.ReadFile(filepath.Clean(templateYaml)) + if errYAML != nil && errCUE != nil { + return false, errors.Errorf("cannot stat %s or %s", TemplateFileName, AppTemplateCueFileName) + } + + // template.cue have higher priority + if errCUE == nil { + templateContent, err := ioutil.ReadFile(filepath.Clean(templateCUE)) + if err != nil { + return false, fmt.Errorf("cannot read %s: %w", AppTemplateCueFileName, err) + } + // Just look for `output` field is enough. + // No need to load the whole addon package to render the Application. + if !strings.Contains(string(templateContent), renderOutputCuePath) { + return false, fmt.Errorf("no %s field in %s", renderOutputCuePath, AppTemplateCueFileName) + } + return true, nil + } + + // then check template.yaml + templateYamlContent, err := ioutil.ReadFile(filepath.Clean(templateYAML)) if err != nil { return false, errors.Errorf("cannot read %s in directory %q", TemplateFileName, dirName) } - // Check template.yaml contents - templateContent := new(v1beta1.Application) - if err := yaml.Unmarshal(templateYamlContent, &templateContent); err != nil { + template := new(v1beta1.Application) + if err := yaml.Unmarshal(templateYamlContent, &template); err != nil { return false, err } - if templateContent == nil { - return false, errors.Errorf("chart metadata (%s) missing", TemplateFileName) + if template == nil { + return false, errors.Errorf("template (%s) missing", TemplateFileName) } return true, nil diff --git a/pkg/addon/utils_test.go b/pkg/addon/utils_test.go index 76930682d..b911880b7 100644 --- a/pkg/addon/utils_test.go +++ b/pkg/addon/utils_test.go @@ -23,19 +23,16 @@ import ( "strings" "testing" - "helm.sh/helm/v3/pkg/chartutil" - - "github.com/stretchr/testify/assert" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v3/pkg/chartutil" v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" velatypes "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/oam" @@ -213,7 +210,7 @@ func TestIsAddonDir(t *testing.T) { assert.Equal(t, isAddonDir, false) assert.Contains(t, err.Error(), "addon version is empty") - // No template.yaml + // No metadata.yaml meta = &Meta{ Name: "name", Version: "1.0.0", @@ -233,6 +230,13 @@ func TestIsAddonDir(t *testing.T) { assert.Equal(t, isAddonDir, false) assert.Contains(t, err.Error(), "missing") + // Empty template.cue + err = os.WriteFile(filepath.Join("testdata", "testaddon", AppTemplateCueFileName), []byte{}, 0644) + assert.NoError(t, err) + isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon")) + assert.Equal(t, isAddonDir, false) + assert.Contains(t, err.Error(), renderOutputCuePath) + // Pass all checks cmd := InitCmd{ Path: filepath.Join("testdata", "testaddon2"), diff --git a/references/cli/addon.go b/references/cli/addon.go index b3c4aaa46..bd0b0e49d 100644 --- a/references/cli/addon.go +++ b/references/cli/addon.go @@ -393,15 +393,8 @@ func NewAddonStatusCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com // NewAddonInitCommand creates an addon scaffold func NewAddonInitCommand() *cobra.Command { - var ( - helmRepoURL string - chartName string - chartVersion string - urls []string - path string - noSample bool - overwrite bool - ) + var path string + initCmd := pkgaddon.InitCmd{} cmd := &cobra.Command{ Use: "init", @@ -438,29 +431,21 @@ func NewAddonInitCommand() *cobra.Command { return fmt.Errorf("addon name or path should not be empty") } - initCmd := pkgaddon.InitCmd{ - AddonName: addonName, - HelmChartName: chartName, - HelmChartVersion: chartVersion, - HelmRepoURL: helmRepoURL, - Path: addonPath, - RefObjURLs: urls, - NoSamples: noSample, - Overwrite: overwrite, - } + initCmd.AddonName = addonName + initCmd.Path = addonPath return initCmd.CreateScaffold() }, } f := cmd.Flags() - f.StringVar(&helmRepoURL, "helm-repo", "", "URL that points to a Helm repo") - f.StringVar(&chartName, "chart", "", "Helm Chart name") - f.StringVar(&chartVersion, "chart-version", "", "version of the Chart") + f.StringVar(&initCmd.HelmRepoURL, "helm-repo", "", "URL that points to a Helm repo") + f.StringVar(&initCmd.HelmChartName, "chart", "", "Helm Chart name") + f.StringVar(&initCmd.HelmChartVersion, "chart-version", "", "version of the Chart") f.StringVarP(&path, "path", "p", "", "path to the addon directory (default is ./)") - f.StringArrayVarP(&urls, "url", "u", []string{}, "add URL resources using ref-object component") - f.BoolVarP(&noSample, "no-samples", "", false, "do not generate sample files") - f.BoolVarP(&overwrite, "force", "f", false, "overwrite existing addon files") + f.StringArrayVarP(&initCmd.RefObjURLs, "url", "u", []string{}, "add URL resources using ref-object component") + f.BoolVarP(&initCmd.NoSamples, "no-samples", "", false, "do not generate sample files") + f.BoolVarP(&initCmd.Overwrite, "force", "f", false, "overwrite existing addon files") return cmd }