mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-14 13:26:44 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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"}}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user