Compare commits

...

15 Commits

Author SHA1 Message Date
github-actions[bot]
a783393ebd [Backport release-1.6] Fix: bug of filter registry func will modify origin data (#5120)
* fix filter registry func flaky

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit fbdd7b7ede)

* fix comments

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 49671dd3e0)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-11-24 15:06:29 +08:00
github-actions[bot]
a19ed0b510 [Backport release-1.6] Chore: add definition example doc CI check (#5119)
* Chore: add definition example doc CI check

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
(cherry picked from commit 1afaaf047d)

* Fix: add example doc for trait

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
(cherry picked from commit 6c61643b8a)

Co-authored-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-11-24 15:05:18 +08:00
github-actions[bot]
03223aa786 [Backport release-1.6] Fix: bug when addon dependent an addon in other registry (#5115)
* fix several bugs of addon

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit eadabe6517)

* fix golint error

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 2ba81880bf)

* fix error and add tests

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 43f6925566)

* fix comments and fix apiserver test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 96a6a3f4a3)

* fix typo

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit cafa5adb46)

* fix tests

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 3599b01aeb)

* small fix

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 30cafbc3e9)

* small fix

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 083413479e)

* add parameter in apiserver and test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 9159749477)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-11-24 09:46:54 +08:00
github-actions[bot]
55c8dad116 Fix: multicluster cluster scope ref (#5112)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
(cherry picked from commit 40a9a981d9)

Co-authored-by: Somefive <yd219913@alibaba-inc.com>
2022-11-24 09:16:15 +08:00
github-actions[bot]
38c57c38c8 [Backport release-1.6] Fix: end test environments (#5107)
* Fix: end test environments

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

* fix

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

Co-authored-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-11-22 19:37:12 +08:00
github-actions[bot]
7f734e9479 Fix: patchOutputs bug for multiple outputs (#5104)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
(cherry picked from commit 4864ca3a27)

Co-authored-by: Somefive <yd219913@alibaba-inc.com>
2022-11-22 11:40:20 +08:00
github-actions[bot]
7814232b7c Fix: fix acr webhook for enterprise registry (#5098)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit c0519d3fba)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-11-21 14:21:58 +08:00
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
101 changed files with 1827 additions and 337 deletions

44
.github/workflows/definition-lint.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Definition-Lint
on:
push:
branches:
- master
- release-*
workflow_dispatch: {}
pull_request:
branches:
- master
- release-*
env:
# Common versions
GO_VERSION: '1.19'
jobs:
definition-doc:
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
- name: Setup K3d
uses: nolar/setup-k3d-k3s@v1.0.9
with:
version: v1.20
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Definition Doc generate check
run: |
go build -o docgen hack/docgen/def/gen.go
./docgen --type=comp --force-example-doc --path=./comp-def-check.md
./docgen --type=trait --force-example-doc --path=./trait-def-check.md
./docgen --type=wf --force-example-doc --path=./wf-def-check.md
./docgen --type=policy --force-example-doc --path=./policy-def-check.md

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

View File

@@ -0,0 +1,8 @@
name: mock-dep-addon
version: v1.0.0
description: Vela test addon named mock-dep-addon
icon: https://www.test.com/icon
url: https://www.test.com
dependencies:
- name: mock-be-dep-addon

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -34,4 +34,39 @@ 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
mock-be-dep-addon:
- created: "2022-10-29T09:11:16.865230605Z"
description: Vela test addon named mock-be-dep-addon
home: https://www.test.com/icon
icon: https://www.test.com
name: mock-be-dep-addon
urls:
- http://127.0.0.1:9098/helm/mock-be-dep-addon-v1.0.0.tgz
version: v1.0.0
generated: "2022-06-15T13:17:04.733573+08:00"

View File

@@ -131,6 +131,30 @@ 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)
case strings.Contains(req.URL.Path, "mock-be-dep-addon-v1.0.0.tgz"):
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/mock-be-dep-addon-v1.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

@@ -42,6 +42,7 @@ func main() {
defdir := flag.String("def-dir", "", "path of definition dir")
tp := flag.String("type", "", "choose one of the definition to print")
i18nfile := flag.String("i18n", "../kubevela.io/static/reference-i18n.json", "file path of i18n data")
forceExample := flag.Bool("force-example-doc", false, "example must be provided for definitions")
flag.Parse()
if *i18nfile != "" {
@@ -52,21 +53,29 @@ func main() {
fmt.Println("you must specify a type with definition ref path specified ")
os.Exit(1)
}
opt := mods.Options{
Path: *path,
Location: *location,
DefDir: *defdir,
ForceExamples: *forceExample,
}
fmt.Printf("creating docs with args path=%s, location=%s, defdir=%s, type=%s.\n", *path, *location, *defdir, *tp)
switch types.CapType(*tp) {
case types.TypeComponentDefinition, "component", "comp":
mods.ComponentDef(ctx, c, path, location, *defdir)
mods.ComponentDef(ctx, c, opt)
case types.TypeTrait:
mods.TraitDef(ctx, c, path, location, *defdir)
mods.TraitDef(ctx, c, opt)
case types.TypePolicy:
mods.PolicyDef(ctx, c, path, location, *defdir)
mods.PolicyDef(ctx, c, opt)
case types.TypeWorkflowStep, "workflow", "wf":
mods.WorkflowDef(ctx, c, path, location, *defdir)
mods.WorkflowDef(ctx, c, opt)
case "":
mods.ComponentDef(ctx, c, path, location, *defdir)
mods.TraitDef(ctx, c, path, location, *defdir)
mods.PolicyDef(ctx, c, path, location, *defdir)
mods.WorkflowDef(ctx, c, path, location, *defdir)
mods.ComponentDef(ctx, c, opt)
mods.TraitDef(ctx, c, opt)
mods.PolicyDef(ctx, c, opt)
mods.WorkflowDef(ctx, c, opt)
default:
fmt.Printf("type %s not supported\n", *tp)
os.Exit(1)

View File

@@ -19,7 +19,6 @@ package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
@@ -60,12 +59,13 @@ title: 内置组件列表
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// ComponentDef generate component def reference doc
func ComponentDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = ComponentDefDir
func ComponentDef(ctx context.Context, c common.Args, opt Options) {
if opt.DefDir == "" {
opt.DefDir = ComponentDefDir
}
ref := &docgen.MarkdownReference{
AllInOne: true,
AllInOne: true,
ForceExample: opt.ForceExamples,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypeComponentDefinition || capability.Category != types.CUECategory {
return false
@@ -74,9 +74,9 @@ func ComponentDef(ctx context.Context, c common.Args, path, location *string, de
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
files, err := os.ReadDir(opt.DefDir)
if err != nil {
fmt.Println("read dir err", defdir, err)
fmt.Println("read dir err", opt.DefDir, err)
return false
}
for _, f := range files {
@@ -96,19 +96,20 @@ func ComponentDef(ctx context.Context, c common.Args, path, location *string, de
return
}
ref.DiscoveryMapper = dm
if *path != "" {
if opt.Path != "" {
ref.I18N = &docgen.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
if strings.Contains(opt.Location, "zh") || strings.Contains(opt.Location, "chinese") {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomComponentHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
if err := ref.GenerateReferenceDocs(ctx, c, opt.Path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("component reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
fmt.Printf("component reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), opt.Path)
return
}
if *location == "" || *location == "en" {
if opt.Location == "" || opt.Location == "en" {
ref.I18N = &docgen.En
if err := ref.GenerateReferenceDocs(ctx, c, ComponentDefRefPath); err != nil {
fmt.Println(err)
@@ -116,7 +117,7 @@ func ComponentDef(ctx context.Context, c common.Args, path, location *string, de
}
fmt.Printf("component reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), ComponentDefRefPath)
}
if *location == "" || *location == "zh" {
if opt.Location == "" || opt.Location == "zh" {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomComponentHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, ComponentDefRefPathZh); err != nil {

View File

@@ -19,7 +19,6 @@ package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
@@ -58,12 +57,13 @@ title: 内置策略列表
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// PolicyDef generate policy def reference doc
func PolicyDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = PolicyDefDir
func PolicyDef(ctx context.Context, c common.Args, opt Options) {
if opt.DefDir == "" {
opt.DefDir = PolicyDefDir
}
ref := &docgen.MarkdownReference{
AllInOne: true,
AllInOne: true,
ForceExample: opt.ForceExamples,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypePolicy || capability.Category != types.CUECategory {
return false
@@ -72,9 +72,9 @@ func PolicyDef(ctx context.Context, c common.Args, path, location *string, defdi
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
files, err := os.ReadDir(opt.DefDir)
if err != nil {
fmt.Println("read dir err", defdir, err)
fmt.Println("read dir err", opt.DefDir, err)
return false
}
for _, f := range files {
@@ -86,20 +86,21 @@ func PolicyDef(ctx context.Context, c common.Args, path, location *string, defdi
},
CustomDocHeader: CustomPolicyHeaderEN,
}
ref.Remote = &docgen.FromCluster{Namespace: types.DefaultKubeVelaNS}
if *path != "" {
ref.Local = &docgen.FromLocal{Path: PolicyDefDir}
if opt.Path != "" {
ref.I18N = &docgen.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
if strings.Contains(opt.Location, "zh") || strings.Contains(opt.Location, "chinese") {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomPolicyHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
if err := ref.GenerateReferenceDocs(ctx, c, opt.Path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("policy reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
fmt.Printf("policy reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), opt.Path)
return
}
if *location == "" || *location == "en" {
if opt.Location == "" || opt.Location == "en" {
ref.I18N = &docgen.En
if err := ref.GenerateReferenceDocs(ctx, c, PolicyDefRefPath); err != nil {
fmt.Println(err)
@@ -107,7 +108,7 @@ func PolicyDef(ctx context.Context, c common.Args, path, location *string, defdi
}
fmt.Printf("policy reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), PolicyDefRefPath)
}
if *location == "" || *location == "zh" {
if opt.Location == "" || opt.Location == "zh" {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomPolicyHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, PolicyDefRefPathZh); err != nil {

View File

@@ -19,7 +19,6 @@ package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
@@ -58,12 +57,13 @@ title: 内置运维特征列表
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// TraitDef generate trait def reference doc
func TraitDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = TraitDefDir
func TraitDef(ctx context.Context, c common.Args, opt Options) {
if opt.DefDir == "" {
opt.DefDir = TraitDefDir
}
ref := &docgen.MarkdownReference{
AllInOne: true,
AllInOne: true,
ForceExample: opt.ForceExamples,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypeTrait || capability.Category != types.CUECategory {
return false
@@ -72,9 +72,9 @@ func TraitDef(ctx context.Context, c common.Args, path, location *string, defdir
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
files, err := os.ReadDir(opt.DefDir)
if err != nil {
fmt.Println("read dir err", defdir, err)
fmt.Println("read dir err", opt.DefDir, err)
return false
}
for _, f := range files {
@@ -90,20 +90,20 @@ func TraitDef(ctx context.Context, c common.Args, path, location *string, defdir
Path: TraitDefDir,
}
if *path != "" {
if opt.Path != "" {
ref.I18N = &docgen.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
if strings.Contains(opt.Location, "zh") || strings.Contains(opt.Location, "chinese") {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomTraitHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
if err := ref.GenerateReferenceDocs(ctx, c, opt.Path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("trait reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
fmt.Printf("trait reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), opt.Path)
} else {
// Generate to default path depends on language
if *location == "" || *location == "en" {
if opt.Location == "" || opt.Location == "en" {
ref.I18N = &docgen.En
if err := ref.GenerateReferenceDocs(ctx, c, TraitDefRefPath); err != nil {
fmt.Println(err)
@@ -111,7 +111,7 @@ func TraitDef(ctx context.Context, c common.Args, path, location *string, defdir
}
fmt.Printf("trait reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), TraitDefRefPath)
}
if *location == "" || *location == "zh" {
if opt.Location == "" || opt.Location == "zh" {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomTraitHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, TraitDefRefPathZh); err != nil {

View File

@@ -0,0 +1,25 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mods
// Options defines the doc generate options
type Options struct {
Path string
Location string
DefDir string
ForceExamples bool
}

View File

@@ -19,7 +19,6 @@ package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
@@ -58,12 +57,13 @@ title: 内置工作流步骤列表
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// WorkflowDef generate workflow def reference doc
func WorkflowDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = WorkflowDefDir
func WorkflowDef(ctx context.Context, c common.Args, opt Options) {
if opt.DefDir == "" {
opt.DefDir = WorkflowDefDir
}
ref := &docgen.MarkdownReference{
AllInOne: true,
AllInOne: true,
ForceExample: opt.ForceExamples,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypeWorkflowStep || capability.Category != types.CUECategory {
@@ -74,9 +74,9 @@ func WorkflowDef(ctx context.Context, c common.Args, path, location *string, def
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
files, err := os.ReadDir(opt.DefDir)
if err != nil {
fmt.Println("read dir err", defdir, err)
fmt.Println("read dir err", opt.DefDir, err)
return false
}
for _, f := range files {
@@ -88,21 +88,22 @@ func WorkflowDef(ctx context.Context, c common.Args, path, location *string, def
},
CustomDocHeader: CustomWorkflowHeaderEN,
}
ref.Remote = &docgen.FromCluster{Namespace: types.DefaultKubeVelaNS}
ref.Local = &docgen.FromLocal{Path: WorkflowDefDir}
if *path != "" {
if opt.Path != "" {
ref.I18N = &docgen.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
if strings.Contains(opt.Location, "zh") || strings.Contains(opt.Location, "chinese") {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomWorkflowHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
if err := ref.GenerateReferenceDocs(ctx, c, opt.Path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("workflow reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
fmt.Printf("workflow reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), opt.Path)
return
}
if *location == "" || *location == "en" {
if opt.Location == "" || opt.Location == "en" {
ref.I18N = &docgen.En
if err := ref.GenerateReferenceDocs(ctx, c, WorkflowDefRefPath); err != nil {
fmt.Println(err)
@@ -110,7 +111,7 @@ func WorkflowDef(ctx context.Context, c common.Args, path, location *string, def
}
fmt.Printf("workflow reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), WorkflowDefRefPath)
}
if *location == "" || *location == "zh" {
if opt.Location == "" || opt.Location == "zh" {
ref.I18N = &docgen.Zh
ref.CustomDocHeader = CustomWorkflowHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, WorkflowDefRefPathZh); err != nil {

View File

@@ -932,10 +932,12 @@ type Installer struct {
dryRun bool
dryRunBuff *bytes.Buffer
registries []Registry
}
// NewAddonInstaller will create an installer for addon
func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache, opts ...InstallOption) Installer {
func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache, registries []Registry, opts ...InstallOption) Installer {
i := Installer{
ctx: ctx,
config: config,
@@ -946,6 +948,7 @@ func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *
cache: cache,
dc: discoveryClient,
dryRunBuff: &bytes.Buffer{},
registries: registries,
}
for _, opt := range opts {
opt(&i)
@@ -1042,16 +1045,41 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
if h.dryRun {
continue
}
// always install addon's latest version
depAddon, err := h.loadInstallPackage(dep.Name, "")
if err != nil {
return err
}
depHandler := *h
depHandler.args = nil
if err = depHandler.enableAddon(depAddon); err != nil {
return errors.Wrap(err, "fail to dispatch dependent addon resource")
var depAddon *InstallPackage
// try to install the dependent addon from the same registry with the current addon
depAddon, err = h.loadInstallPackage(dep.Name, dep.Version)
if err == nil {
if err = depHandler.enableAddon(depAddon); err != nil {
return errors.Wrap(err, "fail to dispatch dependent addon resource")
}
return nil
}
if !errors.Is(err, ErrNotExist) {
return err
}
for _, registry := range h.registries {
// try to install dependent addon from other registries
depHandler.r = &Registry{
Name: registry.Name, Helm: registry.Helm, OSS: registry.OSS, Git: registry.Git, Gitee: registry.Gitee, Gitlab: registry.Gitlab,
}
depAddon, err = depHandler.loadInstallPackage(dep.Name, dep.Version)
if err == nil {
break
}
if errors.Is(err, ErrNotExist) {
continue
}
return err
}
if err == nil {
if err = depHandler.enableAddon(depAddon); err != nil {
return errors.Wrap(err, "fail to dispatch dependent addon resource")
}
return nil
}
return fmt.Errorf("dependency addon: %s with version: %s cannot be found from all registries", dep.Name, dep.Version)
}
if h.dryRun && len(dependencies) > 0 {
klog.Warningf("dry run addon won't install dependencies, please make sure your system has already installed these addons: %v", strings.Join(dependencies, ", "))

View File

@@ -355,7 +355,7 @@ var _ = Describe("func addon update ", func() {
}, time.Millisecond*500, 30*time.Second).Should(BeNil())
pkg := &InstallPackage{Meta: Meta{Name: "test-update", Version: "1.3.0"}}
h := NewAddonInstaller(context.Background(), k8sClient, nil, nil, nil, &Registry{Name: "test"}, nil, nil)
h := NewAddonInstaller(context.Background(), k8sClient, nil, nil, nil, &Registry{Name: "test"}, nil, nil, nil)
h.addon = pkg
Expect(h.dispatchAddonResource(pkg)).Should(BeNil())
@@ -418,7 +418,7 @@ var _ = Describe("test dry-run addon from local dir", func() {
pkg, err := GetInstallPackageFromReader(r, &meta, UIData)
Expect(err).Should(BeNil())
h := NewAddonInstaller(ctx, k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, &Registry{Name: LocalAddonRegistryName}, map[string]interface{}{"example": "test-dry-run"}, nil, DryRunAddon)
h := NewAddonInstaller(ctx, k8sClient, dc, apply.NewAPIApplicator(k8sClient), cfg, &Registry{Name: LocalAddonRegistryName}, map[string]interface{}{"example": "test-dry-run"}, nil, nil, DryRunAddon)
err = h.enableAddon(pkg)
Expect(err).Should(BeNil())

View File

@@ -55,8 +55,8 @@ const (
)
// EnableAddon will enable addon with dependency check, source is where addon from.
func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache, opts ...InstallOption) error {
h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache, opts...)
func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache, registries []Registry, opts ...InstallOption) error {
h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache, registries, opts...)
pkg, err := h.loadInstallPackage(name, version)
if err != nil {
return err
@@ -113,7 +113,7 @@ func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli cli
if err != nil {
return err
}
h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil, opts...)
h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil, nil, opts...)
needEnableAddonNames, err := h.checkDependency(pkg)
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

@@ -187,7 +187,7 @@ func findLegacyAddonDefs(ctx context.Context, k8sClient client.Client, addonName
if registry.Name == registryName {
var uiData *UIData
if !IsVersionRegistry(registry) {
installer := NewAddonInstaller(ctx, k8sClient, nil, nil, config, &registries[i], nil, nil)
installer := NewAddonInstaller(ctx, k8sClient, nil, nil, config, &registries[i], nil, nil, nil)
metas, err := installer.getAddonMeta()
if err != nil {
return err
@@ -502,3 +502,17 @@ func checkBondComponentExist(u unstructured.Unstructured, app v1beta1.Applicatio
}
return false
}
// FilterDependencyRegistries will return all registries besides the target registry itself
func FilterDependencyRegistries(i int, rs []Registry) []Registry {
if i >= len(rs) {
return rs
}
if i < 0 {
return rs
}
ret := make([]Registry, len(rs)-1)
copy(ret, rs[:i])
copy(ret[i:], rs[i+1:])
return ret
}

View File

@@ -329,6 +329,57 @@ func TestCheckObjectBindingComponent(t *testing.T) {
}
}
func TestFilterDependencyRegistries(t *testing.T) {
testCases := []struct {
registries []Registry
index int
res []Registry
origin []Registry
}{
{
registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
index: 0,
res: []Registry{{Name: "r2"}, {Name: "r3"}},
origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
},
{
registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
index: 1,
res: []Registry{{Name: "r1"}, {Name: "r3"}},
origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
},
{
registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
index: 2,
res: []Registry{{Name: "r1"}, {Name: "r2"}},
origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
},
{
registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
index: 3,
res: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
},
{
registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
index: -1,
res: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}},
},
{
registries: []Registry{},
index: 0,
res: []Registry{},
origin: []Registry{},
},
}
for _, testCase := range testCases {
res := FilterDependencyRegistries(testCase.index, testCase.registries)
assert.Equal(t, res, testCase.res)
assert.Equal(t, testCase.registries, testCase.origin)
}
}
const (
compDefYaml = `
apiVersion: core.oam.dev/v1beta1

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
@@ -383,6 +386,7 @@ type ApplicationTrigger struct {
Type string `json:"type"`
PayloadType string `json:"payloadType"`
ComponentName string `json:"componentName"`
Registry string `json:"registry,omitempty"`
}
const (

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

@@ -400,8 +400,22 @@ func (u *addonServiceImpl) EnableAddon(ctx context.Context, name string, args ap
if err != nil {
return err
}
for _, r := range registries {
err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache)
if len(args.RegistryName) != 0 {
foundRegistry := false
for _, registry := range registries {
if registry.Name == args.RegistryName {
foundRegistry = true
}
}
if !foundRegistry {
return bcode.ErrAddonRegistryNotExist.SetMessage(fmt.Sprintf("specified registry %s not exist", args.RegistryName))
}
}
for i, r := range registries {
if len(args.RegistryName) != 0 && args.RegistryName != r.Name {
continue
}
err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries))
if err == nil {
return nil
}
@@ -471,8 +485,8 @@ func (u *addonServiceImpl) UpdateAddon(ctx context.Context, name string, args ap
return err
}
for _, r := range registries {
err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache)
for i, r := range registries {
err = pkgaddon.EnableAddon(ctx, name, args.Version, u.kubeClient, u.discoveryClient, u.apply, u.config, r, args.Args, u.addonRegistryCache, pkgaddon.FilterDependencyRegistries(i, registries))
if err == nil {
return nil
}

View File

@@ -418,6 +418,7 @@ func (c *applicationServiceImpl) CreateApplicationTrigger(ctx context.Context, a
Type: req.Type,
PayloadType: req.PayloadType,
ComponentName: req.ComponentName,
Registry: req.Registry,
Token: genWebhookToken(),
}
if err := c.Store.Add(ctx, trigger); err != nil {

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

@@ -215,13 +215,16 @@ func (c *customHandlerImpl) install() {
}
func (c *acrHandlerImpl) handle(ctx context.Context, webhookTrigger *model.ApplicationTrigger, app *model.Application) (interface{}, error) {
component, err := getComponent(ctx, c.w.Store, webhookTrigger)
if err != nil {
return nil, err
}
acrReq := c.req
image := fmt.Sprintf("registry.%s.aliyuncs.com/%s:%s", acrReq.Repository.Region, acrReq.Repository.RepoFullName, acrReq.PushData.Tag)
registry := webhookTrigger.Registry
if registry == "" {
registry = fmt.Sprintf("registry.%s.aliyuncs.com", acrReq.Repository.Region)
}
image := fmt.Sprintf("%s/%s:%s", registry, acrReq.Repository.RepoFullName, acrReq.PushData.Tag)
if err := c.w.patchComponentProperties(ctx, component, &runtime.RawExtension{
Raw: []byte(fmt.Sprintf(`{"image": "%s"}`, image)),
}); err != nil {

View File

@@ -196,6 +196,40 @@ var _ = Describe("Test application service function", func() {
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("registry.test-region.aliyuncs.com/test-namespace/test-repo:test-tag"))
By("Test HandleApplicationWebhook function with ACR payload and registry info")
acrTrigger, err = appService.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
Name: "test-acr",
PayloadType: "acr",
Type: "webhook",
ComponentName: "component-name-webhook",
Registry: "test-enterprise-registry.test-region.cr.aliyuncs.com",
})
Expect(err).Should(BeNil())
acrBody = apisv1.HandleApplicationTriggerACRRequest{
PushData: apisv1.ACRPushData{
Digest: "test-digest",
Tag: "test-tag",
},
Repository: apisv1.ACRRepository{
Name: "test-repo",
Namespace: "test-namespace",
Region: "test-region",
RepoFullName: "test-namespace/test-repo",
RepoType: "public",
},
}
body, err = json.Marshal(acrBody)
Expect(err).Should(BeNil())
httpreq, err = http.NewRequest("post", "/", bytes.NewBuffer(body))
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
Expect(err).Should(BeNil())
_, err = webhookService.HandleApplicationWebhook(context.TODO(), acrTrigger.Token, restful.NewRequest(httpreq))
Expect(err).Should(BeNil())
comp, err = appService.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("test-enterprise-registry.test-region.cr.aliyuncs.com/test-namespace/test-repo:test-tag"))
By("Test HandleApplicationWebhook function with harbor payload")
harborTrigger, err := appService.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
Name: "test-harbor",

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

@@ -129,6 +129,8 @@ type EnableAddonRequest struct {
Clusters []string `json:"clusters,omitempty"`
// Version specify the version of addon to enable
Version string `json:"version,omitempty"`
// RegistryName specify the registry name
RegistryName string `json:"registryName,omitempty"`
}
// ListAddonResponse defines the format for addon list response
@@ -504,6 +506,7 @@ type CreateApplicationTriggerRequest struct {
Type string `json:"type" validate:"oneof=webhook"`
PayloadType string `json:"payloadType" validate:"checkpayloadtype"`
ComponentName string `json:"componentName,omitempty" optional:"true"`
Registry string `json:"registry,omitempty" optional:"true"`
}
// ApplicationTriggerBase application trigger base model

View File

@@ -73,6 +73,9 @@ var (
// ErrCloudShellNotInit means the cloudshell CR not created
ErrCloudShellNotInit = NewBcode(400, 50021, "Closing the console window and retry")
// ErrRegistryNotExist means the specified registry not exist
ErrRegistryNotExist = NewBcode(400, 50022, "The specified not exist")
)
// isGithubRateLimit check if error is github rate limit

View File

@@ -19,6 +19,7 @@ package component
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
@@ -141,10 +142,18 @@ func SelectRefObjectsForDispatch(ctx context.Context, cli client.Client, appNs s
if err != nil {
return nil, err
}
isNamespaced, err := IsGroupVersionKindNamespaceScoped(cli.RESTMapper(), gvk)
if err != nil {
return nil, err
}
if selector.Name == "" && labelSelector != nil {
uns := &unstructured.UnstructuredList{}
uns.SetGroupVersionKind(gvk)
if err = cli.List(ctx, uns, client.InNamespace(ns), client.MatchingLabels(labelSelector)); err != nil {
opts := []client.ListOption{client.MatchingLabels(labelSelector)}
if isNamespaced {
opts = append(opts, client.InNamespace(ns))
}
if err = cli.List(ctx, uns, opts...); err != nil {
return nil, errors.Wrapf(err, "failed to load ref object %s with selector", gvk.Kind)
}
for _, _un := range uns.Items {
@@ -154,7 +163,9 @@ func SelectRefObjectsForDispatch(ctx context.Context, cli client.Client, appNs s
un := &unstructured.Unstructured{}
un.SetGroupVersionKind(gvk)
un.SetName(selector.Name)
un.SetNamespace(ns)
if isNamespaced {
un.SetNamespace(ns)
}
if selector.Name == "" {
un.SetName(compName)
}
@@ -248,3 +259,15 @@ func ConvertUnstructuredsToReferredObjects(uns []*unstructured.Unstructured) (re
}
return refObjs, nil
}
// IsGroupVersionKindNamespaceScoped check if the target GroupVersionKind is namespace scoped resource
func IsGroupVersionKindNamespaceScoped(mapper meta.RESTMapper, gvk schema.GroupVersionKind) (bool, error) {
mappings, err := mapper.RESTMappings(gvk.GroupKind(), gvk.Version)
if err != nil {
return false, err
}
if len(mappings) == 0 {
return false, fmt.Errorf("unable to fund the mappings for gvk %s", gvk)
}
return mappings[0].Scope.Name() == meta.RESTScopeNameNamespace, nil
}

View File

@@ -25,6 +25,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilfeature "k8s.io/apiserver/pkg/util/feature"
@@ -111,6 +112,10 @@ var _ = Describe("Test ref-objects functions", func() {
Namespace: "test",
Labels: map[string]string{"key": "value"},
},
}, &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster-role",
},
}} {
Expect(k8sClient.Create(context.Background(), obj)).Should(Succeed())
}
@@ -119,25 +124,26 @@ var _ = Describe("Test ref-objects functions", func() {
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
},
"metadata": map[string]interface{}{"name": name},
},
}
if namespace != "" {
un.SetNamespace(namespace)
}
if labels != nil {
un.Object["metadata"].(map[string]interface{})["labels"] = labels
}
return un
}
testcases := map[string]struct {
Input v1alpha1.ObjectReferrer
compName string
appNs string
Output []*unstructured.Unstructured
Error string
Scope string
IsService bool
Input v1alpha1.ObjectReferrer
compName string
appNs string
Output []*unstructured.Unstructured
Error string
Scope string
IsService bool
IsClusterRole bool
}{
"normal": {
Input: v1alpha1.ObjectReferrer{
@@ -259,6 +265,16 @@ var _ = Describe("Test ref-objects functions", func() {
Scope: RefObjectsAvailableScopeCluster,
Error: "cannot refer to objects outside control plane",
},
"test-cluster-scope-resource": {
Input: v1alpha1.ObjectReferrer{
ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "clusterrole"},
ObjectSelector: v1alpha1.ObjectSelector{Name: "test-cluster-role"},
},
appNs: "test",
Scope: RefObjectsAvailableScopeCluster,
Output: []*unstructured.Unstructured{createUnstructured("rbac.authorization.k8s.io/v1", "ClusterRole", "test-cluster-role", "", nil)},
IsClusterRole: true,
},
}
for name, tt := range testcases {
By("Test " + name)
@@ -276,6 +292,9 @@ var _ = Describe("Test ref-objects functions", func() {
Expect(output[0].Object["kind"]).Should(Equal("Service"))
Expect(output[0].Object["spec"].(map[string]interface{})["clusterIP"]).Should(BeNil())
} else {
if tt.IsClusterRole {
delete(output[0].Object, "rules")
}
Expect(output).Should(Equal(tt.Output))
}
}

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

@@ -365,10 +365,9 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string, param
for _, auxiliary := range auxiliaries {
target := outputsPatcher.LookupPath(value.FieldPath(auxiliary.Name))
if !target.Exists() {
return errors.WithMessagef(err, "trait=%s, to=%s, invalid patch trait into auxiliary workload", td.name, auxiliary.Name)
continue
}
patcher := outputsPatcher.LookupPath(value.FieldPath(auxiliary.Name))
if err := auxiliary.Ins.Unify(patcher); err != nil {
if err = auxiliary.Ins.Unify(target); err != nil {
return errors.WithMessagef(err, "trait=%s, to=%s, invalid patch trait into auxiliary workload", td.name, auxiliary.Name)
}
}

View File

@@ -1480,3 +1480,57 @@ if len(context.outputs.ingress.status.loadBalancer.ingress) == 0 {
assert.Equal(t, ca.expMessage, gotMessage, message)
}
}
func TestTraitPatchSingleOutput(t *testing.T) {
baseTemplate := `
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: selector: matchLabels: "app.oam.dev/component": context.name
}
outputs: gameconfig: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: name: context.name + "game-config"
data: {}
}
outputs: sideconfig: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: name: context.name + "side-config"
data: {}
}
parameter: {}
`
traitTemplate := `
patchOutputs: sideconfig: data: key: "val"
parameter: {}
`
ctx := process.NewContext(process.ContextData{
AppName: "myapp",
CompName: "test",
Namespace: "default",
AppRevisionName: "myapp-v1",
})
wt := NewWorkloadAbstractEngine("-", &packages.PackageDiscover{})
if err := wt.Complete(ctx, baseTemplate, map[string]interface{}{}); err != nil {
t.Error(err)
return
}
td := NewTraitAbstractEngine("single-patch", &packages.PackageDiscover{})
r := require.New(t)
err := td.Complete(ctx, traitTemplate, map[string]string{})
r.NoError(err)
base, assists := ctx.Output()
r.NotNil(base)
r.Equal(2, len(assists))
got, err := assists[1].Ins.Unstructured()
r.NoError(err)
val, ok, err := unstructured.NestedString(got.Object, "data", "key")
r.NoError(err)
r.True(ok)
r.Equal("val", val)
}

View File

@@ -51,6 +51,9 @@ func TestCreateEnv(t *testing.T) {
var err error
cfg, err = testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
assert.NoError(t, clientgoscheme.AddToScheme(testScheme))
rawClient, err = client.New(cfg, client.Options{Scheme: testScheme})
@@ -95,4 +98,5 @@ func TestCreateEnv(t *testing.T) {
}
})
}
}

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

@@ -148,6 +148,8 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com
vela addon enable <your-local-addon-path>
Enable addon with specified args (the args should be defined in addon's parameters):
vela addon enable <addon-name> <my-parameter-of-addon>=<my-value>
Enable addon with specified registry:
vela addon enable <registryName>/<addonName>
`,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -543,10 +545,27 @@ func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.Dis
if err != nil {
return err
}
for _, registry := range registries {
registryName, addonName, err := splitSpecifyRegistry(name)
if err != nil {
return err
}
if len(registryName) != 0 {
foundRegistry := false
for _, registry := range registries {
if registry.Name == registryName {
foundRegistry = true
}
}
if !foundRegistry {
return fmt.Errorf("specified registry %s not exist", registryName)
}
}
for i, registry := range registries {
opts := addonOptions()
err = pkgaddon.EnableAddon(ctx, name, version, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil, opts...)
if len(registryName) != 0 && registryName != registry.Name {
continue
}
err = pkgaddon.EnableAddon(ctx, addonName, version, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil, pkgaddon.FilterDependencyRegistries(i, registries), opts...)
if errors.Is(err, pkgaddon.ErrNotExist) {
continue
}
@@ -558,21 +577,24 @@ func enableAddon(ctx context.Context, k8sClient client.Client, dc *discovery.Dis
}
input := NewUserInput()
if input.AskBool(unMatchErr.Error(), &UserInputOptions{AssumeYes: false}) {
err = pkgaddon.EnableAddon(ctx, name, availableVersion, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil)
err = pkgaddon.EnableAddon(ctx, addonName, availableVersion, k8sClient, dc, apply.NewAPIApplicator(k8sClient), config, registry, args, nil, pkgaddon.FilterDependencyRegistries(i, registries))
return err
}
// The user does not agree to use the version provided by us
return fmt.Errorf("you can try another version by command: \"vela addon enable %s --version <version> \" ", name)
return fmt.Errorf("you can try another version by command: \"vela addon enable %s --version <version> \" ", addonName)
}
if err != nil {
return err
}
if err = waitApplicationRunning(k8sClient, name); err != nil {
if err = waitApplicationRunning(k8sClient, addonName); err != nil {
return err
}
return nil
}
return fmt.Errorf("addon: %s not found in registries", name)
if len(registryName) != 0 {
return fmt.Errorf("addon: %s not found in registry %s", addonName, registryName)
}
return fmt.Errorf("addon: %s not found in all candidate registries", addonName)
}
func addonOptions() []pkgaddon.InstallOption {
@@ -1136,3 +1158,15 @@ func NewAddonPackageCommand(c common.Args) *cobra.Command {
}
return cmd
}
func splitSpecifyRegistry(name string) (string, string, error) {
res := strings.Split(name, "/")
switch len(res) {
case 2:
return res[0], res[1], nil
case 1:
return "", res[0], nil
default:
return "", "", fmt.Errorf("invalid addon name, you should specify name only <addonName> or with registry as prefix <registryName>/<addonName>")
}
}

View File

@@ -443,3 +443,37 @@ func TestNewAddonCreateCommand(t *testing.T) {
_ = os.RemoveAll("test-addon")
}
func TestCheckSpecifyRegistry(t *testing.T) {
testCases := []struct {
name string
registry string
addonName string
hasError bool
}{
{
name: "fluxcd",
registry: "",
addonName: "fluxcd",
hasError: false,
},
{
name: "kubevela/fluxcd",
registry: "kubevela",
addonName: "fluxcd",
hasError: false,
},
{
name: "test/kubevela/fluxcd",
registry: "",
addonName: "",
hasError: true,
},
}
for _, testCase := range testCases {
r, n, err := splitSpecifyRegistry(testCase.name)
assert.Equal(t, err != nil, testCase.hasError)
assert.Equal(t, r, testCase.registry)
assert.Equal(t, n, testCase.addonName)
}
}

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

@@ -33,6 +33,9 @@ func TestInfo(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
info := NewInfo()
info.Init(cfg)
assert.Equal(t, info.GetColumnCount(), 7)

View File

@@ -38,6 +38,9 @@ func TestApp(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -38,6 +38,10 @@ func TestTopologyView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -39,6 +39,10 @@ func TestApplicationView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -38,6 +38,10 @@ func TestClusterNamespaceView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")
@@ -83,4 +87,5 @@ func TestClusterNamespaceView(t *testing.T) {
cnsView.Table.Table = cnsView.Table.Select(1, 1)
assert.Empty(t, cnsView.managedResourceView(nil))
})
}

View File

@@ -39,6 +39,10 @@ func TestClusterView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -39,6 +39,10 @@ func TestContainerView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -36,6 +36,10 @@ func TestHelpView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -39,6 +39,10 @@ func TestLogView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -39,6 +39,10 @@ func TestManagedResourceView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -39,6 +39,10 @@ func TestNamespaceView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -36,6 +36,10 @@ func TestPageStack(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -39,6 +39,10 @@ func TestPodView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

View File

@@ -38,6 +38,10 @@ func TestYamlView(t *testing.T) {
}
cfg, err := testEnv.Start()
assert.NoError(t, err)
defer func() {
assert.NoError(t, testEnv.Stop())
}()
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
assert.NoError(t, err)
app := NewApp(testClient, cfg, "")

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,23 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: example-app-rollout
namespace: default
spec:
components:
- name: hello-world-server
type: webservice
properties:
image: crccheck/hello-world
ports:
- port: 8000
expose: true
type: webservice
policies:
- name: health-policy-demo
type: health
properties:
probeInterval: 5
probeTimeout: 10
```

View File

@@ -0,0 +1,29 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: busybox
spec:
components:
- name: busybox
type: webservice
properties:
image: busybox
cmd: ["sleep", "86400"]
labels:
label-key: label-value
to-delete-label-key: to-delete-label-value
traits:
- type: affinity
properties:
podAffinity:
preferred:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: "secrity"
values: ["S1"]
namespaces: ["default"]
topologyKey: "kubernetes.io/hostname"
```

View File

@@ -0,0 +1,41 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: busybox
spec:
components:
- name: busybox
type: webservice
properties:
image: busybox
cmd: ["sleep", "86400"]
labels:
pod-label-key: pod-label-value
to-delete-label-key: to-delete-label-value
traits:
# the json merge patch can be used to add, replace and delete fields
# the following part will
# 1. add `deploy-label-key` to deployment labels
# 2. set deployment replicas to 3
# 3. set `pod-label-key` to `pod-label-modified-value` in pod labels
# 4. delete `to-delete-label-key` in pod labels
# 5. reset `containers` for pod
- type: json-merge-patch
properties:
metadata:
labels:
deploy-label-key: deploy-label-added-value
spec:
replicas: 3
template:
metadata:
labels:
pod-label-key: pod-label-modified-value
to-delete-label-key: null
spec:
containers:
- name: busybox-new
image: busybox:1.34
command: ["sleep", "864000"]
```

View File

@@ -0,0 +1,41 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: busybox
spec:
components:
- name: busybox
type: webservice
properties:
image: busybox
cmd: ["sleep", "86400"]
labels:
pod-label-key: pod-label-value
to-delete-label-key: to-delete-label-value
traits:
# the json patch can be used to add, replace and delete fields
# the following part will
# 1. add `deploy-label-key` to deployment labels
# 2. set deployment replicas to 3
# 3. set `pod-label-key` to `pod-label-modified-value` in pod labels
# 4. delete `to-delete-label-key` in pod labels
# 5. add sidecar container for pod
- type: json-patch
properties:
operations:
- op: add
path: "/spec/replicas"
value: 3
- op: replace
path: "/spec/template/metadata/labels/pod-label-key"
value: pod-label-modified-value
- op: remove
path: "/spec/template/metadata/labels/to-delete-label-key"
- op: add
path: "/spec/template/spec/containers/1"
value:
name: busybox-sidecar
image: busybox:1.34
command: ["sleep", "864000"]
```

View File

@@ -0,0 +1,40 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: application-with-topologyspreadconstraints
spec:
components:
- name: busybox-runner
type: worker
properties:
image: busybox
cmd:
- sleep
- '1000'
traits:
- type: topologyspreadconstraints
properties:
constraints:
- topologyKey: zone
labelSelector:
matchLabels:
zone: us-east-1a
maxSkew: 1
whenUnsatisfiable: DoNotSchedule
minDomains: 1
nodeAffinityPolicy: Ignore
nodeTaintsPolicy: Ignore
- topologyKey: node
labelSelector:
matchExpressions:
- key: foo
operator: In
values:
- abc
maxSkew: 1
whenUnsatisfiable: ScheduleAnyway
minDomains: 1
nodeAffinityPolicy: Ignore
nodeTaintsPolicy: Ignore
```

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,32 @@
```yaml
kind: Application
apiVersion: core.oam.dev/v1beta1
metadata:
name: test-config
namespace: "config-e2e-test"
spec:
components: []
workflow:
steps:
- name: write-config
type: create-config
properties:
name: test
config:
key1: value1
key2: 2
key3: true
key4:
key5: value5
- name: read-config
type: read-config
properties:
name: test
outputs:
- fromKey: config
name: read-config
- name: delete-config
type: delete-config
properties:
name: test
```

View File

@@ -0,0 +1,25 @@
```yaml
kind: Application
apiVersion: core.oam.dev/v1beta1
metadata:
name: test-config
namespace: "config-e2e-test"
spec:
components: []
workflow:
steps:
- name: write-config
type: create-config
properties:
name: test
config:
key1: value1
key2: 2
key3: true
key4:
key5: value5
- name: delete-config
type: delete-config
properties:
name: test
```

View File

@@ -0,0 +1,80 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: rds-app
namespace: project-1
spec:
components:
- name: db
type: alibaba-rds
properties:
instance_name: db
account_name: kubevela
password: my-password
writeConnectionSecretToRef:
name: project-1-rds-conn-credential
policies:
- name: env-policy
type: env-binding
properties:
envs:
# 部署 RDS 给杭州集群
- name: hangzhou
placement:
clusterSelector:
name: cluster-hangzhou
patch:
components:
- name: db
type: alibaba-rds
properties:
# region: hangzhou
instance_name: hangzhou_db
# 部署 RDS 给香港集群
- name: hongkong
placement:
clusterSelector:
name: cluster-hongkong
namespaceSelector:
name: hk-project-1
patch:
components:
- name: db
type: alibaba-rds
properties:
# region: hongkong
instance_name: hongkong_db
writeConnectionSecretToRef:
name: hk-project-rds-credential
workflow:
steps:
# 部署 RDS 给杭州区用
- name: deploy-hangzhou-rds
type: deploy-cloud-resource
properties:
env: hangzhou
# 将给杭州区用的 RDS 共享给北京区
- name: share-hangzhou-rds-to-beijing
type: share-cloud-resource
properties:
env: hangzhou
placements:
- cluster: cluster-beijing
# 部署 RDS 给香港区用
- name: deploy-hongkong-rds
type: deploy-cloud-resource
properties:
env: hongkong
# 将给香港区用的 RDS 共享给香港区其他项目用
- name: share-hongkong-rds-to-other-namespace
type: share-cloud-resource
properties:
env: hongkong
placements:
- cluster: cluster-hongkong
namespace: hk-project-2
- cluster: cluster-hongkong
namespace: hk-project-3
```

View File

@@ -0,0 +1,42 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: jdbc
spec:
components:
- name: db
type: alibaba-rds
properties:
instance_name: favorite-links
database_name: db1
account_name: oamtest
password: U34rfwefwefffaked
security_ips: [ "0.0.0.0/0" ]
privilege: ReadWrite
writeConnectionSecretToRef:
name: db-conn
- name: express-server
type: webservice
properties:
image: crccheck/hello-world
port: 8000
workflow:
steps:
- name: jdbc
type: generate-jdbc-connection
outputs:
- name: jdbc
valueFrom: jdbc
properties:
name: db-conn
namespace: default
- name: apply
type: apply-component
inputs:
- from: jdbc
parameterKey: env
properties:
component: express-server
```

View File

@@ -0,0 +1,50 @@
```yaml
apiVersion: core.oam.dev/v1alpha1
kind: WorkflowRun
metadata:
name: observability
namespace: vela-system
spec:
context:
readConfig: true
mode:
workflowSpec:
steps:
- name: Enable Prism
type: addon-operation
properties:
addonName: vela-prism
- name: Enable o11y
type: addon-operation
properties:
addonName: o11y-definitions
operation: enable
args:
- --override-definitions
- name: Prepare Prometheus
type: step-group
subSteps:
- name: get-exist-prometheus
type: list-config
properties:
template: prometheus-server
outputs:
- name: prometheus
valueFrom: "output.configs"
- name: prometheus-server
inputs:
- from: prometheus
# TODO: Make it is not required
parameterKey: configs
if: "!context.readConfig || len(inputs.prometheus) == 0"
type: addon-operation
properties:
addonName: prometheus-server
operation: enable
args:
- memory=4096Mi
- serviceType=LoadBalancer
```

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

@@ -0,0 +1,18 @@
```yaml
kind: Application
apiVersion: core.oam.dev/v1beta1
metadata:
name: test-config
namespace: "config-e2e-test"
spec:
components: []
workflow:
steps:
- name: read-config
type: read-config
properties:
name: test
outputs:
- fromKey: config
name: read-config
```

View File

@@ -0,0 +1,80 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: rds-app
namespace: project-1
spec:
components:
- name: db
type: alibaba-rds
properties:
instance_name: db
account_name: kubevela
password: my-password
writeConnectionSecretToRef:
name: project-1-rds-conn-credential
policies:
- name: env-policy
type: env-binding
properties:
envs:
# 部署 RDS 给杭州集群
- name: hangzhou
placement:
clusterSelector:
name: cluster-hangzhou
patch:
components:
- name: db
type: alibaba-rds
properties:
# region: hangzhou
instance_name: hangzhou_db
# 部署 RDS 给香港集群
- name: hongkong
placement:
clusterSelector:
name: cluster-hongkong
namespaceSelector:
name: hk-project-1
patch:
components:
- name: db
type: alibaba-rds
properties:
# region: hongkong
instance_name: hongkong_db
writeConnectionSecretToRef:
name: hk-project-rds-credential
workflow:
steps:
# 部署 RDS 给杭州区用
- name: deploy-hangzhou-rds
type: deploy-cloud-resource
properties:
env: hangzhou
# 将给杭州区用的 RDS 共享给北京区
- name: share-hangzhou-rds-to-beijing
type: share-cloud-resource
properties:
env: hangzhou
placements:
- cluster: cluster-beijing
# 部署 RDS 给香港区用
- name: deploy-hongkong-rds
type: deploy-cloud-resource
properties:
env: hongkong
# 将给香港区用的 RDS 共享给香港区其他项目用
- name: share-hongkong-rds-to-other-namespace
type: share-cloud-resource
properties:
env: hongkong
placements:
- cluster: cluster-hongkong
namespace: hk-project-2
- cluster: cluster-hongkong
namespace: hk-project-3
```

View File

@@ -47,6 +47,7 @@ const AllComponentTypes = "*"
type MarkdownReference struct {
Filter func(types.Capability) bool
AllInOne bool
ForceExample bool
CustomDocHeader string
DiscoveryMapper discoverymapper.DiscoveryMapper
ParseReference
@@ -255,6 +256,9 @@ func (ref *MarkdownReference) GenerateMarkdownForCap(ctx context.Context, c type
if sampleContent != "" {
sample = fmt.Sprintf("\n\n%s %s\n\n%s", sharp, exampleTitle, sampleContent)
} else if ref.ForceExample {
fmt.Printf("You must provide example doc for the new added definition \"%s\", place the example doc in the /refereces/docgen/def-doc folders, for more details refer to https://kubevela.io/docs/contributor/cli-ref-doc#how-the-docs-generated", capName)
os.Exit(1)
}
if c.Category == types.CUECategory && baseDoc != "" {
base = fmt.Sprintf("\n\n%s %s\n\n%s", sharp, baseTitle, baseDoc)

View File

@@ -213,4 +213,54 @@ 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())
})
})
Describe("Test addon dependency addon in other registry", func() {
It("Test Operation of enable addon from other registry", func() {
req := apisv1.EnableAddonRequest{}
res := post("/addons/mock-dep-addon/enable", req)
defer res.Body.Close()
var addon apisv1.AddonStatusResponse
Expect(decodeResponseBody(res, &addon)).Should(Succeed())
Expect(addon.Name).Should(BeEquivalentTo("mock-dep-addon"))
Eventually(func(g Gomega) {
status := get("/addons/mock-dep-addon/status")
var newaddonStatus apisv1.AddonStatusResponse
g.Expect(decodeResponseBody(status, &newaddonStatus)).Should(Succeed())
g.Expect(newaddonStatus.Name).Should(BeEquivalentTo("mock-dep-addon"))
g.Expect(newaddonStatus.InstalledVersion).Should(BeEquivalentTo("v1.0.0"))
g.Expect(newaddonStatus.Phase).Should(BeEquivalentTo(apisv1.AddonPhaseEnabled))
}, 30*time.Second, 300*time.Millisecond).Should(Succeed())
})
})
Describe("Test enable an addon with specified registry", func() {
It("Test with a not exist registry", func() {
req := apisv1.EnableAddonRequest{
RegistryName: "not-exist",
}
res := post("/addons/test-addon/enable", req)
defer res.Body.Close()
Expect(res.StatusCode).Should(BeEquivalentTo(400))
})
})
})

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
}
}

Some files were not shown because too many files have changed in this diff Show More