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:
wyike
2022-07-25 21:32:32 +08:00
committed by GitHub
parent bb8f4e426a
commit c68061bca7
4 changed files with 164 additions and 6 deletions
+14
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+4
View File
@@ -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)
}