mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-23 14:23:54 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
360f69bea5 | ||
|
|
974644ddc4 | ||
|
|
f3a00984da | ||
|
|
92be5b3424 | ||
|
|
e528902bea | ||
|
|
c3b736f753 | ||
|
|
f343111f87 | ||
|
|
007901f9f1 | ||
|
|
fcd721ffed | ||
|
|
37718a095d | ||
|
|
2ca81e037d | ||
|
|
33940c621a | ||
|
|
705ea38158 | ||
|
|
8fabbbf2de | ||
|
|
c010d1c7f3 | ||
|
|
856a3a636e | ||
|
|
24c33b8313 | ||
|
|
c3ae3fedf3 | ||
|
|
83962d44d9 | ||
|
|
3250b0003a | ||
|
|
024a34585a | ||
|
|
460cdbeea6 | ||
|
|
21418d2f06 | ||
|
|
65c1bea03a | ||
|
|
17a76cc0e2 | ||
|
|
ae6697b316 | ||
|
|
03f582ad88 | ||
|
|
75f8209a4c | ||
|
|
3975fbcda6 |
11
.github/workflows/e2e-multicluster-test.yml
vendored
11
.github/workflows/e2e-multicluster-test.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
e2e-multi-cluster-tests:
|
||||
runs-on: aliyun
|
||||
runs-on: self-hosted
|
||||
needs: [ detect-noop ]
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
strategy:
|
||||
@@ -49,11 +49,18 @@ jobs:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.k8s-version }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install make gcc jq ca-certificates curl gnupg -y
|
||||
snap install docker
|
||||
snap install kubectl --classic
|
||||
snap install helm --classic
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9
|
||||
with:
|
||||
|
||||
4
.github/workflows/e2e-rollout-test.yml
vendored
4
.github/workflows/e2e-rollout-test.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
e2e-rollout-tests:
|
||||
runs-on: aliyun
|
||||
runs-on: self-hosted
|
||||
needs: [ detect-noop ]
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
strategy:
|
||||
@@ -62,6 +62,8 @@ jobs:
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
go install github.com/onsi/ginkgo/ginkgo
|
||||
go get github.com/onsi/gomega/...
|
||||
|
||||
- name: Tear down K3d if exist
|
||||
run: |
|
||||
|
||||
12
.github/workflows/e2e-test.yml
vendored
12
.github/workflows/e2e-test.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
e2e-tests:
|
||||
runs-on: aliyun
|
||||
runs-on: self-hosted
|
||||
needs: [ detect-noop ]
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
strategy:
|
||||
@@ -54,6 +54,14 @@ jobs:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install make gcc jq ca-certificates curl gnupg -y
|
||||
snap install docker
|
||||
snap install kubectl --classic
|
||||
snap install helm --classic
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9
|
||||
with:
|
||||
@@ -62,6 +70,8 @@ jobs:
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
go install github.com/onsi/ginkgo/ginkgo
|
||||
go get github.com/onsi/gomega/...
|
||||
|
||||
- name: Tear down K3d if exist
|
||||
run: |
|
||||
|
||||
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
||||
version: ${{ env.GOLANGCI_VERSION }}
|
||||
|
||||
check-diff:
|
||||
runs-on: aliyun
|
||||
runs-on: self-hosted
|
||||
needs: detect-noop
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
|
||||
|
||||
4
.github/workflows/sdk-test.yml
vendored
4
.github/workflows/sdk-test.yml
vendored
@@ -42,10 +42,10 @@ jobs:
|
||||
run: make vela-cli
|
||||
|
||||
- name: Build SDK
|
||||
run: bin/vela def gen-api -f vela-templates/definitions/internal/ -o ./kubevela-go-sdk --package=github.com/kubevela-contrib/kubevela-go-sdk
|
||||
run: bin/vela def gen-api -f vela-templates/definitions/internal/ -o ./kubevela-go-sdk --package=github.com/kubevela-contrib/kubevela-go-sdk --init
|
||||
|
||||
- name: Validate SDK
|
||||
run: |
|
||||
cd kubevela-go-sdk
|
||||
go mod tidy
|
||||
golangci-lint run --timeout 5m ./...
|
||||
golangci-lint run --timeout 5m -e "exported:" -e "dot-imports" ./...
|
||||
|
||||
2
.github/workflows/timed-task.yml
vendored
2
.github/workflows/timed-task.yml
vendored
@@ -8,7 +8,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
clean-image:
|
||||
runs-on: aliyun
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Cleanup image
|
||||
run: docker image prune -f
|
||||
@@ -176,9 +176,8 @@ type Capability struct {
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// Plugin Source
|
||||
Source *Source `json:"source,omitempty"`
|
||||
Install *Installation `json:"install,omitempty"`
|
||||
CrdInfo *CRDInfo `json:"crdInfo,omitempty"`
|
||||
Source *Source `json:"source,omitempty"`
|
||||
CrdInfo *CRDInfo `json:"crdInfo,omitempty"`
|
||||
|
||||
// Terraform
|
||||
TerraformConfiguration string `json:"terraformConfiguration,omitempty"`
|
||||
|
||||
@@ -49,7 +49,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `disableCaps` | Disable capability | `rollout` |
|
||||
| `dependCheckWait` | dependCheckWait is the time to wait for ApplicationConfiguration's dependent-resource ready | `30s` |
|
||||
|
||||
|
||||
### KubeVela workflow parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
@@ -59,7 +58,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `workflow.backoff.maxTime.failedState` | The max backoff time of workflow in a failed condition | `300` |
|
||||
| `workflow.step.errorRetryTimes` | The max retry times of a failed workflow step | `10` |
|
||||
|
||||
|
||||
### KubeVela controller parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
@@ -77,7 +75,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `webhookService.port` | KubeVela webhook service port | `9443` |
|
||||
| `healthCheck.port` | KubeVela health check port | `9440` |
|
||||
|
||||
|
||||
### KubeVela controller optimization parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
@@ -104,7 +101,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `featureGates.sharedDefinitionStorageForApplicationRevision` | use definition cache to reduce duplicated definition storage for application revision, must be used with InformerCacheFilterUnnecessaryFields | `true` |
|
||||
| `featureGates.disableWorkflowContextConfigMapCache` | disable the workflow context's configmap informer cache | `true` |
|
||||
|
||||
|
||||
### MultiCluster parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
@@ -125,7 +121,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `multicluster.clusterGateway.secureTLS.certPath` | Path to the certificate file | `/etc/k8s-cluster-gateway-certs` |
|
||||
| `multicluster.clusterGateway.secureTLS.certManager.enabled` | Whether to enable cert-manager | `false` |
|
||||
|
||||
|
||||
### Test parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
@@ -135,7 +130,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
| `test.k8s.repository` | Test k8s repository | `oamdev/alpine-k8s` |
|
||||
| `test.k8s.tag` | Test k8s tag | `1.18.2` |
|
||||
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
|
||||
@@ -2306,6 +2306,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it
|
||||
defines the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow
|
||||
step.
|
||||
@@ -4168,6 +4173,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it defines
|
||||
the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
|
||||
@@ -1068,6 +1068,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it defines
|
||||
the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/check-metrics.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
custom.definition.oam.dev/category: Application Delivery
|
||||
definition.oam.dev/description: Verify application's metrics
|
||||
labels:
|
||||
custom.definition.oam.dev/catalog: Delivery
|
||||
name: check-metrics
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
check: op.#PromCheck & {
|
||||
query: parameter.query
|
||||
metricEndpoint: parameter.metricEndpoint
|
||||
condition: parameter.condition
|
||||
stepID: context.stepSessionID
|
||||
duration: parameter.duration
|
||||
failDuration: parameter.failDuration
|
||||
}
|
||||
fail: op.#Steps & {
|
||||
if check.failed != _|_ {
|
||||
if check.failed == true {
|
||||
breakWorkflow: op.#Fail & {
|
||||
message: check.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: check.result
|
||||
if check.message != _|_ {
|
||||
message: check.message
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Query is a raw prometheus query to perform
|
||||
query: string
|
||||
// +usage=The HTTP address and port of the prometheus server
|
||||
metricEndpoint?: "http://prometheus-server.o11y-system.svc:9090" | string
|
||||
// +usage=Condition is an expression which determines if a measurement is considered successful. eg: >=0.95
|
||||
condition: string
|
||||
// +usage=Duration defines the duration of time required for this step to be considered successful.
|
||||
duration?: *"5m" | string
|
||||
// +usage=FailDuration is the duration of time that, if the check fails, will result in the step being marked as failed.
|
||||
failDuration?: *"2m" | string
|
||||
}
|
||||
|
||||
@@ -318,8 +318,5 @@ spec:
|
||||
failureThreshold: *3 | int
|
||||
}
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
type: cronjobs.batch
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ spec:
|
||||
template: |
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
outputs: service: {
|
||||
@@ -25,22 +26,66 @@ spec:
|
||||
metadata: name: context.name
|
||||
metadata: annotations: parameter.annotations
|
||||
spec: {
|
||||
selector: "app.oam.dev/component": context.name
|
||||
ports: [
|
||||
for p in parameter.port {
|
||||
name: "port-" + strconv.FormatInt(p, 10)
|
||||
port: p
|
||||
targetPort: p
|
||||
if parameter["matchLabels"] == _|_ {
|
||||
selector: "app.oam.dev/component": context.name
|
||||
}
|
||||
if parameter["matchLabels"] != _|_ {
|
||||
selector: parameter["matchLabels"]
|
||||
}
|
||||
|
||||
// compatible with the old way
|
||||
if parameter["port"] != _|_ if parameter["ports"] == _|_ {
|
||||
ports: [
|
||||
for p in parameter.port {
|
||||
name: "port-" + strconv.FormatInt(p, 10)
|
||||
port: p
|
||||
targetPort: p
|
||||
},
|
||||
]
|
||||
}
|
||||
if parameter["ports"] != _|_ {
|
||||
ports: [ for v in parameter.ports {
|
||||
port: v.port
|
||||
targetPort: v.port
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
_name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
name: *_name | string
|
||||
if v.protocol != "TCP" {
|
||||
name: _name + "-" + strings.ToLower(v.protocol)
|
||||
}
|
||||
}
|
||||
if v.nodePort != _|_ if parameter.type == "NodePort" {
|
||||
nodePort: v.nodePort
|
||||
}
|
||||
if v.protocol != _|_ {
|
||||
protocol: v.protocol
|
||||
}
|
||||
},
|
||||
]
|
||||
]
|
||||
}
|
||||
type: parameter.type
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the exposion ports
|
||||
port: [...int]
|
||||
// +usage=Deprecated, the old way to specify the exposion ports
|
||||
port?: [...int]
|
||||
// +usage=Specify portsyou want customer traffic sent to
|
||||
ports?: [...{
|
||||
// +usage=Number of port to expose on the pod's IP address
|
||||
port: int
|
||||
// +usage=Name of the port
|
||||
name?: string
|
||||
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
|
||||
protocol: *"TCP" | "UDP" | "SCTP"
|
||||
// +usage=exposed node port. Only Valid when exposeType is NodePort
|
||||
nodePort?: int
|
||||
}]
|
||||
// +usage=Specify the annotaions of the exposed service
|
||||
annotations: [string]: string
|
||||
annotations: [string]: string
|
||||
matchLabels?: [string]: string
|
||||
// +usage=Specify what kind of Service you want. options: "ClusterIP","NodePort","LoadBalancer","ExternalName"
|
||||
type: *"ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
|
||||
}
|
||||
|
||||
@@ -113,19 +113,20 @@ spec:
|
||||
}
|
||||
if context.outputs.ingress.status.loadBalancer.ingress != _|_ {
|
||||
let igs = context.outputs.ingress.status.loadBalancer.ingress
|
||||
let host = context.outputs.ingress.spec.rules[0].host
|
||||
if igs[0].ip != _|_ {
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host + ", IP: " + igs[0].ip
|
||||
}
|
||||
if igs[0].host == _|_ {
|
||||
if host == _|_ {
|
||||
message: "Host not specified, visit the cluster or load balancer in front of the cluster with IP: " + igs[0].ip
|
||||
}
|
||||
}
|
||||
if igs[0].ip == _|_ {
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host
|
||||
}
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Host not specified, visit the cluster or load balancer in front of the cluster"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ spec:
|
||||
- mountPath: {{ .Values.admissionWebhooks.certificate.mountPath }}
|
||||
name: tls-cert-vol
|
||||
readOnly: true
|
||||
{{ if and .Values.multicluster.clusterGateway.secureTLS.enabled .Values.multicluster.clusterGateway.direct }}
|
||||
{{ if and .Values.multicluster.enabled .Values.multicluster.clusterGateway.secureTLS.enabled .Values.multicluster.clusterGateway.direct }}
|
||||
- mountPath: /cluster-gateway-tls-cert
|
||||
name: tls-cert-vol-cg
|
||||
readOnly: true
|
||||
@@ -362,7 +362,7 @@ spec:
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: {{ template "kubevela.fullname" . }}-admission
|
||||
{{ if and .Values.multicluster.clusterGateway.secureTLS.enabled .Values.multicluster.clusterGateway.direct }}
|
||||
{{ if and .Values.multicluster.enabled .Values.multicluster.clusterGateway.secureTLS.enabled .Values.multicluster.clusterGateway.direct }}
|
||||
- name: tls-cert-vol-cg
|
||||
secret:
|
||||
defaultMode: 420
|
||||
|
||||
@@ -65,10 +65,8 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-minimal --
|
||||
| `controllerArgs.reSyncPeriod` | The period for resync the applications | `5m` |
|
||||
| `OAMSpecVer` | OAMSpecVer is the oam spec version controller want to setup | `minimal` |
|
||||
| `disableCaps` | Disable capability | `envbinding,rollout` |
|
||||
| `applyOnceOnly` | Valid applyOnceOnly values: true/false/on/off/force | `off` |
|
||||
| `dependCheckWait` | dependCheckWait is the time to wait for ApplicationConfiguration's dependent-resource ready | `30s` |
|
||||
|
||||
|
||||
### KubeVela workflow parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
@@ -78,7 +76,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-minimal --
|
||||
| `workflow.backoff.maxTime.failedState` | The max backoff time of workflow in a failed condition | `300` |
|
||||
| `workflow.step.errorRetryTimes` | The max retry times of a failed workflow step | `10` |
|
||||
|
||||
|
||||
### KubeVela controller parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
@@ -96,22 +93,28 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-minimal --
|
||||
| `webhookService.port` | KubeVela webhook service port | `9443` |
|
||||
| `healthCheck.port` | KubeVela health check port | `9440` |
|
||||
|
||||
### KubeVela controller optimization parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `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` |
|
||||
|
||||
### MultiCluster parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ----------------------------------------------------- | -------------------------------- | -------------------------------- |
|
||||
| `multicluster.enabled` | Whether to enable multi-cluster | `true` |
|
||||
| `multicluster.clusterGateway.replicaCount` | ClusterGateway replica count | `1` |
|
||||
| `multicluster.clusterGateway.port` | ClusterGateway port | `9443` |
|
||||
| `multicluster.clusterGateway.image.repository` | ClusterGateway image repository | `oamdev/cluster-gateway` |
|
||||
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.7.0` |
|
||||
| `multicluster.clusterGateway.image.pullPolicy` | ClusterGateway image pull policy | `IfNotPresent` |
|
||||
| `multicluster.clusterGateway.resources.limits.cpu` | ClusterGateway cpu limit | `100m` |
|
||||
| `multicluster.clusterGateway.resources.limits.memory` | ClusterGateway memory limit | `200Mi` |
|
||||
| `multicluster.clusterGateway.secureTLS.enabled` | Whether to enable secure TLS | `true` |
|
||||
| `multicluster.clusterGateway.secureTLS.certPath` | Path to the certificate file | `/etc/k8s-cluster-gateway-certs` |
|
||||
|
||||
| Name | Description | Value |
|
||||
| ------------------------------------------------------- | -------------------------------- | -------------------------------- |
|
||||
| `multicluster.enabled` | Whether to enable multi-cluster | `true` |
|
||||
| `multicluster.clusterGateway.replicaCount` | ClusterGateway replica count | `1` |
|
||||
| `multicluster.clusterGateway.port` | ClusterGateway port | `9443` |
|
||||
| `multicluster.clusterGateway.image.repository` | ClusterGateway image repository | `oamdev/cluster-gateway` |
|
||||
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.8.0-alpha.3` |
|
||||
| `multicluster.clusterGateway.image.pullPolicy` | ClusterGateway image pull policy | `IfNotPresent` |
|
||||
| `multicluster.clusterGateway.resources.requests.cpu` | ClusterGateway cpu request | `50m` |
|
||||
| `multicluster.clusterGateway.resources.requests.memory` | ClusterGateway memory request | `20Mi` |
|
||||
| `multicluster.clusterGateway.resources.limits.cpu` | ClusterGateway cpu limit | `500m` |
|
||||
| `multicluster.clusterGateway.resources.limits.memory` | ClusterGateway memory limit | `200Mi` |
|
||||
| `multicluster.clusterGateway.secureTLS.enabled` | Whether to enable secure TLS | `true` |
|
||||
| `multicluster.clusterGateway.secureTLS.certPath` | Path to the certificate file | `/etc/k8s-cluster-gateway-certs` |
|
||||
|
||||
### Test parameters
|
||||
|
||||
@@ -122,7 +125,6 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-minimal --
|
||||
| `test.k8s.repository` | Test k8s repository | `oamdev/alpine-k8s` |
|
||||
| `test.k8s.tag` | Test k8s tag | `1.18.2` |
|
||||
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
|
||||
@@ -2306,6 +2306,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it
|
||||
defines the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow
|
||||
step.
|
||||
@@ -4168,6 +4173,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it defines
|
||||
the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
|
||||
@@ -1068,6 +1068,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it defines
|
||||
the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/check-metrics.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
custom.definition.oam.dev/category: Application Delivery
|
||||
definition.oam.dev/description: Verify application's metrics
|
||||
labels:
|
||||
custom.definition.oam.dev/catalog: Delivery
|
||||
name: check-metrics
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
check: op.#PromCheck & {
|
||||
query: parameter.query
|
||||
metricEndpoint: parameter.metricEndpoint
|
||||
condition: parameter.condition
|
||||
stepID: context.stepSessionID
|
||||
duration: parameter.duration
|
||||
failDuration: parameter.failDuration
|
||||
}
|
||||
fail: op.#Steps & {
|
||||
if check.failed != _|_ {
|
||||
if check.failed == true {
|
||||
breakWorkflow: op.#Fail & {
|
||||
message: check.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: check.result
|
||||
if check.message != _|_ {
|
||||
message: check.message
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Query is a raw prometheus query to perform
|
||||
query: string
|
||||
// +usage=The HTTP address and port of the prometheus server
|
||||
metricEndpoint?: "http://prometheus-server.o11y-system.svc:9090" | string
|
||||
// +usage=Condition is an expression which determines if a measurement is considered successful. eg: >=0.95
|
||||
condition: string
|
||||
// +usage=Duration defines the duration of time required for this step to be considered successful.
|
||||
duration?: *"5m" | string
|
||||
// +usage=FailDuration is the duration of time that, if the check fails, will result in the step being marked as failed.
|
||||
failDuration?: *"2m" | string
|
||||
}
|
||||
|
||||
@@ -318,8 +318,5 @@ spec:
|
||||
failureThreshold: *3 | int
|
||||
}
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
type: cronjobs.batch
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ spec:
|
||||
template: |
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
outputs: service: {
|
||||
@@ -25,22 +26,66 @@ spec:
|
||||
metadata: name: context.name
|
||||
metadata: annotations: parameter.annotations
|
||||
spec: {
|
||||
selector: "app.oam.dev/component": context.name
|
||||
ports: [
|
||||
for p in parameter.port {
|
||||
name: "port-" + strconv.FormatInt(p, 10)
|
||||
port: p
|
||||
targetPort: p
|
||||
if parameter["matchLabels"] == _|_ {
|
||||
selector: "app.oam.dev/component": context.name
|
||||
}
|
||||
if parameter["matchLabels"] != _|_ {
|
||||
selector: parameter["matchLabels"]
|
||||
}
|
||||
|
||||
// compatible with the old way
|
||||
if parameter["port"] != _|_ if parameter["ports"] == _|_ {
|
||||
ports: [
|
||||
for p in parameter.port {
|
||||
name: "port-" + strconv.FormatInt(p, 10)
|
||||
port: p
|
||||
targetPort: p
|
||||
},
|
||||
]
|
||||
}
|
||||
if parameter["ports"] != _|_ {
|
||||
ports: [ for v in parameter.ports {
|
||||
port: v.port
|
||||
targetPort: v.port
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
_name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
name: *_name | string
|
||||
if v.protocol != "TCP" {
|
||||
name: _name + "-" + strings.ToLower(v.protocol)
|
||||
}
|
||||
}
|
||||
if v.nodePort != _|_ if parameter.type == "NodePort" {
|
||||
nodePort: v.nodePort
|
||||
}
|
||||
if v.protocol != _|_ {
|
||||
protocol: v.protocol
|
||||
}
|
||||
},
|
||||
]
|
||||
]
|
||||
}
|
||||
type: parameter.type
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the exposion ports
|
||||
port: [...int]
|
||||
// +usage=Deprecated, the old way to specify the exposion ports
|
||||
port?: [...int]
|
||||
// +usage=Specify portsyou want customer traffic sent to
|
||||
ports?: [...{
|
||||
// +usage=Number of port to expose on the pod's IP address
|
||||
port: int
|
||||
// +usage=Name of the port
|
||||
name?: string
|
||||
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
|
||||
protocol: *"TCP" | "UDP" | "SCTP"
|
||||
// +usage=exposed node port. Only Valid when exposeType is NodePort
|
||||
nodePort?: int
|
||||
}]
|
||||
// +usage=Specify the annotaions of the exposed service
|
||||
annotations: [string]: string
|
||||
annotations: [string]: string
|
||||
matchLabels?: [string]: string
|
||||
// +usage=Specify what kind of Service you want. options: "ClusterIP","NodePort","LoadBalancer","ExternalName"
|
||||
type: *"ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
|
||||
}
|
||||
|
||||
@@ -113,19 +113,20 @@ spec:
|
||||
}
|
||||
if context.outputs.ingress.status.loadBalancer.ingress != _|_ {
|
||||
let igs = context.outputs.ingress.status.loadBalancer.ingress
|
||||
let host = context.outputs.ingress.spec.rules[0].host
|
||||
if igs[0].ip != _|_ {
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host + ", IP: " + igs[0].ip
|
||||
}
|
||||
if igs[0].host == _|_ {
|
||||
if host == _|_ {
|
||||
message: "Host not specified, visit the cluster or load balancer in front of the cluster with IP: " + igs[0].ip
|
||||
}
|
||||
}
|
||||
if igs[0].ip == _|_ {
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host
|
||||
}
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Host not specified, visit the cluster or load balancer in front of the cluster"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,10 +166,8 @@ spec:
|
||||
- "--use-webhook=true"
|
||||
- "--webhook-port={{ .Values.webhookService.port }}"
|
||||
- "--webhook-cert-dir={{ .Values.admissionWebhooks.certificate.mountPath }}"
|
||||
- "--autogen-workload-definition={{ .Values.admissionWebhooks.autoGenWorkloadDefinition }}"
|
||||
{{ end }}
|
||||
- "--health-addr=:{{ .Values.healthCheck.port }}"
|
||||
- "--apply-once-only={{ .Values.applyOnceOnly }}"
|
||||
{{ if ne .Values.disableCaps "" }}
|
||||
- "--disable-caps={{ .Values.disableCaps }}"
|
||||
{{ end }}
|
||||
@@ -188,6 +186,7 @@ spec:
|
||||
- "--max-workflow-step-error-retry-times={{ .Values.workflow.step.errorRetryTimes }}"
|
||||
- "--feature-gates=EnableSuspendOnFailure={{- .Values.workflow.enableSuspendOnFailure | toString -}}"
|
||||
- "--feature-gates=AuthenticateApplication={{- .Values.authentication.enabled | toString -}}"
|
||||
- "--feature-gates=ApplyOnce={{- .Values.featureGates.applyOnce | toString -}}"
|
||||
{{ if .Values.authentication.enabled }}
|
||||
{{ if .Values.authentication.withUser }}
|
||||
- "--authentication-with-user"
|
||||
|
||||
@@ -26,9 +26,6 @@ OAMSpecVer: "minimal"
|
||||
## @param disableCaps Disable capability
|
||||
disableCaps: "envbinding,rollout"
|
||||
|
||||
## @param applyOnceOnly Valid applyOnceOnly values: true/false/on/off/force
|
||||
applyOnceOnly: "off"
|
||||
|
||||
## @param dependCheckWait dependCheckWait is the time to wait for ApplicationConfiguration's dependent-resource ready
|
||||
dependCheckWait: 30s
|
||||
|
||||
@@ -86,6 +83,11 @@ webhookService:
|
||||
healthCheck:
|
||||
port: 9440
|
||||
|
||||
## @section KubeVela controller optimization parameters
|
||||
|
||||
##@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
|
||||
featureGates:
|
||||
applyOnce: false
|
||||
|
||||
## @section MultiCluster parameters
|
||||
|
||||
@@ -95,6 +97,8 @@ healthCheck:
|
||||
## @param multicluster.clusterGateway.image.repository ClusterGateway image repository
|
||||
## @param multicluster.clusterGateway.image.tag ClusterGateway image tag
|
||||
## @param multicluster.clusterGateway.image.pullPolicy ClusterGateway image pull policy
|
||||
## @param multicluster.clusterGateway.resources.requests.cpu ClusterGateway cpu request
|
||||
## @param multicluster.clusterGateway.resources.requests.memory ClusterGateway memory request
|
||||
## @param multicluster.clusterGateway.resources.limits.cpu ClusterGateway cpu limit
|
||||
## @param multicluster.clusterGateway.resources.limits.memory ClusterGateway memory limit
|
||||
## @param multicluster.clusterGateway.secureTLS.enabled Whether to enable secure TLS
|
||||
@@ -106,11 +110,14 @@ multicluster:
|
||||
port: 9443
|
||||
image:
|
||||
repository: oamdev/cluster-gateway
|
||||
tag: v1.7.0
|
||||
tag: v1.8.0-alpha.3
|
||||
pullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 20Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
cpu: 500m
|
||||
memory: 200Mi
|
||||
secureTLS:
|
||||
enabled: true
|
||||
|
||||
@@ -96,10 +96,10 @@ This command requires `docker` in PATH as we'll run openapi-generator in docker.
|
||||
|
||||
```shell
|
||||
# Initialize the SDK, generate API from all definitions,
|
||||
vela def gen-api --init -f /path/to/def/dir -o /path/to/sdk --lang go
|
||||
vela def gen-api --init -f /path/to/def/dir -o /path/to/sdk --language go
|
||||
|
||||
# Incrementally generate API from definitions
|
||||
vela def gen-api -f /path/to/def/dir -o /path/to/sdk --lang go
|
||||
vela def gen-api -f /path/to/def/dir -o /path/to/sdk --language go
|
||||
```
|
||||
|
||||
## Future work
|
||||
|
||||
@@ -18,6 +18,7 @@ package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/e2e"
|
||||
"github.com/oam-dev/kubevela/pkg/addon"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
@@ -176,4 +178,160 @@ var _ = Describe("Addon Test", func() {
|
||||
Expect(output).To(ContainSubstring("Successfully delete an addon registry my-repo"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Enable dependency addon test", func() {
|
||||
It(" enable mock-dependence-rely without specified clusters when mock-dependence addon is not enabled", func() {
|
||||
output, err := e2e.Exec("vela addon enable mock-dependence-rely")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring("enabled successfully."))
|
||||
Eventually(func(g Gomega) {
|
||||
app := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-mock-dependence", Namespace: "vela-system"}, app)).Should(Succeed())
|
||||
topologyPolicyValue := map[string]interface{}{}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if policy.Type == "topology" {
|
||||
Expect(json.Unmarshal(policy.Properties.Raw, &topologyPolicyValue)).Should(Succeed())
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(topologyPolicyValue["clusterLabelSelector"]).Should(Equal(map[string]interface{}{}))
|
||||
}, 30*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("enable mock-dependence-rely with specified clusters when mock-dependence addon is not enabled ", func() {
|
||||
output, err := e2e.Exec("vela addon enable mock-dependence-rely2 --clusters local")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring("enabled successfully."))
|
||||
Eventually(func(g Gomega) {
|
||||
app := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-mock-dependence2", Namespace: "vela-system"}, app)).Should(Succeed())
|
||||
topologyPolicyValue := map[string]interface{}{}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if policy.Type == "topology" {
|
||||
Expect(json.Unmarshal(policy.Properties.Raw, &topologyPolicyValue)).Should(Succeed())
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(topologyPolicyValue["clusters"]).Should(Equal([]interface{}{"local"}))
|
||||
}, 30*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("enable mock-dependence-rely without specified clusters when mock-dependence addon was enabled with specified clusters", func() {
|
||||
// 1. enable mock-dependence addon with local clusters
|
||||
output, err := e2e.InteractiveExec("vela addon enable mock-dependence --clusters local myparam=test", func(c *expect.Console) {
|
||||
_, err = c.SendLine("y")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring("enabled successfully."))
|
||||
Eventually(func(g Gomega) {
|
||||
// check application render cluster
|
||||
app := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-mock-dependence", Namespace: "vela-system"}, app)).Should(Succeed())
|
||||
topologyPolicyValue := map[string]interface{}{}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if policy.Type == "topology" {
|
||||
Expect(json.Unmarshal(policy.Properties.Raw, &topologyPolicyValue)).Should(Succeed())
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(topologyPolicyValue["clusters"]).Should(Equal([]interface{}{"local"}))
|
||||
Expect(topologyPolicyValue["clusterLabelSelector"]).Should(BeNil())
|
||||
}, 600*time.Second).Should(Succeed())
|
||||
// 2. enable mock-dependence-rely addon without clusters
|
||||
output1, err := e2e.InteractiveExec("vela addon enable mock-dependence-rely", func(c *expect.Console) {
|
||||
_, err = c.SendLine("y")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output1).To(ContainSubstring("enabled successfully."))
|
||||
// 3. enable mock-dependence-rely addon changes the mock-dependence topology policy
|
||||
Eventually(func(g Gomega) {
|
||||
app := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-mock-dependence", Namespace: "vela-system"}, app)).Should(Succeed())
|
||||
topologyPolicyValue := map[string]interface{}{}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if policy.Type == "topology" {
|
||||
Expect(json.Unmarshal(policy.Properties.Raw, &topologyPolicyValue)).Should(Succeed())
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(topologyPolicyValue["clusterLabelSelector"]).Should(Equal(map[string]interface{}{}))
|
||||
Expect(topologyPolicyValue["clusters"]).Should(BeNil())
|
||||
}, 30*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test addon dependency with specified clusters", func() {
|
||||
const clusterName = "k3s-default"
|
||||
// enable addon
|
||||
output, err := e2e.InteractiveExec("vela addon enable mock-dependence --clusters local myparam=test", func(c *expect.Console) {
|
||||
_, err = c.SendLine("y")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output).To(ContainSubstring("enabled successfully."))
|
||||
output1, err := e2e.Exec("vela ls -A")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output1).To(ContainSubstring("mock-dependence"))
|
||||
output2, err := e2e.Exec("vela addon list")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(output2).To(ContainSubstring("mock-dependence"))
|
||||
// check dependence application parameter
|
||||
Eventually(func(g Gomega) {
|
||||
// check parameter
|
||||
sec := &v1.Secret{}
|
||||
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-secret-mock-dependence", Namespace: "vela-system"}, sec)).Should(Succeed())
|
||||
parameters := map[string]interface{}{}
|
||||
json.Unmarshal(sec.Data[addon.AddonParameterDataKey], ¶meters)
|
||||
g.Expect(parameters).Should(BeEquivalentTo(map[string]interface{}{
|
||||
"clusters": []interface{}{"local"},
|
||||
"myparam": "test",
|
||||
}))
|
||||
// check application render cluster
|
||||
app := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-mock-dependence", Namespace: "vela-system"}, app)).Should(Succeed())
|
||||
topologyPolicyValue := map[string]interface{}{}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if policy.Type == "topology" {
|
||||
Expect(json.Unmarshal(policy.Properties.Raw, &topologyPolicyValue)).Should(Succeed())
|
||||
break
|
||||
}
|
||||
}
|
||||
fluxcdYaml, err1 := e2e.Exec("vela status addon-mock-dependence -n vela-system -oyaml")
|
||||
Expect(err1).NotTo(HaveOccurred())
|
||||
Expect(fluxcdYaml).To(ContainSubstring("mock-dependence"))
|
||||
fluxcdStatus, err2 := e2e.Exec("vela addon status mock-dependence -v")
|
||||
Expect(err2).NotTo(HaveOccurred())
|
||||
Expect(fluxcdStatus).To(ContainSubstring("mock-dependence"))
|
||||
Expect(topologyPolicyValue["clusters"]).Should(Equal([]interface{}{"local"}))
|
||||
}, 600*time.Second).Should(Succeed())
|
||||
// enable addon which rely on mock-dependence addon
|
||||
e2e.InteractiveExec("vela addon enable mock-dependence-rely --clusters local,"+clusterName, func(c *expect.Console) {
|
||||
_, err = c.SendLine("y")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
// check mock-dependence application parameter
|
||||
Eventually(func(g Gomega) {
|
||||
sec := &v1.Secret{}
|
||||
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-secret-mock-dependence", Namespace: "vela-system"}, sec)).Should(Succeed())
|
||||
parameters := map[string]interface{}{}
|
||||
json.Unmarshal(sec.Data[addon.AddonParameterDataKey], ¶meters)
|
||||
g.Expect(parameters).Should(BeEquivalentTo(map[string]interface{}{
|
||||
"clusters": []interface{}{"local", clusterName},
|
||||
"myparam": "test",
|
||||
}))
|
||||
app := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-mock-dependence", Namespace: "vela-system"}, app)).Should(Succeed())
|
||||
topologyPolicyValue := map[string]interface{}{}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if policy.Type == "topology" {
|
||||
Expect(json.Unmarshal(policy.Properties.Raw, &topologyPolicyValue)).Should(Succeed())
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(topologyPolicyValue["clusters"]).Should(Equal([]interface{}{"local", clusterName}))
|
||||
}, 30*time.Second).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
3
e2e/addon/mock/testdata/mock-dependence-rely/README.md
vendored
Normal file
3
e2e/addon/mock/testdata/mock-dependence-rely/README.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# mock-dependence-rely
|
||||
|
||||
This is an addon template. Check how to build your own addon: https://kubevela.net/docs/platform-engineers/addon/intro
|
||||
25
e2e/addon/mock/testdata/mock-dependence-rely/definitions/mytraitb.cue
vendored
Normal file
25
e2e/addon/mock/testdata/mock-dependence-rely/definitions/mytraitb.cue
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// We put Definitions in definitions directory.
|
||||
// References:
|
||||
// - https://kubevela.net/docs/platform-engineers/cue/definition-edit
|
||||
// - https://kubevela.net/docs/platform-engineers/addon/intro#definitions-directoryoptional
|
||||
"mytraitb": {
|
||||
alias: "mtb"
|
||||
annotations: {}
|
||||
attributes: {
|
||||
appliesToWorkloads: [
|
||||
"deployments.apps",
|
||||
"replicasets.apps",
|
||||
"statefulsets.apps",
|
||||
]
|
||||
conflictsWith: []
|
||||
podDisruptive: false
|
||||
workloadRefPath: ""
|
||||
}
|
||||
description: "My trait description."
|
||||
labels: {}
|
||||
type: "trait"
|
||||
}
|
||||
template: {
|
||||
parameter: {param: ""}
|
||||
outputs: {sample: {}}
|
||||
}
|
||||
10
e2e/addon/mock/testdata/mock-dependence-rely/metadata.yaml
vendored
Normal file
10
e2e/addon/mock/testdata/mock-dependence-rely/metadata.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
description: An addon for testing addon dependency with specified clusters.
|
||||
icon: ""
|
||||
invisible: false
|
||||
name: mock-dependence-rely
|
||||
tags:
|
||||
- my-tag
|
||||
version: 1.0.0
|
||||
dependencies:
|
||||
# install controller by helm.
|
||||
- name: mock-dependence
|
||||
10
e2e/addon/mock/testdata/mock-dependence-rely/parameter.cue
vendored
Normal file
10
e2e/addon/mock/testdata/mock-dependence-rely/parameter.cue
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// parameter.cue is used to store addon parameters.
|
||||
//
|
||||
// You can use these parameters in template.cue or in resources/ by 'parameter.myparam'
|
||||
//
|
||||
// For example, you can use parameters to allow the user to customize
|
||||
// container images, ports, and etc.
|
||||
parameter: {
|
||||
// +usage=Custom parameter description
|
||||
myparam: *"mynsrely" | string
|
||||
}
|
||||
18
e2e/addon/mock/testdata/mock-dependence-rely/resources/myresourceb.cue
vendored
Normal file
18
e2e/addon/mock/testdata/mock-dependence-rely/resources/myresourceb.cue
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// We put Components in resources directory.
|
||||
// References:
|
||||
// - https://kubevela.net/docs/end-user/components/references
|
||||
// - https://kubevela.net/docs/platform-engineers/addon/intro#resources-directoryoptional
|
||||
output: {
|
||||
type: "k8s-objects"
|
||||
properties: {
|
||||
objects: [
|
||||
{
|
||||
// This creates a plain old Kubernetes namespace
|
||||
apiVersion: "v1"
|
||||
kind: "Namespace"
|
||||
// We can use the parameter defined in parameter.cue like this.
|
||||
metadata: name: parameter.myparam
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
9
e2e/addon/mock/testdata/mock-dependence-rely/template.cue
vendored
Normal file
9
e2e/addon/mock/testdata/mock-dependence-rely/template.cue
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
spec: {
|
||||
components: []
|
||||
policies: []
|
||||
}
|
||||
}
|
||||
3
e2e/addon/mock/testdata/mock-dependence-rely2/README.md
vendored
Normal file
3
e2e/addon/mock/testdata/mock-dependence-rely2/README.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# mock-dependence-rely2
|
||||
|
||||
This is an addon template. Check how to build your own addon: https://kubevela.net/docs/platform-engineers/addon/intro
|
||||
10
e2e/addon/mock/testdata/mock-dependence-rely2/metadata.yaml
vendored
Normal file
10
e2e/addon/mock/testdata/mock-dependence-rely2/metadata.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
description: An addon for testing addon dependency with specified clusters.
|
||||
icon: ""
|
||||
invisible: false
|
||||
name: mock-dependence-rely2
|
||||
tags:
|
||||
- my-tag
|
||||
version: 1.0.0
|
||||
dependencies:
|
||||
# install controller by helm.
|
||||
- name: mock-dependence2
|
||||
4
e2e/addon/mock/testdata/mock-dependence-rely2/parameter.cue
vendored
Normal file
4
e2e/addon/mock/testdata/mock-dependence-rely2/parameter.cue
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
parameter: {
|
||||
// +usage=Custom parameter description
|
||||
myparam: *"mynsrely" | string
|
||||
}
|
||||
9
e2e/addon/mock/testdata/mock-dependence-rely2/template.cue
vendored
Normal file
9
e2e/addon/mock/testdata/mock-dependence-rely2/template.cue
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
spec: {
|
||||
components: []
|
||||
policies: []
|
||||
}
|
||||
}
|
||||
3
e2e/addon/mock/testdata/mock-dependence/README.md
vendored
Normal file
3
e2e/addon/mock/testdata/mock-dependence/README.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# mock-dependence
|
||||
|
||||
This is an addon template. Check how to build your own addon: https://kubevela.net/docs/platform-engineers/addon/intro
|
||||
25
e2e/addon/mock/testdata/mock-dependence/definitions/mytrait.cue
vendored
Normal file
25
e2e/addon/mock/testdata/mock-dependence/definitions/mytrait.cue
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// We put Definitions in definitions directory.
|
||||
// References:
|
||||
// - https://kubevela.net/docs/platform-engineers/cue/definition-edit
|
||||
// - https://kubevela.net/docs/platform-engineers/addon/intro#definitions-directoryoptional
|
||||
"mytrait-depend": {
|
||||
alias: "mt-depend"
|
||||
annotations: {}
|
||||
attributes: {
|
||||
appliesToWorkloads: [
|
||||
"deployments.apps",
|
||||
"replicasets.apps",
|
||||
"statefulsets.apps",
|
||||
]
|
||||
conflictsWith: []
|
||||
podDisruptive: false
|
||||
workloadRefPath: ""
|
||||
}
|
||||
description: "My trait description."
|
||||
labels: {}
|
||||
type: "trait"
|
||||
}
|
||||
template: {
|
||||
parameter: {param: ""}
|
||||
outputs: {sample: {}}
|
||||
}
|
||||
7
e2e/addon/mock/testdata/mock-dependence/metadata.yaml
vendored
Normal file
7
e2e/addon/mock/testdata/mock-dependence/metadata.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
description: An addon for testing addon dependency with specified clusters.
|
||||
icon: ""
|
||||
invisible: false
|
||||
name: mock-dependence
|
||||
tags:
|
||||
- my-tag
|
||||
version: 1.0.0
|
||||
12
e2e/addon/mock/testdata/mock-dependence/parameter.cue
vendored
Normal file
12
e2e/addon/mock/testdata/mock-dependence/parameter.cue
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// parameter.cue is used to store addon parameters.
|
||||
//
|
||||
// You can use these parameters in template.cue or in resources/ by 'parameter.myparam'
|
||||
//
|
||||
// For example, you can use parameters to allow the user to customize
|
||||
// container images, ports, and etc.
|
||||
parameter: {
|
||||
// +usage=Custom parameter description
|
||||
myparam: *"myns" | string
|
||||
//+usage=Deploy to specified clusters. Leave empty to deploy to all clusters.
|
||||
clusters?: [...string]
|
||||
}
|
||||
18
e2e/addon/mock/testdata/mock-dependence/resources/myresource-depend.cue
vendored
Normal file
18
e2e/addon/mock/testdata/mock-dependence/resources/myresource-depend.cue
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// We put Components in resources directory.
|
||||
// References:
|
||||
// - https://kubevela.net/docs/end-user/components/references
|
||||
// - https://kubevela.net/docs/platform-engineers/addon/intro#resources-directoryoptional
|
||||
output: {
|
||||
type: "k8s-objects"
|
||||
properties: {
|
||||
objects: [
|
||||
{
|
||||
// This creates a plain old Kubernetes namespace
|
||||
apiVersion: "v1"
|
||||
kind: "Namespace"
|
||||
// We can use the parameter defined in parameter.cue like this.
|
||||
metadata: name: parameter.myparam
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
24
e2e/addon/mock/testdata/mock-dependence/template.cue
vendored
Normal file
24
e2e/addon/mock/testdata/mock-dependence/template.cue
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
_targetNamespace: parameter.myparam
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
spec: {
|
||||
components: [],
|
||||
policies: [
|
||||
{
|
||||
type: "topology"
|
||||
name: "deploy-mock-dependency-ns"
|
||||
properties: {
|
||||
namespace: _targetNamespace
|
||||
if parameter.clusters != _|_ {
|
||||
clusters: parameter.clusters
|
||||
}
|
||||
if parameter.clusters == _|_ {
|
||||
clusterLabelSelector: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
3
e2e/addon/mock/testdata/mock-dependence2/README.md
vendored
Normal file
3
e2e/addon/mock/testdata/mock-dependence2/README.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# mock-dependence2
|
||||
|
||||
This is an addon template. Check how to build your own addon: https://kubevela.net/docs/platform-engineers/addon/intro
|
||||
7
e2e/addon/mock/testdata/mock-dependence2/metadata.yaml
vendored
Normal file
7
e2e/addon/mock/testdata/mock-dependence2/metadata.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
description: An addon for testing addon dependency with specified clusters.
|
||||
icon: ""
|
||||
invisible: false
|
||||
name: mock-dependence2
|
||||
tags:
|
||||
- my-tag
|
||||
version: 1.0.0
|
||||
6
e2e/addon/mock/testdata/mock-dependence2/parameter.cue
vendored
Normal file
6
e2e/addon/mock/testdata/mock-dependence2/parameter.cue
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
parameter: {
|
||||
// +usage=Custom parameter description
|
||||
myparam: *"myns" | string
|
||||
//+usage=Deploy to specified clusters. Leave empty to deploy to all clusters.
|
||||
clusters?: [...string]
|
||||
}
|
||||
24
e2e/addon/mock/testdata/mock-dependence2/template.cue
vendored
Normal file
24
e2e/addon/mock/testdata/mock-dependence2/template.cue
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
_targetNamespace: parameter.myparam
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
spec: {
|
||||
components: [],
|
||||
policies: [
|
||||
{
|
||||
type: "topology"
|
||||
name: "deploy-mock-dependency-ns"
|
||||
properties: {
|
||||
namespace: _targetNamespace
|
||||
if parameter.clusters != _|_ {
|
||||
clusters: parameter.clusters
|
||||
}
|
||||
if parameter.clusters == _|_ {
|
||||
clusterLabelSelector: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
@@ -52,7 +52,7 @@ require (
|
||||
github.com/imdario/mergo v0.3.13
|
||||
github.com/kubevela/pkg v0.0.0-20230316114047-e2b41b377bac
|
||||
github.com/kubevela/prism v1.7.0-alpha.1
|
||||
github.com/kubevela/workflow v0.4.1-0.20230313085319-59e7c1c967fe
|
||||
github.com/kubevela/workflow v0.5.1-0.20230412142923-1f15ba091699
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -892,6 +892,7 @@ github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9q
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -953,8 +954,8 @@ github.com/kubevela/pkg v0.0.0-20230316114047-e2b41b377bac h1:TLQchMx+BRTnHyebDp
|
||||
github.com/kubevela/pkg v0.0.0-20230316114047-e2b41b377bac/go.mod h1:GilLxt+9L4sU2tLeZAGHga8wiYmjjfPX/Q6JkyuuXSM=
|
||||
github.com/kubevela/prism v1.7.0-alpha.1 h1:oeZFn1Oy6gxSSFzMTfsWjLOCKaaooMVm1JGNK4j4Mlo=
|
||||
github.com/kubevela/prism v1.7.0-alpha.1/go.mod h1:AJSDfdA+RkRSnWx3xEcogbmOTpX+l7RSIwqVHxwUtaI=
|
||||
github.com/kubevela/workflow v0.4.1-0.20230313085319-59e7c1c967fe h1:Ke96MD4rRhXkua9AbuXvcprptkwE8mD/At4AZsL14ls=
|
||||
github.com/kubevela/workflow v0.4.1-0.20230313085319-59e7c1c967fe/go.mod h1:bTBlr/0s3oaxYDN0qzp33lgYA9ESkpFdPdJjSAym3V4=
|
||||
github.com/kubevela/workflow v0.5.1-0.20230412142923-1f15ba091699 h1:XvHs/8a10AvHnetlGSpylFnx8PFvONTzmR9CAzFAHbY=
|
||||
github.com/kubevela/workflow v0.5.1-0.20230412142923-1f15ba091699/go.mod h1:+Ah40fwzX9fi/xeWdphew9J4kqfJiGwXw5MDeGPq3IU=
|
||||
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
@@ -1110,6 +1111,7 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
|
||||
@@ -41,6 +41,10 @@ Set `CLUSTER=3` will inject the `CLUSTER` variable to the app template. You can
|
||||
|
||||
> To make multicluster load testing environment, you can set up multiple k3d instances and register them in the control plane.
|
||||
|
||||
#### QPS
|
||||
|
||||
By default, there is no rate limit for the client. If you want to set the QPS for each worker, you can use `QPS=2`.
|
||||
|
||||
### Cleanup
|
||||
|
||||
Run `SHARD=3 WORKER=4 BEGIN=0 SIZE=1000 bash cleanup.sh` to delete `app-0` to `app-999` from namespace `load-test-0` to `load-test-2` in 4 threads.
|
||||
Run `SHARD=3 WORKER=4 BEGIN=0 SIZE=1000 bash cleanup.sh` to delete `app-0` to `app-999` from namespace `load-test-0` to `load-test-2` in 4 threads.
|
||||
|
||||
@@ -5,6 +5,7 @@ SIZE=${SIZE:-1000}
|
||||
WORKER=${WORKER:-6}
|
||||
VERSION=${VERSION:-1}
|
||||
CLUSTER=${CLUSTER:-4}
|
||||
QPS=${QPS:-1}
|
||||
|
||||
SHARD=${SHARD:-3}
|
||||
|
||||
@@ -12,6 +13,8 @@ TEMPLATE=${TEMPLATE:-"light"}
|
||||
|
||||
END=$(expr $BEGIN + $SIZE - 1)
|
||||
|
||||
waitTime=$(expr 1000 / $QPS)e-3
|
||||
|
||||
run() {
|
||||
for i in $(seq $1 $3 $2); do
|
||||
sid=$(expr $i % $SHARD)
|
||||
@@ -24,6 +27,7 @@ run() {
|
||||
sed 's/CLUSTER/'$c'/g' | \
|
||||
kubectl apply -f -
|
||||
echo "worker $4: apply app $i to $sid"
|
||||
sleep $waitTime
|
||||
done
|
||||
echo "worker $4: done"
|
||||
}
|
||||
@@ -32,4 +36,4 @@ for i in $(seq 1 $WORKER); do
|
||||
run $(expr $BEGIN + $i - 1) $END $WORKER $i &
|
||||
done
|
||||
|
||||
wait
|
||||
wait
|
||||
|
||||
@@ -33,7 +33,7 @@ config() {
|
||||
git config --global user.name "kubevela-bot"
|
||||
}
|
||||
|
||||
cloneAndClearRepo() {
|
||||
cloneAndClearCoreAPI() {
|
||||
echo "git clone"
|
||||
|
||||
if [[ -n "$SSH_DEPLOY_KEY" ]]; then
|
||||
@@ -42,8 +42,13 @@ cloneAndClearRepo() {
|
||||
git clone --single-branch --depth 1 https://github.com/$VELA_GO_SDK.git kubevela-go-sdk
|
||||
fi
|
||||
|
||||
echo "Clear kubevela-go-sdk pkg/*"
|
||||
rm -r kubevela-go-sdk/pkg/*
|
||||
echo "Clear kubevela-go-sdk pkg/apis/common, pkg/apis/component, pkg/apis/policy, pkg/apis/trait, pkg/apis/workflow-step, pkg/apis/utils, pkg/apis/types.go "
|
||||
rm -rf kubevela-go-sdk/pkg/apis/common
|
||||
rm -rf kubevela-go-sdk/pkg/apis/component
|
||||
rm -rf kubevela-go-sdk/pkg/apis/policy
|
||||
rm -rf kubevela-go-sdk/pkg/apis/trait
|
||||
rm -rf kubevela-go-sdk/pkg/apis/workflow-step
|
||||
rm -rf kubevela-go-sdk/pkg/apis/utils
|
||||
}
|
||||
|
||||
updateRepo() {
|
||||
@@ -79,7 +84,7 @@ syncRepo() {
|
||||
|
||||
main() {
|
||||
config
|
||||
cloneAndClearRepo
|
||||
cloneAndClearCoreAPI
|
||||
updateRepo
|
||||
syncRepo
|
||||
}
|
||||
|
||||
@@ -2306,6 +2306,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it
|
||||
defines the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow
|
||||
step.
|
||||
@@ -4168,6 +4173,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it defines
|
||||
the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
|
||||
@@ -1068,6 +1068,11 @@ spec:
|
||||
alias:
|
||||
type: string
|
||||
type: object
|
||||
mode:
|
||||
description: Mode is only valid for sub steps, it defines
|
||||
the mode of the sub steps
|
||||
nullable: true
|
||||
type: string
|
||||
name:
|
||||
description: Name is the unique name of the workflow step.
|
||||
type: string
|
||||
|
||||
@@ -54,6 +54,7 @@ import (
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/klog/v2"
|
||||
stringslices "k8s.io/utils/strings/slices"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
@@ -999,20 +1000,29 @@ func (h *Installer) getAddonMeta() (map[string]SourceMeta, error) {
|
||||
// installDependency checks if addon's dependency and install it
|
||||
func (h *Installer) installDependency(addon *InstallPackage) error {
|
||||
var dependencies []string
|
||||
var addonClusters = getClusters(h.args)
|
||||
for _, dep := range addon.Dependencies {
|
||||
_, err := FetchAddonRelatedApp(h.ctx, h.cli, dep.Name)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if !apierrors.IsNotFound(err) {
|
||||
needInstallAddonDep, depClusters, err := checkDependencyNeedInstall(h.ctx, h.cli, dep.Name, addonClusters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !needInstallAddonDep {
|
||||
continue
|
||||
}
|
||||
|
||||
dependencies = append(dependencies, dep.Name)
|
||||
if h.dryRun {
|
||||
continue
|
||||
}
|
||||
depHandler := *h
|
||||
depHandler.args = nil
|
||||
// reset dependency addon clusters parameter
|
||||
depArgs, depArgsErr := getDependencyArgs(h.ctx, h.cli, dep.Name, depClusters)
|
||||
if depArgsErr != nil {
|
||||
return depArgsErr
|
||||
}
|
||||
|
||||
depHandler.args = depArgs
|
||||
|
||||
var depAddon *InstallPackage
|
||||
// try to install the dependent addon from the same registry with the current addon
|
||||
depAddon, err = h.loadInstallPackage(dep.Name, dep.Version)
|
||||
@@ -1062,6 +1072,72 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDependencyNeedInstall checks whether dependency addon needs to be installed on other clusters
|
||||
func checkDependencyNeedInstall(ctx context.Context, k8sClient client.Client, depName string, addonClusters []string) (bool, []string, error) {
|
||||
depApp, err := FetchAddonRelatedApp(ctx, k8sClient, depName)
|
||||
if err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return false, nil, err
|
||||
}
|
||||
// dependent addon is not exist
|
||||
return true, addonClusters, nil
|
||||
}
|
||||
topologyPolicyValue := map[string]interface{}{}
|
||||
for _, policy := range depApp.Spec.Policies {
|
||||
if policy.Type == "topology" {
|
||||
unmarshalErr := json.Unmarshal(policy.Properties.Raw, &topologyPolicyValue)
|
||||
if unmarshalErr != nil {
|
||||
return false, nil, unmarshalErr
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// nil clusters indicates that the dependent addon is installed on all clusters
|
||||
if topologyPolicyValue["clusters"] == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
// nil addonClusters indicates the addon will be installed,
|
||||
// thus we should set the dependent addon's clusters arg to be nil so that it is installed on all clusters
|
||||
if addonClusters == nil {
|
||||
return true, nil, nil
|
||||
}
|
||||
// Determine whether the dependent addon's existing clusters can cover the new addon's clusters
|
||||
var needInstallAddonDep = false
|
||||
var depClusters []string
|
||||
originClusters := topologyPolicyValue["clusters"].([]interface{})
|
||||
for _, r := range originClusters {
|
||||
depClusters = append(depClusters, r.(string))
|
||||
}
|
||||
for _, addonCluster := range addonClusters {
|
||||
if !stringslices.Contains(depClusters, addonCluster) {
|
||||
depClusters = append(depClusters, addonCluster)
|
||||
needInstallAddonDep = true
|
||||
}
|
||||
}
|
||||
return needInstallAddonDep, depClusters, nil
|
||||
}
|
||||
|
||||
// getDependencyArgs resets the dependency clusters arg according needed install depClusters
|
||||
func getDependencyArgs(ctx context.Context, k8sClient client.Client, depName string, depClusters []string) (map[string]interface{}, error) {
|
||||
depArgs, depArgsErr := GetAddonLegacyParameters(ctx, k8sClient, depName)
|
||||
if depArgsErr != nil && !apierrors.IsNotFound(depArgsErr) {
|
||||
return nil, depArgsErr
|
||||
}
|
||||
// reset the cluster arg
|
||||
if depClusters == nil {
|
||||
// delete clusters args, when render addon, it will use clusterLabelSelector then render addon to all clusters
|
||||
if depArgs != nil && depArgs[types.ClustersArg] != nil {
|
||||
delete(depArgs, types.ClustersArg)
|
||||
}
|
||||
} else {
|
||||
if depArgs == nil {
|
||||
depArgs = map[string]interface{}{}
|
||||
}
|
||||
depArgs[types.ClustersArg] = depClusters
|
||||
}
|
||||
return depArgs, nil
|
||||
}
|
||||
|
||||
// checkDependency checks if addon's dependency
|
||||
func (h *Installer) checkDependency(addon *InstallPackage) ([]string, error) {
|
||||
var app v1beta1.Application
|
||||
|
||||
@@ -174,6 +174,107 @@ var _ = Describe("Addon test", func() {
|
||||
}, 30*time.Second, 300*time.Millisecond).Should(BeNil())
|
||||
})
|
||||
|
||||
It("checkDependencyNeedInstall func test", func() {
|
||||
// case1: dependency addon not exist, adonClusters is not nil
|
||||
depAddonName := "legacy-addon"
|
||||
addonClusters := []string{"cluster1", "cluster2"}
|
||||
needInstallAddonDep, depClusters, err := checkDependencyNeedInstall(ctx, k8sClient, depAddonName, addonClusters)
|
||||
Expect(needInstallAddonDep).Should(BeTrue())
|
||||
Expect(depClusters).Should(Equal(addonClusters))
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// case1.1: dependency addon not exist, adonClusters is nil
|
||||
needInstallAddonDep1, depClusters1, err := checkDependencyNeedInstall(ctx, k8sClient, depAddonName, nil)
|
||||
Expect(needInstallAddonDep1).Should(BeTrue())
|
||||
Expect(depClusters1).Should(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// case2: dependency addon exist, no topology policy, addonClusters is not nil
|
||||
app = v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(legacyAppYaml), &app)).Should(BeNil())
|
||||
app.SetNamespace(testns)
|
||||
Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
|
||||
Eventually(func(g Gomega) {
|
||||
needInstallAddonDep, depClusters, err := checkDependencyNeedInstall(ctx, k8sClient, depAddonName, addonClusters)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(needInstallAddonDep).Should(BeFalse())
|
||||
Expect(depClusters).Should(BeNil())
|
||||
}, 30*time.Second).Should(Succeed())
|
||||
|
||||
// case3: clusters is nil (no topology policy), addonClusters is nil
|
||||
needInstallAddonDep2, depClusters2, err := checkDependencyNeedInstall(ctx, k8sClient, depAddonName, nil)
|
||||
Expect(needInstallAddonDep2).Should(BeFalse())
|
||||
Expect(depClusters2).Should(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// case4: clusters is nil, addonClusters is nil
|
||||
app = v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(legacy3AppYaml), &app)).Should(BeNil())
|
||||
app.SetNamespace(testns)
|
||||
Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
|
||||
Eventually(func(g Gomega) {
|
||||
needInstallAddonDep, depClusters, err := checkDependencyNeedInstall(ctx, k8sClient, "legacy-addon3", nil)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(needInstallAddonDep).Should(BeFalse())
|
||||
Expect(depClusters).Should(BeNil())
|
||||
}, 60*time.Second).Should(Succeed())
|
||||
|
||||
// case5: clusters is not nil, addonClusters is nil,
|
||||
app = v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(legacy2AppYaml), &app)).Should(BeNil())
|
||||
app.SetNamespace(testns)
|
||||
Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
|
||||
Eventually(func(g Gomega) {
|
||||
needInstallAddonDep, depClusters, err := checkDependencyNeedInstall(ctx, k8sClient, "legacy-addon2", nil)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(needInstallAddonDep).Should(BeTrue())
|
||||
Expect(depClusters).Should(BeNil())
|
||||
}, 60*time.Second).Should(Succeed())
|
||||
|
||||
// case6: clusters is [local], addonClusters is ["cluster1", "cluster2"]
|
||||
Eventually(func(g Gomega) {
|
||||
needInstallAddonDep, depClusters, err := checkDependencyNeedInstall(ctx, k8sClient, "legacy-addon2", addonClusters)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(needInstallAddonDep).Should(BeTrue())
|
||||
Expect(depClusters).Should(Equal(append([]string{"local"}, addonClusters...)))
|
||||
}, 60*time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("getDependencyArgs func test", func() {
|
||||
// case1: depClusters is nil
|
||||
depAddonName := "legacy-addon"
|
||||
depArgs, err := getDependencyArgs(ctx, k8sClient, depAddonName, nil)
|
||||
Expect(depArgs).Should(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// case2: depClusters is not nil
|
||||
app = v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(legacyAppYaml), &app)).Should(BeNil())
|
||||
app.SetNamespace(testns)
|
||||
//Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
|
||||
depClusters := []string{"cluster1", "cluster2"}
|
||||
depArgs2, err := getDependencyArgs(ctx, k8sClient, depAddonName, depClusters)
|
||||
Expect(depArgs2["clusters"]).Should(Equal(depClusters))
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// clusters exist, depClusters is nil
|
||||
sec := v1.Secret{}
|
||||
Expect(yaml.Unmarshal([]byte(secretYaml), &sec)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &sec)).Should(BeNil())
|
||||
depArgs3, err := getDependencyArgs(ctx, k8sClient, "fluxcd", nil)
|
||||
Expect(depArgs3).ToNot(BeNil())
|
||||
Expect(depArgs3["clusters"]).Should(BeNil())
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// getArgs throw exception
|
||||
sec1 := v1.Secret{}
|
||||
Expect(yaml.Unmarshal([]byte(secretErrorYaml), &sec1)).Should(BeNil())
|
||||
Expect(k8sClient.Create(ctx, &sec1)).Should(BeNil())
|
||||
depArgs4, err := getDependencyArgs(ctx, k8sClient, "fluxcd1", nil)
|
||||
Expect(depArgs4).Should(BeNil())
|
||||
Expect(err).ToNot(BeNil())
|
||||
})
|
||||
|
||||
It(" determineAddonAppName func test", func() {
|
||||
app = v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal([]byte(legacyAppYaml), &app)).Should(BeNil())
|
||||
@@ -577,6 +678,60 @@ spec:
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8000
|
||||
`
|
||||
legacy2AppYaml = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: legacy-addon2
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8000
|
||||
policies:
|
||||
- name: target-default
|
||||
type: topology
|
||||
properties:
|
||||
clusters: ["local"]
|
||||
namespace: "default"
|
||||
`
|
||||
legacy3AppYaml = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: legacy-addon3
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
type: webservice
|
||||
properties:
|
||||
image: crccheck/hello-world
|
||||
port: 8000
|
||||
policies:
|
||||
- name: target-default
|
||||
type: topology
|
||||
properties:
|
||||
clusterLabelSelector: {}
|
||||
namespace: "default"
|
||||
`
|
||||
secretYaml = `apiVersion: v1
|
||||
data:
|
||||
addonParameterDataKey: eyJjbHVzdGVycyI6WyJsb2NhbCIsInZlbGEtbTEiXX0K
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: addon-secret-fluxcd
|
||||
namespace: vela-system
|
||||
type: Opaque
|
||||
`
|
||||
secretErrorYaml = `apiVersion: v1
|
||||
data:
|
||||
addonParameterDataKey: eyJjbHVzdGVycyI6WyJsb2NhbCIsInZlbGEtbTEiXQo=
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: addon-secret-fluxcd1
|
||||
namespace: vela-system
|
||||
type: Opaque
|
||||
`
|
||||
deployYaml = `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
|
||||
@@ -83,17 +83,21 @@ type WholeAddonPackage struct {
|
||||
|
||||
// Meta defines the format for a single addon
|
||||
type Meta struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
// UXPlugins used for velaux plugins download/install with the use of addon registry.
|
||||
UXPlugins map[string]string `json:"uxPlugins,omitempty"`
|
||||
DeployTo *DeployTo `json:"deployTo,omitempty"`
|
||||
Dependencies []*Dependency `json:"dependencies,omitempty"`
|
||||
NeedNamespace []string `json:"needNamespace,omitempty"`
|
||||
Invisible bool `json:"invisible"`
|
||||
SystemRequirements *SystemRequirements `json:"system,omitempty"`
|
||||
// Annotations used for addon maintainers to add their own description or extensions to metadata.
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// DeployTo defines where the addon to deploy to
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kubevela/pkg/multicluster"
|
||||
"github.com/pkg/errors"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -70,6 +71,7 @@ type Template struct {
|
||||
// It returns a helper struct, Template, which will be used for further
|
||||
// processing.
|
||||
func LoadTemplate(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli client.Reader, capName string, capType types.CapType) (*Template, error) {
|
||||
ctx = multicluster.WithCluster(ctx, multicluster.Local)
|
||||
// Application Controller only load template from ComponentDefinition and TraitDefinition
|
||||
switch capType {
|
||||
case types.TypeComponentDefinition, types.TypeWorkload:
|
||||
|
||||
@@ -1434,6 +1434,111 @@ var _ = Describe("Test Application Controller", func() {
|
||||
Expect(checkApp.Status.Workflow.Mode).Should(BeEquivalentTo(fmt.Sprintf("%s-%s", workflowv1alpha1.WorkflowModeDAG, workflowv1alpha1.WorkflowModeStep)))
|
||||
})
|
||||
|
||||
It("application with mode in workflow step group", func() {
|
||||
ns := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-group-mode",
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, &ns)).Should(BeNil())
|
||||
healthComponentDef := &v1beta1.ComponentDefinition{}
|
||||
hCDefJson, _ := yaml.YAMLToJSON([]byte(cdDefWithHealthStatusYaml))
|
||||
Expect(json.Unmarshal(hCDefJson, healthComponentDef)).Should(BeNil())
|
||||
healthComponentDef.Name = "worker-with-health"
|
||||
healthComponentDef.Namespace = "app-with-group-mode"
|
||||
Expect(k8sClient.Create(ctx, healthComponentDef)).Should(BeNil())
|
||||
app := &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-group-mode",
|
||||
Namespace: "app-with-group-mode",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "myweb1",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb3",
|
||||
Type: "worker",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2",
|
||||
Type: "worker-with-health",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox","lives": "i am lives","enemies": "empty"}`)},
|
||||
},
|
||||
},
|
||||
Workflow: &v1beta1.Workflow{
|
||||
Mode: &workflowv1alpha1.WorkflowExecuteMode{
|
||||
Steps: workflowv1alpha1.WorkflowModeDAG,
|
||||
},
|
||||
Steps: []workflowv1alpha1.WorkflowStep{
|
||||
{
|
||||
WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
|
||||
Name: "myweb1",
|
||||
Type: "apply-component",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb1"}`)},
|
||||
},
|
||||
},
|
||||
{
|
||||
WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
|
||||
Name: "myweb2",
|
||||
Type: "step-group",
|
||||
},
|
||||
Mode: workflowv1alpha1.WorkflowModeStep,
|
||||
SubSteps: []workflowv1alpha1.WorkflowStepBase{
|
||||
{
|
||||
Name: "myweb2-sub1",
|
||||
Type: "apply-component",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb2"}`)},
|
||||
},
|
||||
{
|
||||
Name: "myweb2-sub2",
|
||||
Type: "apply-component",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"component":"myweb3"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(k8sClient.Create(context.Background(), app)).Should(BeNil())
|
||||
appKey := types.NamespacedName{Namespace: ns.Name, Name: app.Name}
|
||||
testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
|
||||
expDeployment := &v1.Deployment{}
|
||||
web3Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb3"}
|
||||
Expect(k8sClient.Get(ctx, web3Key, expDeployment)).Should(util.NotFoundMatcher{})
|
||||
|
||||
web1Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb1"}
|
||||
Expect(k8sClient.Get(ctx, web1Key, expDeployment)).Should(BeNil())
|
||||
expDeployment.Status.Replicas = 1
|
||||
expDeployment.Status.ReadyReplicas = 1
|
||||
Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil())
|
||||
web2Key := types.NamespacedName{Namespace: ns.Name, Name: "myweb2"}
|
||||
Expect(k8sClient.Get(ctx, web2Key, expDeployment)).Should(BeNil())
|
||||
expDeployment.Status.Replicas = 1
|
||||
expDeployment.Status.ReadyReplicas = 1
|
||||
Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil())
|
||||
|
||||
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
|
||||
|
||||
Expect(k8sClient.Get(ctx, web3Key, expDeployment)).Should(BeNil())
|
||||
|
||||
checkApp := &v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning))
|
||||
Expect(checkApp.Status.Workflow.Mode).Should(BeEquivalentTo(fmt.Sprintf("%s-%s", workflowv1alpha1.WorkflowModeDAG, workflowv1alpha1.WorkflowModeDAG)))
|
||||
})
|
||||
|
||||
It("application with sub steps", func() {
|
||||
ns := corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
@@ -4,6 +4,8 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/oam-dev/kubevela-core-api v1.5.8
|
||||
|
||||
// for main module
|
||||
github.com/pkg/errors v0.9.1
|
||||
k8s.io/apimachinery v0.23.6
|
||||
k8s.io/client-go v0.23.6
|
||||
@@ -11,6 +13,9 @@ require (
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
// for sub-module
|
||||
// require github.com/kubevela/vela-go-sdk v0.0.0-20230309022604-cd431bb25a9a
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.5.0-alpha.1 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
|
||||
@@ -285,14 +285,14 @@ func (a *ApplicationBuilder) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
func FromK8sObject(app v1beta1.Application) (TypedApplication, error) {
|
||||
a := &ApplicationBuilder{}
|
||||
a.Name(app.Name)
|
||||
a.Namespace(app.Namespace)
|
||||
a.resourceVersion = app.ResourceVersion
|
||||
|
||||
for _, comp := range app.Spec.Components {
|
||||
c, err := FromComponent(&comp)
|
||||
c, err := FromComponent(comp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert component from k8s object")
|
||||
}
|
||||
@@ -300,7 +300,7 @@ func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
}
|
||||
if app.Spec.Workflow != nil {
|
||||
for _, step := range app.Spec.Workflow.Steps {
|
||||
s, err := FromWorkflowStep(&step)
|
||||
s, err := FromWorkflowStep(step)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert workflow step from k8s object")
|
||||
}
|
||||
@@ -308,7 +308,7 @@ func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
}
|
||||
}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
p, err := FromPolicy(&policy)
|
||||
p, err := FromPolicy(policy)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert policy from k8s object")
|
||||
}
|
||||
@@ -317,34 +317,34 @@ func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func FromComponent(component *common.ApplicationComponent) (Component, error) {
|
||||
func FromComponent(component common.ApplicationComponent) (Component, error) {
|
||||
build, ok := ComponentsBuilders[component.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no component type %s registered", component.Type)
|
||||
}
|
||||
return build(*component)
|
||||
return build(component)
|
||||
}
|
||||
|
||||
func FromWorkflowStep(step *v1beta1.WorkflowStep) (WorkflowStep, error) {
|
||||
func FromWorkflowStep(step v1beta1.WorkflowStep) (WorkflowStep, error) {
|
||||
build, ok := WorkflowStepsBuilders[step.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no workflow step type %s registered", step.Type)
|
||||
}
|
||||
return build(*step)
|
||||
return build(step)
|
||||
}
|
||||
|
||||
func FromPolicy(policy *v1beta1.AppPolicy) (Policy, error) {
|
||||
func FromPolicy(policy v1beta1.AppPolicy) (Policy, error) {
|
||||
build, ok := PoliciesBuilders[policy.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no policy type %s registered", policy.Type)
|
||||
}
|
||||
return build(*policy)
|
||||
return build(policy)
|
||||
}
|
||||
|
||||
func FromTrait(trait *common.ApplicationTrait) (Trait, error) {
|
||||
func FromTrait(trait common.ApplicationTrait) (Trait, error) {
|
||||
build, ok := TraitBuilders[trait.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no trait type %s registered", trait.Type)
|
||||
}
|
||||
return build(*trait)
|
||||
return build(trait)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func (c clientImpl) Get(ctx context.Context, key client.ObjectKey) (apis.TypedAp
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sdkcommon.FromK8sObject(&_app)
|
||||
return sdkcommon.FromK8sObject(_app)
|
||||
}
|
||||
|
||||
func (c clientImpl) List(ctx context.Context, opts client.ListOption) ([]apis.TypedApplication, error) {
|
||||
@@ -108,7 +108,7 @@ func (c clientImpl) List(ctx context.Context, opts client.ListOption) ([]apis.Ty
|
||||
}
|
||||
var apps []apis.TypedApplication
|
||||
for _, app := range _appList.Items {
|
||||
_app, err := sdkcommon.FromK8sObject(&app)
|
||||
_app, err := sdkcommon.FromK8sObject(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
@@ -47,17 +49,31 @@ import (
|
||||
|
||||
type byteHandler func([]byte) []byte
|
||||
|
||||
var (
|
||||
defaultAPIDir = map[string]string{
|
||||
"go": "pkg/apis",
|
||||
}
|
||||
// LangArgsRegistry is used to store the argument info
|
||||
LangArgsRegistry = map[string]map[langArgKey]LangArg{}
|
||||
)
|
||||
|
||||
// GenMeta stores the metadata for generator.
|
||||
type GenMeta struct {
|
||||
config *rest.Config
|
||||
name string
|
||||
kind string
|
||||
|
||||
Output string
|
||||
Lang string
|
||||
Package string
|
||||
Template string
|
||||
File []string
|
||||
InitSDK bool
|
||||
Verbose bool
|
||||
Output string
|
||||
APIDirectory string
|
||||
IsSubModule bool
|
||||
Lang string
|
||||
Package string
|
||||
Template string
|
||||
File []string
|
||||
InitSDK bool
|
||||
Verbose bool
|
||||
|
||||
LangArgs LanguageArgs
|
||||
|
||||
cuePaths []string
|
||||
templatePath string
|
||||
@@ -67,11 +83,70 @@ type GenMeta struct {
|
||||
// Generator is used to generate SDK code from CUE template for one language.
|
||||
type Generator struct {
|
||||
meta *GenMeta
|
||||
name string
|
||||
kind string
|
||||
def definition.Definition
|
||||
openapiSchema []byte
|
||||
modifiers []Modifier
|
||||
// defModifiers are the modifiers for each definition.
|
||||
defModifiers []Modifier
|
||||
// moduleModifiers are the modifiers for the whole module. It will be executed after generating all definitions.
|
||||
moduleModifiers []Modifier
|
||||
}
|
||||
|
||||
// LanguageArgs is used to store the arguments for the language.
|
||||
type LanguageArgs interface {
|
||||
Get(key langArgKey) string
|
||||
Set(key langArgKey, value string)
|
||||
}
|
||||
|
||||
// langArgKey is language argument key.
|
||||
type langArgKey string
|
||||
|
||||
// LangArg is language-specific argument.
|
||||
type LangArg struct {
|
||||
Name langArgKey
|
||||
Desc string
|
||||
Default string
|
||||
}
|
||||
|
||||
// registerLangArg should be called in init() function of each language.
|
||||
func registerLangArg(lang string, arg ...LangArg) {
|
||||
if _, ok := LangArgsRegistry[lang]; !ok {
|
||||
LangArgsRegistry[lang] = map[langArgKey]LangArg{}
|
||||
}
|
||||
for _, a := range arg {
|
||||
LangArgsRegistry[lang][a.Name] = a
|
||||
}
|
||||
}
|
||||
|
||||
// NewLanguageArgs parses the language arguments and returns a LanguageArgs.
|
||||
func NewLanguageArgs(lang string, langArgs []string) (LanguageArgs, error) {
|
||||
availableArgs := LangArgsRegistry[lang]
|
||||
res := languageArgs{}
|
||||
for _, arg := range langArgs {
|
||||
parts := strings.Split(arg, "=")
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.Errorf("argument %s is not in the format of key=value", arg)
|
||||
}
|
||||
if _, ok := availableArgs[langArgKey(parts[0])]; !ok {
|
||||
return nil, errors.Errorf("argument %s is not supported for language %s", parts[0], lang)
|
||||
}
|
||||
res.Set(langArgKey(parts[0]), parts[1])
|
||||
}
|
||||
for k, v := range availableArgs {
|
||||
if res.Get(k) == "" {
|
||||
res.Set(k, v.Default)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type languageArgs map[string]string
|
||||
|
||||
func (l languageArgs) Get(key langArgKey) string {
|
||||
return l[string(key)]
|
||||
}
|
||||
|
||||
func (l languageArgs) Set(key langArgKey, value string) {
|
||||
l[string(key)] = value
|
||||
}
|
||||
|
||||
// Modifier is used to modify the generated code.
|
||||
@@ -82,7 +157,7 @@ type Modifier interface {
|
||||
|
||||
// Init initializes the generator.
|
||||
// It will validate the param, analyze the CUE files, read them to memory, mkdir for output.
|
||||
func (meta *GenMeta) Init(c common.Args) (err error) {
|
||||
func (meta *GenMeta) Init(c common.Args, langArgs []string) (err error) {
|
||||
meta.config, err = c.GetConfig()
|
||||
if err != nil {
|
||||
klog.Info("No kubeconfig found, skipping")
|
||||
@@ -95,9 +170,18 @@ func (meta *GenMeta) Init(c common.Args) (err error) {
|
||||
return fmt.Errorf("language %s is not supported", meta.Lang)
|
||||
}
|
||||
|
||||
// Init arguments
|
||||
if meta.APIDirectory == "" {
|
||||
meta.APIDirectory = defaultAPIDir[meta.Lang]
|
||||
}
|
||||
|
||||
meta.LangArgs, err = NewLanguageArgs(meta.Lang, langArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
packageFuncs := map[string]byteHandler{
|
||||
"go": func(b []byte) []byte {
|
||||
return bytes.ReplaceAll(b, []byte("github.com/kubevela/vela-go-sdk"), []byte(meta.Package))
|
||||
return bytes.ReplaceAll(b, []byte(PackagePlaceHolder), []byte(meta.Package))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -137,7 +221,7 @@ func (meta *GenMeta) CreateScaffold() error {
|
||||
if !meta.InitSDK {
|
||||
return nil
|
||||
}
|
||||
fmt.Println("Flag --init is set, creating scaffold...")
|
||||
klog.Info("Flag --init is set, creating scaffold...")
|
||||
langDirPrefix := fmt.Sprintf("%s/%s", ScaffoldDir, meta.Lang)
|
||||
err := fs.WalkDir(Scaffold, ScaffoldDir, func(_path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
@@ -155,7 +239,7 @@ func (meta *GenMeta) CreateScaffold() error {
|
||||
}
|
||||
fileContent = meta.packageFunc(fileContent)
|
||||
fileName := path.Join(meta.Output, strings.TrimPrefix(_path, langDirPrefix))
|
||||
// go.mod_ is a special file name, it will be renamed to go.mod. Go will exclude directory go.mod located from the build process.
|
||||
// go.mod_ is a special file name, it will be renamed to go.mod. Go will ignore directory containing go.mod during the build process.
|
||||
fileName = strings.ReplaceAll(fileName, "go.mod_", "go.mod")
|
||||
fileDir := path.Dir(fileName)
|
||||
if err = os.MkdirAll(fileDir, 0750); err != nil {
|
||||
@@ -236,18 +320,24 @@ func (meta *GenMeta) PrepareGeneratorAndTemplate() error {
|
||||
// 1. Generate OpenAPI schema from cue files
|
||||
// 2. Generate code from OpenAPI schema
|
||||
func (meta *GenMeta) Run() error {
|
||||
g := NewModifiableGenerator(meta)
|
||||
if len(meta.cuePaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
APIGenerated := false
|
||||
for _, cuePath := range meta.cuePaths {
|
||||
klog.Infof("Generating SDK for %s", cuePath)
|
||||
g := NewModifiableGenerator(meta)
|
||||
klog.Infof("Generating API for %s", cuePath)
|
||||
// nolint:gosec
|
||||
cueBytes, err := os.ReadFile(cuePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read %s", cuePath)
|
||||
}
|
||||
template, err := g.GetDefinitionValue(cueBytes)
|
||||
template, defName, defKind, err := g.GetDefinitionValue(cueBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.meta.SetDefinition(defName, defKind)
|
||||
|
||||
err = g.GenOpenAPISchema(template)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unsupported node string (*ast.Ident)") {
|
||||
@@ -257,32 +347,51 @@ func (meta *GenMeta) Run() error {
|
||||
}
|
||||
return errors.Wrapf(err, "generate OpenAPI schema")
|
||||
}
|
||||
|
||||
err = g.GenerateCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
APIGenerated = true
|
||||
}
|
||||
if !APIGenerated {
|
||||
return nil
|
||||
}
|
||||
for _, m := range g.moduleModifiers {
|
||||
err := m.Modify()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefinitionValue returns a value.Value from cue bytes
|
||||
func (g *Generator) GetDefinitionValue(cueBytes []byte) (*value.Value, error) {
|
||||
// SetDefinition sets definition name and kind
|
||||
func (meta *GenMeta) SetDefinition(defName, defKind string) {
|
||||
meta.name = defName
|
||||
meta.kind = defKind
|
||||
}
|
||||
|
||||
// GetDefinitionValue returns a value.Value definition name, definition kind from cue bytes
|
||||
func (g *Generator) GetDefinitionValue(cueBytes []byte) (*value.Value, string, string, error) {
|
||||
g.def = definition.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
if err := g.def.FromCUEString(string(cueBytes), g.meta.config); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse CUE")
|
||||
return nil, "", "", errors.Wrapf(err, "failed to parse CUE")
|
||||
}
|
||||
g.name = g.def.GetName()
|
||||
g.kind = g.def.GetKind()
|
||||
|
||||
templateString, _, err := unstructured.NestedString(g.def.Object, definition.DefinitionTemplateKeys...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", "", err
|
||||
}
|
||||
if templateString == "" {
|
||||
return nil, "", "", errors.New("definition doesn't include cue schematic")
|
||||
}
|
||||
template, err := value.NewValue(templateString+velacue.BaseTemplate, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", "", err
|
||||
}
|
||||
return template, nil
|
||||
return template, g.def.GetName(), g.def.GetKind(), nil
|
||||
}
|
||||
|
||||
// GenOpenAPISchema generates OpenAPI json schema from cue.Instance
|
||||
@@ -327,8 +436,8 @@ func (g *Generator) GenOpenAPISchema(val *value.Value) error {
|
||||
openapiSchema, err := doc.MarshalJSON()
|
||||
g.openapiSchema = openapiSchema
|
||||
if g.meta.Verbose {
|
||||
fmt.Println("OpenAPI schema:")
|
||||
fmt.Println(string(g.openapiSchema))
|
||||
klog.Info("OpenAPI schema:")
|
||||
klog.Info(string(g.openapiSchema))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -337,13 +446,13 @@ func (g *Generator) completeOpenAPISchema(doc *openapi3.T) {
|
||||
for key, schema := range doc.Components.Schemas {
|
||||
switch key {
|
||||
case "parameter":
|
||||
spec := g.name + "-spec"
|
||||
spec := g.meta.name + "-spec"
|
||||
schema.Value.Title = spec
|
||||
completeFreeFormSchema(schema)
|
||||
completeSchema(key, schema)
|
||||
doc.Components.Schemas[spec] = schema
|
||||
delete(doc.Components.Schemas, key)
|
||||
case g.name + "-spec":
|
||||
case g.meta.name + "-spec":
|
||||
continue
|
||||
default:
|
||||
completeSchema(key, schema)
|
||||
@@ -353,7 +462,7 @@ func (g *Generator) completeOpenAPISchema(doc *openapi3.T) {
|
||||
|
||||
// GenerateCode will call openapi-generator to generate code and modify it
|
||||
func (g *Generator) GenerateCode() (err error) {
|
||||
tmpFile, err := os.CreateTemp("", g.name+"-*.json")
|
||||
tmpFile, err := os.CreateTemp("", g.meta.name+"-*.json")
|
||||
_, err = tmpFile.Write(g.openapiSchema)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "write openapi schema to temporary file")
|
||||
@@ -364,11 +473,11 @@ func (g *Generator) GenerateCode() (err error) {
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}
|
||||
}()
|
||||
apiDir, err := filepath.Abs(path.Join(g.meta.Output, "pkg", "apis"))
|
||||
apiDir, err := filepath.Abs(path.Join(g.meta.Output, g.meta.APIDirectory))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get absolute path of %s", apiDir)
|
||||
}
|
||||
err = os.MkdirAll(path.Join(apiDir, definition.DefinitionKindToType[g.kind]), 0750)
|
||||
err = os.MkdirAll(path.Join(apiDir, definition.DefinitionKindToType[g.meta.kind]), 0750)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create directory %s", apiDir)
|
||||
}
|
||||
@@ -384,28 +493,28 @@ func (g *Generator) GenerateCode() (err error) {
|
||||
"generate",
|
||||
"-i", "/local/input/"+filepath.Base(tmpFile.Name()),
|
||||
"-g", g.meta.Lang,
|
||||
"-o", fmt.Sprintf("/local/output/%s/%s", definition.DefinitionKindToType[g.kind], g.name),
|
||||
"-o", fmt.Sprintf("/local/output/%s/%s", definition.DefinitionKindToType[g.meta.kind], g.meta.name),
|
||||
"-t", "/local/template",
|
||||
"--skip-validate-spec",
|
||||
"--enable-post-process-file",
|
||||
"--generate-alias-as-model",
|
||||
"--inline-schema-name-defaults", "arrayItemSuffix=,mapItemSuffix=",
|
||||
"--additional-properties", fmt.Sprintf("isGoSubmodule=true,packageName=%s", strings.ReplaceAll(g.name, "-", "_")),
|
||||
"--additional-properties", fmt.Sprintf("packageName=%s", strings.ReplaceAll(g.meta.name, "-", "_")),
|
||||
"--global-property", "modelDocs=false,models,supportingFiles=utils.go",
|
||||
)
|
||||
if g.meta.Verbose {
|
||||
fmt.Println(cmd.String())
|
||||
klog.Info(cmd.String())
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(output))
|
||||
}
|
||||
if g.meta.Verbose {
|
||||
fmt.Println(string(output))
|
||||
klog.Info(string(output))
|
||||
}
|
||||
|
||||
// Adjust the generated files and code
|
||||
for _, m := range g.modifiers {
|
||||
for _, m := range g.defModifiers {
|
||||
err := m.Modify()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "modify fail by %s", m.Name())
|
||||
@@ -531,20 +640,21 @@ func completeSchemas(schemas openapi3.Schemas) {
|
||||
// NewModifiableGenerator returns a new Generator with modifiers
|
||||
func NewModifiableGenerator(meta *GenMeta) *Generator {
|
||||
g := &Generator{
|
||||
meta: meta,
|
||||
modifiers: []Modifier{},
|
||||
meta: meta,
|
||||
defModifiers: []Modifier{},
|
||||
moduleModifiers: []Modifier{},
|
||||
}
|
||||
mo := newModifierOnLanguage(meta.Lang, g)
|
||||
g.modifiers = append(g.modifiers, mo)
|
||||
appendModifiersByLanguage(g, meta)
|
||||
return g
|
||||
}
|
||||
|
||||
func newModifierOnLanguage(lang string, generator *Generator) Modifier {
|
||||
switch lang {
|
||||
func appendModifiersByLanguage(g *Generator, meta *GenMeta) {
|
||||
switch meta.Lang {
|
||||
case "go":
|
||||
return &GoModifier{g: generator}
|
||||
g.defModifiers = append(g.defModifiers, &GoDefModifier{GenMeta: meta})
|
||||
g.moduleModifiers = append(g.moduleModifiers, &GoModuleModifier{GenMeta: meta})
|
||||
default:
|
||||
panic("unsupported language: " + lang)
|
||||
panic(fmt.Sprintf("unsupported language: %s", meta.Lang))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,3 +717,7 @@ func defaultValueMatchOneOfItem(item *openapi3.Schema, defaultValue interface{})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fnName(fn interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
|
||||
}
|
||||
|
||||
@@ -30,19 +30,28 @@ import (
|
||||
var _ = Describe("Test Generating SDK", func() {
|
||||
var err error
|
||||
outputDir := filepath.Join("testdata", "output")
|
||||
lang := "go"
|
||||
meta := GenMeta{
|
||||
Output: outputDir,
|
||||
Lang: "go",
|
||||
Package: "github.com/kubevela/test-gen-sdk",
|
||||
File: []string{filepath.Join("testdata", "cron-task.cue")},
|
||||
InitSDK: true,
|
||||
Output: outputDir,
|
||||
Lang: lang,
|
||||
Package: "github.com/kubevela-contrib/kubevela-go-sdk",
|
||||
APIDirectory: defaultAPIDir[lang],
|
||||
Verbose: true,
|
||||
}
|
||||
var langArgs []string
|
||||
|
||||
BeforeEach(func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{filepath.Join("testdata", "cron-task.cue")}
|
||||
meta.cuePaths = []string{}
|
||||
})
|
||||
|
||||
checkDirNotEmpty := func(dir string) {
|
||||
_, err = os.Stat(dir)
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
genWithMeta := func(meta GenMeta) {
|
||||
err = meta.Init(common.Args{})
|
||||
genWithMeta := func() {
|
||||
err = meta.Init(common.Args{}, langArgs)
|
||||
Expect(err).Should(BeNil())
|
||||
err = meta.CreateScaffold()
|
||||
Expect(err).Should(BeNil())
|
||||
@@ -52,43 +61,40 @@ var _ = Describe("Test Generating SDK", func() {
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
It("Test generating SDK and init the scaffold", func() {
|
||||
genWithMeta(meta)
|
||||
meta.InitSDK = true
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis"))
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "component", "cron-task"))
|
||||
})
|
||||
|
||||
It("Test generating SDK, append apis", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = append(meta.File, "testdata/shared-resource.cue")
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "policy", "shared-resource"))
|
||||
})
|
||||
|
||||
It("Test free form parameter {...}", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/json-merge-patch.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "trait", "json-merge-patch"))
|
||||
})
|
||||
|
||||
It("Test workflow step", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/deploy.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "deploy"))
|
||||
})
|
||||
|
||||
It("Test step-group", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/step-group.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "step-group"))
|
||||
By("check if AddSubStep is generated")
|
||||
content, err := os.ReadFile(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "step-group", "step_group.go"))
|
||||
@@ -97,20 +103,27 @@ var _ = Describe("Test Generating SDK", func() {
|
||||
})
|
||||
|
||||
It("Test oneOf", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/one_of.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "one_of"))
|
||||
By("check if ")
|
||||
})
|
||||
|
||||
It("Test known issue: apply-terraform-provider", func() {
|
||||
meta.InitSDK = false
|
||||
meta.Verbose = true
|
||||
meta.File = []string{"testdata/apply-terraform-provider.cue"}
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
})
|
||||
|
||||
It("Test generate sub-module", func() {
|
||||
meta.APIDirectory = "pkg/apis/addons/test_addon"
|
||||
langArgs = []string{
|
||||
string(mainModuleVersionKey) + "=" + mainModuleVersion.Default,
|
||||
}
|
||||
meta.IsSubModule = true
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "addons", "test_addon", "component", "cron-task"))
|
||||
})
|
||||
|
||||
AfterSuite(func() {
|
||||
@@ -207,3 +220,117 @@ var _ = Describe("FixSchemaWithOneAnyAllOf", func() {
|
||||
Expect(schema.Value.OneOf[0].Value.Enum).To(Equal([]interface{}{"go", "java", "python", "node", "ruby"}))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("TestNewLanguageArgs", func() {
|
||||
type args struct {
|
||||
lang string
|
||||
langArgs []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "should create a languageArgs struct with the correct values",
|
||||
args: args{
|
||||
lang: "go",
|
||||
langArgs: []string{"flag1=value1", "flag2=value2"},
|
||||
},
|
||||
want: map[string]string{"flag1": "value1", "flag2": "value2"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should not set a value for an unknown flag",
|
||||
args: args{
|
||||
lang: "go",
|
||||
langArgs: []string{"unknownFlag=value"},
|
||||
},
|
||||
want: map[string]string{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "should warn if an argument is not in the key=value format",
|
||||
args: args{
|
||||
lang: "go",
|
||||
langArgs: []string{"invalidArgument"},
|
||||
},
|
||||
want: map[string]string{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
It(tt.name, func() {
|
||||
got, err := NewLanguageArgs(tt.args.lang, tt.args.langArgs)
|
||||
if tt.wantErr {
|
||||
Expect(err).To(HaveOccurred())
|
||||
return
|
||||
}
|
||||
for k, v := range tt.want {
|
||||
Expect(got.Get(langArgKey(k))).To(Equal(v))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("getValueType", func() {
|
||||
|
||||
type valueTypeTest struct {
|
||||
input interface{}
|
||||
expected CUEType
|
||||
}
|
||||
|
||||
tests := []valueTypeTest{
|
||||
{nil, ""},
|
||||
{"hello", "string"},
|
||||
{42, "integer"},
|
||||
{float32(3.14), "number"},
|
||||
{3.14159265358979323846, "number"},
|
||||
{true, "boolean"},
|
||||
{map[string]interface{}{"key": "value"}, "object"},
|
||||
{[]interface{}{1, 2, 3}, "array"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt // capture range variable
|
||||
It("should return the correct CUEType for the input", func() {
|
||||
Expect(getValueType(tt.input)).To(Equal(tt.expected))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var _ = Describe("type fit", func() {
|
||||
|
||||
var schema *openapi3.Schema
|
||||
|
||||
BeforeEach(func() {
|
||||
schema = &openapi3.Schema{Type: "string"}
|
||||
})
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
cueType CUEType
|
||||
expectedFit bool
|
||||
schemaType string
|
||||
}{
|
||||
{"string can be oas string", CUEType("string"), true, "string"},
|
||||
{"string not oas integer", CUEType("string"), false, "integer"},
|
||||
{"integer can be oas integer", CUEType("integer"), true, "integer"},
|
||||
{"integer can be oas number", CUEType("integer"), true, "number"},
|
||||
{"number can be oas number", CUEType("number"), true, "number"},
|
||||
{"number not oas integer", CUEType("number"), false, "integer"},
|
||||
{"boolean can be oas boolean", CUEType("boolean"), true, "boolean"},
|
||||
{"array can be oas array", CUEType("array"), true, "array"},
|
||||
{"invalid type and any schema", CUEType(""), false, "anyschema"},
|
||||
}
|
||||
|
||||
It("should return whether the CUEType fits the schema type or not", func() {
|
||||
for _, tc := range testCases {
|
||||
schema.Type = tc.schemaType
|
||||
result := tc.cueType.fit(schema)
|
||||
Expect(result).To(Equal(tc.expectedFit), tc.name)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,11 +23,10 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
// we need dot import here to make the complex go code generating simpler
|
||||
// nolint:revive
|
||||
j "github.com/dave/jennifer/jen"
|
||||
"github.com/ettle/strcase"
|
||||
"github.com/pkg/errors"
|
||||
@@ -36,6 +35,32 @@ import (
|
||||
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
|
||||
)
|
||||
|
||||
var (
|
||||
mainModuleVersionKey langArgKey = "MainModuleVersion"
|
||||
goProxyKey langArgKey = "GoProxy"
|
||||
|
||||
mainModuleVersion = LangArg{
|
||||
Name: mainModuleVersionKey,
|
||||
Desc: "The version of main module, it will be used in go get command. For example, tag, commit id, branch name",
|
||||
// default hash of main module. This is a commit hash of kubevela-contrib/kubvela-go-sdk. It will be used in go get command.
|
||||
Default: "cd431bb25a9a",
|
||||
}
|
||||
goProxy = LangArg{
|
||||
Name: goProxyKey,
|
||||
Desc: "The proxy for go get/go mod tidy command",
|
||||
Default: "https://goproxy.cn,direct",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerLangArg("go", mainModuleVersion, goProxy)
|
||||
}
|
||||
|
||||
const (
|
||||
// PackagePlaceHolder is the package name placeholder
|
||||
PackagePlaceHolder = "github.com/kubevela/vela-go-sdk"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefinitionKindToPascal is the map of definition kind to pascal case
|
||||
DefinitionKindToPascal = map[string]string{
|
||||
@@ -60,14 +85,22 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// GoModifier is the Modifier for golang
|
||||
type GoModifier struct {
|
||||
g *Generator
|
||||
// GoDefModifier is the Modifier for golang, modify code for each definition
|
||||
type GoDefModifier struct {
|
||||
*GenMeta
|
||||
*goArgs
|
||||
|
||||
defName string
|
||||
defKind string
|
||||
verbose bool
|
||||
defStructPointer *j.Statement
|
||||
}
|
||||
|
||||
// GoModuleModifier is the Modifier for golang, modify code for each module which contains multiple definitions
|
||||
type GoModuleModifier struct {
|
||||
*GenMeta
|
||||
*goArgs
|
||||
}
|
||||
|
||||
type goArgs struct {
|
||||
apiDir string
|
||||
defDir string
|
||||
utilsDir string
|
||||
// def name of different cases
|
||||
@@ -77,16 +110,57 @@ type GoModifier struct {
|
||||
typeVarName string
|
||||
defStructName string
|
||||
defFuncReceiver string
|
||||
defStructPointer *j.Statement
|
||||
}
|
||||
|
||||
func (a *goArgs) init(m *GenMeta) error {
|
||||
var err error
|
||||
a.apiDir, err = filepath.Abs(path.Join(m.Output, m.APIDirectory))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.defDir = path.Join(a.apiDir, pkgdef.DefinitionKindToType[m.kind], m.name)
|
||||
a.utilsDir = path.Join(m.Output, "pkg", "apis", "utils")
|
||||
a.nameInSnakeCase = strcase.ToSnake(m.name)
|
||||
a.nameInPascalCase = strcase.ToPascal(m.name)
|
||||
a.typeVarName = a.nameInPascalCase + "Type"
|
||||
a.specNameInPascalCase = a.nameInPascalCase + "Spec"
|
||||
a.defStructName = strcase.ToGoPascal(m.name + "-" + pkgdef.DefinitionKindToType[m.kind])
|
||||
a.defFuncReceiver = m.name[:1]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Modify implements Modifier
|
||||
func (m *GoModuleModifier) Modify() error {
|
||||
for _, fn := range []func() error{
|
||||
m.init,
|
||||
m.format,
|
||||
m.addSubGoMod,
|
||||
m.tidyMainMod,
|
||||
} {
|
||||
if err := fn(); err != nil {
|
||||
return errors.Wrap(err, fnName(fn))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModuleModifier) init() error {
|
||||
m.goArgs = &goArgs{}
|
||||
return m.goArgs.init(m.GenMeta)
|
||||
}
|
||||
|
||||
// Name the name of modifier
|
||||
func (m *GoModifier) Name() string {
|
||||
return "GoModifier"
|
||||
func (m *GoModuleModifier) Name() string {
|
||||
return "goModuleModifier"
|
||||
}
|
||||
|
||||
// Name the name of modifier
|
||||
func (m *GoDefModifier) Name() string {
|
||||
return "GoDefModifier"
|
||||
}
|
||||
|
||||
// Modify the modification of generated code
|
||||
func (m *GoModifier) Modify() error {
|
||||
func (m *GoDefModifier) Modify() error {
|
||||
for _, fn := range []func() error{
|
||||
m.init,
|
||||
m.clean,
|
||||
@@ -95,35 +169,28 @@ func (m *GoModifier) Modify() error {
|
||||
m.addDefAPI,
|
||||
m.addValidateTraits,
|
||||
m.exportMethods,
|
||||
m.format,
|
||||
} {
|
||||
if err := fn(); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, fnName(fn))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) init() error {
|
||||
m.defName = m.g.name
|
||||
m.defKind = m.g.kind
|
||||
m.verbose = m.g.meta.Verbose
|
||||
func (m *GoDefModifier) init() error {
|
||||
m.goArgs = &goArgs{}
|
||||
err := m.goArgs.init(m.GenMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkgAPIDir := path.Join(m.g.meta.Output, "pkg", "apis")
|
||||
m.defDir = path.Join(pkgAPIDir, pkgdef.DefinitionKindToType[m.defKind], m.defName)
|
||||
m.utilsDir = path.Join(pkgAPIDir, "utils")
|
||||
m.nameInSnakeCase = strcase.ToSnake(m.defName)
|
||||
m.nameInPascalCase = strcase.ToPascal(m.defName)
|
||||
m.typeVarName = m.nameInPascalCase + "Type"
|
||||
m.specNameInPascalCase = m.nameInPascalCase + "Spec"
|
||||
m.defStructName = strcase.ToGoPascal(m.defName + "-" + pkgdef.DefinitionKindToType[m.defKind])
|
||||
m.defStructPointer = j.Op("*").Id(m.defStructName)
|
||||
m.defFuncReceiver = m.defName[:1]
|
||||
err := os.MkdirAll(m.utilsDir, 0750)
|
||||
|
||||
err = os.MkdirAll(m.utilsDir, 0750)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *GoModifier) clean() error {
|
||||
func (m *GoDefModifier) clean() error {
|
||||
err := os.RemoveAll(path.Join(m.defDir, ".openapi-generator"))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -148,17 +215,101 @@ func (m *GoModifier) clean() error {
|
||||
|
||||
}
|
||||
|
||||
// addSubGoMod will add a go.mod and go.sum in the api directory if user mark that the api is a submodule
|
||||
func (m *GoModuleModifier) addSubGoMod() error {
|
||||
if !m.IsSubModule {
|
||||
return nil
|
||||
}
|
||||
files := map[string]string{
|
||||
"go.mod_": "go.mod",
|
||||
"go.sum": "go.sum",
|
||||
}
|
||||
for src, dst := range files {
|
||||
srcContent, err := Scaffold.ReadFile(path.Join(ScaffoldDir, "go", src))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read "+src)
|
||||
}
|
||||
subModuleName := strings.TrimSuffix(fmt.Sprintf("%s/%s", m.Package, m.APIDirectory), "/")
|
||||
srcContent = bytes.ReplaceAll(srcContent, []byte("module "+PackagePlaceHolder), []byte("module "+subModuleName))
|
||||
srcContent = bytes.ReplaceAll(srcContent, []byte("// require "+PackagePlaceHolder), []byte("require "+m.Package))
|
||||
|
||||
err = os.WriteFile(path.Join(m.apiDir, dst), srcContent, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "write "+dst)
|
||||
}
|
||||
}
|
||||
|
||||
cmds := make([]*exec.Cmd, 0)
|
||||
if m.LangArgs.Get(mainModuleVersionKey) != mainModuleVersion.Default {
|
||||
// nolint:gosec
|
||||
cmds = append(cmds, exec.Command("docker", "run",
|
||||
"--rm",
|
||||
"-v", m.apiDir+":/api",
|
||||
"-w", "/api",
|
||||
"golang:1.19-alpine",
|
||||
"go", "get", fmt.Sprintf("%s@%s", m.Package, m.LangArgs.Get(mainModuleVersionKey)),
|
||||
))
|
||||
}
|
||||
// nolint:gosec
|
||||
cmds = append(cmds, exec.Command("docker", "run",
|
||||
"--rm",
|
||||
"-v", m.apiDir+":/api",
|
||||
"-w", "/api",
|
||||
"--env", "GOPROXY="+m.LangArgs.Get(goProxyKey),
|
||||
"golang:1.19-alpine",
|
||||
"go", "mod", "tidy",
|
||||
))
|
||||
for _, cmd := range cmds {
|
||||
if m.Verbose {
|
||||
fmt.Println(cmd.String())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "fail to run command %s", cmd.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tidyMainMod will run go mod tidy in the main module
|
||||
func (m *GoModuleModifier) tidyMainMod() error {
|
||||
if !m.InitSDK {
|
||||
return nil
|
||||
}
|
||||
outDir, err := filepath.Abs(m.GenMeta.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint:gosec
|
||||
cmd := exec.Command("docker", "run",
|
||||
"--rm",
|
||||
"-v", outDir+":/api",
|
||||
"-w", "/api",
|
||||
"golang:1.19-alpine",
|
||||
"go", "mod", "tidy",
|
||||
)
|
||||
if m.Verbose {
|
||||
fmt.Println(cmd.String())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// read all files in definition directory,
|
||||
// 1. replace the Nullable* Struct
|
||||
// 2. replace the package name
|
||||
func (m *GoModifier) modifyDefs() error {
|
||||
func (m *GoDefModifier) modifyDefs() error {
|
||||
changeNullableType := func(b []byte) []byte {
|
||||
return regexp.MustCompile("Nullable(String|(Float|Int)(32|64)|Bool)").ReplaceAll(b, []byte("utils.Nullable$1"))
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(m.defDir)
|
||||
defHandleFunc := []byteHandler{
|
||||
m.g.meta.packageFunc,
|
||||
m.packageFunc,
|
||||
changeNullableType,
|
||||
}
|
||||
if err != nil {
|
||||
@@ -180,7 +331,7 @@ func (m *GoModifier) modifyDefs() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) moveUtils() error {
|
||||
func (m *GoDefModifier) moveUtils() error {
|
||||
// Adjust the generated files and code
|
||||
err := os.Rename(path.Join(m.defDir, "utils.go"), path.Join(m.utilsDir, "utils.go"))
|
||||
if err != nil {
|
||||
@@ -193,7 +344,7 @@ func (m *GoModifier) moveUtils() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utilsBytes = bytes.Replace(utilsBytes, []byte(fmt.Sprintf("package %s", strcase.ToSnake(m.defName))), []byte("package utils"), 1)
|
||||
utilsBytes = bytes.Replace(utilsBytes, []byte(fmt.Sprintf("package %s", strcase.ToSnake(m.name))), []byte("package utils"), 1)
|
||||
utilsBytes = bytes.ReplaceAll(utilsBytes, []byte("isNil"), []byte("IsNil"))
|
||||
err = os.WriteFile(utilsFile, utilsBytes, 0600)
|
||||
if err != nil {
|
||||
@@ -203,7 +354,7 @@ func (m *GoModifier) moveUtils() error {
|
||||
}
|
||||
|
||||
// addDefAPI will add component/trait/workflowstep/policy Object to the api
|
||||
func (m *GoModifier) addDefAPI() error {
|
||||
func (m *GoDefModifier) addDefAPI() error {
|
||||
file, err := os.OpenFile(path.Join(m.defDir, m.nameInSnakeCase+".go"), os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -236,11 +387,11 @@ func (m *GoModifier) addDefAPI() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genCommonFunc() []*j.Statement {
|
||||
kind := m.defKind
|
||||
func (m *GoDefModifier) genCommonFunc() []*j.Statement {
|
||||
kind := m.kind
|
||||
typeName := j.Id(m.nameInPascalCase + "Type")
|
||||
typeConst := j.Const().Add(typeName).Op("=").Lit(m.defName)
|
||||
j.Op("=").Lit(m.defName)
|
||||
typeConst := j.Const().Add(typeName).Op("=").Lit(m.name)
|
||||
j.Op("=").Lit(m.name)
|
||||
defStruct := j.Type().Id(m.defStructName).Struct(
|
||||
j.Id("Base").Id("apis").Dot(DefinitionKindToBaseType[kind]),
|
||||
j.Id("Properties").Id(m.specNameInPascalCase),
|
||||
@@ -328,8 +479,8 @@ func (m *GoModifier) genCommonFunc() []*j.Statement {
|
||||
return []*j.Statement{typeConst, initFunc, defStruct, defStructConstructor, buildFunc}
|
||||
}
|
||||
|
||||
func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
kind := m.g.kind
|
||||
func (m *GoDefModifier) genFromFunc() []*j.Statement {
|
||||
kind := m.kind
|
||||
kindBaseProperties := map[string][]string{
|
||||
v1beta1.ComponentDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs"},
|
||||
v1beta1.WorkflowStepDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta"},
|
||||
@@ -340,7 +491,7 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
// fromFuncRsv means build from a part of K8s Object (e.g. v1beta1.Application.spec.component[*] to internal presentation (e.g. Component)
|
||||
// fromFuncRsv will have a function receiver
|
||||
getSubSteps := func(sub bool) func(g *j.Group) {
|
||||
if m.defKind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
if m.kind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
return func(g *j.Group) {}
|
||||
}
|
||||
return func(g *j.Group) {
|
||||
@@ -358,7 +509,7 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
}
|
||||
}
|
||||
assignSubSteps := func(sub bool) func(g *j.Group) {
|
||||
if m.defKind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
if m.kind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
return func(g *j.Group) {}
|
||||
}
|
||||
return func(g *j.Group) {
|
||||
@@ -379,7 +530,7 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
BlockFunc(func(g *j.Group) {
|
||||
if kind == v1beta1.ComponentDefinitionKind {
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("trait")).Op(":=").Range().Id("from").Dot("Traits")).Block(
|
||||
j.List(j.Id("_t"), j.Err()).Op(":=").Qual("sdkcommon", "FromTrait").Call(j.Op("&").Id("trait")),
|
||||
j.List(j.Id("_t"), j.Err()).Op(":=").Qual("sdkcommon", "FromTrait").Call(j.Id("trait")),
|
||||
j.If(j.Err().Op("!=").Nil()).Block(
|
||||
j.Return(j.Nil(), j.Err()),
|
||||
),
|
||||
@@ -425,15 +576,15 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
)
|
||||
|
||||
res := []*j.Statement{fromFuncRsv(false), fromFunc}
|
||||
if m.defKind == v1beta1.WorkflowStepDefinitionKind {
|
||||
if m.kind == v1beta1.WorkflowStepDefinitionKind {
|
||||
res = append(res, fromFuncRsv(true), fromSubFunc)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// genDedicatedFunc generate functions for definition kinds
|
||||
func (m *GoModifier) genDedicatedFunc() []*j.Statement {
|
||||
switch m.defKind {
|
||||
func (m *GoDefModifier) genDedicatedFunc() []*j.Statement {
|
||||
switch m.kind {
|
||||
case v1beta1.ComponentDefinitionKind:
|
||||
setTraitFunc := j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
@@ -449,9 +600,9 @@ func (m *GoModifier) genDedicatedFunc() []*j.Statement {
|
||||
j.Id("found").Op("=").True(),
|
||||
j.Break(),
|
||||
),
|
||||
j.If(j.Op("!").Id("found")).Block(
|
||||
j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("addTrait")),
|
||||
),
|
||||
),
|
||||
j.If(j.Op("!").Id("found")).Block(
|
||||
j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("addTrait")),
|
||||
),
|
||||
),
|
||||
j.Return(j.Id(m.defFuncReceiver)),
|
||||
@@ -484,14 +635,14 @@ func (m *GoModifier) genDedicatedFunc() []*j.Statement {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genNameTypeFunc() []*j.Statement {
|
||||
nameFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id(DefinitionKindToPascal[m.defKind] + "Name").Params().String().Block(
|
||||
func (m *GoDefModifier) genNameTypeFunc() []*j.Statement {
|
||||
nameFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id(DefinitionKindToPascal[m.kind] + "Name").Params().String().Block(
|
||||
j.Return(j.Id(m.defFuncReceiver).Dot("Base").Dot("Name")),
|
||||
)
|
||||
typeFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id("DefType").Params().String().Block(
|
||||
j.Return(j.Id(m.typeVarName)),
|
||||
)
|
||||
switch m.defKind {
|
||||
switch m.kind {
|
||||
case v1beta1.ComponentDefinitionKind, v1beta1.WorkflowStepDefinitionKind, v1beta1.PolicyDefinitionKind:
|
||||
return []*j.Statement{nameFunc, typeFunc}
|
||||
case v1beta1.TraitDefinitionKind:
|
||||
@@ -500,11 +651,11 @@ func (m *GoModifier) genNameTypeFunc() []*j.Statement {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genUnmarshalFunc() []*j.Statement {
|
||||
func (m *GoDefModifier) genUnmarshalFunc() []*j.Statement {
|
||||
return []*j.Statement{j.Null()}
|
||||
}
|
||||
|
||||
func (m *GoModifier) genBaseSetterFunc() []*j.Statement {
|
||||
func (m *GoDefModifier) genBaseSetterFunc() []*j.Statement {
|
||||
baseFuncArgs := map[string][]struct {
|
||||
funcName string
|
||||
argName string
|
||||
@@ -533,7 +684,7 @@ func (m *GoModifier) genBaseSetterFunc() []*j.Statement {
|
||||
},
|
||||
}
|
||||
baseFuncs := make([]*j.Statement, 0)
|
||||
for _, fn := range baseFuncArgs[m.defKind] {
|
||||
for _, fn := range baseFuncArgs[m.kind] {
|
||||
if fn.dst == nil {
|
||||
fn.dst = j.Dot(fn.funcName)
|
||||
}
|
||||
@@ -556,8 +707,8 @@ func (m *GoModifier) genBaseSetterFunc() []*j.Statement {
|
||||
return baseFuncs
|
||||
}
|
||||
|
||||
func (m *GoModifier) genAddSubStepFunc() *j.Statement {
|
||||
if m.defName != "step-group" || m.defKind != v1beta1.WorkflowStepDefinitionKind {
|
||||
func (m *GoDefModifier) genAddSubStepFunc() *j.Statement {
|
||||
if m.name != "step-group" || m.kind != v1beta1.WorkflowStepDefinitionKind {
|
||||
return j.Null()
|
||||
}
|
||||
subList := j.Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")
|
||||
@@ -573,7 +724,7 @@ func (m *GoModifier) genAddSubStepFunc() *j.Statement {
|
||||
}
|
||||
|
||||
// exportMethods will export methods from definition spec struct to definition struct
|
||||
func (m *GoModifier) exportMethods() error {
|
||||
func (m *GoDefModifier) exportMethods() error {
|
||||
fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
|
||||
// nolint:gosec
|
||||
file, err := os.ReadFile(fileLoc)
|
||||
@@ -604,8 +755,8 @@ func (m *GoModifier) exportMethods() error {
|
||||
return os.WriteFile(fileLoc, []byte(fileStr), 0600)
|
||||
}
|
||||
|
||||
func (m *GoModifier) addValidateTraits() error {
|
||||
if m.defKind != v1beta1.ComponentDefinitionKind {
|
||||
func (m *GoDefModifier) addValidateTraits() error {
|
||||
if m.kind != v1beta1.ComponentDefinitionKind {
|
||||
return nil
|
||||
}
|
||||
fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
|
||||
@@ -632,11 +783,12 @@ func (m *GoModifier) addValidateTraits() error {
|
||||
|
||||
return os.WriteFile(fileLoc, []byte(fileStr), 0600)
|
||||
}
|
||||
func (m *GoModifier) format() error {
|
||||
func (m *GoModuleModifier) format() error {
|
||||
// check if gofmt is installed
|
||||
// todo (chivalryq): support go mod tidy for sub-module
|
||||
|
||||
formatters := []string{"gofmt", "goimports"}
|
||||
formatterPaths := []string{}
|
||||
var formatterPaths []string
|
||||
allFormattersInstalled := true
|
||||
for _, formatter := range formatters {
|
||||
p, err := exec.LookPath(formatter)
|
||||
@@ -648,11 +800,11 @@ func (m *GoModifier) format() error {
|
||||
}
|
||||
if allFormattersInstalled {
|
||||
for _, fmter := range formatterPaths {
|
||||
if m.verbose {
|
||||
if m.Verbose {
|
||||
fmt.Printf("Use %s to format code\n", fmter)
|
||||
}
|
||||
// nolint:gosec
|
||||
cmd := exec.Command(fmter, "-w", m.defDir)
|
||||
cmd := exec.Command(fmter, "-w", m.apiDir)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(output))
|
||||
@@ -661,31 +813,31 @@ func (m *GoModifier) format() error {
|
||||
return nil
|
||||
}
|
||||
// fallback to use go lib
|
||||
if m.verbose {
|
||||
if m.Verbose {
|
||||
fmt.Println("At least one of linters is not installed, use go/format lib to format code")
|
||||
}
|
||||
files, err := os.ReadDir(m.defDir)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read dir")
|
||||
}
|
||||
for _, f := range files {
|
||||
if !strings.HasSuffix(f.Name(), ".go") {
|
||||
continue
|
||||
}
|
||||
filePath := path.Join(m.defDir, f.Name())
|
||||
// nolint:gosec
|
||||
content, err := os.ReadFile(filePath)
|
||||
|
||||
// format all .go files
|
||||
return filepath.Walk(m.apiDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read file %s", filePath)
|
||||
return err
|
||||
}
|
||||
if !strings.HasSuffix(path, ".go") {
|
||||
return nil
|
||||
}
|
||||
// nolint:gosec
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read file %s", path)
|
||||
}
|
||||
formatted, err := format.Source(content)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "format file %s", filePath)
|
||||
return errors.Wrapf(err, "format file %s", path)
|
||||
}
|
||||
err = os.WriteFile(filePath, formatted, 0600)
|
||||
err = os.WriteFile(path, formatted, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "write file %s", filePath)
|
||||
return errors.Wrapf(err, "write file %s", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -235,6 +235,9 @@ const (
|
||||
|
||||
// AnnotationAddonDefinitionBondCompKey indicates the definition in addon bond component.
|
||||
AnnotationAddonDefinitionBondCompKey = "addon.oam.dev/bind-component"
|
||||
|
||||
// AnnotationSkipResume annotation indicates that the resource does not need to be resumed.
|
||||
AnnotationSkipResume = "controller.core.oam.dev/skip-resume"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -51,13 +51,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
)
|
||||
|
||||
var (
|
||||
// KindDeployment is the k8s Deployment kind.
|
||||
KindDeployment = reflect.TypeOf(appsv1.Deployment{}).Name()
|
||||
// KindService is the k8s Service kind.
|
||||
KindService = reflect.TypeOf(corev1.Service{}).Name()
|
||||
)
|
||||
|
||||
const (
|
||||
// TraitPrefixKey is prefix of trait name
|
||||
TraitPrefixKey = "trait"
|
||||
@@ -870,28 +863,6 @@ func MergeMapOverrideWithDst(src, dst map[string]string) map[string]string {
|
||||
return r
|
||||
}
|
||||
|
||||
// ConvertComponentDef2WorkloadDef help convert a ComponentDefinition to WorkloadDefinition
|
||||
func ConvertComponentDef2WorkloadDef(dm discoverymapper.DiscoveryMapper, componentDef *v1beta1.ComponentDefinition,
|
||||
workloadDef *v1beta1.WorkloadDefinition) error {
|
||||
var reference common.DefinitionReference
|
||||
reference, err := ConvertWorkloadGVK2Definition(dm, componentDef.Spec.Workload.Definition)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create DefinitionReference fail %w", err)
|
||||
}
|
||||
|
||||
workloadDef.SetName(componentDef.Name)
|
||||
workloadDef.SetNamespace(componentDef.Namespace)
|
||||
workloadDef.SetLabels(componentDef.Labels)
|
||||
workloadDef.SetAnnotations(componentDef.Annotations)
|
||||
workloadDef.Spec.Reference = reference
|
||||
workloadDef.Spec.ChildResourceKinds = componentDef.Spec.ChildResourceKinds
|
||||
workloadDef.Spec.Extension = componentDef.Spec.Extension
|
||||
workloadDef.Spec.RevisionLabel = componentDef.Spec.RevisionLabel
|
||||
workloadDef.Spec.Status = componentDef.Spec.Status
|
||||
workloadDef.Spec.Schematic = componentDef.Spec.Schematic
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractComponentName will extract the componentName from a revisionName
|
||||
func ExtractComponentName(revisionName string) string {
|
||||
splits := strings.Split(revisionName, "-")
|
||||
|
||||
@@ -37,12 +37,10 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/condition"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/mock"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -1560,128 +1558,6 @@ func TestGetScopeDefinition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertComponentDef2WorkloadDef(t *testing.T) {
|
||||
var cd = v1beta1.ComponentDefinition{}
|
||||
mapper := mock.NewMockDiscoveryMapper()
|
||||
|
||||
var componentDefWithWrongDefinition = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
spec:
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: /apps/v1/
|
||||
kind: Deployment
|
||||
`
|
||||
cd = v1beta1.ComponentDefinition{}
|
||||
err := yaml.Unmarshal([]byte(componentDefWithWrongDefinition), &cd)
|
||||
assert.Equal(t, nil, err)
|
||||
err = util.ConvertComponentDef2WorkloadDef(mapper, &cd, &v1beta1.WorkloadDefinition{})
|
||||
assert.Error(t, err)
|
||||
|
||||
mapper.MockRESTMapping = mock.NewMockRESTMapping("deployments")
|
||||
var Template = `
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
template: {
|
||||
metadata: labels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
spec: {
|
||||
containers: [{
|
||||
name: context.name
|
||||
image: parameter.image
|
||||
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Which image would you like to use for your service
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=Commands to run in the container
|
||||
cmd?: [...string]
|
||||
}
|
||||
`
|
||||
var componentDefWithDefinition = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: vela-system
|
||||
labels:
|
||||
env: test
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend."
|
||||
spec:
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
childResourceKinds:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
status:
|
||||
healthPolicy: |
|
||||
isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas)` + Template
|
||||
|
||||
var expectWorkloadDef = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: vela-system
|
||||
labels:
|
||||
env: test
|
||||
annotations:
|
||||
definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend."
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
version: v1
|
||||
childResourceKinds:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
status:
|
||||
healthPolicy: |
|
||||
isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas)` + Template
|
||||
cd = v1beta1.ComponentDefinition{}
|
||||
wd := &v1beta1.WorkloadDefinition{}
|
||||
err = yaml.Unmarshal([]byte(componentDefWithDefinition), &cd)
|
||||
assert.NoError(t, err)
|
||||
err = util.ConvertComponentDef2WorkloadDef(mapper, &cd, wd)
|
||||
assert.NoError(t, err)
|
||||
expectWd := v1beta1.WorkloadDefinition{}
|
||||
err = yaml.Unmarshal([]byte(expectWorkloadDef), &expectWd)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectWd.Namespace, wd.Namespace)
|
||||
assert.Equal(t, expectWd.Name, wd.Name)
|
||||
assert.Equal(t, expectWd.Labels, wd.Labels)
|
||||
assert.Equal(t, expectWd.Annotations, wd.Annotations)
|
||||
assert.Equal(t, expectWd.Spec.Reference, wd.Spec.Reference)
|
||||
assert.Equal(t, expectWd.Spec.ChildResourceKinds, wd.Spec.ChildResourceKinds)
|
||||
assert.Equal(t, expectWd.Spec.Status, wd.Spec.Status)
|
||||
assert.Equal(t, expectWd.Spec.Schematic, wd.Spec.Schematic)
|
||||
}
|
||||
|
||||
func TestExtractRevisionNum(t *testing.T) {
|
||||
testcases := []struct {
|
||||
revName string
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -62,6 +64,9 @@ func getAssociatedRollouts(ctx context.Context, cli client.Client, app *v1beta1.
|
||||
}
|
||||
return nil, errors.Wrapf(err, "failed to get kruise rollout %s/%s in cluster %s", mr.Namespace, mr.Name, mr.Cluster)
|
||||
}
|
||||
if value, ok := rollout.Annotations[oam.AnnotationSkipResume]; ok && value == "true" {
|
||||
continue
|
||||
}
|
||||
rollouts = append(rollouts, &ClusterRollout{Rollout: rollout, Cluster: mr.Cluster})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"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"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
@@ -37,11 +38,13 @@ var _ = Describe("Kruise rollout test", func() {
|
||||
Expect(k8sClient.Create(ctx, rollout.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, rt.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, app.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, rollingReleaseRollout.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("test get associated rollout func", func() {
|
||||
rollouts, err := getAssociatedRollouts(ctx, k8sClient, &app, false)
|
||||
Expect(err).Should(BeNil())
|
||||
// test will only fetch one rollout in result
|
||||
Expect(len(rollouts)).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
@@ -120,6 +123,19 @@ var rt = v1beta1.ResourceTracker{
|
||||
Component: "my-rollout",
|
||||
},
|
||||
},
|
||||
{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
ObjectReference: v1.ObjectReference{
|
||||
APIVersion: "rollouts.kruise.io/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
Name: "rolling-release-rollout",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common.OAMObjectReference{
|
||||
Component: "my-rollout",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -153,3 +169,36 @@ var rollout = kruisev1alpha1.Rollout{
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var rollingReleaseRollout = kruisev1alpha1.Rollout{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "rollouts.kruise.io/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "rolling-release-rollout",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationSkipResume: "true",
|
||||
},
|
||||
},
|
||||
Spec: kruisev1alpha1.RolloutSpec{
|
||||
ObjectRef: kruisev1alpha1.ObjectRef{
|
||||
WorkloadRef: &kruisev1alpha1.WorkloadRef{
|
||||
APIVersion: "appsv1",
|
||||
Kind: "Deployment",
|
||||
Name: "canary-demo",
|
||||
},
|
||||
},
|
||||
Strategy: kruisev1alpha1.RolloutStrategy{
|
||||
Canary: &kruisev1alpha1.CanaryStrategy{
|
||||
Steps: []kruisev1alpha1.CanaryStep{
|
||||
{
|
||||
Weight: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
Paused: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -126,12 +126,12 @@ func init() {
|
||||
|
||||
// HTTPOption define the https options
|
||||
type HTTPOption struct {
|
||||
Username string
|
||||
Password string
|
||||
CaFile string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
InsecureSkipTLS bool
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
CaFile string `json:"caFile,omitempty"`
|
||||
CertFile string `json:"certFile,omitempty"`
|
||||
KeyFile string `json:"keyFile,omitempty"`
|
||||
InsecureSkipTLS bool `json:"insecureSkipTLS,omitempty"`
|
||||
}
|
||||
|
||||
// InitBaseRestConfig will return reset config for create controller runtime client
|
||||
|
||||
@@ -115,16 +115,6 @@ func SuspendWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.Ap
|
||||
found := stepName == ""
|
||||
|
||||
for i, step := range steps {
|
||||
if step.Phase != workflowv1alpha1.WorkflowStepPhaseRunning {
|
||||
continue
|
||||
}
|
||||
if stepName == "" {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseSuspending)
|
||||
} else if stepName == step.Name {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseSuspending)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
for j, sub := range step.SubStepsStatus {
|
||||
if sub.Phase != workflowv1alpha1.WorkflowStepPhaseRunning {
|
||||
continue
|
||||
@@ -137,6 +127,16 @@ func SuspendWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.Ap
|
||||
break
|
||||
}
|
||||
}
|
||||
if step.Phase != workflowv1alpha1.WorkflowStepPhaseRunning {
|
||||
continue
|
||||
}
|
||||
if stepName == "" {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseSuspending)
|
||||
} else if stepName == step.Name {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseSuspending)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("can not find step %s", stepName)
|
||||
@@ -209,16 +209,6 @@ func ResumeWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.App
|
||||
found := stepName == ""
|
||||
|
||||
for i, step := range steps {
|
||||
if step.Phase != workflowv1alpha1.WorkflowStepPhaseSuspending {
|
||||
continue
|
||||
}
|
||||
if stepName == "" {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseRunning)
|
||||
} else if stepName == step.Name {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseRunning)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
for j, sub := range step.SubStepsStatus {
|
||||
if sub.Phase != workflowv1alpha1.WorkflowStepPhaseSuspending {
|
||||
continue
|
||||
@@ -231,6 +221,16 @@ func ResumeWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.App
|
||||
break
|
||||
}
|
||||
}
|
||||
if step.Phase != workflowv1alpha1.WorkflowStepPhaseSuspending {
|
||||
continue
|
||||
}
|
||||
if stepName == "" {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseRunning)
|
||||
} else if stepName == step.Name {
|
||||
wfUtils.OperateSteps(steps, i, -1, workflowv1alpha1.WorkflowStepPhaseRunning)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
@@ -489,7 +489,7 @@ func TerminateWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.
|
||||
if step.Reason != wfTypes.StatusReasonFailedAfterRetries && step.Reason != wfTypes.StatusReasonTimeout {
|
||||
steps[i].Reason = wfTypes.StatusReasonTerminate
|
||||
}
|
||||
case workflowv1alpha1.WorkflowStepPhaseRunning:
|
||||
case workflowv1alpha1.WorkflowStepPhaseRunning, workflowv1alpha1.WorkflowStepPhaseSuspending:
|
||||
steps[i].Phase = workflowv1alpha1.WorkflowStepPhaseFailed
|
||||
steps[i].Reason = wfTypes.StatusReasonTerminate
|
||||
default:
|
||||
@@ -500,7 +500,7 @@ func TerminateWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.
|
||||
if sub.Reason != wfTypes.StatusReasonFailedAfterRetries && sub.Reason != wfTypes.StatusReasonTimeout {
|
||||
steps[i].SubStepsStatus[j].Reason = wfTypes.StatusReasonTerminate
|
||||
}
|
||||
case workflowv1alpha1.WorkflowStepPhaseRunning:
|
||||
case workflowv1alpha1.WorkflowStepPhaseRunning, workflowv1alpha1.WorkflowStepPhaseSuspending:
|
||||
steps[i].SubStepsStatus[j].Phase = workflowv1alpha1.WorkflowStepPhaseFailed
|
||||
steps[i].SubStepsStatus[j].Reason = wfTypes.StatusReasonTerminate
|
||||
default:
|
||||
|
||||
@@ -72,6 +72,17 @@ 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())
|
||||
checkApp.Status.Workflow = &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{
|
||||
{
|
||||
StepStatus: workflowv1alpha1.StepStatus{
|
||||
Name: "step1",
|
||||
Type: "suspend",
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
|
||||
Expect(operator.Terminate(ctx)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
@@ -98,7 +109,7 @@ var _ = Describe("Kruise rollout test", func() {
|
||||
StepStatus: workflowv1alpha1.StepStatus{
|
||||
Name: "step1",
|
||||
Type: "suspend",
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import "list"
|
||||
metadata: {
|
||||
name: string
|
||||
namespace?: string
|
||||
annotations?: [string]: string
|
||||
...
|
||||
}
|
||||
...
|
||||
@@ -30,6 +31,12 @@ import "list"
|
||||
properties?: {...}
|
||||
}
|
||||
|
||||
#WorkflowStep: {
|
||||
type: string
|
||||
name: string
|
||||
properties?: {...}
|
||||
}
|
||||
|
||||
#Application: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
@@ -42,7 +49,9 @@ import "list"
|
||||
spec: {
|
||||
components: [...#Component]
|
||||
policies?: [...#Policy]
|
||||
workflow?: {...}
|
||||
workflow?: {
|
||||
steps: [...#WorkflowStep]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +60,7 @@ import "list"
|
||||
type: *"native" | "helm" | string
|
||||
appName: string
|
||||
appNamespace: string
|
||||
resources: [...#Resource]
|
||||
resources: [...#Resource]
|
||||
...
|
||||
}
|
||||
|
||||
@@ -77,8 +86,12 @@ import "list"
|
||||
_category: *"unknown" | string
|
||||
for key, kinds in resourceCategoryMap if list.Contains(kinds, r.kind) {
|
||||
_category: key
|
||||
},
|
||||
if r.metadata.annotations != _|_ if r.metadata.annotations["app.oam.dev/cluster"] != _|_ {
|
||||
_cluster: r.metadata.annotations["app.oam.dev/cluster"]
|
||||
}
|
||||
}]
|
||||
_clusters: [ for r in _resources if r._cluster != _|_ {r._cluster} ]
|
||||
resourceMap: {
|
||||
for key, val in resourceCategoryMap {
|
||||
"\(key)": [ for r in _resources if r._category == key {r}]
|
||||
@@ -95,56 +108,82 @@ import "list"
|
||||
comps: [
|
||||
if len(resourceMap.crd) > 0 {
|
||||
type: "k8s-objects"
|
||||
name: "\(appName).crds"
|
||||
name: "crds"
|
||||
properties: objects: [ for r in resourceMap.crd {
|
||||
apiVersion: r.apiVersion
|
||||
kind: r.kind
|
||||
metadata: name: r.metadata.name
|
||||
if r._cluster != _|_ {
|
||||
metadata: annotations: "app.oam.dev/cluster": (r._cluster)
|
||||
}
|
||||
}]
|
||||
},
|
||||
for r in resourceMap.ns {
|
||||
if len(resourceMap.ns) > 0 {
|
||||
type: "k8s-objects"
|
||||
name: "\(appName).ns.\(r.metadata.name)"
|
||||
properties: objects: [{
|
||||
name: "ns"
|
||||
properties: objects: [ for r in resourceMap.ns {
|
||||
apiVersion: r.apiVersion
|
||||
kind: r.kind
|
||||
metadata: name: r.metadata.name
|
||||
if r._cluster != _|_ {
|
||||
metadata: annotations: "app.oam.dev/cluster": (r._cluster)
|
||||
}
|
||||
}]
|
||||
},
|
||||
for r in resourceMap.workload + resourceMap.service {
|
||||
type: "k8s-objects"
|
||||
name: "\(appName).\(r.kind).\(r.metadata.name)"
|
||||
name: "\(r.kind).\(r.metadata.name)"
|
||||
properties: objects: [{
|
||||
apiVersion: r.apiVersion
|
||||
kind: r.kind
|
||||
metadata: name: r.metadata.name
|
||||
metadata: namespace: r.metadata.namespace
|
||||
if r.metadata.namespace != _|_ {
|
||||
metadata: namespace: r.metadata.namespace
|
||||
}
|
||||
spec: r.spec
|
||||
}]
|
||||
},
|
||||
for key in ["config", "sa", "operator", "storage"] if len(resourceMap[key]) > 0 {
|
||||
type: "k8s-objects"
|
||||
name: "\(appName).\(key)"
|
||||
properties: objects: [ for r in resourceMap.config {
|
||||
name: "\(key)"
|
||||
properties: objects: [ for r in resourceMap[key] {
|
||||
apiVersion: r.apiVersion
|
||||
kind: r.kind
|
||||
metadata: name: r.metadata.name
|
||||
if r.metadata.namespace != _|_ {
|
||||
metadata: namespace: r.metadata.namespace
|
||||
}
|
||||
if r._cluster != _|_ {
|
||||
metadata: annotations: "app.oam.dev/cluster": (r._cluster)
|
||||
}
|
||||
}]
|
||||
},
|
||||
for kind, rs in unknownByKinds {
|
||||
type: "k8s-objects"
|
||||
name: "\(appName).\(kind)"
|
||||
name: "\(kind)"
|
||||
properties: objects: [ for r in rs {
|
||||
apiVersion: r.apiVersion
|
||||
kind: r.kind
|
||||
metadata: name: r.metadata.name
|
||||
if r._cluster != _|_ {
|
||||
metadata: annotations: "app.oam.dev/cluster": (r._cluster)
|
||||
}
|
||||
}]
|
||||
},
|
||||
]
|
||||
|
||||
clusterCompMap: {
|
||||
for cluster in _clusters {
|
||||
"\(cluster)": [ for comp in comps if comp.properties.objects[0].metadata.annotations != _|_ if comp.properties.objects[0].metadata.annotations["app.oam.dev/cluster"] == cluster {comp.name} ]
|
||||
}
|
||||
}
|
||||
|
||||
compClusterMap: {
|
||||
for comp in comps if comp.properties.objects[0].metadata.annotations != _|_ {
|
||||
"\(comp.name)": comp.properties.objects[0].metadata.annotations["app.oam.dev/cluster"]
|
||||
}
|
||||
}
|
||||
|
||||
$returns: #Application & {
|
||||
metadata: {
|
||||
name: $args.appName
|
||||
@@ -152,7 +191,7 @@ import "list"
|
||||
labels: "app.oam.dev/adopt": $args.type
|
||||
}
|
||||
spec: components: comps
|
||||
spec: policies: [
|
||||
spec: policies: [
|
||||
{
|
||||
type: $args.mode
|
||||
name: $args.mode
|
||||
@@ -160,6 +199,11 @@ import "list"
|
||||
selector: componentNames: [ for comp in spec.components {comp.name}]
|
||||
}]
|
||||
},
|
||||
for cluster, comp in clusterCompMap if (len(clusterCompMap) < 2) && (cluster != "local") {
|
||||
type: "topology"
|
||||
name: "topology-" + cluster
|
||||
properties: clusters: [cluster]
|
||||
},
|
||||
if $args.mode == "take-over" {
|
||||
type: "garbage-collect"
|
||||
name: "garbage-collect"
|
||||
@@ -175,5 +219,11 @@ import "list"
|
||||
selector: resourceTypes: ["CustomResourceDefinition"]
|
||||
}]
|
||||
}]
|
||||
spec: workflow: steps: [for comp, c in compClusterMap if len(clusterCompMap) > 1 {
|
||||
type: "apply-component"
|
||||
name: "apply-component-" + comp
|
||||
properties: component: comp
|
||||
properties: cluster: c
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"time"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/kubevela/pkg/cue/cuex"
|
||||
"github.com/spf13/cobra"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
@@ -50,6 +50,8 @@ import (
|
||||
"github.com/kubevela/pkg/util/resourcetopology"
|
||||
velaslices "github.com/kubevela/pkg/util/slices"
|
||||
|
||||
"github.com/kubevela/pkg/multicluster"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -63,14 +65,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
adoptTypeNative = "native"
|
||||
adoptTypeHelm = "helm"
|
||||
adoptModeReadOnly = v1alpha1.ReadOnlyPolicyType
|
||||
adoptModeTakeOver = v1alpha1.TakeOverPolicyType
|
||||
helmDriverEnvKey = "HELM_DRIVER"
|
||||
defaultHelmDriver = "secret"
|
||||
adoptCUETempVal = "adopt"
|
||||
adoptCUETempFunc = "#Adopt"
|
||||
adoptTypeNative = "native"
|
||||
adoptTypeHelm = "helm"
|
||||
adoptModeReadOnly = v1alpha1.ReadOnlyPolicyType
|
||||
adoptModeTakeOver = v1alpha1.TakeOverPolicyType
|
||||
helmDriverEnvKey = "HELM_DRIVER"
|
||||
defaultHelmDriver = "secret"
|
||||
adoptCUETempVal = "adopt"
|
||||
adoptCUETempFunc = "#Adopt"
|
||||
defaultLocalCluster = "local"
|
||||
)
|
||||
|
||||
//go:embed adopt-templates/default.cue
|
||||
@@ -87,7 +90,8 @@ var (
|
||||
type resourceRef struct {
|
||||
schema.GroupVersionKind
|
||||
apitypes.NamespacedName
|
||||
Arg string
|
||||
Cluster string
|
||||
Arg string
|
||||
}
|
||||
|
||||
// AdoptOptions options for vela adopt command
|
||||
@@ -151,7 +155,7 @@ func (opt *AdoptOptions) parseResourceRef(f velacmd.Factory, cmd *cobra.Command,
|
||||
return nil, fmt.Errorf("no mappings found for resource %s: %w", arg, err)
|
||||
}
|
||||
mapping := mappings[0]
|
||||
or := &resourceRef{GroupVersionKind: gvk, Arg: arg}
|
||||
or := &resourceRef{GroupVersionKind: gvk, Cluster: defaultLocalCluster, Arg: arg}
|
||||
switch len(parts) {
|
||||
case 2:
|
||||
or.Name = parts[1]
|
||||
@@ -164,14 +168,18 @@ func (opt *AdoptOptions) parseResourceRef(f velacmd.Factory, cmd *cobra.Command,
|
||||
case 3:
|
||||
or.Namespace = parts[1]
|
||||
or.Name = parts[2]
|
||||
case 4:
|
||||
or.Cluster = parts[1]
|
||||
or.Namespace = parts[2]
|
||||
or.Name = parts[3]
|
||||
default:
|
||||
return nil, fmt.Errorf("resource should be like <type>/<name> or <type>/<namespace>/<name>")
|
||||
return nil, fmt.Errorf("resource should be like <type>/<name> or <type>/<namespace>/<name> or <type>/<cluster>/<namespace>/<name>")
|
||||
}
|
||||
return or, nil
|
||||
}
|
||||
|
||||
// Init .
|
||||
func (opt *AdoptOptions) Init(f velacmd.Factory, cmd *cobra.Command, args []string) error {
|
||||
func (opt *AdoptOptions) Init(f velacmd.Factory, cmd *cobra.Command, args []string) (err error) {
|
||||
if opt.All {
|
||||
if len(args) > 0 {
|
||||
for _, arg := range args {
|
||||
@@ -181,7 +189,7 @@ func (opt *AdoptOptions) Init(f velacmd.Factory, cmd *cobra.Command, args []stri
|
||||
}
|
||||
opt.AllGVKs = append(opt.AllGVKs, gvk)
|
||||
apiVersion, kind := gvk.ToAPIVersionAndKind()
|
||||
fmt.Fprintf(opt.Out, "Adopt all %s/%s resources\n", apiVersion, kind)
|
||||
_, _ = fmt.Fprintf(opt.Out, "Adopt all %s/%s resources\n", apiVersion, kind)
|
||||
}
|
||||
}
|
||||
if len(opt.AllGVKs) == 0 {
|
||||
@@ -212,7 +220,10 @@ func (opt *AdoptOptions) Init(f velacmd.Factory, cmd *cobra.Command, args []stri
|
||||
opt.ResourceTopologyRule = defaultResourceTopologyRule
|
||||
}
|
||||
opt.AppNamespace = velacmd.GetNamespace(f, cmd)
|
||||
opt.AdoptTemplateCUEValue = cuecontext.New().CompileString(fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
|
||||
opt.AdoptTemplateCUEValue, err = cuex.CompileString(cmd.Context(), fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile template: %w", err)
|
||||
}
|
||||
switch opt.Type {
|
||||
case adoptTypeNative:
|
||||
if opt.Recycle {
|
||||
@@ -277,9 +288,9 @@ func (opt *AdoptOptions) MultipleRun(f velacmd.Factory, cmd *cobra.Command) erro
|
||||
_, _ = fmt.Fprintf(opt.Out, "Warning: failed to list resources from %s/%s: %s", apiVersion, kind, err.Error())
|
||||
continue
|
||||
}
|
||||
engine := resourcetopology.New(opt.ResourceTopologyRule)
|
||||
dedup := make([]k8s.ResourceIdentifier, 0)
|
||||
for _, item := range list.Items {
|
||||
engine := resourcetopology.New(opt.ResourceTopologyRule)
|
||||
itemIdentifier := k8s.ResourceIdentifier{
|
||||
Name: item.GetName(),
|
||||
Namespace: item.GetNamespace(),
|
||||
@@ -351,7 +362,7 @@ func (opt *AdoptOptions) MultipleRun(f velacmd.Factory, cmd *cobra.Command) erro
|
||||
}
|
||||
|
||||
// Complete autofill fields in opts
|
||||
func (opt *AdoptOptions) Complete(f velacmd.Factory, cmd *cobra.Command, args []string) error {
|
||||
func (opt *AdoptOptions) Complete(f velacmd.Factory, cmd *cobra.Command, args []string) (err error) {
|
||||
opt.AppNamespace = velacmd.GetNamespace(f, cmd)
|
||||
switch opt.Type {
|
||||
case adoptTypeNative:
|
||||
@@ -389,21 +400,25 @@ func (opt *AdoptOptions) Complete(f velacmd.Factory, cmd *cobra.Command, args []
|
||||
default:
|
||||
}
|
||||
if opt.AppName != "" {
|
||||
var ctx = context.Background()
|
||||
app := &v1beta1.Application{}
|
||||
err := f.Client().Get(ctx, apitypes.NamespacedName{Namespace: opt.AppNamespace, Name: opt.AppName}, app)
|
||||
err := f.Client().Get(cmd.Context(), apitypes.NamespacedName{Namespace: opt.AppNamespace, Name: opt.AppName}, app)
|
||||
if err == nil && app != nil {
|
||||
if !opt.Yes {
|
||||
if !opt.Yes && opt.Apply {
|
||||
userInput := NewUserInput()
|
||||
confirm := userInput.AskBool("Application '%s' already exists, apply will override the existing app with the adopted one, please confirm [Y/n]: "+opt.AppName, &UserInputOptions{AssumeYes: false})
|
||||
confirm := userInput.AskBool(
|
||||
fmt.Sprintf("Application '%s' already exists, apply will override the existing app with the adopted one, please confirm [Y/n]: ", opt.AppName),
|
||||
&UserInputOptions{AssumeYes: false})
|
||||
if !confirm {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
opt.AdoptTemplateCUEValue = cuecontext.New().CompileString(fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
|
||||
return nil
|
||||
opt.AdoptTemplateCUEValue, err = cuex.CompileString(cmd.Context(), fmt.Sprintf("%s\n\n%s: %s", opt.AdoptTemplate, adoptCUETempVal, adoptCUETempFunc))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile cue template: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate if opts is valid
|
||||
@@ -428,9 +443,13 @@ func (opt *AdoptOptions) loadNative(f velacmd.Factory, cmd *cobra.Command) error
|
||||
for _, ref := range opt.NativeResourceRefs {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetGroupVersionKind(ref.GroupVersionKind)
|
||||
if err := f.Client().Get(cmd.Context(), apitypes.NamespacedName{Namespace: ref.Namespace, Name: ref.Name}, obj); err != nil {
|
||||
return fmt.Errorf("failed to get resource for %s: %w", ref.Arg, err)
|
||||
if err := f.Client().Get(multicluster.WithCluster(cmd.Context(), ref.Cluster), apitypes.NamespacedName{Namespace: ref.Namespace, Name: ref.Name}, obj); err != nil {
|
||||
return fmt.Errorf("fail to get resource for %s: %w", ref.Arg, err)
|
||||
}
|
||||
annos := map[string]string{
|
||||
oam.LabelAppCluster: ref.Cluster,
|
||||
}
|
||||
obj.SetAnnotations(annos)
|
||||
opt.Resources = append(opt.Resources, obj)
|
||||
}
|
||||
return nil
|
||||
@@ -456,6 +475,10 @@ func (opt *AdoptOptions) loadHelm() error {
|
||||
klog.Warningf("unable to decode object %s: %s", val, err)
|
||||
continue
|
||||
}
|
||||
annos := map[string]string{
|
||||
oam.LabelAppCluster: defaultLocalCluster,
|
||||
}
|
||||
obj.SetAnnotations(annos)
|
||||
objs = append(objs, obj)
|
||||
}
|
||||
opt.Resources = objs
|
||||
|
||||
@@ -1065,19 +1065,22 @@ func NewDefinitionValidateCommand(c common.Args) *cobra.Command {
|
||||
// NewDefinitionGenAPICommand create the `vela def gen-api` command to help user generate Go code from the definition
|
||||
func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
|
||||
meta := gen_sdk.GenMeta{}
|
||||
var languageArgs []string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "gen-api DEFINITION.cue",
|
||||
Short: "Generate SDK from X-Definition.",
|
||||
Long: "Generate SDK from X-definition file.\n" +
|
||||
"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH" +
|
||||
"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH\n" +
|
||||
"* Currently, this function is still working in progress and not all formats of parameter in X-definition are supported yet.",
|
||||
Example: "# Generate SDK for golang with scaffold initialized\n" +
|
||||
"> vela def gen-api --init --lang go -f /path/to/def -o /path/to/sdk\n" +
|
||||
"> vela def gen-api --init --language go -f /path/to/def -o /path/to/sdk\n" +
|
||||
"# Generate incremental definition files to existing sdk directory\n" +
|
||||
"> vela def gen-api --lang go -f /path/to/def -o /path/to/sdk",
|
||||
"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk\n" +
|
||||
"# Generate definitions to a sub-module\n" +
|
||||
"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk --submodule --api-dir path/relative/to/output --language-args arg1=val1,arg2=val2\n",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := meta.Init(c)
|
||||
err := meta.Init(c, languageArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1097,13 +1100,26 @@ func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&meta.Output, "output", "o", "./apis", "Output directory path")
|
||||
cmd.Flags().StringVarP(&meta.Package, "package", "p", "github.com/kubevela/vela-go-sdk", "Package name of generated code")
|
||||
cmd.Flags().StringVarP(&meta.Lang, "lang", "g", "go", "Language to generate code. Valid languages: go")
|
||||
cmd.Flags().StringVar(&meta.APIDirectory, "api-dir", "", "API directory path to put definition API files, relative to output directory. Default value: go: pkg/apis")
|
||||
cmd.Flags().BoolVar(&meta.IsSubModule, "submodule", false, "Whether the generated code is a submodule of the project. If set, the directory specified by `api-dir` will be treated as a submodule of the project")
|
||||
cmd.Flags().StringVarP(&meta.Package, "package", "p", gen_sdk.PackagePlaceHolder, "Package name of generated code")
|
||||
cmd.Flags().StringVarP(&meta.Lang, "language", "g", "go", "Language to generate code. Valid languages: go")
|
||||
cmd.Flags().StringVarP(&meta.Template, "template", "t", "", "Template file path, if not specified, the default template will be used")
|
||||
cmd.Flags().StringSliceVarP(&meta.File, "file", "f", nil, "File name of definitions, can be specified multiple times, or use comma to separate multiple files. If directory specified, all files found recursively in the directory will be used")
|
||||
cmd.Flags().BoolVar(&meta.InitSDK, "init", false, "Init the whole SDK project, if not set, only the API file will be generated")
|
||||
cmd.Flags().BoolVarP(&meta.Verbose, "verbose", "v", false, "Print verbose logs")
|
||||
var langArgsDescStr string
|
||||
for lang, args := range gen_sdk.LangArgsRegistry {
|
||||
langArgsDescStr += lang + ": \n"
|
||||
for key, arg := range args {
|
||||
langArgsDescStr += fmt.Sprintf("\t%s: %s(default: %s)\n", key, arg.Name, arg.Default)
|
||||
}
|
||||
}
|
||||
cmd.Flags().StringSliceVar(&languageArgs, "language-args", []string{},
|
||||
fmt.Sprintf("language-specific arguments to pass to the go generator, available options: \n"+langArgsDescStr),
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package cli
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
@@ -34,6 +35,8 @@ import (
|
||||
"github.com/oam-dev/kubevela/references/appfile"
|
||||
)
|
||||
|
||||
var re = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`)
|
||||
|
||||
// NewLogsCommand creates `logs` command to tail logs of application
|
||||
func NewLogsCommand(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command {
|
||||
largs := &Args{Args: c}
|
||||
@@ -122,6 +125,10 @@ func (l *Args) printPodLogs(ctx context.Context, ioStreams util.IOStreams, selec
|
||||
}
|
||||
}
|
||||
if show {
|
||||
match := re.FindStringSubmatch(str)
|
||||
if len(match) > 1 {
|
||||
str = strings.ReplaceAll(match[1], "\\n", "\n")
|
||||
}
|
||||
ioStreams.Infonln(str)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -58,8 +58,10 @@ commonPeerResources: [{
|
||||
resource: "configMap"
|
||||
selectors: {
|
||||
name: [
|
||||
for v in context.data.spec.template.spec.volumes if v.configMap != _|_ if v.configMap.name != _|_ {
|
||||
v.configMap.name
|
||||
if context.data.spec.template.spec.volumes != _|_ {
|
||||
for v in context.data.spec.template.spec.volumes if v.configMap != _|_ if v.configMap.name != _|_ {
|
||||
v.configMap.name
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -68,8 +70,10 @@ commonPeerResources: [{
|
||||
resource: "secret"
|
||||
selectors: {
|
||||
name: [
|
||||
for v in context.data.spec.template.spec.volumes if v.secret != _|_ if v.secret.name != _|_ {
|
||||
v.secret.name
|
||||
if context.data.spec.template.spec.volumes != _|_ {
|
||||
for v in context.data.spec.template.spec.volumes if v.secret != _|_ if v.secret.name != _|_ {
|
||||
v.secret.name
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ func (m *Menu) StackPop(_, new model.View) {
|
||||
} else {
|
||||
m.UpdateMenu(new.Hint())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// StackPush change itself when accept "push" notify from app's main view
|
||||
|
||||
@@ -291,7 +291,7 @@ func makeThemeConfigFileIfNotExist() bool {
|
||||
if _, err := os.Open(filepath.Clean(themeConfigFilePath)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// make file if not exist
|
||||
_ = os.MkdirAll(filepath.Clean(velaThemeHome), 0600)
|
||||
_ = os.MkdirAll(filepath.Clean(velaThemeHome), 0700)
|
||||
_ = os.WriteFile(filepath.Clean(themeConfigFilePath), []byte("name : "+DefaultTheme), 0600)
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
@@ -35,3 +37,11 @@ func TestColor(t *testing.T) {
|
||||
c2 := Color("red")
|
||||
assert.Equal(t, c2.Color(), tcell.GetColor("red").TrueColor())
|
||||
}
|
||||
|
||||
func TestPersistentThemeConfig(t *testing.T) {
|
||||
defer PersistentThemeConfig(DefaultTheme)
|
||||
PersistentThemeConfig("foo")
|
||||
bytes, err := os.ReadFile(themeConfigFilePath)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, strings.Contains(string(bytes), "foo"))
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (v *ApplicationView) Title() string {
|
||||
func (v *ApplicationView) bindKeys() {
|
||||
v.Actions().Delete([]tcell.Key{tcell.KeyEnter})
|
||||
v.Actions().Add(model.KeyActions{
|
||||
tcell.KeyESC: model.KeyAction{Description: "Exist", Action: v.app.Exist, Visible: true, Shared: true},
|
||||
tcell.KeyESC: model.KeyAction{Description: "Exit", Action: v.app.Exist, Visible: true, Shared: true},
|
||||
tcell.KeyEnter: model.KeyAction{Description: "Managed Resource", Action: v.managedResourceView, Visible: true, Shared: true},
|
||||
component.KeyN: model.KeyAction{Description: "Select Namespace", Action: v.namespaceView, Visible: true, Shared: true},
|
||||
component.KeyY: model.KeyAction{Description: "Yaml", Action: v.yamlView, Visible: true, Shared: true},
|
||||
|
||||
@@ -254,6 +254,7 @@ type WorkflowArgs struct {
|
||||
Writer io.Writer
|
||||
Args common.Args
|
||||
StepName string
|
||||
StepID string
|
||||
ErrMap map[string]string
|
||||
App *v1beta1.Application
|
||||
WorkflowRun *workflowv1alpha1.WorkflowRun
|
||||
@@ -412,7 +413,7 @@ func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioS
|
||||
return w.printResourceLogs(ctx, cli, ioStreams, []wfTypes.Resource{{
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
LabelSelector: w.ControllerLabels,
|
||||
}}, []string{fmt.Sprintf(`step_name="%s"`, w.StepName), fmt.Sprintf("%s/%s", w.WorkflowInstance.Namespace, w.WorkflowInstance.Name), "cue logs"})
|
||||
}}, []string{fmt.Sprintf(`stepSessionID="%s"`, w.StepID), fmt.Sprintf("%s/%s", w.WorkflowInstance.Namespace, w.WorkflowInstance.Name), "cue logs"})
|
||||
case logConfig.Source != nil:
|
||||
if len(logConfig.Source.Resources) > 0 {
|
||||
return w.printResourceLogs(ctx, cli, ioStreams, logConfig.Source.Resources, nil)
|
||||
@@ -467,7 +468,8 @@ func (w *WorkflowArgs) selectWorkflowStep(msg string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select step %s: %w", unwrapStepName(w.StepName), err)
|
||||
}
|
||||
w.StepName = unwrapStepID(stepName, w.WorkflowInstance)
|
||||
w.StepName = unwrapStepName(stepName)
|
||||
w.StepID = unwrapStepID(stepName, w.WorkflowInstance)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
@@ -48,16 +47,6 @@ func InstallComponentDefinition(client client.Client, componentData []byte, ioSt
|
||||
}
|
||||
cd.Namespace = types.DefaultKubeVelaNS
|
||||
ioStreams.Info("Installing component: " + cd.Name)
|
||||
if tp.Install != nil {
|
||||
tp.Source.ChartName = tp.Install.Helm.Name
|
||||
if err = helm.InstallHelmChart(ioStreams, tp.Install.Helm); err != nil {
|
||||
return err
|
||||
}
|
||||
err = addSourceIntoExtension(cd.Spec.Extension, tp.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cd.Spec.Workload.Type == "" {
|
||||
tp.CrdInfo = &types.CRDInfo{
|
||||
APIVersion: cd.Spec.Workload.Definition.APIVersion,
|
||||
@@ -79,19 +68,6 @@ func InstallTraitDefinition(client client.Client, mapper discoverymapper.Discove
|
||||
}
|
||||
td.Namespace = types.DefaultKubeVelaNS
|
||||
ioStreams.Info("Installing trait " + td.Name)
|
||||
if cap.Install != nil {
|
||||
cap.Source.ChartName = cap.Install.Helm.Name
|
||||
if err = helm.InstallHelmChart(ioStreams, cap.Install.Helm); err != nil {
|
||||
return err
|
||||
}
|
||||
err = addSourceIntoExtension(td.Spec.Extension, cap.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = HackForStandardTrait(*cap, client); err != nil {
|
||||
return err
|
||||
}
|
||||
gvk, err := util.GetGVKFromDefinition(mapper, td.Spec.Reference)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -109,25 +85,6 @@ func InstallTraitDefinition(client client.Client, mapper discoverymapper.Discove
|
||||
return nil
|
||||
}
|
||||
|
||||
// HackForStandardTrait will do some hack install for standard registry
|
||||
func HackForStandardTrait(tp types.Capability, client client.Client) error {
|
||||
switch tp.Name {
|
||||
case "metrics":
|
||||
// metrics trait will rely on a Prometheus instance to be installed
|
||||
// make sure the chart is a prometheus operator
|
||||
if tp.Install == nil {
|
||||
break
|
||||
}
|
||||
if tp.Install.Helm.Namespace == "monitoring" && tp.Install.Helm.Name == "kube-prometheus-stack" {
|
||||
if err := InstallPrometheusInstance(client); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addSourceIntoExtension(in *runtime.RawExtension, source *types.Source) error {
|
||||
var extension map[string]interface{}
|
||||
err := json.Unmarshal(in.Raw, &extension)
|
||||
|
||||
@@ -19,7 +19,6 @@ package docgen
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -42,8 +41,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -300,16 +297,9 @@ func GetPolicies(ctx context.Context, namespace string, c common.Args) ([]types.
|
||||
return templates, templateErrors, nil
|
||||
}
|
||||
|
||||
// validateCapabilities validates whether helm charts are successful installed, GVK are successfully retrieved.
|
||||
// validateCapabilities validates whether GVK are successfully retrieved.
|
||||
func validateCapabilities(tmp *types.Capability, dm discoverymapper.DiscoveryMapper, definitionName string, reference commontypes.DefinitionReference) error {
|
||||
var err error
|
||||
if tmp.Install != nil {
|
||||
tmp.Source = &types.Source{ChartName: tmp.Install.Helm.Name}
|
||||
ioStream := util2.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
|
||||
if err = helm.InstallHelmChart(ioStream, tmp.Install.Helm); err != nil {
|
||||
return fmt.Errorf("unable to install helm chart dependency %s(%s from %s) for this trait '%s': %w ", tmp.Install.Helm.Name, tmp.Install.Helm.Version, tmp.Install.Helm.URL, definitionName, err)
|
||||
}
|
||||
}
|
||||
gvk, err := util.GetGVKFromDefinition(dm, reference)
|
||||
if err != nil {
|
||||
errMsg := err.Error()
|
||||
|
||||
35
references/docgen/def-doc/workflowstep/check-metrics.eg.md
Normal file
35
references/docgen/def-doc/workflowstep/check-metrics.eg.md
Normal file
@@ -0,0 +1,35 @@
|
||||
```yaml
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: canary-demo
|
||||
annotations:
|
||||
app.oam.dev/publishVersion: v2
|
||||
spec:
|
||||
components:
|
||||
- name: canary-demo
|
||||
type: webservice
|
||||
properties:
|
||||
image: wangyikewyk/canarydemo:v2
|
||||
ports:
|
||||
- port: 8090
|
||||
traits:
|
||||
- type: scaler
|
||||
properties:
|
||||
replicas: 5
|
||||
- type: gateway
|
||||
properties:
|
||||
domain: canary-demo.com
|
||||
http:
|
||||
"/version": 8090
|
||||
workflow:
|
||||
steps:
|
||||
- name: 200-status-percent-2-phase
|
||||
type: check-metrics
|
||||
timeout: 3m
|
||||
properties:
|
||||
query: sum(irate(nginx_ingress_controller_requests{host="canary-demo.com",status="200"}[5m]))/sum(irate(nginx_ingress_controller_requests{host="canary-demo.com"}[2m]))
|
||||
promAddress: "http://prometheus-server.o11y-system.svc:9090"
|
||||
condition: ">=0.95"
|
||||
duration: 2m
|
||||
```
|
||||
@@ -711,7 +711,9 @@ var _ = Describe("Test multicluster scenario", func() {
|
||||
app := &v1beta1.Application{}
|
||||
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
|
||||
app.SetNamespace(testNamespace)
|
||||
Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
|
||||
Eventually(func(g Gomega) { // informer may have latency for the added definition
|
||||
g.Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
|
||||
}).WithTimeout(10 * time.Second).WithPolling(2 * time.Second).Should(Succeed())
|
||||
key := client.ObjectKeyFromObject(app)
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Get(hubCtx, key, app)).Should(Succeed())
|
||||
@@ -967,7 +969,9 @@ var _ = Describe("Test multicluster scenario", func() {
|
||||
}},
|
||||
}}},
|
||||
}
|
||||
Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
|
||||
Eventually(func(g Gomega) { // in case the trait definition has not been watched by vela-core
|
||||
g.Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
|
||||
}).WithTimeout(10 * time.Second).WithPolling(2 * time.Second).Should(Succeed())
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Get(hubCtx, appKey, app)).Should(Succeed())
|
||||
@@ -987,5 +991,32 @@ var _ = Describe("Test multicluster scenario", func() {
|
||||
g.Expect(app.Status.Services[0].Traits[0].Healthy).Should(BeTrue())
|
||||
}).WithTimeout(20 * time.Second).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test application carrying deploy step with inline policy", func() {
|
||||
ctx := context.Background()
|
||||
wsDef := &v1beta1.WorkflowStepDefinition{}
|
||||
bs, err := os.ReadFile("./testdata/def/inline-deploy.yaml")
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(yaml.Unmarshal(bs, wsDef)).Should(Succeed())
|
||||
wsDef.SetNamespace(namespace)
|
||||
Expect(k8sClient.Create(ctx, wsDef)).Should(Succeed())
|
||||
app := &v1beta1.Application{}
|
||||
bs, err = os.ReadFile("./testdata/app/app-carrying-deploy-step-with-inline-policy.yaml")
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
|
||||
app.SetNamespace(namespace)
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
}).WithPolling(2 * time.Second).WithTimeout(5 * time.Second).Should(Succeed())
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
Eventually(func(g Gomega) {
|
||||
_app := &v1beta1.Application{}
|
||||
g.Expect(k8sClient.Get(ctx, appKey, _app)).Should(Succeed())
|
||||
g.Expect(_app.Status.Phase).Should(Equal(common.ApplicationRunning))
|
||||
}).WithPolling(2 * time.Second).WithTimeout(20 * time.Second).Should(Succeed())
|
||||
_deploy := &appsv1.Deployment{}
|
||||
Expect(k8sClient.Get(ctx, appKey, _deploy)).Should(Succeed())
|
||||
Expect(int(*_deploy.Spec.Replicas)).Should(Equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
19
test/e2e-multicluster-test/testdata/app/app-carrying-deploy-step-with-inline-policy.yaml
vendored
Normal file
19
test/e2e-multicluster-test/testdata/app/app-carrying-deploy-step-with-inline-policy.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
components:
|
||||
- name: test
|
||||
type: webservice
|
||||
properties:
|
||||
image: nginx:1.20
|
||||
policies:
|
||||
- type: topology
|
||||
name: topo
|
||||
properties:
|
||||
clusters: ["cluster-worker"]
|
||||
workflow:
|
||||
steps:
|
||||
- type: inline-deploy
|
||||
name: deploy
|
||||
25
test/e2e-multicluster-test/testdata/def/inline-deploy.yaml
vendored
Normal file
25
test/e2e-multicluster-test/testdata/def/inline-deploy.yaml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
name: inline-deploy
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import "vela/op"
|
||||
deploy: op.#Deploy & {
|
||||
policies: []
|
||||
parallelism: 5
|
||||
ignoreTerraformComponent: true
|
||||
inlinePolicies: [{
|
||||
type: "override"
|
||||
name: "set-replica"
|
||||
properties: components: [{
|
||||
traits: [{
|
||||
type: "scaler"
|
||||
properties: replicas: 0
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
parameter: {}
|
||||
@@ -76,7 +76,7 @@ var _ = Describe("HealthScope", func() {
|
||||
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test an application with health policy", func() {
|
||||
PIt("Test an application with health policy", func() {
|
||||
By("Apply a healthy application")
|
||||
var newApp v1beta1.Application
|
||||
var healthyAppName, unhealthyAppName string
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: "Describes cron jobs that run code or a script to completion."
|
||||
attributes: workload: {
|
||||
definition: {
|
||||
apiVersion: "batch/v1beta1"
|
||||
kind: "CronJob"
|
||||
}
|
||||
type: "cronjobs.batch"
|
||||
}
|
||||
attributes: workload: type: "autodetects.core.oam.dev"
|
||||
}
|
||||
template: {
|
||||
output: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
expose: {
|
||||
@@ -54,22 +55,66 @@ template: {
|
||||
metadata: name: context.name
|
||||
metadata: annotations: parameter.annotations
|
||||
spec: {
|
||||
selector: "app.oam.dev/component": context.name
|
||||
ports: [
|
||||
for p in parameter.port {
|
||||
name: "port-" + strconv.FormatInt(p, 10)
|
||||
port: p
|
||||
targetPort: p
|
||||
if parameter["matchLabels"] == _|_ {
|
||||
selector: "app.oam.dev/component": context.name
|
||||
}
|
||||
if parameter["matchLabels"] != _|_ {
|
||||
selector: parameter["matchLabels"]
|
||||
}
|
||||
|
||||
// compatible with the old way
|
||||
if parameter["port"] != _|_ if parameter["ports"] == _|_ {
|
||||
ports: [
|
||||
for p in parameter.port {
|
||||
name: "port-" + strconv.FormatInt(p, 10)
|
||||
port: p
|
||||
targetPort: p
|
||||
},
|
||||
]
|
||||
}
|
||||
if parameter["ports"] != _|_ {
|
||||
ports: [ for v in parameter.ports {
|
||||
port: v.port
|
||||
targetPort: v.port
|
||||
if v.name != _|_ {
|
||||
name: v.name
|
||||
}
|
||||
if v.name == _|_ {
|
||||
_name: "port-" + strconv.FormatInt(v.port, 10)
|
||||
name: *_name | string
|
||||
if v.protocol != "TCP" {
|
||||
name: _name + "-" + strings.ToLower(v.protocol)
|
||||
}
|
||||
}
|
||||
if v.nodePort != _|_ if parameter.type == "NodePort" {
|
||||
nodePort: v.nodePort
|
||||
}
|
||||
if v.protocol != _|_ {
|
||||
protocol: v.protocol
|
||||
}
|
||||
},
|
||||
]
|
||||
]
|
||||
}
|
||||
type: parameter.type
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the exposion ports
|
||||
port: [...int]
|
||||
// +usage=Deprecated, the old way to specify the exposion ports
|
||||
port?: [...int]
|
||||
// +usage=Specify portsyou want customer traffic sent to
|
||||
ports?: [...{
|
||||
// +usage=Number of port to expose on the pod's IP address
|
||||
port: int
|
||||
// +usage=Name of the port
|
||||
name?: string
|
||||
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
|
||||
protocol: *"TCP" | "UDP" | "SCTP"
|
||||
// +usage=exposed node port. Only Valid when exposeType is NodePort
|
||||
nodePort?: int
|
||||
}]
|
||||
// +usage=Specify the annotaions of the exposed service
|
||||
annotations: [string]: string
|
||||
annotations: [string]: string
|
||||
matchLabels?: [string]: string
|
||||
// +usage=Specify what kind of Service you want. options: "ClusterIP","NodePort","LoadBalancer","ExternalName"
|
||||
type: *"ClusterIP" | "NodePort" | "LoadBalancer" | "ExternalName"
|
||||
}
|
||||
|
||||
@@ -14,19 +14,20 @@ gateway: {
|
||||
}
|
||||
if context.outputs.ingress.status.loadBalancer.ingress != _|_ {
|
||||
let igs = context.outputs.ingress.status.loadBalancer.ingress
|
||||
let host = context.outputs.ingress.spec.rules[0].host
|
||||
if igs[0].ip != _|_ {
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host + ", IP: " + igs[0].ip
|
||||
}
|
||||
if igs[0].host == _|_ {
|
||||
if host == _|_ {
|
||||
message: "Host not specified, visit the cluster or load balancer in front of the cluster with IP: " + igs[0].ip
|
||||
}
|
||||
}
|
||||
if igs[0].ip == _|_ {
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host
|
||||
}
|
||||
if igs[0].host != _|_ {
|
||||
if host != _|_ {
|
||||
message: "Host not specified, visit the cluster or load balancer in front of the cluster"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
"check-metrics": {
|
||||
type: "workflow-step"
|
||||
labels: {
|
||||
"catalog": "Delivery"
|
||||
}
|
||||
annotations: {
|
||||
"category": "Application Delivery"
|
||||
}
|
||||
description: "Verify application's metrics"
|
||||
}
|
||||
template: {
|
||||
check: op.#PromCheck & {
|
||||
query: parameter.query
|
||||
metricEndpoint: parameter.metricEndpoint
|
||||
condition: parameter.condition
|
||||
stepID: context.stepSessionID
|
||||
duration: parameter.duration
|
||||
failDuration: parameter.failDuration
|
||||
}
|
||||
|
||||
fail: op.#Steps & {
|
||||
if check.failed != _|_ {
|
||||
if check.failed == true {
|
||||
breakWorkflow: op.#Fail & {
|
||||
message: check.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: check.result
|
||||
if check.message != _|_ {
|
||||
message: check.message
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Query is a raw prometheus query to perform
|
||||
query: string
|
||||
// +usage=The HTTP address and port of the prometheus server
|
||||
metricEndpoint?: "http://prometheus-server.o11y-system.svc:9090" | string
|
||||
// +usage=Condition is an expression which determines if a measurement is considered successful. eg: >=0.95
|
||||
condition: string
|
||||
// +usage=Duration defines the duration of time required for this step to be considered successful.
|
||||
duration?: *"5m" | string
|
||||
// +usage=FailDuration is the duration of time that, if the check fails, will result in the step being marked as failed.
|
||||
failDuration?: *"2m" | string
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user