mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
Feat: support vela addon enable with package (#4443)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
This commit is contained in:
@@ -1056,7 +1056,6 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "render addon application fail")
|
||||
}
|
||||
|
||||
appName, err := determineAddonAppName(h.ctx, h.cli, h.addon.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+51
-11
@@ -23,6 +23,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -51,32 +52,42 @@ type addonCueTemplateRender struct {
|
||||
inputArgs map[string]interface{}
|
||||
}
|
||||
|
||||
// This func can be used for addon render, supporting render app template and component.
|
||||
// Please notice the result will be stored in object parameter, so object must be a pointer type
|
||||
func (a addonCueTemplateRender) toObject(cueTemplate string, path string, object interface{}) error {
|
||||
func (a addonCueTemplateRender) formatContext() (string, error) {
|
||||
args := a.inputArgs
|
||||
if args == nil {
|
||||
args = map[string]interface{}{}
|
||||
}
|
||||
bt, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
paramFile := fmt.Sprintf("%s: %s", cuemodel.ParameterFieldName, string(bt))
|
||||
|
||||
var contextFile = strings.Builder{}
|
||||
// user custom parameter but be the first data and generated data should be appended at last
|
||||
// in case the user defined data has packages
|
||||
contextFile.WriteString(a.addon.Parameters + "\n")
|
||||
|
||||
// addon metadata context
|
||||
metadataJSON, err := json.Marshal(a.addon.Meta)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
contextFile.WriteString(fmt.Sprintf("context: metadata: %s\n", string(metadataJSON)))
|
||||
// parameter definition
|
||||
contextFile.WriteString(paramFile + "\n")
|
||||
// user custom parameter
|
||||
contextFile.WriteString(a.addon.Parameters + "\n")
|
||||
|
||||
v, err := value.NewValue(contextFile.String(), nil, "")
|
||||
return contextFile.String(), nil
|
||||
}
|
||||
|
||||
// This func can be used for addon render component.
|
||||
// Please notice the result will be stored in object parameter, so object must be a pointer type
|
||||
func (a addonCueTemplateRender) toObject(cueTemplate string, path string, object interface{}) error {
|
||||
contextFile, err := a.formatContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := value.NewValue(contextFile, nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -91,6 +102,35 @@ func (a addonCueTemplateRender) toObject(cueTemplate string, path string, object
|
||||
return outputContent.UnmarshalTo(object)
|
||||
}
|
||||
|
||||
// renderApp will render Application from CUE files
|
||||
func (a addonCueTemplateRender) renderApp() (*v1beta1.Application, error) {
|
||||
var app v1beta1.Application
|
||||
|
||||
contextFile, err := a.formatContext()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "format context for app render")
|
||||
}
|
||||
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, "")
|
||||
if err != nil {
|
||||
return 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")
|
||||
}
|
||||
err = outputContent.UnmarshalTo(&app)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decode app from CUE")
|
||||
}
|
||||
return &app, 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) {
|
||||
if len(addon.AppCueTemplate.Data) != 0 && addon.AppTemplate != nil {
|
||||
@@ -128,15 +168,15 @@ func generateAppFramework(addon *InstallPackage, parameters map[string]interface
|
||||
}
|
||||
|
||||
func renderAppAccordingToCueTemplate(addon *InstallPackage, args map[string]interface{}) (*v1beta1.Application, error) {
|
||||
app := v1beta1.Application{}
|
||||
r := addonCueTemplateRender{
|
||||
addon: addon,
|
||||
inputArgs: args,
|
||||
}
|
||||
if err := r.toObject(addon.AppCueTemplate.Data, renderOutputCuePath, &app); err != nil {
|
||||
app, err := r.renderApp()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &app, nil
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// renderCompAccordingCUETemplate will return a component from cue template
|
||||
|
||||
@@ -34,6 +34,14 @@ func TestRenderAppTemplate(t *testing.T) {
|
||||
clusters?: [...string]
|
||||
namespace: string
|
||||
}`
|
||||
resourceComponent1 := `
|
||||
myref: {
|
||||
type: "ref-objects"
|
||||
properties: {
|
||||
urls: ["https://hello.yaml"]
|
||||
}
|
||||
}
|
||||
`
|
||||
appTemplate := `output: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
@@ -50,7 +58,7 @@ func TestRenderAppTemplate(t *testing.T) {
|
||||
kind: "Namespace"
|
||||
metadata: name: parameter.namespace
|
||||
}]
|
||||
}]
|
||||
},myref]
|
||||
policies: [{
|
||||
type: "shared-resource"
|
||||
name: "namespace"
|
||||
@@ -77,7 +85,9 @@ func TestRenderAppTemplate(t *testing.T) {
|
||||
RuntimeCluster: true,
|
||||
},
|
||||
},
|
||||
Parameters: paraDefined,
|
||||
Parameters: paraDefined,
|
||||
CUETemplates: []ElementFile{{Data: resourceComponent1}},
|
||||
AppCueTemplate: ElementFile{Data: appTemplate},
|
||||
}
|
||||
|
||||
render := addonCueTemplateRender{
|
||||
@@ -86,18 +96,43 @@ func TestRenderAppTemplate(t *testing.T) {
|
||||
"namespace": "vela-system",
|
||||
},
|
||||
}
|
||||
app := v1beta1.Application{}
|
||||
err := render.toObject(appTemplate, renderOutputCuePath, &app)
|
||||
app, err := render.renderApp()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(app.Spec.Components), 1)
|
||||
assert.Equal(t, len(app.Spec.Components), 2)
|
||||
str, err := json.Marshal(app.Spec.Components[0].Properties)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.Contains(string(str), `{"name":"vela-system"}`))
|
||||
str2, err := json.Marshal(app.Spec.Components[1].Properties)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.Contains(string(str2), `{"urls":["https://hello.yaml"]}`))
|
||||
|
||||
assert.Equal(t, len(app.Spec.Policies), 2)
|
||||
str, err = json.Marshal(app.Spec.Policies)
|
||||
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()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(app.Spec.Components), 2)
|
||||
|
||||
addon.Parameters = "package main\n" + paraDefined
|
||||
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()
|
||||
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()
|
||||
assert.Equal(t, err.Error(), `load app template with CUE files: reference "myref" not found`)
|
||||
|
||||
}
|
||||
|
||||
func TestOutputsRender(t *testing.T) {
|
||||
@@ -370,13 +405,17 @@ func TestGenerateAppFrameworkWithYamlTemplate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderCueResourceError(t *testing.T) {
|
||||
cueTemplate1 := `{
|
||||
cueTemplate1 := `output: {
|
||||
type: "webservice"
|
||||
name: "velaux"
|
||||
}`
|
||||
cueTemplate2 := `output: {
|
||||
type: "webservice"
|
||||
name: "velaux"
|
||||
name: "velaux2"
|
||||
}`
|
||||
cueTemplate3 := `nooutput: {
|
||||
type: "webservice"
|
||||
name: "velaux3"
|
||||
}`
|
||||
comp, err := renderResources(&InstallPackage{
|
||||
CUETemplates: []ElementFile{
|
||||
@@ -388,8 +427,12 @@ func TestRenderCueResourceError(t *testing.T) {
|
||||
Data: cueTemplate2,
|
||||
Name: "tmplaate2.cue",
|
||||
},
|
||||
{
|
||||
Data: cueTemplate3,
|
||||
Name: "tmplaate3.cue",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(comp), 1)
|
||||
assert.Equal(t, len(comp), 2)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,49 @@ 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) {
|
||||
builder := &build.Instance{}
|
||||
|
||||
mainFile, err := parser.ParseFile("main.cue", main, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse main file")
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(mainFile); err != nil {
|
||||
return nil, errors.Wrap(err, "run option func for main file")
|
||||
}
|
||||
}
|
||||
if err := builder.AddSyntax(mainFile); err != nil {
|
||||
return nil, errors.Wrap(err, "add main file to CUE builder")
|
||||
}
|
||||
|
||||
for idx, sf := range slaveFiles {
|
||||
cueSF, err := parser.ParseFile("sf-"+strconv.Itoa(idx)+".cue", sf, parser.ParseComments)
|
||||
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
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(cueSF); err != nil {
|
||||
return nil, errors.Wrap(err, "run option func for files")
|
||||
}
|
||||
}
|
||||
if err := builder.AddSyntax(cueSF); err != nil {
|
||||
return nil, errors.Wrap(err, "add slave files to CUE builder")
|
||||
}
|
||||
}
|
||||
return newValue(builder, pd, tagTempl)
|
||||
}
|
||||
|
||||
// NewValue new a value
|
||||
func NewValue(s string, pd *packages.PackageDiscover, tagTempl string, opts ...func(*ast.File) error) (*Value, error) {
|
||||
builder := &build.Instance{}
|
||||
@@ -91,6 +134,10 @@ func NewValue(s string, pd *packages.PackageDiscover, tagTempl string, opts ...f
|
||||
if err := builder.AddSyntax(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newValue(builder, pd, tagTempl)
|
||||
}
|
||||
|
||||
func newValue(builder *build.Instance, pd *packages.PackageDiscover, tagTempl string) (*Value, error) {
|
||||
addImports := func(inst *build.Instance) error {
|
||||
if pd != nil {
|
||||
pd.ImportBuiltinPackagesFor(inst)
|
||||
|
||||
Reference in New Issue
Block a user