Feat: vela dry-run render results should be affected by override policy and deploy workflowstep (#4815)

* [Feature] vela dry-run render results should be affected by override policy and deploy workflowstep

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* multiple input files support; policy,workflow support; new flag: merge orphan policy or workflow

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* add more tests

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* fix comment issues

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* add tests

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* fix e2e

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

* fix tests

Signed-off-by: cezhang <c1zhang.dev@gmail.com>

Signed-off-by: cezhang <c1zhang.dev@gmail.com>
This commit is contained in:
cezhang
2023-01-11 13:52:49 +08:00
committed by GitHub
parent bee732b107
commit 1ce5c6d8ea
25 changed files with 987 additions and 174 deletions

View File

@@ -23,8 +23,6 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"
@@ -167,7 +165,6 @@ var _ = Describe("Test Live-Diff", func() {
Expect(k8sClient.Create(context.Background(), un)).Should(Succeed())
}
ctx := context.Background()
Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(Succeed())
applyFile("diff-input-app-with-externals.yaml", "default")
applyFile("diff-apprevision.yaml", "default")
app := &v1beta1.Application{}
@@ -190,7 +187,6 @@ var _ = Describe("Test Live-Diff", func() {
Expect(runDiff()).Should(ContainSubstring("\"myworker\" not found"))
applyFile("td-myingress.yaml", "vela-system")
applyFile("cd-myworker.yaml", "vela-system")
applyFile("wd-deploy.yaml", "vela-system")
applyFile("wd-ref-objects.yaml", "vela-system")
Expect(runDiff()).Should(ContainSubstring("\"deploy-livediff-demo\" not found"))
applyFile("external-workflow.yaml", "default")

View File

@@ -24,6 +24,11 @@ import (
"os"
"path/filepath"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/policy/envbinding"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/workflow/step"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -143,6 +148,7 @@ func (d *Option) ExecuteDryRun(ctx context.Context, application *v1beta1.Applica
if appFile.Namespace == "" {
appFile.Namespace = corev1.NamespaceDefault
}
comps, err := appFile.GenerateComponentManifests()
if err != nil {
return nil, nil, errors.WithMessage(err, "cannot generate manifests from components and traits")
@@ -206,3 +212,120 @@ func (d *Option) PrintDryRun(buff *bytes.Buffer, appName string, comps []*types.
}
return nil
}
// ExecuteDryRunWithPolicies is similar to ExecuteDryRun func, but considers deploy workflow step and topology+override policies
func (d *Option) ExecuteDryRunWithPolicies(ctx context.Context, application *v1beta1.Application, buff *bytes.Buffer) error {
app := application.DeepCopy()
if app.Namespace == "" {
app.Namespace = corev1.NamespaceDefault
} else {
ctx = oamutil.SetNamespaceInCtx(ctx, app.Namespace)
}
parser := appfile.NewDryRunApplicationParser(d.Client, d.DiscoveryMapper, d.PackageDiscover, d.Auxiliaries)
af, err := parser.GenerateAppFileFromApp(ctx, app)
if err != nil {
return err
}
deployWorkflowCount := 0
for _, wfs := range af.WorkflowSteps {
if wfs.Type == step.DeployWorkflowStep {
deployWorkflowCount++
deployWorkflowStepSpec := &step.DeployWorkflowStepSpec{}
if err := utils.StrictUnmarshal(wfs.Properties.Raw, deployWorkflowStepSpec); err != nil {
return err
}
topologyPolicies, overridePolicies, err := filterPolicies(af.Policies, deployWorkflowStepSpec.Policies)
if err != nil {
return err
}
if len(topologyPolicies) > 0 {
for _, tp := range topologyPolicies {
patchedApp, err := patchApp(app, overridePolicies)
if err != nil {
return err
}
comps, pms, err := d.ExecuteDryRun(ctx, patchedApp)
if err != nil {
return err
}
err = d.PrintDryRun(buff, fmt.Sprintf("%s with topology %s", patchedApp.Name, tp.Name), comps, pms)
if err != nil {
return err
}
}
} else {
patchedApp, err := patchApp(app, overridePolicies)
if err != nil {
return err
}
comps, pms, err := d.ExecuteDryRun(ctx, patchedApp)
if err != nil {
return err
}
err = d.PrintDryRun(buff, fmt.Sprintf("%s only with override policies", patchedApp.Name), comps, pms)
if err != nil {
return err
}
}
}
}
if deployWorkflowCount == 0 {
comps, pms, err := d.ExecuteDryRun(ctx, app)
if err != nil {
return err
}
err = d.PrintDryRun(buff, app.Name, comps, pms)
if err != nil {
return err
}
}
return nil
}
func filterPolicies(policies []v1beta1.AppPolicy, policyNames []string) ([]v1beta1.AppPolicy, []v1beta1.AppPolicy, error) {
policyMap := make(map[string]v1beta1.AppPolicy)
for _, policy := range policies {
policyMap[policy.Name] = policy
}
var topologyPolicies []v1beta1.AppPolicy
var overridePolicies []v1beta1.AppPolicy
for _, policyName := range policyNames {
if policy, found := policyMap[policyName]; found {
switch policy.Type {
case v1alpha1.TopologyPolicyType:
topologyPolicies = append(topologyPolicies, policy)
case v1alpha1.OverridePolicyType:
overridePolicies = append(overridePolicies, policy)
}
} else {
return nil, nil, errors.Errorf("policy %s not found", policyName)
}
}
return topologyPolicies, overridePolicies, nil
}
func patchApp(application *v1beta1.Application, overridePolicies []v1beta1.AppPolicy) (*v1beta1.Application, error) {
app := application.DeepCopy()
for _, policy := range overridePolicies {
if policy.Properties == nil {
return nil, fmt.Errorf("override policy %s must not have empty properties", policy.Name)
}
overrideSpec := &v1alpha1.OverridePolicySpec{}
if err := utils.StrictUnmarshal(policy.Properties.Raw, overrideSpec); err != nil {
return nil, errors.Wrapf(err, "failed to parse override policy %s", policy.Name)
}
overrideComps, err := envbinding.PatchComponents(app.Spec.Components, overrideSpec.Components, overrideSpec.Selector)
if err != nil {
return nil, errors.Wrapf(err, "failed to apply override policy %s", policy.Name)
}
app.Spec.Components = overrideComps
}
return app, nil
}

View File

@@ -17,8 +17,11 @@ limitations under the License.
package dryrun
import (
"bytes"
"context"
"encoding/json"
"os"
"strings"
"github.com/oam-dev/kubevela/apis/types"
@@ -59,3 +62,108 @@ var _ = Describe("Test DryRun", func() {
Expect(diff).Should(BeEmpty())
})
})
var _ = Describe("Test dry run with policies", func() {
It("Test dry run with override policy", func() {
webservice, err := os.ReadFile("../../../charts/vela-core/templates/defwithtemplate/webservice.yaml")
Expect(err).Should(BeNil())
webserviceYAML := strings.Replace(string(webservice), "{{ include \"systemDefinitionNamespace\" . }}", types.DefaultKubeVelaNS, 1)
wwd := v1beta1.ComponentDefinition{}
Expect(yaml.Unmarshal([]byte(webserviceYAML), &wwd)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &wwd)).Should(BeNil())
scaler, err := os.ReadFile("../../../charts/vela-core/templates/defwithtemplate/scaler.yaml")
Expect(err).Should(BeNil())
scalerYAML := strings.Replace(string(scaler), "{{ include \"systemDefinitionNamespace\" . }}", types.DefaultKubeVelaNS, 1)
var td v1beta1.TraitDefinition
Expect(yaml.Unmarshal([]byte(scalerYAML), &td)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &td)).Should(BeNil())
appYAML := readDataFromFile("./testdata/testing-dry-run-1.yaml")
app := &v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(appYAML), &app)).Should(BeNil())
var buff = bytes.Buffer{}
err = dryrunOpt.ExecuteDryRunWithPolicies(context.TODO(), app, &buff)
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology target-default)"))
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology target-prod)"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
Expect(buff.String()).Should(ContainSubstring("replicas: 1"))
Expect(buff.String()).Should(ContainSubstring("replicas: 3"))
Expect(buff.String()).Should(ContainSubstring("kind: Service"))
})
It("Test dry run only with override policy", func() {
appYAML := readDataFromFile("./testdata/testing-dry-run-2.yaml")
app := &v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(appYAML), &app)).Should(BeNil())
var buff = bytes.Buffer{}
err := dryrunOpt.ExecuteDryRunWithPolicies(context.TODO(), app, &buff)
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app only with override policies)"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
Expect(buff.String()).Should(ContainSubstring("replicas: 3"))
Expect(buff.String()).Should(ContainSubstring("kind: Service"))
})
It("Test dry run without deploy workflow", func() {
appYAML := readDataFromFile("./testdata/testing-dry-run-3.yaml")
app := &v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(appYAML), &app)).Should(BeNil())
var buff = bytes.Buffer{}
err := dryrunOpt.ExecuteDryRunWithPolicies(context.TODO(), app, &buff)
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app)"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
})
It("Test dry run without custom policy", func() {
topo, err := os.ReadFile("./testdata/pd-mypolicy.yaml")
Expect(err).Should(BeNil())
var pd v1beta1.PolicyDefinition
Expect(yaml.Unmarshal([]byte(topo), &pd)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &pd)).Should(BeNil())
appYAML := readDataFromFile("./testdata/testing-dry-run-4.yaml")
app := &v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(appYAML), &app)).Should(BeNil())
var buff = bytes.Buffer{}
err = dryrunOpt.ExecuteDryRunWithPolicies(context.TODO(), app, &buff)
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app) -- Component(testing-dryrun)"))
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app) -- Policy(mypolicy)"))
Expect(buff.String()).Should(ContainSubstring("name: my-policy"))
})
It("Test dry run with trait", func() {
nocalhost, err := os.ReadFile("../../../charts/vela-core/templates/defwithtemplate/nocalhost.yaml")
Expect(err).Should(BeNil())
nocalhostYAML := strings.Replace(string(nocalhost), "{{ include \"systemDefinitionNamespace\" . }}", types.DefaultKubeVelaNS, 1)
var td v1beta1.TraitDefinition
Expect(yaml.Unmarshal([]byte(nocalhostYAML), &td)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &td)).Should(BeNil())
appYAML := readDataFromFile("./testdata/testing-dry-run-5.yaml")
app := &v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(appYAML), &app)).Should(BeNil())
var buff = bytes.Buffer{}
err = dryrunOpt.ExecuteDryRunWithPolicies(context.TODO(), app, &buff)
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app) -- Component(testing-dryrun)"))
Expect(buff.String()).Should(ContainSubstring("## From the trait nocalhost"))
Expect(buff.String()).Should(ContainSubstring("trait.oam.dev/type: nocalhost"))
})
})

View File

@@ -17,11 +17,19 @@ limitations under the License.
package dryrun
import (
"context"
"os"
"path/filepath"
"testing"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -103,6 +111,16 @@ var _ = BeforeSuite(func(done Done) {
tdMyIngress, err := oamutil.Object2Unstructured(myingressDef)
Expect(err).Should(BeNil())
// create vela-system ns
Expect(k8sClient.Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: types.DefaultKubeVelaNS}})).Should(Succeed())
// create deploy workflow step definition
deploy, err := os.ReadFile("./testdata/wd-deploy.yaml")
Expect(err).Should(BeNil())
var wfsd v1beta1.WorkflowStepDefinition
Expect(yaml.Unmarshal([]byte(deploy), &wfsd)).Should(BeNil())
wfsd.SetNamespace(types.DefaultKubeVelaNS)
Expect(k8sClient.Create(context.TODO(), &wfsd)).Should(BeNil())
dryrunOpt = NewDryRunOption(k8sClient, cfg, dm, pd, []oam.Object{cdMyWorker, tdMyIngress}, false)
diffOpt = &LiveDiffOption{DryRun: dryrunOpt, Parser: appfile.NewApplicationParser(k8sClient, dm, pd)}

View File

@@ -0,0 +1,22 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/topology.cue
apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
name: mypolicy
namespace: vela-system
spec:
schematic:
cue:
template: |
output: {
apiVersion: "testing/v1"
kind: "Policy"
policy: {
name: parameter.name
}
}
parameter: {
name: string
}

View File

@@ -0,0 +1,48 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
namespace: default
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
ports:
- port: 8000
expose: true
traits:
- type: scaler
properties:
replicas: 1
policies:
- name: target-default
type: topology
properties:
clusters: [ "local" ]
namespace: "default"
- name: target-prod
type: topology
properties:
clusters: [ "local" ]
namespace: "prod"
- name: deploy-ha
type: override
properties:
components:
- type: webservice
traits:
- type: scaler
properties:
replicas: 3
workflow:
steps:
- name: deploy2default
type: deploy
properties:
policies: [ "target-default" ]
- name: deploy2prod
type: deploy
properties:
policies: [ "target-prod", "deploy-ha" ]

View File

@@ -0,0 +1,27 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
ports:
- port: 8000
expose: true
traits:
- type: scaler
properties:
replicas: 1
policies:
- name: deploy-ha
type: override
properties:
components:
- type: webservice
traits:
- type: scaler
properties:
replicas: 3

View File

@@ -0,0 +1,15 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
workflow:
steps:
- name: suspend
type: suspend

View File

@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
policies:
- name: mypolicy
type: mypolicy
properties:
name: "my-policy"

View File

@@ -0,0 +1,48 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
traits:
- type: nocalhost
properties:
port: 9080
gitUrl: https://github.com/nocalhost/bookinfo-productpage.git
image: nocalhost-docker.pkg.coding.net/nocalhost/dev-images/python:3.7.7-slim-productpage-with-pydevd
shell: "bash"
workDir: "/opt/work"
resources:
limits:
memory: 1Gi
cpu: "1"
requests:
memory: 512Mi
cpu: "0.5"
debug:
remoteDebugPort: 9009
hotReload: true
sync:
type: send
filePattern:
- ./
ignoreFilePattern:
- .git
- .idea
command:
run:
- sh
- run.sh
debug:
- sh
- debug.sh
env:
- name: "foo"
value: "bar"
portForward:
- 39080:9080

View File

@@ -20,10 +20,17 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
wfv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
"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"
@@ -45,9 +52,10 @@ import (
// DryRunCmdOptions contains dry-run cmd options
type DryRunCmdOptions struct {
cmdutil.IOStreams
ApplicationFile string
DefinitionFile string
OfflineMode bool
ApplicationFiles []string
DefinitionFile string
OfflineMode bool
MergeStandaloneFiles bool
}
// NewDryRunCommand creates `dry-run` command
@@ -62,8 +70,26 @@ func NewDryRunCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command
You can also specify a remote url for app:
vela dry-run -d /definition/directory/or/file/ -f https://remote-host/app.yaml
And more, you can specify policy and workflow with application file:
vela dry-run -d /definition/directory/or/file/ -f /path/to/app.yaml -f /path/to/policy.yaml -f /path/to/workflow.yaml, OR
vela dry-run -d /definition/directory/or/file/ -f /path/to/app.yaml,/path/to/policy.yaml,/path/to/workflow.yaml
Additionally, if the provided policy and workflow files are not referenced by application file, warning message will show up
and those files will be ignored. You can use "merge" flag to make those standalone files effective:
vela dry-run -d /definition/directory/or/file/ -f /path/to/app.yaml,/path/to/policy.yaml,/path/to/workflow.yaml --merge
Limitation:
1. Only support one object per file(yaml) for "-f" flag. More support will be added in the future improvement.
2. Dry Run with policy and workflow will only take override/topology policies and deploy workflow step into considerations. Other workflow step will be ignored.
`,
Example: `
# dry-run application
vela dry-run -f app.yaml
# dry-run application with policy and workflow
vela dry-run -f app.yaml -f policy.yaml -f workflow.yaml
`,
Example: "vela dry-run",
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
},
@@ -88,9 +114,10 @@ You can also specify a remote url for app:
},
}
cmd.Flags().StringVarP(&o.ApplicationFile, "file", "f", "./app.yaml", "application file name")
cmd.Flags().StringSliceVarP(&o.ApplicationFiles, "file", "f", []string{"app.yaml"}, "application related file names")
cmd.Flags().StringVarP(&o.DefinitionFile, "definition", "d", "", "specify a definition file or directory, it will only be used in dry-run rather than applied to K8s cluster")
cmd.Flags().BoolVar(&o.OfflineMode, "offline", false, "Run `dry-run` in offline / local mode, all validation steps will be skipped")
cmd.Flags().BoolVar(&o.MergeStandaloneFiles, "merge", false, "Merge standalone files to produce dry-run results")
addNamespaceAndEnvArg(cmd)
cmd.SetOut(ioStreams.Out)
return cmd
@@ -140,23 +167,21 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
// Perform validation only if not in offline mode
if !cmdOption.OfflineMode {
err = dryRunOpt.ValidateApp(ctx, cmdOption.ApplicationFile)
if err != nil {
return buff, errors.WithMessagef(err, "validate application: %s by dry-run", cmdOption.ApplicationFile)
for _, applicationFile := range cmdOption.ApplicationFiles {
err = dryRunOpt.ValidateApp(ctx, applicationFile)
if err != nil {
return buff, errors.WithMessagef(err, "validate application: %s by dry-run", applicationFile)
}
}
}
app, err := readApplicationFromFile(cmdOption.ApplicationFile)
app, err := readApplicationFromFiles(cmdOption, &buff)
if err != nil {
return buff, errors.WithMessagef(err, "read application file: %s", cmdOption.ApplicationFile)
return buff, errors.WithMessagef(err, "read application files: %s", cmdOption.ApplicationFiles)
}
comps, policies, err := dryRunOpt.ExecuteDryRun(ctx, app)
err = dryRunOpt.ExecuteDryRunWithPolicies(ctx, app, &buff)
if err != nil {
return buff, errors.WithMessage(err, "generate OAM objects")
}
if err = dryRunOpt.PrintDryRun(&buff, app.Name, comps, policies); err != nil {
return buff, err
}
return buff, nil
@@ -241,3 +266,140 @@ func readApplicationFromFile(filename string) (*corev1beta1.Application, error)
err = json.Unmarshal(fileContent, app)
return app, err
}
func readApplicationFromFiles(cmdOption *DryRunCmdOptions, buff *bytes.Buffer) (*corev1beta1.Application, error) {
var app *corev1beta1.Application
var policies []*v1alpha1.Policy
var wf *wfv1alpha1.Workflow
policyNameMap := make(map[string]struct{})
for _, filename := range cmdOption.ApplicationFiles {
fileContent, err := utils.ReadRemoteOrLocalPath(filename, true)
if err != nil {
return nil, err
}
fileType := filepath.Ext(filename)
switch fileType {
case ".yaml", ".yml":
// only support one object in one yaml file
fileContent, err = yaml.YAMLToJSON(fileContent)
if err != nil {
return nil, err
}
decode := scheme.Codecs.UniversalDeserializer().Decode
// cannot guarantee get the object, but gkv is enough
_, gkv, _ := decode(fileContent, nil, nil)
jsonFileContent, err := yaml.YAMLToJSON(fileContent)
if err != nil {
return nil, err
}
switch *gkv {
case corev1beta1.ApplicationKindVersionKind:
if app != nil {
return nil, errors.New("more than one applications provided")
}
app = new(corev1beta1.Application)
err = json.Unmarshal(jsonFileContent, app)
if err != nil {
return nil, err
}
case v1alpha1.PolicyGroupVersionKind:
policy := new(v1alpha1.Policy)
err = json.Unmarshal(jsonFileContent, policy)
if err != nil {
return nil, err
}
policies = append(policies, policy)
case v1alpha1.WorkflowGroupVersionKind:
if wf != nil {
return nil, errors.New("more than one external workflow provided")
}
wf = new(wfv1alpha1.Workflow)
err = json.Unmarshal(jsonFileContent, wf)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("file %s is not application, policy or workflow", filename)
}
}
}
// only allow one application
if app == nil {
return nil, errors.New("no application provided")
}
// workflow not referenced by application
if !cmdOption.MergeStandaloneFiles {
if wf != nil &&
((app.Spec.Workflow != nil && app.Spec.Workflow.Ref != wf.Name) || app.Spec.Workflow == nil) {
buff.WriteString(fmt.Sprintf("WARNING: workflow %s not referenced by application\n\n", wf.Name))
}
} else {
if wf != nil {
app.Spec.Workflow = &corev1beta1.Workflow{
Ref: "",
Steps: wf.Steps,
}
}
err := getPolicyNameFromWorkflow(wf, policyNameMap)
if err != nil {
return nil, err
}
}
for _, policy := range policies {
// check standalone policies
if _, exist := policyNameMap[policy.Name]; !exist && !cmdOption.MergeStandaloneFiles {
buff.WriteString(fmt.Sprintf("WARNING: policy %s not referenced by application\n\n", policy.Name))
continue
}
app.Spec.Policies = append(app.Spec.Policies, corev1beta1.AppPolicy{
Name: policy.Name,
Type: policy.Type,
Properties: policy.Properties,
})
}
return app, nil
}
func getPolicyNameFromWorkflow(wf *wfv1alpha1.Workflow, policyNameMap map[string]struct{}) error {
checkPolicy := func(wfsb wfv1alpha1.WorkflowStepBase, policyNameMap map[string]struct{}) error {
workflowStepSpec := &step.DeployWorkflowStepSpec{}
if err := utils.StrictUnmarshal(wfsb.Properties.Raw, workflowStepSpec); err != nil {
return err
}
for _, p := range workflowStepSpec.Policies {
policyNameMap[p] = struct{}{}
}
return nil
}
if wf == nil {
return nil
}
for _, wfs := range wf.Steps {
if wfs.Type == step.DeployWorkflowStep {
err := checkPolicy(wfs.WorkflowStepBase, policyNameMap)
if err != nil {
return err
}
for _, sub := range wfs.SubSteps {
if sub.Type == step.DeployWorkflowStep {
err = checkPolicy(sub, policyNameMap)
if err != nil {
return err
}
}
}
}
}
return nil
}

View File

@@ -17,24 +17,27 @@
package cli
import (
"bytes"
"context"
"os"
"strings"
wfv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"sigs.k8s.io/yaml"
"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"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
)
var _ = Describe("Test dry run with policy", func() {
It("Test dry run with normal policy", func() {
var _ = Describe("Testing dry-run", func() {
It("Testing dry-run", func() {
webservice, err := os.ReadFile("../../charts/vela-core/templates/defwithtemplate/webservice.yaml")
Expect(err).Should(BeNil())
webserviceYAML := strings.Replace(string(webservice), "{{ include \"systemDefinitionNamespace\" . }}", types.DefaultKubeVelaNS, 1)
@@ -42,100 +45,195 @@ var _ = Describe("Test dry run with policy", func() {
Expect(yaml.Unmarshal([]byte(webserviceYAML), &wwd)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &wwd)).Should(BeNil())
plcd := v1beta1.PolicyDefinition{}
Expect(yaml.Unmarshal([]byte(plcdef), &plcd)).Should(BeNil())
plcd.Namespace = types.DefaultKubeVelaNS
Expect(k8sClient.Create(context.TODO(), &plcd)).Should(BeNil())
app := v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(plcapp), &app)).Should(BeNil())
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
pd, err := c.GetPackageDiscover()
scaler, err := os.ReadFile("../../charts/vela-core/templates/defwithtemplate/scaler.yaml")
Expect(err).Should(BeNil())
dm, err := discoverymapper.New(cfg)
Expect(err).Should(BeNil())
dryRunOpt := dryrun.NewDryRunOption(k8sClient, cfg, dm, pd, nil, false)
comps, plcs, err := dryRunOpt.ExecuteDryRun(context.TODO(), &app)
Expect(err).Should(BeNil())
speci := plcs[0].Object["spec"].(map[string]interface{})
Expect(speci["service"].(string)).Should(BeEquivalentTo("unified"))
buff := bytes.NewBufferString("")
Expect(dryRunOpt.PrintDryRun(buff, app.Name, comps, plcs)).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring(`backends:
- service: server-v1
weight: 80
- service: server-v2
weight: 20
service: unified`))
Expect(buff.String()).Should(ContainSubstring("- image: oamdev/hello-world:v1\n name: server-v1"))
Expect(buff.String()).Should(ContainSubstring("- image: oamdev/hello-world:v2\n name: server-v2"))
})
It("Test dry run with cue component format", func() {
scalerYAML := strings.Replace(string(scaler), "{{ include \"systemDefinitionNamespace\" . }}", types.DefaultKubeVelaNS, 1)
var td v1beta1.TraitDefinition
Expect(yaml.Unmarshal([]byte(scalerYAML), &td)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &td)).Should(BeNil())
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFile: "test-data/dry-run/app.yaml", DefinitionFile: "test-data/dry-run/my-comp.cue", OfflineMode: true}
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-1.yaml"}, OfflineMode: false}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("name: hello-world"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
Expect(buff.String()).Should(ContainSubstring("name: hello-world-service"))
Expect(buff.String()).Should(ContainSubstring("kind: Service"))
Expect(buff.String()).Should(ContainSubstring("replicas: 1"))
})
It("Testing dry-run with policy", func() {
deploy, err := os.ReadFile("../../charts/vela-core/templates/defwithtemplate/deploy.yaml")
Expect(err).Should(BeNil())
deployYAML := strings.Replace(string(deploy), "{{ include \"systemDefinitionNamespace\" . }}", types.DefaultKubeVelaNS, 1)
var wfsd v1beta1.WorkflowStepDefinition
Expect(yaml.Unmarshal([]byte(deployYAML), &wfsd)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &wfsd)).Should(BeNil())
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-2.yaml"}, OfflineMode: false}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
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("replicas: 1"))
})
It("Testing dry-run with workflow", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-3.yaml"}, OfflineMode: false}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology target-default)"))
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology target-prod)"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
Expect(buff.String()).Should(ContainSubstring("replicas: 1"))
Expect(buff.String()).Should(ContainSubstring("replicas: 3"))
})
It("Testing dry-run with ref workflow", func() {
policy, err := os.ReadFile("test-data/dry-run/testing-policy.yaml")
Expect(err).Should(BeNil())
var p v1alpha1.Policy
Expect(yaml.Unmarshal([]byte(policy), &p)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &p)).Should(BeNil())
workflow, err := os.ReadFile("test-data/dry-run/testing-wf.yaml")
Expect(err).Should(BeNil())
var wf wfv1alpha1.Workflow
Expect(yaml.Unmarshal([]byte(workflow), &wf)).Should(BeNil())
Expect(k8sClient.Create(context.TODO(), &wf)).Should(BeNil())
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-4.yaml"}, OfflineMode: false}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology deploy-somewhere)"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
})
It("Testing dry-run without application provided", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-policy.yaml"}, OfflineMode: false}
_, err := DryRunApplication(&opt, c, "")
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(ContainSubstring("no application provided"))
})
It("Testing dry-run with more than one applications provided", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-1.yaml", "test-data/dry-run/testing-dry-run-2.yaml"}, OfflineMode: false}
_, err := DryRunApplication(&opt, c, "")
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(ContainSubstring("more than one applications provided"))
})
It("Testing dry-run with more than one workflow provided", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-1.yaml", "test-data/dry-run/testing-wf.yaml", "test-data/dry-run/testing-wf.yaml"}, OfflineMode: false}
_, err := DryRunApplication(&opt, c, "")
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(ContainSubstring("more than one external workflow provided"))
})
It("Testing dry-run with unexpected file", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-trait.yaml"}, OfflineMode: false}
_, err := DryRunApplication(&opt, c, "")
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(ContainSubstring("is not application, policy or workflow"))
})
It("Testing dry-run with unexpected file", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-trait.yaml"}, OfflineMode: false}
_, err := DryRunApplication(&opt, c, "")
Expect(err).ShouldNot(BeNil())
Expect(err.Error()).Should(ContainSubstring("is not application, policy or workflow"))
})
It("Testing dry-run merging with external workflow and policy", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-5.yaml", "test-data/dry-run/testing-wf.yaml", "test-data/dry-run/testing-policy.yaml"}, OfflineMode: false, MergeStandaloneFiles: true}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app with topology deploy-somewhere)"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
})
It("Testing dry-run with standalone policy", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-5.yaml", "test-data/dry-run/testing-policy.yaml"}, OfflineMode: false, MergeStandaloneFiles: false}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("WARNING: policy deploy-somewhere not referenced by application"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
})
It("Testing dry-run with standalone workflow", func() {
c := common2.Args{}
c.SetConfig(cfg)
c.SetClient(k8sClient)
opt := DryRunCmdOptions{ApplicationFiles: []string{"test-data/dry-run/testing-dry-run-5.yaml", "test-data/dry-run/testing-wf.yaml"}, OfflineMode: false, MergeStandaloneFiles: false}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("WARNING: workflow testing-wf not referenced by application"))
Expect(buff.String()).Should(ContainSubstring("name: testing-dryrun"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
})
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())
Expect(buff.String()).Should(ContainSubstring("# Application(testing-app)"))
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"))
})
})
var plcapp = `apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: my-test-2
spec:
components:
- name: server-v1
type: webservice
properties:
image: oamdev/hello-world:v1
- name: server-v2
type: webservice
properties:
image: oamdev/hello-world:v2
policies:
- type: my-plc
name: unified
properties:
weights:
- service: server-v1
weight: 80
- service: server-v2
weight: 20
`
var plcdef = `apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
annotations:
definition.oam.dev/description: My ingress route policy.
name: my-plc
spec:
schematic:
cue:
template: |
#ServerWeight: {
service: string
weight: int
}
parameter: weights: [...#ServerWeight]
output: {
apiVersion: "split.smi-spec.io/v1alpha3"
kind: "TrafficSplit"
metadata: name: context.name
spec: {
service: context.name
backends: parameter.weights
}
}`

View File

@@ -37,7 +37,9 @@ import (
// LiveDiffCmdOptions contains the live-diff cmd options
type LiveDiffCmdOptions struct {
DryRunCmdOptions
cmdutil.IOStreams
ApplicationFile string
DefinitionFile string
AppName string
Namespace string
Revision string
@@ -47,10 +49,8 @@ type LiveDiffCmdOptions struct {
// NewLiveDiffCommand creates `live-diff` command
func NewLiveDiffCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
o := &LiveDiffCmdOptions{
DryRunCmdOptions: DryRunCmdOptions{
IOStreams: ioStreams,
}}
o := &LiveDiffCmdOptions{IOStreams: ioStreams}
cmd := &cobra.Command{
Use: "live-diff",
DisableFlagsInUseLine: true,

View File

@@ -1,8 +0,0 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: vela-app
spec:
components:
- name: express-server
type: my-comp

View File

@@ -1,50 +0,0 @@
"my-comp": {
annotations: {}
attributes: workload: definition: {
apiVersion: "apps/v1"
kind: "Deployment"
}
description: "My component."
labels: {}
type: "component"
}
template: {
output: {
metadata: name: "hello-world"
spec: {
replicas: 1
selector: matchLabels: "app.kubernetes.io/name": "hello-world"
template: {
metadata: labels: "app.kubernetes.io/name": "hello-world"
spec: containers: [{
name: "hello-world"
image: "somefive/hello-world"
ports: [{
name: "http"
containerPort: 80
protocol: "TCP"
}]
}]
}
}
apiVersion: "apps/v1"
kind: "Deployment"
}
outputs: "hello-world-service": {
metadata: name: "hello-world-service"
spec: {
ports: [{
name: "http"
protocol: "TCP"
port: 80
targetPort: 8080
}]
selector: app: "hello-world"
type: "LoadBalancer"
}
apiVersion: "v1"
kind: "Service"
}
parameter: {}
}

View File

@@ -0,0 +1,16 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
traits:
- type: scaler
properties:
replicas: 1

View File

@@ -0,0 +1,20 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
traits:
- type: scaler
properties:
replicas: 1
policies:
- name: target-default
type: topology
properties:
clusters: [ "local" ]
namespace: "default"

View File

@@ -0,0 +1,44 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
traits:
- type: scaler
properties:
replicas: 1
policies:
- name: target-default
type: topology
properties:
clusters: [ "local" ]
namespace: "default"
- name: target-prod
type: topology
properties:
clusters: [ "local" ]
namespace: "prod"
- name: deploy-ha
type: override
properties:
components:
- type: webservice
traits:
- type: scaler
properties:
replicas: 3
workflow:
steps:
- name: deploy2default
type: deploy
properties:
policies: [ "target-default" ]
- name: deploy2prod
type: deploy
properties:
policies: [ "target-prod", "deploy-ha" ]

View File

@@ -0,0 +1,12 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1
workflow:
ref: testing-wf

View File

@@ -0,0 +1,11 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: webservice
properties:
image: oamdev/hello-world:v1

View File

@@ -0,0 +1,12 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: testing-app
spec:
components:
- name: testing-dryrun
type: myworker
properties:
image: oamdev/hello-world:v1

View File

@@ -0,0 +1,8 @@
apiVersion: core.oam.dev/v1alpha1
kind: Policy
metadata:
name: deploy-somewhere
namespace: default
type: topology
properties:
clusters: ["anywhere"]

View File

@@ -0,0 +1,8 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: bars.example.com
namespace: default
spec:
definitionRef:
name: bars.example.com

View File

@@ -0,0 +1,10 @@
apiVersion: core.oam.dev/v1alpha1
kind: Workflow
metadata:
name: testing-wf
namespace: default
steps:
- type: deploy
name: deploy-somewhere
properties:
policies: ["deploy-somewhere"]

View File

@@ -0,0 +1,49 @@
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: myworker
spec:
workload:
definition:
apiVersion: apps/v1
kind: Deployment
schematic:
cue:
template: |
output: {
apiVersion: "apps/v1"
kind: "Deployment"
}
output: {
spec: {
selector: matchLabels: {
"app.oam.dev/component": context.name
}
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
}]
}
}
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Commands to run in the container
cmd?: [...string]
}