Feat: support vela addon enable with package (#4443)

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
This commit is contained in:
Jianbo Sun
2022-07-25 22:15:59 +08:00
committed by GitHub
parent e595879da9
commit 8fd47ef5b9
4 changed files with 149 additions and 20 deletions
-1
View File
@@ -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
View File
@@ -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
+51 -8
View File
@@ -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)
}
+47
View File
@@ -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)