mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-24 23:04:10 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a783393ebd | ||
|
|
a19ed0b510 | ||
|
|
03223aa786 | ||
|
|
55c8dad116 | ||
|
|
38c57c38c8 | ||
|
|
7f734e9479 | ||
|
|
7814232b7c | ||
|
|
b1cc06b0f3 | ||
|
|
ed9d53b448 | ||
|
|
ad83e59865 | ||
|
|
b62eeca3f9 | ||
|
|
5d9757fcb8 | ||
|
|
4d653951a1 | ||
|
|
bcda4976a9 | ||
|
|
a01d0e773a |
44
.github/workflows/definition-lint.yml
vendored
Normal file
44
.github/workflows/definition-lint.yml
vendored
Normal 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
|
||||
@@ -189,8 +189,6 @@ type Status struct {
|
||||
type ApplicationPhase string
|
||||
|
||||
const (
|
||||
// ApplicationRollingOut means the app is in the middle of rolling out
|
||||
ApplicationRollingOut ApplicationPhase = "rollingOut"
|
||||
// ApplicationStarting means the app is preparing for reconcile
|
||||
ApplicationStarting ApplicationPhase = "starting"
|
||||
// ApplicationRendering means the app is rendering
|
||||
@@ -205,8 +203,6 @@ const (
|
||||
ApplicationWorkflowTerminated ApplicationPhase = "workflowTerminated"
|
||||
// ApplicationWorkflowFailed means the app's workflow is failed
|
||||
ApplicationWorkflowFailed ApplicationPhase = "workflowFailed"
|
||||
// ApplicationWorkflowFinished means the app's workflow is finished
|
||||
ApplicationWorkflowFinished ApplicationPhase = "workflowFinished"
|
||||
// ApplicationRunning means the app finished rendering and applied result to the cluster
|
||||
ApplicationRunning ApplicationPhase = "running"
|
||||
// ApplicationUnhealthy means the app finished rendering and applied result to the cluster, but still unhealthy
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/apply-component.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Apply a specific component and its corresponding traits in application
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-component
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
// +usage=Specify the component name to apply
|
||||
component: string
|
||||
// +usage=Specify the cluster
|
||||
cluster: *"" | string
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: pring message in workflow status
|
||||
definition.oam.dev/description: print message in workflow step status
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: print-message-in-status
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/apply-component.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Apply a specific component and its corresponding traits in application
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-component
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
// +usage=Specify the component name to apply
|
||||
component: string
|
||||
// +usage=Specify the cluster
|
||||
cluster: *"" | string
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: pring message in workflow status
|
||||
definition.oam.dev/description: print message in workflow step status
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: print-message-in-status
|
||||
|
||||
8
e2e/addon/mock/testdata/mock-dep-addon/metadata.yaml
vendored
Normal file
8
e2e/addon/mock/testdata/mock-dep-addon/metadata.yaml
vendored
Normal 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
|
||||
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz
Normal file
Binary file not shown.
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz
Normal file
Binary file not shown.
BIN
e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz
Normal file
Binary file not shown.
@@ -34,4 +34,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"
|
||||
BIN
e2e/addon/mock/testrepo/helm-repo/mock-be-dep-addon-v1.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/mock-be-dep-addon-v1.0.0.tgz
Normal file
Binary file not shown.
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ var (
|
||||
appbasicJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
|
||||
appbasicAddTraitJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}],"scaler":{"replicas":2}}}}`
|
||||
velaQL = "test-component-pod-view{appNs=default,appName=nginx-vela,name=nginx}"
|
||||
|
||||
waitAppfileToSuccess = `{"name":"app-wait-success","services":{"app-basic1":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
|
||||
waitAppfileToFail = `{"name":"app-wait-fail","services":{"app-basic2":{"type":"webservice","image":"nginx:fail","ports":[{port: 80, expose: true}]}}}`
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("Test Vela Application", func() {
|
||||
@@ -75,6 +78,9 @@ var _ = ginkgo.Describe("Test Vela Application", func() {
|
||||
|
||||
e2e.JsonAppFileContext("json appfile apply", testDeleteJsonAppFile)
|
||||
VelaQLPodListContext("ql", velaQL)
|
||||
|
||||
e2e.JsonAppFileContextWithWait("json appfile apply with wait", waitAppfileToSuccess)
|
||||
e2e.JsonAppFileContextWithTimeout("json appfile apply with wait but timeout", waitAppfileToFail, "3s")
|
||||
})
|
||||
|
||||
var ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
|
||||
@@ -182,7 +188,7 @@ var ApplicationInitIntercativeCliContext = func(context string, appName string,
|
||||
c.ExpectEOF()
|
||||
})
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Checking Status"))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Waiting app to be healthy"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,6 +84,30 @@ var (
|
||||
})
|
||||
}
|
||||
|
||||
JsonAppFileContextWithWait = func(context, jsonAppFile string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("Start the application through the app file in JSON format.", func() {
|
||||
writeStatus := os.WriteFile("vela.json", []byte(jsonAppFile), 0644)
|
||||
gomega.Expect(writeStatus).NotTo(gomega.HaveOccurred())
|
||||
output, err := Exec("vela up -f vela.json --wait")
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Application Deployed Successfully!"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
JsonAppFileContextWithTimeout = func(context, jsonAppFile, duration string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("Start the application through the app file in JSON format.", func() {
|
||||
writeStatus := os.WriteFile("vela.json", []byte(jsonAppFile), 0644)
|
||||
gomega.Expect(writeStatus).NotTo(gomega.HaveOccurred())
|
||||
output, err := Exec("vela up -f vela.json --wait --timeout=" + duration)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Timeout waiting Application to be healthy!"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
DeleteEnvFunc = func(context string, envName string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("should print env does not exist message", func() {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -58,7 +58,7 @@ require (
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
||||
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2
|
||||
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f
|
||||
github.com/kubevela/workflow v0.3.2
|
||||
github.com/kubevela/workflow v0.3.3
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.1
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1335,8 +1335,8 @@ github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2 h1:C3cAfrxst1+dIWgLLh
|
||||
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2/go.mod h1:TgIGEB/r0NOy63Jzem7WsL3AIr34l+ClH9dmPqcZ4d4=
|
||||
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f h1:1lUtU1alPThdcsn4MI6XjPb7eJLuZPpmlEdgjtnUMKw=
|
||||
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f/go.mod h1:m724/7ANnB/iukyHW20+DicpeJMEC/JA0ZhgsHY10MA=
|
||||
github.com/kubevela/workflow v0.3.2 h1:r9jznJN5Tzwkg002qiHduhSD/iDwX+F+lG72yj2B5Eo=
|
||||
github.com/kubevela/workflow v0.3.2/go.mod h1:5jfZ8T1m/En44wDGRf2YqCSlODfEnAV+9PnzoLoDlFs=
|
||||
github.com/kubevela/workflow v0.3.3 h1:NSbQGcABWJIzUV5wfWFJsrO/ffZ4mCVfofUtUHCTojQ=
|
||||
github.com/kubevela/workflow v0.3.3/go.mod h1:5jfZ8T1m/En44wDGRf2YqCSlODfEnAV+9PnzoLoDlFs=
|
||||
github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
|
||||
github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
|
||||
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
25
hack/docgen/def/mods/types.go
Normal file
25
hack/docgen/def/mods/types.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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, ", "))
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ®istries[i], nil, nil)
|
||||
installer := NewAddonInstaller(ctx, k8sClient, nil, nil, config, ®istries[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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/pkg/errors"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
|
||||
@@ -146,7 +147,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
|
||||
sort.Sort(sort.Reverse(versions))
|
||||
addonVersion, availableVersions := chooseVersion(version, versions)
|
||||
if addonVersion == nil {
|
||||
return nil, fmt.Errorf("specified version %s not exist", version)
|
||||
return nil, errors.Errorf("specified version %s not exist", utils.Sanitize(version))
|
||||
}
|
||||
for _, chartURL := range addonVersion.URLs {
|
||||
if !utils.IsValidURL(chartURL) {
|
||||
|
||||
@@ -247,6 +247,9 @@ var RevisionStatusTerminated = "terminated"
|
||||
// RevisionStatusRollback event status rollback
|
||||
var RevisionStatusRollback = "rollback"
|
||||
|
||||
// WorkflowStepPhaseStopped is the stopped phase
|
||||
var WorkflowStepPhaseStopped workflowv1alpha1.WorkflowStepPhase = "stopped"
|
||||
|
||||
// ApplicationRevision be created when an application initiates deployment and describes the phased version of the application.
|
||||
type ApplicationRevision struct {
|
||||
BaseModel
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -210,7 +210,7 @@ func (p pipelineServiceImpl) ListPipelines(ctx context.Context, req apis.ListPip
|
||||
info, err = p.getPipelineInfo(ctx, pipeline, projectMap[pipeline.Project].Namespace)
|
||||
if err != nil {
|
||||
// Since we are listing pipelines. We should not return directly if we cannot get pipeline info
|
||||
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pipeline.Name, err)
|
||||
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pkgutils.Sanitize(pipeline.Name), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -248,7 +248,7 @@ func (p pipelineServiceImpl) GetPipeline(ctx context.Context, name string, getIn
|
||||
if getInfo {
|
||||
in, err := p.getPipelineInfo(ctx, pipeline, project.Namespace)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pipeline.Name, err)
|
||||
log.Logger.Errorf("get pipeline %s/%s info error: %v", pipeline.Project, pkgutils.Sanitize(pipeline.Name), err)
|
||||
return nil, bcode.ErrGetPipelineInfo
|
||||
}
|
||||
if in != nil {
|
||||
@@ -1031,7 +1031,7 @@ func (c contextServiceImpl) CreateContext(ctx context.Context, projectName, pipe
|
||||
}
|
||||
}
|
||||
if _, ok := modelCtx.Contexts[context.Name]; ok {
|
||||
log.Logger.Errorf("context %s already exists", context.Name)
|
||||
log.Logger.Errorf("context %s already exists", pkgutils.Sanitize(context.Name))
|
||||
return nil, bcode.ErrContextAlreadyExist
|
||||
}
|
||||
modelCtx.Contexts[context.Name] = context.Values
|
||||
|
||||
@@ -52,6 +52,9 @@ func guaranteePolicyNotExist(c []string, policy string) ([]string, bool) {
|
||||
// extractPolicyListAndProperty can extract policy from string-format properties, and return
|
||||
// map-format properties in order to further update operation.
|
||||
func extractPolicyListAndProperty(property string) ([]string, map[string]interface{}, error) {
|
||||
if len(property) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
content := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(property), &content)
|
||||
if err != nil {
|
||||
|
||||
@@ -208,6 +208,14 @@ func TestExtractPolicyListAndProperty(t *testing.T) {
|
||||
noError bool
|
||||
}{noError: false},
|
||||
},
|
||||
{
|
||||
input: ``,
|
||||
res: struct {
|
||||
policies []string
|
||||
properties map[string]interface{}
|
||||
noError bool
|
||||
}{policies: nil, properties: nil, noError: true},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
policy, properties, err := extractPolicyListAndProperty(testCase.input)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -623,7 +623,7 @@ func resetRevisionsAndRecords(ctx context.Context, ds datastore.DataStore, appNa
|
||||
record.Finished = "true"
|
||||
for i, step := range record.Steps {
|
||||
if step.Phase == workflowv1alpha1.WorkflowStepPhaseRunning {
|
||||
record.Steps[i].Phase = workflowv1alpha1.WorkflowStepPhaseStopped
|
||||
record.Steps[i].Phase = model.WorkflowStepPhaseStopped
|
||||
}
|
||||
}
|
||||
if err := ds.Put(ctx, record); err != nil {
|
||||
|
||||
@@ -586,7 +586,7 @@ var _ = Describe("Test workflow service functions", func() {
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(record.Status).Should(Equal(model.RevisionStatusTerminated))
|
||||
Expect(record.Finished).Should(Equal("true"))
|
||||
Expect(record.Steps[1].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseStopped))
|
||||
Expect(record.Steps[1].Phase).Should(Equal(model.WorkflowStepPhaseStopped))
|
||||
})
|
||||
|
||||
It("Test deleting workflow", func() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3757,11 +3757,11 @@ var _ = Describe("Test Application Controller", func() {
|
||||
By("Check debug Config Map is created")
|
||||
debugCM := &corev1.ConfigMap{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{
|
||||
Name: debug.GenerateContextName(app.Name, "step1"),
|
||||
Name: debug.GenerateContextName(app.Name, "step1", string(app.UID)),
|
||||
Namespace: "default",
|
||||
}, debugCM)).Should(BeNil())
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{
|
||||
Name: debug.GenerateContextName(app.Name, "step2-sub1"),
|
||||
Name: debug.GenerateContextName(app.Name, "step2-sub1", string(app.UID)),
|
||||
Namespace: "default",
|
||||
}, debugCM)).Should(BeNil())
|
||||
|
||||
@@ -3770,7 +3770,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
updatedCM := &corev1.ConfigMap{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{
|
||||
Name: debug.GenerateContextName(app.Name, "step1"),
|
||||
Name: debug.GenerateContextName(app.Name, "step1", string(app.UID)),
|
||||
Namespace: "default",
|
||||
}, updatedCM)).Should(BeNil())
|
||||
|
||||
|
||||
@@ -162,16 +162,13 @@ func needRestart(app *v1beta1.Application, revName string) (string, bool) {
|
||||
}
|
||||
|
||||
func generateWorkflowInstance(af *appfile.Appfile, app *v1beta1.Application, appRev string) *wfTypes.WorkflowInstance {
|
||||
anno := make(map[string]string)
|
||||
if af.Debug {
|
||||
anno[wfTypes.AnnotationWorkflowRunDebug] = "true"
|
||||
}
|
||||
instance := &wfTypes.WorkflowInstance{
|
||||
WorkflowMeta: wfTypes.WorkflowMeta{
|
||||
Name: af.Name,
|
||||
Namespace: af.Namespace,
|
||||
Annotations: app.Annotations,
|
||||
Labels: app.Labels,
|
||||
UID: app.UID,
|
||||
ChildOwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
@@ -237,6 +234,7 @@ func generateWorkflowInstance(af *appfile.Appfile, app *v1beta1.Application, app
|
||||
func convertStepProperties(step *workflowv1alpha1.WorkflowStep, app *v1beta1.Application) error {
|
||||
o := struct {
|
||||
Component string `json:"component"`
|
||||
Cluster string `json:"cluster"`
|
||||
}{}
|
||||
js, err := common.RawExtensionPointer{RawExtension: step.Properties}.MarshalJSON()
|
||||
if err != nil {
|
||||
@@ -262,6 +260,9 @@ func convertStepProperties(step *workflowv1alpha1.WorkflowStep, app *v1beta1.App
|
||||
if parameterKey != "" && !strings.HasPrefix(parameterKey, "properties") && !strings.HasPrefix(parameterKey, "traits[") {
|
||||
parameterKey = "properties." + parameterKey
|
||||
}
|
||||
if parameterKey != "" {
|
||||
parameterKey = "value." + parameterKey
|
||||
}
|
||||
step.Inputs[index].ParameterKey = parameterKey
|
||||
}
|
||||
step.Outputs = append(step.Outputs, c.Outputs...)
|
||||
@@ -269,7 +270,11 @@ func convertStepProperties(step *workflowv1alpha1.WorkflowStep, app *v1beta1.App
|
||||
c.Inputs = nil
|
||||
c.Outputs = nil
|
||||
c.DependsOn = nil
|
||||
step.Properties = util.Object2RawExtension(c)
|
||||
stepProperties := map[string]interface{}{
|
||||
"value": c,
|
||||
"cluster": o.Cluster,
|
||||
}
|
||||
step.Properties = util.Object2RawExtension(stepProperties)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
4
pkg/utils/env/env_test.go
vendored
4
pkg/utils/env/env_test.go
vendored
@@ -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) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,21 +38,22 @@ import (
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
"github.com/kubevela/workflow/pkg/debug"
|
||||
"github.com/kubevela/workflow/pkg/tasks/custom"
|
||||
wfTypes "github.com/kubevela/workflow/pkg/types"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/appfile"
|
||||
)
|
||||
|
||||
type debugOpts struct {
|
||||
step string
|
||||
focus string
|
||||
errMsg string
|
||||
opts []string
|
||||
errMap map[string]string
|
||||
// TODO: (fog) add watch flag
|
||||
// watch bool
|
||||
}
|
||||
@@ -61,25 +62,32 @@ type debugOpts struct {
|
||||
func NewDebugCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
ctx := context.Background()
|
||||
dOpts := &debugOpts{}
|
||||
wargs := &WorkflowArgs{
|
||||
Args: c,
|
||||
Writer: ioStreams.Out,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "debug",
|
||||
Aliases: []string{"debug"},
|
||||
Short: "Debug running application",
|
||||
Long: "Debug running application with debug policy.",
|
||||
Example: `vela debug <application-name>`,
|
||||
PreRun: wargs.checkDebugMode(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("must specify application name")
|
||||
}
|
||||
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
|
||||
if err != nil {
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appfile.LoadApplication(namespace, args[0], c)
|
||||
if err != nil {
|
||||
return err
|
||||
if wargs.Type == instanceTypeWorkflowRun {
|
||||
return fmt.Errorf("please use `vela workflow debug <name>` instead")
|
||||
}
|
||||
return dOpts.debugApplication(ctx, c, app, ioStreams)
|
||||
if wargs.App == nil {
|
||||
return fmt.Errorf("application %s not found", args[0])
|
||||
}
|
||||
|
||||
return dOpts.debugApplication(ctx, wargs, c, ioStreams)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
@@ -88,7 +96,8 @@ func NewDebugCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (d *debugOpts) debugApplication(ctx context.Context, c common.Args, app *v1beta1.Application, ioStreams cmdutil.IOStreams) error {
|
||||
func (d *debugOpts) debugApplication(ctx context.Context, wargs *WorkflowArgs, c common.Args, ioStreams cmdutil.IOStreams) error {
|
||||
app := wargs.App
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,60 +110,64 @@ func (d *debugOpts) debugApplication(ctx context.Context, c common.Args, app *v1
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.opts = wargs.getWorkflowSteps()
|
||||
d.errMap = wargs.ErrMap
|
||||
if app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0 {
|
||||
return d.debugWorkflow(ctx, wargs, cli, pd, ioStreams)
|
||||
}
|
||||
|
||||
s, opts, errMap := d.getDebugOptions(app)
|
||||
if s == "workflow steps" {
|
||||
if d.step == "" {
|
||||
prompt := &survey.Select{
|
||||
Message: fmt.Sprintf("Select the %s to debug:", s),
|
||||
Options: opts,
|
||||
}
|
||||
var step string
|
||||
err := survey.AskOne(prompt, &step, survey.WithValidator(survey.Required))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select %s: %w", s, err)
|
||||
}
|
||||
d.step = unwrapStepName(step)
|
||||
d.errMsg = errMap[d.step]
|
||||
}
|
||||
|
||||
// debug workflow steps
|
||||
rawValue, data, err := d.getDebugRawValue(ctx, cli, pd, app)
|
||||
if err != nil {
|
||||
if data != "" {
|
||||
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
|
||||
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.handleCueSteps(rawValue, ioStreams); err != nil {
|
||||
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
|
||||
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// dry run components
|
||||
dm, err := discoverymapper.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dryRunOpt := dryrun.NewDryRunOption(cli, config, dm, pd, []oam.Object{}, false)
|
||||
comps, _, err := dryRunOpt.ExecuteDryRun(ctx, app)
|
||||
if err != nil {
|
||||
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
|
||||
return nil
|
||||
}
|
||||
if err := d.debugComponents(opts, comps, ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
dm, err := discoverymapper.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dryRunOpt := dryrun.NewDryRunOption(cli, config, dm, pd, []oam.Object{}, false)
|
||||
comps, _, err := dryRunOpt.ExecuteDryRun(ctx, app)
|
||||
if err != nil {
|
||||
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
|
||||
return nil
|
||||
}
|
||||
if err := d.debugComponents(comps, ioStreams); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *debugOpts) debugComponents(compList []string, comps []*types.ComponentManifest, ioStreams cmdutil.IOStreams) error {
|
||||
opts := compList
|
||||
func (d *debugOpts) debugWorkflow(ctx context.Context, wargs *WorkflowArgs, cli client.Client, pd *packages.PackageDiscover, ioStreams cmdutil.IOStreams) error {
|
||||
if d.step == "" {
|
||||
prompt := &survey.Select{
|
||||
Message: "Select the workflow step to debug:",
|
||||
Options: d.opts,
|
||||
}
|
||||
var step string
|
||||
err := survey.AskOne(prompt, &step, survey.WithValidator(survey.Required))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select workflow step: %w", err)
|
||||
}
|
||||
d.step = unwrapStepName(step)
|
||||
d.errMsg = d.errMap[d.step]
|
||||
}
|
||||
|
||||
// debug workflow steps
|
||||
rawValue, data, err := d.getDebugRawValue(ctx, cli, pd, wargs.WorkflowInstance)
|
||||
if err != nil {
|
||||
if data != "" {
|
||||
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
|
||||
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.handleCueSteps(rawValue, ioStreams); err != nil {
|
||||
ioStreams.Info(color.RedString("%s%s", emojiFail, err.Error()))
|
||||
ioStreams.Info(color.GreenString("Original Data in Debug:\n"), data)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *debugOpts) debugComponents(comps []*types.ComponentManifest, ioStreams cmdutil.IOStreams) error {
|
||||
opts := d.opts
|
||||
all := color.YellowString("all fields")
|
||||
exit := color.CyanString("exit debug mode")
|
||||
opts = append(opts, all, exit)
|
||||
@@ -184,7 +197,7 @@ func (d *debugOpts) debugComponents(compList []string, comps []*types.ComponentM
|
||||
break
|
||||
}
|
||||
if step == all {
|
||||
for _, step := range compList {
|
||||
for _, step := range d.opts {
|
||||
step = unwrapStepName(step)
|
||||
if err := renderComponents(step, components[step], traits[step], ioStreams); err != nil {
|
||||
return err
|
||||
@@ -217,34 +230,6 @@ func renderComponents(compName string, comp *unstructured.Unstructured, traits [
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *debugOpts) getDebugOptions(app *v1beta1.Application) (string, []string, map[string]string) {
|
||||
s := "components"
|
||||
stepList := make([]string, 0)
|
||||
if app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0 {
|
||||
s = "workflow steps"
|
||||
}
|
||||
errMap := make(map[string]string)
|
||||
switch {
|
||||
case app.Status.Workflow != nil:
|
||||
for _, step := range app.Status.Workflow.Steps {
|
||||
stepName := wrapStepName(step.StepStatus)
|
||||
if strings.HasPrefix(stepName, emojiFail) {
|
||||
errMap[step.Name] = step.Message
|
||||
}
|
||||
stepList = append(stepList, stepName)
|
||||
}
|
||||
case app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) > 0:
|
||||
for _, step := range app.Spec.Workflow.Steps {
|
||||
stepList = append(stepList, step.Name)
|
||||
}
|
||||
default:
|
||||
for _, c := range app.Spec.Components {
|
||||
stepList = append(stepList, c.Name)
|
||||
}
|
||||
}
|
||||
return s, stepList, errMap
|
||||
}
|
||||
|
||||
func wrapStepName(step workflowv1alpha1.StepStatus) string {
|
||||
var stepName string
|
||||
switch step.Phase {
|
||||
@@ -276,10 +261,20 @@ func unwrapStepName(step string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *debugOpts) getDebugRawValue(ctx context.Context, cli client.Client, pd *packages.PackageDiscover, app *v1beta1.Application) (*value.Value, string, error) {
|
||||
func (d *debugOpts) getDebugRawValue(ctx context.Context, cli client.Client, pd *packages.PackageDiscover, instance *wfTypes.WorkflowInstance) (*value.Value, string, error) {
|
||||
debugCM := &corev1.ConfigMap{}
|
||||
if err := cli.Get(ctx, client.ObjectKey{Name: debug.GenerateContextName(app.Name, d.step), Namespace: app.Namespace}, debugCM); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to get debug configmap, please make sure your application have the debug policy, you can add the debug policy by using `vela up -f <app.yaml> --debug`: %w", err)
|
||||
if err := cli.Get(ctx, client.ObjectKey{Name: debug.GenerateContextName(instance.Name, d.step, string(instance.UID)), Namespace: instance.Namespace}, debugCM); err != nil {
|
||||
for _, step := range instance.Status.Steps {
|
||||
if step.Name == d.step && (step.Type == wfTypes.WorkflowStepTypeSuspend || step.Type == wfTypes.WorkflowStepTypeStepGroup) {
|
||||
return nil, "", fmt.Errorf("no debug data for a suspend or step-group step, please choose another step")
|
||||
}
|
||||
for _, sub := range step.SubStepsStatus {
|
||||
if sub.Name == d.step && sub.Type == wfTypes.WorkflowStepTypeSuspend {
|
||||
return nil, "", fmt.Errorf("no debug data for a suspend step, please choose another step")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, "", fmt.Errorf("failed to get debug configmap, please make sure the you're in the debug mode`: %w", err)
|
||||
}
|
||||
|
||||
if debugCM.Data == nil || debugCM.Data["debug"] == "" {
|
||||
|
||||
@@ -49,8 +49,10 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
|
||||
Name: "no-debug-config-map",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{
|
||||
Workflow: &common.WorkflowStatus{},
|
||||
},
|
||||
},
|
||||
step: "test-wf1",
|
||||
focus: "test",
|
||||
@@ -61,13 +63,16 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-no-data",
|
||||
Namespace: "default",
|
||||
UID: "12345",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{
|
||||
Workflow: &common.WorkflowStatus{},
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
cm: &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-no-data-test-wf1-debug",
|
||||
Name: "config-map-no-data-test-wf1-debug-12345",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
@@ -80,13 +85,16 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-error-data",
|
||||
Namespace: "default",
|
||||
UID: "12345",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{
|
||||
Workflow: &common.WorkflowStatus{},
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
cm: &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-map-error-data-test-wf1-debug",
|
||||
Name: "config-map-error-data-test-wf1-debug-12345",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
@@ -100,13 +108,16 @@ func TestDebugApplicationWithWorkflow(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "success",
|
||||
Namespace: "default",
|
||||
UID: "12345",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{
|
||||
Workflow: &common.WorkflowStatus{},
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
cm: &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "success-test-wf1-debug",
|
||||
Name: "success-test-wf1-debug-12345",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
@@ -131,7 +142,9 @@ test: test
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
}},
|
||||
},
|
||||
Status: common.AppStatus{},
|
||||
Status: common.AppStatus{
|
||||
Workflow: &common.WorkflowStatus{},
|
||||
},
|
||||
},
|
||||
step: "test-component",
|
||||
},
|
||||
@@ -140,7 +153,6 @@ test: test
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
d := &debugOpts{
|
||||
step: tc.step,
|
||||
focus: tc.focus,
|
||||
@@ -151,7 +163,14 @@ test: test
|
||||
err := client.Create(ctx, tc.cm)
|
||||
r.NoError(err)
|
||||
}
|
||||
err = d.debugApplication(ctx, c, tc.app, ioStream)
|
||||
wargs := &WorkflowArgs{
|
||||
Args: c,
|
||||
Type: instanceTypeApplication,
|
||||
App: tc.app,
|
||||
}
|
||||
err = wargs.generateWorkflowInstance(ctx, client)
|
||||
r.NoError(err)
|
||||
err = d.debugApplication(ctx, wargs, c, ioStream)
|
||||
if tc.expectedErr != "" {
|
||||
r.Contains(err.Error(), tc.expectedErr)
|
||||
return
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
|
||||
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -101,7 +103,7 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
|
||||
|
||||
objs := []oam.Object{}
|
||||
if cmdOption.DefinitionFile != "" {
|
||||
objs, err = ReadObjectsFromFile(cmdOption.DefinitionFile)
|
||||
objs, err = ReadDefinitionsFromFile(cmdOption.DefinitionFile)
|
||||
if err != nil {
|
||||
return buff, err
|
||||
}
|
||||
@@ -160,15 +162,37 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str
|
||||
return buff, nil
|
||||
}
|
||||
|
||||
// ReadObjectsFromFile will read objects from file or dir in the format of yaml
|
||||
func ReadObjectsFromFile(path string) ([]oam.Object, error) {
|
||||
func readObj(path string) (oam.Object, error) {
|
||||
switch {
|
||||
case strings.HasSuffix(path, ".cue"):
|
||||
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
defBytes, err := os.ReadFile(filepath.Clean(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := def.FromCUEString(string(defBytes), nil); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse CUE for definition")
|
||||
}
|
||||
obj := &unstructured.Unstructured{Object: def.UnstructuredContent()}
|
||||
return obj, nil
|
||||
default:
|
||||
obj := &unstructured.Unstructured{}
|
||||
err := common.ReadYamlToObject(path, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReadDefinitionsFromFile will read objects from file or dir in the format of yaml
|
||||
func ReadDefinitionsFromFile(path string) ([]oam.Object, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
obj := &unstructured.Unstructured{}
|
||||
err = common.ReadYamlToObject(path, obj)
|
||||
obj, err := readObj(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -186,11 +210,10 @@ func ReadObjectsFromFile(path string) ([]oam.Object, error) {
|
||||
continue
|
||||
}
|
||||
fileType := filepath.Ext(fi.Name())
|
||||
if fileType != ".yaml" && fileType != ".yml" {
|
||||
if fileType != ".yaml" && fileType != ".yml" && fileType != ".cue" {
|
||||
continue
|
||||
}
|
||||
obj := &unstructured.Unstructured{}
|
||||
err = common.ReadYamlToObject(filepath.Join(path, fi.Name()), obj)
|
||||
obj, err := readObj(filepath.Join(path, fi.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -74,6 +74,20 @@ var _ = Describe("Test dry run with policy", func() {
|
||||
Expect(buff.String()).Should(ContainSubstring("- image: oamdev/hello-world:v2\n name: server-v2"))
|
||||
})
|
||||
|
||||
It("Test dry run with cue component format", func() {
|
||||
|
||||
c := common2.Args{}
|
||||
c.SetConfig(cfg)
|
||||
c.SetClient(k8sClient)
|
||||
|
||||
opt := DryRunCmdOptions{ApplicationFile: "test-data/dry-run/app.yaml", DefinitionFile: "test-data/dry-run/my-comp.cue", OfflineMode: true}
|
||||
buff, err := DryRunApplication(&opt, c, "")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(buff.String()).Should(ContainSubstring("name: hello-world"))
|
||||
Expect(buff.String()).Should(ContainSubstring("kind: Deployment"))
|
||||
Expect(buff.String()).Should(ContainSubstring("name: hello-world-service"))
|
||||
Expect(buff.String()).Should(ContainSubstring("kind: Service"))
|
||||
})
|
||||
})
|
||||
|
||||
var plcapp = `apiVersion: core.oam.dev/v1beta1
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@@ -111,11 +112,11 @@ func NewInitCommand(c common2.Args, order string, ioStreams cmdutil.IOStreams) *
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deployStatus, err := printTrackingDeployStatus(c, o.IOStreams, o.appName, o.Namespace)
|
||||
deployStatus, err := printTrackingDeployStatus(c, o.IOStreams, o.appName, o.Namespace, 300*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if deployStatus != compStatusDeployed {
|
||||
if deployStatus != appDeployedHealthy {
|
||||
return nil
|
||||
}
|
||||
return printAppStatus(context.Background(), newClient, ioStreams, o.appName, o.Namespace, cmd, c, false)
|
||||
|
||||
@@ -104,7 +104,7 @@ func LiveDiffApplication(cmdOption *LiveDiffCmdOptions, c common.Args) (bytes.Bu
|
||||
}
|
||||
objs := []oam.Object{}
|
||||
if cmdOption.DefinitionFile != "" {
|
||||
objs, err = ReadObjectsFromFile(cmdOption.DefinitionFile)
|
||||
objs, err = ReadDefinitionsFromFile(cmdOption.DefinitionFile)
|
||||
if err != nil {
|
||||
return buff, err
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ func newUITable() *uitable.Table {
|
||||
}
|
||||
|
||||
func newTrackingSpinnerWithDelay(suffix string, interval time.Duration) *spinner.Spinner {
|
||||
suffixColor := color.New(color.Bold, color.FgGreen)
|
||||
suffixColor := color.New(color.Bold, color.FgWhite)
|
||||
return spinner.New(
|
||||
spinner.CharSets[14],
|
||||
interval,
|
||||
spinner.WithColor("green"),
|
||||
spinner.WithColor("white"),
|
||||
spinner.WithHiddenCursor(true),
|
||||
spinner.WithSuffix(suffixColor.Sprintf(" %s", suffix)))
|
||||
}
|
||||
|
||||
@@ -78,26 +78,18 @@ type WorkloadHealthCondition = v1alpha2.WorkloadHealthCondition
|
||||
// ScopeHealthCondition holds health condition of a scope
|
||||
type ScopeHealthCondition = v1alpha2.ScopeHealthCondition
|
||||
|
||||
// CompStatus represents the status of a component during "vela init"
|
||||
type CompStatus int
|
||||
// AppDeployStatus represents the status of application during "vela init" and "vela up --wait"
|
||||
type AppDeployStatus int
|
||||
|
||||
// Enums of CompStatus
|
||||
// Enums of ApplicationStatus
|
||||
const (
|
||||
compStatusDeploying CompStatus = iota
|
||||
compStatusDeployFail
|
||||
compStatusDeployed
|
||||
compStatusUnknown
|
||||
)
|
||||
|
||||
// Error msg used in `status` command
|
||||
const (
|
||||
// ErrNotLoadAppConfig display the error message load
|
||||
ErrNotLoadAppConfig = "cannot load the application"
|
||||
appDeployFail AppDeployStatus = iota
|
||||
appDeployedHealthy
|
||||
appDeployError
|
||||
)
|
||||
|
||||
const (
|
||||
trackingInterval time.Duration = 1 * time.Second
|
||||
deployTimeout time.Duration = 10 * time.Second
|
||||
trackingInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
// NewAppStatusCommand creates `status` command for showing status
|
||||
@@ -402,59 +394,47 @@ func loopCheckStatus(c client.Client, ioStreams cmdutil.IOStreams, appName strin
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTrackingDeployStatus(c common.Args, ioStreams cmdutil.IOStreams, appName string, namespace string) (CompStatus, error) {
|
||||
sDeploy := newTrackingSpinnerWithDelay("Checking Status ...", trackingInterval)
|
||||
func printTrackingDeployStatus(c common.Args, ioStreams cmdutil.IOStreams, appName, namespace string, timeout time.Duration) (AppDeployStatus, error) {
|
||||
sDeploy := newTrackingSpinnerWithDelay("Waiting app to be healthy ...", trackingInterval)
|
||||
sDeploy.Start()
|
||||
defer sDeploy.Stop()
|
||||
startTime := time.Now()
|
||||
TrackDeployLoop:
|
||||
for {
|
||||
time.Sleep(trackingInterval)
|
||||
deployStatus, failMsg, err := TrackDeployStatus(c, appName, namespace)
|
||||
deployStatus, err := TrackDeployStatus(c, appName, namespace)
|
||||
if err != nil {
|
||||
return compStatusUnknown, err
|
||||
return appDeployError, err
|
||||
}
|
||||
switch deployStatus {
|
||||
case compStatusDeploying:
|
||||
case commontypes.ApplicationStarting, commontypes.ApplicationRendering, commontypes.ApplicationPolicyGenerating, commontypes.ApplicationRunningWorkflow, commontypes.ApplicationUnhealthy:
|
||||
if time.Now().After(startTime.Add(timeout)) {
|
||||
ioStreams.Info(red.Sprintf("\n%s Timeout waiting Application to be healthy!", emojiFail))
|
||||
return appDeployFail, nil
|
||||
}
|
||||
continue
|
||||
case compStatusDeployed:
|
||||
case commontypes.ApplicationWorkflowSuspending, commontypes.ApplicationRunning:
|
||||
ioStreams.Info(green.Sprintf("\n%sApplication Deployed Successfully!", emojiSucceed))
|
||||
break TrackDeployLoop
|
||||
case compStatusDeployFail:
|
||||
ioStreams.Info(red.Sprintf("\n%sApplication Failed to Deploy!", emojiFail))
|
||||
ioStreams.Info(red.Sprintf("Reason: %s", failMsg))
|
||||
return compStatusDeployFail, nil
|
||||
default:
|
||||
continue
|
||||
case commontypes.ApplicationWorkflowTerminated, commontypes.ApplicationWorkflowFailed:
|
||||
ioStreams.Info(red.Sprintf("\n%sApplication Deployment Failed!", emojiFail))
|
||||
ioStreams.Info(red.Sprintf("Please run the following command to check details: \n vela status %s -n %s\n", appName, namespace))
|
||||
return appDeployFail, nil
|
||||
case commontypes.ApplicationDeleting:
|
||||
ioStreams.Info(red.Sprintf("\n%sApplication is in the deleting process!", emojiFail))
|
||||
return appDeployFail, nil
|
||||
}
|
||||
}
|
||||
return compStatusDeployed, nil
|
||||
return appDeployedHealthy, nil
|
||||
}
|
||||
|
||||
// TrackDeployStatus will only check AppConfig is deployed successfully,
|
||||
func TrackDeployStatus(c common.Args, appName string, namespace string) (CompStatus, string, error) {
|
||||
func TrackDeployStatus(c common.Args, appName string, namespace string) (commontypes.ApplicationPhase, error) {
|
||||
appObj, err := appfile.LoadApplication(namespace, appName, c)
|
||||
if err != nil {
|
||||
return compStatusUnknown, "", err
|
||||
return "", err
|
||||
}
|
||||
if appObj == nil {
|
||||
return compStatusUnknown, "", errors.New(ErrNotLoadAppConfig)
|
||||
}
|
||||
condition := appObj.Status.Conditions
|
||||
if len(condition) < 1 {
|
||||
return compStatusDeploying, "", nil
|
||||
}
|
||||
|
||||
// If condition is true, we can regard appConfig is deployed successfully
|
||||
if appObj.Status.Phase == commontypes.ApplicationRunning {
|
||||
return compStatusDeployed, "", nil
|
||||
}
|
||||
|
||||
// if not found workload status in AppConfig
|
||||
// then use age to check whether the workload controller is running
|
||||
if time.Since(appObj.GetCreationTimestamp().Time) > deployTimeout {
|
||||
return compStatusDeployFail, condition[0].Message, nil
|
||||
}
|
||||
return compStatusDeploying, "", nil
|
||||
return appObj.Status.Phase, nil
|
||||
}
|
||||
|
||||
func getHealthStatusColor(s bool) *color.Color {
|
||||
|
||||
8
references/cli/test-data/dry-run/app.yaml
Normal file
8
references/cli/test-data/dry-run/app.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: vela-app
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: my-comp
|
||||
50
references/cli/test-data/dry-run/my-comp.cue
Normal file
50
references/cli/test-data/dry-run/my-comp.cue
Normal file
@@ -0,0 +1,50 @@
|
||||
"my-comp": {
|
||||
annotations: {}
|
||||
attributes: workload: definition: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
}
|
||||
description: "My component."
|
||||
labels: {}
|
||||
type: "component"
|
||||
}
|
||||
template: {
|
||||
output: {
|
||||
metadata: name: "hello-world"
|
||||
spec: {
|
||||
replicas: 1
|
||||
selector: matchLabels: "app.kubernetes.io/name": "hello-world"
|
||||
template: {
|
||||
metadata: labels: "app.kubernetes.io/name": "hello-world"
|
||||
spec: containers: [{
|
||||
name: "hello-world"
|
||||
image: "somefive/hello-world"
|
||||
ports: [{
|
||||
name: "http"
|
||||
containerPort: 80
|
||||
protocol: "TCP"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
}
|
||||
outputs: "hello-world-service": {
|
||||
metadata: name: "hello-world-service"
|
||||
spec: {
|
||||
ports: [{
|
||||
name: "http"
|
||||
protocol: "TCP"
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
}]
|
||||
selector: app: "hello-world"
|
||||
type: "LoadBalancer"
|
||||
}
|
||||
apiVersion: "v1"
|
||||
kind: "Service"
|
||||
}
|
||||
parameter: {}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -18,6 +18,9 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -53,6 +56,8 @@ type UpCommandOptions struct {
|
||||
PublishVersion string
|
||||
RevisionName string
|
||||
Debug bool
|
||||
Wait bool
|
||||
WaitTimeout string
|
||||
}
|
||||
|
||||
// Complete fill the args for vela up
|
||||
@@ -211,34 +216,38 @@ func (opt *UpCommandOptions) deployApplicationFromFile(f velacmd.Factory, cmd *c
|
||||
}
|
||||
if common.IsAppfile(body) { // legacy compatibility
|
||||
o := &common.AppfileOptions{Kubecli: cli, IO: ioStream, Namespace: opt.Namespace}
|
||||
return o.Run(opt.File, o.Namespace, utilcommon.Args{Schema: utilcommon.Scheme})
|
||||
}
|
||||
var app v1beta1.Application
|
||||
err = yaml.Unmarshal(body, &app)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
|
||||
}
|
||||
if err = o.Run(opt.File, o.Namespace, utilcommon.Args{Schema: utilcommon.Scheme}); err != nil {
|
||||
return err
|
||||
}
|
||||
opt.AppName = o.Name
|
||||
} else {
|
||||
var app v1beta1.Application
|
||||
err = yaml.Unmarshal(body, &app)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
|
||||
}
|
||||
|
||||
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
|
||||
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
|
||||
if opt.Namespace != "" && opt.Namespace != types.DefaultAppNamespace {
|
||||
app.SetNamespace(opt.Namespace)
|
||||
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
|
||||
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
|
||||
if opt.Namespace != "" && opt.Namespace != types.DefaultAppNamespace {
|
||||
app.SetNamespace(opt.Namespace)
|
||||
}
|
||||
if opt.PublishVersion != "" {
|
||||
oam.SetPublishVersion(&app, opt.PublishVersion)
|
||||
}
|
||||
opt.AppName = app.Name
|
||||
if opt.Debug {
|
||||
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
|
||||
Name: "debug",
|
||||
Type: "debug",
|
||||
})
|
||||
}
|
||||
err = common.ApplyApplication(app, ioStream, cli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Printf("Application %s applied.\n", green.Sprintf("%s/%s", app.Namespace, app.Name))
|
||||
}
|
||||
if opt.PublishVersion != "" {
|
||||
oam.SetPublishVersion(&app, opt.PublishVersion)
|
||||
}
|
||||
opt.AppName = app.Name
|
||||
if opt.Debug {
|
||||
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
|
||||
Name: "debug",
|
||||
Type: "debug",
|
||||
})
|
||||
}
|
||||
err = common.ApplyApplication(app, ioStream, cli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Printf("Application %s applied.\n", green.Sprintf("%s/%s", app.Namespace, app.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -276,7 +285,9 @@ var (
|
||||
|
||||
// NewUpCommand will create command for applying an AppFile
|
||||
func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream util.IOStreams) *cobra.Command {
|
||||
o := &UpCommandOptions{}
|
||||
o := &UpCommandOptions{
|
||||
WaitTimeout: "300s",
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "up",
|
||||
DisableFlagsInUseLine: true,
|
||||
@@ -301,10 +312,29 @@ func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream u
|
||||
cmdutil.CheckErr(o.Run(f, cmd))
|
||||
if o.Debug {
|
||||
dOpts := &debugOpts{}
|
||||
cli := f.Client()
|
||||
app := &v1beta1.Application{}
|
||||
cmdutil.CheckErr(cli.Get(cmd.Context(), apitypes.NamespacedName{Name: o.AppName, Namespace: o.Namespace}, app))
|
||||
cmdutil.CheckErr(dOpts.debugApplication(context.Background(), c, app, ioStream))
|
||||
wargs := &WorkflowArgs{Args: c}
|
||||
ctx := context.Background()
|
||||
cmdutil.CheckErr(wargs.getWorkflowInstance(ctx, cmd, []string{o.AppName}))
|
||||
if wargs.Type == instanceTypeWorkflowRun {
|
||||
cmdutil.CheckErr(fmt.Errorf("please use `vela workflow debug <name>` instead"))
|
||||
}
|
||||
if wargs.App == nil {
|
||||
cmdutil.CheckErr(fmt.Errorf("application %s not found", args[0]))
|
||||
}
|
||||
cmdutil.CheckErr(dOpts.debugApplication(ctx, wargs, c, ioStream))
|
||||
}
|
||||
if o.Wait {
|
||||
dur, err := time.ParseDuration(o.WaitTimeout)
|
||||
if err != nil {
|
||||
cmdutil.CheckErr(fmt.Errorf("parse timeout duration err: %w", err))
|
||||
}
|
||||
status, err := printTrackingDeployStatus(c, ioStream, o.AppName, o.Namespace, dur)
|
||||
if err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
if status != appDeployedHealthy {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -312,6 +342,8 @@ func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream u
|
||||
cmd.Flags().StringVarP(&o.PublishVersion, "publish-version", "v", o.PublishVersion, "The publish version for deploying application.")
|
||||
cmd.Flags().StringVarP(&o.RevisionName, "revision", "r", o.RevisionName, "The revision to use for deploying the application, if empty, the current application configuration will be used.")
|
||||
cmd.Flags().BoolVarP(&o.Debug, "debug", "", o.Debug, "Enable debug mode for application")
|
||||
cmd.Flags().BoolVarP(&o.Wait, "wait", "w", o.Wait, "Wait app to be healthy until timout, if no timeout specified, the default duration is 300s.")
|
||||
cmd.Flags().StringVarP(&o.WaitTimeout, "timeout", "", o.WaitTimeout, "Set the timout for wait app to be healthy, if not specified, the default duration is 300s.")
|
||||
cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
|
||||
"revision",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
wfTypes "github.com/kubevela/workflow/pkg/types"
|
||||
wfUtils "github.com/kubevela/workflow/pkg/utils"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
@@ -61,6 +62,7 @@ func NewWorkflowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
|
||||
NewWorkflowRestartCommand(c, ioStreams, wargs),
|
||||
NewWorkflowRollbackCommand(c, ioStreams, wargs),
|
||||
NewWorkflowLogsCommand(c, ioStreams, wargs),
|
||||
NewWorkflowDebugCommand(c, ioStreams, wargs),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
@@ -194,6 +196,42 @@ func NewWorkflowLogsCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *Wo
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewWorkflowDebugCommand create workflow debug command
|
||||
func NewWorkflowDebugCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
|
||||
dOpts := &debugOpts{
|
||||
step: wargs.StepName,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "debug",
|
||||
Short: "Debug workflow steps",
|
||||
Long: "Debug workflow steps",
|
||||
Example: "vela workflow debug <workflow-name>",
|
||||
PreRun: wargs.checkDebugMode(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pd, err := c.GetPackageDiscover()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
dOpts.opts = wargs.getWorkflowSteps()
|
||||
dOpts.errMap = wargs.ErrMap
|
||||
return dOpts.debugWorkflow(ctx, wargs, cli, pd, ioStream)
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&wargs.StepName, "step", "s", "", "specify the step name in the workflow")
|
||||
cmd.Flags().StringVarP(&dOpts.focus, "focus", "f", "", "specify the focus value to debug, only valid for application with workflow")
|
||||
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// WorkflowArgs is the args for workflow command
|
||||
type WorkflowArgs struct {
|
||||
Type string
|
||||
@@ -203,6 +241,7 @@ type WorkflowArgs struct {
|
||||
Writer io.Writer
|
||||
Args common.Args
|
||||
StepName string
|
||||
ErrMap map[string]string
|
||||
App *v1beta1.Application
|
||||
WorkflowRun *workflowv1alpha1.WorkflowRun
|
||||
WorkflowInstance *wfTypes.WorkflowInstance
|
||||
@@ -265,18 +304,26 @@ func (w *WorkflowArgs) getWorkflowInstance(ctx context.Context, cmd *cobra.Comma
|
||||
}
|
||||
|
||||
func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.Client) error {
|
||||
debug := false
|
||||
switch w.Type {
|
||||
case instanceTypeApplication:
|
||||
if w.App.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application %s is not start", w.App.Name)
|
||||
}
|
||||
for _, policy := range w.App.Spec.Policies {
|
||||
if policy.Type == v1alpha1.DebugPolicyType {
|
||||
debug = true
|
||||
break
|
||||
}
|
||||
}
|
||||
status := w.App.Status.Workflow
|
||||
w.WorkflowInstance = &wfTypes.WorkflowInstance{
|
||||
WorkflowMeta: wfTypes.WorkflowMeta{
|
||||
Name: w.App.Name,
|
||||
Namespace: w.App.Namespace,
|
||||
UID: w.App.UID,
|
||||
},
|
||||
Steps: w.App.Spec.Workflow.Steps,
|
||||
Debug: debug,
|
||||
Status: workflowv1alpha1.WorkflowRunStatus{
|
||||
Phase: status.Phase,
|
||||
Message: status.Message,
|
||||
@@ -290,6 +337,9 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
|
||||
EndTime: status.EndTime,
|
||||
},
|
||||
}
|
||||
if w.App.Spec.Workflow != nil {
|
||||
w.WorkflowInstance.Steps = w.App.Spec.Workflow.Steps
|
||||
}
|
||||
w.Operator = operation.NewApplicationWorkflowOperator(cli, w.Writer, w.App)
|
||||
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-core"}
|
||||
case instanceTypeWorkflowRun:
|
||||
@@ -303,13 +353,20 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
|
||||
} else {
|
||||
steps = w.WorkflowRun.Spec.WorkflowSpec.Steps
|
||||
}
|
||||
if w.WorkflowRun.Annotations != nil {
|
||||
if d, ok := w.WorkflowRun.Annotations[wfTypes.AnnotationWorkflowRunDebug]; ok && d == "true" {
|
||||
debug = true
|
||||
}
|
||||
}
|
||||
w.WorkflowInstance = &wfTypes.WorkflowInstance{
|
||||
WorkflowMeta: wfTypes.WorkflowMeta{
|
||||
Name: w.WorkflowRun.Name,
|
||||
Namespace: w.WorkflowRun.Namespace,
|
||||
UID: w.WorkflowRun.UID,
|
||||
},
|
||||
Steps: steps,
|
||||
Status: w.WorkflowRun.Status,
|
||||
Debug: debug,
|
||||
}
|
||||
w.Operator = wfUtils.NewWorkflowRunOperator(cli, w.Writer, w.WorkflowRun)
|
||||
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-workflow"}
|
||||
@@ -321,7 +378,7 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
|
||||
|
||||
func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioStreams cmdutil.IOStreams) error {
|
||||
if w.StepName == "" {
|
||||
if err := w.selectWorkflowStep(); err != nil {
|
||||
if err := w.selectWorkflowStep("Select a step to show logs:"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -360,20 +417,34 @@ func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioS
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WorkflowArgs) selectWorkflowStep() error {
|
||||
func (w *WorkflowArgs) getWorkflowSteps() []string {
|
||||
if w.ErrMap == nil {
|
||||
w.ErrMap = make(map[string]string)
|
||||
}
|
||||
stepsKey := make([]string, 0)
|
||||
for _, step := range w.WorkflowInstance.Status.Steps {
|
||||
stepsKey = append(stepsKey, wrapStepName(step.StepStatus))
|
||||
if step.Phase == workflowv1alpha1.WorkflowStepPhaseFailed {
|
||||
w.ErrMap[step.Name] = step.Message
|
||||
}
|
||||
for _, sub := range step.SubStepsStatus {
|
||||
stepsKey = append(stepsKey, fmt.Sprintf(" %s", wrapStepName(sub)))
|
||||
if sub.Phase == workflowv1alpha1.WorkflowStepPhaseFailed {
|
||||
w.ErrMap[step.Name] = sub.Message
|
||||
}
|
||||
}
|
||||
}
|
||||
return stepsKey
|
||||
}
|
||||
|
||||
func (w *WorkflowArgs) selectWorkflowStep(msg string) error {
|
||||
stepsKey := w.getWorkflowSteps()
|
||||
if len(stepsKey) == 0 {
|
||||
return fmt.Errorf("workflow is not start")
|
||||
}
|
||||
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a step to show logs:",
|
||||
Message: msg,
|
||||
Options: stepsKey,
|
||||
}
|
||||
var stepName string
|
||||
@@ -445,3 +516,21 @@ func (w *WorkflowArgs) checkWorkflowNotComplete() func(cmd *cobra.Command, args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkflowArgs) checkDebugMode() func(cmd *cobra.Command, args []string) {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
if err := w.getWorkflowInstance(context.Background(), cmd, args); err != nil {
|
||||
return
|
||||
}
|
||||
if !w.WorkflowInstance.Debug {
|
||||
msg := ""
|
||||
if w.Type == instanceTypeApplication {
|
||||
msg = "please make sure your application have the debug policy, you can add the debug policy by using `vela up -f <app.yaml> --debug"
|
||||
} else {
|
||||
msg = "please make sure your workflow have the debug annotation [workflowrun.oam.dev/debug:true] then re-run the workflow"
|
||||
}
|
||||
cmd.Printf("workflow %s is not in debug mode, %s", w.WorkflowInstance.Name, msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ type AppfileOptions struct {
|
||||
Kubecli client.Client
|
||||
IO cmdutil.IOStreams
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// BuildResult is the export struct from AppFile yaml or AppFile object
|
||||
@@ -380,7 +381,7 @@ func (o *AppfileOptions) BaseAppFileRun(result *BuildResult, args common.Args) e
|
||||
return err
|
||||
}
|
||||
result.application.Spec.Components = kubernetesComponent
|
||||
|
||||
o.Name = result.application.Name
|
||||
o.IO.Infof("\nApplying application ...\n")
|
||||
return o.ApplyApp(result.application, result.scopes)
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ func GetWorkflowSteps(ctx context.Context, namespace string, c common.Args) ([]t
|
||||
for _, def := range workflowStepDefs.Items {
|
||||
tmp, err := GetCapabilityByWorkflowStepDefinitionObject(def, pd)
|
||||
if err != nil {
|
||||
templateErrors = append(templateErrors, err)
|
||||
templateErrors = append(templateErrors, errors.WithMessage(err, def.Name))
|
||||
continue
|
||||
}
|
||||
templates = append(templates, *tmp)
|
||||
|
||||
23
references/docgen/def-doc/policy/health.eg.md
Normal file
23
references/docgen/def-doc/policy/health.eg.md
Normal 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
|
||||
```
|
||||
29
references/docgen/def-doc/trait/affinity.eg.md
Normal file
29
references/docgen/def-doc/trait/affinity.eg.md
Normal 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"
|
||||
```
|
||||
41
references/docgen/def-doc/trait/json-merge-patch.eg.md
Normal file
41
references/docgen/def-doc/trait/json-merge-patch.eg.md
Normal 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"]
|
||||
```
|
||||
41
references/docgen/def-doc/trait/json-patch.eg.md
Normal file
41
references/docgen/def-doc/trait/json-patch.eg.md
Normal 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"]
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
27
references/docgen/def-doc/workflowstep/apply-component.eg.md
Normal file
27
references/docgen/def-doc/workflowstep/apply-component.eg.md
Normal file
@@ -0,0 +1,27 @@
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: first-vela-workflow
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: oamdev/hello-world
|
||||
port: 8000
|
||||
traits:
|
||||
- type: ingress
|
||||
properties:
|
||||
domain: testsvc.example.com
|
||||
http:
|
||||
/: 8000
|
||||
workflow:
|
||||
steps:
|
||||
- name: express-server
|
||||
type: apply-component
|
||||
properties:
|
||||
component: express-server
|
||||
# cluster: <your cluster name>
|
||||
```
|
||||
32
references/docgen/def-doc/workflowstep/create-config.eg.md
Normal file
32
references/docgen/def-doc/workflowstep/create-config.eg.md
Normal 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
|
||||
```
|
||||
25
references/docgen/def-doc/workflowstep/delete-config.eg.md
Normal file
25
references/docgen/def-doc/workflowstep/delete-config.eg.md
Normal 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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
50
references/docgen/def-doc/workflowstep/list-config.eg.md
Normal file
50
references/docgen/def-doc/workflowstep/list-config.eg.md
Normal 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
|
||||
```
|
||||
@@ -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."
|
||||
```
|
||||
18
references/docgen/def-doc/workflowstep/read-config.eg.md
Normal file
18
references/docgen/def-doc/workflowstep/read-config.eg.md
Normal 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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -669,6 +669,21 @@ var _ = Describe("Test multicluster scenario", func() {
|
||||
}, 20*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test application with apply-component and cluster", func() {
|
||||
By("create application")
|
||||
bs, err := os.ReadFile("./testdata/app/app-component-with-cluster.yaml")
|
||||
Expect(err).Should(Succeed())
|
||||
app := &v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
|
||||
app.SetNamespace(testNamespace)
|
||||
Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Get(hubCtx, client.ObjectKeyFromObject(app), app)).Should(Succeed())
|
||||
g.Expect(app.Status.Phase).Should(Equal(common.ApplicationRunning))
|
||||
}, 20*time.Second).Should(Succeed())
|
||||
Expect(k8sClient.Get(workerCtx, client.ObjectKey{Namespace: testNamespace, Name: "component-cluster"}, &appsv1.Deployment{})).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test application with component using cluster context", func() {
|
||||
By("Create definition")
|
||||
bs, err := os.ReadFile("./testdata/def/cluster-config.yaml")
|
||||
|
||||
20
test/e2e-multicluster-test/testdata/app/app-component-with-cluster.yaml
vendored
Normal file
20
test/e2e-multicluster-test/testdata/app/app-component-with-cluster.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: component-cluster
|
||||
spec:
|
||||
components:
|
||||
- name: component-cluster
|
||||
type: worker
|
||||
properties:
|
||||
image: busybox
|
||||
cmd:
|
||||
- sleep
|
||||
- '1000000'
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply
|
||||
type: apply-component
|
||||
properties:
|
||||
component: component-cluster
|
||||
cluster: cluster-worker
|
||||
@@ -0,0 +1,17 @@
|
||||
"apply-component": {
|
||||
type: "workflow-step"
|
||||
annotations: {}
|
||||
labels: {
|
||||
"ui-hidden": "true"
|
||||
"scope": "Application"
|
||||
}
|
||||
description: "Apply a specific component and its corresponding traits in application"
|
||||
}
|
||||
template: {
|
||||
parameter: {
|
||||
// +usage=Specify the component name to apply
|
||||
component: string
|
||||
// +usage=Specify the cluster
|
||||
cluster: *"" | string
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user