Compare commits

...

8 Commits

Author SHA1 Message Date
github-actions[bot]
b1cc06b0f3 Feat: support dry-run with cue format definition (#5080)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
(cherry picked from commit ce75a33633)

Co-authored-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-11-16 18:11:58 +08:00
github-actions[bot]
ed9d53b448 Feat: add print message example (#5079)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit ee2b854c80)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-11-16 16:38:06 +08:00
github-actions[bot]
ad83e59865 [Backport release-1.6] Feat: add apply component definition for docs (#5076)
* Feat: add apply component definition for docs

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit d31dbecb76)

* Feat: add apply component definition for docs

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 454edb05ff)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-11-16 15:45:49 +08:00
github-actions[bot]
b62eeca3f9 [Backport release-1.6] Fix: code vulnerability (#5075)
* Fix: code vulnerability

Signed-off-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit d47019de35)

* lint

Signed-off-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 7a51a1f22a)

* imports

Signed-off-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 8040fe63ce)

* use space

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 80d16b480c)

* reuse sanitize function

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 75695440b1)

Co-authored-by: qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-11-16 15:45:25 +08:00
github-actions[bot]
5d9757fcb8 Feat: support vela up --wait and --timeout (#5074)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
(cherry picked from commit f81f26f66b)

Co-authored-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-11-16 10:01:14 +08:00
github-actions[bot]
4d653951a1 add tests (#5068)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 7080d7ae31)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-11-15 13:17:06 +08:00
github-actions[bot]
bcda4976a9 [Backport release-1.6] Fix: Failed to get detail policy for application (#5049)
* Fix: Failed to get detail policy for application

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
(cherry picked from commit 50f63bf8bc)

* Fix: Failed to get detail policy for application

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
(cherry picked from commit 8c70f067fc)

Co-authored-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
2022-11-10 21:50:22 +08:00
github-actions[bot]
a01d0e773a Fix: add debug for workflowrun and support debug sub steps (#5042)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 5749babe71)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-11-10 11:04:47 +08:00
48 changed files with 709 additions and 233 deletions

View File

@@ -189,8 +189,6 @@ type Status struct {
type ApplicationPhase string
const (
// ApplicationRollingOut means the app is in the middle of rolling out
ApplicationRollingOut ApplicationPhase = "rollingOut"
// ApplicationStarting means the app is preparing for reconcile
ApplicationStarting ApplicationPhase = "starting"
// ApplicationRendering means the app is rendering
@@ -205,8 +203,6 @@ const (
ApplicationWorkflowTerminated ApplicationPhase = "workflowTerminated"
// ApplicationWorkflowFailed means the app's workflow is failed
ApplicationWorkflowFailed ApplicationPhase = "workflowFailed"
// ApplicationWorkflowFinished means the app's workflow is finished
ApplicationWorkflowFinished ApplicationPhase = "workflowFinished"
// ApplicationRunning means the app finished rendering and applied result to the cluster
ApplicationRunning ApplicationPhase = "running"
// ApplicationUnhealthy means the app finished rendering and applied result to the cluster, but still unhealthy

View File

@@ -0,0 +1,23 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/apply-component.cue
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Apply a specific component and its corresponding traits in application
labels:
custom.definition.oam.dev/scope: Application
custom.definition.oam.dev/ui-hidden: "true"
name: apply-component
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
parameter: {
// +usage=Specify the component name to apply
component: string
// +usage=Specify the cluster
cluster: *"" | string
}

View File

@@ -4,7 +4,7 @@ apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: pring message in workflow status
definition.oam.dev/description: print message in workflow step status
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: print-message-in-status

View File

@@ -0,0 +1,23 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/apply-component.cue
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Apply a specific component and its corresponding traits in application
labels:
custom.definition.oam.dev/scope: Application
custom.definition.oam.dev/ui-hidden: "true"
name: apply-component
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
parameter: {
// +usage=Specify the component name to apply
component: string
// +usage=Specify the cluster
cluster: *"" | string
}

View File

@@ -4,7 +4,7 @@ apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: pring message in workflow status
definition.oam.dev/description: print message in workflow step status
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: print-message-in-status

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -34,4 +34,30 @@ entries:
urls:
- http://127.0.0.1:9098/helm/vela-workflow-v0.3.1.tgz
version: v0.3.1
foo:
- created: "2022-10-29T09:11:16.865230605Z"
description: Vela test addon named foo
home: https://www.foo.com/icon
icon: https://www.foo.com
name: foo
urls:
- http://127.0.0.1:9098/helm/foo-v1.0.0.tgz
version: v1.0.0
bar:
- created: "2022-10-29T09:11:16.865230605Z"
description: Vela test addon named bar
home: https://www.bar.com/icon
icon: https://www.bar.com
name: foo
urls:
- http://127.0.0.1:9098/helm/bar-v1.0.0.tgz
version: v1.0.0
- created: "2022-10-29T09:11:16.865230605Z"
description: Vela test addon named bar
home: https://www.bar.com/icon
icon: https://www.bar.com
name: foo
urls:
- http://127.0.0.1:9098/helm/bar-v2.0.0.tgz
version: v2.0.0
generated: "2022-06-15T13:17:04.733573+08:00"

View File

@@ -131,6 +131,24 @@ var helmHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Reques
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
case strings.Contains(req.URL.Path, "foo-v1.0.0.tgz"):
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz")
if err != nil {
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
case strings.Contains(req.URL.Path, "bar-v1.0.0.tgz"):
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz")
if err != nil {
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
case strings.Contains(req.URL.Path, "bar-v2.0.0.tgz"):
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz")
if err != nil {
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
}
}

View File

@@ -47,6 +47,9 @@ var (
appbasicJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
appbasicAddTraitJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}],"scaler":{"replicas":2}}}}`
velaQL = "test-component-pod-view{appNs=default,appName=nginx-vela,name=nginx}"
waitAppfileToSuccess = `{"name":"app-wait-success","services":{"app-basic1":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
waitAppfileToFail = `{"name":"app-wait-fail","services":{"app-basic2":{"type":"webservice","image":"nginx:fail","ports":[{port: 80, expose: true}]}}}`
)
var _ = ginkgo.Describe("Test Vela Application", func() {
@@ -75,6 +78,9 @@ var _ = ginkgo.Describe("Test Vela Application", func() {
e2e.JsonAppFileContext("json appfile apply", testDeleteJsonAppFile)
VelaQLPodListContext("ql", velaQL)
e2e.JsonAppFileContextWithWait("json appfile apply with wait", waitAppfileToSuccess)
e2e.JsonAppFileContextWithTimeout("json appfile apply with wait but timeout", waitAppfileToFail, "3s")
})
var ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
@@ -182,7 +188,7 @@ var ApplicationInitIntercativeCliContext = func(context string, appName string,
c.ExpectEOF()
})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("Checking Status"))
gomega.Expect(output).To(gomega.ContainSubstring("Waiting app to be healthy"))
})
})
}

View File

@@ -84,6 +84,30 @@ var (
})
}
JsonAppFileContextWithWait = func(context, jsonAppFile string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("Start the application through the app file in JSON format.", func() {
writeStatus := os.WriteFile("vela.json", []byte(jsonAppFile), 0644)
gomega.Expect(writeStatus).NotTo(gomega.HaveOccurred())
output, err := Exec("vela up -f vela.json --wait")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("Application Deployed Successfully!"))
})
})
}
JsonAppFileContextWithTimeout = func(context, jsonAppFile, duration string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("Start the application through the app file in JSON format.", func() {
writeStatus := os.WriteFile("vela.json", []byte(jsonAppFile), 0644)
gomega.Expect(writeStatus).NotTo(gomega.HaveOccurred())
output, err := Exec("vela up -f vela.json --wait --timeout=" + duration)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("Timeout waiting Application to be healthy!"))
})
})
}
DeleteEnvFunc = func(context string, envName string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should print env does not exist message", func() {

2
go.mod
View File

@@ -58,7 +58,7 @@ require (
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f
github.com/kubevela/workflow v0.3.2
github.com/kubevela/workflow v0.3.3
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/mitchellh/hashstructure/v2 v2.0.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd

4
go.sum
View File

@@ -1335,8 +1335,8 @@ github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2 h1:C3cAfrxst1+dIWgLLh
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2/go.mod h1:TgIGEB/r0NOy63Jzem7WsL3AIr34l+ClH9dmPqcZ4d4=
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f h1:1lUtU1alPThdcsn4MI6XjPb7eJLuZPpmlEdgjtnUMKw=
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f/go.mod h1:m724/7ANnB/iukyHW20+DicpeJMEC/JA0ZhgsHY10MA=
github.com/kubevela/workflow v0.3.2 h1:r9jznJN5Tzwkg002qiHduhSD/iDwX+F+lG72yj2B5Eo=
github.com/kubevela/workflow v0.3.2/go.mod h1:5jfZ8T1m/En44wDGRf2YqCSlODfEnAV+9PnzoLoDlFs=
github.com/kubevela/workflow v0.3.3 h1:NSbQGcABWJIzUV5wfWFJsrO/ffZ4mCVfofUtUHCTojQ=
github.com/kubevela/workflow v0.3.3/go.mod h1:5jfZ8T1m/En44wDGRf2YqCSlODfEnAV+9PnzoLoDlFs=
github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=

View File

@@ -1043,7 +1043,7 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
continue
}
// always install addon's latest version
depAddon, err := h.loadInstallPackage(dep.Name, "")
depAddon, err := h.loadInstallPackage(dep.Name, dep.Version)
if err != nil {
return err
}

View File

@@ -105,7 +105,8 @@ type DeployTo struct {
// Dependency defines the other addons it depends on
type Dependency struct {
Name string `json:"name,omitempty"`
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
}
// ElementFile can be addon's definition or addon's component

View File

@@ -23,6 +23,7 @@ import (
"sort"
"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/repo"
@@ -146,7 +147,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
sort.Sort(sort.Reverse(versions))
addonVersion, availableVersions := chooseVersion(version, versions)
if addonVersion == nil {
return nil, fmt.Errorf("specified version %s not exist", version)
return nil, errors.Errorf("specified version %s not exist", utils.Sanitize(version))
}
for _, chartURL := range addonVersion.URLs {
if !utils.IsValidURL(chartURL) {

View File

@@ -247,6 +247,9 @@ var RevisionStatusTerminated = "terminated"
// RevisionStatusRollback event status rollback
var RevisionStatusRollback = "rollback"
// WorkflowStepPhaseStopped is the stopped phase
var WorkflowStepPhaseStopped workflowv1alpha1.WorkflowStepPhase = "stopped"
// ApplicationRevision be created when an application initiates deployment and describes the phased version of the application.
type ApplicationRevision struct {
BaseModel

View File

@@ -24,6 +24,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
"github.com/oam-dev/kubevela/pkg/utils"
)
// ListApplicationPolicies query the application policies
@@ -83,7 +84,7 @@ func ListApplicationCommonPolicies(ctx context.Context, store datastore.DataStor
// DeleteApplicationEnvPolicies delete the policies via app name and env name
func DeleteApplicationEnvPolicies(ctx context.Context, store datastore.DataStore, app *model.Application, envName string) error {
log.Logger.Debugf("clear the policies via app name %s and env name %s", app.PrimaryKey(), envName)
log.Logger.Debugf("clear the policies via app name %s and env name %s", app.PrimaryKey(), utils.Sanitize(envName))
policies, err := ListApplicationEnvPolicies(ctx, store, app, envName)
if err != nil {
return err

View File

@@ -210,7 +210,7 @@ func (p pipelineServiceImpl) ListPipelines(ctx context.Context, req apis.ListPip
info, err = p.getPipelineInfo(ctx, pipeline, projectMap[pipeline.Project].Namespace)
if err != nil {
// Since we are listing pipelines. We should not return directly if we cannot get pipeline info
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pipeline.Name, err)
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pkgutils.Sanitize(pipeline.Name), err)
continue
}
}
@@ -248,7 +248,7 @@ func (p pipelineServiceImpl) GetPipeline(ctx context.Context, name string, getIn
if getInfo {
in, err := p.getPipelineInfo(ctx, pipeline, project.Namespace)
if err != nil {
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pipeline.Name, err)
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pkgutils.Sanitize(pipeline.Name), err)
return nil, bcode.ErrGetPipelineInfo
}
if in != nil {
@@ -1031,7 +1031,7 @@ func (c contextServiceImpl) CreateContext(ctx context.Context, projectName, pipe
}
}
if _, ok := modelCtx.Contexts[context.Name]; ok {
log.Logger.Errorf("context %s already exists", context.Name)
log.Logger.Errorf("context %s already exists", pkgutils.Sanitize(context.Name))
return nil, bcode.ErrContextAlreadyExist
}
modelCtx.Contexts[context.Name] = context.Values

View File

@@ -52,6 +52,9 @@ func guaranteePolicyNotExist(c []string, policy string) ([]string, bool) {
// extractPolicyListAndProperty can extract policy from string-format properties, and return
// map-format properties in order to further update operation.
func extractPolicyListAndProperty(property string) ([]string, map[string]interface{}, error) {
if len(property) == 0 {
return nil, nil, nil
}
content := map[string]interface{}{}
err := json.Unmarshal([]byte(property), &content)
if err != nil {

View File

@@ -208,6 +208,14 @@ func TestExtractPolicyListAndProperty(t *testing.T) {
noError bool
}{noError: false},
},
{
input: ``,
res: struct {
policies []string
properties map[string]interface{}
noError bool
}{policies: nil, properties: nil, noError: true},
},
}
for _, testCase := range testCases {
policy, properties, err := extractPolicyListAndProperty(testCase.input)

View File

@@ -623,7 +623,7 @@ func resetRevisionsAndRecords(ctx context.Context, ds datastore.DataStore, appNa
record.Finished = "true"
for i, step := range record.Steps {
if step.Phase == workflowv1alpha1.WorkflowStepPhaseRunning {
record.Steps[i].Phase = workflowv1alpha1.WorkflowStepPhaseStopped
record.Steps[i].Phase = model.WorkflowStepPhaseStopped
}
}
if err := ds.Put(ctx, record); err != nil {

View File

@@ -586,7 +586,7 @@ var _ = Describe("Test workflow service functions", func() {
Expect(err).Should(BeNil())
Expect(record.Status).Should(Equal(model.RevisionStatusTerminated))
Expect(record.Finished).Should(Equal("true"))
Expect(record.Steps[1].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseStopped))
Expect(record.Steps[1].Phase).Should(Equal(model.WorkflowStepPhaseStopped))
})
It("Test deleting workflow", func() {

View File

@@ -3757,11 +3757,11 @@ var _ = Describe("Test Application Controller", func() {
By("Check debug Config Map is created")
debugCM := &corev1.ConfigMap{}
Expect(k8sClient.Get(ctx, types.NamespacedName{
Name: debug.GenerateContextName(app.Name, "step1"),
Name: debug.GenerateContextName(app.Name, "step1", string(app.UID)),
Namespace: "default",
}, debugCM)).Should(BeNil())
Expect(k8sClient.Get(ctx, types.NamespacedName{
Name: debug.GenerateContextName(app.Name, "step2-sub1"),
Name: debug.GenerateContextName(app.Name, "step2-sub1", string(app.UID)),
Namespace: "default",
}, debugCM)).Should(BeNil())
@@ -3770,7 +3770,7 @@ var _ = Describe("Test Application Controller", func() {
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
updatedCM := &corev1.ConfigMap{}
Expect(k8sClient.Get(ctx, types.NamespacedName{
Name: debug.GenerateContextName(app.Name, "step1"),
Name: debug.GenerateContextName(app.Name, "step1", string(app.UID)),
Namespace: "default",
}, updatedCM)).Should(BeNil())

View File

@@ -162,16 +162,13 @@ func needRestart(app *v1beta1.Application, revName string) (string, bool) {
}
func generateWorkflowInstance(af *appfile.Appfile, app *v1beta1.Application, appRev string) *wfTypes.WorkflowInstance {
anno := make(map[string]string)
if af.Debug {
anno[wfTypes.AnnotationWorkflowRunDebug] = "true"
}
instance := &wfTypes.WorkflowInstance{
WorkflowMeta: wfTypes.WorkflowMeta{
Name: af.Name,
Namespace: af.Namespace,
Annotations: app.Annotations,
Labels: app.Labels,
UID: app.UID,
ChildOwnerReferences: []metav1.OwnerReference{
{
APIVersion: v1beta1.SchemeGroupVersion.String(),
@@ -237,6 +234,7 @@ func generateWorkflowInstance(af *appfile.Appfile, app *v1beta1.Application, app
func convertStepProperties(step *workflowv1alpha1.WorkflowStep, app *v1beta1.Application) error {
o := struct {
Component string `json:"component"`
Cluster string `json:"cluster"`
}{}
js, err := common.RawExtensionPointer{RawExtension: step.Properties}.MarshalJSON()
if err != nil {
@@ -262,6 +260,9 @@ func convertStepProperties(step *workflowv1alpha1.WorkflowStep, app *v1beta1.App
if parameterKey != "" && !strings.HasPrefix(parameterKey, "properties") && !strings.HasPrefix(parameterKey, "traits[") {
parameterKey = "properties." + parameterKey
}
if parameterKey != "" {
parameterKey = "value." + parameterKey
}
step.Inputs[index].ParameterKey = parameterKey
}
step.Outputs = append(step.Outputs, c.Outputs...)
@@ -269,7 +270,11 @@ func convertStepProperties(step *workflowv1alpha1.WorkflowStep, app *v1beta1.App
c.Inputs = nil
c.Outputs = nil
c.DependsOn = nil
step.Properties = util.Object2RawExtension(c)
stepProperties := map[string]interface{}{
"value": c,
"cluster": o.Cluster,
}
step.Properties = util.Object2RawExtension(stepProperties)
return nil
}
}

View File

@@ -5,7 +5,8 @@ import (
oam: op.oam
// apply component and traits
apply: oam.#ApplyComponent & {
value: parameter
value: parameter.value
cluster: parameter.cluster
}
if apply.output != _|_ {
@@ -15,4 +16,7 @@ if apply.output != _|_ {
if apply.outputs != _|_ {
outputs: apply.outputs
}
parameter: {...}
parameter: {
value: {...}
cluster: *"" | string
}

View File

@@ -38,21 +38,22 @@ import (
"github.com/kubevela/workflow/pkg/cue/packages"
"github.com/kubevela/workflow/pkg/debug"
"github.com/kubevela/workflow/pkg/tasks/custom"
wfTypes "github.com/kubevela/workflow/pkg/types"
"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"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/appfile"
)
type debugOpts struct {
step string
focus string
errMsg string
opts []string
errMap map[string]string
// TODO: (fog) add watch flag
// watch bool
}
@@ -61,25 +62,32 @@ type debugOpts struct {
func NewDebugCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
dOpts := &debugOpts{}
wargs := &WorkflowArgs{
Args: c,
Writer: ioStreams.Out,
}
cmd := &cobra.Command{
Use: "debug",
Aliases: []string{"debug"},
Short: "Debug running application",
Long: "Debug running application with debug policy.",
Example: `vela debug <application-name>`,
PreRun: wargs.checkDebugMode(),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify application name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return err
if wargs.Type == instanceTypeWorkflowRun {
return fmt.Errorf("please use `vela workflow debug <name>` instead")
}
return dOpts.debugApplication(ctx, c, app, ioStreams)
if wargs.App == nil {
return fmt.Errorf("application %s not found", args[0])
}
return dOpts.debugApplication(ctx, wargs, c, ioStreams)
},
}
addNamespaceAndEnvArg(cmd)
@@ -88,7 +96,8 @@ func NewDebugCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command
return cmd
}
func (d *debugOpts) debugApplication(ctx context.Context, c common.Args, app *v1beta1.Application, ioStreams cmdutil.IOStreams) error {
func (d *debugOpts) debugApplication(ctx context.Context, wargs *WorkflowArgs, c common.Args, ioStreams cmdutil.IOStreams) error {
app := wargs.App
cli, err := c.GetClient()
if err != nil {
return err
@@ -101,60 +110,64 @@ func (d *debugOpts) debugApplication(ctx context.Context, c common.Args, app *v1
if err != nil {
return err
}
d.opts = wargs.getWorkflowSteps()
d.errMap = wargs.ErrMap
if app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0 {
return d.debugWorkflow(ctx, wargs, cli, pd, ioStreams)
}
s, opts, errMap := d.getDebugOptions(app)
if s == "workflow steps" {
if d.step == "" {
prompt := &survey.Select{
Message: fmt.Sprintf("Select the %s to debug:", s),
Options: opts,
}
var step string
err := survey.AskOne(prompt, &step, survey.WithValidator(survey.Required))
if err != nil {
return fmt.Errorf("failed to select %s: %w", s, err)
}
d.step = unwrapStepName(step)
d.errMsg = errMap[d.step]
}
// debug workflow steps
rawValue, data, err := d.getDebugRawValue(ctx, cli, pd, app)
if err != nil {
if data != "" {
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
return nil
}
return err
}
if err := d.handleCueSteps(rawValue, ioStreams); err != nil {
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
return nil
}
} else {
// dry run components
dm, err := discoverymapper.New(config)
if err != nil {
return err
}
dryRunOpt := dryrun.NewDryRunOption(cli, config, dm, pd, []oam.Object{}, false)
comps, _, err := dryRunOpt.ExecuteDryRun(ctx, app)
if err != nil {
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
return nil
}
if err := d.debugComponents(opts, comps, ioStreams); err != nil {
return err
}
dm, err := discoverymapper.New(config)
if err != nil {
return err
}
dryRunOpt := dryrun.NewDryRunOption(cli, config, dm, pd, []oam.Object{}, false)
comps, _, err := dryRunOpt.ExecuteDryRun(ctx, app)
if err != nil {
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
return nil
}
if err := d.debugComponents(comps, ioStreams); err != nil {
return err
}
return nil
}
func (d *debugOpts) debugComponents(compList []string, comps []*types.ComponentManifest, ioStreams cmdutil.IOStreams) error {
opts := compList
func (d *debugOpts) debugWorkflow(ctx context.Context, wargs *WorkflowArgs, cli client.Client, pd *packages.PackageDiscover, ioStreams cmdutil.IOStreams) error {
if d.step == "" {
prompt := &survey.Select{
Message: "Select the workflow step to debug:",
Options: d.opts,
}
var step string
err := survey.AskOne(prompt, &step, survey.WithValidator(survey.Required))
if err != nil {
return fmt.Errorf("failed to select workflow step: %w", err)
}
d.step = unwrapStepName(step)
d.errMsg = d.errMap[d.step]
}
// debug workflow steps
rawValue, data, err := d.getDebugRawValue(ctx, cli, pd, wargs.WorkflowInstance)
if err != nil {
if data != "" {
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
return nil
}
return err
}
if err := d.handleCueSteps(rawValue, ioStreams); err != nil {
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
return nil
}
return nil
}
func (d *debugOpts) debugComponents(comps []*types.ComponentManifest, ioStreams cmdutil.IOStreams) error {
opts := d.opts
all := color.YellowString("all fields")
exit := color.CyanString("exit debug mode")
opts = append(opts, all, exit)
@@ -184,7 +197,7 @@ func (d *debugOpts) debugComponents(compList []string, comps []*types.ComponentM
break
}
if step == all {
for _, step := range compList {
for _, step := range d.opts {
step = unwrapStepName(step)
if err := renderComponents(step, components[step], traits[step], ioStreams); err != nil {
return err
@@ -217,34 +230,6 @@ func renderComponents(compName string, comp *unstructured.Unstructured, traits [
return nil
}
func (d *debugOpts) getDebugOptions(app *v1beta1.Application) (string, []string, map[string]string) {
s := "components"
stepList := make([]string, 0)
if app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0 {
s = "workflow steps"
}
errMap := make(map[string]string)
switch {
case app.Status.Workflow != nil:
for _, step := range app.Status.Workflow.Steps {
stepName := wrapStepName(step.StepStatus)
if strings.HasPrefix(stepName, emojiFail) {
errMap[step.Name] = step.Message
}
stepList = append(stepList, stepName)
}
case app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0:
for _, step := range app.Spec.Workflow.Steps {
stepList = append(stepList, step.Name)
}
default:
for _, c := range app.Spec.Components {
stepList = append(stepList, c.Name)
}
}
return s, stepList, errMap
}
func wrapStepName(step workflowv1alpha1.StepStatus) string {
var stepName string
switch step.Phase {
@@ -276,10 +261,20 @@ func unwrapStepName(step string) string {
}
}
func (d *debugOpts) getDebugRawValue(ctx context.Context, cli client.Client, pd *packages.PackageDiscover, app *v1beta1.Application) (*value.Value, string, error) {
func (d *debugOpts) getDebugRawValue(ctx context.Context, cli client.Client, pd *packages.PackageDiscover, instance *wfTypes.WorkflowInstance) (*value.Value, string, error) {
debugCM := &corev1.ConfigMap{}
if err := cli.Get(ctx, client.ObjectKey{Name: debug.GenerateContextName(app.Name, d.step), Namespace: app.Namespace}, debugCM); err != nil {
return nil, "", fmt.Errorf("failed to get debug configmap, please make sure your application have the debug policy, you can add the debug policy by using `vela up -f <app.yaml> --debug`: %w", err)
if err := cli.Get(ctx, client.ObjectKey{Name: debug.GenerateContextName(instance.Name, d.step, string(instance.UID)), Namespace: instance.Namespace}, debugCM); err != nil {
for _, step := range instance.Status.Steps {
if step.Name == d.step && (step.Type == wfTypes.WorkflowStepTypeSuspend || step.Type == wfTypes.WorkflowStepTypeStepGroup) {
return nil, "", fmt.Errorf("no debug data for a suspend or step-group step, please choose another step")
}
for _, sub := range step.SubStepsStatus {
if sub.Name == d.step && sub.Type == wfTypes.WorkflowStepTypeSuspend {
return nil, "", fmt.Errorf("no debug data for a suspend step, please choose another step")
}
}
}
return nil, "", fmt.Errorf("failed to get debug configmap, please make sure the you're in the debug mode`: %w", err)
}
if debugCM.Data == nil || debugCM.Data["debug"] == "" {

View File

@@ -49,8 +49,10 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
Name: "no-debug-config-map",
Namespace: "default",
},
Spec: workflowSpec,
Status: common.AppStatus{},
Spec: workflowSpec,
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{},
},
},
step: "test-wf1",
focus: "test",
@@ -61,13 +63,16 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "config-map-no-data",
Namespace: "default",
UID: "12345",
},
Spec: workflowSpec,
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{},
},
Spec: workflowSpec,
Status: common.AppStatus{},
},
cm: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "config-map-no-data-test-wf1-debug",
Name: "config-map-no-data-test-wf1-debug-12345",
Namespace: "default",
},
},
@@ -80,13 +85,16 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "config-map-error-data",
Namespace: "default",
UID: "12345",
},
Spec: workflowSpec,
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{},
},
Spec: workflowSpec,
Status: common.AppStatus{},
},
cm: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "config-map-error-data-test-wf1-debug",
Name: "config-map-error-data-test-wf1-debug-12345",
Namespace: "default",
},
Data: map[string]string{
@@ -100,13 +108,16 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "success",
Namespace: "default",
UID: "12345",
},
Spec: workflowSpec,
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{},
},
Spec: workflowSpec,
Status: common.AppStatus{},
},
cm: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "success-test-wf1-debug",
Name: "success-test-wf1-debug-12345",
Namespace: "default",
},
Data: map[string]string{
@@ -131,7 +142,9 @@ test: test
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
}},
},
Status: common.AppStatus{},
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{},
},
},
step: "test-component",
},
@@ -140,7 +153,6 @@ test: test
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
r := require.New(t)
d := &debugOpts{
step: tc.step,
focus: tc.focus,
@@ -151,7 +163,14 @@ test: test
err := client.Create(ctx, tc.cm)
r.NoError(err)
}
err = d.debugApplication(ctx, c, tc.app, ioStream)
wargs := &WorkflowArgs{
Args: c,
Type: instanceTypeApplication,
App: tc.app,
}
err = wargs.generateWorkflowInstance(ctx, client)
r.NoError(err)
err = d.debugApplication(ctx, wargs, c, ioStream)
if tc.expectedErr != "" {
r.Contains(err.Error(), tc.expectedErr)
return

View File

@@ -22,6 +22,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -32,6 +33,7 @@ import (
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"
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
@@ -101,7 +103,7 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
objs := []oam.Object{}
if cmdOption.DefinitionFile != "" {
objs, err = ReadObjectsFromFile(cmdOption.DefinitionFile)
objs, err = ReadDefinitionsFromFile(cmdOption.DefinitionFile)
if err != nil {
return buff, err
}
@@ -160,15 +162,37 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
return buff, nil
}
// ReadObjectsFromFile will read objects from file or dir in the format of yaml
func ReadObjectsFromFile(path string) ([]oam.Object, error) {
func readObj(path string) (oam.Object, error) {
switch {
case strings.HasSuffix(path, ".cue"):
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
defBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
return nil, err
}
if err := def.FromCUEString(string(defBytes), nil); err != nil {
return nil, errors.Wrapf(err, "failed to parse CUE for definition")
}
obj := &unstructured.Unstructured{Object: def.UnstructuredContent()}
return obj, nil
default:
obj := &unstructured.Unstructured{}
err := common.ReadYamlToObject(path, obj)
if err != nil {
return nil, err
}
return obj, nil
}
}
// ReadDefinitionsFromFile will read objects from file or dir in the format of yaml
func ReadDefinitionsFromFile(path string) ([]oam.Object, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
if !fi.IsDir() {
obj := &unstructured.Unstructured{}
err = common.ReadYamlToObject(path, obj)
obj, err := readObj(path)
if err != nil {
return nil, err
}
@@ -186,11 +210,10 @@ func ReadObjectsFromFile(path string) ([]oam.Object, error) {
continue
}
fileType := filepath.Ext(fi.Name())
if fileType != ".yaml" && fileType != ".yml" {
if fileType != ".yaml" && fileType != ".yml" && fileType != ".cue" {
continue
}
obj := &unstructured.Unstructured{}
err = common.ReadYamlToObject(filepath.Join(path, fi.Name()), obj)
obj, err := readObj(filepath.Join(path, fi.Name()))
if err != nil {
return nil, err
}

View File

@@ -74,6 +74,20 @@ var _ = Describe("Test dry run with policy", func() {
Expect(buff.String()).Should(ContainSubstring("- image: oamdev/hello-world:v2\n name: server-v2"))
})
It("Test dry run with cue component format", func() {
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}
buff, err := DryRunApplication(&opt, c, "")
Expect(err).Should(BeNil())
Expect(buff.String()).Should(ContainSubstring("name: hello-world"))
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
Expect(buff.String()).Should(ContainSubstring("name: hello-world-service"))
Expect(buff.String()).Should(ContainSubstring("kind: Service"))
})
})
var plcapp = `apiVersion: core.oam.dev/v1beta1

View File

@@ -22,6 +22,7 @@ import (
"fmt"
"os"
"strconv"
"time"
"cuelang.org/go/cue"
"github.com/AlecAivazis/survey/v2"
@@ -111,11 +112,11 @@ func NewInitCommand(c common2.Args, order string, ioStreams cmdutil.IOStreams) *
if err != nil {
return err
}
deployStatus, err := printTrackingDeployStatus(c, o.IOStreams, o.appName, o.Namespace)
deployStatus, err := printTrackingDeployStatus(c, o.IOStreams, o.appName, o.Namespace, 300*time.Second)
if err != nil {
return err
}
if deployStatus != compStatusDeployed {
if deployStatus != appDeployedHealthy {
return nil
}
return printAppStatus(context.Background(), newClient, ioStreams, o.appName, o.Namespace, cmd, c, false)

View File

@@ -104,7 +104,7 @@ func LiveDiffApplication(cmdOption *LiveDiffCmdOptions, c common.Args) (bytes.Bu
}
objs := []oam.Object{}
if cmdOption.DefinitionFile != "" {
objs, err = ReadObjectsFromFile(cmdOption.DefinitionFile)
objs, err = ReadDefinitionsFromFile(cmdOption.DefinitionFile)
if err != nil {
return buff, err
}

View File

@@ -50,11 +50,11 @@ func newUITable() *uitable.Table {
}
func newTrackingSpinnerWithDelay(suffix string, interval time.Duration) *spinner.Spinner {
suffixColor := color.New(color.Bold, color.FgGreen)
suffixColor := color.New(color.Bold, color.FgWhite)
return spinner.New(
spinner.CharSets[14],
interval,
spinner.WithColor("green"),
spinner.WithColor("white"),
spinner.WithHiddenCursor(true),
spinner.WithSuffix(suffixColor.Sprintf(" %s", suffix)))
}

View File

@@ -78,26 +78,18 @@ type WorkloadHealthCondition = v1alpha2.WorkloadHealthCondition
// ScopeHealthCondition holds health condition of a scope
type ScopeHealthCondition = v1alpha2.ScopeHealthCondition
// CompStatus represents the status of a component during "vela init"
type CompStatus int
// AppDeployStatus represents the status of application during "vela init" and "vela up --wait"
type AppDeployStatus int
// Enums of CompStatus
// Enums of ApplicationStatus
const (
compStatusDeploying CompStatus = iota
compStatusDeployFail
compStatusDeployed
compStatusUnknown
)
// Error msg used in `status` command
const (
// ErrNotLoadAppConfig display the error message load
ErrNotLoadAppConfig = "cannot load the application"
appDeployFail AppDeployStatus = iota
appDeployedHealthy
appDeployError
)
const (
trackingInterval time.Duration = 1 * time.Second
deployTimeout time.Duration = 10 * time.Second
trackingInterval = 1 * time.Second
)
// NewAppStatusCommand creates `status` command for showing status
@@ -402,59 +394,47 @@ func loopCheckStatus(c client.Client, ioStreams cmdutil.IOStreams, appName strin
return nil
}
func printTrackingDeployStatus(c common.Args, ioStreams cmdutil.IOStreams, appName string, namespace string) (CompStatus, error) {
sDeploy := newTrackingSpinnerWithDelay("Checking Status ...", trackingInterval)
func printTrackingDeployStatus(c common.Args, ioStreams cmdutil.IOStreams, appName, namespace string, timeout time.Duration) (AppDeployStatus, error) {
sDeploy := newTrackingSpinnerWithDelay("Waiting app to be healthy ...", trackingInterval)
sDeploy.Start()
defer sDeploy.Stop()
startTime := time.Now()
TrackDeployLoop:
for {
time.Sleep(trackingInterval)
deployStatus, failMsg, err := TrackDeployStatus(c, appName, namespace)
deployStatus, err := TrackDeployStatus(c, appName, namespace)
if err != nil {
return compStatusUnknown, err
return appDeployError, err
}
switch deployStatus {
case compStatusDeploying:
case commontypes.ApplicationStarting, commontypes.ApplicationRendering, commontypes.ApplicationPolicyGenerating, commontypes.ApplicationRunningWorkflow, commontypes.ApplicationUnhealthy:
if time.Now().After(startTime.Add(timeout)) {
ioStreams.Info(red.Sprintf("\n%s Timeout waiting Application to be healthy!", emojiFail))
return appDeployFail, nil
}
continue
case compStatusDeployed:
case commontypes.ApplicationWorkflowSuspending, commontypes.ApplicationRunning:
ioStreams.Info(green.Sprintf("\n%sApplication Deployed Successfully!", emojiSucceed))
break TrackDeployLoop
case compStatusDeployFail:
ioStreams.Info(red.Sprintf("\n%sApplication Failed to Deploy!", emojiFail))
ioStreams.Info(red.Sprintf("Reason: %s", failMsg))
return compStatusDeployFail, nil
default:
continue
case commontypes.ApplicationWorkflowTerminated, commontypes.ApplicationWorkflowFailed:
ioStreams.Info(red.Sprintf("\n%sApplication Deployment Failed!", emojiFail))
ioStreams.Info(red.Sprintf("Please run the following command to check details: \n vela status %s -n %s\n", appName, namespace))
return appDeployFail, nil
case commontypes.ApplicationDeleting:
ioStreams.Info(red.Sprintf("\n%sApplication is in the deleting process!", emojiFail))
return appDeployFail, nil
}
}
return compStatusDeployed, nil
return appDeployedHealthy, nil
}
// TrackDeployStatus will only check AppConfig is deployed successfully,
func TrackDeployStatus(c common.Args, appName string, namespace string) (CompStatus, string, error) {
func TrackDeployStatus(c common.Args, appName string, namespace string) (commontypes.ApplicationPhase, error) {
appObj, err := appfile.LoadApplication(namespace, appName, c)
if err != nil {
return compStatusUnknown, "", err
return "", err
}
if appObj == nil {
return compStatusUnknown, "", errors.New(ErrNotLoadAppConfig)
}
condition := appObj.Status.Conditions
if len(condition) < 1 {
return compStatusDeploying, "", nil
}
// If condition is true, we can regard appConfig is deployed successfully
if appObj.Status.Phase == commontypes.ApplicationRunning {
return compStatusDeployed, "", nil
}
// if not found workload status in AppConfig
// then use age to check whether the workload controller is running
if time.Since(appObj.GetCreationTimestamp().Time) > deployTimeout {
return compStatusDeployFail, condition[0].Message, nil
}
return compStatusDeploying, "", nil
return appObj.Status.Phase, nil
}
func getHealthStatusColor(s bool) *color.Color {

View File

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

View File

@@ -0,0 +1,50 @@
"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

@@ -18,6 +18,9 @@ package cli
import (
"context"
"fmt"
"os"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -53,6 +56,8 @@ type UpCommandOptions struct {
PublishVersion string
RevisionName string
Debug bool
Wait bool
WaitTimeout string
}
// Complete fill the args for vela up
@@ -211,34 +216,38 @@ func (opt *UpCommandOptions) deployApplicationFromFile(f velacmd.Factory, cmd *c
}
if common.IsAppfile(body) { // legacy compatibility
o := &common.AppfileOptions{Kubecli: cli, IO: ioStream, Namespace: opt.Namespace}
return o.Run(opt.File, o.Namespace, utilcommon.Args{Schema: utilcommon.Scheme})
}
var app v1beta1.Application
err = yaml.Unmarshal(body, &app)
if err != nil {
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
}
if err = o.Run(opt.File, o.Namespace, utilcommon.Args{Schema: utilcommon.Scheme}); err != nil {
return err
}
opt.AppName = o.Name
} else {
var app v1beta1.Application
err = yaml.Unmarshal(body, &app)
if err != nil {
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
}
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
if opt.Namespace != "" && opt.Namespace != types.DefaultAppNamespace {
app.SetNamespace(opt.Namespace)
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
if opt.Namespace != "" && opt.Namespace != types.DefaultAppNamespace {
app.SetNamespace(opt.Namespace)
}
if opt.PublishVersion != "" {
oam.SetPublishVersion(&app, opt.PublishVersion)
}
opt.AppName = app.Name
if opt.Debug {
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
Name: "debug",
Type: "debug",
})
}
err = common.ApplyApplication(app, ioStream, cli)
if err != nil {
return err
}
cmd.Printf("Application %s applied.\n", green.Sprintf("%s/%s", app.Namespace, app.Name))
}
if opt.PublishVersion != "" {
oam.SetPublishVersion(&app, opt.PublishVersion)
}
opt.AppName = app.Name
if opt.Debug {
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
Name: "debug",
Type: "debug",
})
}
err = common.ApplyApplication(app, ioStream, cli)
if err != nil {
return err
}
cmd.Printf("Application %s applied.\n", green.Sprintf("%s/%s", app.Namespace, app.Name))
return nil
}
@@ -276,7 +285,9 @@ var (
// NewUpCommand will create command for applying an AppFile
func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream util.IOStreams) *cobra.Command {
o := &UpCommandOptions{}
o := &UpCommandOptions{
WaitTimeout: "300s",
}
cmd := &cobra.Command{
Use: "up",
DisableFlagsInUseLine: true,
@@ -301,10 +312,29 @@ func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream u
cmdutil.CheckErr(o.Run(f, cmd))
if o.Debug {
dOpts := &debugOpts{}
cli := f.Client()
app := &v1beta1.Application{}
cmdutil.CheckErr(cli.Get(cmd.Context(), apitypes.NamespacedName{Name: o.AppName, Namespace: o.Namespace}, app))
cmdutil.CheckErr(dOpts.debugApplication(context.Background(), c, app, ioStream))
wargs := &WorkflowArgs{Args: c}
ctx := context.Background()
cmdutil.CheckErr(wargs.getWorkflowInstance(ctx, cmd, []string{o.AppName}))
if wargs.Type == instanceTypeWorkflowRun {
cmdutil.CheckErr(fmt.Errorf("please use `vela workflow debug <name>` instead"))
}
if wargs.App == nil {
cmdutil.CheckErr(fmt.Errorf("application %s not found", args[0]))
}
cmdutil.CheckErr(dOpts.debugApplication(ctx, wargs, c, ioStream))
}
if o.Wait {
dur, err := time.ParseDuration(o.WaitTimeout)
if err != nil {
cmdutil.CheckErr(fmt.Errorf("parse timeout duration err: %w", err))
}
status, err := printTrackingDeployStatus(c, ioStream, o.AppName, o.Namespace, dur)
if err != nil {
cmdutil.CheckErr(err)
}
if status != appDeployedHealthy {
os.Exit(1)
}
}
},
}
@@ -312,6 +342,8 @@ func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream u
cmd.Flags().StringVarP(&o.PublishVersion, "publish-version", "v", o.PublishVersion, "The publish version for deploying application.")
cmd.Flags().StringVarP(&o.RevisionName, "revision", "r", o.RevisionName, "The revision to use for deploying the application, if empty, the current application configuration will be used.")
cmd.Flags().BoolVarP(&o.Debug, "debug", "", o.Debug, "Enable debug mode for application")
cmd.Flags().BoolVarP(&o.Wait, "wait", "w", o.Wait, "Wait app to be healthy until timout, if no timeout specified, the default duration is 300s.")
cmd.Flags().StringVarP(&o.WaitTimeout, "timeout", "", o.WaitTimeout, "Set the timout for wait app to be healthy, if not specified, the default duration is 300s.")
cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
"revision",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@@ -32,6 +32,7 @@ import (
wfTypes "github.com/kubevela/workflow/pkg/types"
wfUtils "github.com/kubevela/workflow/pkg/utils"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
@@ -61,6 +62,7 @@ func NewWorkflowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
NewWorkflowRestartCommand(c, ioStreams, wargs),
NewWorkflowRollbackCommand(c, ioStreams, wargs),
NewWorkflowLogsCommand(c, ioStreams, wargs),
NewWorkflowDebugCommand(c, ioStreams, wargs),
)
return cmd
}
@@ -194,6 +196,42 @@ func NewWorkflowLogsCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *Wo
return cmd
}
// NewWorkflowDebugCommand create workflow debug command
func NewWorkflowDebugCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
dOpts := &debugOpts{
step: wargs.StepName,
}
cmd := &cobra.Command{
Use: "debug",
Short: "Debug workflow steps",
Long: "Debug workflow steps",
Example: "vela workflow debug <workflow-name>",
PreRun: wargs.checkDebugMode(),
RunE: func(cmd *cobra.Command, args []string) error {
cli, err := c.GetClient()
if err != nil {
return err
}
pd, err := c.GetPackageDiscover()
if err != nil {
return err
}
ctx := context.Background()
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
dOpts.opts = wargs.getWorkflowSteps()
dOpts.errMap = wargs.ErrMap
return dOpts.debugWorkflow(ctx, wargs, cli, pd, ioStream)
},
}
cmd.Flags().StringVarP(&wargs.StepName, "step", "s", "", "specify the step name in the workflow")
cmd.Flags().StringVarP(&dOpts.focus, "focus", "f", "", "specify the focus value to debug, only valid for application with workflow")
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
addNamespaceAndEnvArg(cmd)
return cmd
}
// WorkflowArgs is the args for workflow command
type WorkflowArgs struct {
Type string
@@ -203,6 +241,7 @@ type WorkflowArgs struct {
Writer io.Writer
Args common.Args
StepName string
ErrMap map[string]string
App *v1beta1.Application
WorkflowRun *workflowv1alpha1.WorkflowRun
WorkflowInstance *wfTypes.WorkflowInstance
@@ -265,18 +304,26 @@ func (w *WorkflowArgs) getWorkflowInstance(ctx context.Context, cmd *cobra.Comma
}
func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.Client) error {
debug := false
switch w.Type {
case instanceTypeApplication:
if w.App.Status.Workflow == nil {
return fmt.Errorf("the workflow in application %s is not start", w.App.Name)
}
for _, policy := range w.App.Spec.Policies {
if policy.Type == v1alpha1.DebugPolicyType {
debug = true
break
}
}
status := w.App.Status.Workflow
w.WorkflowInstance = &wfTypes.WorkflowInstance{
WorkflowMeta: wfTypes.WorkflowMeta{
Name: w.App.Name,
Namespace: w.App.Namespace,
UID: w.App.UID,
},
Steps: w.App.Spec.Workflow.Steps,
Debug: debug,
Status: workflowv1alpha1.WorkflowRunStatus{
Phase: status.Phase,
Message: status.Message,
@@ -290,6 +337,9 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
EndTime: status.EndTime,
},
}
if w.App.Spec.Workflow != nil {
w.WorkflowInstance.Steps = w.App.Spec.Workflow.Steps
}
w.Operator = operation.NewApplicationWorkflowOperator(cli, w.Writer, w.App)
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-core"}
case instanceTypeWorkflowRun:
@@ -303,13 +353,20 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
} else {
steps = w.WorkflowRun.Spec.WorkflowSpec.Steps
}
if w.WorkflowRun.Annotations != nil {
if d, ok := w.WorkflowRun.Annotations[wfTypes.AnnotationWorkflowRunDebug]; ok && d == "true" {
debug = true
}
}
w.WorkflowInstance = &wfTypes.WorkflowInstance{
WorkflowMeta: wfTypes.WorkflowMeta{
Name: w.WorkflowRun.Name,
Namespace: w.WorkflowRun.Namespace,
UID: w.WorkflowRun.UID,
},
Steps: steps,
Status: w.WorkflowRun.Status,
Debug: debug,
}
w.Operator = wfUtils.NewWorkflowRunOperator(cli, w.Writer, w.WorkflowRun)
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-workflow"}
@@ -321,7 +378,7 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioStreams cmdutil.IOStreams) error {
if w.StepName == "" {
if err := w.selectWorkflowStep(); err != nil {
if err := w.selectWorkflowStep("Select a step to show logs:"); err != nil {
return err
}
}
@@ -360,20 +417,34 @@ func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioS
return nil
}
func (w *WorkflowArgs) selectWorkflowStep() error {
func (w *WorkflowArgs) getWorkflowSteps() []string {
if w.ErrMap == nil {
w.ErrMap = make(map[string]string)
}
stepsKey := make([]string, 0)
for _, step := range w.WorkflowInstance.Status.Steps {
stepsKey = append(stepsKey, wrapStepName(step.StepStatus))
if step.Phase == workflowv1alpha1.WorkflowStepPhaseFailed {
w.ErrMap[step.Name] = step.Message
}
for _, sub := range step.SubStepsStatus {
stepsKey = append(stepsKey, fmt.Sprintf(" %s", wrapStepName(sub)))
if sub.Phase == workflowv1alpha1.WorkflowStepPhaseFailed {
w.ErrMap[step.Name] = sub.Message
}
}
}
return stepsKey
}
func (w *WorkflowArgs) selectWorkflowStep(msg string) error {
stepsKey := w.getWorkflowSteps()
if len(stepsKey) == 0 {
return fmt.Errorf("workflow is not start")
}
prompt := &survey.Select{
Message: "Select a step to show logs:",
Message: msg,
Options: stepsKey,
}
var stepName string
@@ -445,3 +516,21 @@ func (w *WorkflowArgs) checkWorkflowNotComplete() func(cmd *cobra.Command, args
}
}
}
func (w *WorkflowArgs) checkDebugMode() func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
if err := w.getWorkflowInstance(context.Background(), cmd, args); err != nil {
return
}
if !w.WorkflowInstance.Debug {
msg := ""
if w.Type == instanceTypeApplication {
msg = "please make sure your application have the debug policy, you can add the debug policy by using `vela up -f <app.yaml> --debug"
} else {
msg = "please make sure your workflow have the debug annotation [workflowrun.oam.dev/debug:true] then re-run the workflow"
}
cmd.Printf("workflow %s is not in debug mode, %s", w.WorkflowInstance.Name, msg)
os.Exit(1)
}
}
}

View File

@@ -61,6 +61,7 @@ type AppfileOptions struct {
Kubecli client.Client
IO cmdutil.IOStreams
Namespace string
Name string
}
// BuildResult is the export struct from AppFile yaml or AppFile object
@@ -380,7 +381,7 @@ func (o *AppfileOptions) BaseAppFileRun(result *BuildResult, args common.Args) e
return err
}
result.application.Spec.Components = kubernetesComponent
o.Name = result.application.Name
o.IO.Infof("\nApplying application ...\n")
return o.ApplyApp(result.application, result.scopes)
}

View File

@@ -266,7 +266,7 @@ func GetWorkflowSteps(ctx context.Context, namespace string, c common.Args) ([]t
for _, def := range workflowStepDefs.Items {
tmp, err := GetCapabilityByWorkflowStepDefinitionObject(def, pd)
if err != nil {
templateErrors = append(templateErrors, err)
templateErrors = append(templateErrors, errors.WithMessage(err, def.Name))
continue
}
templates = append(templates, *tmp)

View File

@@ -0,0 +1,27 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: first-vela-workflow
namespace: default
spec:
components:
- name: express-server
type: webservice
properties:
image: oamdev/hello-world
port: 8000
traits:
- type: ingress
properties:
domain: testsvc.example.com
http:
/: 8000
workflow:
steps:
- name: express-server
type: apply-component
properties:
component: express-server
# cluster: <your cluster name>
```

View File

@@ -0,0 +1,24 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: print-message-in-status
namespace: default
spec:
components:
- name: express-server
type: webservice
properties:
image: oamdev/hello-world
port: 8000
workflow:
steps:
- name: express-server
type: apply-component
properties:
component: express-server
- name: message
type: print-message-in-status
properties:
message: "All addons have been enabled successfully, you can use 'vela addon list' to check them."
```

View File

@@ -213,4 +213,23 @@ var _ = Describe("Test addon rest api", func() {
Expect(strings.Contains(errResponse.Message, "fail to install"))
})
})
Describe("Test addon dependency installed version", func() {
It("Test Operation of enabling foo addon will enable bar addon automatically", func() {
req := apisv1.EnableAddonRequest{}
res := post("/addons/foo/enable", req)
defer res.Body.Close()
var addon apisv1.AddonStatusResponse
Expect(decodeResponseBody(res, &addon)).Should(Succeed())
Expect(addon.Name).Should(BeEquivalentTo("foo"))
Eventually(func(g Gomega) {
status := get("/addons/bar/status")
var newaddonStatus apisv1.AddonStatusResponse
g.Expect(decodeResponseBody(status, &newaddonStatus)).Should(Succeed())
g.Expect(newaddonStatus.Name).Should(BeEquivalentTo("bar"))
g.Expect(newaddonStatus.InstalledVersion).Should(BeEquivalentTo("v1.0.0"))
}, 30*time.Second, 300*time.Millisecond).Should(Succeed())
})
})
})

View File

@@ -669,6 +669,21 @@ var _ = Describe("Test multicluster scenario", func() {
}, 20*time.Second).Should(Succeed())
})
It("Test application with apply-component and cluster", func() {
By("create application")
bs, err := os.ReadFile("./testdata/app/app-component-with-cluster.yaml")
Expect(err).Should(Succeed())
app := &v1beta1.Application{}
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
app.SetNamespace(testNamespace)
Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(hubCtx, client.ObjectKeyFromObject(app), app)).Should(Succeed())
g.Expect(app.Status.Phase).Should(Equal(common.ApplicationRunning))
}, 20*time.Second).Should(Succeed())
Expect(k8sClient.Get(workerCtx, client.ObjectKey{Namespace: testNamespace, Name: "component-cluster"}, &appsv1.Deployment{})).Should(Succeed())
})
It("Test application with component using cluster context", func() {
By("Create definition")
bs, err := os.ReadFile("./testdata/def/cluster-config.yaml")

View File

@@ -0,0 +1,20 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: component-cluster
spec:
components:
- name: component-cluster
type: worker
properties:
image: busybox
cmd:
- sleep
- '1000000'
workflow:
steps:
- name: apply
type: apply-component
properties:
component: component-cluster
cluster: cluster-worker

View File

@@ -0,0 +1,17 @@
"apply-component": {
type: "workflow-step"
annotations: {}
labels: {
"ui-hidden": "true"
"scope": "Application"
}
description: "Apply a specific component and its corresponding traits in application"
}
template: {
parameter: {
// +usage=Specify the component name to apply
component: string
// +usage=Specify the cluster
cluster: *"" | string
}
}

View File

@@ -8,7 +8,7 @@ import (
labels: {
"ui-hidden": "true"
}
description: "pring message in workflow status"
description: "print message in workflow step status"
}
template: {