Feat: support addon dry-run to get the yaml results (#4753)

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
This commit is contained in:
Jianbo Sun
2022-09-19 17:24:07 +08:00
committed by GitHub
parent dd22f27e39
commit cdbf14c328
4 changed files with 142 additions and 51 deletions

View File

@@ -842,7 +842,7 @@ func renderCUEView(elem ElementFile) (*unstructured.Unstructured, error) {
return util.Object2Unstructured(*cm)
}
// RenderArgsSecret render addon enable argument to secret
// RenderArgsSecret render addon enable argument to secret to remember when restart or upgrade
func RenderArgsSecret(addon *InstallPackage, args map[string]interface{}) *unstructured.Unstructured {
argsByte, err := json.Marshal(args)
if err != nil {
@@ -899,19 +899,23 @@ type Installer struct {
dc *discovery.DiscoveryClient
skipVersionValidate bool
overrideDefs bool
dryRun bool
dryRunBuff *bytes.Buffer
}
// NewAddonInstaller will create an installer for addon
func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache, opts ...InstallOption) Installer {
i := Installer{
ctx: ctx,
config: config,
cli: cli,
apply: apply,
r: r,
args: args,
cache: cache,
dc: discoveryClient,
ctx: ctx,
config: config,
cli: cli,
apply: apply,
r: r,
args: args,
cache: cache,
dc: discoveryClient,
dryRunBuff: &bytes.Buffer{},
}
for _, opt := range opts {
opt(&i)
@@ -995,6 +999,7 @@ func (h *Installer) getAddonMeta() (map[string]SourceMeta, error) {
// installDependency checks if addon's dependency and install it
func (h *Installer) installDependency(addon *InstallPackage) error {
var dependencies []string
for _, dep := range addon.Dependencies {
_, err := FetchAddonRelatedApp(h.ctx, h.cli, dep.Name)
if err == nil {
@@ -1003,6 +1008,10 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
if !apierrors.IsNotFound(err) {
return err
}
dependencies = append(dependencies, dep.Name)
if h.dryRun {
continue
}
// always install addon's latest version
depAddon, err := h.loadInstallPackage(dep.Name, "")
if err != nil {
@@ -1014,6 +1023,10 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
return errors.Wrap(err, "fail to dispatch dependent addon resource")
}
}
if h.dryRun && len(dependencies) > 0 {
klog.Warningf("dry run addon won't install dependencies, please make sure your system has already installed these addons: %v", strings.Join(dependencies, ", "))
return nil
}
return nil
}
@@ -1098,43 +1111,40 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
if err := passDefInAppAnnotation(defs, app); err != nil {
return errors.Wrapf(err, "cannot pass definition to addon app's annotation")
}
if err = h.createOrUpdate(app); err != nil {
return err
}
for _, def := range defs {
if !checkBondComponentExist(*def, *app) {
continue
if h.dryRun {
result, err := yaml.Marshal(app)
if err != nil {
return errors.Wrapf(err, "dry-run marshal app into yaml %s", app.Name)
}
// if binding component exist, apply the definition
addOwner(def, app)
err = h.apply.Apply(h.ctx, def, apply.DisableUpdateAnnotation())
h.dryRunBuff.Write(result)
h.dryRunBuff.WriteString("\n")
} else {
err = h.createOrUpdate(app)
if err != nil {
return err
}
}
for _, schema := range schemas {
addOwner(schema, app)
err = h.apply.Apply(h.ctx, schema, apply.DisableUpdateAnnotation())
if err != nil {
return err
}
}
for _, view := range views {
addOwner(view, app)
err = h.apply.Apply(h.ctx, view, apply.DisableUpdateAnnotation())
if err != nil {
return err
}
}
auxiliaryOutputs = append(auxiliaryOutputs, defs...)
auxiliaryOutputs = append(auxiliaryOutputs, schemas...)
auxiliaryOutputs = append(auxiliaryOutputs, views...)
for _, o := range auxiliaryOutputs {
// bind-component means the content is related with the component
// if component not exists, the resources shouldn't be applied
if !checkBondComponentExist(*o, *app) {
continue
}
if h.dryRun {
result, err := yaml.Marshal(o)
if err != nil {
return errors.Wrapf(err, "dry-run marshal auxiliary object into yaml %s", o.GetName())
}
h.dryRunBuff.WriteString("---\n")
h.dryRunBuff.Write(result)
h.dryRunBuff.WriteString("\n")
continue
}
addOwner(o, app)
err = h.apply.Apply(h.ctx, o, apply.DisableUpdateAnnotation())
if err != nil {
@@ -1142,6 +1152,11 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
}
}
if h.dryRun {
fmt.Print(h.dryRunBuff.String())
return nil
}
if h.args != nil && len(h.args) > 0 {
sec := RenderArgsSecret(addon, h.args)
addOwner(sec, app)
@@ -1157,6 +1172,9 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
// 1. if last apply failed an workflow have suspend, this func will continue the workflow
// 2. restart the workflow, if the new cluster have been added in KubeVela
func (h *Installer) continueOrRestartWorkflow() error {
if h.dryRun {
return nil
}
app, err := FetchAddonRelatedApp(h.ctx, h.cli, h.addon.Name)
if err != nil {
return err

View File

@@ -19,10 +19,13 @@ package addon
import (
"context"
"fmt"
"io"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
yaml3 "gopkg.in/yaml.v3"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -42,6 +45,7 @@ import (
"github.com/oam-dev/kubevela/pkg/oam/util"
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
"github.com/oam-dev/kubevela/pkg/utils/apply"
"github.com/oam-dev/kubevela/references/cli/top/model"
)
var _ = Describe("Addon test", func() {
@@ -385,6 +389,57 @@ var _ = Describe("test enable addon in local dir", func() {
})
})
var _ = Describe("test dry-run addon from local dir", func() {
BeforeEach(func() {
app := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-example"}}
Expect(k8sClient.Delete(ctx, &app)).Should(SatisfyAny(BeNil(), util.NotFoundMatcher{}))
})
AfterEach(func() {
app := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-example"}}
Expect(k8sClient.Delete(ctx, &app)).Should(SatisfyAny(BeNil(), util.NotFoundMatcher{}))
cd := v1beta1.ComponentDefinition{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "helm-example"}}
Expect(k8sClient.Delete(ctx, &cd)).Should(SatisfyAny(BeNil(), util.NotFoundMatcher{}))
})
It("test dry-run enable addon from local dir", func() {
ctx := context.Background()
r := localReader{dir: "./testdata/example", name: "addon-example"}
metas, err := r.ListAddonMeta()
Expect(err).Should(BeNil())
meta := metas[r.name]
UIData, err := GetUIDataFromReader(r, &meta, UIMetaOptions)
Expect(err).Should(BeNil())
pkg, err := GetInstallPackageFromReader(r, &meta, UIData)
Expect(err).Should(BeNil())
h := NewAddonInstaller(ctx, k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, &Registry{Name: LocalAddonRegistryName}, map[string]interface{}{"example": "test-dry-run"}, nil, DryRunAddon)
err = h.enableAddon(pkg)
Expect(err).Should(BeNil())
decoder := yaml3.NewDecoder(h.dryRunBuff)
for {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
err := decoder.Decode(obj.Object)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
Expect(err).Should(BeNil())
}
Expect(obj.GetNamespace()).Should(BeEquivalentTo(model.VelaSystemNS))
Expect(k8sClient.Create(ctx, obj)).Should(BeNil())
}
app := v1beta1.Application{}
Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-example"}, &app)).Should(BeNil())
})
})
var _ = Describe("test enable addon which applies the views independently", func() {
BeforeEach(func() {
app := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "vela-system", Name: "addon-test-view"}}

View File

@@ -24,13 +24,13 @@ import (
"path/filepath"
"strings"
errors "github.com/pkg/errors"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
errors2 "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
rest "k8s.io/client-go/rest"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
@@ -272,7 +272,12 @@ func SkipValidateVersion(installer *Installer) {
installer.skipVersionValidate = true
}
// OverrideDefinitions menas override definitions within this addon if some of them already exist
// DryRunAddon means only generate yaml for addon instead of installing it
func DryRunAddon(installer *Installer) {
installer.dryRun = true
}
// OverrideDefinitions means override definitions within this addon if some of them already exist
func OverrideDefinitions(installer *Installer) {
installer.overrideDefs = true
}
@@ -476,7 +481,7 @@ func produceDefConflictError(conflictDefs map[string]string) error {
return errors.New(errorInfo)
}
// checkBondComponentExistt will check the ready-to-apply object(def or auxiliary outputs) whether bind to a component
// checkBondComponentExist will check the ready-to-apply object(def or auxiliary outputs) whether bind to a component
// if the target component not exist, return false.
func checkBondComponentExist(u unstructured.Unstructured, app v1beta1.Application) bool {
var comp string