mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
Feat: support outputs for addon (#4444)
* support outputs for addon Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> * fix comments Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix comments Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
This commit is contained in:
@@ -1084,6 +1084,12 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
||||
return errors.Wrapf(err, "cannot pass definition to addon app's annotation")
|
||||
}
|
||||
|
||||
var auxiliaryOutputs []*unstructured.Unstructured
|
||||
auxiliaryOutputs, err = renderOutputs(addon, h.args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = h.createOrUpdate(app); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1112,6 +1118,14 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, o := range auxiliaryOutputs {
|
||||
addOwner(o, app)
|
||||
err = h.apply.Apply(h.ctx, o, apply.DisableUpdateAnnotation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if h.args != nil && len(h.args) > 0 {
|
||||
sec := RenderArgsSecret(addon, h.args)
|
||||
addOwner(sec, app)
|
||||
|
||||
+27
-4
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
@@ -42,6 +43,7 @@ const (
|
||||
specifyAddonClustersTopologyPolicy = "deploy-addon-to-specified-clusters"
|
||||
addonAllClusterPolicy = "deploy-addon-to-all-clusters"
|
||||
renderOutputCuePath = "output"
|
||||
renderAuxiliaryOutputsPath = "outputs"
|
||||
)
|
||||
|
||||
type addonCueTemplateRender struct {
|
||||
@@ -51,7 +53,7 @@ type addonCueTemplateRender struct {
|
||||
|
||||
// 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, object interface{}) error {
|
||||
func (a addonCueTemplateRender) toObject(cueTemplate string, path string, object interface{}) error {
|
||||
args := a.inputArgs
|
||||
if args == nil {
|
||||
args = map[string]interface{}{}
|
||||
@@ -82,7 +84,7 @@ func (a addonCueTemplateRender) toObject(cueTemplate string, object interface{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputContent, err := out.LookupValue(renderOutputCuePath)
|
||||
outputContent, err := out.LookupValue(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -131,7 +133,7 @@ func renderAppAccordingToCueTemplate(addon *InstallPackage, args map[string]inte
|
||||
addon: addon,
|
||||
inputArgs: args,
|
||||
}
|
||||
if err := r.toObject(addon.AppCueTemplate.Data, &app); err != nil {
|
||||
if err := r.toObject(addon.AppCueTemplate.Data, renderOutputCuePath, &app); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &app, nil
|
||||
@@ -145,7 +147,7 @@ func renderCompAccordingCUETemplate(cueTemplate ElementFile, addon *InstallPacka
|
||||
addon: addon,
|
||||
inputArgs: args,
|
||||
}
|
||||
if err := r.toObject(cueTemplate.Data, &comp); err != nil {
|
||||
if err := r.toObject(cueTemplate.Data, renderOutputCuePath, &comp); err != nil {
|
||||
return nil, fmt.Errorf("error rendering file %s: %w", cueTemplate.Name, err)
|
||||
}
|
||||
// If the name of component has been set, just keep it, otherwise will set with file name.
|
||||
@@ -275,6 +277,27 @@ func checkNeedAttachTopologyPolicy(app *v1beta1.Application, addon *InstallPacka
|
||||
return true
|
||||
}
|
||||
|
||||
func renderOutputs(addon *InstallPackage, args map[string]interface{}) ([]*unstructured.Unstructured, error) {
|
||||
outputs := map[string]interface{}{}
|
||||
r := addonCueTemplateRender{
|
||||
addon: addon,
|
||||
inputArgs: args,
|
||||
}
|
||||
if err := r.toObject(addon.AppCueTemplate.Data, renderAuxiliaryOutputsPath, &outputs); err != nil {
|
||||
if isErrorCueRenderPathNotFound(err, renderAuxiliaryOutputsPath) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var res []*unstructured.Unstructured
|
||||
for _, o := range outputs {
|
||||
if ao, ok := o.(map[string]interface{}); ok {
|
||||
res = append(res, &unstructured.Unstructured{Object: ao})
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func isDeployToRuntime(addon *InstallPackage) bool {
|
||||
if addon.DeployTo == nil {
|
||||
return false
|
||||
|
||||
+119
-2
@@ -87,7 +87,7 @@ func TestRenderAppTemplate(t *testing.T) {
|
||||
},
|
||||
}
|
||||
app := v1beta1.Application{}
|
||||
err := render.toObject(appTemplate, &app)
|
||||
err := render.toObject(appTemplate, renderOutputCuePath, &app)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(app.Spec.Components), 1)
|
||||
str, err := json.Marshal(app.Spec.Components[0].Properties)
|
||||
@@ -100,6 +100,123 @@ func TestRenderAppTemplate(t *testing.T) {
|
||||
assert.True(t, strings.Contains(string(str), `"clusterLabelSelector":{}`))
|
||||
}
|
||||
|
||||
func TestOutputsRender(t *testing.T) {
|
||||
appTemplate := `output: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
metadata: {
|
||||
name: "velaux"
|
||||
namespace: "vela-system"
|
||||
}
|
||||
spec: {
|
||||
components: [{
|
||||
type: "k8s-objects"
|
||||
name: "vela-namespace"
|
||||
properties: objects: [{
|
||||
apiVersion: "v1"
|
||||
kind: "Namespace"
|
||||
metadata: name: parameter.namespace
|
||||
}]
|
||||
}]
|
||||
policies: [{
|
||||
type: "shared-resource"
|
||||
name: "namespace"
|
||||
properties: rules: [{selector: resourceTypes: ["Namespace"]}]
|
||||
}, {
|
||||
type: "topology"
|
||||
name: "deploy-topology"
|
||||
properties: {
|
||||
if parameter.clusters != _|_ {
|
||||
clusters: parameter.clusters
|
||||
}
|
||||
if parameter.clusters == _|_ {
|
||||
clusterLabelSelector: {}
|
||||
}
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
outputs: configmap: {
|
||||
apiVersion: "v1"
|
||||
kind: "Configmap"
|
||||
metadata: {
|
||||
name: "test-cm"
|
||||
namespace: "default"
|
||||
}
|
||||
data: parameter.data
|
||||
}
|
||||
`
|
||||
paraDefined := `parameter: {
|
||||
// +usage=The clusters to install
|
||||
data: "myData"
|
||||
}`
|
||||
appTemplateNoOutputs := `output: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
metadata: {
|
||||
name: "velaux"
|
||||
namespace: "vela-system"
|
||||
}
|
||||
spec: {
|
||||
components: [{
|
||||
type: "k8s-objects"
|
||||
name: "vela-namespace"
|
||||
properties: objects: [{
|
||||
apiVersion: "v1"
|
||||
kind: "Namespace"
|
||||
metadata: name: parameter.namespace
|
||||
}]
|
||||
}]
|
||||
policies: [{
|
||||
type: "shared-resource"
|
||||
name: "namespace"
|
||||
properties: rules: [{selector: resourceTypes: ["Namespace"]}]
|
||||
}, {
|
||||
type: "topology"
|
||||
name: "deploy-topology"
|
||||
properties: {
|
||||
if parameter.clusters != _|_ {
|
||||
clusters: parameter.clusters
|
||||
}
|
||||
if parameter.clusters == _|_ {
|
||||
clusterLabelSelector: {}
|
||||
}
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
`
|
||||
|
||||
addon := &InstallPackage{
|
||||
Meta: Meta{
|
||||
Name: "velaux",
|
||||
DeployTo: &DeployTo{
|
||||
RuntimeCluster: true,
|
||||
},
|
||||
},
|
||||
Parameters: paraDefined,
|
||||
AppCueTemplate: ElementFile{Data: appTemplate},
|
||||
}
|
||||
list, err := renderOutputs(addon, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, len(list) == 1)
|
||||
|
||||
addon = &InstallPackage{
|
||||
Meta: Meta{
|
||||
Name: "velaux",
|
||||
DeployTo: &DeployTo{
|
||||
RuntimeCluster: true,
|
||||
},
|
||||
},
|
||||
Parameters: paraDefined,
|
||||
AppCueTemplate: ElementFile{Data: appTemplateNoOutputs},
|
||||
}
|
||||
_, err = renderOutputs(addon, nil)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAppComponentRender(t *testing.T) {
|
||||
paraDefined := `parameter: {
|
||||
image: string
|
||||
@@ -127,7 +244,7 @@ func TestAppComponentRender(t *testing.T) {
|
||||
},
|
||||
}
|
||||
comp := common.ApplicationComponent{}
|
||||
err := render.toObject(compTemplate, &comp)
|
||||
err := render.toObject(compTemplate, renderOutputCuePath, &comp)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, comp.Name, "velaux")
|
||||
assert.Equal(t, comp.Type, "webservice")
|
||||
|
||||
@@ -405,3 +405,7 @@ func generateAnnotation(meta *Meta) map[string]string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func isErrorCueRenderPathNotFound(err error, path string) bool {
|
||||
return err.Error() == fmt.Sprintf("var(path=%s) not exist", path)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user