Skip to content

Commit

Permalink
Feat: support offline dryrun with deploy step (#6234)
Browse files Browse the repository at this point in the history
  • Loading branch information
chivalryq authored Jul 26, 2023
1 parent f0357fd commit 00ae0c9
Show file tree
Hide file tree
Showing 14 changed files with 132 additions and 57 deletions.
5 changes: 3 additions & 2 deletions pkg/appfile/dryrun/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/aryann/difflib"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
Expand All @@ -40,7 +41,7 @@ import (
)

// NewLiveDiffOption creates a live-diff option
func NewLiveDiffOption(c client.Client, cfg *rest.Config, pd *packages.PackageDiscover, as []oam.Object) *LiveDiffOption {
func NewLiveDiffOption(c client.Client, cfg *rest.Config, pd *packages.PackageDiscover, as []*unstructured.Unstructured) *LiveDiffOption {
parser := appfile.NewApplicationParser(c, pd)
return &LiveDiffOption{DryRun: NewDryRunOption(c, cfg, pd, as, false), Parser: parser}
}
Expand Down Expand Up @@ -136,7 +137,7 @@ func (l *LiveDiffOption) RenderlessDiff(ctx context.Context, base, comparor Live
m := &manifest{Name: app.Name, Kind: AppKind, Data: string(bs)}
if appfileError != nil {
m.Data += "Error: " + appfileError.Error() + "\n"
return m, nil //nolint
return m, nil // nolint
}
for _, policy := range af.ExternalPolicies {
if bs, err = marshalObject(policy); err == nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/appfile/dryrun/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ type DryRun interface {
}

// NewDryRunOption creates a dry-run option
func NewDryRunOption(c client.Client, cfg *rest.Config, pd *packages.PackageDiscover, as []oam.Object, serverSideDryRun bool) *Option {
func NewDryRunOption(c client.Client, cfg *rest.Config, pd *packages.PackageDiscover, as []*unstructured.Unstructured, serverSideDryRun bool) *Option {
parser := appfile.NewDryRunApplicationParser(c, pd, as)
return &Option{c, pd, parser, parser.GenerateAppFileFromApp, cfg, as, serverSideDryRun}
}
Expand All @@ -74,7 +74,7 @@ type Option struct {
// Auxiliaries are capability definitions used to parse application.
// DryRun will use capabilities in Auxiliaries as higher priority than
// getting one from cluster.
Auxiliaries []oam.Object
Auxiliaries []*unstructured.Unstructured

// serverSideDryRun If set to true, means will dry run via the apiserver.
serverSideDryRun bool
Expand Down
4 changes: 2 additions & 2 deletions pkg/appfile/dryrun/suit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"

"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
Expand All @@ -46,7 +47,6 @@ import (

coreoam "github.com/oam-dev/kubevela/apis/core.oam.dev"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/oam"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
)

Expand Down Expand Up @@ -113,7 +113,7 @@ var _ = BeforeSuite(func() {
wfsd.SetNamespace(types.DefaultKubeVelaNS)
Expect(k8sClient.Create(context.TODO(), &wfsd)).Should(BeNil())

dryrunOpt = NewDryRunOption(k8sClient, cfg, pd, []oam.Object{cdMyWorker, tdMyIngress}, false)
dryrunOpt = NewDryRunOption(k8sClient, cfg, pd, []*unstructured.Unstructured{cdMyWorker, tdMyIngress}, false)
diffOpt = &LiveDiffOption{DryRun: dryrunOpt, Parser: appfile.NewApplicationParser(k8sClient, pd)}
})

Expand Down
2 changes: 1 addition & 1 deletion pkg/appfile/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func NewApplicationParser(cli client.Client, pd *packages.PackageDiscover) *Pars
}

// NewDryRunApplicationParser create an appfile parser for DryRun
func NewDryRunApplicationParser(cli client.Client, pd *packages.PackageDiscover, defs []oam.Object) *Parser {
func NewDryRunApplicationParser(cli client.Client, pd *packages.PackageDiscover, defs []*unstructured.Unstructured) *Parser {
return &Parser{
client: cli,
pd: pd,
Expand Down
47 changes: 22 additions & 25 deletions pkg/appfile/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
)

Expand Down Expand Up @@ -252,35 +251,33 @@ func verifyRevisionName(capName string, capType types.CapType, apprev *v1beta1.A
// DryRunTemplateLoader return a function that do the same work as
// LoadTemplate, but load template from provided ones before loading from
// cluster through LoadTemplate
func DryRunTemplateLoader(defs []oam.Object) TemplateLoaderFn {
func DryRunTemplateLoader(defs []*unstructured.Unstructured) TemplateLoaderFn {
return func(ctx context.Context, r client.Client, capName string, capType types.CapType) (*Template, error) {
// retrieve provided cap definitions
for _, def := range defs {
if unstructDef, ok := def.(*unstructured.Unstructured); ok {
if unstructDef.GetKind() == v1beta1.ComponentDefinitionKind &&
capType == types.TypeComponentDefinition && unstructDef.GetName() == capName {
compDef := &v1beta1.ComponentDefinition{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructDef.Object, compDef); err != nil {
return nil, errors.Wrap(err, "invalid component definition")
}
tmpl, err := newTemplateOfCompDefinition(compDef)
if err != nil {
return nil, errors.WithMessagef(err, "cannot load template of component definition %q", capName)
}
return tmpl, nil
if def.GetKind() == v1beta1.ComponentDefinitionKind &&
capType == types.TypeComponentDefinition && def.GetName() == capName {
compDef := &v1beta1.ComponentDefinition{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(def.Object, compDef); err != nil {
return nil, errors.Wrap(err, "invalid component definition")
}
if unstructDef.GetKind() == v1beta1.TraitDefinitionKind &&
capType == types.TypeTrait && unstructDef.GetName() == capName {
traitDef := &v1beta1.TraitDefinition{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructDef.Object, traitDef); err != nil {
return nil, errors.Wrap(err, "invalid trait definition")
}
tmpl, err := newTemplateOfTraitDefinition(traitDef)
if err != nil {
return nil, errors.WithMessagef(err, "cannot load template of trait definition %q", capName)
}
return tmpl, nil
tmpl, err := newTemplateOfCompDefinition(compDef)
if err != nil {
return nil, errors.WithMessagef(err, "cannot load template of component definition %q", capName)
}
return tmpl, nil
}
if def.GetKind() == v1beta1.TraitDefinitionKind &&
capType == types.TypeTrait && def.GetName() == capName {
traitDef := &v1beta1.TraitDefinition{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(def.Object, traitDef); err != nil {
return nil, errors.Wrap(err, "invalid trait definition")
}
tmpl, err := newTemplateOfTraitDefinition(traitDef)
if err != nil {
return nil, errors.WithMessagef(err, "cannot load template of trait definition %q", capName)
}
return tmpl, nil
}
}
// not found in provided cap definitions
Expand Down
4 changes: 2 additions & 2 deletions pkg/appfile/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
ktypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
)

Expand Down Expand Up @@ -362,7 +362,7 @@ spec:
TraitDefinition: traitDef,
}

dryRunLoadTemplate := DryRunTemplateLoader([]oam.Object{unstrctCompDef, unstrctTraitDef})
dryRunLoadTemplate := DryRunTemplateLoader([]*unstructured.Unstructured{unstrctCompDef, unstrctTraitDef})
compTmpl, err := dryRunLoadTemplate(nil, nil, "myworker", types.TypeComponentDefinition)
if err != nil {
t.Error("failed load template of component defintion", err)
Expand Down
8 changes: 2 additions & 6 deletions pkg/utils/common/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/kubevela/workflow/pkg/cue/packages"

"github.com/oam-dev/kubevela/pkg/oam"
)

// Args is args for controller-runtime client
Expand Down Expand Up @@ -125,7 +123,7 @@ func (a *Args) GetClient() (client.Client, error) {
}

// GetFakeClient returns a fake client with the definition objects preloaded
func (a *Args) GetFakeClient(defs []oam.Object) (client.Client, error) {
func (a *Args) GetFakeClient(defs []*unstructured.Unstructured) (client.Client, error) {
if a.client != nil {
return a.client, nil
}
Expand All @@ -136,9 +134,7 @@ func (a *Args) GetFakeClient(defs []oam.Object) (client.Client, error) {
}
objs := make([]client.Object, 0, len(defs))
for _, def := range defs {
if unstructDef, ok := def.(*unstructured.Unstructured); ok {
objs = append(objs, unstructDef)
}
objs = append(objs, def)
}
return fake.NewClientBuilder().WithObjects(objs...).WithScheme(a.Schema).Build(), nil
}
Expand Down
1 change: 0 additions & 1 deletion pkg/workflow/step/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ func IsBuiltinWorkflowStepType(wfType string) bool {
wftypes.WorkflowStepTypeApplyComponent,
wftypes.WorkflowStepTypeBuiltinApplyComponent,
wftypes.WorkflowStepTypeStepGroup,
DeployWorkflowStep,
} {
if _type == wfType {
return true
Expand Down
1 change: 0 additions & 1 deletion pkg/workflow/step/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,6 @@ func TestWorkflowStepGenerator(t *testing.T) {
}

func TestIsBuiltinWorkflowStepType(t *testing.T) {
assert.True(t, IsBuiltinWorkflowStepType("deploy"))
assert.True(t, IsBuiltinWorkflowStepType("suspend"))
assert.True(t, IsBuiltinWorkflowStepType("apply-component"))
assert.True(t, IsBuiltinWorkflowStepType("step-group"))
Expand Down
3 changes: 1 addition & 2 deletions references/cli/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import (

"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
)
Expand Down Expand Up @@ -119,7 +118,7 @@ func (d *debugOpts) debugApplication(ctx context.Context, wargs *WorkflowArgs, c
return d.debugWorkflow(ctx, wargs, cli, pd, ioStreams)
}

dryRunOpt := dryrun.NewDryRunOption(cli, config, pd, []oam.Object{}, false)
dryRunOpt := dryrun.NewDryRunOption(cli, config, pd, []*unstructured.Unstructured{}, false)
comps, _, err := dryRunOpt.ExecuteDryRun(ctx, app)
if err != nil {
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
Expand Down
60 changes: 52 additions & 8 deletions references/cli/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ import (
"strings"

wfv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"

"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/workflow/step"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"

apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
Expand All @@ -46,6 +46,7 @@ import (
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/pkg/workflow/step"
)

// DryRunCmdOptions contains dry-run cmd options
Expand Down Expand Up @@ -130,7 +131,7 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
var err error
var buff = bytes.Buffer{}

var objs []oam.Object
var objs []*unstructured.Unstructured
if cmdOption.DefinitionFile != "" {
objs, err = ReadDefinitionsFromFile(cmdOption.DefinitionFile)
if err != nil {
Expand All @@ -142,6 +143,7 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
var newClient client.Client
if cmdOption.OfflineMode {
// We will load a fake client with all the objects present in the definitions file preloaded
objs = includeBuiltinWorkflowStepDefinition(objs)
newClient, err = c.GetFakeClient(objs)
} else {
// Load an actual client here
Expand Down Expand Up @@ -185,7 +187,7 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
return buff, nil
}

func readObj(path string) (oam.Object, error) {
func readObj(path string) (*unstructured.Unstructured, error) {
switch {
case strings.HasSuffix(path, ".cue"):
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
Expand All @@ -209,7 +211,7 @@ func readObj(path string) (oam.Object, error) {
}

// ReadDefinitionsFromFile will read objects from file or dir in the format of yaml
func ReadDefinitionsFromFile(path string) ([]oam.Object, error) {
func ReadDefinitionsFromFile(path string) ([]*unstructured.Unstructured, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
Expand All @@ -219,10 +221,10 @@ func ReadDefinitionsFromFile(path string) ([]oam.Object, error) {
if err != nil {
return nil, err
}
return []oam.Object{obj}, nil
return []*unstructured.Unstructured{obj}, nil
}

var objs []oam.Object
var objs []*unstructured.Unstructured
//nolint:gosec
fis, err := os.ReadDir(path)
if err != nil {
Expand Down Expand Up @@ -401,3 +403,45 @@ func getPolicyNameFromWorkflow(wf *wfv1alpha1.Workflow, policyNameMap map[string
}
return nil
}

// includeBuiltinWorkflowStepDefinition adds builtin workflow step definition to the given objects
// A few builtin workflow steps have cue definition. They should be included when building offline fake client.
func includeBuiltinWorkflowStepDefinition(objs []*unstructured.Unstructured) []*unstructured.Unstructured {
deployUnstructured, _ := oamutil.Object2Unstructured(deployDefinition)
return append(objs, deployUnstructured)
}

// deployDefinition is the definition of deploy step
// Copied it here to make dry-run work in offline mode.
var deployDefinition = &corev1beta1.WorkflowStepDefinition{
TypeMeta: metav1.TypeMeta{
Kind: corev1beta1.WorkflowStepDefinitionKind,
APIVersion: corev1beta1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "deploy",
Namespace: oam.SystemDefinitionNamespace,
},
Spec: corev1beta1.WorkflowStepDefinitionSpec{
Schematic: &apicommon.Schematic{
CUE: &apicommon.CUE{Template: `
import (
"vela/op"
)
"deploy": {
type: "workflow-step"
annotations: {
"category": "Application Delivery"
}
labels: {
"scope": "Application"
}
description: "A powerful and unified deploy step for components multi-cluster delivery with policies."
}
// Ignore the template field for it's useless in dry-run.
template: {}`,
},
},
},
}
15 changes: 12 additions & 3 deletions references/cli/dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,7 @@ var _ = Describe("Testing dry-run", func() {
})

It("Testing dry-run offline", func() {

c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-6.yaml"}, DefinitionFile: "test-data/dry-run/testing-worker-def.yaml", OfflineMode: true}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expand All @@ -237,6 +234,18 @@ var _ = Describe("Testing dry-run", func() {
Expect(buff.String()).Should(ContainSubstring("workload.oam.dev/type: myworker"))
})

It("Testing dry-run offline with deploy workflow step", func() {
c := common2.Args{}
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-7.yaml"}, DefinitionFile: "test-data/dry-run/testing-worker-def.yaml", OfflineMode: true}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology target-prod)"))
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology target-default)"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
Expect(buff.String()).Should(ContainSubstring("workload.oam.dev/type: myworker"))
})

It("Testing dry-run with default application namespace", func() {
c := common2.Args{}
c.SetConfig(cfg)
Expand Down
Loading

0 comments on commit 00ae0c9

Please sign in to comment.