mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-26 15:54:08 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1cc06b0f3 | ||
|
|
ed9d53b448 | ||
|
|
ad83e59865 | ||
|
|
b62eeca3f9 | ||
|
|
5d9757fcb8 | ||
|
|
4d653951a1 | ||
|
|
bcda4976a9 | ||
|
|
a01d0e773a |
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz
Normal file
Binary file not shown.
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz
Normal file
Binary file not shown.
BIN
e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz
Normal file
Binary file not shown.
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
2
go.mod
@@ -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
4
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"] == "" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
8
references/cli/test-data/dry-run/app.yaml
Normal file
8
references/cli/test-data/dry-run/app.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: vela-app
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: my-comp
|
||||
50
references/cli/test-data/dry-run/my-comp.cue
Normal file
50
references/cli/test-data/dry-run/my-comp.cue
Normal 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: {}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
27
references/docgen/def-doc/workflowstep/apply-component.eg.md
Normal file
27
references/docgen/def-doc/workflowstep/apply-component.eg.md
Normal 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>
|
||||
```
|
||||
@@ -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."
|
||||
```
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
|
||||
20
test/e2e-multicluster-test/testdata/app/app-component-with-cluster.yaml
vendored
Normal file
20
test/e2e-multicluster-test/testdata/app/app-component-with-cluster.yaml
vendored
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
labels: {
|
||||
"ui-hidden": "true"
|
||||
}
|
||||
description: "pring message in workflow status"
|
||||
description: "print message in workflow step status"
|
||||
}
|
||||
|
||||
template: {
|
||||
|
||||
Reference in New Issue
Block a user