mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-27 16:23:52 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b4e4f8530 | ||
|
|
0121e8b6ef | ||
|
|
382510aa67 | ||
|
|
7ae7d2a5ef | ||
|
|
0736e85e07 | ||
|
|
f01e6d9723 | ||
|
|
2d7d4ef99d | ||
|
|
6bbce07a21 | ||
|
|
12ba4631c1 | ||
|
|
d5b4f9ae5d | ||
|
|
d62185315a | ||
|
|
12f0cebc6c | ||
|
|
284a7d08b2 | ||
|
|
c91850ce0d | ||
|
|
e13b31d00e | ||
|
|
71d0d7344f | ||
|
|
247845db0a | ||
|
|
427809cea7 | ||
|
|
6c29b7b088 | ||
|
|
77e85472fa | ||
|
|
c60df945c3 | ||
|
|
28488a4e9b | ||
|
|
1ae7ba1e1e | ||
|
|
2076c2f937 |
2
.github/workflows/apiserver-test.yaml
vendored
2
.github/workflows/apiserver-test.yaml
vendored
@@ -176,10 +176,12 @@ jobs:
|
||||
make e2e-cleanup
|
||||
make e2e-setup-core
|
||||
bin/vela addon enable fluxcd
|
||||
bin/vela addon enable vela-workflow
|
||||
timeout 600s bash -c -- 'while true; do kubectl get ns flux-system; if [ $? -eq 0 ] ; then break; else sleep 5; fi;done'
|
||||
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-core,app.kubernetes.io/instance=kubevela -n vela-system --timeout=600s
|
||||
kubectl wait --for=condition=Ready pod -l app=source-controller -n flux-system --timeout=600s
|
||||
kubectl wait --for=condition=Ready pod -l app=helm-controller -n flux-system --timeout=600s
|
||||
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-workflow -n vela-system --timeout=600s
|
||||
|
||||
- name: Run api server e2e test
|
||||
run: |
|
||||
|
||||
@@ -23,8 +23,17 @@ import (
|
||||
const (
|
||||
// ApplyOncePolicyType refers to the type of configuration drift policy
|
||||
ApplyOncePolicyType = "apply-once"
|
||||
// ApplyOnceStrategyOnAppUpdate policy takes effect on application updating
|
||||
ApplyOnceStrategyOnAppUpdate ApplyOnceAffectStrategy = "onUpdate"
|
||||
// ApplyOnceStrategyOnAppStateKeep policy takes effect on application state keep
|
||||
ApplyOnceStrategyOnAppStateKeep ApplyOnceAffectStrategy = "onStateKeep"
|
||||
// ApplyOnceStrategyAlways policy takes effect always
|
||||
ApplyOnceStrategyAlways ApplyOnceAffectStrategy = "always"
|
||||
)
|
||||
|
||||
// ApplyOnceAffectStrategy is a string that mark the policy effective stage
|
||||
type ApplyOnceAffectStrategy string
|
||||
|
||||
// ApplyOncePolicySpec defines the spec of preventing configuration drift
|
||||
type ApplyOncePolicySpec struct {
|
||||
Enable bool `json:"enable"`
|
||||
@@ -45,6 +54,9 @@ type ApplyOnceStrategy struct {
|
||||
// Path the specified path that allow configuration drift
|
||||
// like 'spec.template.spec.containers[0].resources' and '*' means the whole target allow configuration drift
|
||||
Path []string `json:"path"`
|
||||
// ApplyOnceAffectStrategy Decide when the strategy will take effect
|
||||
// like affect:onUpdate/onStateKeep/always
|
||||
ApplyOnceAffectStrategy ApplyOnceAffectStrategy `json:"affect"`
|
||||
}
|
||||
|
||||
// FindStrategy find apply-once strategy for target resource
|
||||
|
||||
@@ -97,6 +97,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `featureGates.gzipResourceTracker` | if enabled, resourceTracker will be compressed using gzip before being stored | `false` |
|
||||
| `featureGates.zstdResourceTracker` | if enabled, resourceTracker will be compressed using zstd before being stored. It is much faster and more efficient than gzip. If both gzip and zstd are enabled, zstd will be used. | `false` |
|
||||
| `featureGates.applyOnce` | if enabled, the apply-once feature will be applied to all applications, no state-keep and no resource data storage in ResourceTracker | `false` |
|
||||
| `featureGates.multiStageComponentApply` | if enabled, the multiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply. | `false` |
|
||||
|
||||
|
||||
### MultiCluster parameters
|
||||
|
||||
@@ -2209,10 +2209,11 @@ spec:
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: SubSteps is the mode of workflow sub
|
||||
steps execution
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
@@ -4008,6 +4009,17 @@ spec:
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: WorkflowExecuteMode defines the mode of workflow
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow
|
||||
|
||||
@@ -1020,10 +1020,10 @@ spec:
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.2
|
||||
controller-gen.kubebuilder.io/version: v0.9.0
|
||||
creationTimestamp: null
|
||||
name: workflows.core.oam.dev
|
||||
spec:
|
||||
group: core.oam.dev
|
||||
@@ -34,6 +34,16 @@ spec:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
mode:
|
||||
description: WorkflowExecuteMode defines the mode of workflow execution
|
||||
properties:
|
||||
steps:
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow step.
|
||||
@@ -161,153 +171,3 @@ spec:
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
- name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Workflow defines workflow steps and other attributes
|
||||
properties:
|
||||
mode:
|
||||
description: WorkflowExecuteMode defines the mode of workflow execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
type: string
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow step.
|
||||
properties:
|
||||
dependsOn:
|
||||
description: DependsOn is the dependency of the step
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
if:
|
||||
description: If is the if condition of the step
|
||||
type: string
|
||||
inputs:
|
||||
description: Inputs is the inputs of the step
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
meta:
|
||||
description: Meta is the meta data of the workflow step.
|
||||
properties:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: Outputs is the outputs of the step
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
description: Properties is the properties of the step
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
subSteps:
|
||||
items:
|
||||
description: WorkflowStepBase defines the workflow step base
|
||||
properties:
|
||||
dependsOn:
|
||||
description: DependsOn is the dependency of the step
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
if:
|
||||
description: If is the if condition of the step
|
||||
type: string
|
||||
inputs:
|
||||
description: Inputs is the inputs of the step
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
meta:
|
||||
description: Meta is the meta data of the workflow step.
|
||||
properties:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: Outputs is the outputs of the step
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
description: Properties is the properties of the step
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
timeout:
|
||||
description: Timeout is the timeout of the step
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of the workflow step.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
timeout:
|
||||
description: Timeout is the timeout of the step
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of the workflow step.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
served: true
|
||||
storage: false
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
@@ -12,6 +12,8 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
#ApplyOnceStrategy: {
|
||||
// +usage=When the strategy takes effect,e.g. onUpdate、onStateKeep
|
||||
affect?: string
|
||||
// +usage=Specify the path of the resource that allow configuration drift
|
||||
path: [...string]
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -221,6 +254,7 @@ spec:
|
||||
- "--feature-gates=GzipResourceTracker={{- .Values.featureGates.gzipResourceTracker | toString -}}"
|
||||
- "--feature-gates=ZstdResourceTracker={{- .Values.featureGates.zstdResourceTracker | toString -}}"
|
||||
- "--feature-gates=ApplyOnce={{- .Values.featureGates.applyOnce | toString -}}"
|
||||
- "--feature-gates=MultiStageComponentApply= {{- .Values.featureGates.multiStageComponentApply | toString -}}"
|
||||
{{ if .Values.authentication.enabled }}
|
||||
{{ if .Values.authentication.withUser }}
|
||||
- "--authentication-with-user"
|
||||
|
||||
@@ -113,11 +113,13 @@ optimize:
|
||||
##@param featureGates.gzipResourceTracker if enabled, resourceTracker will be compressed using gzip before being stored
|
||||
##@param featureGates.zstdResourceTracker if enabled, resourceTracker will be compressed using zstd before being stored. It is much faster and more efficient than gzip. If both gzip and zstd are enabled, zstd will be used.
|
||||
##@param featureGates.applyOnce if enabled, the apply-once feature will be applied to all applications, no state-keep and no resource data storage in ResourceTracker
|
||||
##@param featureGates.multiStageComponentApply if enabled, the multiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply.
|
||||
featureGates:
|
||||
enableLegacyComponentRevision: false
|
||||
gzipResourceTracker: false
|
||||
zstdResourceTracker: false
|
||||
applyOnce: false
|
||||
multiStageComponentApply: false
|
||||
|
||||
## @section MultiCluster parameters
|
||||
|
||||
|
||||
@@ -2209,10 +2209,11 @@ spec:
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: SubSteps is the mode of workflow sub
|
||||
steps execution
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
@@ -4008,6 +4009,17 @@ spec:
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: WorkflowExecuteMode defines the mode of workflow
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow
|
||||
|
||||
@@ -1020,10 +1020,10 @@ spec:
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
|
||||
@@ -12,6 +12,8 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
#ApplyOnceStrategy: {
|
||||
// +usage=When the strategy takes effect,e.g. onUpdate、onStateKeep
|
||||
affect?: string
|
||||
// +usage=Specify the path of the resource that allow configuration drift
|
||||
path: [...string]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -120,4 +120,61 @@ EOF
|
||||
|
||||
In the `apply-once-app-3` case, any changes of `hello-cosmos` deployment will not be brought back and any changes
|
||||
of `hello-cosmos` service will be brought back in the next reconcile loop. In the same time, any changes
|
||||
of `hello-world` component will be brought back in the next reconcile loop.
|
||||
of `hello-world` component will be brought back in the next reconcile loop.
|
||||
|
||||
```shell
|
||||
$ cat <<EOF | kubectl apply -f -
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: apply-once-app-4
|
||||
spec:
|
||||
components:
|
||||
- name: hello-world
|
||||
type: webservice
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8080
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 1
|
||||
- name: hello-cosmos
|
||||
type: webservice
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8080
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 1
|
||||
policies:
|
||||
- name: apply-once
|
||||
type: apply-once
|
||||
properties:
|
||||
enable: true
|
||||
rules:
|
||||
- selector:
|
||||
componentNames: [ "hello-cosmos" ]
|
||||
resourceTypes: [ "Deployment" ]
|
||||
strategy:
|
||||
affect: onStateKeep
|
||||
path: [ "spec.replicas"]
|
||||
EOF
|
||||
```
|
||||
|
||||
By default, KubeVela executes the apply-once policy in two phases: application update and cycle state maintenance,
|
||||
allowing configuration drift depending on the policy configuration.
|
||||
|
||||
If you have special requirements, you can set the affect to determine the phase of policy execution .
|
||||
affect supported configurations: onUpdate/onStateKeep/always (default)
|
||||
|
||||
When affect=always, or not set, the policy is executed in two phase.
|
||||
|
||||
When affect=onStateKeep, the policy is executed only during the stateKeep phase. In the case of `apply-once-app-4`, any
|
||||
changes to the deployed copy of `hello-cosmos` will not be brought back to the next state keeping loop, but will be
|
||||
brought back to the next application update.
|
||||
|
||||
When affect=onUpdate, the policy is only executed when the application is updated. In the case of `
|
||||
apply-once-app-4`, if affect=onUpdate is set, any changes to the deployed copy of `hello-cosmos` will not be brought
|
||||
back in the next application update, but will be brought back in the next state keeping loop.
|
||||
|
||||
43
docs/examples/multi-stage-component-apply/README.md
Normal file
43
docs/examples/multi-stage-component-apply/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# MultiStageComponentApply
|
||||
|
||||
This example shows how to enable MultiStageComponentApply, the MultiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply. Currently, the stage field in TraitDefinition is an optional parameter, which provides `PreDispatch` and `PostDispatch`.
|
||||
|
||||
## How to use multi-stage
|
||||
> The future-gate is still in alpha stage, and it is recommended to use it only in short-term test clusters.
|
||||
|
||||
The `MultiStageComponentApply` is not enabled by default, you need some extra works to use it.
|
||||
|
||||
1. Add an args `--feature-gates=MultiStageComponentApply=ture` in KubeVela controller's deployment like:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --feature-gates=MultiStageComponentApply=true
|
||||
...
|
||||
```
|
||||
|
||||
2. Sometime, you have multi-stage apply requirements inside the component, and it is the `outputs` resource defined in the trait. In this case, you can use the `stage` with the value `PreDispatch` or `PostDispatch` like:
|
||||
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Add storages on K8s pod for your workload which follows the pod spec in path 'spec.template'.
|
||||
name: storage
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- deployments.apps
|
||||
- statefulsets.apps
|
||||
- daemonsets.apps
|
||||
- jobs.batch
|
||||
podDisruptive: true
|
||||
stage: PreDispatch
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
...
|
||||
```
|
||||
|
||||
19
e2e/addon/mock/testdata/not-match-addon/metadata.yaml
vendored
Normal file
19
e2e/addon/mock/testdata/not-match-addon/metadata.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: not-match-addon
|
||||
version: 1.0.0
|
||||
description: Extended workload to do continuous and progressive delivery
|
||||
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
|
||||
url: https://fluxcd.io
|
||||
|
||||
tags:
|
||||
- mock
|
||||
dependencies: []
|
||||
#- name: addon_name
|
||||
|
||||
# set invisible means this won't be list and will be enabled when depended on
|
||||
# for example, terraform-alibaba depends on terraform which is invisible,
|
||||
# when terraform-alibaba is enabled, terraform will be enabled automatically
|
||||
# default: false
|
||||
invisible: false
|
||||
|
||||
system:
|
||||
kubernetes: "<=v1.3.0"
|
||||
@@ -23,4 +23,15 @@ entries:
|
||||
annotations:
|
||||
system.vela: ">=1.5.0"
|
||||
system.kubernetes: ">=1.30.0"
|
||||
vela-workflow:
|
||||
- annotations:
|
||||
system.vela: '>=v1.6.0-beta.1'
|
||||
created: "2022-10-29T09:11:16.865230605Z"
|
||||
description: vela-workflow provides the capability to run a standalone workflow
|
||||
home: https://github.com/kubevela/workflow
|
||||
icon: https://static.kubevela.net/images/logos/KubeVela%20-03.png
|
||||
name: vela-workflow
|
||||
urls:
|
||||
- http://127.0.0.1:9098/helm/vela-workflow-v0.3.1.tgz
|
||||
version: v0.3.1
|
||||
generated: "2022-06-15T13:17:04.733573+08:00"
|
||||
BIN
e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.1.tgz
Normal file
BIN
e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.1.tgz
Normal file
Binary file not shown.
@@ -22,9 +22,9 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
@@ -108,24 +108,31 @@ var ossHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request
|
||||
var helmHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request) {
|
||||
switch {
|
||||
case strings.Contains(req.URL.Path, "index.yaml"):
|
||||
file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/index.yaml")
|
||||
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/index.yaml")
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
case strings.Contains(req.URL.Path, "fluxcd-test-version-1.0.0.tgz"):
|
||||
file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz")
|
||||
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz")
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
case strings.Contains(req.URL.Path, "fluxcd-test-version-2.0.0.tgz"):
|
||||
file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz")
|
||||
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz")
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
case strings.Contains(req.URL.Path, "vela-workflow-v0.3.1.tgz"):
|
||||
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.1.tgz")
|
||||
if err != nil {
|
||||
_, _ = rw.Write([]byte(err.Error()))
|
||||
}
|
||||
rw.Write(file)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
18
go.mod
18
go.mod
@@ -3,7 +3,7 @@ module github.com/oam-dev/kubevela
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.4.4-0.20220915174651-ad253ed099e9
|
||||
cuelang.org/go v0.5.0-alpha.1
|
||||
github.com/AlecAivazis/survey/v2 v2.1.1
|
||||
github.com/FogDong/uitable v0.0.5
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
@@ -53,13 +53,14 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.9.1
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/klauspost/compress v1.15.9
|
||||
github.com/klauspost/compress v1.15.11
|
||||
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.0.0-20221019093241-b5b7a0d79051
|
||||
github.com/kubevela/workflow v0.3.1
|
||||
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
|
||||
github.com/oam-dev/cluster-gateway v1.4.0
|
||||
github.com/oam-dev/cluster-register v1.0.4-0.20220928064144-5f76a9d7ca8c
|
||||
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28
|
||||
@@ -77,7 +78,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/tidwall/gjson v1.9.3
|
||||
github.com/wercker/stern v0.0.0-20190705090245-4fa46dd6987f
|
||||
github.com/wonderflow/cert-manager-api v1.0.4-0.20210304051430-e08aa76f6c5f
|
||||
@@ -85,7 +86,7 @@ require (
|
||||
github.com/xlab/treeprint v1.1.0
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/text v0.3.8
|
||||
@@ -239,7 +240,6 @@ require (
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
@@ -292,12 +292,12 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 // indirect
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
|
||||
29
go.sum
29
go.sum
@@ -73,8 +73,8 @@ 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.4.4-0.20220915174651-ad253ed099e9 h1:4mfDNgtdb398g0bekqiW8J8tw+JN3/U/3wh+Jw/I4Yk=
|
||||
cuelang.org/go v0.4.4-0.20220915174651-ad253ed099e9/go.mod h1:nxWFAPWKYvZJ+eYayxArWqKKjdBTeU1N52vJpML/c6w=
|
||||
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=
|
||||
@@ -1304,8 +1304,8 @@ github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
|
||||
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
@@ -1334,8 +1334,8 @@ github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2 h1:C3cAfrxst1+dIWgLLh
|
||||
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2/go.mod h1:TgIGEB/r0NOy63Jzem7WsL3AIr34l+ClH9dmPqcZ4d4=
|
||||
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f h1:1lUtU1alPThdcsn4MI6XjPb7eJLuZPpmlEdgjtnUMKw=
|
||||
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f/go.mod h1:m724/7ANnB/iukyHW20+DicpeJMEC/JA0ZhgsHY10MA=
|
||||
github.com/kubevela/workflow v0.0.0-20221019093241-b5b7a0d79051 h1:ET01t1GCjbERb+uwgGZnLHoLo4ceE1+gHnmgM/9or5g=
|
||||
github.com/kubevela/workflow v0.0.0-20221019093241-b5b7a0d79051/go.mod h1:1XyGmfIkD6gPAegUkeDBXXModeiu8NVUWIgersTqwr8=
|
||||
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/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=
|
||||
@@ -1923,8 +1923,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
@@ -2137,8 +2138,9 @@ go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
@@ -2198,8 +2200,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -2517,8 +2519,8 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -2550,8 +2552,9 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -2209,10 +2209,11 @@ spec:
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: SubSteps is the mode of workflow sub
|
||||
steps execution
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
@@ -4008,6 +4009,17 @@ spec:
|
||||
namespace:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: WorkflowExecuteMode defines the mode of workflow
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow
|
||||
|
||||
@@ -1021,10 +1021,10 @@ spec:
|
||||
execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.2
|
||||
controller-gen.kubebuilder.io/version: v0.9.0
|
||||
name: workflows.core.oam.dev
|
||||
spec:
|
||||
group: core.oam.dev
|
||||
@@ -34,6 +33,16 @@ spec:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
mode:
|
||||
description: WorkflowExecuteMode defines the mode of workflow execution
|
||||
properties:
|
||||
steps:
|
||||
description: Steps is the mode of workflow steps execution
|
||||
type: string
|
||||
subSteps:
|
||||
description: SubSteps is the mode of workflow sub steps execution
|
||||
type: string
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow step.
|
||||
@@ -161,297 +170,3 @@ spec:
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Workflow is the Schema for the workflow API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow step.
|
||||
properties:
|
||||
dependsOn:
|
||||
description: DependsOn is the dependency of the step
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
if:
|
||||
description: If is the if condition of the step
|
||||
type: string
|
||||
inputs:
|
||||
description: Inputs is the inputs of the step
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
meta:
|
||||
description: Meta is the meta data of the workflow step.
|
||||
properties:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: Outputs is the outputs of the step
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
description: Properties is the properties of the step
|
||||
type: object
|
||||
|
||||
subSteps:
|
||||
items:
|
||||
description: WorkflowStepBase defines the workflow step base
|
||||
properties:
|
||||
dependsOn:
|
||||
description: DependsOn is the dependency of the step
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
if:
|
||||
description: If is the if condition of the step
|
||||
type: string
|
||||
inputs:
|
||||
description: Inputs is the inputs of the step
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
meta:
|
||||
description: Meta is the meta data of the workflow step.
|
||||
properties:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: Outputs is the outputs of the step
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
description: Properties is the properties of the step
|
||||
type: object
|
||||
|
||||
timeout:
|
||||
description: Timeout is the timeout of the step
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of the workflow step.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
timeout:
|
||||
description: Timeout is the timeout of the step
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of the workflow step.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
served: true
|
||||
storage: false
|
||||
- name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Workflow defines workflow steps and other attributes
|
||||
properties:
|
||||
mode:
|
||||
description: WorkflowExecuteMode defines the mode of workflow execution
|
||||
properties:
|
||||
steps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
subSteps:
|
||||
description: WorkflowMode describes the mode of workflow
|
||||
type: string
|
||||
type: object
|
||||
ref:
|
||||
type: string
|
||||
steps:
|
||||
items:
|
||||
description: WorkflowStep defines how to execute a workflow step.
|
||||
properties:
|
||||
dependsOn:
|
||||
description: DependsOn is the dependency of the step
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
if:
|
||||
description: If is the if condition of the step
|
||||
type: string
|
||||
inputs:
|
||||
description: Inputs is the inputs of the step
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
meta:
|
||||
description: Meta is the meta data of the workflow step.
|
||||
properties:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: Outputs is the outputs of the step
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
description: Properties is the properties of the step
|
||||
type: object
|
||||
|
||||
subSteps:
|
||||
items:
|
||||
description: WorkflowStepBase defines the workflow step base
|
||||
properties:
|
||||
dependsOn:
|
||||
description: DependsOn is the dependency of the step
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
if:
|
||||
description: If is the if condition of the step
|
||||
type: string
|
||||
inputs:
|
||||
description: Inputs is the inputs of the step
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
parameterKey:
|
||||
type: string
|
||||
required:
|
||||
- from
|
||||
- parameterKey
|
||||
type: object
|
||||
type: array
|
||||
meta:
|
||||
description: Meta is the meta data of the workflow step.
|
||||
properties:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
outputs:
|
||||
description: Outputs is the outputs of the step
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
valueFrom:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- valueFrom
|
||||
type: object
|
||||
type: array
|
||||
properties:
|
||||
description: Properties is the properties of the step
|
||||
type: object
|
||||
|
||||
timeout:
|
||||
description: Timeout is the timeout of the step
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of the workflow step.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
timeout:
|
||||
description: Timeout is the timeout of the step
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of the workflow step.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
served: true
|
||||
storage: false
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
@@ -612,7 +612,7 @@ func unmarshalToContent(content []byte) (fileContent *github.RepositoryContent,
|
||||
}
|
||||
|
||||
func genAddonAPISchema(addonRes *UIData) error {
|
||||
cueScript := script.CUE([]byte(addonRes.Parameters))
|
||||
cueScript := script.CUE(addonRes.Parameters)
|
||||
schema, err := cueScript.ParsePropertiesToSchema()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -16,10 +16,60 @@ limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterModel(&PipelineContext{})
|
||||
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 WorkflowSpec
|
||||
Name string `json:"name"`
|
||||
Project string `json:"project"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// PrimaryKey return custom primary key
|
||||
func (p Pipeline) PrimaryKey() string {
|
||||
return fmt.Sprintf("%s-%s", p.Project, p.Name)
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
func (p Pipeline) TableName() string {
|
||||
return tableNamePrefix + "pipeline"
|
||||
}
|
||||
|
||||
// ShortTableName is the compressed version of table name for kubeapi storage and others
|
||||
func (p Pipeline) ShortTableName() string {
|
||||
return "pipeline"
|
||||
}
|
||||
|
||||
// Index return custom index
|
||||
func (p Pipeline) Index() map[string]string {
|
||||
var index = make(map[string]string)
|
||||
if p.Project != "" {
|
||||
index["project"] = p.Project
|
||||
}
|
||||
if p.Name != "" {
|
||||
index["name"] = p.Name
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// Value is a k-v pair
|
||||
@@ -16,8 +16,6 @@ limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
func init() {
|
||||
RegisterModel(&Project{})
|
||||
}
|
||||
@@ -29,11 +27,15 @@ type Project struct {
|
||||
Alias string `json:"alias"`
|
||||
Owner string `json:"owner"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// GetNamespace get the namespace name of this project.
|
||||
func (p *Project) GetNamespace() string {
|
||||
return fmt.Sprintf("project-%s", p.Name)
|
||||
if p.Namespace != "" {
|
||||
return p.Namespace
|
||||
}
|
||||
return p.Name
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
|
||||
@@ -50,16 +50,25 @@ type Workflow struct {
|
||||
|
||||
// WorkflowStep defines how to execute a workflow step.
|
||||
type WorkflowStep struct {
|
||||
WorkflowStepBase `json:",inline"`
|
||||
SubSteps []WorkflowStepBase `json:"subSteps,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowStepBase is the step base of workflow
|
||||
type WorkflowStepBase struct {
|
||||
// Name is the unique name of the workflow step.
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
OrderIndex int `json:"orderIndex"`
|
||||
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty"`
|
||||
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty"`
|
||||
DependsOn []string `json:"dependsOn"`
|
||||
Properties *JSONStruct `json:"properties,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
OrderIndex int `json:"orderIndex"`
|
||||
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty"`
|
||||
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty"`
|
||||
DependsOn []string `json:"dependsOn"`
|
||||
Properties *JSONStruct `json:"properties,omitempty"`
|
||||
Meta *workflowv1alpha1.WorkflowStepMeta `json:"meta,omitempty"`
|
||||
If string `json:"if,omitempty"`
|
||||
Timeout string `json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// TableName return custom table name
|
||||
@@ -114,6 +123,12 @@ type WorkflowRecord struct {
|
||||
|
||||
// WorkflowStepStatus is the workflow step status database model
|
||||
type WorkflowStepStatus struct {
|
||||
StepStatus `json:",inline"`
|
||||
SubStepsStatus []StepStatus `json:"subSteps,omitempty"`
|
||||
}
|
||||
|
||||
// StepStatus is the workflow step status database model
|
||||
type StepStatus struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
@@ -599,25 +600,20 @@ func GenEnvWorkflowStepsAndPolicies(ctx context.Context, kubeClient client.Clien
|
||||
}
|
||||
var steps []model.WorkflowStep
|
||||
for _, step := range workflowSteps {
|
||||
base, err := convert.FromCRWorkflowStepBase(step.WorkflowStepBase)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("workflow %s step %s properties is invalid %s", pkgUtils.Sanitize(app.Name), pkgUtils.Sanitize(step.Name), err.Error())
|
||||
continue
|
||||
}
|
||||
targetName := strings.Replace(step.Name, "-cloud-resource", "", 1)
|
||||
s := model.WorkflowStep{
|
||||
Name: step.Name,
|
||||
Type: step.Type,
|
||||
Alias: fmt.Sprintf("Deploy To %s", targetName),
|
||||
Description: fmt.Sprintf("deploy app to delivery target %s", targetName),
|
||||
DependsOn: step.DependsOn,
|
||||
Inputs: step.Inputs,
|
||||
Outputs: step.Outputs,
|
||||
base.Alias = fmt.Sprintf("Deploy To %s", targetName)
|
||||
base.Description = fmt.Sprintf("deploy app to delivery target %s", targetName)
|
||||
ws := model.WorkflowStep{
|
||||
WorkflowStepBase: *base,
|
||||
SubSteps: make([]model.WorkflowStepBase, 0),
|
||||
}
|
||||
if step.Properties != nil {
|
||||
properties, err := model.NewJSONStruct(step.Properties)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("workflow %s step %s properties is invalid %s", pkgUtils.Sanitize(app.Name), pkgUtils.Sanitize(step.Name), err.Error())
|
||||
continue
|
||||
}
|
||||
s.Properties = properties
|
||||
}
|
||||
steps = append(steps, s)
|
||||
// no sub steps handle here
|
||||
steps = append(steps, ws)
|
||||
}
|
||||
return steps, policies
|
||||
}
|
||||
|
||||
@@ -40,54 +40,70 @@ import (
|
||||
func TestCompareWorkflowSteps(t *testing.T) {
|
||||
existSteps := []model.WorkflowStep{
|
||||
{
|
||||
Name: "step1",
|
||||
Type: "deploy2env",
|
||||
Properties: &model.JSONStruct{
|
||||
"policy": "env-policy",
|
||||
"env": "target1",
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step1",
|
||||
Type: "deploy2env",
|
||||
Properties: &model.JSONStruct{
|
||||
"policy": "env-policy",
|
||||
"env": "target1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "suspend",
|
||||
Type: "suspend",
|
||||
},
|
||||
{
|
||||
Name: "step2",
|
||||
Type: "deploy2env",
|
||||
Properties: &model.JSONStruct{
|
||||
"policy": "env-policy",
|
||||
"env": "target2",
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "suspend",
|
||||
Type: "suspend",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "step3",
|
||||
Type: "deploy2env",
|
||||
Properties: &model.JSONStruct{
|
||||
"policy": "env-policy",
|
||||
"env": "target3",
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step2",
|
||||
Type: "deploy2env",
|
||||
Properties: &model.JSONStruct{
|
||||
"policy": "env-policy",
|
||||
"env": "target2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "notify",
|
||||
Type: "notify",
|
||||
Properties: &model.JSONStruct{"message": "dddd"},
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step3",
|
||||
Type: "deploy2env",
|
||||
Properties: &model.JSONStruct{
|
||||
"policy": "env-policy",
|
||||
"env": "target3",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "notify",
|
||||
Type: "notify",
|
||||
Properties: &model.JSONStruct{"message": "dddd"},
|
||||
},
|
||||
},
|
||||
}
|
||||
newSteps := []model.WorkflowStep{
|
||||
{
|
||||
Name: "step1",
|
||||
Type: "deploy",
|
||||
Properties: &model.JSONStruct{"policies": []string{"target1"}},
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step1",
|
||||
Type: "deploy",
|
||||
Properties: &model.JSONStruct{"policies": []string{"target1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "step2",
|
||||
Type: "deploy",
|
||||
Properties: &model.JSONStruct{"policies": []string{"target2"}},
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step2",
|
||||
Type: "deploy",
|
||||
Properties: &model.JSONStruct{"policies": []string{"target2"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "step4",
|
||||
Type: "deploy",
|
||||
Properties: &model.JSONStruct{"policies": []string{"target4"}},
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step4",
|
||||
Type: "deploy",
|
||||
Properties: &model.JSONStruct{"policies": []string{"target4"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
exist := createWorkflowSteps(existSteps, []datastore.Entity{
|
||||
@@ -368,11 +384,15 @@ var _ = Describe("Test workflow model", func() {
|
||||
|
||||
workflow.Steps = []model.WorkflowStep{
|
||||
workflow.Steps[0], {
|
||||
Type: "suspend",
|
||||
Name: "suspend",
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Type: "suspend",
|
||||
Name: "suspend",
|
||||
},
|
||||
}, workflow.Steps[1], {
|
||||
Type: "notification",
|
||||
Name: "notification",
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Type: "notification",
|
||||
Name: "notification",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -412,14 +412,12 @@ func (u *addonServiceImpl) EnableAddon(ctx context.Context, name string, args ap
|
||||
continue
|
||||
}
|
||||
if strings.Contains(err.Error(), "specified version") {
|
||||
berr := bcode.ErrAddonInvalidVersion
|
||||
berr.Message = err.Error()
|
||||
return berr
|
||||
return bcode.ErrAddonInvalidVersion.SetMessage(err.Error())
|
||||
}
|
||||
|
||||
// wrap this error with special bcode
|
||||
if errors.As(err, &pkgaddon.VersionUnMatchError{}) {
|
||||
return bcode.ErrAddonSystemVersionMismatch
|
||||
return bcode.ErrAddonSystemVersionMismatch.SetMessage(err.Error())
|
||||
}
|
||||
// except `addon not found`, other errors should return directly
|
||||
return err
|
||||
|
||||
@@ -533,7 +533,7 @@ var _ = Describe("Test application service function", func() {
|
||||
Expect(cmp.Diff(compareResponse.TargetAppYAML, "")).Should(BeEmpty())
|
||||
Expect(cmp.Diff(compareResponse.BaseAppYAML, "")).ShouldNot(BeEmpty())
|
||||
|
||||
By("compare when app's env add target, should return true")
|
||||
By("compare when app's env add target, should return false")
|
||||
_, err = targetService.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: "dev-target1", Project: appModel.Project, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: "dev-target1"}})
|
||||
Expect(err).Should(BeNil())
|
||||
_, err = envService.UpdateEnv(context.TODO(), "app-dev",
|
||||
@@ -548,7 +548,8 @@ var _ = Describe("Test application service function", func() {
|
||||
},
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
check(compareResponse, true)
|
||||
// Existing applications are not affected after update env.
|
||||
check(compareResponse, false)
|
||||
|
||||
By("compare when update app's trait, should return true")
|
||||
// reset app config
|
||||
@@ -846,19 +847,25 @@ var _ = Describe("Test apiserver policy rest api", func() {
|
||||
EnvName: "default",
|
||||
Steps: []v1.WorkflowStep{
|
||||
{
|
||||
Name: "default",
|
||||
Type: "deploy",
|
||||
Properties: `{"policies":["local"]}`,
|
||||
WorkflowStepBase: v1.WorkflowStepBase{
|
||||
Name: "default",
|
||||
Type: "deploy",
|
||||
Properties: `{"policies":["local"]}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "suspend",
|
||||
Type: "suspend",
|
||||
Properties: `{"duration": "10m"}`,
|
||||
WorkflowStepBase: v1.WorkflowStepBase{
|
||||
Name: "suspend",
|
||||
Type: "suspend",
|
||||
Properties: `{"duration": "10m"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
Type: "deploy",
|
||||
Properties: `{"policies":["cluster1"]}`,
|
||||
WorkflowStepBase: v1.WorkflowStepBase{
|
||||
Name: "second",
|
||||
Type: "deploy",
|
||||
Properties: `{"policies":["cluster1"]}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -870,9 +877,11 @@ var _ = Describe("Test apiserver policy rest api", func() {
|
||||
EnvName: "default",
|
||||
Steps: []v1.WorkflowStep{
|
||||
{
|
||||
Name: "second",
|
||||
Type: "deploy",
|
||||
Properties: `{"policies":["cluster3"]}`,
|
||||
WorkflowStepBase: v1.WorkflowStepBase{
|
||||
Name: "second",
|
||||
Type: "deploy",
|
||||
Properties: `{"policies":["cluster3"]}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -66,7 +67,8 @@ var _ = Describe("Test cloudshell service function", func() {
|
||||
ProjectService: projectService,
|
||||
}
|
||||
projectService = &projectServiceImpl{
|
||||
Store: ds,
|
||||
Store: ds,
|
||||
K8sClient: k8sClient,
|
||||
RbacService: &rbacServiceImpl{
|
||||
Store: ds,
|
||||
},
|
||||
@@ -99,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())
|
||||
|
||||
@@ -170,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())
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
apierror "k8s.io/apimachinery/pkg/api/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/repository"
|
||||
@@ -184,23 +185,6 @@ func checkEqual(old, new []string) bool {
|
||||
return reflect.DeepEqual(old, new)
|
||||
}
|
||||
|
||||
func (p *envServiceImpl) updateAppWithNewEnv(ctx context.Context, envName string, env *model.Env) error {
|
||||
|
||||
// List all apps inside the env
|
||||
apps, err := listApp(ctx, p.Store, apisv1.ListApplicationOptions{Env: envName})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, app := range apps {
|
||||
err = repository.UpdateEnvWorkflow(ctx, p.KubeClient, p.Store, app, env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// UpdateEnv update an env for request
|
||||
func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.UpdateEnvRequest) (*apisv1.Env, error) {
|
||||
env := &model.Env{}
|
||||
@@ -221,25 +205,33 @@ func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
|
||||
if err != nil || !pass {
|
||||
return nil, bcode.ErrEnvTargetConflict
|
||||
}
|
||||
|
||||
var targetChanged bool
|
||||
if len(req.Targets) > 0 && !checkEqual(env.Targets, req.Targets) {
|
||||
targetChanged = true
|
||||
env.Targets = req.Targets
|
||||
}
|
||||
|
||||
targets, err := repository.ListTarget(ctx, p.Store, "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var targetMap = make(map[string]*model.Target, len(targets))
|
||||
for i, existTarget := range targets {
|
||||
targetMap[existTarget.Name] = targets[i]
|
||||
}
|
||||
for _, target := range req.Targets {
|
||||
if _, exist := targetMap[target]; !exist {
|
||||
var targets []*model.Target
|
||||
if len(req.Targets) > 0 {
|
||||
_, _, deleted := util.ThreeWaySliceCompare(req.Targets, env.Targets)
|
||||
if len(deleted) > 0 {
|
||||
count, err := p.GetAppCountInEnv(ctx, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil, bcode.ErrEnvTargetNotAllowDelete
|
||||
}
|
||||
}
|
||||
targets, err = repository.ListTarget(ctx, p.Store, "", &datastore.ListOptions{
|
||||
FilterOptions: datastore.FilterOptions{
|
||||
In: []datastore.InQueryOption{{
|
||||
Key: "name",
|
||||
Values: req.Targets,
|
||||
}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(targets) != len(req.Targets) {
|
||||
return nil, bcode.ErrTargetNotExist
|
||||
}
|
||||
env.Targets = req.Targets
|
||||
}
|
||||
|
||||
// create namespace at first
|
||||
@@ -247,13 +239,6 @@ func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if targetChanged {
|
||||
if err = p.updateAppWithNewEnv(ctx, name, env); err != nil {
|
||||
log.Logger.Errorf("update envbinding failure %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := managePrivilegesForEnvironment(ctx, p.KubeClient, env, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -262,6 +247,14 @@ func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (p *envServiceImpl) GetAppCountInEnv(ctx context.Context, env *model.Env) (int, error) {
|
||||
var appList v1beta1.ApplicationList
|
||||
if err := p.KubeClient.List(ctx, &appList, client.InNamespace(env.Namespace), client.MatchingLabels{model.LabelSourceOfTruth: model.FromUX}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(appList.Items), nil
|
||||
}
|
||||
|
||||
// CreateEnv create an env for request
|
||||
func (p *envServiceImpl) CreateEnv(ctx context.Context, req apisv1.CreateEnvRequest) (*apisv1.Env, error) {
|
||||
newEnv := &model.Env{
|
||||
@@ -344,14 +337,23 @@ func convertEnvModel2Base(env *model.Env, targets []*model.Target) *apisv1.Env {
|
||||
UpdateTime: env.UpdateTime,
|
||||
}
|
||||
for _, dt := range env.Targets {
|
||||
var t *model.Target
|
||||
for _, tg := range targets {
|
||||
if dt == tg.Name {
|
||||
data.Targets = append(data.Targets, apisv1.NameAlias{
|
||||
Name: dt,
|
||||
Alias: tg.Alias,
|
||||
})
|
||||
t = tg
|
||||
break
|
||||
}
|
||||
}
|
||||
if t != nil {
|
||||
data.Targets = append(data.Targets, apisv1.NameAlias{
|
||||
Name: dt,
|
||||
Alias: t.Alias,
|
||||
})
|
||||
} else {
|
||||
data.Targets = append(data.Targets, apisv1.NameAlias{
|
||||
Name: dt,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &data
|
||||
}
|
||||
|
||||
@@ -25,8 +25,11 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
@@ -53,6 +56,8 @@ var _ = Describe("Test env service functions", func() {
|
||||
// create target
|
||||
err := ds.Add(context.TODO(), &model.Target{Name: "env-test"})
|
||||
Expect(err).Should(BeNil())
|
||||
err = ds.Add(context.TODO(), &model.Target{Name: "env-test-2"})
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
req := apisv1.CreateEnvRequest{
|
||||
Name: "test-env",
|
||||
@@ -113,6 +118,35 @@ var _ = Describe("Test env service functions", func() {
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(cmp.Diff(env.Description, req5.Description)).Should(BeEmpty())
|
||||
|
||||
By("Test update the targets of the env")
|
||||
req6 := apisv1.UpdateEnvRequest{
|
||||
Description: "this is a env description update",
|
||||
Targets: []string{"env-test", "env-test-2"},
|
||||
}
|
||||
env, err = envService.UpdateEnv(context.TODO(), "test-env-2", req6)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(cmp.Diff(len(env.Targets), len(req6.Targets))).Should(BeEmpty())
|
||||
|
||||
Expect(k8sClient.Create(context.TODO(), &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "env-app",
|
||||
Namespace: env.Namespace,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromUX,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
},
|
||||
})).Should(BeNil())
|
||||
|
||||
req7 := apisv1.UpdateEnvRequest{
|
||||
Description: "this is a env description update",
|
||||
Targets: []string{"env-test"},
|
||||
}
|
||||
_, err = envService.UpdateEnv(context.TODO(), "test-env-2", req7)
|
||||
Expect(err).Should(Equal(bcode.ErrEnvTargetNotAllowDelete))
|
||||
|
||||
// clean up the env
|
||||
err = envService.DeleteEnv(context.TODO(), "test-env")
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
@@ -83,7 +83,7 @@ var _ = Describe("Test helm repo list", func() {
|
||||
pSec = v1.Secret{}
|
||||
gSec = v1.Secret{}
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "project-my-project"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "my-project"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(yaml.Unmarshal([]byte(projectSecret), &pSec)).Should(BeNil())
|
||||
Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &pSec)).Should(BeNil())
|
||||
@@ -390,7 +390,7 @@ apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: project-helm-repo
|
||||
namespace: project-my-project
|
||||
namespace: my-project
|
||||
labels:
|
||||
config.oam.dev/type: helm-repository
|
||||
config.oam.dev/catalog: velacore-config
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
142
pkg/apiserver/domain/service/pipeline_test.go
Normal file
142
pkg/apiserver/domain/service/pipeline_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var (
|
||||
// defaultNamespace = "project-default-ns1-test"
|
||||
pipelineService *pipelineServiceImpl
|
||||
pipelineRunService *pipelineRunServiceImpl
|
||||
userService *userServiceImpl
|
||||
contextService *contextServiceImpl
|
||||
projectService *projectServiceImpl
|
||||
ctx context.Context
|
||||
|
||||
pipelineName = "test-pipeline"
|
||||
projectName = "test-project"
|
||||
)
|
||||
var _ = Describe("Test pipeline service functions", func() {
|
||||
It("Init services and project", func() {
|
||||
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "pipeline-test-kubevela"})
|
||||
Expect(ds).ToNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
pipelineService = NewTestPipelineService(ds, k8sClient, cfg).(*pipelineServiceImpl)
|
||||
pipelineRunService = pipelineService.PipelineRunService.(*pipelineRunServiceImpl)
|
||||
contextService = pipelineService.ContextService.(*contextServiceImpl)
|
||||
projectService = pipelineService.ProjectService.(*projectServiceImpl)
|
||||
userService = &userServiceImpl{Store: ds, K8sClient: k8sClient}
|
||||
|
||||
ctx = context.WithValue(context.TODO(), &apisv1.CtxKeyUser, "admin")
|
||||
err = userService.Init(context.TODO())
|
||||
Expect(err).Should(BeNil())
|
||||
_, err = projectService.CreateProject(ctx, apisv1.CreateProjectRequest{
|
||||
Name: projectName,
|
||||
Owner: "admin",
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
projModel, err := projectService.GetProject(context.TODO(), projectName)
|
||||
Expect(err).Should(BeNil())
|
||||
ctx = context.WithValue(ctx, &apisv1.CtxKeyProject, projModel)
|
||||
})
|
||||
|
||||
It("Test create pipeline", func() {
|
||||
props := model.JSONStruct{
|
||||
"url": "https://api.github.com/repos/kubevela/kubevela",
|
||||
}
|
||||
testPipelineSteps := []model.WorkflowStep{
|
||||
{
|
||||
SubSteps: []model.WorkflowStepBase{
|
||||
{
|
||||
Name: "request",
|
||||
Type: "request",
|
||||
Outputs: v1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
|
||||
Name: "stars",
|
||||
},
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
},
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "step-group",
|
||||
Type: "step-group",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("create pipeline with sub-steps")
|
||||
pipeline, err := pipelineService.CreatePipeline(ctx, apisv1.CreatePipelineRequest{
|
||||
Name: pipelineName,
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: testPipelineSteps,
|
||||
},
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(pipeline.Name).Should(Equal(pipelineName))
|
||||
Expect(pipeline.Spec.Steps[0].Name).Should(Equal("step-group"))
|
||||
})
|
||||
|
||||
It("list pipeline", func() {
|
||||
pipelines, err := pipelineService.ListPipelines(ctx, apisv1.ListPipelineRequest{
|
||||
Detailed: true,
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(pipelines).ShouldNot(BeNil())
|
||||
Expect(pipelines.Total).Should(Equal(1))
|
||||
Expect(len(pipelines.Pipelines)).Should(Equal(1))
|
||||
Expect(pipelines.Pipelines[0].Info).ShouldNot(BeNil())
|
||||
})
|
||||
|
||||
It("get pipeline contexts", func() {
|
||||
By("no context")
|
||||
contexts, err := contextService.ListContexts(ctx, projectName, pipelineName)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(contexts.Total).Should(Equal(0))
|
||||
Expect(len(contexts.Contexts)).Should(Equal(0))
|
||||
|
||||
By("create context")
|
||||
contextName := "test-context"
|
||||
contextKey := "test-key"
|
||||
contextVal := "test-val"
|
||||
ppCtx := apisv1.Context{
|
||||
Name: contextName,
|
||||
Values: []model.Value{
|
||||
{
|
||||
Key: contextKey,
|
||||
Value: contextVal,
|
||||
},
|
||||
},
|
||||
}
|
||||
context, err := contextService.CreateContext(ctx, projectName, pipelineName, ppCtx)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(context.Contexts)).Should(Equal(1))
|
||||
})
|
||||
})
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
)
|
||||
|
||||
// ProjectService project manage service.
|
||||
@@ -73,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
|
||||
@@ -159,6 +151,13 @@ func (p *projectServiceImpl) GetProject(ctx context.Context, projectName string)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if _, err := utils.GetNamespace(ctx, p.K8sClient, project.GetNamespace()); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
if err := utils.CreateNamespace(ctx, p.K8sClient, projectName); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return nil, bcode.ErrProjectNamespaceFail
|
||||
}
|
||||
}
|
||||
}
|
||||
return project, nil
|
||||
}
|
||||
|
||||
@@ -296,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())
|
||||
@@ -319,19 +317,24 @@ func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.Creat
|
||||
}
|
||||
}
|
||||
|
||||
if err := utils.CreateNamespace(ctx, p.K8sClient, req.Name); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return nil, bcode.ErrProjectNamespaceFail
|
||||
}
|
||||
|
||||
newProject := &model.Project{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Alias: req.Alias,
|
||||
Owner: owner,
|
||||
Namespace: req.Name,
|
||||
}
|
||||
|
||||
if err := p.Store.Add(ctx, newProject); err != nil {
|
||||
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
|
||||
@@ -526,6 +529,7 @@ func ConvertProjectModel2Base(project *model.Project, owner *model.User) *apisv1
|
||||
CreateTime: project.CreateTime,
|
||||
UpdateTime: project.UpdateTime,
|
||||
Owner: apisv1.NameAlias{Name: project.Owner},
|
||||
Namespace: project.GetNamespace(),
|
||||
}
|
||||
if owner != nil && owner.Name == project.Owner {
|
||||
base.Owner = apisv1.NameAlias{Name: owner.Name, Alias: owner.Alias}
|
||||
|
||||
@@ -59,6 +59,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
|
||||
"project:{projectName}/permission:*",
|
||||
"project:{projectName}/environment:*",
|
||||
"project:{projectName}/application:*/*",
|
||||
"project:{projectName}/pipeline:*/*",
|
||||
},
|
||||
Actions: []string{"detail", "list"},
|
||||
Effect: "Allow",
|
||||
@@ -89,13 +90,23 @@ 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",
|
||||
},
|
||||
{
|
||||
Name: "pipeline-management",
|
||||
Alias: "Pipeline Management",
|
||||
Resources: []string{
|
||||
"project:{projectName}/pipeline:*",
|
||||
},
|
||||
Actions: []string{"*"},
|
||||
Effect: "Allow",
|
||||
Scope: "project",
|
||||
},
|
||||
}
|
||||
|
||||
var defaultPlatformPermission = []*model.PermissionTemplate{
|
||||
@@ -234,6 +245,17 @@ var ResourceMaps = map[string]resourceMetadata{
|
||||
pathName: "configName",
|
||||
},
|
||||
"provider": {},
|
||||
"pipeline": {
|
||||
pathName: "pipelineName",
|
||||
subResources: map[string]resourceMetadata{
|
||||
"context": {
|
||||
pathName: "contextName",
|
||||
},
|
||||
"pipelineRun": {
|
||||
pathName: "pipelineRunName",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pathName: "projectName",
|
||||
},
|
||||
@@ -276,7 +298,7 @@ var ResourceMaps = map[string]resourceMetadata{
|
||||
"configTemplate": {},
|
||||
}
|
||||
|
||||
var existResourcePaths = convert(ResourceMaps)
|
||||
var existResourcePaths = convertSources(ResourceMaps)
|
||||
|
||||
type resourceMetadata struct {
|
||||
subResources map[string]resourceMetadata
|
||||
@@ -320,11 +342,11 @@ func checkResourcePath(resource string) (string, error) {
|
||||
return path, fmt.Errorf("there is no resource %s", resource)
|
||||
}
|
||||
|
||||
func convert(sources map[string]resourceMetadata) map[string]string {
|
||||
func convertSources(sources map[string]resourceMetadata) map[string]string {
|
||||
list := make(map[string]string)
|
||||
for k, v := range sources {
|
||||
if len(v.subResources) > 0 {
|
||||
for sub, subWithPathName := range convert(v.subResources) {
|
||||
for sub, subWithPathName := range convertSources(v.subResources) {
|
||||
if subWithPathName != "" {
|
||||
withPathname := fmt.Sprintf("/%s:*%s", k, subWithPathName)
|
||||
if v.pathName != "" {
|
||||
@@ -383,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
|
||||
}
|
||||
|
||||
@@ -835,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{}
|
||||
@@ -849,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", "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)
|
||||
@@ -198,7 +198,7 @@ var _ = Describe("Test rbac service", func() {
|
||||
|
||||
policies, err := rbacService.ListPermissions(context.TODO(), "init-test")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(policies)).Should(BeEquivalentTo(int64(5)))
|
||||
Expect(len(policies)).Should(BeEquivalentTo(int64(6)))
|
||||
})
|
||||
|
||||
It("Test UpdatePermission", func() {
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -74,7 +75,10 @@ var _ = BeforeSuite(func(done Done) {
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
scheme := common.Scheme
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
By("new kube client success")
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/repository"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
|
||||
assembler "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/assembler/v1"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
@@ -149,26 +150,12 @@ func (w *workflowServiceImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
|
||||
if err != nil && errors.Is(err, datastore.ErrRecordNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
var steps []model.WorkflowStep
|
||||
for _, step := range req.Steps {
|
||||
properties, err := model.NewJSONStructByString(step.Properties)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("parse trait properties failire %w", err)
|
||||
return nil, bcode.ErrInvalidProperties
|
||||
}
|
||||
steps = append(steps, model.WorkflowStep{
|
||||
Name: step.Name,
|
||||
Type: step.Type,
|
||||
Alias: step.Alias,
|
||||
Inputs: step.Inputs,
|
||||
Outputs: step.Outputs,
|
||||
Description: step.Description,
|
||||
DependsOn: step.DependsOn,
|
||||
Properties: properties,
|
||||
})
|
||||
modelSteps, err := assembler.CreateWorkflowStepModel(req.Steps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if workflow != nil {
|
||||
workflow.Steps = steps
|
||||
workflow.Steps = modelSteps
|
||||
workflow.Alias = req.Alias
|
||||
workflow.Description = req.Description
|
||||
workflow.Default = req.Default
|
||||
@@ -178,7 +165,7 @@ func (w *workflowServiceImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
|
||||
} else {
|
||||
// It is allowed to set multiple workflows as default, and only one takes effect.
|
||||
workflow = &model.Workflow{
|
||||
Steps: steps,
|
||||
Steps: modelSteps,
|
||||
Name: req.Name,
|
||||
Alias: req.Alias,
|
||||
Description: req.Description,
|
||||
@@ -490,6 +477,8 @@ func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimary
|
||||
status := app.Status.Workflow
|
||||
summaryStatus := model.RevisionStatusRunning
|
||||
switch {
|
||||
case status.Phase == workflowv1alpha1.WorkflowStateFailed:
|
||||
summaryStatus = model.RevisionStatusFail
|
||||
case status.Finished:
|
||||
summaryStatus = model.RevisionStatusComplete
|
||||
case status.Terminated:
|
||||
@@ -497,17 +486,26 @@ func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimary
|
||||
}
|
||||
|
||||
record.Status = summaryStatus
|
||||
stepStatus := make(map[string]*workflowv1alpha1.WorkflowStepStatus, len(status.Steps))
|
||||
for i, step := range status.Steps {
|
||||
stepStatus[step.Name] = &status.Steps[i]
|
||||
stepStatus := make(map[string]*model.WorkflowStepStatus, len(status.Steps))
|
||||
stepAlias := make(map[string]string)
|
||||
for _, step := range record.Steps {
|
||||
stepAlias[step.Name] = step.Alias
|
||||
for _, sub := range step.SubStepsStatus {
|
||||
stepAlias[sub.Name] = sub.Alias
|
||||
}
|
||||
}
|
||||
for _, step := range status.Steps {
|
||||
stepStatus[step.Name] = &model.WorkflowStepStatus{
|
||||
StepStatus: convert.FromCRWorkflowStepStatus(step.StepStatus, stepAlias[step.Name]),
|
||||
SubStepsStatus: make([]model.StepStatus, 0),
|
||||
}
|
||||
for _, sub := range step.SubStepsStatus {
|
||||
stepStatus[step.Name].SubStepsStatus = append(stepStatus[step.Name].SubStepsStatus, convert.FromCRWorkflowStepStatus(sub, stepAlias[sub.Name]))
|
||||
}
|
||||
}
|
||||
for i, step := range record.Steps {
|
||||
if stepStatus[step.Name] != nil {
|
||||
record.Steps[i].Phase = stepStatus[step.Name].Phase
|
||||
record.Steps[i].Message = stepStatus[step.Name].Message
|
||||
record.Steps[i].Reason = stepStatus[step.Name].Reason
|
||||
record.Steps[i].FirstExecuteTime = stepStatus[step.Name].FirstExecuteTime.Time
|
||||
record.Steps[i].LastExecuteTime = stepStatus[step.Name].LastExecuteTime.Time
|
||||
record.Steps[i] = *stepStatus[step.Name]
|
||||
}
|
||||
}
|
||||
record.Finished = strconv.FormatBool(status.Finished)
|
||||
@@ -542,9 +540,19 @@ func (w *workflowServiceImpl) CreateWorkflowRecord(ctx context.Context, appModel
|
||||
steps := make([]model.WorkflowStepStatus, len(workflow.Steps))
|
||||
for i, step := range workflow.Steps {
|
||||
steps[i] = model.WorkflowStepStatus{
|
||||
Name: step.Name,
|
||||
Alias: step.Alias,
|
||||
Type: step.Type,
|
||||
StepStatus: model.StepStatus{
|
||||
Name: step.Name,
|
||||
Alias: step.Alias,
|
||||
Type: step.Type,
|
||||
},
|
||||
SubStepsStatus: make([]model.StepStatus, 0),
|
||||
}
|
||||
for _, sub := range step.SubSteps {
|
||||
steps[i].SubStepsStatus = append(steps[i].SubStepsStatus, model.StepStatus{
|
||||
Name: sub.Name,
|
||||
Alias: sub.Alias,
|
||||
Type: sub.Type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +717,7 @@ func TerminateWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.
|
||||
switch sub.Phase {
|
||||
case workflowv1alpha1.WorkflowStepPhaseFailed:
|
||||
if sub.Reason != wfTypes.StatusReasonFailedAfterRetries && sub.Reason != wfTypes.StatusReasonTimeout {
|
||||
steps[i].SubStepsStatus[j].Phase = wfTypes.StatusReasonTerminate
|
||||
steps[i].SubStepsStatus[j].Reason = wfTypes.StatusReasonTerminate
|
||||
}
|
||||
case workflowv1alpha1.WorkflowStepPhaseRunning:
|
||||
steps[i].SubStepsStatus[j].Phase = workflowv1alpha1.WorkflowStepPhaseFailed
|
||||
|
||||
@@ -58,7 +58,7 @@ var _ = Describe("Test workflow service functions", func() {
|
||||
Expect(ds).ToNot(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
rbacService := &rbacServiceImpl{Store: ds}
|
||||
projectService = &projectServiceImpl{Store: ds, RbacService: rbacService}
|
||||
projectService = &projectServiceImpl{Store: ds, RbacService: rbacService, K8sClient: k8sClient}
|
||||
envService = &envServiceImpl{Store: ds, KubeClient: k8sClient, ProjectService: projectService}
|
||||
envBinding = &envBindingServiceImpl{
|
||||
Store: ds,
|
||||
@@ -123,12 +123,28 @@ var _ = Describe("Test workflow service functions", func() {
|
||||
EnvName: "dev",
|
||||
Steps: []apisv1.WorkflowStep{
|
||||
{
|
||||
Name: "apply-pvc",
|
||||
Alias: "step-alias-1",
|
||||
WorkflowStepBase: apisv1.WorkflowStepBase{
|
||||
Name: "apply-server",
|
||||
Alias: "step-alias-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "apply-server",
|
||||
Alias: "step-alias-2",
|
||||
WorkflowStepBase: apisv1.WorkflowStepBase{
|
||||
Name: "apply-server2",
|
||||
Alias: "step-alias-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
WorkflowStepBase: apisv1.WorkflowStepBase{
|
||||
Name: "group",
|
||||
Alias: "group-alias",
|
||||
},
|
||||
SubSteps: []apisv1.WorkflowStepBase{
|
||||
{
|
||||
Name: "suspend",
|
||||
Alias: "my-suspend",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Default: &defaultW,
|
||||
@@ -255,16 +271,20 @@ var _ = Describe("Test workflow service functions", func() {
|
||||
By("check the record")
|
||||
record, err := workflowService.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-233")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(record.Status).Should(Equal(model.RevisionStatusComplete))
|
||||
Expect(record.Status).Should(Equal(model.RevisionStatusFail))
|
||||
Expect(record.Steps[0].Alias).Should(Equal("step-alias-1"))
|
||||
Expect(record.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseSucceeded))
|
||||
Expect(record.Steps[1].Alias).Should(Equal("step-alias-2"))
|
||||
Expect(record.Steps[1].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseSucceeded))
|
||||
Expect(record.Steps[2].Alias).Should(Equal("group-alias"))
|
||||
Expect(record.Steps[2].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseFailed))
|
||||
Expect(record.Steps[2].SubStepsStatus[0].Alias).Should(Equal("my-suspend"))
|
||||
Expect(record.Steps[2].SubStepsStatus[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseFailed))
|
||||
|
||||
By("check the application revision")
|
||||
err = workflowService.Store.Get(ctx, revision)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(revision.Status).Should(Equal(model.RevisionStatusComplete))
|
||||
Expect(revision.Status).Should(Equal(model.RevisionStatusFail))
|
||||
|
||||
By("create another workflow record to test sync status from controller revision")
|
||||
app.Status.Workflow.Finished = false
|
||||
@@ -314,12 +334,12 @@ var _ = Describe("Test workflow service functions", func() {
|
||||
By("check the record")
|
||||
anotherRecord, err := workflowService.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-111")
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(anotherRecord.Status).Should(Equal(model.RevisionStatusComplete))
|
||||
Expect(anotherRecord.Status).Should(Equal(model.RevisionStatusFail))
|
||||
|
||||
By("check the application revision")
|
||||
err = workflowService.Store.Get(ctx, anotherRevision)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(anotherRevision.Status).Should(Equal(model.RevisionStatusComplete))
|
||||
Expect(anotherRevision.Status).Should(Equal(model.RevisionStatusFail))
|
||||
})
|
||||
|
||||
It("Test CreateRecord function", func() {
|
||||
@@ -541,10 +561,14 @@ var _ = Describe("Test workflow service functions", func() {
|
||||
Finished: "false",
|
||||
Steps: []model.WorkflowStepStatus{
|
||||
{
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
|
||||
StepStatus: model.StepStatus{
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
|
||||
},
|
||||
},
|
||||
{
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
|
||||
StepStatus: model.StepStatus{
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -619,10 +643,10 @@ var yamlStr = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
app.oam.dev/workflowName: test-workflow-2
|
||||
app.oam.dev/appName: app-workflow
|
||||
app.oam.dev/deployVersion: "1234"
|
||||
app.oam.dev/publishVersion: "test-workflow-name-111"
|
||||
app.oam.dev/appName: "app-workflow"
|
||||
app.oam.dev/publishVersion: test-workflow-name-111
|
||||
app.oam.dev/workflowName: test-workflow-2
|
||||
name: app-workflow
|
||||
namespace: default
|
||||
spec:
|
||||
@@ -632,31 +656,71 @@ spec:
|
||||
image: crccheck/hello-world
|
||||
port: 8000
|
||||
type: webservice
|
||||
- name: express-server2
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8000
|
||||
type: webservice
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply-server
|
||||
properties:
|
||||
component: express-server
|
||||
type: apply-component
|
||||
- name: apply-server2
|
||||
properties:
|
||||
component: express-server
|
||||
type: apply-component
|
||||
- name: group
|
||||
subSteps:
|
||||
- name: suspend
|
||||
timeout: 1s
|
||||
type: suspend
|
||||
type: step-group
|
||||
status:
|
||||
status: workflowFailed
|
||||
workflow:
|
||||
appRevision: test-workflow-name-111
|
||||
contextBackend:
|
||||
name: workflow-app-workflow-context
|
||||
namespace: default
|
||||
uid: ef9bcf49-66a7-4c69-b349-150810aa2bac
|
||||
endTime: "2022-10-28T06:45:46Z"
|
||||
finished: true
|
||||
message: The workflow terminates because of the failed steps
|
||||
mode: StepByStep-DAG
|
||||
startTime: "2022-10-28T06:45:37Z"
|
||||
status: failed
|
||||
steps:
|
||||
- firstExecuteTime: "2021-10-26T11:19:33Z"
|
||||
id: t8bpvi88d1
|
||||
lastExecuteTime: "2021-10-26T11:19:33Z"
|
||||
name: apply-pvc
|
||||
phase: succeeded
|
||||
type: apply-object
|
||||
- firstExecuteTime: "2021-10-26T11:19:33Z"
|
||||
id: 9fou7rbq9r
|
||||
lastExecuteTime: "2021-10-26T11:19:33Z"
|
||||
- firstExecuteTime: "2022-10-28T06:45:37Z"
|
||||
id: fg5uiwroe6
|
||||
lastExecuteTime: "2022-10-28T06:45:45Z"
|
||||
name: apply-server
|
||||
phase: succeeded
|
||||
type: apply-component
|
||||
- firstExecuteTime: "2022-10-28T06:45:45Z"
|
||||
id: prouwp48y7
|
||||
lastExecuteTime: "2022-10-28T06:45:45Z"
|
||||
name: apply-server2
|
||||
phase: succeeded
|
||||
type: apply-component
|
||||
- firstExecuteTime: "2022-10-28T06:45:45Z"
|
||||
id: s6o27xnkzq
|
||||
lastExecuteTime: "2022-10-28T06:45:46Z"
|
||||
name: group
|
||||
phase: failed
|
||||
reason: Timeout
|
||||
subSteps:
|
||||
- firstExecuteTime: "2022-10-28T06:45:45Z"
|
||||
id: ctu63esz2m
|
||||
lastExecuteTime: "2022-10-28T06:45:46Z"
|
||||
name: suspend
|
||||
phase: failed
|
||||
reason: Timeout
|
||||
type: suspend
|
||||
type: step-group
|
||||
suspend: false
|
||||
terminated: false
|
||||
finished: true
|
||||
appRevision: "test-workflow-name-111"`
|
||||
terminated: true`
|
||||
|
||||
func (w *workflowServiceImpl) createTestApplicationRevision(ctx context.Context, revision *model.ApplicationRevision) error {
|
||||
if err := w.Store.Add(ctx, revision); err != nil {
|
||||
|
||||
@@ -112,25 +112,48 @@ func FromCRWorkflow(ctx context.Context, cli client.Client, appPrimaryKey string
|
||||
steps = app.Spec.Workflow.Steps
|
||||
}
|
||||
for _, s := range steps {
|
||||
ws := model.WorkflowStep{
|
||||
Name: s.Name,
|
||||
Type: s.Type,
|
||||
Inputs: s.Inputs,
|
||||
Outputs: s.Outputs,
|
||||
DependsOn: s.DependsOn,
|
||||
base, err := FromCRWorkflowStepBase(s.WorkflowStepBase)
|
||||
if err != nil {
|
||||
return dataWf, nil, err
|
||||
}
|
||||
if s.Properties != nil {
|
||||
properties, err := model.NewJSONStruct(s.Properties)
|
||||
ws := model.WorkflowStep{
|
||||
WorkflowStepBase: *base,
|
||||
SubSteps: make([]model.WorkflowStepBase, 0),
|
||||
}
|
||||
for _, sub := range s.SubSteps {
|
||||
subBase, err := FromCRWorkflowStepBase(sub)
|
||||
if err != nil {
|
||||
return dataWf, nil, err
|
||||
}
|
||||
ws.Properties = properties
|
||||
ws.SubSteps = append(ws.SubSteps, *subBase)
|
||||
}
|
||||
dataWf.Steps = append(dataWf.Steps, ws)
|
||||
}
|
||||
return dataWf, steps, nil
|
||||
}
|
||||
|
||||
// FromCRWorkflowStepBase convert cr to model
|
||||
func FromCRWorkflowStepBase(step workflowv1alpha1.WorkflowStepBase) (*model.WorkflowStepBase, error) {
|
||||
base := &model.WorkflowStepBase{
|
||||
Name: step.Name,
|
||||
Type: step.Type,
|
||||
Inputs: step.Inputs,
|
||||
Outputs: step.Outputs,
|
||||
DependsOn: step.DependsOn,
|
||||
Meta: step.Meta,
|
||||
If: step.If,
|
||||
Timeout: step.Timeout,
|
||||
}
|
||||
if step.Properties != nil {
|
||||
properties, err := model.NewJSONStruct(step.Properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base.Properties = properties
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// FromCRTargets converts deployed Cluster/Namespace from Application CR Status into velaux data store
|
||||
func FromCRTargets(ctx context.Context, cli client.Client, targetApp *v1beta1.Application, existTargets []datastore.Entity, project string) ([]*model.Target, map[string]string) {
|
||||
existTarget := make(map[string]*model.Target)
|
||||
@@ -187,9 +210,11 @@ func FromCRWorkflowRecord(app *v1beta1.Application, workflow model.Workflow, rev
|
||||
steps := make([]model.WorkflowStepStatus, len(workflow.Steps))
|
||||
for i, step := range workflow.Steps {
|
||||
steps[i] = model.WorkflowStepStatus{
|
||||
Name: step.Name,
|
||||
Alias: step.Alias,
|
||||
Type: step.Type,
|
||||
StepStatus: model.StepStatus{
|
||||
Name: step.Name,
|
||||
Alias: step.Alias,
|
||||
Type: step.Type,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &model.WorkflowRecord{
|
||||
@@ -205,6 +230,21 @@ func FromCRWorkflowRecord(app *v1beta1.Application, workflow model.Workflow, rev
|
||||
}
|
||||
}
|
||||
|
||||
// FromCRWorkflowStepStatus convert the workflow step status to workflow step status
|
||||
func FromCRWorkflowStepStatus(stepStatus workflowv1alpha1.StepStatus, alias string) model.StepStatus {
|
||||
return model.StepStatus{
|
||||
Name: stepStatus.Name,
|
||||
Alias: alias,
|
||||
ID: stepStatus.ID,
|
||||
Type: stepStatus.Type,
|
||||
Message: stepStatus.Message,
|
||||
Reason: stepStatus.Reason,
|
||||
Phase: stepStatus.Phase,
|
||||
FirstExecuteTime: stepStatus.FirstExecuteTime.Time,
|
||||
LastExecuteTime: stepStatus.LastExecuteTime.Time,
|
||||
}
|
||||
}
|
||||
|
||||
// FromCRApplicationRevision convert the application revision to the revision in the data store
|
||||
func FromCRApplicationRevision(ctx context.Context, cli client.Client, app *v1beta1.Application, workflow model.Workflow, envName string) *model.ApplicationRevision {
|
||||
if app.Status.Workflow == nil || app.Status.Workflow.AppRevision == "" {
|
||||
|
||||
@@ -17,19 +17,18 @@ limitations under the License.
|
||||
package clients
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/cue/packages"
|
||||
|
||||
apiConfig "github.com/oam-dev/kubevela/pkg/apiserver/config"
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
@@ -74,20 +73,13 @@ func GetKubeClient() (client.Client, error) {
|
||||
if kubeConfig == nil {
|
||||
return nil, fmt.Errorf("please call SetKubeConfig first")
|
||||
}
|
||||
var err error
|
||||
kubeClient, err = multicluster.Initialize(kubeConfig, false)
|
||||
if err == nil {
|
||||
return kubeClient, nil
|
||||
}
|
||||
if !errors.Is(err, multicluster.ErrDetectClusterGateway) {
|
||||
return nil, err
|
||||
}
|
||||
// create single cluster client
|
||||
kubeClient, err = client.New(kubeConfig, client.Options{Scheme: common.Scheme})
|
||||
err := v1alpha1.AddToScheme(common.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kubeClient, nil
|
||||
return pkgmulticluster.NewClient(kubeConfig, pkgmulticluster.ClientOptions{
|
||||
Options: client.Options{Scheme: common.Scheme},
|
||||
})
|
||||
}
|
||||
|
||||
// GetKubeConfig create/get kube runtime config
|
||||
|
||||
@@ -212,13 +212,11 @@ func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datasto
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
// bson.D{{}} specifies 'all documents'
|
||||
filter := bson.D{}
|
||||
if entity.Index() != nil {
|
||||
for k, v := range entity.Index() {
|
||||
filter = append(filter, bson.E{
|
||||
Key: strings.ToLower(k),
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
for k, v := range entity.Index() {
|
||||
filter = append(filter, bson.E{
|
||||
Key: strings.ToLower(k),
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
if op != nil {
|
||||
filter = _applyFilterOptions(filter, op.FilterOptions)
|
||||
@@ -272,13 +270,11 @@ func (m *mongodb) Count(ctx context.Context, entity datastore.Entity, filterOpti
|
||||
}
|
||||
collection := m.client.Database(m.database).Collection(entity.TableName())
|
||||
filter := bson.D{}
|
||||
if entity.Index() != nil {
|
||||
for k, v := range entity.Index() {
|
||||
filter = append(filter, bson.E{
|
||||
Key: strings.ToLower(k),
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
for k, v := range entity.Index() {
|
||||
filter = append(filter, bson.E{
|
||||
Key: strings.ToLower(k),
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
if filterOptions != nil {
|
||||
filter = _applyFilterOptions(filter, *filterOptions)
|
||||
|
||||
@@ -152,19 +152,36 @@ func ConvertFromRecordModel(record *model.WorkflowRecord) *apisv1.WorkflowRecord
|
||||
// ConvertFromWorkflowStepModel assemble the WorkflowStep model to DTO
|
||||
func ConvertFromWorkflowStepModel(step model.WorkflowStep) apisv1.WorkflowStep {
|
||||
apiStep := apisv1.WorkflowStep{
|
||||
WorkflowStepBase: ConvertFromWorkflowStepBaseModel(step.WorkflowStepBase),
|
||||
SubSteps: make([]apisv1.WorkflowStepBase, 0),
|
||||
}
|
||||
if step.Properties != nil {
|
||||
apiStep.Properties = step.Properties.JSON()
|
||||
}
|
||||
for _, sub := range step.SubSteps {
|
||||
apiStep.SubSteps = append(apiStep.SubSteps, ConvertFromWorkflowStepBaseModel(sub))
|
||||
}
|
||||
return apiStep
|
||||
}
|
||||
|
||||
// ConvertFromWorkflowStepBaseModel assemble the WorkflowStep model to DTO
|
||||
func ConvertFromWorkflowStepBaseModel(step model.WorkflowStepBase) apisv1.WorkflowStepBase {
|
||||
apiStepBase := apisv1.WorkflowStepBase{
|
||||
Name: step.Name,
|
||||
Type: step.Type,
|
||||
Alias: step.Alias,
|
||||
Description: step.Description,
|
||||
Inputs: step.Inputs,
|
||||
Outputs: step.Outputs,
|
||||
Properties: step.Properties.JSON(),
|
||||
DependsOn: step.DependsOn,
|
||||
Meta: step.Meta,
|
||||
If: step.If,
|
||||
Timeout: step.Timeout,
|
||||
}
|
||||
if step.Properties != nil {
|
||||
apiStep.Properties = step.Properties.JSON()
|
||||
apiStepBase.Properties = step.Properties.JSON()
|
||||
}
|
||||
return apiStep
|
||||
return apiStepBase
|
||||
}
|
||||
|
||||
// ConvertWorkflowBase assemble the Workflow model to DTO
|
||||
|
||||
@@ -47,21 +47,44 @@ func ConvertToEnvBindingModel(app *model.Application, envBind apisv1.EnvBinding)
|
||||
func CreateWorkflowStepModel(apiSteps []apisv1.WorkflowStep) ([]model.WorkflowStep, error) {
|
||||
var steps []model.WorkflowStep
|
||||
for _, step := range apiSteps {
|
||||
properties, err := model.NewJSONStructByString(step.Properties)
|
||||
base, err := CreateWorkflowStepBaseModel(step.WorkflowStepBase)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("parse trait properties failure %w", err)
|
||||
return nil, bcode.ErrInvalidProperties
|
||||
return nil, err
|
||||
}
|
||||
steps = append(steps, model.WorkflowStep{
|
||||
Name: step.Name,
|
||||
Alias: step.Alias,
|
||||
Description: step.Description,
|
||||
DependsOn: step.DependsOn,
|
||||
Type: step.Type,
|
||||
Inputs: step.Inputs,
|
||||
Outputs: step.Outputs,
|
||||
Properties: properties,
|
||||
})
|
||||
stepModel := model.WorkflowStep{
|
||||
WorkflowStepBase: *base,
|
||||
SubSteps: make([]model.WorkflowStepBase, 0),
|
||||
}
|
||||
for _, sub := range step.SubSteps {
|
||||
base, err := CreateWorkflowStepBaseModel(sub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stepModel.SubSteps = append(stepModel.SubSteps, *base)
|
||||
}
|
||||
steps = append(steps, stepModel)
|
||||
}
|
||||
return steps, nil
|
||||
}
|
||||
|
||||
// CreateWorkflowStepBaseModel convert api to model
|
||||
func CreateWorkflowStepBaseModel(step apisv1.WorkflowStepBase) (*model.WorkflowStepBase, error) {
|
||||
properties, err := model.NewJSONStructByString(step.Properties)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("parse trait workflow step failure %w", err)
|
||||
return nil, bcode.ErrInvalidProperties
|
||||
}
|
||||
return &model.WorkflowStepBase{
|
||||
Name: step.Name,
|
||||
Type: step.Type,
|
||||
Alias: step.Alias,
|
||||
Description: step.Description,
|
||||
Properties: properties,
|
||||
Inputs: step.Inputs,
|
||||
Outputs: step.Outputs,
|
||||
DependsOn: step.DependsOn,
|
||||
Meta: step.Meta,
|
||||
If: step.If,
|
||||
Timeout: step.Timeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -50,12 +50,14 @@ var (
|
||||
CtxKeyApplicationComponent = "component"
|
||||
// CtxKeyUser request context key of user
|
||||
CtxKeyUser = "user"
|
||||
// CtxKeyProject request context key of project
|
||||
CtxKeyProject = "project"
|
||||
// CtxKeyToken request context key of request token
|
||||
CtxKeyToken = "token"
|
||||
// CtxKeyPipeline request context key of pipeline
|
||||
CtxKeyPipeline = "pipeline"
|
||||
// CtxKeyPipelineContex request context key of pipeline context
|
||||
CtxKeyPipelineContex = "pipeline-context"
|
||||
// CtxKeyPipelineContext request context key of pipeline context
|
||||
CtxKeyPipelineContext = "pipeline-context"
|
||||
// CtxKeyPipelineRun request context key of pipeline run
|
||||
CtxKeyPipelineRun = "pipeline-run"
|
||||
)
|
||||
@@ -791,6 +793,7 @@ type ProjectBase struct {
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
UpdateTime time.Time `json:"updateTime"`
|
||||
Owner NameAlias `json:"owner,omitempty"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// CreateProjectRequest create project request body
|
||||
@@ -1003,15 +1006,24 @@ type UpdateWorkflowRequest struct {
|
||||
|
||||
// WorkflowStep workflow step config
|
||||
type WorkflowStep struct {
|
||||
WorkflowStepBase `json:",inline"`
|
||||
SubSteps []WorkflowStepBase `json:"subSteps,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowStepBase is the step base of workflow
|
||||
type WorkflowStepBase struct {
|
||||
// Name is the unique name of the workflow step.
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
Type string `json:"type" validate:"checkname"`
|
||||
Description string `json:"description" optional:"true"`
|
||||
DependsOn []string `json:"dependsOn" optional:"true"`
|
||||
Properties string `json:"properties,omitempty"`
|
||||
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty" optional:"true"`
|
||||
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty" optional:"true"`
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
Type string `json:"type" validate:"checkname"`
|
||||
Description string `json:"description" optional:"true"`
|
||||
DependsOn []string `json:"dependsOn" optional:"true"`
|
||||
Properties string `json:"properties,omitempty"`
|
||||
Meta *workflowv1alpha1.WorkflowStepMeta `json:"meta,omitempty" optional:"true"`
|
||||
If string `json:"if,omitempty" optional:"true"`
|
||||
Timeout string `json:"timeout,omitempty" optional:"true"`
|
||||
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty" optional:"true"`
|
||||
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty" optional:"true"`
|
||||
}
|
||||
|
||||
// DetailWorkflowResponse detail workflow response
|
||||
@@ -1542,16 +1554,17 @@ type ListConfigDistributionResponse struct {
|
||||
|
||||
// PipelineMeta is metadata of pipeline
|
||||
type PipelineMeta struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Alias string `json:"alias" validate:"checkalias" optional:"true"`
|
||||
Project string `json:"project"`
|
||||
Description string `json:"description" optional:"true"`
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
Project NameAlias `json:"project"`
|
||||
Description string `json:"description"`
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1570,11 +1583,10 @@ type RunStat struct {
|
||||
|
||||
// CreatePipelineRequest is the request body of creating pipeline
|
||||
type CreatePipelineRequest struct {
|
||||
Name string `json:"name" validate:"checkname"`
|
||||
Project string `json:"project"`
|
||||
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
|
||||
@@ -1584,8 +1596,9 @@ type PipelineMetaResponse struct {
|
||||
|
||||
// ListPipelineRequest is the request body of listing pipeline
|
||||
type ListPipelineRequest struct {
|
||||
Projects []string `json:"projects"`
|
||||
Query string `json:"query"`
|
||||
Projects []string `json:"projects" optional:"true"`
|
||||
Query string `json:"query" optional:"true"`
|
||||
Detailed bool `json:"detailed" optional:"true"`
|
||||
}
|
||||
|
||||
// ListPipelineResponse is the response body of listing pipeline
|
||||
@@ -1602,14 +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"`
|
||||
}
|
||||
|
||||
// GetPipelineRequest is the request body of getting pipeline
|
||||
type GetPipelineRequest struct {
|
||||
Detailed bool `json:"detailed"`
|
||||
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
|
||||
@@ -1620,9 +1628,8 @@ type GetPipelineResponse struct {
|
||||
|
||||
// PipelineInfo is the info of pipeline
|
||||
type PipelineInfo struct {
|
||||
RelatedApps []ApplicationBase `json:"relatedApps"`
|
||||
LastRunStatus workflowv1alpha1.WorkflowRunStatus `json:"lastRunStatus"`
|
||||
RunStat RunStat `json:"runStat"`
|
||||
LastRun *PipelineRun `json:"lastRun"`
|
||||
RunStat RunStat `json:"runStat"`
|
||||
}
|
||||
|
||||
/***********************/
|
||||
@@ -1643,9 +1650,9 @@ type PipelineRunBriefing struct {
|
||||
|
||||
// PipelineRunMeta is the metadata of pipeline run
|
||||
type PipelineRunMeta struct {
|
||||
PipelineName string `json:"pipelineName"`
|
||||
Project string `json:"project"`
|
||||
PipelineRunName string `json:"pipelineRunName"`
|
||||
PipelineName string `json:"pipelineName"`
|
||||
Project NameAlias `json:"project"`
|
||||
PipelineRunName string `json:"pipelineRunName"`
|
||||
}
|
||||
|
||||
// PipelineRun is the info of pipeline run
|
||||
@@ -1658,14 +1665,16 @@ type PipelineRun struct {
|
||||
type PipelineRunBase struct {
|
||||
PipelineRunMeta `json:",inline"`
|
||||
// Record marks the run of the pipeline
|
||||
Record int64 `json:"record"`
|
||||
ContextName string `json:"contextName"`
|
||||
Spec workflowv1alpha1.WorkflowRunSpec `json:"spec"`
|
||||
Record int64 `json:"record"`
|
||||
ContextName string `json:"contextName"`
|
||||
ContextValues []model.Value `json:"contextValues"`
|
||||
Spec workflowv1alpha1.WorkflowRunSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// RunPipelineRequest is the request body of running pipeline
|
||||
type RunPipelineRequest struct {
|
||||
// Mode is the mode of the pipeline run. Available values are: "StepByStep", "DAG" for both `step` and `subStep`
|
||||
// default: "StepByStep" for `step`, "DAG" for `subStep`
|
||||
Mode workflowv1alpha1.WorkflowExecuteMode `json:"mode" optional:"true"`
|
||||
ContextName string `json:"contextName"`
|
||||
}
|
||||
@@ -1678,12 +1687,18 @@ type ListPipelineRunResponse struct {
|
||||
|
||||
// GetPipelineRunLogResponse is the response body of getting pipeline run log
|
||||
type GetPipelineRunLogResponse struct {
|
||||
Log []Log `json:"log"`
|
||||
StepBase `json:",inline"`
|
||||
Log string `json:"log"`
|
||||
}
|
||||
|
||||
// GetPipelineRunOutputResponse is the response body of getting pipeline run output
|
||||
type GetPipelineRunOutputResponse struct {
|
||||
Output []Output `json:"output"`
|
||||
StepOutputs []StepOutputBase `json:"outputs"`
|
||||
}
|
||||
|
||||
// GetPipelineRunInputResponse is the response body of getting pipeline run input
|
||||
type GetPipelineRunInputResponse struct {
|
||||
StepInputs []StepInputBase `json:"inputs"`
|
||||
}
|
||||
|
||||
// StepBase is the base info of step
|
||||
@@ -1694,16 +1709,31 @@ type StepBase struct {
|
||||
Phase string `json:"phase"`
|
||||
}
|
||||
|
||||
// Log is the log of step
|
||||
type Log struct {
|
||||
// StepOutputBase is the output of step
|
||||
type StepOutputBase struct {
|
||||
StepBase `json:",inline"`
|
||||
Log string `json:"log"`
|
||||
Values []OutputVar `json:"values"`
|
||||
}
|
||||
|
||||
// Output is the output of step
|
||||
type Output struct {
|
||||
// StepInputBase is the input of step
|
||||
type StepInputBase struct {
|
||||
StepBase `json:",inline"`
|
||||
Vars map[string]string `json:"vars"`
|
||||
Values []InputVar `json:"values"`
|
||||
}
|
||||
|
||||
// OutputVar is one output var
|
||||
type OutputVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
ValueFrom string `json:"valueFrom"`
|
||||
}
|
||||
|
||||
// InputVar is one input var
|
||||
type InputVar struct {
|
||||
From string `json:"from"`
|
||||
FromStep string `json:"fromStep"`
|
||||
ParameterKey string `json:"parameterKey"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
/*******************/
|
||||
|
||||
@@ -62,6 +62,7 @@ func InitAPIBean() []interface{} {
|
||||
RegisterAPIInterface(NewApplicationAPIInterface())
|
||||
RegisterAPIInterface(NewProjectAPIInterface())
|
||||
RegisterAPIInterface(NewEnvAPIInterface())
|
||||
RegisterAPIInterface(NewPipelineAPIInterface())
|
||||
|
||||
// Extension
|
||||
RegisterAPIInterface(NewDefinitionAPIInterface())
|
||||
@@ -82,7 +83,6 @@ func InitAPIBean() []interface{} {
|
||||
RegisterAPIInterface(NewWebhookAPIInterface())
|
||||
RegisterAPIInterface(NewRepositoryAPIInterface())
|
||||
RegisterAPIInterface(NewCloudShellAPIInterface())
|
||||
RegisterAPIInterface(NewPipelineAPIInterface())
|
||||
|
||||
// Authentication
|
||||
RegisterAPIInterface(NewAuthenticationAPIInterface())
|
||||
|
||||
@@ -18,199 +18,242 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
|
||||
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
)
|
||||
|
||||
type pipelineAPIInterface struct {
|
||||
PipelineService service.PipelineService `inject:""`
|
||||
PipelineRunService service.PipelineRunService `inject:""`
|
||||
ContextService service.ContextService `inject:""`
|
||||
}
|
||||
|
||||
type pipelinePathParamKey string
|
||||
|
||||
const (
|
||||
// Project is the project name key of query param
|
||||
Project pipelinePathParamKey = "projectName"
|
||||
Project string = "projectName"
|
||||
// Pipeline is the pipeline name of query param
|
||||
Pipeline pipelinePathParamKey = "pipelineName"
|
||||
Pipeline string = "pipelineName"
|
||||
// PipelineRun is the pipeline run name of query param
|
||||
PipelineRun pipelinePathParamKey = "runName"
|
||||
PipelineRun string = "runName"
|
||||
// ContextName is the context name of query param
|
||||
ContextName pipelinePathParamKey = "contextName"
|
||||
ContextName string = "contextName"
|
||||
)
|
||||
|
||||
// GetWebServiceRoute is the implementation of pipeline Interface
|
||||
func (p *pipelineAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
|
||||
ws := new(restful.WebService)
|
||||
func initPipelineRoutes(ws *restful.WebService, n *projectAPIInterface) {
|
||||
tags := []string{"pipeline"}
|
||||
|
||||
projParam := func(builder *restful.RouteBuilder) {
|
||||
builder.Param(ws.QueryParameter(string(Project), "project name").Required(true))
|
||||
builder.Param(ws.PathParameter(Project, "project name").Required(true))
|
||||
builder.Filter(n.projectCheckFilter)
|
||||
}
|
||||
pipelineParam := func(builder *restful.RouteBuilder) {
|
||||
builder.Param(ws.PathParameter(string(Pipeline), "pipeline name").Required(true))
|
||||
builder.Filter(p.pipelineCheckFilter)
|
||||
builder.Param(ws.PathParameter(Pipeline, "pipeline name").Required(true))
|
||||
builder.Filter(n.pipelineCheckFilter)
|
||||
}
|
||||
ctxParam := func(builder *restful.RouteBuilder) {
|
||||
builder.Param(ws.PathParameter(string(ContextName), "pipeline context name").Required(true))
|
||||
builder.Filter(p.pipelineContextCheckFilter)
|
||||
builder.Param(ws.PathParameter(ContextName, "pipeline context name").Required(true))
|
||||
builder.Filter(n.pipelineContextCheckFilter)
|
||||
}
|
||||
runParam := func(builder *restful.RouteBuilder) {
|
||||
builder.Param(ws.PathParameter(string(PipelineRun), "pipeline run name").Required(true))
|
||||
builder.Filter(p.pipelineRunCheckFilter)
|
||||
builder.Param(ws.PathParameter(PipelineRun, "pipeline run name").Required(true))
|
||||
builder.Filter(n.pipelineRunCheckFilter)
|
||||
}
|
||||
meta := func(builder *restful.RouteBuilder) {
|
||||
builder.Metadata(restfulspec.KeyOpenAPITags, tags)
|
||||
}
|
||||
|
||||
ws.Path(versionPrefix+"/pipelines").
|
||||
Consumes(restful.MIME_JSON, restful.MIME_XML).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for pipeline manage")
|
||||
|
||||
ws.Route(ws.POST("").To(p.createPipeline).
|
||||
ws.Route(ws.POST("/{projectName}/pipelines").To(n.createPipeline).
|
||||
Doc("create pipeline").
|
||||
Reads(apis.CreatePipelineRequest{}).
|
||||
Returns(200, "OK", apis.PipelineBase{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.PipelineBase{}).Do(meta))
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline", "create")).
|
||||
Writes(apis.PipelineBase{}).Do(meta, projParam))
|
||||
|
||||
ws.Route(ws.GET("").To(p.listPipelines).
|
||||
Doc("list pipelines").
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")).
|
||||
Returns(200, "OK", apis.ListPipelineResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListPipelineResponse{}).Do(meta, projParam))
|
||||
|
||||
ws.Route(ws.GET("/{pipelineName}").To(p.getPipeline).
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}").To(n.getPipeline).
|
||||
Doc("get pipeline").
|
||||
Reads(apis.GetPipelineRequest{}).
|
||||
Returns(200, "OK", apis.GetPipelineResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.GetPipelineResponse{}).Do(meta, projParam, pipelineParam))
|
||||
// use Param instead of pipelineParam to get pipeline information
|
||||
Param(ws.PathParameter(Pipeline, "pipeline name").Required(true)).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline", "detail")).
|
||||
Writes(apis.GetPipelineResponse{}).Do(meta, projParam))
|
||||
|
||||
ws.Route(ws.PUT("/{pipelineName}").To(p.updatePipeline).
|
||||
ws.Route(ws.PUT("/{projectName}/pipelines/{pipelineName}").To(n.updatePipeline).
|
||||
Doc("update pipeline").
|
||||
Reads(apis.UpdatePipelineRequest{}).
|
||||
Returns(200, "OK", apis.PipelineBase{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline", "update")).
|
||||
Writes(apis.PipelineBase{}).Do(meta, projParam, pipelineParam))
|
||||
|
||||
ws.Route(ws.DELETE("/{pipelineName}").To(p.deletePipeline).
|
||||
ws.Route(ws.DELETE("/{projectName}/pipelines/{pipelineName}").To(n.deletePipeline).
|
||||
Doc("delete pipeline").
|
||||
Returns(200, "OK", apis.PipelineMetaResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline", "delete")).
|
||||
Writes(apis.PipelineMetaResponse{}).Do(meta, projParam, pipelineParam))
|
||||
|
||||
ws.Route(ws.POST("/{pipelineName}/contexts").To(p.createContextValue).
|
||||
ws.Route(ws.POST("/{projectName}/pipelines/{pipelineName}/contexts").To(n.createContextValue).
|
||||
Doc("create pipeline context values").
|
||||
Reads(apis.CreateContextValuesRequest{}).
|
||||
Returns(200, "OK", apis.Context{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/context", "create")).
|
||||
Writes(apis.Context{}).Do(meta, projParam, pipelineParam))
|
||||
|
||||
ws.Route(ws.GET("/{pipelineName}/contexts").To(p.listContextValues).
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/contexts").To(n.listContextValues).
|
||||
Doc("list pipeline context values").
|
||||
Returns(200, "OK", apis.ListContextValueResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/context", "list")).
|
||||
Writes(apis.ListContextValueResponse{}).Do(meta, projParam, pipelineParam))
|
||||
|
||||
ws.Route(ws.PUT("/{pipelineName}/contexts/{contextName}").To(p.updateContextValue).
|
||||
ws.Route(ws.PUT("/{projectName}/pipelines/{pipelineName}/contexts/{contextName}").To(n.updateContextValue).
|
||||
Doc("update pipeline context value").
|
||||
Reads(apis.UpdateContextValuesRequest{}).
|
||||
Returns(200, "OK", apis.Context{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/context", "update")).
|
||||
Writes(apis.Context{}).Do(meta, projParam, pipelineParam, ctxParam))
|
||||
|
||||
ws.Route(ws.DELETE("/{pipelineName}/contexts/{contextName}").To(p.deleteContextValue).
|
||||
ws.Route(ws.DELETE("/{projectName}/pipelines/{pipelineName}/contexts/{contextName}").To(n.deleteContextValue).
|
||||
Doc("delete pipeline context value").
|
||||
Returns(200, "OK", apis.ContextNameResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/context", "delete")).
|
||||
Writes(apis.ContextNameResponse{}).Do(meta, projParam, pipelineParam, ctxParam))
|
||||
|
||||
ws.Route(ws.POST("/{pipelineName}/run").To(p.runPipeline).
|
||||
ws.Route(ws.POST("/{projectName}/pipelines/{pipelineName}/run").To(n.runPipeline).
|
||||
Doc("run pipeline").
|
||||
Reads(apis.RunPipelineRequest{}).
|
||||
Returns(200, "OK", apis.PipelineRunMeta{}).
|
||||
Returns(200, "OK", apis.PipelineRun{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline", "run")).
|
||||
Writes(apis.PipelineRunMeta{}).Do(meta, projParam, pipelineParam))
|
||||
|
||||
ws.Route(ws.GET("/{pipelineName}/runs").To(p.listPipelineRuns).
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs").To(n.listPipelineRuns).
|
||||
Doc("list pipeline runs").
|
||||
Param(ws.QueryParameter("status", "query identifier of the status").DataType("string")).
|
||||
Returns(200, "OK", apis.ListPipelineRunResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "list")).
|
||||
Writes(apis.ListPipelineRunResponse{}).Do(meta, projParam, pipelineParam))
|
||||
|
||||
ws.Route(ws.POST("/{pipelineName}/runs/{runName}/stop").To(p.stopPipeline).
|
||||
ws.Route(ws.POST("/{projectName}/pipelines/{pipelineName}/runs/{runName}/stop").To(n.stopPipeline).
|
||||
Doc("stop pipeline run").
|
||||
Returns(200, "OK", apis.PipelineRunMeta{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "stop")).
|
||||
Writes(apis.PipelineRunMeta{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
|
||||
ws.Route(ws.GET("/{pipelineName}/runs/{runName}").To(p.getPipelineRun).
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}").To(n.getPipelineRun).
|
||||
Doc("get pipeline run").
|
||||
Returns(200, "OK", apis.PipelineRunBase{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "get")).
|
||||
Writes(apis.PipelineRunBase{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
|
||||
ws.Route(ws.DELETE("/{pipelineName}/runs/{runName}").To(p.deletePipelineRun).
|
||||
ws.Route(ws.DELETE("/{projectName}/pipelines/{pipelineName}/runs/{runName}").To(n.deletePipelineRun).
|
||||
Doc("delete pipeline run").
|
||||
Returns(200, "OK", apis.PipelineRunMeta{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "delete")).
|
||||
Writes(apis.PipelineRunMeta{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
|
||||
// get pipeline run status
|
||||
ws.Route(ws.GET("/{pipelineName}/runs/{runName}/status").To(p.getPipelineRunStatus).
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/status").To(n.getPipelineRunStatus).
|
||||
Doc("get pipeline run status").
|
||||
Returns(200, "OK", workflowv1alpha1.WorkflowRunStatus{}).
|
||||
Returns(200, "OK", v1alpha1.WorkflowRunStatus{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(workflowv1alpha1.WorkflowRunStatus{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
|
||||
Writes(v1alpha1.WorkflowRunStatus{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
|
||||
// get pipeline run log
|
||||
ws.Route(ws.GET("/{pipelineName}/runs/{runName}/log").To(p.getPipelineRunLog).
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/log").To(n.getPipelineRunLog).
|
||||
Doc("get pipeline run log").
|
||||
Param(ws.QueryParameter("step", "query by specific id").DataType("string")).
|
||||
Param(ws.QueryParameter("step", "query by specific step name").DataType("string")).
|
||||
Returns(200, "OK", apis.GetPipelineRunLogResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
|
||||
Writes(apis.GetPipelineRunLogResponse{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
|
||||
// get pipeline run output
|
||||
ws.Route(ws.GET("/{pipelineName}/runs/{runName}/output").To(p.getPipelineRunOutput).
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/output").To(n.getPipelineRunOutput).
|
||||
Doc("get pipeline run output").
|
||||
Param(ws.QueryParameter("step", "query by specific id").DataType("string")).
|
||||
Param(ws.QueryParameter("step", "query by specific step name").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.GetPipelineRunOutputResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
|
||||
Writes(apis.GetPipelineRunOutputResponse{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
|
||||
// get pipeline run input
|
||||
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/input").To(n.getPipelineRunInput).
|
||||
Doc("get pipeline run input").
|
||||
Param(ws.QueryParameter("step", "query by specific step name").DataType("string").Required(true)).
|
||||
Returns(200, "OK", apis.GetPipelineRunInputResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
|
||||
Writes(apis.GetPipelineRunInputResponse{}).Do(meta, projParam, pipelineParam, runParam))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
}
|
||||
|
||||
// GetWebServiceRoute is the implementation of pipeline Interface
|
||||
func (n *pipelineAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
tags := []string{"pipeline"}
|
||||
meta := func(builder *restful.RouteBuilder) {
|
||||
builder.Metadata(restfulspec.KeyOpenAPITags, tags)
|
||||
}
|
||||
|
||||
ws := new(restful.WebService)
|
||||
|
||||
ws.Path(versionPrefix+"/pipelines").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML).
|
||||
Doc("api for project manage")
|
||||
|
||||
ws.Route(ws.GET("").To(n.listPipelines).
|
||||
Doc("list pipelines").
|
||||
Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")).
|
||||
Param(ws.QueryParameter("projectName", "query pipelines within a project").DataType("string")).
|
||||
Param(ws.QueryParameter("detailed", "query pipelines with detail").DataType("bool").DefaultValue("true")).
|
||||
Returns(200, "OK", apis.ListPipelineResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListPipelineResponse{}).Do(meta))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
|
||||
type pipelineAPIInterface struct {
|
||||
PipelineService service.PipelineService `inject:""`
|
||||
}
|
||||
|
||||
// NewPipelineAPIInterface new pipeline manage APIInterface
|
||||
func NewPipelineAPIInterface() Interface {
|
||||
return &pipelineAPIInterface{}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) listPipelines(req *restful.Request, res *restful.Response) {
|
||||
var projetNames []string
|
||||
if req.QueryParameter("project") != "" {
|
||||
projetNames = append(projetNames, req.QueryParameter("project"))
|
||||
func (n *pipelineAPIInterface) listPipelines(req *restful.Request, res *restful.Response) {
|
||||
var projectNames []string
|
||||
if req.QueryParameter(Project) != "" {
|
||||
projectNames = append(projectNames, req.QueryParameter(Project))
|
||||
}
|
||||
pipelines, err := p.PipelineService.ListPipelines(req.Request.Context(), apis.ListPipelineRequest{
|
||||
Projects: projetNames,
|
||||
_detailed := req.QueryParameter("detailed")
|
||||
if _detailed == "" {
|
||||
_detailed = "true"
|
||||
}
|
||||
detailed, err := strconv.ParseBool(_detailed)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, errors.Wrap(err, "invalid detailed param"))
|
||||
}
|
||||
pipelines, err := n.PipelineService.ListPipelines(req.Request.Context(), apis.ListPipelineRequest{
|
||||
Projects: projectNames,
|
||||
Query: req.QueryParameter("query"),
|
||||
Detailed: detailed,
|
||||
})
|
||||
if err != nil {
|
||||
log.Logger.Errorf("list pipeline failure %s", err.Error())
|
||||
@@ -223,15 +266,18 @@ func (p *pipelineAPIInterface) listPipelines(req *restful.Request, res *restful.
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) getPipeline(req *restful.Request, res *restful.Response) {
|
||||
pipeline := req.Request.Context().Value(apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
func (n *projectAPIInterface) getPipeline(req *restful.Request, res *restful.Response) {
|
||||
pipeline, err := n.PipelineService.GetPipeline(req.Request.Context(), req.PathParameter(Pipeline), true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(pipeline); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) createPipeline(req *restful.Request, res *restful.Response) {
|
||||
func (n *projectAPIInterface) createPipeline(req *restful.Request, res *restful.Response) {
|
||||
var createReq apis.CreatePipelineRequest
|
||||
if err := req.ReadEntity(&createReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -241,19 +287,25 @@ func (p *pipelineAPIInterface) createPipeline(req *restful.Request, res *restful
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
pipelineBase, err := p.PipelineService.CreatePipeline(req.Request.Context(), createReq)
|
||||
pipelineBase, err := n.PipelineService.CreatePipeline(req.Request.Context(), createReq)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("create pipeline failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
_, err = n.ContextService.InitContext(req.Request.Context(), pipelineBase.Project.Name, pipelineBase.Name)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("init pipeline context failure: %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(pipelineBase); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) updatePipeline(req *restful.Request, res *restful.Response) {
|
||||
func (n *projectAPIInterface) updatePipeline(req *restful.Request, res *restful.Response) {
|
||||
var updateReq apis.UpdatePipelineRequest
|
||||
if err := req.ReadEntity(&updateReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -263,8 +315,8 @@ func (p *pipelineAPIInterface) updatePipeline(req *restful.Request, res *restful
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
base := req.Request.Context().Value(apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
pipelineBase, err := p.PipelineService.UpdatePipeline(req.Request.Context(), base.Name, base.Project, updateReq)
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
pipelineBase, err := n.PipelineService.UpdatePipeline(req.Request.Context(), pipeline.Name, updateReq)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("update pipeline failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -276,43 +328,43 @@ func (p *pipelineAPIInterface) updatePipeline(req *restful.Request, res *restful
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) deletePipeline(req *restful.Request, res *restful.Response) {
|
||||
func (n *projectAPIInterface) deletePipeline(req *restful.Request, res *restful.Response) {
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
err := p.PipelineService.DeletePipeline(req.Request.Context(), pipeline)
|
||||
err := n.PipelineService.DeletePipeline(req.Request.Context(), pipeline)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("delete pipeline failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(pipeline.PipelineMeta); err != nil {
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) runPipeline(req *restful.Request, res *restful.Response) {
|
||||
func (n *projectAPIInterface) runPipeline(req *restful.Request, res *restful.Response) {
|
||||
var runReq apis.RunPipelineRequest
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
if err := req.ReadEntity(runReq); err != nil {
|
||||
if err := req.ReadEntity(&runReq); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := p.PipelineService.RunPipeline(req.Request.Context(), pipeline, runReq)
|
||||
run, err := n.PipelineService.RunPipeline(req.Request.Context(), pipeline, runReq)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("run pipeline failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(pipeline.PipelineMeta); err != nil {
|
||||
if err := res.WriteEntity(run); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) stopPipeline(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
|
||||
err := p.PipelineRunService.StopPipelineRun(req.Request.Context(), pipelineRun.PipelineRunBase)
|
||||
func (n *projectAPIInterface) stopPipeline(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
|
||||
err := n.PipelineRunService.StopPipelineRun(req.Request.Context(), pipelineRun.PipelineRunBase)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("stop pipeline failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -324,9 +376,9 @@ func (p *pipelineAPIInterface) stopPipeline(req *restful.Request, res *restful.R
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) listPipelineRuns(req *restful.Request, res *restful.Response) {
|
||||
func (n *projectAPIInterface) listPipelineRuns(req *restful.Request, res *restful.Response) {
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
pipelineRuns, err := p.PipelineRunService.ListPipelineRuns(req.Request.Context(), pipeline)
|
||||
pipelineRuns, err := n.PipelineRunService.ListPipelineRuns(req.Request.Context(), pipeline)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("list pipeline runs failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -338,33 +390,65 @@ func (p *pipelineAPIInterface) listPipelineRuns(req *restful.Request, res *restf
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) getPipelineRun(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
|
||||
func (n *projectAPIInterface) getPipelineRun(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
|
||||
if err := res.WriteEntity(pipelineRun.PipelineRunBase); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) getPipelineRunStatus(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
|
||||
func (n *projectAPIInterface) getPipelineRunStatus(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
|
||||
if err := res.WriteEntity(pipelineRun.Status); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) getPipelineRunLog(req *restful.Request, res *restful.Response) {
|
||||
|
||||
func (n *projectAPIInterface) getPipelineRunLog(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
|
||||
logs, err := n.PipelineRunService.GetPipelineRunLog(req.Request.Context(), *pipelineRun, req.QueryParameter("step"))
|
||||
if err != nil {
|
||||
log.Logger.Errorf("get pipeline run log failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(logs); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) getPipelineRunOutput(req *restful.Request, res *restful.Response) {
|
||||
|
||||
func (n *projectAPIInterface) getPipelineRunOutput(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
|
||||
output, err := n.PipelineRunService.GetPipelineRunOutput(req.Request.Context(), *pipelineRun, req.QueryParameter("step"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(output); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) deletePipelineRun(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
|
||||
err := p.PipelineRunService.DeletePipelineRun(req.Request.Context(), pipelineRun.PipelineRunMeta)
|
||||
func (n *projectAPIInterface) getPipelineRunInput(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
|
||||
input, err := n.PipelineRunService.GetPipelineRunInput(req.Request.Context(), *pipelineRun, req.QueryParameter("step"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if err := res.WriteEntity(input); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) deletePipelineRun(req *restful.Request, res *restful.Response) {
|
||||
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
|
||||
err := n.PipelineRunService.DeletePipelineRun(req.Request.Context(), pipelineRun.PipelineRunMeta)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("delete pipeline run failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -376,11 +460,11 @@ func (p *pipelineAPIInterface) deletePipelineRun(req *restful.Request, res *rest
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) listContextValues(req *restful.Request, res *restful.Response) {
|
||||
func (n *projectAPIInterface) listContextValues(req *restful.Request, res *restful.Response) {
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
contextValues, err := p.ContextService.ListContexts(req.Request.Context(), pipeline.Project, pipeline.Name)
|
||||
contextValues, err := n.ContextService.ListContexts(req.Request.Context(), pipeline.Project.Name, pipeline.Name)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("list context values failure %s", err.Error())
|
||||
log.Logger.Errorf("list context values failure: %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
@@ -390,7 +474,7 @@ func (p *pipelineAPIInterface) listContextValues(req *restful.Request, res *rest
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) createContextValue(req *restful.Request, res *restful.Response) {
|
||||
func (n *projectAPIInterface) createContextValue(req *restful.Request, res *restful.Response) {
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
var createReq apis.CreateContextValuesRequest
|
||||
if err := req.ReadEntity(&createReq); err != nil {
|
||||
@@ -403,7 +487,7 @@ func (p *pipelineAPIInterface) createContextValue(req *restful.Request, res *res
|
||||
}
|
||||
|
||||
pipelineCtx := apis.Context(createReq)
|
||||
_, err := p.ContextService.CreateContext(req.Request.Context(), pipeline.Project, pipeline.Name, pipelineCtx)
|
||||
_, err := n.ContextService.CreateContext(req.Request.Context(), pipeline.Project.Name, pipeline.Name, pipelineCtx)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("create context failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -415,8 +499,8 @@ func (p *pipelineAPIInterface) createContextValue(req *restful.Request, res *res
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) updateContextValue(req *restful.Request, res *restful.Response) {
|
||||
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContex).(apis.Context)
|
||||
func (n *projectAPIInterface) updateContextValue(req *restful.Request, res *restful.Response) {
|
||||
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContext).(apis.Context)
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
var updateReq apis.UpdateContextValuesRequest
|
||||
if err := req.ReadEntity(&updateReq); err != nil {
|
||||
@@ -428,7 +512,7 @@ func (p *pipelineAPIInterface) updateContextValue(req *restful.Request, res *res
|
||||
return
|
||||
}
|
||||
pipelineCtx := apis.Context{Name: plCtx.Name, Values: updateReq.Values}
|
||||
_, err := p.ContextService.UpdateContext(req.Request.Context(), pipeline.Project, pipeline.Name, pipelineCtx)
|
||||
_, err := n.ContextService.UpdateContext(req.Request.Context(), pipeline.Project.Name, pipeline.Name, pipelineCtx)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("update context failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -440,10 +524,10 @@ func (p *pipelineAPIInterface) updateContextValue(req *restful.Request, res *res
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) deleteContextValue(req *restful.Request, res *restful.Response) {
|
||||
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContex).(apis.Context)
|
||||
func (n *projectAPIInterface) deleteContextValue(req *restful.Request, res *restful.Response) {
|
||||
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContext).(apis.Context)
|
||||
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
|
||||
err := p.ContextService.DeleteContext(req.Request.Context(), pipeline.Project, pipeline.Name, plCtx.Name)
|
||||
err := n.ContextService.DeleteContext(req.Request.Context(), pipeline.Project.Name, pipeline.Name, plCtx.Name)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("delete context failure %s", err.Error())
|
||||
bcode.ReturnError(req, res, err)
|
||||
@@ -455,8 +539,18 @@ func (p *pipelineAPIInterface) deleteContextValue(req *restful.Request, res *res
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) pipelineCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
pipeline, err := p.PipelineService.GetPipeline(req.Request.Context(), req.PathParameter("pipelineName"), req.QueryParameter("projectName"))
|
||||
func (n *projectAPIInterface) projectCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
project, err := n.ProjectService.GetProject(req.Request.Context(), req.PathParameter(Project))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyProject, project))
|
||||
chain.ProcessFilter(req, res)
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) pipelineCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
pipeline, err := n.PipelineService.GetPipeline(req.Request.Context(), req.PathParameter(Pipeline), false)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -465,8 +559,8 @@ func (p *pipelineAPIInterface) pipelineCheckFilter(req *restful.Request, res *re
|
||||
chain.ProcessFilter(req, res)
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) pipelineContextCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
contexts, err := p.ContextService.ListContexts(req.Request.Context(), req.PathParameter("pipelineName"), req.QueryParameter("projectName"))
|
||||
func (n *projectAPIInterface) pipelineContextCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
contexts, err := n.ContextService.ListContexts(req.Request.Context(), req.PathParameter(Project), req.PathParameter(Pipeline))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -477,20 +571,22 @@ func (p *pipelineAPIInterface) pipelineContextCheckFilter(req *restful.Request,
|
||||
bcode.ReturnError(req, res, bcode.ErrContextNotFound)
|
||||
return
|
||||
}
|
||||
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyPipelineContex, apis.Context{
|
||||
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyPipelineContext, apis.Context{
|
||||
Name: contextName,
|
||||
Values: contextValue,
|
||||
}))
|
||||
chain.ProcessFilter(req, res)
|
||||
}
|
||||
|
||||
func (p *pipelineAPIInterface) pipelineRunCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
func (n *projectAPIInterface) pipelineRunCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||
meta := apis.PipelineRunMeta{
|
||||
PipelineName: req.PathParameter(string(Pipeline)),
|
||||
Project: req.QueryParameter(string(Project)),
|
||||
PipelineRunName: req.PathParameter(string(PipelineRun)),
|
||||
PipelineName: req.PathParameter(Pipeline),
|
||||
Project: apis.NameAlias{
|
||||
Name: req.PathParameter(Project),
|
||||
},
|
||||
PipelineRunName: req.PathParameter(PipelineRun),
|
||||
}
|
||||
run, err := p.PipelineRunService.GetPipelineRun(req.Request.Context(), meta)
|
||||
run, err := n.PipelineRunService.GetPipelineRun(req.Request.Context(), meta)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
|
||||
@@ -29,10 +29,14 @@ import (
|
||||
)
|
||||
|
||||
type projectAPIInterface struct {
|
||||
RbacService service.RBACService `inject:""`
|
||||
ProjectService service.ProjectService `inject:""`
|
||||
TargetService service.TargetService `inject:""`
|
||||
ConfigService service.ConfigService `inject:""`
|
||||
RbacService service.RBACService `inject:""`
|
||||
ProjectService service.ProjectService `inject:""`
|
||||
TargetService service.TargetService `inject:""`
|
||||
ConfigService service.ConfigService `inject:""`
|
||||
PipelineService service.PipelineService `inject:""`
|
||||
PipelineRunService service.PipelineRunService `inject:""`
|
||||
ContextService service.ContextService `inject:""`
|
||||
RBACService service.RBACService `inject:""`
|
||||
}
|
||||
|
||||
// NewProjectAPIInterface new project APIInterface
|
||||
@@ -305,6 +309,7 @@ func (n *projectAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes(apis.ListTerraformProviderResponse{}))
|
||||
|
||||
initPipelineRoutes(ws, n)
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -33,3 +33,6 @@ var ErrDeleteEnvButAppExist = NewBcode(400, 11005, "env can't be deleted as app
|
||||
|
||||
// ErrEnvTargetConflict in one project, one target can only belong to one env
|
||||
var ErrEnvTargetConflict = NewBcode(400, 11006, "in one project, one target can only belong to one env.")
|
||||
|
||||
// ErrEnvTargetNotAllowDelete means can not remove existing targets from this environment, because there are applications deployed.
|
||||
var ErrEnvTargetNotAllowDelete = NewBcode(400, 11007, "target can not be deleted, because there are applications deployed.")
|
||||
|
||||
@@ -21,4 +21,22 @@ var (
|
||||
ErrContextNotFound = NewBcode(400, 17001, "pipeline context is not found")
|
||||
// ErrContextAlreadyExist means the certain context already exists
|
||||
ErrContextAlreadyExist = NewBcode(400, 17002, "pipeline context of pipeline already exist")
|
||||
// ErrGetPipelineInfo means failed to get pipeline info
|
||||
ErrGetPipelineInfo = NewBcode(400, 17003, "get pipeline info failed")
|
||||
// ErrPipelineNotExist means specific pipeline not found
|
||||
ErrPipelineNotExist = NewBcode(404, 17004, "failed to find log pods")
|
||||
// ErrGetPodsLogs means failed to get pods logs
|
||||
ErrGetPodsLogs = NewBcode(500, 17006, "failed to get pods logs")
|
||||
// ErrReadSourceLog means failed to read source log
|
||||
ErrReadSourceLog = NewBcode(500, 17007, "failed to read log from URL source")
|
||||
// ErrGetContextBackendData means failed to get context backend data
|
||||
ErrGetContextBackendData = NewBcode(500, 17008, "failed to get context backend data")
|
||||
// ErrNoSteps means pipeline doesn't have a step
|
||||
ErrNoSteps = NewBcode(400, 17009, "pipeline step number is zero")
|
||||
// ErrPipelineExist means the pipeline is exist
|
||||
ErrPipelineExist = NewBcode(400, 17010, "the pipeline is exist")
|
||||
// ErrPipelineRunFinished means pipeline run is finished
|
||||
ErrPipelineRunFinished = NewBcode(400, 17011, "pipeline run is finished")
|
||||
// ErrWrongMode means the pipeline run mode is wrong
|
||||
ErrWrongMode = NewBcode(400, 17012, "wrong pipeline run mode, only \"DAG\" and \"StepByStep\" are supported")
|
||||
)
|
||||
|
||||
@@ -452,15 +452,24 @@ func (p *Parser) loadWorkflowToAppfile(ctx context.Context, af *Appfile) error {
|
||||
Steps: workflowv1alpha1.WorkflowModeDAG,
|
||||
SubSteps: workflowv1alpha1.WorkflowModeDAG,
|
||||
}
|
||||
if wfSpec := af.app.Spec.Workflow; wfSpec != nil && len(wfSpec.Steps) > 0 {
|
||||
if wfSpec := af.app.Spec.Workflow; wfSpec != nil {
|
||||
app := af.app
|
||||
mode := wfSpec.Mode
|
||||
if wfSpec.Ref != "" && mode == nil {
|
||||
wf := &workflowv1alpha1.Workflow{}
|
||||
if err := af.WorkflowClient(p.client).Get(ctx, ktypes.NamespacedName{Namespace: af.app.Namespace, Name: app.Spec.Workflow.Ref}, wf); err != nil {
|
||||
return err
|
||||
}
|
||||
mode = wf.Mode
|
||||
}
|
||||
af.WorkflowSteps = wfSpec.Steps
|
||||
af.WorkflowMode.Steps = workflowv1alpha1.WorkflowModeStep
|
||||
if wfSpec.Mode != nil {
|
||||
if wfSpec.Mode.Steps != "" {
|
||||
af.WorkflowMode.Steps = wfSpec.Mode.Steps
|
||||
if mode != nil {
|
||||
if mode.Steps != "" {
|
||||
af.WorkflowMode.Steps = mode.Steps
|
||||
}
|
||||
if wfSpec.Mode.SubSteps != "" {
|
||||
af.WorkflowMode.SubSteps = wfSpec.Mode.SubSteps
|
||||
if mode.SubSteps != "" {
|
||||
af.WorkflowMode.SubSteps = mode.SubSteps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -222,7 +222,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
isUpdate := app.Status.Workflow.Message != "" && workflowInstance.Status.Message == ""
|
||||
workflowInstance.Status.Phase = workflowState
|
||||
app.Status.Workflow = workflow.ConvertWorkflowStatus(workflowInstance.Status, app.Status.Workflow.AppRevision)
|
||||
logCtx.Info("Workflow return state=%s", workflowState)
|
||||
logCtx.Info(fmt.Sprintf("Workflow return state=%s", workflowState))
|
||||
switch workflowState {
|
||||
case workflowv1alpha1.WorkflowStateSuspending:
|
||||
if duration := executor.GetSuspendBackoffWaitTime(); duration > 0 {
|
||||
|
||||
@@ -60,7 +60,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam/testutil"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow"
|
||||
)
|
||||
|
||||
// TODO: Refactor the tests to not copy and paste duplicated code 10 times
|
||||
@@ -1207,7 +1206,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationWorkflowSuspending))
|
||||
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(workflow.MessageSuspendFailedAfterRetries))
|
||||
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(wfTypes.MessageSuspendFailedAfterRetries))
|
||||
Expect(checkApp.Status.Workflow.Steps[1].Phase).Should(BeEquivalentTo(workflowv1alpha1.WorkflowStepPhaseFailed))
|
||||
Expect(checkApp.Status.Workflow.Steps[1].Reason).Should(BeEquivalentTo(wfTypes.StatusReasonFailedAfterRetries))
|
||||
|
||||
@@ -1327,7 +1326,7 @@ var _ = Describe("Test Application Controller", func() {
|
||||
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationWorkflowSuspending))
|
||||
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(workflow.MessageSuspendFailedAfterRetries))
|
||||
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(wfTypes.MessageSuspendFailedAfterRetries))
|
||||
Expect(checkApp.Status.Workflow.Steps[1].Phase).Should(BeEquivalentTo(workflowv1alpha1.WorkflowStepPhaseFailed))
|
||||
Expect(checkApp.Status.Workflow.Steps[1].Reason).Should(BeEquivalentTo(wfTypes.StatusReasonFailedAfterRetries))
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
Copyright 2021. The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/testutil"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Test Application with apply-once policy", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
initReplicas := int32(2)
|
||||
targetReplicas := int32(5)
|
||||
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apply-once-policy-test",
|
||||
},
|
||||
}
|
||||
|
||||
baseApp := &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "baseApp",
|
||||
Namespace: ns.Name,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "baseComp",
|
||||
Type: "worker",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
Traits: []common.ApplicationTrait{{
|
||||
Type: "scale",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"replicas": %d}`, initReplicas))},
|
||||
}},
|
||||
},
|
||||
},
|
||||
Policies: []v1beta1.AppPolicy{{
|
||||
Name: "basePolicy",
|
||||
Type: "apply-once",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"enable": true,"rules": [{"selector": { "resourceTypes": ["Deployment"] }, "strategy": {"affect":"%s", "path": ["spec.replicas"] }}]}`, ""))},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
worker := &v1beta1.ComponentDefinition{}
|
||||
workerCdDefJson, _ := yaml.YAMLToJSON([]byte(componentDefYaml))
|
||||
|
||||
scaleTrait := &v1beta1.TraitDefinition{}
|
||||
scaleTdDefJson, _ := yaml.YAMLToJSON([]byte(scaleTraitDefYaml))
|
||||
|
||||
BeforeEach(func() {
|
||||
Expect(k8sClient.Create(ctx, ns.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
Expect(json.Unmarshal(workerCdDefJson, worker)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, worker.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
Expect(json.Unmarshal(scaleTdDefJson, scaleTrait)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, scaleTrait.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
Context("Test Application with apply-once policy in different affect stage", func() {
|
||||
|
||||
It(" Affect not set or affect is empty , test effective globally", func() {
|
||||
app := baseApp.DeepCopy()
|
||||
app.SetName("apply-once-app-1")
|
||||
app.Spec.Components[0].Name = "apply-once-comp-1"
|
||||
|
||||
By("step 1. Create app , replicas: 2")
|
||||
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
|
||||
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
|
||||
|
||||
By("step 2. Update deployment to replicas: 5 ")
|
||||
Eventually(updateDeployReplicas(ctx, app, targetReplicas), time.Second*3, time.Microsecond*300).Should(BeNil())
|
||||
|
||||
By("step 3. Check OnUpdate, e.g. update app's component with new properties, replicas should be 5 ")
|
||||
for i := 0; i <= 3; i++ {
|
||||
properties := &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"cmd":["sleep","%d"],"image":"busybox"}`, i*1000))}
|
||||
Eventually(updateApp(ctx, app, properties), time.Second*3, time.Microsecond*300).Should(BeNil())
|
||||
testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
|
||||
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
|
||||
|
||||
deploy := new(v1.Deployment)
|
||||
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
|
||||
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
|
||||
Expect(*deploy.Spec.Replicas).Should(Equal(targetReplicas))
|
||||
}
|
||||
|
||||
By("step 4. Check OnStateKeep, replicas also should be 5 ")
|
||||
rk, err := resourcekeeper.NewResourceKeeper(context.Background(), k8sClient, app)
|
||||
Expect(err).Should(BeNil())
|
||||
for i := 0; i <= 3; i++ {
|
||||
// state keep :5
|
||||
Expect(rk.StateKeep(context.Background())).Should(BeNil())
|
||||
deploy := new(v1.Deployment)
|
||||
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
|
||||
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
|
||||
Expect(*deploy.Spec.Replicas).Should(Equal(targetReplicas))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
It("Affect: onStateKeep, test only effective when state keep", func() {
|
||||
|
||||
By("step 1. Create app , replicas: 2")
|
||||
app := baseApp.DeepCopy()
|
||||
app.SetName("apply-once-app-2")
|
||||
app.Spec.Components[0].Name = "apply-once-comp-2"
|
||||
app.Spec.Policies[0].Properties = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"enable": true,"rules": [{"selector": { "resourceTypes": ["Deployment"] }, "strategy": {"affect":"%s", "path": ["spec.replicas"] }}]}`, v1alpha1.ApplyOnceStrategyOnAppStateKeep))}
|
||||
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
|
||||
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
|
||||
|
||||
By("step 2. Update deployment, replicas: 5 ")
|
||||
Eventually(updateDeployReplicas(ctx, app, targetReplicas), time.Second*3, time.Microsecond*300).Should(BeNil())
|
||||
|
||||
By("step 3. Check OnStateKeep, replicas should be 5 ")
|
||||
rk, err := resourcekeeper.NewResourceKeeper(context.Background(), k8sClient, app)
|
||||
Expect(err).Should(BeNil())
|
||||
for i := 0; i <= 3; i++ {
|
||||
// state keep : use newest replicas
|
||||
Expect(rk.StateKeep(context.Background())).Should(BeNil())
|
||||
deploy := new(v1.Deployment)
|
||||
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
|
||||
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
|
||||
Expect(*deploy.Spec.Replicas).Should(Equal(targetReplicas))
|
||||
}
|
||||
By("step 4. Check OnUpdate, e.g. update app's component with new properties, replicas should be 2 ")
|
||||
for i := 0; i <= 3; i++ {
|
||||
// onupdate: not use newest replicas
|
||||
properties := &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"cmd":["sleep","%d"],"image":"busybox"}`, i*1000))}
|
||||
Eventually(updateApp(ctx, app, properties), time.Second*3, time.Microsecond*300).Should(BeNil())
|
||||
testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
|
||||
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
|
||||
deploy := new(v1.Deployment)
|
||||
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
|
||||
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
|
||||
Expect(*deploy.Spec.Replicas).Should(Equal(initReplicas))
|
||||
}
|
||||
})
|
||||
|
||||
It("Affect: onUpdate , test only effective when updating the app", func() {
|
||||
|
||||
By("step 1. Create app , replicas: 2")
|
||||
app := baseApp.DeepCopy()
|
||||
app.SetName("apply-once-app-3")
|
||||
app.Spec.Components[0].Name = "apply-once-comp-3"
|
||||
app.Spec.Policies[0].Properties = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"enable": true,"rules": [{"selector": { "resourceTypes": ["Deployment"] }, "strategy": {"affect":"%s", "path": ["spec.replicas"] }}]}`, v1alpha1.ApplyOnceStrategyOnAppUpdate))}
|
||||
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
|
||||
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
|
||||
|
||||
By("step 2. Update deployment, replicas: 5 ")
|
||||
Eventually(updateDeployReplicas(ctx, app, targetReplicas), time.Second*3, time.Microsecond*300).Should(BeNil())
|
||||
|
||||
By("step 3. Check OnUpdate, e.g. update app's component with new properties, replicas should be 5 ")
|
||||
for i := 0; i <= 3; i++ {
|
||||
// onUpdate : use newest replicas
|
||||
properties := &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"cmd":["sleep","%d"],"image":"busybox"}`, i*1000))}
|
||||
Eventually(updateApp(ctx, app, properties), time.Second*3, time.Microsecond*300).Should(BeNil())
|
||||
testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
|
||||
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
|
||||
deploy := new(v1.Deployment)
|
||||
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
|
||||
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
|
||||
Expect(*deploy.Spec.Replicas).Should(Equal(initReplicas))
|
||||
}
|
||||
|
||||
By("step 4. Check OnStateKeep, replicas should be 2 ")
|
||||
rk, err := resourcekeeper.NewResourceKeeper(context.Background(), k8sClient, app)
|
||||
Expect(err).Should(BeNil())
|
||||
for i := 0; i <= 3; i++ {
|
||||
// state keep : not use newest replicas
|
||||
Expect(rk.StateKeep(context.Background())).Should(BeNil())
|
||||
deploy := new(v1.Deployment)
|
||||
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
|
||||
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
|
||||
Expect(*deploy.Spec.Replicas).Should(Equal(initReplicas))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func updateDeployReplicas(ctx context.Context, app *v1beta1.Application, targetReplicas int32) func() error {
|
||||
return func() error {
|
||||
deploy := new(v1.Deployment)
|
||||
deployKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
|
||||
Expect(k8sClient.Get(ctx, deployKey, deploy)).Should(BeNil())
|
||||
deploy.Spec.Replicas = &targetReplicas
|
||||
return k8sClient.Update(ctx, deploy)
|
||||
}
|
||||
}
|
||||
|
||||
func waitAppRunning(ctx context.Context, app *v1beta1.Application) func() error {
|
||||
return func() error {
|
||||
appV1 := new(v1beta1.Application)
|
||||
_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(app), appV1); err != nil {
|
||||
return err
|
||||
}
|
||||
if appV1.Status.Phase != common.ApplicationRunning {
|
||||
return errors.New("app is not in running status")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateApp(ctx context.Context, app *v1beta1.Application, properties *runtime.RawExtension) func() error {
|
||||
return func() error {
|
||||
oldApp := new(v1beta1.Application)
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(app), oldApp)).Should(BeNil())
|
||||
newApp := oldApp.DeepCopy()
|
||||
newApp.Spec.Components[0].Properties = properties
|
||||
return k8sClient.Update(ctx, newApp)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
scaleTraitDefYaml = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Manually scale K8s pod for your workload which follows the pod spec in path 'spec.template'.
|
||||
name: scale
|
||||
namespace: vela-system
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- deployments.apps
|
||||
- statefulsets.apps
|
||||
podDisruptive: false
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
parameter: {
|
||||
// +usage=Specify the number of workload
|
||||
replicas: *1 | int
|
||||
}
|
||||
// +patchStrategy=retainKeys
|
||||
patch: spec: replicas: parameter.replicas
|
||||
`
|
||||
)
|
||||
@@ -23,6 +23,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
|
||||
errors2 "github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -32,12 +36,6 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
@@ -51,7 +49,7 @@ const (
|
||||
|
||||
var (
|
||||
// ClusterGatewaySecretNamespace the namespace where cluster-gateway secret locates
|
||||
ClusterGatewaySecretNamespace string
|
||||
ClusterGatewaySecretNamespace = "vela-system"
|
||||
)
|
||||
|
||||
// ClusterNameInContext extract cluster name from context
|
||||
@@ -174,16 +172,6 @@ func UpgradeExistingClusterSecret(ctx context.Context, c client.Client) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMulticlusterKubernetesClient get client with multicluster function enabled
|
||||
func GetMulticlusterKubernetesClient() (client.Client, *rest.Config, error) {
|
||||
k8sConfig, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
k8sClient, err := Initialize(k8sConfig, false)
|
||||
return k8sClient, k8sConfig, err
|
||||
}
|
||||
|
||||
// ListExistingClusterSecrets list existing cluster secrets
|
||||
func ListExistingClusterSecrets(ctx context.Context, c client.Client) ([]v1.Secret, error) {
|
||||
secrets := &v1.SecretList{}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
@@ -141,6 +142,10 @@ func (h *resourceKeeper) dispatch(ctx context.Context, manifests []*unstructured
|
||||
if h.isShared(manifest) {
|
||||
ao = append([]apply.ApplyOption{apply.SharedByApp(h.app)}, ao...)
|
||||
}
|
||||
manifest, err := ApplyStrategies(applyCtx, h, manifest, v1alpha1.ApplyOnceStrategyOnAppUpdate)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to apply once policy for application %s,%s", h.app.Name, err.Error())
|
||||
}
|
||||
return h.applicator.Apply(applyCtx, manifest, ao...)
|
||||
}, manifests, MaxDispatchConcurrent)
|
||||
return velaerrors.AggregateErrors(errs.([]error))
|
||||
|
||||
@@ -24,6 +24,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"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/pkg/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
@@ -60,7 +63,7 @@ func (h *resourceKeeper) StateKeep(ctx context.Context) error {
|
||||
return errors.Wrapf(err, "failed to decode resource %s from resourcetracker", mr.ResourceKey())
|
||||
}
|
||||
applyCtx := multicluster.ContextWithClusterName(ctx, mr.Cluster)
|
||||
manifest, err = ApplyStrategies(applyCtx, h, manifest)
|
||||
manifest, err = ApplyStrategies(applyCtx, h, manifest, v1alpha1.ApplyOnceStrategyOnAppStateKeep)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to apply once resource %s from resourcetracker %s", mr.ResourceKey(), rt.Name)
|
||||
}
|
||||
@@ -79,34 +82,49 @@ func (h *resourceKeeper) StateKeep(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// ApplyStrategies will generate manifest with applyOnceStrategy
|
||||
func ApplyStrategies(ctx context.Context, h *resourceKeeper, manifest *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
func ApplyStrategies(ctx context.Context, h *resourceKeeper, manifest *unstructured.Unstructured, matchedAffectStage v1alpha1.ApplyOnceAffectStrategy) (*unstructured.Unstructured, error) {
|
||||
if h.applyOncePolicy == nil {
|
||||
return manifest, nil
|
||||
}
|
||||
applyOncePath := h.applyOncePolicy.FindStrategy(manifest)
|
||||
if applyOncePath != nil {
|
||||
un := new(unstructured.Unstructured)
|
||||
un.SetAPIVersion(manifest.GetAPIVersion())
|
||||
un.SetKind(manifest.GetKind())
|
||||
err := h.Get(ctx, types.NamespacedName{Name: manifest.GetName(), Namespace: manifest.GetNamespace()}, un)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, path := range applyOncePath.Path {
|
||||
if path == "*" {
|
||||
manifest = un.DeepCopy()
|
||||
break
|
||||
}
|
||||
value, err := fieldpath.Pave(un.UnstructuredContent()).GetValue(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = fieldpath.Pave(manifest.UnstructuredContent()).SetValue(path, value)
|
||||
strategy := h.applyOncePolicy.FindStrategy(manifest)
|
||||
if strategy != nil {
|
||||
affectStage := strategy.ApplyOnceAffectStrategy
|
||||
if shouldMerge(affectStage, matchedAffectStage) {
|
||||
un := new(unstructured.Unstructured)
|
||||
un.SetAPIVersion(manifest.GetAPIVersion())
|
||||
un.SetKind(manifest.GetKind())
|
||||
err := h.Get(ctx, types.NamespacedName{Name: manifest.GetName(), Namespace: manifest.GetNamespace()}, un)
|
||||
if err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return manifest, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return mergeValue(strategy.Path, manifest, un)
|
||||
}
|
||||
|
||||
}
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
func shouldMerge(affectStage v1alpha1.ApplyOnceAffectStrategy, matchedAffectType v1alpha1.ApplyOnceAffectStrategy) bool {
|
||||
return affectStage == "" || affectStage == v1alpha1.ApplyOnceStrategyAlways || affectStage == matchedAffectType
|
||||
}
|
||||
|
||||
func mergeValue(paths []string, manifest *unstructured.Unstructured, un *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
for _, path := range paths {
|
||||
if path == "*" {
|
||||
manifest = un.DeepCopy()
|
||||
break
|
||||
}
|
||||
value, err := fieldpath.Pave(un.UnstructuredContent()).GetValue(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = fieldpath.Pave(manifest.UnstructuredContent()).SetValue(path, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return manifest, nil
|
||||
}
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#ApplyComponent: {
|
||||
#provider: "oam"
|
||||
#do: "component-apply"
|
||||
cluster: *"" | string
|
||||
env: *"" | string
|
||||
namespace: *"" | string
|
||||
#provider: "oam"
|
||||
#do: "component-apply"
|
||||
|
||||
// +usage=The cluster to use
|
||||
cluster: *"" | string
|
||||
// +usage=The env to use
|
||||
env: *"" | string
|
||||
// +usage=The namespace to apply
|
||||
namespace: *"" | string
|
||||
// +usage=Whether to wait healthy of the applied component
|
||||
waitHealthy: *true | bool
|
||||
// +usage=The value of the component resource
|
||||
value: {...}
|
||||
// +usage=The patcher that will be applied to the resource, you can define the strategy of list merge through comments. Reference doc here: https://kubevela.io/docs/platform-engineers/traits/patch-trait#patch-in-workflow-step
|
||||
patch?: {...}
|
||||
...
|
||||
}
|
||||
@@ -26,6 +33,11 @@
|
||||
#LoadComponets: {
|
||||
#provider: "oam"
|
||||
#do: "load"
|
||||
|
||||
// +usage=If specify `app`, use specified application to load its component resources otherwise use current application
|
||||
app?: string
|
||||
// +usage=The value of the components will be filled in this field after the action is executed, you can use value[componentName] to refer a specified component
|
||||
value?: {...}
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package common
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
"k8s.io/client-go/discovery"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -86,7 +87,9 @@ func (a *Args) GetClient() (client.Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
newClient, err := client.New(a.config, client.Options{Scheme: a.Schema})
|
||||
newClient, err := pkgmulticluster.NewClient(a.config,
|
||||
pkgmulticluster.ClientOptions{
|
||||
Options: client.Options{Scheme: a.Schema}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
141
pkg/utils/k8s.go
141
pkg/utils/k8s.go
@@ -21,7 +21,19 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wercker/stern/stern"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
|
||||
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -78,10 +90,11 @@ func MergeNoConflictLabels(labels map[string]string) MutateOption {
|
||||
// It will report an error if the labels conflict while it will override the annotations
|
||||
func CreateOrUpdateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error {
|
||||
err := CreateNamespace(ctx, kubeClient, name, options...)
|
||||
if err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return err
|
||||
// only if namespace don't have the env label that we need to update it
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
return UpdateNamespace(ctx, kubeClient, name, options...)
|
||||
}
|
||||
return UpdateNamespace(ctx, kubeClient, name, options...)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateNamespace will create a namespace with mutate option
|
||||
@@ -198,3 +211,125 @@ func IsClusterScope(gvk schema.GroupVersionKind, mapper meta.RESTMapper) (bool,
|
||||
isClusterScope := len(mappings) > 0 && mappings[0].Scope.Name() == meta.RESTScopeNameRoot
|
||||
return isClusterScope, err
|
||||
}
|
||||
|
||||
// GetPodsLogs get logs from pods
|
||||
func GetPodsLogs(ctx context.Context, config *rest.Config, containerName string, selectPods []*querytypes.PodBase, tmpl string, logC chan<- string, tailLines *int64) error {
|
||||
if err := verifyPods(selectPods); err != nil {
|
||||
return err
|
||||
}
|
||||
podRegex := getPodRegex(selectPods)
|
||||
pods, err := regexp.Compile(podRegex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to compile '%s' for logs query", podRegex)
|
||||
}
|
||||
container := regexp.MustCompile(".*")
|
||||
if containerName != "" {
|
||||
container = regexp.MustCompile(containerName + ".*")
|
||||
}
|
||||
// These pods are from the same namespace, so we can use the first one to get the namespace
|
||||
namespace := selectPods[0].Metadata.Namespace
|
||||
selector := labels.NewSelector()
|
||||
// Only use the labels to select pod if query one pod's log. It is only used when query vela-core log
|
||||
if len(selectPods) == 1 {
|
||||
for k, v := range selectPods[0].Metadata.Labels {
|
||||
req, _ := labels.NewRequirement(k, selection.Equals, []string{v})
|
||||
if req != nil {
|
||||
selector = selector.Add(*req)
|
||||
}
|
||||
}
|
||||
}
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
added, removed, err := stern.Watch(ctx,
|
||||
clientSet.CoreV1().Pods(namespace),
|
||||
pods,
|
||||
container,
|
||||
nil,
|
||||
[]stern.ContainerState{stern.RUNNING, stern.TERMINATED},
|
||||
selector,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tails := make(map[string]*stern.Tail)
|
||||
|
||||
funs := map[string]interface{}{
|
||||
"json": func(in interface{}) (string, error) {
|
||||
b, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
},
|
||||
"color": func(color color.Color, text string) string {
|
||||
return color.SprintFunc()(text)
|
||||
},
|
||||
}
|
||||
template, err := template.New("log").Funcs(funs).Parse(tmpl)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse template")
|
||||
}
|
||||
|
||||
go func() {
|
||||
for p := range added {
|
||||
id := p.GetID()
|
||||
if tails[id] != nil {
|
||||
continue
|
||||
}
|
||||
// 48h
|
||||
dur, _ := time.ParseDuration("48h")
|
||||
tail := stern.NewTail(p.Namespace, p.Pod, p.Container, template, &stern.TailOptions{
|
||||
Timestamps: true,
|
||||
SinceSeconds: int64(dur.Seconds()),
|
||||
Exclude: nil,
|
||||
Include: nil,
|
||||
Namespace: false,
|
||||
TailLines: tailLines, // default for all logs
|
||||
})
|
||||
tails[id] = tail
|
||||
|
||||
tail.Start(ctx, clientSet.CoreV1().Pods(p.Namespace), logC)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for p := range removed {
|
||||
id := p.GetID()
|
||||
if tails[id] == nil {
|
||||
continue
|
||||
}
|
||||
tails[id].Close()
|
||||
delete(tails, id)
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
close(logC)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPodRegex(pods []*querytypes.PodBase) string {
|
||||
var podNames []string
|
||||
for _, pod := range pods {
|
||||
podNames = append(podNames, fmt.Sprintf("(%s.*)", pod.Metadata.Name))
|
||||
}
|
||||
return strings.Join(podNames, "|")
|
||||
}
|
||||
|
||||
func verifyPods(pods []*querytypes.PodBase) error {
|
||||
if len(pods) == 0 {
|
||||
return errors.New("no pods selected")
|
||||
}
|
||||
if len(pods) == 1 {
|
||||
return nil
|
||||
}
|
||||
namespace := pods[0].Metadata.Namespace
|
||||
for _, pod := range pods {
|
||||
if pod.Metadata.Namespace != namespace {
|
||||
return errors.New("cannot select pods from different namespaces")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
@@ -60,7 +60,11 @@ var _ = BeforeSuite(func(done Done) {
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
|
||||
scheme := common.Scheme
|
||||
batchv1.AddToScheme(scheme)
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
@@ -206,6 +206,15 @@ func init() {
|
||||
DefaultGenListOptionFunc: kustomization2AnyListOption,
|
||||
DisableFilterByOwnerReference: true,
|
||||
},
|
||||
ChildrenResourcesRule{
|
||||
SubResources: buildSubResources([]*SubResourceSelector{
|
||||
{
|
||||
ResourceType: ResourceType{APIVersion: "v1", Kind: "Pod"},
|
||||
listOptions: cronJobLabelListOption,
|
||||
},
|
||||
}),
|
||||
GroupResourceType: GroupResourceType{Group: "batch/v1", Kind: "CronJob"},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -292,9 +301,9 @@ type WorkloadUnstructured struct {
|
||||
unstructured.Unstructured
|
||||
}
|
||||
|
||||
// GetSelector get the selector from the field path: spec.selector
|
||||
func (w *WorkloadUnstructured) GetSelector() (labels.Selector, error) {
|
||||
value, exist, err := unstructured.NestedFieldNoCopy(w.Object, "spec", "selector")
|
||||
// GetSelector get the selector from the field path
|
||||
func (w *WorkloadUnstructured) GetSelector(fields ...string) (labels.Selector, error) {
|
||||
value, exist, err := unstructured.NestedFieldNoCopy(w.Object, fields...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -311,9 +320,27 @@ func (w *WorkloadUnstructured) GetSelector() (labels.Selector, error) {
|
||||
return labels.Everything(), nil
|
||||
}
|
||||
|
||||
func (w *WorkloadUnstructured) convertLabel2Selector(fields ...string) (labels.Selector, error) {
|
||||
value, exist, err := unstructured.NestedFieldNoCopy(w.Object, fields...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return labels.Everything(), nil
|
||||
}
|
||||
if v, ok := value.(map[string]interface{}); ok {
|
||||
var selector v1.LabelSelector
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(v, &selector.MatchLabels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v1.LabelSelectorAsSelector(&selector)
|
||||
}
|
||||
return labels.Everything(), nil
|
||||
}
|
||||
|
||||
var defaultWorkloadLabelListOption genListOptionFunc = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
||||
workload := WorkloadUnstructured{obj}
|
||||
deploySelector, err := workload.GetSelector()
|
||||
deploySelector, err := workload.GetSelector("spec", "selector")
|
||||
if err != nil {
|
||||
return client.ListOptions{}, err
|
||||
}
|
||||
@@ -333,6 +360,15 @@ var service2EndpointListOption = func(obj unstructured.Unstructured) (client.Lis
|
||||
return client.ListOptions{Namespace: svc.Namespace, LabelSelector: stsSelector}, nil
|
||||
}
|
||||
|
||||
var cronJobLabelListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
||||
workload := WorkloadUnstructured{obj}
|
||||
cronJobSelector, err := workload.convertLabel2Selector("spec", "jobTemplate", "metadata", "labels")
|
||||
if err != nil {
|
||||
return client.ListOptions{}, err
|
||||
}
|
||||
return client.ListOptions{Namespace: obj.GetNamespace(), LabelSelector: cronJobSelector}, nil
|
||||
}
|
||||
|
||||
var helmRelease2AnyListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
|
||||
hrSelector, err := v1.LabelSelectorAsSelector(&v1.LabelSelector{MatchLabels: map[string]string{
|
||||
"helm.toolkit.fluxcd.io/name": obj.GetName(),
|
||||
|
||||
@@ -28,10 +28,13 @@ import (
|
||||
"github.com/fluxcd/source-controller/api/v1beta2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v12 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
||||
types2 "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -201,6 +204,91 @@ func TestService2EndpointOption(t *testing.T) {
|
||||
assert.Equal(t, "service-name=test,uid=test-uid", l.LabelSelector.String())
|
||||
}
|
||||
|
||||
func TestConvertLabel2Selector(t *testing.T) {
|
||||
cronJob1 := `
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: cronjob1
|
||||
labels:
|
||||
app: cronjob1
|
||||
spec:
|
||||
schedule: "* * * * *"
|
||||
jobTemplate:
|
||||
metadata:
|
||||
labels:
|
||||
app: cronJob1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: cronjob
|
||||
image: busybox
|
||||
command: ["/bin/sh","-c","date"]
|
||||
restartPolicy: Never
|
||||
`
|
||||
obj := unstructured.Unstructured{}
|
||||
dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
_, _, err := dec.Decode([]byte(cronJob1), nil, &obj)
|
||||
assert.NoError(t, err)
|
||||
workload1 := WorkloadUnstructured{obj}
|
||||
selector1, err := workload1.convertLabel2Selector("not", "exist")
|
||||
assert.Equal(t, selector1, labels.Everything())
|
||||
assert.NoError(t, err)
|
||||
|
||||
selector2, err := workload1.convertLabel2Selector()
|
||||
assert.Equal(t, selector2, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, _, err = dec.Decode([]byte(cronJob1), nil, &obj)
|
||||
assert.NoError(t, err)
|
||||
workload2 := WorkloadUnstructured{obj}
|
||||
selector3, err := workload2.convertLabel2Selector("apiVersion")
|
||||
assert.Equal(t, selector3, labels.Everything())
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, _, err = dec.Decode([]byte(cronJob1), nil, &obj)
|
||||
assert.NoError(t, err)
|
||||
workload3 := WorkloadUnstructured{obj}
|
||||
selector4, err := workload3.convertLabel2Selector("spec", "jobTemplate", "metadata", "labels")
|
||||
assert.Equal(t, selector4.String(), "app=cronJob1")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCronJobLabelListOption(t *testing.T) {
|
||||
cronJob := `
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: cronjob1
|
||||
labels:
|
||||
app: cronjob1
|
||||
spec:
|
||||
schedule: "* * * * *"
|
||||
jobTemplate:
|
||||
metadata:
|
||||
labels:
|
||||
app: cronJob1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: cronjob
|
||||
image: busybox
|
||||
command: ["/bin/sh","-c","date"]
|
||||
restartPolicy: Never
|
||||
`
|
||||
|
||||
// convert yaml to unstructured
|
||||
obj := unstructured.Unstructured{}
|
||||
dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
_, _, err := dec.Decode([]byte(cronJob), nil, &obj)
|
||||
assert.NoError(t, err)
|
||||
l, err := cronJobLabelListOption(obj)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "app=cronJob1", l.LabelSelector.String())
|
||||
}
|
||||
|
||||
func TestServiceStatus(t *testing.T) {
|
||||
lbHealthSvc := v1.Service{Spec: v1.ServiceSpec{Type: v1.ServiceTypeLoadBalancer}, Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
@@ -1212,9 +1300,66 @@ var _ = Describe("unit-test to e2e test", func() {
|
||||
},
|
||||
},
|
||||
}
|
||||
pod5 := v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod5",
|
||||
Namespace: "test-namespace",
|
||||
Labels: map[string]string{
|
||||
"app": "cronJob1",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Image: "nginx",
|
||||
Name: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
cronJob1 := batchv1.CronJob{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "batch/v1",
|
||||
Kind: "CronJob",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cronjob1",
|
||||
Namespace: "test-namespace",
|
||||
Labels: map[string]string{
|
||||
"app": "cronJob1",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "* * * * *",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "cronJob1",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
RestartPolicy: "OnFailure",
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Image: "nginx",
|
||||
Name: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var objectList []client.Object
|
||||
objectList = append(objectList, &deploy1, &deploy1, &rs1, &rs2, &rs3, &rs4, &pod1, &pod2, &pod3, &rs4, &pod4)
|
||||
objectList = append(objectList, &deploy1, &deploy1, &rs1, &rs2, &rs3, &rs4, &pod1, &pod2, &pod3, &rs4, &pod4, &pod5, &cronJob1)
|
||||
BeforeEach(func() {
|
||||
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, deploy1.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
@@ -1225,6 +1370,7 @@ var _ = Describe("unit-test to e2e test", func() {
|
||||
Expect(k8sClient.Create(ctx, pod1.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, pod2.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, pod3.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, pod5.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
|
||||
cRs4 := rs4.DeepCopy()
|
||||
Expect(k8sClient.Create(ctx, cRs4)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
@@ -1238,6 +1384,7 @@ var _ = Describe("unit-test to e2e test", func() {
|
||||
},
|
||||
})
|
||||
Expect(k8sClient.Create(ctx, cPod4)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, cronJob1.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@@ -1285,6 +1432,18 @@ var _ = Describe("unit-test to e2e test", func() {
|
||||
nil, nil, true)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(items3)).Should(BeEquivalentTo(1))
|
||||
|
||||
u4 := unstructured.Unstructured{}
|
||||
u4.SetNamespace(cronJob1.Namespace)
|
||||
u4.SetName(cronJob1.Name)
|
||||
u4.SetAPIVersion("batch/v1")
|
||||
u4.SetKind("CronJob")
|
||||
Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: u4.GetNamespace(), Name: u4.GetName()}, &u4))
|
||||
Expect(err).Should(BeNil())
|
||||
item4, err := listItemByRule(ctx, k8sClient, ResourceType{APIVersion: "v1", Kind: "Pod"}, u4,
|
||||
cronJobLabelListOption, nil, true)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(item4)).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
It("iterate resource", func() {
|
||||
|
||||
@@ -22,9 +22,12 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
wfUtils "github.com/kubevela/workflow/pkg/utils"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
|
||||
@@ -36,27 +39,24 @@ import (
|
||||
errors3 "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
)
|
||||
|
||||
// WorkflowOperator is opratior handler for workflow's resume/rollback/restart
|
||||
type WorkflowOperator interface {
|
||||
Suspend(ctx context.Context, app *v1beta1.Application) error
|
||||
Resume(ctx context.Context, app *v1beta1.Application) error
|
||||
Rollback(ctx context.Context, app *v1beta1.Application) error
|
||||
Restart(ctx context.Context, app *v1beta1.Application) error
|
||||
Terminate(ctx context.Context, app *v1beta1.Application) error
|
||||
// NewApplicationWorkflowOperator get an workflow operator with k8sClient, ioWriter(optional, useful for cli) and application
|
||||
func NewApplicationWorkflowOperator(cli client.Client, w io.Writer, app *v1beta1.Application) wfUtils.WorkflowOperator {
|
||||
return appWorkflowOperator{
|
||||
cli: cli,
|
||||
outputWriter: w,
|
||||
application: app,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWorkflowOperator get an workflow operator with k8sClient and ioWriter(optional, useful for cli)
|
||||
func NewWorkflowOperator(cli client.Client, w io.Writer) WorkflowOperator {
|
||||
return wfOperator{cli: cli, outputWriter: w}
|
||||
}
|
||||
|
||||
type wfOperator struct {
|
||||
type appWorkflowOperator struct {
|
||||
cli client.Client
|
||||
outputWriter io.Writer
|
||||
application *v1beta1.Application
|
||||
}
|
||||
|
||||
// Suspend a running workflow
|
||||
func (wo wfOperator) Suspend(ctx context.Context, app *v1beta1.Application) error {
|
||||
func (wo appWorkflowOperator) Suspend(ctx context.Context) error {
|
||||
app := wo.application
|
||||
if app.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application is not running")
|
||||
}
|
||||
@@ -80,7 +80,8 @@ func (wo wfOperator) Suspend(ctx context.Context, app *v1beta1.Application) erro
|
||||
}
|
||||
|
||||
// Resume a suspending workflow
|
||||
func (wo wfOperator) Resume(ctx context.Context, app *v1beta1.Application) error {
|
||||
func (wo appWorkflowOperator) Resume(ctx context.Context) error {
|
||||
app := wo.application
|
||||
if app.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application is not running")
|
||||
}
|
||||
@@ -108,9 +109,28 @@ func (wo wfOperator) Resume(ctx context.Context, app *v1beta1.Application) error
|
||||
|
||||
// Rollback a running in middle state workflow.
|
||||
// nolint
|
||||
func (wo wfOperator) Rollback(ctx context.Context, app *v1beta1.Application) error {
|
||||
func (wo appWorkflowOperator) Rollback(ctx context.Context) error {
|
||||
app := wo.application
|
||||
if app.Status.Workflow != nil && !app.Status.Workflow.Terminated && !app.Status.Workflow.Suspend && !app.Status.Workflow.Finished {
|
||||
return fmt.Errorf("can not rollback a running workflow")
|
||||
}
|
||||
if oam.GetPublishVersion(app) == "" {
|
||||
return fmt.Errorf("app without public version cannot rollback")
|
||||
if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
|
||||
return fmt.Errorf("the latest revision is not set: %s", app.Name)
|
||||
}
|
||||
// get the last revision
|
||||
revision := &v1beta1.ApplicationRevision{}
|
||||
if err := wo.cli.Get(ctx, types.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
|
||||
return fmt.Errorf("failed to get the latest revision: %w", err)
|
||||
}
|
||||
|
||||
app.Spec = revision.Spec.Application.Spec
|
||||
if err := wo.cli.Status().Update(ctx, app); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
appRevs, err := application.GetSortedAppRevisions(ctx, wo.cli, app.Name, app.Namespace)
|
||||
@@ -249,7 +269,8 @@ func (wo wfOperator) Rollback(ctx context.Context, app *v1beta1.Application) err
|
||||
}
|
||||
|
||||
// Restart a terminated or finished workflow.
|
||||
func (wo wfOperator) Restart(ctx context.Context, app *v1beta1.Application) error {
|
||||
func (wo appWorkflowOperator) Restart(ctx context.Context) error {
|
||||
app := wo.application
|
||||
if app.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application is not running")
|
||||
}
|
||||
@@ -263,7 +284,8 @@ func (wo wfOperator) Restart(ctx context.Context, app *v1beta1.Application) erro
|
||||
return wo.writeOutputF("Successfully restart workflow: %s\n", app.Name)
|
||||
}
|
||||
|
||||
func (wo wfOperator) Terminate(ctx context.Context, app *v1beta1.Application) error {
|
||||
func (wo appWorkflowOperator) Terminate(ctx context.Context) error {
|
||||
app := wo.application
|
||||
if err := service.TerminateWorkflow(context.TODO(), wo.cli, app); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -271,7 +293,7 @@ func (wo wfOperator) Terminate(ctx context.Context, app *v1beta1.Application) er
|
||||
return wo.writeOutputF("Successfully terminate workflow: %s\n", app.Name)
|
||||
}
|
||||
|
||||
func (wo wfOperator) writeOutput(str string) error {
|
||||
func (wo appWorkflowOperator) writeOutput(str string) error {
|
||||
if wo.outputWriter == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -279,7 +301,7 @@ func (wo wfOperator) writeOutput(str string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (wo wfOperator) writeOutputF(format string, a ...interface{}) error {
|
||||
func (wo appWorkflowOperator) writeOutputF(format string, a ...interface{}) error {
|
||||
if wo.outputWriter == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -48,10 +48,10 @@ var _ = Describe("Kruise rollout test", func() {
|
||||
StartTime: metav1.Now(),
|
||||
}
|
||||
Expect(k8sClient.Status().Update(ctx, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(operator.Suspend(ctx, checkApp.DeepCopy())).Should(BeNil())
|
||||
Expect(operator.Suspend(ctx)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow.Suspend).Should(BeEquivalentTo(true))
|
||||
@@ -60,8 +60,8 @@ var _ = Describe("Kruise rollout test", func() {
|
||||
It("Resume workflow", func() {
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Resume(ctx, &checkApp)).Should(BeNil())
|
||||
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
|
||||
Expect(operator.Resume(ctx)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow.Suspend).Should(BeEquivalentTo(false))
|
||||
@@ -70,8 +70,8 @@ var _ = Describe("Kruise rollout test", func() {
|
||||
It("Terminate workflow", func() {
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Terminate(ctx, &checkApp)).Should(BeNil())
|
||||
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
|
||||
Expect(operator.Terminate(ctx)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow.Terminated).Should(BeEquivalentTo(true))
|
||||
@@ -80,8 +80,8 @@ var _ = Describe("Kruise rollout test", func() {
|
||||
It("Restart workflow", func() {
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Restart(ctx, &checkApp)).Should(BeNil())
|
||||
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
|
||||
Expect(operator.Restart(ctx)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow).Should(BeNil())
|
||||
@@ -99,8 +99,8 @@ var _ = Describe("Kruise rollout test", func() {
|
||||
checkApp.Annotations = map[string]string{
|
||||
oam.AnnotationPublishVersion: "v2",
|
||||
}
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Rollback(ctx, checkApp.DeepCopy())).Should(BeNil())
|
||||
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
|
||||
Expect(operator.Rollback(ctx)).Should(BeNil())
|
||||
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
wfTypes "github.com/kubevela/workflow/pkg/types"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -30,16 +31,9 @@ var (
|
||||
DisableRecorder = false
|
||||
)
|
||||
|
||||
const (
|
||||
// MessageTerminatedFailedAfterRetries is the message of failed after retries
|
||||
MessageTerminatedFailedAfterRetries = "The workflow terminates automatically because the failed times of steps have reached the limit"
|
||||
// MessageSuspendFailedAfterRetries is the message of failed after retries
|
||||
MessageSuspendFailedAfterRetries = "The workflow suspends automatically because the failed times of steps have reached the limit"
|
||||
)
|
||||
|
||||
// IsFailedAfterRetry check if application is hang due to FailedAfterRetry
|
||||
func IsFailedAfterRetry(app *oamcore.Application) bool {
|
||||
return app.Status.Workflow != nil && (app.Status.Workflow.Message == MessageTerminatedFailedAfterRetries || app.Status.Workflow.Message == MessageSuspendFailedAfterRetries)
|
||||
return app.Status.Workflow != nil && app.Status.Workflow.Message == wfTypes.MessageSuspendFailedAfterRetries
|
||||
}
|
||||
|
||||
// ConvertWorkflowStatus convert workflow run status to workflow status
|
||||
|
||||
@@ -31,19 +31,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
addonRegistryType = "type"
|
||||
addonEndpoint = "endpoint"
|
||||
addonOssBucket = "bucket"
|
||||
addonPath = "path"
|
||||
addonGitToken = "gitToken"
|
||||
addonOssType = "OSS"
|
||||
addonGitType = "git"
|
||||
addonGiteeType = "gitee"
|
||||
addonGitlabType = "gitlab"
|
||||
addonHelmType = "helm"
|
||||
addonUsername = "username"
|
||||
addonPassword = "password"
|
||||
addonRepoName = "repoName"
|
||||
addonRegistryType = "type"
|
||||
addonEndpoint = "endpoint"
|
||||
addonOssBucket = "bucket"
|
||||
addonPath = "path"
|
||||
addonGitToken = "gitToken"
|
||||
addonOssType = "OSS"
|
||||
addonGitType = "git"
|
||||
addonGiteeType = "gitee"
|
||||
addonGitlabType = "gitlab"
|
||||
addonHelmType = "helm"
|
||||
addonUsername = "username"
|
||||
addonPassword = "password"
|
||||
// only gitlab registry need set this flag
|
||||
addonRepoName = "gitlabRepoName"
|
||||
addonHelmInsecureSkipTLS = "insecureSkipTLS"
|
||||
)
|
||||
|
||||
@@ -67,10 +68,12 @@ func NewAddonRegistryCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.
|
||||
// NewAddAddonRegistryCommand return an addon registry create command
|
||||
func NewAddAddonRegistryCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add an addon registry.",
|
||||
Long: "Add an addon registry.",
|
||||
Example: `"vela addon registry add <my-registry-name> --type OSS --endpoint=<URL> --bucket=<bukect-name> or vela addon registry add my-repo --type git --endpoint=<URL> --path=<OSS-ptah> --gitToken=<git token>"`,
|
||||
Use: "add",
|
||||
Short: "Add an addon registry.",
|
||||
Long: "Add an addon registry.",
|
||||
Example: `add a helm repo registry: vela addon registry add --type=helm my-repo --endpoint=<URL>
|
||||
add a github registry: vela addon registry add my-repo --type git --endpoint=<URL> --path=<ptah> --token=<git token>"
|
||||
add a gitlab registry: vela addon registry add my-repo --type gitlab --endpoint=<URL> --gitlabRepoName=<repoName> --path=<path> --token=<git token>`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
registry, err := getRegistryFromArgs(cmd, args)
|
||||
if err != nil {
|
||||
@@ -298,6 +301,7 @@ func parseArgsFromFlag(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP(addonGitToken, "", "", "specify the github repo token")
|
||||
cmd.Flags().StringP(addonUsername, "", "", "specify the Helm addon registry username")
|
||||
cmd.Flags().StringP(addonPassword, "", "", "specify the Helm addon registry password")
|
||||
cmd.Flags().StringP(addonRepoName, "", "", "specify the gitlab addon registry repoName")
|
||||
cmd.Flags().BoolP(addonHelmInsecureSkipTLS, "", false,
|
||||
"specify the Helm addon registry skip tls verify")
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/oam-dev/cluster-gateway/pkg/config"
|
||||
|
||||
@@ -66,11 +65,6 @@ func ClusterCommandGroup(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comm
|
||||
},
|
||||
// check if cluster-gateway is ready
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get k8s client")
|
||||
|
||||
@@ -261,6 +261,7 @@ func wrapStepName(step workflowv1alpha1.StepStatus) string {
|
||||
}
|
||||
|
||||
func unwrapStepName(step string) string {
|
||||
step = strings.TrimPrefix(step, " ")
|
||||
switch {
|
||||
case strings.HasPrefix(step, emojiSucceed):
|
||||
return strings.TrimPrefix(step, emojiSucceed)
|
||||
|
||||
@@ -18,24 +18,16 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wercker/stern/stern"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
|
||||
@@ -99,45 +91,25 @@ type Args struct {
|
||||
}
|
||||
|
||||
func (l *Args) printPodLogs(ctx context.Context, ioStreams util.IOStreams, selectPod *querytypes.PodBase, filters []string) error {
|
||||
pod, err := regexp.Compile(selectPod.Metadata.Name + ".*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to compile '%s' for logs query", selectPod.Metadata.Name+".*")
|
||||
}
|
||||
container := regexp.MustCompile(".*")
|
||||
if l.ContainerName != "" {
|
||||
container = regexp.MustCompile(l.ContainerName + ".*")
|
||||
}
|
||||
namespace := selectPod.Metadata.Namespace
|
||||
selector := labels.NewSelector()
|
||||
for k, v := range selectPod.Metadata.Labels {
|
||||
req, _ := labels.NewRequirement(k, selection.Equals, []string{v})
|
||||
if req != nil {
|
||||
selector = selector.Add(*req)
|
||||
}
|
||||
}
|
||||
|
||||
config, err := l.Args.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
added, removed, err := stern.Watch(ctx,
|
||||
clientSet.CoreV1().Pods(namespace),
|
||||
pod,
|
||||
container,
|
||||
nil,
|
||||
[]stern.ContainerState{stern.RUNNING, stern.TERMINATED},
|
||||
selector,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tails := make(map[string]*stern.Tail)
|
||||
logC := make(chan string, 1024)
|
||||
|
||||
var t string
|
||||
switch l.Output {
|
||||
case "default":
|
||||
if color.NoColor {
|
||||
t = "{{.ContainerName}} {{.Message}}"
|
||||
} else {
|
||||
t = "{{color .ContainerColor .ContainerName}} {{.Message}}"
|
||||
}
|
||||
case "raw":
|
||||
t = "{{.Message}}"
|
||||
case "json":
|
||||
t = "{{json .}}\n"
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
@@ -158,71 +130,11 @@ func (l *Args) printPodLogs(ctx context.Context, ioStreams util.IOStreams, selec
|
||||
}
|
||||
}()
|
||||
|
||||
var t string
|
||||
switch l.Output {
|
||||
case "default":
|
||||
if color.NoColor {
|
||||
t = "{{.ContainerName}} {{.Message}}"
|
||||
} else {
|
||||
t = "{{color .ContainerColor .ContainerName}} {{.Message}}"
|
||||
}
|
||||
case "raw":
|
||||
t = "{{.Message}}"
|
||||
case "json":
|
||||
t = "{{json .}}\n"
|
||||
}
|
||||
funs := map[string]interface{}{
|
||||
"json": func(in interface{}) (string, error) {
|
||||
b, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
},
|
||||
"color": func(color color.Color, text string) string {
|
||||
return color.SprintFunc()(text)
|
||||
},
|
||||
}
|
||||
template, err := template.New("log").Funcs(funs).Parse(t)
|
||||
err = utils.GetPodsLogs(ctx, config, l.ContainerName, []*querytypes.PodBase{selectPod}, t, logC, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse template")
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for p := range added {
|
||||
id := p.GetID()
|
||||
if tails[id] != nil {
|
||||
continue
|
||||
}
|
||||
// 48h
|
||||
dur, _ := time.ParseDuration("48h")
|
||||
tail := stern.NewTail(p.Namespace, p.Pod, p.Container, template, &stern.TailOptions{
|
||||
Timestamps: true,
|
||||
SinceSeconds: int64(dur.Seconds()),
|
||||
Exclude: nil,
|
||||
Include: nil,
|
||||
Namespace: false,
|
||||
TailLines: nil, // default for all logs
|
||||
})
|
||||
tails[id] = tail
|
||||
|
||||
tail.Start(ctx, clientSet.CoreV1().Pods(p.Namespace), logC)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for p := range removed {
|
||||
id := p.GetID()
|
||||
if tails[id] == nil {
|
||||
continue
|
||||
}
|
||||
tails[id].Close()
|
||||
delete(tails, id)
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -224,15 +224,6 @@ func formatEndpoints(endpoints []types2.ServiceEndpoint) [][]string {
|
||||
}
|
||||
|
||||
func printAppEndpoints(ctx context.Context, appName string, namespace string, f Filter, velaC common.Args, skipEmptyTable bool) error {
|
||||
config, err := velaC.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := multicluster.Initialize(config, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
velaC.SetClient(client)
|
||||
endpoints, err := GetServiceEndpoints(ctx, appName, namespace, velaC, f)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -86,59 +86,10 @@ func (ui *UserInput) read() (string, error) {
|
||||
//
|
||||
// format = "yaml" / "json" / "jsonpath={.field}"
|
||||
func formatApplicationString(format string, app *v1beta1.Application) (string, error) {
|
||||
var ret string
|
||||
|
||||
if format == "" {
|
||||
return "", fmt.Errorf("no format provided")
|
||||
}
|
||||
|
||||
// No, we don't want managedFields, get rid of it.
|
||||
app.ManagedFields = nil
|
||||
|
||||
switch format {
|
||||
case "yaml":
|
||||
b, err := yaml.Marshal(app)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = string(b)
|
||||
case "json":
|
||||
b, err := json.MarshalIndent(app, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = string(b)
|
||||
default:
|
||||
// format is not any of json/yaml/jsonpath, not supported
|
||||
if !strings.HasPrefix(format, "jsonpath") {
|
||||
return "", fmt.Errorf("output %s is not supported", format)
|
||||
}
|
||||
|
||||
// format = jsonpath
|
||||
s := strings.Split(format, "=")
|
||||
if len(s) < 2 {
|
||||
return "", fmt.Errorf("jsonpath template format specified but no template given")
|
||||
}
|
||||
path, err := get.RelaxedJSONPathExpression(s[1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jp := jsonpath.New("").AllowMissingKeys(true)
|
||||
err = jp.Parse(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = jp.Execute(buf, app)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = buf.String()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return printObj(format, app)
|
||||
}
|
||||
|
||||
// AskToChooseOnePod will ask user to select one pod
|
||||
@@ -231,24 +182,28 @@ func AskToChooseOneService(services []types.ResourceItem, selectPort bool) (*typ
|
||||
}
|
||||
|
||||
func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevision) (string, error) {
|
||||
// No, we don't want managedFields, get rid of it.
|
||||
apprev.ManagedFields = nil
|
||||
|
||||
return printObj(format, apprev)
|
||||
}
|
||||
|
||||
func printObj(format string, obj interface{}) (string, error) {
|
||||
var ret string
|
||||
|
||||
if format == "" {
|
||||
return "", fmt.Errorf("no format provided")
|
||||
}
|
||||
|
||||
// No, we don't want managedFields, get rid of it.
|
||||
apprev.ManagedFields = nil
|
||||
|
||||
switch format {
|
||||
case "yaml":
|
||||
b, err := yaml.Marshal(apprev)
|
||||
b, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = string(b)
|
||||
case "json":
|
||||
b, err := json.MarshalIndent(apprev, "", " ")
|
||||
b, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -260,7 +215,7 @@ func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevi
|
||||
}
|
||||
|
||||
// format = jsonpath
|
||||
s := strings.Split(format, "=")
|
||||
s := strings.SplitN(format, "=", 2)
|
||||
if len(s) < 2 {
|
||||
return "", fmt.Errorf("jsonpath template format specified but no template given")
|
||||
}
|
||||
@@ -276,7 +231,7 @@ func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevi
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = jp.Execute(buf, apprev)
|
||||
err = jp.Execute(buf, obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"gotest.tools/assert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
@@ -89,6 +90,23 @@ status: {}
|
||||
str, err = formatApplicationString("jsonpath={.apiVersion}", app)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, str, "core.oam.dev/v1beta1")
|
||||
|
||||
str, err = formatApplicationString("jsonpath={.spec.components[?(@.name==\"test-server\")].type}", &v1beta1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "dev",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "test-server",
|
||||
Type: "webservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, str, "webservice")
|
||||
}
|
||||
|
||||
func TestConvertApplicationRevisionTo(t *testing.T) {
|
||||
@@ -200,6 +218,32 @@ status:
|
||||
},
|
||||
},
|
||||
}, exp: Exp{out: "core.oam.dev/v1beta1", err: ""}},
|
||||
"jsonpath filter expression": {format: "jsonpath={.spec.application.spec.components[?(@.name==\"test-server\")].type}", apprev: &v1beta1.ApplicationRevision{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ApplicationRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-apprev",
|
||||
Namespace: "dev",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{
|
||||
Application: v1beta1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "dev",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "test-server",
|
||||
Type: "webservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, exp: Exp{out: "webservice", err: ""}},
|
||||
"jsonpath with error": {format: "jsonpath", apprev: &v1beta1.ApplicationRevision{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ApplicationRevision",
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
@@ -372,7 +371,6 @@ func QueryValue(ctx context.Context, velaC common.Args, queryView *velaql.QueryV
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
client, err := velaC.GetClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
@@ -33,15 +32,12 @@ import (
|
||||
wfTypes "github.com/kubevela/workflow/pkg/types"
|
||||
wfUtils "github.com/kubevela/workflow/pkg/utils"
|
||||
|
||||
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/operation"
|
||||
"github.com/oam-dev/kubevela/references/appfile"
|
||||
)
|
||||
|
||||
// NewWorkflowCommand create `workflow` command
|
||||
@@ -49,252 +45,143 @@ func NewWorkflowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
|
||||
cmd := &cobra.Command{
|
||||
Use: "workflow",
|
||||
Short: "Operate application delivery workflow.",
|
||||
Long: "Operate the Workflow during Application Delivery. Note that workflow command is both valid for Application Workflow and WorkflowRun. The command will try to find the Application first, if not found, it will try to find WorkflowRun. You can also specify the resource type by using --type flag.",
|
||||
Long: "Operate the Workflow during Application Delivery. Note that workflow command is both valid for Application Workflow and WorkflowRun(expect for [restart, rollout] command, they're only valid for Application Workflow). The command will try to find the Application first, if not found, it will try to find WorkflowRun. You can also specify the resource type by using --type flag.",
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeCD,
|
||||
},
|
||||
}
|
||||
wargs := &WorkflowArgs{
|
||||
Args: c,
|
||||
Writer: ioStreams.Out,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
NewWorkflowSuspendCommand(c, ioStreams),
|
||||
NewWorkflowResumeCommand(c, ioStreams),
|
||||
NewWorkflowTerminateCommand(c, ioStreams),
|
||||
NewWorkflowRestartCommand(c, ioStreams),
|
||||
NewWorkflowRollbackCommand(c, ioStreams),
|
||||
NewWorkflowLogsCommand(c, ioStreams),
|
||||
NewWorkflowSuspendCommand(c, ioStreams, wargs),
|
||||
NewWorkflowResumeCommand(c, ioStreams, wargs),
|
||||
NewWorkflowTerminateCommand(c, ioStreams, wargs),
|
||||
NewWorkflowRestartCommand(c, ioStreams, wargs),
|
||||
NewWorkflowRollbackCommand(c, ioStreams, wargs),
|
||||
NewWorkflowLogsCommand(c, ioStreams, wargs),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewWorkflowSuspendCommand create workflow suspend command
|
||||
func NewWorkflowSuspendCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
func NewWorkflowSuspendCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "suspend",
|
||||
Short: "Suspend an application workflow.",
|
||||
Long: "Suspend an application workflow in cluster.",
|
||||
Example: "vela workflow suspend <application-name>",
|
||||
PreRun: checkApplicationNotRunning(c),
|
||||
PreRun: wargs.checkWorkflowNotComplete(),
|
||||
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 {
|
||||
ctx := context.Background()
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appfile.LoadApplication(namespace, args[0], c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
|
||||
return wo.Suspend(context.Background(), app)
|
||||
return wargs.Operator.Suspend(ctx)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewWorkflowResumeCommand create workflow resume command
|
||||
func NewWorkflowResumeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
func NewWorkflowResumeCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "resume",
|
||||
Short: "Resume a suspend application workflow.",
|
||||
Long: "Resume a suspend application workflow in cluster.",
|
||||
Example: "vela workflow resume <application-name>",
|
||||
PreRun: checkApplicationNotRunning(c),
|
||||
PreRun: wargs.checkWorkflowNotComplete(),
|
||||
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 {
|
||||
ctx := context.Background()
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appfile.LoadApplication(namespace, args[0], c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
|
||||
return wo.Resume(context.Background(), app)
|
||||
return wargs.Operator.Resume(ctx)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewWorkflowTerminateCommand create workflow terminate command
|
||||
func NewWorkflowTerminateCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
func NewWorkflowTerminateCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "terminate",
|
||||
Short: "Terminate an application workflow.",
|
||||
Long: "Terminate an application workflow in cluster.",
|
||||
Example: "vela workflow terminate <application-name>",
|
||||
PreRun: checkApplicationNotRunning(c),
|
||||
Short: "Terminate an workflow.",
|
||||
Long: "Terminate an workflow in cluster.",
|
||||
Example: "vela workflow terminate <workflow-name>",
|
||||
PreRun: wargs.checkWorkflowNotComplete(),
|
||||
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 {
|
||||
ctx := context.Background()
|
||||
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 app.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application is not running")
|
||||
}
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
|
||||
return wo.Terminate(context.Background(), app)
|
||||
return wargs.Operator.Terminate(ctx)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewWorkflowRestartCommand create workflow restart command
|
||||
func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "restart",
|
||||
Short: "Restart an application workflow.",
|
||||
Long: "Restart an application workflow in cluster.",
|
||||
Example: "vela workflow restart <application-name>",
|
||||
PreRun: checkApplicationNotRunning(c),
|
||||
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 {
|
||||
ctx := context.Background()
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appfile.LoadApplication(namespace, args[0], c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
|
||||
return wo.Restart(context.Background(), app)
|
||||
return wargs.Operator.Restart(ctx)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewWorkflowRollbackCommand create workflow rollback command
|
||||
func NewWorkflowRollbackCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
func NewWorkflowRollbackCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "rollback",
|
||||
Short: "Rollback an application workflow to the latest revision.",
|
||||
Long: "Rollback an application workflow to the latest revision.",
|
||||
Example: "vela workflow rollback <application-name>",
|
||||
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 {
|
||||
ctx := context.Background()
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appfile.LoadApplication(namespace, args[0], c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.Status.Workflow != nil && !app.Status.Workflow.Terminated && !app.Status.Workflow.Suspend && !app.Status.Workflow.Finished {
|
||||
return fmt.Errorf("can not rollback a running workflow")
|
||||
}
|
||||
if oam.GetPublishVersion(app) == "" {
|
||||
if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
|
||||
return fmt.Errorf("the latest revision is not set: %s", app.Name)
|
||||
}
|
||||
// get the last revision
|
||||
revision := &v1beta1.ApplicationRevision{}
|
||||
if err := cli.Get(context.TODO(), k8stypes.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
|
||||
return fmt.Errorf("failed to get the latest revision: %w", err)
|
||||
}
|
||||
|
||||
app.Spec = revision.Spec.Application.Spec
|
||||
if err := cli.Status().Update(context.TODO(), app); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
|
||||
return nil
|
||||
}
|
||||
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
|
||||
return wo.Rollback(context.Background(), app)
|
||||
return wargs.Operator.Rollback(ctx)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewWorkflowLogsCommand create workflow logs command
|
||||
func NewWorkflowLogsCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
wargs := &WorkflowArgs{Args: c}
|
||||
func NewWorkflowLogsCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "Tail logs for workflow steps",
|
||||
Long: "Tail logs for workflow steps, note that you need to use op.#Logs in step definition to set the log config of the step.",
|
||||
Example: "vela workflow logs <application-name>",
|
||||
Example: "vela workflow logs <workflow-name>",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("must specify Application or WorkflowRun name")
|
||||
}
|
||||
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := wargs.getWorkflowInstance(ctx, cli, namespace, args[0]); err != nil {
|
||||
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
return wargs.printStepLogs(ctx, cli, ioStream)
|
||||
@@ -312,6 +199,8 @@ type WorkflowArgs struct {
|
||||
Type string
|
||||
Output string
|
||||
ControllerLabels map[string]string
|
||||
Operator wfUtils.WorkflowOperator
|
||||
Writer io.Writer
|
||||
Args common.Args
|
||||
StepName string
|
||||
App *v1beta1.Application
|
||||
@@ -324,7 +213,24 @@ const (
|
||||
instanceTypeWorkflowRun string = "workflow"
|
||||
)
|
||||
|
||||
func (w *WorkflowArgs) getWorkflowInstance(ctx context.Context, cli client.Client, namespace, name string) error {
|
||||
func (w *WorkflowArgs) getWorkflowInstance(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("please specify the name of application/workflow")
|
||||
}
|
||||
name := args[0]
|
||||
namespace, err := GetFlagNamespaceOrEnv(cmd, w.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, err := w.Args.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, err := w.Args.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
switch w.Type {
|
||||
case "":
|
||||
app := &v1beta1.Application{}
|
||||
@@ -384,6 +290,7 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
|
||||
EndTime: status.EndTime,
|
||||
},
|
||||
}
|
||||
w.Operator = operation.NewApplicationWorkflowOperator(cli, w.Writer, w.App)
|
||||
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-core"}
|
||||
case instanceTypeWorkflowRun:
|
||||
var steps []workflowv1alpha1.WorkflowStep
|
||||
@@ -404,6 +311,7 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
|
||||
Steps: steps,
|
||||
Status: w.WorkflowRun.Status,
|
||||
}
|
||||
w.Operator = wfUtils.NewWorkflowRunOperator(cli, w.Writer, w.WorkflowRun)
|
||||
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-workflow"}
|
||||
default:
|
||||
return fmt.Errorf("unknown workflow instance type %s", w.Type)
|
||||
@@ -422,7 +330,7 @@ func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioS
|
||||
}
|
||||
logConfig, err := wfUtils.GetLogConfigFromStep(ctx, cli, w.WorkflowInstance.Status.ContextBackend.Name, w.WorkflowInstance.Name, w.WorkflowInstance.Namespace, w.StepName)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithMessage(err, fmt.Sprintf("step [%s]", w.StepName))
|
||||
}
|
||||
if err := selectStepLogSource(logConfig); err != nil {
|
||||
return err
|
||||
@@ -453,11 +361,17 @@ func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioS
|
||||
}
|
||||
|
||||
func (w *WorkflowArgs) selectWorkflowStep() error {
|
||||
steps := make(map[string]workflowv1alpha1.WorkflowStepStatus)
|
||||
stepsKey := make([]string, 0)
|
||||
for _, step := range w.WorkflowInstance.Status.Steps {
|
||||
stepsKey = append(stepsKey, wrapStepName(step.StepStatus))
|
||||
for _, sub := range step.SubStepsStatus {
|
||||
stepsKey = append(stepsKey, fmt.Sprintf(" %s", wrapStepName(sub)))
|
||||
}
|
||||
}
|
||||
if len(stepsKey) == 0 {
|
||||
return fmt.Errorf("workflow is not start")
|
||||
}
|
||||
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a step to show logs:",
|
||||
Options: stepsKey,
|
||||
@@ -465,21 +379,7 @@ func (w *WorkflowArgs) selectWorkflowStep() error {
|
||||
var stepName string
|
||||
err := survey.AskOne(prompt, &stepName, survey.WithValidator(survey.Required))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select %s: %w", w.StepName, err)
|
||||
}
|
||||
if step := steps[w.StepName]; step.Type == wfTypes.WorkflowStepTypeStepGroup {
|
||||
stepsKey := make([]string, 0)
|
||||
for _, sub := range step.SubStepsStatus {
|
||||
stepsKey = append(stepsKey, wrapStepName(sub))
|
||||
}
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a sub step to show logs:",
|
||||
Options: stepsKey,
|
||||
}
|
||||
err := survey.AskOne(prompt, &stepName, survey.WithValidator(survey.Required))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select %s: %w", w.StepName, err)
|
||||
}
|
||||
return fmt.Errorf("failed to select step %s: %w", unwrapStepName(w.StepName), err)
|
||||
}
|
||||
w.StepName = unwrapStepName(stepName)
|
||||
return nil
|
||||
@@ -534,21 +434,12 @@ func (w *WorkflowArgs) printResourceLogs(ctx context.Context, cli client.Client,
|
||||
return l.printPodLogs(ctx, ioStreams, selectPod, filters)
|
||||
}
|
||||
|
||||
func checkApplicationNotRunning(c common.Args) func(cmd *cobra.Command, args []string) {
|
||||
func (w *WorkflowArgs) checkWorkflowNotComplete() func(cmd *cobra.Command, args []string) {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
// Any error will be returned to let the normal execution report the error
|
||||
if len(args) < 1 {
|
||||
if err := w.getWorkflowInstance(context.Background(), cmd, args); err != nil {
|
||||
return
|
||||
}
|
||||
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
app, err := appfile.LoadApplication(namespace, args[0], c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if app.Status.Phase == common2.ApplicationRunning {
|
||||
if w.WorkflowInstance.Status.Phase == workflowv1alpha1.WorkflowStateSucceeded {
|
||||
cmd.Printf("%s workflow not allowed because application %s is running\n", cmd.Use, args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestWorkflowSuspend(t *testing.T) {
|
||||
expectedErr error
|
||||
}{
|
||||
"no app name specified": {
|
||||
expectedErr: fmt.Errorf("must specify application name"),
|
||||
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
|
||||
},
|
||||
"workflow not running": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -74,7 +74,7 @@ func TestWorkflowSuspend(t *testing.T) {
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
expectedErr: fmt.Errorf("the workflow in application is not running"),
|
||||
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
|
||||
},
|
||||
"suspend successfully": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -95,7 +95,7 @@ func TestWorkflowSuspend(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
cmd := NewWorkflowSuspendCommand(c, ioStream)
|
||||
cmd := NewWorkflowSuspendCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
|
||||
initCommand(cmd)
|
||||
// clean up the arguments before start
|
||||
cmd.SetArgs([]string{})
|
||||
@@ -145,7 +145,7 @@ func TestWorkflowResume(t *testing.T) {
|
||||
expectedErr error
|
||||
}{
|
||||
"no app name specified": {
|
||||
expectedErr: fmt.Errorf("must specify application name"),
|
||||
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
|
||||
},
|
||||
"workflow not suspended": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -170,7 +170,7 @@ func TestWorkflowResume(t *testing.T) {
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
expectedErr: fmt.Errorf("the workflow in application is not running"),
|
||||
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
|
||||
},
|
||||
"workflow terminated": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -225,7 +225,7 @@ func TestWorkflowResume(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
cmd := NewWorkflowResumeCommand(c, ioStream)
|
||||
cmd := NewWorkflowResumeCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
|
||||
initCommand(cmd)
|
||||
// clean up the arguments before start
|
||||
cmd.SetArgs([]string{})
|
||||
@@ -285,7 +285,7 @@ func TestWorkflowTerminate(t *testing.T) {
|
||||
expectedErr error
|
||||
}{
|
||||
"no app name specified": {
|
||||
expectedErr: fmt.Errorf("must specify application name"),
|
||||
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
|
||||
},
|
||||
"workflow not running": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -296,7 +296,7 @@ func TestWorkflowTerminate(t *testing.T) {
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
expectedErr: fmt.Errorf("the workflow in application is not running"),
|
||||
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
|
||||
},
|
||||
"terminate successfully": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -350,7 +350,7 @@ func TestWorkflowTerminate(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
cmd := NewWorkflowTerminateCommand(c, ioStream)
|
||||
cmd := NewWorkflowTerminateCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
|
||||
initCommand(cmd)
|
||||
// clean up the arguments before start
|
||||
cmd.SetArgs([]string{})
|
||||
@@ -412,7 +412,7 @@ func TestWorkflowRestart(t *testing.T) {
|
||||
expectedErr error
|
||||
}{
|
||||
"no app name specified": {
|
||||
expectedErr: fmt.Errorf("must specify application name"),
|
||||
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
|
||||
},
|
||||
"workflow not running": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -423,7 +423,7 @@ func TestWorkflowRestart(t *testing.T) {
|
||||
Spec: workflowSpec,
|
||||
Status: common.AppStatus{},
|
||||
},
|
||||
expectedErr: fmt.Errorf("the workflow in application is not running"),
|
||||
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
|
||||
},
|
||||
"restart successfully": {
|
||||
app: &v1beta1.Application{
|
||||
@@ -444,7 +444,7 @@ func TestWorkflowRestart(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
cmd := NewWorkflowRestartCommand(c, ioStream)
|
||||
cmd := NewWorkflowRestartCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
|
||||
initCommand(cmd)
|
||||
// clean up the arguments before start
|
||||
cmd.SetArgs([]string{})
|
||||
@@ -496,12 +496,12 @@ func TestWorkflowRollback(t *testing.T) {
|
||||
expectedErr error
|
||||
}{
|
||||
"no app name specified": {
|
||||
expectedErr: fmt.Errorf("must specify application name"),
|
||||
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
|
||||
},
|
||||
"workflow running": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "workflow-not-running",
|
||||
Name: "workflow-running",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: workflowSpec,
|
||||
@@ -569,7 +569,7 @@ func TestWorkflowRollback(t *testing.T) {
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
cmd := NewWorkflowRollbackCommand(c, ioStream)
|
||||
cmd := NewWorkflowRollbackCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
|
||||
initCommand(cmd)
|
||||
// clean up the arguments before start
|
||||
cmd.SetArgs([]string{})
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
util2 "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/docgen/fix"
|
||||
)
|
||||
|
||||
// DescriptionUndefined indicates the description is not defined
|
||||
@@ -215,10 +216,17 @@ func GetTraitsFromClusterWithValidateOption(ctx context.Context, namespace strin
|
||||
|
||||
var templateErrors []error
|
||||
for _, td := range traitDefs.Items {
|
||||
tmp, err := GetCapabilityByTraitDefinitionObject(td)
|
||||
if err != nil {
|
||||
templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name))
|
||||
continue
|
||||
var tmp *types.Capability
|
||||
var err error
|
||||
// FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed
|
||||
if td.Name == "container-image" {
|
||||
tmp = fix.CapContainerImage
|
||||
} else {
|
||||
tmp, err = GetCapabilityByTraitDefinitionObject(td)
|
||||
if err != nil {
|
||||
templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name))
|
||||
continue
|
||||
}
|
||||
}
|
||||
tmp.Namespace = namespace
|
||||
if validateFlag {
|
||||
|
||||
@@ -2,31 +2,33 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: export-config
|
||||
name: export2config
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: oamdev/hello-world
|
||||
port: 8000
|
||||
- name: export2config-demo-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: oamdev/hello-world
|
||||
port: 8000
|
||||
workflow:
|
||||
steps:
|
||||
- name: apply-server
|
||||
type: apply-component
|
||||
outputs:
|
||||
outputs:
|
||||
- name: status
|
||||
valueFrom: output.status.conditions[0].message
|
||||
properties:
|
||||
component: express-server
|
||||
component: export2config-demo-server
|
||||
- name: export-config
|
||||
type: export-config
|
||||
type: export2config
|
||||
inputs:
|
||||
- from: status
|
||||
parameterKey: data.serverstatus
|
||||
properties:
|
||||
configName: my-configmap
|
||||
data:
|
||||
testkey: testvalue
|
||||
testkey: |
|
||||
testvalue
|
||||
value-line-2
|
||||
```
|
||||
@@ -6,7 +6,7 @@ metadata:
|
||||
namespace: default
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
- name: express-server-sec
|
||||
type: webservice
|
||||
properties:
|
||||
image: oamdev/hello-world
|
||||
@@ -19,14 +19,16 @@ spec:
|
||||
- name: status
|
||||
valueFrom: output.status.conditions[0].message
|
||||
properties:
|
||||
component: express-server
|
||||
component: express-server-sec
|
||||
- name: export-secret
|
||||
type: export-secret
|
||||
type: export2secret
|
||||
inputs:
|
||||
- from: status
|
||||
parameterKey: data.serverstatus
|
||||
properties:
|
||||
secretName: my-secret
|
||||
data:
|
||||
testkey: testvalue
|
||||
testkey: |
|
||||
testvalue
|
||||
value-line-2
|
||||
```
|
||||
34
references/docgen/fix/fix.go
Normal file
34
references/docgen/fix/fix.go
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fix
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// CapContainerImage is the cap for container image
|
||||
CapContainerImage *types.Capability
|
||||
)
|
||||
|
||||
// FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed
|
||||
func init() {
|
||||
legacyJSON := `{"name":"container-image","type":"trait","template":"#PatchParams: {\n\t// +usage=Specify the name of the target container, if not set, use the component name\n\tcontainerName: *\"\" | string\n\t// +usage=Specify the image of the container\n\timage: string\n\t// +usage=Specify the image pull policy of the container\n\timagePullPolicy: *\"\" | \"IfNotPresent\" | \"Always\" | \"Never\"\n}\nPatchContainer: {\n\t_params: #PatchParams\n\tname: _params.containerName\n\t_baseContainers: context.output.spec.template.spec.containers\n\t_matchContainers_: [ for _container_ in _baseContainers if _container_.name == name {_container_}]\n\t_baseContainer: *_|_ | {...}\n\tif len(_matchContainers_) == 0 {\n\t\terr: \"container \\(name) not found\"\n\t}\n\tif len(_matchContainers_) \u003e 0 {\n\t\t// +patchStrategy=retainKeys\n\t\timage: _params.image\n\n\t\tif _params.imagePullPolicy != \"\" {\n\t\t\t// +patchStrategy=retainKeys\n\t\t\timagePullPolicy: _params.imagePullPolicy\n\t\t}\n\t}\n}\npatch: spec: template: spec: {\n\tif parameter.containers == _|_ {\n\t\t// +patchKey=name\n\t\tcontainers: [{\n\t\t\tPatchContainer \u0026 {_params: {\n\t\t\t\tif parameter.containerName == \"\" {\n\t\t\t\t\tcontainerName: context.name\n\t\t\t\t}\n\t\t\t\tif parameter.containerName != \"\" {\n\t\t\t\t\tcontainerName: parameter.containerName\n\t\t\t\t}\n\t\t\t\timage: parameter.image\n\t\t\t\timagePullPolicy: parameter.imagePullPolicy\n\t\t\t}}\n\t\t}]\n\t}\n\tif parameter.containers != _|_ {\n\t\t// +patchKey=name\n\t\tcontainers: [ for c in parameter.containers {\n\t\t\tif c.containerName == \"\" {\n\t\t\t\terr: \"containerName must be set for containers\"\n\t\t\t}\n\t\t\tif c.containerName != \"\" {\n\t\t\t\tPatchContainer \u0026 {_params: c}\n\t\t\t}\n\t\t}]\n\t}\n}\nparameter: *#PatchParams | close({\n\t// +usage=Specify the container image for multiple containers\n\tcontainers: [...#PatchParams]\n})\nerrs: [ for c in patch.spec.template.spec.containers if c.err != _|_ {c.err}]\n","parameters":[{"name":"containerName","default":"","usage":"Specify the name of the target container, if not set, use the component name","type":16},{"name":"image","required":true,"default":"","usage":"Specify the image of the container","type":16},{"name":"imagePullPolicy","default":"","usage":"Specify the image pull policy of the container","type":16}],"description":"Set the image of the container.","category":"cue","appliesTo":["deployments.apps","statefulsets.apps","daemonsets.apps","jobs.batch"],"kubetemplate":null}`
|
||||
_ = json.Unmarshal([]byte(legacyJSON), &CapContainerImage)
|
||||
}
|
||||
@@ -53,6 +53,7 @@ import (
|
||||
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/terraform"
|
||||
"github.com/oam-dev/kubevela/references/docgen/fix"
|
||||
)
|
||||
|
||||
// ParseReference is used to include the common function `parseParameter`
|
||||
@@ -74,7 +75,7 @@ func (ref *ParseReference) getCapabilities(ctx context.Context, c common.Args) (
|
||||
case ref.Local != nil:
|
||||
lcaps, err := ParseLocalFiles(ref.Local.Path, c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get capability from local file %s: %w", ref.DefinitionName, err)
|
||||
return nil, fmt.Errorf("failed to get capability from local file %s: %w", ref.Local.Path, err)
|
||||
}
|
||||
for _, lcap := range lcaps {
|
||||
caps = append(caps, *lcap)
|
||||
@@ -519,6 +520,11 @@ func ParseLocalFiles(localFilePath string, c common.Args) ([]*types.Capability,
|
||||
if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".cue") {
|
||||
return nil
|
||||
}
|
||||
// FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed
|
||||
if strings.Contains(path, "container-image") {
|
||||
lcaps = append(lcaps, fix.CapContainerImage)
|
||||
return nil
|
||||
}
|
||||
lcap, err := ParseLocalFile(path, c)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -588,7 +594,7 @@ func ParseLocalFile(localFilePath string, c common.Args) (*types.Capability, err
|
||||
}
|
||||
lcap, err := ParseCapabilityFromUnstructured(mapper, pd, def.Unstructured)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "fail to parse definition to capability")
|
||||
return nil, errors.Wrapf(err, "fail to parse definition to capability %s", def.GetName())
|
||||
}
|
||||
return &lcap, nil
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ limitations under the License.
|
||||
package e2e_apiserver_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
@@ -188,5 +191,26 @@ var _ = Describe("Test addon rest api", func() {
|
||||
Expect(decodeResponseBody(res, &addonStatus)).Should(Succeed())
|
||||
Expect(addonStatus.Name).Should(BeEquivalentTo("mock-addon"))
|
||||
})
|
||||
|
||||
It("enable addon with not match system version requirement", func() {
|
||||
req := apisv1.EnableAddonRequest{
|
||||
Args: map[string]interface{}{
|
||||
"testkey": "testvalue",
|
||||
},
|
||||
}
|
||||
res := post("/addons/not-match-addon/enable", req)
|
||||
defer res.Body.Close()
|
||||
type errResp struct {
|
||||
BusinessCode int `json:"BusinessCode"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
var errResponse errResp
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
Expect(err).Should(BeNil())
|
||||
err = json.Unmarshal(body, &errResponse)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(errResponse.BusinessCode).Should(BeEquivalentTo(50018))
|
||||
Expect(strings.Contains(errResponse.Message, "fail to install"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
357
test/e2e-apiserver-test/pipeline_test.go
Normal file
357
test/e2e-apiserver-test/pipeline_test.go
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e_apiserver_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/kubevela/workflow/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
|
||||
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var (
|
||||
testPipelineSteps []model.WorkflowStep
|
||||
props = model.JSONStruct{"url": "https://api.github.com/repos/kubevela/kubevela"}
|
||||
)
|
||||
|
||||
func init() {
|
||||
testPipelineSteps = []model.WorkflowStep{
|
||||
{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "request",
|
||||
Type: "request",
|
||||
Outputs: v1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
|
||||
Name: "stars",
|
||||
},
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("Test the rest api about the pipeline", func() {
|
||||
var (
|
||||
projectName1 = testNSprefix + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
pipelineName = "test-pipeline"
|
||||
description = "amazing pipeline"
|
||||
contextName = "test-context"
|
||||
contextKey = "test-key"
|
||||
contextVal = "test-val"
|
||||
pipelineRunName string
|
||||
)
|
||||
defer GinkgoRecover()
|
||||
It("create project and apply definitions", func() {
|
||||
defer GinkgoRecover()
|
||||
var req = apisv1.CreateProjectRequest{
|
||||
Name: projectName1,
|
||||
Description: "KubeVela Project",
|
||||
}
|
||||
res := post("/projects", req)
|
||||
var projectBase apisv1.ProjectBase
|
||||
Expect(decodeResponseBody(res, &projectBase)).Should(Succeed())
|
||||
Expect(cmp.Diff(projectBase.Name, req.Name)).Should(BeEmpty())
|
||||
Expect(cmp.Diff(projectBase.Description, req.Description)).Should(BeEmpty())
|
||||
|
||||
def1 := new(v1beta1.WorkflowStepDefinition)
|
||||
def2 := new(v1beta1.WorkflowStepDefinition)
|
||||
Expect(common.ReadYamlToObject("./testdata/request.yaml", def1)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.Background(), def1)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
Expect(common.ReadYamlToObject("./testdata/log.yaml", def2)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.Background(), def2)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("create pipeline", func() {
|
||||
var req = apisv1.CreatePipelineRequest{
|
||||
Name: pipelineName,
|
||||
Description: description,
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: testPipelineSteps,
|
||||
},
|
||||
}
|
||||
res := post("/projects/"+projectName1+"/pipelines", req)
|
||||
var pipeline apisv1.PipelineBase
|
||||
Expect(decodeResponseBody(res, &pipeline)).Should(Succeed())
|
||||
Expect(cmp.Diff(pipeline.Name, req.Name)).Should(BeEmpty())
|
||||
Expect(len(pipeline.Spec.Steps)).Should(Equal(len(req.Spec.Steps)))
|
||||
})
|
||||
|
||||
It("create context", func() {
|
||||
var req = apisv1.CreateContextValuesRequest{
|
||||
Name: contextName,
|
||||
Values: []model.Value{
|
||||
{
|
||||
Key: contextKey,
|
||||
Value: contextVal,
|
||||
},
|
||||
},
|
||||
}
|
||||
res := post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/contexts", req)
|
||||
var context apisv1.Context
|
||||
Expect(decodeResponseBody(res, &context)).Should(Succeed())
|
||||
Expect(cmp.Diff(context.Name, req.Name)).Should(BeEmpty())
|
||||
Expect(cmp.Diff(context.Values, req.Values)).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("get contexts", func() {
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/contexts")
|
||||
var contexs apisv1.ListContextValueResponse
|
||||
Expect(decodeResponseBody(res, &contexs)).Should(Succeed())
|
||||
Expect(len(contexs.Contexts)).Should(Equal(1))
|
||||
ctx, ok := contexs.Contexts[contextName]
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(len(ctx)).Should(Equal(1))
|
||||
})
|
||||
|
||||
It("update context", func() {
|
||||
var req = apisv1.UpdateContextValuesRequest{
|
||||
Values: []model.Value{
|
||||
{
|
||||
Key: contextKey,
|
||||
Value: "new-val",
|
||||
},
|
||||
},
|
||||
}
|
||||
res := put("/projects/"+projectName1+"/pipelines/"+pipelineName+"/contexts/"+contextName, req)
|
||||
var context apisv1.Context
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
Expect(decodeResponseBody(res, &context)).Should(Succeed())
|
||||
|
||||
By("check the context value")
|
||||
Expect(cmp.Diff(context.Values[0].Value, "new-val")).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("update pipeline", func() {
|
||||
newSteps := make([]model.WorkflowStep, 0)
|
||||
newSteps = append(newSteps, model.WorkflowStep{
|
||||
SubSteps: []model.WorkflowStepBase{
|
||||
{
|
||||
Name: "request1",
|
||||
Type: "request",
|
||||
Outputs: v1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
|
||||
Name: "stars",
|
||||
},
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
{
|
||||
Name: "request2",
|
||||
Type: "request",
|
||||
Outputs: v1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
|
||||
Name: "stars-copy",
|
||||
},
|
||||
},
|
||||
Properties: &props,
|
||||
},
|
||||
},
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "request-group",
|
||||
Type: "step-group",
|
||||
},
|
||||
})
|
||||
newSteps = append(newSteps, model.WorkflowStep{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "log",
|
||||
Type: "log",
|
||||
Inputs: v1alpha1.StepInputs{
|
||||
{
|
||||
ParameterKey: "data",
|
||||
From: "stars",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
var req = apisv1.UpdatePipelineRequest{
|
||||
Description: description,
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: newSteps,
|
||||
},
|
||||
}
|
||||
res := put("/projects/"+projectName1+"/pipelines/"+pipelineName, req)
|
||||
var pipeline apisv1.PipelineBase
|
||||
Expect(decodeResponseBody(res, &pipeline)).Should(Succeed())
|
||||
Expect(len(pipeline.Spec.Steps)).Should(Equal(len(req.Spec.Steps)))
|
||||
})
|
||||
|
||||
It("run pipeline", func() {
|
||||
var req = apisv1.RunPipelineRequest{
|
||||
Mode: v1alpha1.WorkflowExecuteMode{
|
||||
Steps: "StepByStep",
|
||||
SubSteps: "DAG",
|
||||
},
|
||||
ContextName: contextName,
|
||||
}
|
||||
res := post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/run", req)
|
||||
var run apisv1.PipelineRun
|
||||
Expect(decodeResponseBody(res, &run)).Should(Succeed())
|
||||
Expect(run.PipelineRunName).ShouldNot(BeEmpty())
|
||||
pipelineRunName = run.PipelineRunName
|
||||
})
|
||||
|
||||
It("list pipeline", func() {
|
||||
res := get("/pipelines?query=amazing")
|
||||
var pipelines apisv1.ListPipelineResponse
|
||||
Expect(decodeResponseBody(res, &pipelines)).Should(Succeed())
|
||||
Expect(pipelines.Total).Should(BeNumerically("==", 1))
|
||||
Expect(pipelines.Pipelines[0].Name).Should(Equal(pipelineName))
|
||||
})
|
||||
|
||||
It("get pipeline", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName)
|
||||
var pipeline apisv1.GetPipelineResponse
|
||||
g.Expect(decodeResponseBody(res, &pipeline)).Should(Succeed())
|
||||
g.Expect(pipeline.Name).Should(Equal(pipelineName))
|
||||
g.Expect(pipeline.Description).Should(Equal(description))
|
||||
g.Expect(pipeline.PipelineInfo.LastRun).ShouldNot(BeNil())
|
||||
g.Expect(pipeline.PipelineInfo.RunStat.Total).Should(Equal(apisv1.RunStatInfo{Total: 1, Success: 1}))
|
||||
g.Expect(len(pipeline.PipelineInfo.RunStat.Week)).Should(Equal(7))
|
||||
}, 10*time.Second, 1*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("list pipeline runs", func() {
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs")
|
||||
var runs apisv1.ListPipelineRunResponse
|
||||
Expect(decodeResponseBody(res, &runs)).Should(Succeed())
|
||||
Expect(runs.Total).Should(BeNumerically("==", 1))
|
||||
})
|
||||
|
||||
It("get pipeline run", func() {
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName)
|
||||
var run apisv1.PipelineRunBase
|
||||
Expect(decodeResponseBody(res, &run)).Should(Succeed())
|
||||
Expect(run.PipelineRunName).Should(Equal(pipelineRunName))
|
||||
})
|
||||
|
||||
It("get pipeline run status", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/status")
|
||||
var status v1alpha1.WorkflowRunStatus
|
||||
g.Expect(decodeResponseBody(res, &status)).Should(Succeed())
|
||||
g.Expect(status.Finished).Should(Equal(true))
|
||||
g.Expect(status.Phase).Should(Equal(v1alpha1.WorkflowStateSucceeded))
|
||||
g.Expect(status.Message).Should(BeEmpty())
|
||||
}, 100*time.Second, 1*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("get pipeline run output", func() {
|
||||
outputStep := "request1"
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/output?step=" + outputStep)
|
||||
var output apisv1.GetPipelineRunOutputResponse
|
||||
Expect(decodeResponseBody(res, &output)).Should(Succeed())
|
||||
Expect(output.StepOutputs).Should(HaveLen(1))
|
||||
Expect(output.StepOutputs[0].Name).Should(Equal(outputStep))
|
||||
Expect(output.StepOutputs[0].Values).Should(HaveLen(1))
|
||||
Expect(output.StepOutputs[0].Values[0].Value).ShouldNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("get pipeline run input", func() {
|
||||
inputStep := "log"
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/input?step=" + inputStep)
|
||||
var input apisv1.GetPipelineRunInputResponse
|
||||
Expect(decodeResponseBody(res, &input)).Should(Succeed())
|
||||
Expect(input.StepInputs).Should(HaveLen(1))
|
||||
Expect(input.StepInputs[0].Name).Should(Equal(inputStep))
|
||||
Expect(input.StepInputs[0].Values).Should(HaveLen(1))
|
||||
Expect(input.StepInputs[0].Values[0].Value).ShouldNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("get pipeline run logs", func() {
|
||||
logStep := "log"
|
||||
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/log?step=" + logStep)
|
||||
var logs apisv1.GetPipelineRunLogResponse
|
||||
Expect(decodeResponseBody(res, &logs)).Should(Succeed())
|
||||
Expect(logs.Name).Should(Equal(logStep))
|
||||
Expect(logs.Log).ShouldNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("delete pipeline run", func() {
|
||||
res := delete("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName)
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
It("stop pipeline", func() {
|
||||
By("update pipeline so that it will run for a while")
|
||||
var req = apisv1.UpdatePipelineRequest{
|
||||
Spec: model.WorkflowSpec{
|
||||
Steps: []model.WorkflowStep{
|
||||
{
|
||||
WorkflowStepBase: model.WorkflowStepBase{
|
||||
Name: "request",
|
||||
Type: "request",
|
||||
Timeout: "20s",
|
||||
DependsOn: []string{"not-exist-step"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res := put("/projects/"+projectName1+"/pipelines/"+pipelineName, req)
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
|
||||
By("run the pipeline")
|
||||
var run apisv1.PipelineRun
|
||||
res = post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/run", apisv1.RunPipelineRequest{})
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
Expect(decodeResponseBody(res, &run)).Should(Succeed())
|
||||
pipelineRunName = run.PipelineRunName
|
||||
|
||||
By("stop the pipeline")
|
||||
var meta apisv1.PipelineRunMeta
|
||||
res = post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/runs/"+pipelineRunName+"/stop", nil)
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
Expect(decodeResponseBody(res, &meta)).Should(Succeed())
|
||||
Expect(meta.PipelineRunName).Should(Equal(pipelineRunName))
|
||||
|
||||
By("delete pipeline run")
|
||||
res = delete("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName)
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
It("delete context", func() {
|
||||
res := delete("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/contexts/" + contextName)
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
It("delete pipeline", func() {
|
||||
res := delete("/projects/" + projectName1 + "/pipelines/" + pipelineName)
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
It("delete project", func() {
|
||||
res := delete("/projects/" + projectName1)
|
||||
Expect(res.StatusCode).Should(Equal(http.StatusOK))
|
||||
})
|
||||
|
||||
})
|
||||
@@ -59,6 +59,7 @@ func TestE2eApiserverTest(t *testing.T) {
|
||||
|
||||
// Suite test in e2e-apiserver-test relies on the pre-setup kubernetes environment
|
||||
var _ = BeforeSuite(func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -115,7 +116,7 @@ var _ = BeforeSuite(func() {
|
||||
err = json.NewDecoder(resp.Body).Decode(code)
|
||||
Expect(err).Should(BeNil())
|
||||
return fmt.Errorf("rest service not ready code:%d message:%s", resp.StatusCode, code.Message)
|
||||
}, time.Second*10, time.Millisecond*200).Should(BeNil())
|
||||
}, time.Second*20, time.Millisecond*200).Should(BeNil())
|
||||
var err error
|
||||
k8sClient, err = clients.GetKubeClient()
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
35
test/e2e-apiserver-test/testdata/log.yaml
vendored
Normal file
35
test/e2e-apiserver-test/testdata/log.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/alias: ""
|
||||
definition.oam.dev/description: Apply raw kubernetes objects for your workflow steps
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: log
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
apply: op.#Log & {
|
||||
parameter
|
||||
}
|
||||
parameter: {
|
||||
data?: string
|
||||
level: *3 | int
|
||||
source?: close({
|
||||
url: string
|
||||
}) | close({
|
||||
resources?: [...{
|
||||
name?: string
|
||||
cluster?: string
|
||||
namespace?: string
|
||||
labelSelector?: {...}
|
||||
}]
|
||||
})
|
||||
}
|
||||
43
test/e2e-apiserver-test/testdata/request.yaml
vendored
Normal file
43
test/e2e-apiserver-test/testdata/request.yaml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/alias: ""
|
||||
definition.oam.dev/description: Send request to the url
|
||||
name: request
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
http: op.http.#Do & {
|
||||
method: parameter.method
|
||||
url: parameter.url
|
||||
request: {
|
||||
if parameter.body != _|_ {
|
||||
body: json.Marshal(parameter.body)
|
||||
}
|
||||
if parameter.header != _|_ {
|
||||
header: parameter.header
|
||||
}
|
||||
}
|
||||
}
|
||||
fail: op.#Steps & {
|
||||
if http.response.statusCode > 400 {
|
||||
requestFail: op.#Fail & {
|
||||
message: "request of \(parameter.url) is fail: \(http.response.statusCode)"
|
||||
}
|
||||
}
|
||||
}
|
||||
response: json.Unmarshal(http.response.body)
|
||||
parameter: {
|
||||
url: string
|
||||
method: *"GET" | "POST" | "PUT" | "DELETE"
|
||||
body?: {...}
|
||||
header?: [string]: string
|
||||
}
|
||||
@@ -97,8 +97,10 @@ var _ = Describe("Test multicluster standalone scenario", func() {
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
app := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: namespace, Name: "podinfo"}, app)).Should(Succeed())
|
||||
Expect(k8sClient.Delete(context.Background(), app)).Should(Succeed())
|
||||
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: namespace, Name: "podinfo"}, app)).Should(Succeed())
|
||||
g.Expect(app.Status.Workflow).ShouldNot(BeNil())
|
||||
g.Expect(app.Status.Workflow.Mode).Should(Equal("DAG-DAG"))
|
||||
g.Expect(k8sClient.Delete(context.Background(), app)).Should(Succeed())
|
||||
}, 15*time.Second).Should(Succeed())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
|
||||
@@ -2,6 +2,8 @@ apiVersion: core.oam.dev/v1alpha1
|
||||
kind: Workflow
|
||||
metadata:
|
||||
name: deploy-podinfo
|
||||
mode:
|
||||
steps: DAG
|
||||
steps:
|
||||
- type: deploy
|
||||
name: deploy-worker
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
template: {
|
||||
#ApplyOnceStrategy: {
|
||||
// +usage=When the strategy takes effect,e.g. onUpdate、onStateKeep
|
||||
affect?: string
|
||||
// +usage=Specify the path of the resource that allow configuration drift
|
||||
path: [...string]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user