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 <charlie_c_0129@outlook.com>

* Refactor: simplify init cmd

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Feat: ignore metadata

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Feat: remove status

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* do not marshal to application

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Feat: only look for output field

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Feat: use global constant

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>

* Test: update tests according to changes

Signed-off-by: Charlie Chiang <charlie_c_0129@outlook.com>
This commit is contained in:
Charlie Chiang
2022-07-25 21:32:01 +08:00
committed by GitHub
parent 89037b2123
commit bb8f4e426a
4 changed files with 87 additions and 86 deletions
+30 -38
View File
@@ -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: []
}
}
`
)
+36 -16
View File
@@ -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
+11 -7
View File
@@ -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"),
+10 -25
View File
@@ -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 ./<addon-name>)")
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
}