mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-23 22:46:53 +00:00
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:
+30
-38
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user