mirror of
https://github.com/kubevela/kubevela.git
synced 2026-03-01 09:10:43 +00:00
Compare commits
28 Commits
v1.6.0-bet
...
v1.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1cc06b0f3 | ||
|
|
ed9d53b448 | ||
|
|
ad83e59865 | ||
|
|
b62eeca3f9 | ||
|
|
5d9757fcb8 | ||
|
|
4d653951a1 | ||
|
|
bcda4976a9 | ||
|
|
a01d0e773a | ||
|
|
f0e3304c17 | ||
|
|
e9f1e21d55 | ||
|
|
de127b7311 | ||
|
|
9f0558c62e | ||
|
|
0f547fa158 | ||
|
|
84155d06fb | ||
|
|
bc7e31f979 | ||
|
|
f406936dce | ||
|
|
c2ecc71941 | ||
|
|
c1efd3f056 | ||
|
|
7002182072 | ||
|
|
554a06e35e | ||
|
|
4ffb7e6707 | ||
|
|
caeb334340 | ||
|
|
275b61d427 | ||
|
|
11904a6f60 | ||
|
|
4b4e4f8530 | ||
|
|
0121e8b6ef | ||
|
|
382510aa67 | ||
|
|
7ae7d2a5ef |
32
.github/workflows/go.yml
vendored
32
.github/workflows/go.yml
vendored
@@ -141,3 +141,35 @@ jobs:
|
||||
|
||||
- name: Cleanup binary
|
||||
run: make build-cleanup
|
||||
|
||||
check-windows:
|
||||
runs-on: windows-latest
|
||||
needs: detect-noop
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .work/pkg
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
|
||||
- name: Run Build CLI
|
||||
run: make vela-cli
|
||||
|
||||
- name: Run CLI for version
|
||||
shell: cmd
|
||||
run: |
|
||||
move .\bin\vela .\bin\vela.exe
|
||||
.\bin\vela.exe version
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,6 +64,8 @@ const (
|
||||
LabelDefinitionDeprecated = "custom.definition.oam.dev/deprecated"
|
||||
// LabelDefinitionHidden is the label which describe whether the capability is hidden by UI
|
||||
LabelDefinitionHidden = "custom.definition.oam.dev/ui-hidden"
|
||||
// LabelDefinitionScope is the label which describe whether the capability's scope
|
||||
LabelDefinitionScope = "custom.definition.oam.dev/scope"
|
||||
// LabelNodeRoleGateway gateway role of node
|
||||
LabelNodeRoleGateway = "node-role.kubernetes.io/gateway"
|
||||
// LabelNodeRoleWorker worker role of node
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Apply components of an application in parallel for your workflow steps
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-application-in-parallel
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Apply application for your workflow steps, it has no arguments, should be used for custom steps before or after application applied.
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-application
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Apply remaining components and traits
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-remaining
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -5,6 +5,8 @@ kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Deploy cloud resource and deliver secret to multi clusters.
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
name: deploy-cloud-resource
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -5,6 +5,8 @@ kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: A powerful and unified deploy step for components multi-cluster delivery with policies.
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
name: deploy
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Deploy env binding component to target env
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: deploy2env
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Deploy application to runtime clusters
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: deploy2runtime
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,8 @@ kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Sync secrets created by terraform component to runtime clusters so that runtime clusters can share the created cloud resource.
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
name: share-cloud-resource
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -13,6 +13,7 @@ spec:
|
||||
template: |
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
mountsArray: [
|
||||
@@ -167,7 +168,11 @@ spec:
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
_name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
name: *_name | string
|
||||
if v.protocol != "TCP" {
|
||||
name: _name + "-" + strings.ToLower(v.protocol)
|
||||
}
|
||||
}
|
||||
}}]
|
||||
}
|
||||
@@ -283,11 +288,18 @@ spec:
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
_name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
name: *_name | string
|
||||
if v.protocol != "TCP" {
|
||||
name: _name + "-" + strings.ToLower(v.protocol)
|
||||
}
|
||||
}
|
||||
if v.nodePort != _|_ && parameter.exposeType == "NodePort" {
|
||||
nodePort: v.nodePort
|
||||
}
|
||||
if v.protocol != _|_ {
|
||||
protocol: v.protocol
|
||||
}
|
||||
},
|
||||
]
|
||||
outputs: {
|
||||
|
||||
@@ -116,6 +116,39 @@ subjects:
|
||||
name: {{ include "kubevela.serviceAccountName" . }}
|
||||
|
||||
---
|
||||
# permissions to read the view of VelaQL, schemas, and templates.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ include "kubevela.fullname" . }}:template-reader-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps/status
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ include "kubevela.fullname" . }}:template-reader-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ include "kubevela.fullname" . }}:template-reader-role
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: template-reader
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Apply components of an application in parallel for your workflow steps
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-application-in-parallel
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Apply application for your workflow steps, it has no arguments, should be used for custom steps before or after application applied.
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-application
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Apply remaining components and traits
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: apply-remaining
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -5,6 +5,8 @@ kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Deploy cloud resource and deliver secret to multi clusters.
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
name: deploy-cloud-resource
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -5,6 +5,8 @@ kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: A powerful and unified deploy step for components multi-cluster delivery with policies.
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
name: deploy
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -7,6 +7,7 @@ metadata:
|
||||
definition.oam.dev/description: Deploy application to runtime clusters
|
||||
labels:
|
||||
custom.definition.oam.dev/deprecated: "true"
|
||||
custom.definition.oam.dev/scope: Application
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: deploy2runtime
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,8 @@ kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Sync secrets created by terraform component to runtime clusters so that runtime clusters can share the created cloud resource.
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
name: share-cloud-resource
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
|
||||
@@ -13,6 +13,7 @@ spec:
|
||||
template: |
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
mountsArray: [
|
||||
@@ -167,7 +168,11 @@ spec:
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
_name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
name: *_name | string
|
||||
if v.protocol != "TCP" {
|
||||
name: _name + "-" + strings.ToLower(v.protocol)
|
||||
}
|
||||
}
|
||||
}}]
|
||||
}
|
||||
@@ -283,11 +288,18 @@ spec:
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
_name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
name: *_name | string
|
||||
if v.protocol != "TCP" {
|
||||
name: _name + "-" + strings.ToLower(v.protocol)
|
||||
}
|
||||
}
|
||||
if v.nodePort != _|_ && parameter.exposeType == "NodePort" {
|
||||
nodePort: v.nodePort
|
||||
}
|
||||
if v.protocol != _|_ {
|
||||
protocol: v.protocol
|
||||
}
|
||||
},
|
||||
]
|
||||
outputs: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz
Normal file
Binary file not shown.
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz
Normal file
Binary file not shown.
BIN
e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz
Normal file
Binary file not shown.
@@ -34,4 +34,30 @@ entries:
|
||||
urls:
|
||||
- http://127.0.0.1:9098/helm/vela-workflow-v0.3.1.tgz
|
||||
version: v0.3.1
|
||||
foo:
|
||||
- created: "2022-10-29T09:11:16.865230605Z"
|
||||
description: Vela test addon named foo
|
||||
home: https://www.foo.com/icon
|
||||
icon: https://www.foo.com
|
||||
name: foo
|
||||
urls:
|
||||
- http://127.0.0.1:9098/helm/foo-v1.0.0.tgz
|
||||
version: v1.0.0
|
||||
bar:
|
||||
- created: "2022-10-29T09:11:16.865230605Z"
|
||||
description: Vela test addon named bar
|
||||
home: https://www.bar.com/icon
|
||||
icon: https://www.bar.com
|
||||
name: foo
|
||||
urls:
|
||||
- http://127.0.0.1:9098/helm/bar-v1.0.0.tgz
|
||||
version: v1.0.0
|
||||
- created: "2022-10-29T09:11:16.865230605Z"
|
||||
description: Vela test addon named bar
|
||||
home: https://www.bar.com/icon
|
||||
icon: https://www.bar.com
|
||||
name: foo
|
||||
urls:
|
||||
- http://127.0.0.1:9098/helm/bar-v2.0.0.tgz
|
||||
version: v2.0.0
|
||||
generated: "2022-06-15T13:17:04.733573+08:00"
|
||||
@@ -131,6 +131,24 @@ var helmHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Reques
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
case strings.Contains(req.URL.Path, "foo-v1.0.0.tgz"):
|
||||
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/foo-v1.0.0.tgz")
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
case strings.Contains(req.URL.Path, "bar-v1.0.0.tgz"):
|
||||
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/bar-v1.0.0.tgz")
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
case strings.Contains(req.URL.Path, "bar-v2.0.0.tgz"):
|
||||
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/bar-v2.0.0.tgz")
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ var (
|
||||
appbasicJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
|
||||
appbasicAddTraitJsonAppFile = `{"name":"app-basic","services":{"app-basic":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}],"scaler":{"replicas":2}}}}`
|
||||
velaQL = "test-component-pod-view{appNs=default,appName=nginx-vela,name=nginx}"
|
||||
|
||||
waitAppfileToSuccess = `{"name":"app-wait-success","services":{"app-basic1":{"type":"webservice","image":"nginx:1.9.4","ports":[{port: 80, expose: true}]}}}`
|
||||
waitAppfileToFail = `{"name":"app-wait-fail","services":{"app-basic2":{"type":"webservice","image":"nginx:fail","ports":[{port: 80, expose: true}]}}}`
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("Test Vela Application", func() {
|
||||
@@ -75,6 +78,9 @@ var _ = ginkgo.Describe("Test Vela Application", func() {
|
||||
|
||||
e2e.JsonAppFileContext("json appfile apply", testDeleteJsonAppFile)
|
||||
VelaQLPodListContext("ql", velaQL)
|
||||
|
||||
e2e.JsonAppFileContextWithWait("json appfile apply with wait", waitAppfileToSuccess)
|
||||
e2e.JsonAppFileContextWithTimeout("json appfile apply with wait but timeout", waitAppfileToFail, "3s")
|
||||
})
|
||||
|
||||
var ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
|
||||
@@ -182,7 +188,7 @@ var ApplicationInitIntercativeCliContext = func(context string, appName string,
|
||||
c.ExpectEOF()
|
||||
})
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Checking Status"))
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Waiting app to be healthy"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,6 +84,30 @@ var (
|
||||
})
|
||||
}
|
||||
|
||||
JsonAppFileContextWithWait = func(context, jsonAppFile string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("Start the application through the app file in JSON format.", func() {
|
||||
writeStatus := os.WriteFile("vela.json", []byte(jsonAppFile), 0644)
|
||||
gomega.Expect(writeStatus).NotTo(gomega.HaveOccurred())
|
||||
output, err := Exec("vela up -f vela.json --wait")
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Application Deployed Successfully!"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
JsonAppFileContextWithTimeout = func(context, jsonAppFile, duration string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("Start the application through the app file in JSON format.", func() {
|
||||
writeStatus := os.WriteFile("vela.json", []byte(jsonAppFile), 0644)
|
||||
gomega.Expect(writeStatus).NotTo(gomega.HaveOccurred())
|
||||
output, err := Exec("vela up -f vela.json --wait --timeout=" + duration)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(output).To(gomega.ContainSubstring("Timeout waiting Application to be healthy!"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
DeleteEnvFunc = func(context string, envName string) bool {
|
||||
return ginkgo.Context(context, func() {
|
||||
ginkgo.It("should print env does not exist message", func() {
|
||||
|
||||
4
go.mod
4
go.mod
@@ -50,6 +50,7 @@ require (
|
||||
github.com/gosuri/uilive v0.0.4
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/hashicorp/hcl/v2 v2.9.1
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/imdario/mergo v0.3.12
|
||||
@@ -57,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.1
|
||||
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
|
||||
@@ -341,6 +342,7 @@ require (
|
||||
)
|
||||
|
||||
replace (
|
||||
cuelang.org/go => github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.9+incompatible
|
||||
github.com/docker/docker => github.com/moby/moby v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
|
||||
github.com/wercker/stern => github.com/oam-dev/stern v1.13.2
|
||||
|
||||
9
go.sum
9
go.sum
@@ -73,8 +73,6 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq
|
||||
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
|
||||
cuelang.org/go v0.5.0-alpha.1 h1:uftOYkiScCHPCQMF2dIwoyCIJsTAEONkFSA2GCm5xIc=
|
||||
cuelang.org/go v0.5.0-alpha.1/go.mod h1:nxWFAPWKYvZJ+eYayxArWqKKjdBTeU1N52vJpML/c6w=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI=
|
||||
github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
||||
@@ -1182,6 +1180,7 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
@@ -1330,12 +1329,14 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be h1:0xj/Rh4yVy54mUD2nLmAuN1AYgBkkHxBh4PoLGbIg5g=
|
||||
github.com/kubevela/cue v0.4.4-0.20221107123854-a976b0e340be/go.mod h1:Ya12qn7FZc+LSN0qgEhzEpnzQsvnGHVgoDrqe9i3eNg=
|
||||
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2 h1:C3cAfrxst1+dIWgLLhUQt1TQvEEpp1UTq9ZQB2xKbeI=
|
||||
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.1 h1:R2h6bZbcBSF1OswF0LtLIGn+X+fS0xPOoYgWgOPn1Ig=
|
||||
github.com/kubevela/workflow v0.3.1/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=
|
||||
|
||||
@@ -6,8 +6,8 @@ e2e-setup-core-pre-hook:
|
||||
e2e-setup-core-post-hook:
|
||||
kubectl wait --for=condition=Available deployment/kubevela-vela-core -n vela-system --timeout=180s
|
||||
helm install kruise https://github.com/openkruise/charts/releases/download/kruise-1.1.0/kruise-1.1.0.tgz --set featureGates="PreDownloadImageForInPlaceUpdate=true" --set daemon.socketLocation=/run/k3s/containerd/
|
||||
kill -9 $(lsof -it:9098) || true
|
||||
go run ./e2e/addon/mock &
|
||||
sleep 15
|
||||
bin/vela addon enable ./e2e/addon/mock/testdata/fluxcd
|
||||
bin/vela addon enable ./e2e/addon/mock/testdata/rollout
|
||||
bin/vela addon enable ./e2e/addon/mock/testdata/terraform
|
||||
@@ -82,14 +82,9 @@ e2e-api-test:
|
||||
ginkgo -v -skipPackage capability,setup,application -r e2e
|
||||
ginkgo -v -r e2e/application
|
||||
|
||||
ADDONSERVER = $(shell pgrep vela_addon_mock_server)
|
||||
|
||||
|
||||
.PHONY: e2e-apiserver-test
|
||||
e2e-apiserver-test:
|
||||
pkill vela_addon_mock_server || true
|
||||
go run ./e2e/addon/mock/vela_addon_mock_server.go &
|
||||
sleep 15
|
||||
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_apiserver_test.out ./test/e2e-apiserver-test
|
||||
@$(OK) tests pass
|
||||
|
||||
|
||||
@@ -1043,7 +1043,7 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
|
||||
continue
|
||||
}
|
||||
// always install addon's latest version
|
||||
depAddon, err := h.loadInstallPackage(dep.Name, "")
|
||||
depAddon, err := h.loadInstallPackage(dep.Name, dep.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -105,7 +105,8 @@ type DeployTo struct {
|
||||
|
||||
// Dependency defines the other addons it depends on
|
||||
type Dependency struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// ElementFile can be addon's definition or addon's component
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/pkg/errors"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
|
||||
@@ -146,7 +147,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
|
||||
sort.Sort(sort.Reverse(versions))
|
||||
addonVersion, availableVersions := chooseVersion(version, versions)
|
||||
if addonVersion == nil {
|
||||
return nil, fmt.Errorf("specified version %s not exist", version)
|
||||
return nil, errors.Errorf("specified version %s not exist", utils.Sanitize(version))
|
||||
}
|
||||
for _, chartURL := range addonVersion.URLs {
|
||||
if !utils.IsValidURL(chartURL) {
|
||||
|
||||
@@ -247,6 +247,9 @@ var RevisionStatusTerminated = "terminated"
|
||||
// RevisionStatusRollback event status rollback
|
||||
var RevisionStatusRollback = "rollback"
|
||||
|
||||
// WorkflowStepPhaseStopped is the stopped phase
|
||||
var WorkflowStepPhaseStopped workflowv1alpha1.WorkflowStepPhase = "stopped"
|
||||
|
||||
// ApplicationRevision be created when an application initiates deployment and describes the phased version of the application.
|
||||
type ApplicationRevision struct {
|
||||
BaseModel
|
||||
|
||||
@@ -27,10 +27,18 @@ func init() {
|
||||
RegisterModel(&Pipeline{})
|
||||
}
|
||||
|
||||
// Structs copied from workflow/api/v1alpha1/types.go
|
||||
|
||||
// WorkflowSpec defines workflow steps and other attributes
|
||||
type WorkflowSpec struct {
|
||||
Mode *v1alpha1.WorkflowExecuteMode `json:"mode,omitempty"`
|
||||
Steps []WorkflowStep `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// Pipeline is the model of pipeline
|
||||
type Pipeline struct {
|
||||
BaseModel
|
||||
Spec v1alpha1.WorkflowSpec
|
||||
Spec WorkflowSpec
|
||||
Name string `json:"name"`
|
||||
Project string `json:"project"`
|
||||
Alias string `json:"alias"`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -231,6 +231,7 @@ func (c *cloudShellServiceImpl) prepareKubeConfig(ctx context.Context) error {
|
||||
var groups []string
|
||||
for _, p := range projects {
|
||||
permissions, err := c.RBACService.GetUserPermissions(ctx, user, p.Name, false)
|
||||
// The kubernetes permission set is generated based on simple rules, but this is not completely strict.
|
||||
var readOnly bool
|
||||
if err != nil {
|
||||
log.Logger.Errorf("failed to get the user permissions %s", err.Error())
|
||||
@@ -239,7 +240,7 @@ func (c *cloudShellServiceImpl) prepareKubeConfig(ctx context.Context) error {
|
||||
readOnly = checkReadOnly(p.Name, permissions)
|
||||
}
|
||||
if readOnly {
|
||||
groupName, err := c.managePrivilegesForProjectRead(ctx, p.Name, true)
|
||||
groupName, err := c.managePrivilegesForProject(ctx, p, true)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("failed to privileges the user %s", err.Error())
|
||||
}
|
||||
@@ -247,9 +248,16 @@ func (c *cloudShellServiceImpl) prepareKubeConfig(ctx context.Context) error {
|
||||
groups = append(groups, groupName)
|
||||
}
|
||||
} else {
|
||||
groups = append(groups, utils.KubeVelaProjectGroupPrefix+p.Name)
|
||||
groupName, err := c.managePrivilegesForProject(ctx, p, false)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("failed to privileges the user %s", err.Error())
|
||||
}
|
||||
if groupName != "" {
|
||||
groups = append(groups, groupName)
|
||||
}
|
||||
}
|
||||
}
|
||||
groups = append(groups, utils.TemplateReaderGroup)
|
||||
|
||||
if utils.StringsContain(user.UserRoles, "admin") {
|
||||
groups = append(groups, utils.KubeVelaAdminGroupPrefix+"admin")
|
||||
@@ -375,8 +383,9 @@ func checkReadOnly(projectName string, permissions []*model.Permission) bool {
|
||||
return !ra.Match(permissions)
|
||||
}
|
||||
|
||||
// managePrivilegesForProjectRead grant the read privileges for a project
|
||||
func (c *cloudShellServiceImpl) managePrivilegesForProjectRead(ctx context.Context, projectName string, readOnly bool) (string, error) {
|
||||
// managePrivilegesForProject grant the privileges for a project
|
||||
func (c *cloudShellServiceImpl) managePrivilegesForProject(ctx context.Context, project *apisv1.ProjectBase, readOnly bool) (string, error) {
|
||||
projectName := project.Name
|
||||
targets, err := c.TargetService.ListTargets(ctx, 0, 0, projectName)
|
||||
if err != nil {
|
||||
log.Logger.Infof("failed to list the targets by the project name %s :%s", projectName, err.Error())
|
||||
@@ -392,7 +401,14 @@ func (c *cloudShellServiceImpl) managePrivilegesForProjectRead(ctx context.Conte
|
||||
for _, e := range envs.Envs {
|
||||
authPDs = append(authPDs, &auth.ApplicationPrivilege{Cluster: kubevelatypes.ClusterLocalName, Namespace: e.Namespace, ReadOnly: readOnly})
|
||||
}
|
||||
|
||||
// The namespace of the environment: Application and WorkflowRun
|
||||
authPDs = append(authPDs, &auth.ApplicationPrivilege{Cluster: kubevelatypes.ClusterLocalName, Namespace: project.Namespace, ReadOnly: readOnly})
|
||||
|
||||
groupName := utils.KubeVelaProjectReadGroupPrefix + projectName
|
||||
if !readOnly {
|
||||
groupName = utils.KubeVelaProjectGroupPrefix + projectName
|
||||
}
|
||||
identity := &auth.Identity{Groups: []string{groupName}}
|
||||
writer := &bytes.Buffer{}
|
||||
if err := auth.GrantPrivileges(ctx, c.KubeClient, authPDs, identity, writer, auth.WithReplace); err != nil {
|
||||
|
||||
@@ -48,6 +48,7 @@ var _ = Describe("Test cloudshell service function", func() {
|
||||
cloudShellService *cloudShellServiceImpl
|
||||
userService *userServiceImpl
|
||||
projectService *projectServiceImpl
|
||||
envService *envServiceImpl
|
||||
err error
|
||||
database string
|
||||
)
|
||||
@@ -56,7 +57,7 @@ var _ = Describe("Test cloudshell service function", func() {
|
||||
database = "cloudshell-test-kubevela"
|
||||
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: database})
|
||||
Expect(err).Should(Succeed())
|
||||
envService := &envServiceImpl{
|
||||
envService = &envServiceImpl{
|
||||
Store: ds,
|
||||
KubeClient: k8sClient,
|
||||
}
|
||||
@@ -100,14 +101,13 @@ var _ = Describe("Test cloudshell service function", func() {
|
||||
}
|
||||
})
|
||||
|
||||
It("test prepareKubeConfig", func() {
|
||||
It("Test prepareKubeConfig", func() {
|
||||
err = userService.Init(context.TODO())
|
||||
Expect(err).Should(BeNil())
|
||||
err = projectService.Init(context.TODO())
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
By("test the developer users")
|
||||
|
||||
_, err = userService.CreateUser(context.TODO(), apisv1.CreateUserRequest{Name: "test-dev", Password: "test"})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
@@ -171,17 +171,37 @@ var _ = Describe("Test cloudshell service function", func() {
|
||||
|
||||
err = cloudShellService.prepareKubeConfig(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
var cm corev1.ConfigMap
|
||||
err = k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: kubevelatypes.DefaultKubeVelaNS, Name: makeUserConfigName("admin-test")}, &cm)
|
||||
checkConfig := func() {
|
||||
var cm corev1.ConfigMap
|
||||
err = k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: kubevelatypes.DefaultKubeVelaNS, Name: makeUserConfigName("admin-test")}, &cm)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(cm.Data["identity"]) > 0).Should(BeTrue())
|
||||
var identity auth.Identity
|
||||
err = yaml.Unmarshal([]byte(cm.Data["identity"]), &identity)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(utils.StringsContain(identity.Groups, utils.KubeVelaAdminGroupPrefix+"admin")).Should(BeTrue())
|
||||
Expect(utils.StringsContain(identity.Groups, utils.TemplateReaderGroup)).Should(BeTrue())
|
||||
}
|
||||
|
||||
checkConfig()
|
||||
|
||||
By("Test other projects")
|
||||
|
||||
_, err = projectService.CreateProject(ctx, apisv1.CreateProjectRequest{Name: "cloudshell"})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(cm.Data["identity"]) > 0).Should(BeTrue())
|
||||
var identity auth.Identity
|
||||
err = yaml.Unmarshal([]byte(cm.Data["identity"]), &identity)
|
||||
_, err = envService.CreateEnv(ctx, apisv1.CreateEnvRequest{Name: "cloudshell-env", Project: "cloudshell"})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(utils.StringsContain(identity.Groups, utils.KubeVelaAdminGroupPrefix+"admin")).Should(BeTrue())
|
||||
err = cloudShellService.prepareKubeConfig(ctx)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "kubevela:writer:application:binding", Namespace: "cloudshell-env"}, &rb)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(rb.Subjects[0].Name).Should(Equal(utils.KubeVelaProjectGroupPrefix + "cloudshell"))
|
||||
|
||||
checkConfig()
|
||||
})
|
||||
|
||||
It("test prepare", func() {
|
||||
It("Test prepare", func() {
|
||||
By("Test with not CRD")
|
||||
_, err = userService.CreateUser(context.TODO(), apisv1.CreateUserRequest{Name: "test", Password: "test"})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
@@ -69,6 +69,7 @@ type DefinitionQueryOption struct {
|
||||
AppliedWorkloads string `json:"appliedWorkloads"`
|
||||
OwnerAddon string `json:"sourceAddon"`
|
||||
QueryAll bool `json:"queryAll"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
// String return cache key string
|
||||
@@ -109,6 +110,19 @@ func (d *definitionServiceImpl) listDefinitions(ctx context.Context, list *unstr
|
||||
},
|
||||
},
|
||||
}
|
||||
if ops.Scope != "" {
|
||||
var filterScope string
|
||||
if ops.Scope == "Application" {
|
||||
filterScope = "WorkflowRun"
|
||||
} else {
|
||||
filterScope = "Application"
|
||||
}
|
||||
matchLabels.MatchExpressions = append(matchLabels.MatchExpressions, metav1.LabelSelectorRequirement{
|
||||
Key: types.LabelDefinitionScope,
|
||||
Operator: metav1.LabelSelectorOpNotIn,
|
||||
Values: []string{filterScope},
|
||||
})
|
||||
}
|
||||
if !ops.QueryAll {
|
||||
matchLabels.MatchExpressions = append(matchLabels.MatchExpressions, metav1.LabelSelectorRequirement{
|
||||
Key: types.LabelDefinitionHidden,
|
||||
|
||||
@@ -112,6 +112,11 @@ var _ = Describe("Test namespace service functions", func() {
|
||||
Expect(wfstep[0].WorkflowStep.Schematic).ShouldNot(BeNil())
|
||||
Expect(wfstep[0].Alias).Should(Equal("test-alias"))
|
||||
|
||||
wfstep, err = definitionService.ListDefinitions(context.TODO(), DefinitionQueryOption{Type: "workflowstep", Scope: "WorkflowRun"})
|
||||
Expect(err).Should(BeNil())
|
||||
// the definition should be filtered
|
||||
Expect(cmp.Diff(len(wfstep), 1)).Should(BeEmpty())
|
||||
|
||||
step, err = ioutil.ReadFile("./testdata/apply-application-hide.yaml")
|
||||
Expect(err).Should(Succeed())
|
||||
var sd2 v1beta1.WorkflowStepDefinition
|
||||
|
||||
@@ -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 {
|
||||
@@ -673,13 +673,17 @@ func getResourceLogs(ctx context.Context, config *rest.Config, cli client.Client
|
||||
}
|
||||
break
|
||||
}
|
||||
shouldPrint := true
|
||||
if len(filters) != 0 {
|
||||
for _, f := range filters {
|
||||
if strings.Contains(s, f) {
|
||||
buf.WriteString(s)
|
||||
if !strings.Contains(s, f) {
|
||||
shouldPrint = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if shouldPrint {
|
||||
buf.WriteString(s)
|
||||
}
|
||||
}
|
||||
if readErr != nil {
|
||||
errPrint(buf, "error in copy information from APIServer to buffer: %s", err.Error())
|
||||
@@ -705,6 +709,48 @@ func getResourceLogs(ctx context.Context, config *rest.Config, cli client.Client
|
||||
return logBuilder.String(), nil
|
||||
}
|
||||
|
||||
func pipelineStep2WorkflowStep(step model.WorkflowStep) v1alpha1.WorkflowStep {
|
||||
res := v1alpha1.WorkflowStep{
|
||||
WorkflowStepBase: v1alpha1.WorkflowStepBase{
|
||||
Name: step.Name,
|
||||
Type: step.Type,
|
||||
Meta: step.Meta,
|
||||
If: step.If,
|
||||
Timeout: step.Timeout,
|
||||
DependsOn: step.DependsOn,
|
||||
Inputs: step.Inputs,
|
||||
Outputs: step.Outputs,
|
||||
Properties: step.Properties.RawExtension(),
|
||||
},
|
||||
SubSteps: make([]v1alpha1.WorkflowStepBase, 0),
|
||||
}
|
||||
for _, subStep := range step.SubSteps {
|
||||
res.SubSteps = append(res.SubSteps, v1alpha1.WorkflowStepBase{
|
||||
Name: subStep.Name,
|
||||
Type: subStep.Type,
|
||||
Meta: subStep.Meta,
|
||||
If: subStep.If,
|
||||
Timeout: subStep.Timeout,
|
||||
DependsOn: subStep.DependsOn,
|
||||
Inputs: subStep.Inputs,
|
||||
Outputs: subStep.Outputs,
|
||||
Properties: subStep.Properties.RawExtension(),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func pipelineSpec2WorkflowSpec(spec model.WorkflowSpec) *v1alpha1.WorkflowSpec {
|
||||
res := &v1alpha1.WorkflowSpec{
|
||||
Mode: spec.Mode,
|
||||
Steps: make([]v1alpha1.WorkflowStep, 0),
|
||||
}
|
||||
for _, step := range spec.Steps {
|
||||
res.Steps = append(res.Steps, pipelineStep2WorkflowStep(step))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// RunPipeline will run a pipeline
|
||||
func (p pipelineServiceImpl) RunPipeline(ctx context.Context, pipeline apis.PipelineBase, req apis.RunPipelineRequest) (*apis.PipelineRun, error) {
|
||||
if err := checkRunMode(&req.Mode); err != nil {
|
||||
@@ -714,9 +760,10 @@ func (p pipelineServiceImpl) RunPipeline(ctx context.Context, pipeline apis.Pipe
|
||||
run := v1alpha1.WorkflowRun{}
|
||||
version := utils.GenerateVersion("")
|
||||
name := fmt.Sprintf("%s-%s", pipeline.Name, version)
|
||||
s := pipeline.Spec
|
||||
run.Name = name
|
||||
run.Namespace = project.GetNamespace()
|
||||
run.Spec.WorkflowSpec = &pipeline.Spec
|
||||
run.Spec.WorkflowSpec = pipelineSpec2WorkflowSpec(s)
|
||||
run.Spec.Mode = &req.Mode
|
||||
|
||||
run.SetLabels(map[string]string{
|
||||
@@ -884,7 +931,7 @@ func (p pipelineRunServiceImpl) GetPipelineRun(ctx context.Context, meta apis.Pi
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
run.Spec.WorkflowSpec = &pipeline.Spec
|
||||
run.Spec.WorkflowSpec = pipelineSpec2WorkflowSpec(pipeline.Spec)
|
||||
|
||||
}
|
||||
return workflowRun2PipelineRun(run, project, p.ContextService)
|
||||
@@ -984,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
|
||||
@@ -1239,7 +1286,7 @@ func (p pipelineRunServiceImpl) terminatePipelineRun(ctx context.Context, run *v
|
||||
|
||||
}
|
||||
|
||||
func checkPipelineSpec(spec v1alpha1.WorkflowSpec) error {
|
||||
func checkPipelineSpec(spec model.WorkflowSpec) error {
|
||||
if len(spec.Steps) == 0 {
|
||||
return bcode.ErrNoSteps
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
@@ -68,10 +67,12 @@ var _ = Describe("Test pipeline service functions", func() {
|
||||
})
|
||||
|
||||
It("Test create pipeline", func() {
|
||||
rawProps := []byte(`{"url":"https://api.github.com/repos/kubevela/kubevela"}`)
|
||||
testPipelineSteps := []v1alpha1.WorkflowStep{
|
||||
props := model.JSONStruct{
|
||||
"url": "https://api.github.com/repos/kubevela/kubevela",
|
||||
}
|
||||
testPipelineSteps := []model.WorkflowStep{
|
||||
{
|
||||
SubSteps: []v1alpha1.WorkflowStepBase{
|
||||
SubSteps: []model.WorkflowStepBase{
|
||||
{
|
||||
Name: "request",
|
||||
Type: "request",
|
||||
@@ -81,12 +82,10 @@ var _ = Describe("Test pipeline service functions", func() {
|
||||
Name: "stars",
|
||||
},
|
||||
},
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: rawProps,
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
},
|
||||
WorkflowStepBase: v1alpha1.WorkflowStepBase{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step-group",
|
||||
Type: "step-group",
|
||||
},
|
||||
@@ -96,7 +95,7 @@ var _ = Describe("Test pipeline service functions", func() {
|
||||
By("create pipeline with sub-steps")
|
||||
pipeline, err := pipelineService.CreatePipeline(ctx, apisv1.CreatePipelineRequest{
|
||||
Name: pipelineName,
|
||||
Spec: v1alpha1.WorkflowSpec{
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: testPipelineSteps,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -75,29 +75,19 @@ func (p *projectServiceImpl) Init(ctx context.Context) error {
|
||||
// the default env and default target both using the `default` namespace in control plane cluster
|
||||
func (p *projectServiceImpl) InitDefaultProjectEnvTarget(ctx context.Context, defaultNamespace string) error {
|
||||
var project = model.Project{}
|
||||
entities, err := p.Store.List(ctx, &project, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{
|
||||
IsNotExist: []datastore.IsNotExistQueryOption{
|
||||
{
|
||||
Key: "owner",
|
||||
},
|
||||
},
|
||||
}})
|
||||
entities, err := p.Store.List(ctx, &project, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialize project failed %w", err)
|
||||
}
|
||||
if len(entities) > 0 {
|
||||
for _, project := range entities {
|
||||
pro := project.(*model.Project)
|
||||
var init = pro.Owner == ""
|
||||
pro.Owner = model.DefaultAdminUserName
|
||||
if err := p.Store.Put(ctx, pro); err != nil {
|
||||
return err
|
||||
}
|
||||
// owner is empty, it is old data
|
||||
if init {
|
||||
if err := p.RbacService.InitDefaultRoleAndUsersForProject(ctx, pro); err != nil {
|
||||
return fmt.Errorf("init default role and users for project %s failure %w", pro.Name, err)
|
||||
}
|
||||
if err := p.RbacService.SyncDefaultRoleAndUsersForProject(ctx, pro); err != nil {
|
||||
return fmt.Errorf("fail to sync the default role and users for the project %s %w", pro.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -305,7 +295,6 @@ func (p *projectServiceImpl) DeleteProject(ctx context.Context, name string) err
|
||||
|
||||
// CreateProject create project
|
||||
func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.CreateProjectRequest) (*apisv1.ProjectBase, error) {
|
||||
|
||||
exist, err := p.Store.IsExist(ctx, &model.Project{Name: req.Name})
|
||||
if err != nil {
|
||||
log.Logger.Errorf("check project name is exist failure %s", err.Error())
|
||||
@@ -344,8 +333,8 @@ func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.Creat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := p.RbacService.InitDefaultRoleAndUsersForProject(ctx, newProject); err != nil {
|
||||
log.Logger.Errorf("init default role and users for project failure %s", err.Error())
|
||||
if err := p.RbacService.SyncDefaultRoleAndUsersForProject(ctx, newProject); err != nil {
|
||||
log.Logger.Errorf("fail to sync the default role and users for the project: %s", err.Error())
|
||||
}
|
||||
|
||||
return ConvertProjectModel2Base(newProject, user), nil
|
||||
|
||||
@@ -90,10 +90,10 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
|
||||
Scope: "project",
|
||||
},
|
||||
{
|
||||
Name: "configuration-read",
|
||||
Alias: "Environment Management",
|
||||
Name: "config-management",
|
||||
Alias: "Config Management",
|
||||
Resources: []string{"project:{projectName}/config:*", "project:{projectName}/provider:*"},
|
||||
Actions: []string{"list", "detail"},
|
||||
Actions: []string{"*"},
|
||||
Effect: "Allow",
|
||||
Scope: "project",
|
||||
},
|
||||
@@ -405,7 +405,7 @@ type RBACService interface {
|
||||
ListPermissions(ctx context.Context, projectName string) ([]apisv1.PermissionBase, error)
|
||||
CreatePermission(ctx context.Context, projectName string, req apisv1.CreatePermissionRequest) (*apisv1.PermissionBase, error)
|
||||
DeletePermission(ctx context.Context, projectName, permName string) error
|
||||
InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error
|
||||
SyncDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error
|
||||
Init(ctx context.Context) error
|
||||
}
|
||||
|
||||
@@ -857,7 +857,17 @@ func (p *rbacServiceImpl) CreatePermission(ctx context.Context, projectName stri
|
||||
return assembler.ConvertPermission2DTO(&permission), nil
|
||||
}
|
||||
|
||||
func (p *rbacServiceImpl) InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error {
|
||||
func (p *rbacServiceImpl) SyncDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error {
|
||||
|
||||
permissions, err := p.ListPermissions(ctx, project.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var permissionMap = map[string]apisv1.PermissionBase{}
|
||||
for i, per := range permissions {
|
||||
permissionMap[per.Name] = permissions[i]
|
||||
}
|
||||
|
||||
var batchData []datastore.Entity
|
||||
for _, permissionTemp := range defaultProjectPermissionTemplate {
|
||||
var rra = RequestResourceAction{}
|
||||
@@ -871,39 +881,52 @@ func (p *rbacServiceImpl) InitDefaultRoleAndUsersForProject(ctx context.Context,
|
||||
})
|
||||
formattedResource = append(formattedResource, rra.GetResource().String())
|
||||
}
|
||||
batchData = append(batchData, &model.Permission{
|
||||
permission := &model.Permission{
|
||||
Name: permissionTemp.Name,
|
||||
Alias: permissionTemp.Alias,
|
||||
Project: project.Name,
|
||||
Resources: formattedResource,
|
||||
Actions: permissionTemp.Actions,
|
||||
Effect: permissionTemp.Effect,
|
||||
})
|
||||
}
|
||||
batchData = append(batchData, &model.Role{
|
||||
Name: "app-developer",
|
||||
Alias: "App Developer",
|
||||
Permissions: []string{"project-view", "app-management", "env-management", "configuration-read"},
|
||||
Project: project.Name,
|
||||
}, &model.Role{
|
||||
Name: "project-admin",
|
||||
Alias: "Project Admin",
|
||||
Permissions: []string{"project-view", "app-management", "env-management", "role-management", "pipeline-management", "configuration-read"},
|
||||
Project: project.Name,
|
||||
}, &model.Role{
|
||||
Name: "project-viewer",
|
||||
Alias: "Project Viewer",
|
||||
Permissions: []string{"project-view"},
|
||||
Project: project.Name,
|
||||
})
|
||||
if project.Owner != "" {
|
||||
var projectUser = &model.ProjectUser{
|
||||
ProjectName: project.Name,
|
||||
UserRoles: []string{"project-admin"},
|
||||
Username: project.Owner,
|
||||
}
|
||||
batchData = append(batchData, projectUser)
|
||||
if perm, exist := permissionMap[permissionTemp.Name]; exist {
|
||||
if !utils.EqualSlice(perm.Resources, permissionTemp.Resources) || utils.EqualSlice(perm.Actions, permissionTemp.Actions) {
|
||||
if err := p.Store.Put(ctx, permission); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
batchData = append(batchData, permission)
|
||||
}
|
||||
|
||||
if len(permissions) == 0 {
|
||||
batchData = append(batchData, &model.Role{
|
||||
Name: "app-developer",
|
||||
Alias: "App Developer",
|
||||
Permissions: []string{"project-view", "app-management", "env-management", "config-management", "pipeline-management"},
|
||||
Project: project.Name,
|
||||
}, &model.Role{
|
||||
Name: "project-admin",
|
||||
Alias: "Project Admin",
|
||||
Permissions: []string{"project-view", "app-management", "env-management", "pipeline-management", "config-management", "role-management"},
|
||||
Project: project.Name,
|
||||
}, &model.Role{
|
||||
Name: "project-viewer",
|
||||
Alias: "Project Viewer",
|
||||
Permissions: []string{"project-view"},
|
||||
Project: project.Name,
|
||||
})
|
||||
if project.Owner != "" {
|
||||
var projectUser = &model.ProjectUser{
|
||||
ProjectName: project.Name,
|
||||
UserRoles: []string{"project-admin"},
|
||||
Username: project.Owner,
|
||||
}
|
||||
batchData = append(batchData, projectUser)
|
||||
}
|
||||
}
|
||||
|
||||
return p.Store.BatchAdd(ctx, batchData)
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ var _ = Describe("Test rbac service", func() {
|
||||
|
||||
err = ds.Add(context.TODO(), &model.Project{Name: "init-test", Owner: "test-user"})
|
||||
Expect(err).Should(BeNil())
|
||||
err = rbacService.InitDefaultRoleAndUsersForProject(context.TODO(), &model.Project{Name: "init-test"})
|
||||
err = rbacService.SyncDefaultRoleAndUsersForProject(context.TODO(), &model.Project{Name: "init-test"})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
roles, err := rbacService.ListRole(context.TODO(), "init-test", 0, 0)
|
||||
|
||||
@@ -8,6 +8,8 @@ metadata:
|
||||
definition.oam.dev/alias: test-alias
|
||||
name: apply-application
|
||||
namespace: vela-system
|
||||
labels:
|
||||
custom.definition.oam.dev/scope: Application
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
|
||||
@@ -52,6 +52,9 @@ func guaranteePolicyNotExist(c []string, policy string) ([]string, bool) {
|
||||
// extractPolicyListAndProperty can extract policy from string-format properties, and return
|
||||
// map-format properties in order to further update operation.
|
||||
func extractPolicyListAndProperty(property string) ([]string, map[string]interface{}, error) {
|
||||
if len(property) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
content := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(property), &content)
|
||||
if err != nil {
|
||||
|
||||
@@ -208,6 +208,14 @@ func TestExtractPolicyListAndProperty(t *testing.T) {
|
||||
noError bool
|
||||
}{noError: false},
|
||||
},
|
||||
{
|
||||
input: ``,
|
||||
res: struct {
|
||||
policies []string
|
||||
properties map[string]interface{}
|
||||
noError bool
|
||||
}{policies: nil, properties: nil, noError: true},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
policy, properties, err := extractPolicyListAndProperty(testCase.input)
|
||||
|
||||
@@ -623,7 +623,7 @@ func resetRevisionsAndRecords(ctx context.Context, ds datastore.DataStore, appNa
|
||||
record.Finished = "true"
|
||||
for i, step := range record.Steps {
|
||||
if step.Phase == workflowv1alpha1.WorkflowStepPhaseRunning {
|
||||
record.Steps[i].Phase = workflowv1alpha1.WorkflowStepPhaseStopped
|
||||
record.Steps[i].Phase = model.WorkflowStepPhaseStopped
|
||||
}
|
||||
}
|
||||
if err := ds.Put(ctx, record); err != nil {
|
||||
|
||||
@@ -586,7 +586,7 @@ var _ = Describe("Test workflow service functions", func() {
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(record.Status).Should(Equal(model.RevisionStatusTerminated))
|
||||
Expect(record.Finished).Should(Equal("true"))
|
||||
Expect(record.Steps[1].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseStopped))
|
||||
Expect(record.Steps[1].Phase).Should(Equal(model.WorkflowStepPhaseStopped))
|
||||
})
|
||||
|
||||
It("Test deleting workflow", func() {
|
||||
|
||||
@@ -51,6 +51,7 @@ func (d *definitionAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
Param(ws.QueryParameter("queryAll", "query all definitions include hidden in UI").DataType("boolean").DefaultValue("false")).
|
||||
Param(ws.QueryParameter("appliedWorkload", "if specified, query the trait definition applied to the workload").DataType("string")).
|
||||
Param(ws.QueryParameter("ownerAddon", "query by which addon created the definition").DataType("string")).
|
||||
Param(ws.QueryParameter("scope", "query by the specified scope like WorkflowRun or Application").DataType("string")).
|
||||
Returns(200, "OK", apis.ListDefinitionResponse{}).
|
||||
Writes(apis.ListDefinitionResponse{}).Do(returns200, returns500))
|
||||
|
||||
@@ -97,6 +98,7 @@ func (d *definitionAPIInterface) listDefinitions(req *restful.Request, res *rest
|
||||
Type: req.QueryParameter("type"),
|
||||
AppliedWorkloads: req.QueryParameter("appliedWorkload"),
|
||||
OwnerAddon: req.QueryParameter("ownerAddon"),
|
||||
Scope: req.QueryParameter("scope"),
|
||||
QueryAll: queryAll,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -793,7 +793,7 @@ type ProjectBase struct {
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
UpdateTime time.Time `json:"updateTime"`
|
||||
Owner NameAlias `json:"owner,omitempty"`
|
||||
Namespace string `json:"-"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// CreateProjectRequest create project request body
|
||||
@@ -1564,7 +1564,7 @@ type PipelineMeta struct {
|
||||
// PipelineBase is the base info of pipeline
|
||||
type PipelineBase struct {
|
||||
PipelineMeta `json:",inline"`
|
||||
Spec workflowv1alpha1.WorkflowSpec `json:"spec"`
|
||||
Spec model.WorkflowSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// RunStatInfo is the pipeline run statistics info
|
||||
@@ -1583,10 +1583,10 @@ type RunStat struct {
|
||||
|
||||
// CreatePipelineRequest is the request body of creating pipeline
|
||||
type CreatePipelineRequest struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
Description string `json:"description" optional:"true"`
|
||||
Spec workflowv1alpha1.WorkflowSpec `json:"spec"`
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
Description string `json:"description" optional:"true"`
|
||||
Spec model.WorkflowSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// PipelineMetaResponse is the response body contains PipelineMeta
|
||||
@@ -1615,9 +1615,9 @@ type PipelineListItem struct {
|
||||
|
||||
// UpdatePipelineRequest is the request body of updating pipeline
|
||||
type UpdatePipelineRequest struct {
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
Description string `json:"description" optional:"true"`
|
||||
Spec workflowv1alpha1.WorkflowSpec `json:"spec" optional:"true"`
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
Description string `json:"description" optional:"true"`
|
||||
Spec model.WorkflowSpec `json:"spec" optional:"true"`
|
||||
}
|
||||
|
||||
// GetPipelineResponse is the response body of getting pipeline
|
||||
|
||||
@@ -38,6 +38,9 @@ const KubeVelaProjectReadGroupPrefix = "kubevela:project-ro:"
|
||||
// KubeVelaAdminGroupPrefix the prefix kubevela admin
|
||||
const KubeVelaAdminGroupPrefix = "kubevela:admin:"
|
||||
|
||||
// TemplateReaderGroup This group includes the permission that read the ConfigMap in the vela-system namespace.
|
||||
const TemplateReaderGroup = "template-reader"
|
||||
|
||||
// ContextWithUserInfo extract user from context (parse username and project) for impersonation
|
||||
func ContextWithUserInfo(ctx context.Context) context.Context {
|
||||
if !features.APIServerFeatureGate.Enabled(features.APIServerEnableImpersonation) {
|
||||
|
||||
@@ -329,7 +329,7 @@ func (af *Appfile) generateAndFilterCommonLabels(compName string) map[string]str
|
||||
oam.LabelAppComponent: compName,
|
||||
}
|
||||
// merge application's all labels
|
||||
finalLabels := util.MergeMapOverrideWithDst(Labels, af.AppLabels)
|
||||
finalLabels := util.MergeMapOverrideWithDst(af.AppLabels, Labels)
|
||||
filterLabels, ok := af.AppAnnotations[oam.AnnotationFilterLabelKeys]
|
||||
if ok {
|
||||
filter(finalLabels, strings.Split(filterLabels, ","))
|
||||
|
||||
@@ -338,7 +338,7 @@ func (a *ApplicationPrivilege) GetRoles() []client.Object {
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"core.oam.dev"},
|
||||
Resources: []string{"applications", "policies", "workflows"},
|
||||
Resources: []string{"applications", "policies", "workflows", "workflowruns"},
|
||||
Verbs: verbs,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -192,7 +192,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
},
|
||||
{
|
||||
Type: "env",
|
||||
Properties: &runtime.RawExtension{Raw: []byte("{\"env\":{\"thirdKey\":\"thirdValue\"}}")},
|
||||
Properties: &runtime.RawExtension{Raw: []byte("{\"env\":{\"firstKey\":\"newValue\"}}")},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ func JoinClusterByKubeConfig(ctx context.Context, cli client.Client, kubeconfigP
|
||||
}
|
||||
}
|
||||
if cfg, ok := ctx.Value(KubeConfigContext).(*rest.Config); ok {
|
||||
if err = SetClusterVersionInfo(ctx, cfg, clusterName); err != nil {
|
||||
if err = SetClusterVersionInfo(ctx, cfg, clusterConfig.ClusterName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,15 @@ import (
|
||||
var (
|
||||
// Scheme defines the default KubeVela schema
|
||||
Scheme = k8sruntime.NewScheme()
|
||||
// forbidRedirectFunc general check func for http redirect response
|
||||
forbidRedirectFunc = func(req *http.Request, via []*http.Request) error {
|
||||
return errors.New("got a redirect response which is forbidden")
|
||||
}
|
||||
//nolint:gosec
|
||||
// insecureHTTPClient insecure http client
|
||||
insecureHTTPClient = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
|
||||
insecureHTTPClient = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, CheckRedirect: forbidRedirectFunc}
|
||||
// forbidRedirectClient is a http client forbid redirect http request
|
||||
forbidRedirectClient = &http.Client{CheckRedirect: forbidRedirectFunc}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -170,7 +176,7 @@ func HTTPGetResponse(ctx context.Context, url string, opts *HTTPOption) (*http.R
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpClient := http.DefaultClient
|
||||
httpClient := forbidRedirectClient
|
||||
if opts != nil && len(opts.Username) != 0 && len(opts.Password) != 0 {
|
||||
req.SetBasicAuth(opts.Username, opts.Password)
|
||||
}
|
||||
@@ -198,7 +204,7 @@ func HTTPGetResponse(ctx context.Context, url string, opts *HTTPOption) (*http.R
|
||||
}
|
||||
tr.TLSClientConfig = tlsConfig
|
||||
defer tr.CloseIdleConnections()
|
||||
httpClient = &http.Client{Transport: &tr}
|
||||
httpClient = &http.Client{Transport: &tr, CheckRedirect: forbidRedirectFunc}
|
||||
}
|
||||
return httpClient.Do(req)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -223,6 +224,25 @@ func TestHttpGetCaFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpGetForbidRedirect(t *testing.T) {
|
||||
var ctx = context.Background()
|
||||
testServer := &http.Server{Addr: ":19090"}
|
||||
|
||||
http.HandleFunc("/redirect", func(writer http.ResponseWriter, request *http.Request) {
|
||||
http.Redirect(writer, request, "http://192.168.1.1", http.StatusFound)
|
||||
})
|
||||
|
||||
go func() {
|
||||
err := testServer.ListenAndServe()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
_, err := HTTPGetWithOption(ctx, "http://127.0.0.1:19090/redirect", nil)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), "got a redirect response which is forbidden"))
|
||||
}
|
||||
|
||||
func TestGetCUEParameterValue(t *testing.T) {
|
||||
type want struct {
|
||||
err error
|
||||
|
||||
@@ -225,7 +225,7 @@ func (h *Helper) GetIndexInfo(repoURL string, skipCache bool, opts *common.HTTPO
|
||||
}
|
||||
i := &repo.IndexFile{}
|
||||
if err := yaml.UnmarshalStrict(body, i); err != nil {
|
||||
return nil, fmt.Errorf("parse index file from %s failure %w", repoURL, err)
|
||||
return nil, fmt.Errorf("parse index file from %s failure", repoURL)
|
||||
}
|
||||
|
||||
if h.cache != nil {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
networkv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -108,7 +109,7 @@ func getServiceEndpoints(ctx context.Context, cli client.Client, gvk schema.Grou
|
||||
switch gvk.Kind {
|
||||
case "Ingress":
|
||||
if gvk.Group == networkv1beta1.GroupName && (gvk.Version == "v1beta1" || gvk.Version == "v1") {
|
||||
var ingress networkv1beta1.Ingress
|
||||
var ingress v1.Ingress
|
||||
ingress.SetGroupVersionKind(gvk)
|
||||
if err := findResource(ctx, cli, &ingress, name, namespace, cluster); err != nil {
|
||||
klog.Error(err, fmt.Sprintf("find v1 Ingress %s/%s from cluster %s failure", name, namespace, cluster))
|
||||
@@ -232,7 +233,7 @@ func generatorFromService(service corev1.Service, selectorNodeIP func() string,
|
||||
return serviceEndpoints
|
||||
}
|
||||
|
||||
func generatorFromIngress(ingress networkv1beta1.Ingress, cluster, component string) (serviceEndpoints []querytypes.ServiceEndpoint) {
|
||||
func generatorFromIngress(ingress v1.Ingress, cluster, component string) (serviceEndpoints []querytypes.ServiceEndpoint) {
|
||||
getAppProtocol := func(host string) string {
|
||||
if len(ingress.Spec.TLS) > 0 {
|
||||
for _, tls := range ingress.Spec.TLS {
|
||||
@@ -407,26 +408,10 @@ func selectorNodeIP(ctx context.Context, clusterName string, client client.Clien
|
||||
workerNodes = append(workerNodes, nodes.Items[i])
|
||||
}
|
||||
}
|
||||
if gatewayNode == nil && len(workerNodes) > 0 {
|
||||
gatewayNode = &workerNodes[0]
|
||||
}
|
||||
if gatewayNode == nil {
|
||||
gatewayNode = &nodes.Items[0]
|
||||
}
|
||||
if gatewayNode != nil {
|
||||
var addressMap = make(map[corev1.NodeAddressType]string)
|
||||
for _, address := range gatewayNode.Status.Addresses {
|
||||
addressMap[address.Type] = address.Address
|
||||
}
|
||||
// first get external ip
|
||||
if ip, exist := addressMap[corev1.NodeExternalIP]; exist {
|
||||
return ip
|
||||
}
|
||||
if ip, exist := addressMap[corev1.NodeInternalIP]; exist {
|
||||
return ip
|
||||
}
|
||||
return selectGatewayIP([]corev1.Node{*gatewayNode})
|
||||
}
|
||||
return ""
|
||||
return selectGatewayIP(workerNodes)
|
||||
}
|
||||
|
||||
// judgeAppProtocol RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
@@ -444,3 +429,23 @@ func judgeAppProtocol(port int32) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// selectGatewayIP will choose one gateway IP from all nodes, it will pick up external IP first. If there isn't any, it will pick the first node's internal IP.
|
||||
func selectGatewayIP(nodes []corev1.Node) string {
|
||||
if len(nodes) == 0 {
|
||||
return ""
|
||||
}
|
||||
var addressMaps = make([]map[corev1.NodeAddressType]string, 0)
|
||||
for _, node := range nodes {
|
||||
var addressMap = make(map[corev1.NodeAddressType]string)
|
||||
for _, address := range node.Status.Addresses {
|
||||
addressMap[address.Type] = address.Address
|
||||
}
|
||||
// first get external ip
|
||||
if ip, exist := addressMap[corev1.NodeExternalIP]; exist {
|
||||
return ip
|
||||
}
|
||||
addressMaps = append(addressMaps, addressMap)
|
||||
}
|
||||
return addressMaps[0][corev1.NodeInternalIP]
|
||||
}
|
||||
|
||||
@@ -18,15 +18,19 @@ package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
monitorContext "github.com/kubevela/pkg/monitor/context"
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
@@ -39,7 +43,7 @@ import (
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
|
||||
)
|
||||
|
||||
var _ = Describe("Test Query Provider", func() {
|
||||
var _ = Describe("Test query endpoints", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
})
|
||||
@@ -268,5 +272,87 @@ var _ = Describe("Test Query Provider", func() {
|
||||
}
|
||||
Expect(edps).Should(BeEquivalentTo(urls))
|
||||
})
|
||||
|
||||
It("Test select gateway IP", func() {
|
||||
node1 := corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-with-external-ip",
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{Type: corev1.NodeExternalIP, Address: "node1-external-ip"},
|
||||
{Type: corev1.NodeInternalIP, Address: "node1-internal-ip"},
|
||||
},
|
||||
},
|
||||
}
|
||||
node2 := corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-without-external-ip",
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{Type: corev1.NodeInternalIP, Address: "node2-internal-ip"},
|
||||
},
|
||||
},
|
||||
}
|
||||
testCases := []struct {
|
||||
nodes []corev1.Node
|
||||
wantIP string
|
||||
}{
|
||||
{
|
||||
nodes: []corev1.Node{node1, node2},
|
||||
wantIP: "node1-external-ip",
|
||||
},
|
||||
{
|
||||
nodes: []corev1.Node{node2},
|
||||
wantIP: "node2-internal-ip",
|
||||
},
|
||||
{
|
||||
nodes: []corev1.Node{},
|
||||
wantIP: "",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
gotIP := selectGatewayIP(tc.nodes)
|
||||
Expect(gotIP).Should(BeEquivalentTo(tc.wantIP))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Test get ingress endpoint", func() {
|
||||
It("Test get ingress endpoint with different apiVersion", func() {
|
||||
ingress1 := v1.Ingress{}
|
||||
Expect(yaml.Unmarshal([]byte(ingressYaml1), &ingress1)).Should(BeNil())
|
||||
|
||||
err := k8sClient.Create(ctx, &ingress1)
|
||||
Expect(err).Should(BeNil())
|
||||
gvk := schema.GroupVersionKind{Group: "networking.k8s.io", Version: "v1", Kind: "Ingress"}
|
||||
Eventually(func() error {
|
||||
eps := getServiceEndpoints(ctx, k8sClient, gvk, ingress1.Name, ingress1.Namespace, "", "", nil)
|
||||
if len(eps) != 1 {
|
||||
return fmt.Errorf("result length missmatch")
|
||||
}
|
||||
return nil
|
||||
}, 2*time.Second, 500*time.Millisecond).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
var ingressYaml1 = `
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-1
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /testpath
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: test
|
||||
port:
|
||||
number: 80
|
||||
`
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -743,25 +743,29 @@ options: {
|
||||
}
|
||||
}
|
||||
|
||||
var prefixbeta = networkv1beta1.PathTypePrefix
|
||||
var prefixbeta = networkv1.PathTypePrefix
|
||||
testIngress := []client.Object{
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-http",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Spec: networkv1.IngressSpec{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
@@ -772,28 +776,32 @@ options: {
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-https",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
TLS: []networkv1beta1.IngressTLS{
|
||||
Spec: networkv1.IngressSpec{
|
||||
TLS: []networkv1.IngressTLS{
|
||||
{
|
||||
SecretName: "https-secret",
|
||||
},
|
||||
},
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain.https",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
@@ -804,36 +812,44 @@ options: {
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-paths",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
TLS: []networkv1beta1.IngressTLS{
|
||||
Spec: networkv1.IngressSpec{
|
||||
TLS: []networkv1.IngressTLS{
|
||||
{
|
||||
SecretName: "https-secret",
|
||||
},
|
||||
},
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain.path",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/test",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
{
|
||||
Path: "/test2",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
@@ -844,7 +860,7 @@ options: {
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "networking.k8s.io/v1beta1",
|
||||
},
|
||||
@@ -856,18 +872,22 @@ options: {
|
||||
"helm.toolkit.fluxcd.io/namespace": "default",
|
||||
},
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Spec: networkv1.IngressSpec{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain.helm",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
|
||||
@@ -213,7 +213,7 @@ func init() {
|
||||
listOptions: cronJobLabelListOption,
|
||||
},
|
||||
}),
|
||||
GroupResourceType: GroupResourceType{Group: "batch/v1", Kind: "CronJob"},
|
||||
GroupResourceType: GroupResourceType{Group: "batch", Kind: "CronJob"},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1460,6 +1460,19 @@ var _ = Describe("unit-test to e2e test", func() {
|
||||
Expect(len(tn)).Should(BeEquivalentTo(2))
|
||||
Expect(len(tn[0].LeafNodes)).Should(BeEquivalentTo(1))
|
||||
Expect(len(tn[1].LeafNodes)).Should(BeEquivalentTo(1))
|
||||
|
||||
tn, err = iterateListSubResources(ctx, "", k8sClient, types.ResourceTreeNode{
|
||||
Cluster: "",
|
||||
Namespace: "test-namespace",
|
||||
Name: "cronjob1",
|
||||
APIVersion: "batch/v1",
|
||||
Kind: "CronJob",
|
||||
}, 1, func(node types.ResourceTreeNode) bool {
|
||||
return true
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(tn)).Should(BeEquivalentTo(1))
|
||||
Expect(len(tn[0].LeafNodes)).Should(BeEquivalentTo(0))
|
||||
})
|
||||
|
||||
It("test provider handler func", func() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ func NewCreateConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Co
|
||||
vela config create test-config --template=image-registry -f config.yaml
|
||||
|
||||
# Generate a config without the template
|
||||
vela config create --name test-vela -f config.yaml
|
||||
vela config create test-vela -f config.yaml
|
||||
`))
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
)
|
||||
|
||||
// defaultConstraint
|
||||
const defaultConstraint = ">= 1.19, <= 1.22"
|
||||
const defaultConstraint = ">= 1.19, <= 1.24"
|
||||
|
||||
const kubevelaInstallerHelmRepoURL = "https://charts.kubevela.net/core/"
|
||||
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
}
|
||||
@@ -130,9 +130,7 @@ func (a *App) Refresh() {
|
||||
log.Printf("SystemInfo updater canceled!")
|
||||
return
|
||||
case <-time.After(delay):
|
||||
a.QueueUpdateDraw(func() {
|
||||
board.UpdateInfo(a.config.RestConfig)
|
||||
})
|
||||
board.UpdateInfo(a.config.RestConfig)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
|
||||
@@ -32,20 +33,31 @@ import (
|
||||
// TopologyView display the resource topology of application
|
||||
type TopologyView struct {
|
||||
*tview.Grid
|
||||
app *App
|
||||
actions model.KeyActions
|
||||
ctx context.Context
|
||||
focusTopology bool
|
||||
app *App
|
||||
actions model.KeyActions
|
||||
ctx context.Context
|
||||
focusTopology bool
|
||||
cache *lru.Cache
|
||||
appTopologyInstance *TopologyTree
|
||||
resourceTopologyInstance *TopologyTree
|
||||
}
|
||||
|
||||
type topologyTree struct {
|
||||
type cacheView struct {
|
||||
appTopologyInstance *TopologyTree
|
||||
resourceTopologyInstance *TopologyTree
|
||||
}
|
||||
|
||||
// TopologyTree is the abstract of topology tree
|
||||
type TopologyTree struct {
|
||||
*tview.TreeView
|
||||
}
|
||||
|
||||
const (
|
||||
numberOfCacheView = 10
|
||||
)
|
||||
|
||||
var (
|
||||
topologyViewInstance = new(TopologyView)
|
||||
appTopologyInstance = new(topologyTree)
|
||||
resourceTopologyInstance = new(topologyTree)
|
||||
topologyViewInstance = new(TopologyView)
|
||||
)
|
||||
|
||||
// NewTopologyView return a new topology view
|
||||
@@ -56,6 +68,10 @@ func NewTopologyView(ctx context.Context, app *App) model.View {
|
||||
if topologyViewInstance.Grid == nil {
|
||||
topologyViewInstance.Grid = tview.NewGrid()
|
||||
topologyViewInstance.actions = make(model.KeyActions)
|
||||
topologyViewInstance.cache, _ = lru.New(numberOfCacheView)
|
||||
topologyViewInstance.appTopologyInstance = new(TopologyTree)
|
||||
topologyViewInstance.resourceTopologyInstance = new(TopologyTree)
|
||||
|
||||
topologyViewInstance.Init()
|
||||
}
|
||||
return topologyViewInstance
|
||||
@@ -74,13 +90,28 @@ func (v *TopologyView) Init() {
|
||||
|
||||
// Start the topology view
|
||||
func (v *TopologyView) Start() {
|
||||
appTopology := v.NewAppTopologyView()
|
||||
resourceTopology := v.NewResourceTopologyView()
|
||||
appName := v.ctx.Value(&model.CtxKeyAppName).(string)
|
||||
namespace := v.ctx.Value(&model.CtxKeyNamespace).(string)
|
||||
key := fmt.Sprintf("%s-%s", appName, namespace)
|
||||
|
||||
v.Grid.AddItem(appTopology, 0, 0, 1, 1, 0, 0, true)
|
||||
v.Grid.AddItem(resourceTopology, 0, 1, 1, 1, 0, 0, true)
|
||||
value, exist := v.cache.Get(key)
|
||||
view, ok := value.(*cacheView)
|
||||
if exist && ok {
|
||||
v.appTopologyInstance = view.appTopologyInstance
|
||||
v.resourceTopologyInstance = view.resourceTopologyInstance
|
||||
} else {
|
||||
v.resourceTopologyInstance = v.NewResourceTopologyView()
|
||||
v.appTopologyInstance = v.NewAppTopologyView()
|
||||
v.cache.Add(key, &cacheView{
|
||||
resourceTopologyInstance: v.resourceTopologyInstance,
|
||||
appTopologyInstance: v.appTopologyInstance,
|
||||
})
|
||||
}
|
||||
|
||||
v.app.SetFocus(appTopology)
|
||||
v.Grid.AddItem(v.appTopologyInstance, 0, 0, 1, 1, 0, 0, true)
|
||||
v.Grid.AddItem(v.resourceTopologyInstance, 0, 1, 1, 1, 0, 0, true)
|
||||
|
||||
v.app.SetFocus(v.appTopologyInstance)
|
||||
}
|
||||
|
||||
// Stop the topology view
|
||||
@@ -119,54 +150,49 @@ func (v *TopologyView) bindKeys() {
|
||||
}
|
||||
|
||||
// NewResourceTopologyView return a new resource topology view
|
||||
func (v *TopologyView) NewResourceTopologyView() tview.Primitive {
|
||||
if resourceTopologyInstance.TreeView == nil {
|
||||
resourceTopologyInstance.TreeView = tview.NewTreeView()
|
||||
resourceTopologyInstance.SetGraphics(true)
|
||||
resourceTopologyInstance.SetGraphicsColor(tcell.ColorCadetBlue)
|
||||
resourceTopologyInstance.SetBorder(true)
|
||||
resourceTopologyInstance.SetTitle(fmt.Sprintf("[ %s ]", "Resource"))
|
||||
}
|
||||
|
||||
func (v *TopologyView) NewResourceTopologyView() *TopologyTree {
|
||||
newTopology := new(TopologyTree)
|
||||
appName := v.ctx.Value(&model.CtxKeyAppName).(string)
|
||||
namespace := v.ctx.Value(&model.CtxKeyNamespace).(string)
|
||||
|
||||
newTopology.TreeView = tview.NewTreeView()
|
||||
newTopology.SetGraphics(true)
|
||||
newTopology.SetGraphicsColor(tcell.ColorCadetBlue)
|
||||
newTopology.SetBorder(true)
|
||||
newTopology.SetTitle(fmt.Sprintf("[ %s ]", "Resource"))
|
||||
|
||||
root := tview.NewTreeNode(component.EmojiFormat(fmt.Sprintf("%s (%s)", appName, namespace), "app")).SetSelectable(true)
|
||||
resourceTopologyInstance.SetRoot(root)
|
||||
newTopology.SetRoot(root)
|
||||
|
||||
resourceTree, err := model.ApplicationResourceTopology(v.app.client, appName, namespace)
|
||||
if err != nil {
|
||||
return resourceTopologyInstance
|
||||
if err == nil {
|
||||
for _, resource := range resourceTree {
|
||||
root.AddChild(buildTopology(resource.ResourceTree))
|
||||
}
|
||||
}
|
||||
for _, resource := range resourceTree {
|
||||
root.AddChild(buildTopology(resource.ResourceTree))
|
||||
}
|
||||
|
||||
return resourceTopologyInstance
|
||||
return newTopology
|
||||
}
|
||||
|
||||
// NewAppTopologyView return a new app topology view
|
||||
func (v *TopologyView) NewAppTopologyView() tview.Primitive {
|
||||
if appTopologyInstance.TreeView == nil {
|
||||
appTopologyInstance.TreeView = tview.NewTreeView()
|
||||
appTopologyInstance.SetGraphics(true)
|
||||
appTopologyInstance.SetGraphicsColor(tcell.ColorCadetBlue)
|
||||
appTopologyInstance.SetBorder(true)
|
||||
appTopologyInstance.SetTitle(fmt.Sprintf("[ %s ]", "App"))
|
||||
}
|
||||
|
||||
func (v *TopologyView) NewAppTopologyView() *TopologyTree {
|
||||
newTopology := new(TopologyTree)
|
||||
appName := v.ctx.Value(&model.CtxKeyAppName).(string)
|
||||
namespace := v.ctx.Value(&model.CtxKeyNamespace).(string)
|
||||
|
||||
newTopology.TreeView = tview.NewTreeView()
|
||||
newTopology.SetGraphics(true)
|
||||
newTopology.SetGraphicsColor(tcell.ColorCadetBlue)
|
||||
newTopology.SetBorder(true)
|
||||
newTopology.SetTitle(fmt.Sprintf("[ %s ]", "App"))
|
||||
|
||||
root := tview.NewTreeNode(component.EmojiFormat(fmt.Sprintf("%s (%s)", appName, namespace), "app")).SetSelectable(true)
|
||||
|
||||
appTopologyInstance.SetRoot(root)
|
||||
newTopology.SetRoot(root)
|
||||
|
||||
app, err := model.LoadApplication(v.app.client, appName, namespace)
|
||||
if err != nil {
|
||||
return appTopologyInstance
|
||||
return newTopology
|
||||
}
|
||||
|
||||
// workflow
|
||||
workflowNode := tview.NewTreeNode(component.EmojiFormat("WorkFlow", "workflow")).SetSelectable(true)
|
||||
root.AddChild(workflowNode)
|
||||
@@ -206,15 +232,14 @@ func (v *TopologyView) NewAppTopologyView() tview.Primitive {
|
||||
for _, policy := range app.Spec.Policies {
|
||||
policyNode.AddChild(tview.NewTreeNode(policy.Name))
|
||||
}
|
||||
|
||||
return appTopologyInstance
|
||||
return newTopology
|
||||
}
|
||||
|
||||
func (v *TopologyView) switchTopology(_ *tcell.EventKey) *tcell.EventKey {
|
||||
if v.focusTopology {
|
||||
v.app.SetFocus(appTopologyInstance)
|
||||
v.app.SetFocus(v.appTopologyInstance)
|
||||
} else {
|
||||
v.app.SetFocus(resourceTopologyInstance)
|
||||
v.app.SetFocus(v.resourceTopologyInstance)
|
||||
}
|
||||
v.focusTopology = !v.focusTopology
|
||||
return nil
|
||||
|
||||
@@ -61,10 +61,12 @@ func TestTopologyView(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("start", func(t *testing.T) {
|
||||
appTopologyView := topologyView.NewAppTopologyView()
|
||||
assert.Equal(t, appTopologyView.HasFocus(), false)
|
||||
topologyView.Start()
|
||||
assert.Equal(t, appTopologyView.HasFocus(), true)
|
||||
assert.Equal(t, topologyView.appTopologyInstance.HasFocus(), true)
|
||||
assert.Equal(t, topologyView.resourceTopologyInstance.HasFocus(), false)
|
||||
topologyView.switchTopology(nil)
|
||||
assert.Equal(t, topologyView.appTopologyInstance.HasFocus(), false)
|
||||
assert.Equal(t, topologyView.resourceTopologyInstance.HasFocus(), true)
|
||||
})
|
||||
|
||||
t.Run("stop", func(t *testing.T) {
|
||||
|
||||
@@ -48,7 +48,7 @@ func (ps *PageStack) StackPop(old, new model.View) {
|
||||
}
|
||||
ps.app.QueueUpdateDraw(new.Start)
|
||||
ps.app.SetFocus(new)
|
||||
ps.app.QueueUpdate(old.Stop)
|
||||
go old.Stop()
|
||||
}
|
||||
|
||||
// StackPush change itself when accept "pop" notify from app's main view
|
||||
@@ -56,6 +56,6 @@ func (ps *PageStack) StackPush(old, new model.View) {
|
||||
ps.app.QueueUpdateDraw(new.Start)
|
||||
ps.app.SetFocus(new)
|
||||
if old != nil {
|
||||
ps.app.QueueUpdate(old.Stop)
|
||||
go old.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgtypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
@@ -267,25 +267,29 @@ var _ = Describe("Test velaQL", func() {
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
}
|
||||
var prefixbeta = networkv1beta1.PathTypePrefix
|
||||
var prefixbeta = networkv1.PathTypePrefix
|
||||
testIngress := []client.Object{
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-http",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Spec: networkv1.IngressSpec{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
@@ -296,28 +300,32 @@ var _ = Describe("Test velaQL", func() {
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-https",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
TLS: []networkv1beta1.IngressTLS{
|
||||
Spec: networkv1.IngressSpec{
|
||||
TLS: []networkv1.IngressTLS{
|
||||
{
|
||||
SecretName: "https-secret",
|
||||
},
|
||||
},
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain.https",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
@@ -328,36 +336,44 @@ var _ = Describe("Test velaQL", func() {
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-paths",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
TLS: []networkv1beta1.IngressTLS{
|
||||
Spec: networkv1.IngressSpec{
|
||||
TLS: []networkv1.IngressTLS{
|
||||
{
|
||||
SecretName: "https-secret",
|
||||
},
|
||||
},
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain.path",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/test",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
{
|
||||
Path: "/test2",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
@@ -368,7 +384,7 @@ var _ = Describe("Test velaQL", func() {
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkv1beta1.Ingress{
|
||||
&networkv1.Ingress{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "networking.k8s.io/v1beta1",
|
||||
},
|
||||
@@ -380,18 +396,22 @@ var _ = Describe("Test velaQL", func() {
|
||||
"helm.toolkit.fluxcd.io/namespace": "default",
|
||||
},
|
||||
},
|
||||
Spec: networkv1beta1.IngressSpec{
|
||||
Rules: []networkv1beta1.IngressRule{
|
||||
Spec: networkv1.IngressSpec{
|
||||
Rules: []networkv1.IngressRule{
|
||||
{
|
||||
Host: "ingress.domain.helm",
|
||||
IngressRuleValue: networkv1beta1.IngressRuleValue{
|
||||
HTTP: &networkv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1beta1.HTTPIngressPath{
|
||||
IngressRuleValue: networkv1.IngressRuleValue{
|
||||
HTTP: &networkv1.HTTPIngressRuleValue{
|
||||
Paths: []networkv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkv1beta1.IngressBackend{
|
||||
ServiceName: "clusterip",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
Backend: networkv1.IngressBackend{
|
||||
Service: &networkv1.IngressServiceBackend{
|
||||
Name: "clusterip",
|
||||
Port: networkv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
PathType: &prefixbeta,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -135,7 +137,6 @@ func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams, wargs
|
||||
Short: "Restart an application workflow.",
|
||||
Long: "Restart an application workflow in cluster.",
|
||||
Example: "vela workflow restart <application-name>",
|
||||
PreRun: wargs.checkWorkflowNotComplete(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
@@ -195,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
|
||||
@@ -204,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
|
||||
@@ -266,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,
|
||||
@@ -291,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:
|
||||
@@ -304,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"}
|
||||
@@ -322,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
|
||||
}
|
||||
}
|
||||
@@ -361,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
|
||||
@@ -446,3 +516,21 @@ func (w *WorkflowArgs) checkWorkflowNotComplete() func(cmd *cobra.Command, args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkflowArgs) checkDebugMode() func(cmd *cobra.Command, args []string) {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
if err := w.getWorkflowInstance(context.Background(), cmd, args); err != nil {
|
||||
return
|
||||
}
|
||||
if !w.WorkflowInstance.Debug {
|
||||
msg := ""
|
||||
if w.Type == instanceTypeApplication {
|
||||
msg = "please make sure your application have the debug policy, you can add the debug policy by using `vela up -f <app.yaml> --debug"
|
||||
} else {
|
||||
msg = "please make sure your workflow have the debug annotation [workflowrun.oam.dev/debug:true] then re-run the workflow"
|
||||
}
|
||||
cmd.Printf("workflow %s is not in debug mode, %s", w.WorkflowInstance.Name, msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ type AppfileOptions struct {
|
||||
Kubecli client.Client
|
||||
IO cmdutil.IOStreams
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
|
||||
// BuildResult is the export struct from AppFile yaml or AppFile object
|
||||
@@ -380,7 +381,7 @@ func (o *AppfileOptions) BaseAppFileRun(result *BuildResult, args common.Args) e
|
||||
return err
|
||||
}
|
||||
result.application.Spec.Components = kubernetesComponent
|
||||
|
||||
o.Name = result.application.Name
|
||||
o.IO.Infof("\nApplying application ...\n")
|
||||
return o.ApplyApp(result.application, result.scopes)
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ func GetWorkflowSteps(ctx context.Context, namespace string, c common.Args) ([]t
|
||||
for _, def := range workflowStepDefs.Items {
|
||||
tmp, err := GetCapabilityByWorkflowStepDefinitionObject(def, pd)
|
||||
if err != nil {
|
||||
templateErrors = append(templateErrors, err)
|
||||
templateErrors = append(templateErrors, errors.WithMessage(err, def.Name))
|
||||
continue
|
||||
}
|
||||
templates = append(templates, *tmp)
|
||||
|
||||
27
references/docgen/def-doc/workflowstep/apply-component.eg.md
Normal file
27
references/docgen/def-doc/workflowstep/apply-component.eg.md
Normal file
@@ -0,0 +1,27 @@
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: first-vela-workflow
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: oamdev/hello-world
|
||||
port: 8000
|
||||
traits:
|
||||
- type: ingress
|
||||
properties:
|
||||
domain: testsvc.example.com
|
||||
http:
|
||||
/: 8000
|
||||
workflow:
|
||||
steps:
|
||||
- name: express-server
|
||||
type: apply-component
|
||||
properties:
|
||||
component: express-server
|
||||
# cluster: <your cluster name>
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: print-message-in-status
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: oamdev/hello-world
|
||||
port: 8000
|
||||
workflow:
|
||||
steps:
|
||||
- name: express-server
|
||||
type: apply-component
|
||||
properties:
|
||||
component: express-server
|
||||
- name: message
|
||||
type: print-message-in-status
|
||||
properties:
|
||||
message: "All addons have been enabled successfully, you can use 'vela addon list' to check them."
|
||||
```
|
||||
@@ -213,4 +213,23 @@ var _ = Describe("Test addon rest api", func() {
|
||||
Expect(strings.Contains(errResponse.Message, "fail to install"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Test addon dependency installed version", func() {
|
||||
It("Test Operation of enabling foo addon will enable bar addon automatically", func() {
|
||||
req := apisv1.EnableAddonRequest{}
|
||||
res := post("/addons/foo/enable", req)
|
||||
defer res.Body.Close()
|
||||
var addon apisv1.AddonStatusResponse
|
||||
Expect(decodeResponseBody(res, &addon)).Should(Succeed())
|
||||
Expect(addon.Name).Should(BeEquivalentTo("foo"))
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
status := get("/addons/bar/status")
|
||||
var newaddonStatus apisv1.AddonStatusResponse
|
||||
g.Expect(decodeResponseBody(status, &newaddonStatus)).Should(Succeed())
|
||||
g.Expect(newaddonStatus.Name).Should(BeEquivalentTo("bar"))
|
||||
g.Expect(newaddonStatus.InstalledVersion).Should(BeEquivalentTo("v1.0.0"))
|
||||
}, 30*time.Second, 300*time.Millisecond).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
@@ -35,13 +34,15 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var testPipelineSteps []v1alpha1.WorkflowStep
|
||||
var (
|
||||
testPipelineSteps []model.WorkflowStep
|
||||
props = model.JSONStruct{"url": "https://api.github.com/repos/kubevela/kubevela"}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rawProps := []byte(`{"url":"https://api.github.com/repos/kubevela/kubevela"}`)
|
||||
testPipelineSteps = []v1alpha1.WorkflowStep{
|
||||
testPipelineSteps = []model.WorkflowStep{
|
||||
{
|
||||
WorkflowStepBase: v1alpha1.WorkflowStepBase{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "request",
|
||||
Type: "request",
|
||||
Outputs: v1alpha1.StepOutputs{
|
||||
@@ -50,9 +51,7 @@ func init() {
|
||||
Name: "stars",
|
||||
},
|
||||
},
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: rawProps,
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -93,7 +92,7 @@ var _ = Describe("Test the rest api about the pipeline", func() {
|
||||
var req = apisv1.CreatePipelineRequest{
|
||||
Name: pipelineName,
|
||||
Description: description,
|
||||
Spec: v1alpha1.WorkflowSpec{
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: testPipelineSteps,
|
||||
},
|
||||
}
|
||||
@@ -150,10 +149,9 @@ var _ = Describe("Test the rest api about the pipeline", func() {
|
||||
})
|
||||
|
||||
It("update pipeline", func() {
|
||||
rawProps := []byte(`{"url":"https://api.github.com/repos/kubevela/kubevela"}`)
|
||||
newSteps := make([]v1alpha1.WorkflowStep, 0)
|
||||
newSteps = append(newSteps, v1alpha1.WorkflowStep{
|
||||
SubSteps: []v1alpha1.WorkflowStepBase{
|
||||
newSteps := make([]model.WorkflowStep, 0)
|
||||
newSteps = append(newSteps, model.WorkflowStep{
|
||||
SubSteps: []model.WorkflowStepBase{
|
||||
{
|
||||
Name: "request1",
|
||||
Type: "request",
|
||||
@@ -163,9 +161,7 @@ var _ = Describe("Test the rest api about the pipeline", func() {
|
||||
Name: "stars",
|
||||
},
|
||||
},
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: rawProps,
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
{
|
||||
Name: "request2",
|
||||
@@ -176,18 +172,16 @@ var _ = Describe("Test the rest api about the pipeline", func() {
|
||||
Name: "stars-copy",
|
||||
},
|
||||
},
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: rawProps,
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
},
|
||||
WorkflowStepBase: v1alpha1.WorkflowStepBase{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "request-group",
|
||||
Type: "step-group",
|
||||
},
|
||||
})
|
||||
newSteps = append(newSteps, v1alpha1.WorkflowStep{
|
||||
WorkflowStepBase: v1alpha1.WorkflowStepBase{
|
||||
newSteps = append(newSteps, model.WorkflowStep{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "log",
|
||||
Type: "log",
|
||||
Inputs: v1alpha1.StepInputs{
|
||||
@@ -200,7 +194,7 @@ var _ = Describe("Test the rest api about the pipeline", func() {
|
||||
})
|
||||
var req = apisv1.UpdatePipelineRequest{
|
||||
Description: description,
|
||||
Spec: v1alpha1.WorkflowSpec{
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: newSteps,
|
||||
},
|
||||
}
|
||||
@@ -310,10 +304,10 @@ var _ = Describe("Test the rest api about the pipeline", func() {
|
||||
It("stop pipeline", func() {
|
||||
By("update pipeline so that it will run for a while")
|
||||
var req = apisv1.UpdatePipelineRequest{
|
||||
Spec: v1alpha1.WorkflowSpec{
|
||||
Steps: []v1alpha1.WorkflowStep{
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: []model.WorkflowStep{
|
||||
{
|
||||
WorkflowStepBase: v1alpha1.WorkflowStepBase{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "request",
|
||||
Type: "request",
|
||||
Timeout: "20s",
|
||||
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user