mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
Feat: add new providers and fix definitions (#6599)
* feat: add new providers and fix definitions Signed-off-by: FogDong <fog@bentoml.com> * fix: fix definitions and tests Signed-off-by: FogDong <fog@bentoml.com> * fix: fix lint and helm Signed-off-by: FogDong <fog@bentoml.com> * fix: fix definitions Signed-off-by: FogDong <fog@bentoml.com> * fix: add multicluster Signed-off-by: FogDong <fog@bentoml.com> * fix: fix e2e Signed-off-by: FogDong <fog@bentoml.com> * fix: fix dynamic client for cli Signed-off-by: FogDong <fog@bentoml.com> * fix: fix api gen Signed-off-by: FogDong <fog@bentoml.com> * fix: fix lint Signed-off-by: FogDong <fog@bentoml.com> --------- Signed-off-by: FogDong <fog@bentoml.com>
This commit is contained in:
@@ -38,6 +38,11 @@ jobs:
|
||||
make goimports
|
||||
make golangci
|
||||
|
||||
- name: Setup KinD
|
||||
run: |
|
||||
go install sigs.k8s.io/kind@v0.19.0
|
||||
kind create cluster
|
||||
|
||||
- name: Build CLI
|
||||
run: make vela-cli
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ fmt: goimports installcue
|
||||
$(CUE) fmt ./vela-templates/definitions/deprecated/*
|
||||
$(CUE) fmt ./vela-templates/definitions/registry/*
|
||||
$(CUE) fmt ./pkg/workflow/template/static/*
|
||||
$(CUE) fmt ./pkg/workflow/providers/legacy/...
|
||||
$(CUE) fmt ./pkg/workflow/providers/...
|
||||
|
||||
## sdk_fmt: Run go fmt against code
|
||||
sdk_fmt:
|
||||
|
||||
@@ -48,12 +48,14 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
|
||||
### KubeVela workflow parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| -------------------------------------- | ------------------------------------------------------ | ------- |
|
||||
| `workflow.enableSuspendOnFailure` | Enable suspend on workflow failure | `false` |
|
||||
| `workflow.backoff.maxTime.waitState` | The max backoff time of workflow in a wait condition | `60` |
|
||||
| `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` |
|
||||
| Name | Description | Value |
|
||||
| ------------------------------------------------------- | ------------------------------------------------------- | ------- |
|
||||
| `workflow.enableSuspendOnFailure` | Enable suspend on workflow failure | `false` |
|
||||
| `workflow.enableExternalPackageForDefaultCompiler` | Enable external package for default cuex compiler | `true` |
|
||||
| `workflow.enableExternalPackageWatchForDefaultCompiler` | Enable external package watch for default cuex compiler | `false` |
|
||||
| `workflow.backoff.maxTime.waitState` | The max backoff time of workflow in a wait condition | `60` |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.3
|
||||
creationTimestamp: null
|
||||
name: packages.cue.oam.dev
|
||||
spec:
|
||||
group: cue.oam.dev
|
||||
names:
|
||||
kind: Package
|
||||
listKind: PackageList
|
||||
plural: packages
|
||||
shortNames:
|
||||
- pkg
|
||||
- cpkg
|
||||
- cuepkg
|
||||
- cuepackage
|
||||
singular: package
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.path
|
||||
name: PATH
|
||||
type: string
|
||||
- jsonPath: .spec.provider.protocol
|
||||
name: PROTO
|
||||
type: string
|
||||
- jsonPath: .spec.provider.endpoint
|
||||
name: ENDPOINT
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Package is an extension for cuex engine
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PackageSpec the spec for Package
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
provider:
|
||||
description: Provider the external Provider in Package for cuex to
|
||||
run functions
|
||||
properties:
|
||||
endpoint:
|
||||
type: string
|
||||
protocol:
|
||||
description: ProviderProtocol the protocol type for external Provider
|
||||
type: string
|
||||
required:
|
||||
- endpoint
|
||||
- protocol
|
||||
type: object
|
||||
templates:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- path
|
||||
- templates
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources: {}
|
||||
@@ -16,36 +16,39 @@ spec:
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
"vela/builtin"
|
||||
)
|
||||
|
||||
output: op.#Apply & {
|
||||
cluster: parameter.cluster
|
||||
value: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: {
|
||||
name: context.stepName
|
||||
namespace: context.namespace
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
|
||||
replicas: parameter.replicas
|
||||
template: {
|
||||
metadata: labels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
|
||||
spec: containers: [{
|
||||
name: context.stepName
|
||||
image: parameter.image
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
output: kube.#Apply & {
|
||||
$params: {
|
||||
cluster: parameter.cluster
|
||||
value: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: {
|
||||
name: context.stepName
|
||||
namespace: context.namespace
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
|
||||
replicas: parameter.replicas
|
||||
template: {
|
||||
metadata: labels: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"
|
||||
spec: containers: [{
|
||||
name: context.stepName
|
||||
image: parameter.image
|
||||
if parameter["cmd"] != _|_ {
|
||||
command: parameter.cmd
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: output.value.status.readyReplicas == parameter.replicas
|
||||
wait: builtin.#ConditionalWait & {
|
||||
$params: continue: output.$returns.value.status.readyReplicas == parameter.replicas
|
||||
}
|
||||
parameter: {
|
||||
image: string
|
||||
|
||||
@@ -13,13 +13,13 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
)
|
||||
|
||||
apply: op.#Apply & {
|
||||
value: parameter.value
|
||||
cluster: parameter.cluster
|
||||
apply: kube.#Apply & {
|
||||
$params: parameter
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Specify Kubernetes native resource object to be applied
|
||||
value: {...}
|
||||
|
||||
@@ -14,11 +14,12 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
"vela/builtin"
|
||||
)
|
||||
|
||||
apply: op.#Apply & {
|
||||
value: {
|
||||
apply: kube.#Apply & {
|
||||
$params: value: {
|
||||
apiVersion: "terraform.core.oam.dev/v1beta2"
|
||||
kind: "Configuration"
|
||||
metadata: {
|
||||
@@ -53,8 +54,10 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
check: op.#ConditionalWait & {
|
||||
continue: apply.value.status != _|_ && apply.value.status.apply != _|_ && apply.value.status.apply.state == "Available"
|
||||
check: builtin.#ConditionalWait & {
|
||||
if apply.$returns.value.status != _|_ if apply.$returns.value.status.apply != _|_ {
|
||||
$params: continue: apply.$returns.value.status.apply.state == "Available"
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=specify the source of the terraform configuration
|
||||
|
||||
@@ -14,61 +14,65 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/config"
|
||||
"vela/kube"
|
||||
"vela/builtin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
config: op.#CreateConfig & {
|
||||
name: "\(context.name)-\(context.stepName)"
|
||||
namespace: context.namespace
|
||||
template: "terraform-\(parameter.type)"
|
||||
config: {
|
||||
name: parameter.name
|
||||
if parameter.type == "alibaba" {
|
||||
ALICLOUD_ACCESS_KEY: parameter.accessKey
|
||||
ALICLOUD_SECRET_KEY: parameter.secretKey
|
||||
ALICLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "aws" {
|
||||
AWS_ACCESS_KEY_ID: parameter.accessKey
|
||||
AWS_SECRET_ACCESS_KEY: parameter.secretKey
|
||||
AWS_DEFAULT_REGION: parameter.region
|
||||
AWS_SESSION_TOKEN: parameter.token
|
||||
}
|
||||
if parameter.type == "azure" {
|
||||
ARM_CLIENT_ID: parameter.clientID
|
||||
ARM_CLIENT_SECRET: parameter.clientSecret
|
||||
ARM_SUBSCRIPTION_ID: parameter.subscriptionID
|
||||
ARM_TENANT_ID: parameter.tenantID
|
||||
}
|
||||
if parameter.type == "baidu" {
|
||||
BAIDUCLOUD_ACCESS_KEY: parameter.accessKey
|
||||
BAIDUCLOUD_SECRET_KEY: parameter.secretKey
|
||||
BAIDUCLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "ec" {
|
||||
EC_API_KEY: parameter.apiKey
|
||||
}
|
||||
if parameter.type == "gcp" {
|
||||
GOOGLE_CREDENTIALS: parameter.credentials
|
||||
GOOGLE_REGION: parameter.region
|
||||
GOOGLE_PROJECT: parameter.project
|
||||
}
|
||||
if parameter.type == "tencent" {
|
||||
TENCENTCLOUD_SECRET_ID: parameter.secretID
|
||||
TENCENTCLOUD_SECRET_KEY: parameter.secretKey
|
||||
TENCENTCLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "ucloud" {
|
||||
UCLOUD_PRIVATE_KEY: parameter.privateKey
|
||||
UCLOUD_PUBLIC_KEY: parameter.publicKey
|
||||
UCLOUD_PROJECT_ID: parameter.projectID
|
||||
UCLOUD_REGION: parameter.region
|
||||
cfg: config.#CreateConfig & {
|
||||
$params: {
|
||||
name: "\(context.name)-\(context.stepName)"
|
||||
namespace: context.namespace
|
||||
template: "terraform-\(parameter.type)"
|
||||
config: {
|
||||
name: parameter.name
|
||||
if parameter.type == "alibaba" {
|
||||
ALICLOUD_ACCESS_KEY: parameter.accessKey
|
||||
ALICLOUD_SECRET_KEY: parameter.secretKey
|
||||
ALICLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "aws" {
|
||||
AWS_ACCESS_KEY_ID: parameter.accessKey
|
||||
AWS_SECRET_ACCESS_KEY: parameter.secretKey
|
||||
AWS_DEFAULT_REGION: parameter.region
|
||||
AWS_SESSION_TOKEN: parameter.token
|
||||
}
|
||||
if parameter.type == "azure" {
|
||||
ARM_CLIENT_ID: parameter.clientID
|
||||
ARM_CLIENT_SECRET: parameter.clientSecret
|
||||
ARM_SUBSCRIPTION_ID: parameter.subscriptionID
|
||||
ARM_TENANT_ID: parameter.tenantID
|
||||
}
|
||||
if parameter.type == "baidu" {
|
||||
BAIDUCLOUD_ACCESS_KEY: parameter.accessKey
|
||||
BAIDUCLOUD_SECRET_KEY: parameter.secretKey
|
||||
BAIDUCLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "ec" {
|
||||
EC_API_KEY: parameter.apiKey
|
||||
}
|
||||
if parameter.type == "gcp" {
|
||||
GOOGLE_CREDENTIALS: parameter.credentials
|
||||
GOOGLE_REGION: parameter.region
|
||||
GOOGLE_PROJECT: parameter.project
|
||||
}
|
||||
if parameter.type == "tencent" {
|
||||
TENCENTCLOUD_SECRET_ID: parameter.secretID
|
||||
TENCENTCLOUD_SECRET_KEY: parameter.secretKey
|
||||
TENCENTCLOUD_REGION: parameter.region
|
||||
}
|
||||
if parameter.type == "ucloud" {
|
||||
UCLOUD_PRIVATE_KEY: parameter.privateKey
|
||||
UCLOUD_PUBLIC_KEY: parameter.publicKey
|
||||
UCLOUD_PROJECT_ID: parameter.projectID
|
||||
UCLOUD_REGION: parameter.region
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "terraform.core.oam.dev/v1beta1"
|
||||
kind: "Provider"
|
||||
metadata: {
|
||||
@@ -77,12 +81,9 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
check: op.#ConditionalWait & {
|
||||
if read.value.status != _|_ {
|
||||
continue: read.value.status.state == "ready"
|
||||
}
|
||||
if read.value.status == _|_ {
|
||||
continue: false
|
||||
check: builtin.#ConditionalWait & {
|
||||
if read.$returns.value.status != _|_ {
|
||||
$params: continue: read.$returns.value.status.state == "ready"
|
||||
}
|
||||
}
|
||||
providerBasic: {
|
||||
|
||||
@@ -14,7 +14,9 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/builtin"
|
||||
"vela/kube"
|
||||
"vela/util"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
@@ -28,8 +30,8 @@ spec:
|
||||
value: parameter.context
|
||||
}
|
||||
}
|
||||
kaniko: op.#Apply & {
|
||||
value: {
|
||||
kaniko: kube.#Apply & {
|
||||
$params: value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Pod"
|
||||
metadata: {
|
||||
@@ -95,14 +97,14 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
log: op.#Log & {
|
||||
source: resources: [{
|
||||
log: util.#Log & {
|
||||
$params: source: resources: [{
|
||||
name: "\(context.name)-\(context.stepSessionID)-kaniko"
|
||||
namespace: context.namespace
|
||||
}]
|
||||
}
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Pod"
|
||||
metadata: {
|
||||
@@ -111,8 +113,10 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: read.value.status != _|_ && read.value.status.phase == "Succeeded"
|
||||
wait: builtin.#ConditionalWait & {
|
||||
if read.$returns.value.status != _|_ {
|
||||
$params: continue: read.$returns.value.status.phase == "Succeeded"
|
||||
}
|
||||
}
|
||||
#secret: {
|
||||
name: string
|
||||
|
||||
@@ -15,32 +15,35 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/metrics"
|
||||
"vela/builtin"
|
||||
)
|
||||
|
||||
check: op.#PromCheck & {
|
||||
query: parameter.query
|
||||
metricEndpoint: parameter.metricEndpoint
|
||||
condition: parameter.condition
|
||||
stepID: context.stepSessionID
|
||||
duration: parameter.duration
|
||||
failDuration: parameter.failDuration
|
||||
check: metrics.#PromCheck & {
|
||||
$params: {
|
||||
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
|
||||
fail: {
|
||||
if check.$returns.failed != _|_ {
|
||||
if check.$returns.failed == true {
|
||||
breakWorkflow: builtin.#Fail & {
|
||||
$params: message: check.$returns.message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: check.result
|
||||
if check.message != _|_ {
|
||||
message: check.message
|
||||
wait: builtin.#ConditionalWait & {
|
||||
$params: continue: check.$returns.result
|
||||
if check.$returns.message != _|_ {
|
||||
$params: message: check.$returns.message
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
)
|
||||
|
||||
parameter: {
|
||||
@@ -21,42 +21,46 @@ spec:
|
||||
namespace: *context.namespace | string
|
||||
}
|
||||
|
||||
cleanJobs: op.#Delete & {
|
||||
value: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
metadata: {
|
||||
name: context.name
|
||||
cleanJobs: kube.#Delete & {
|
||||
$params: {
|
||||
value: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
filter: {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
filter: {
|
||||
namespace: parameter.namespace
|
||||
if parameter.labelselector != _|_ {
|
||||
matchingLabels: parameter.labelselector
|
||||
}
|
||||
if parameter.labelselector == _|_ {
|
||||
matchingLabels: "workflow.oam.dev/name": context.name
|
||||
if parameter.labelselector != _|_ {
|
||||
matchingLabels: parameter.labelselector
|
||||
}
|
||||
if parameter.labelselector == _|_ {
|
||||
matchingLabels: "workflow.oam.dev/name": context.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanPods: op.#Delete & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "pod"
|
||||
metadata: {
|
||||
name: context.name
|
||||
cleanPods: kube.#Delete & {
|
||||
$params: {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "pod"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
filter: {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
filter: {
|
||||
namespace: parameter.namespace
|
||||
if parameter.labelselector != _|_ {
|
||||
matchingLabels: parameter.labelselector
|
||||
}
|
||||
if parameter.labelselector == _|_ {
|
||||
matchingLabels: "workflow.oam.dev/name": context.name
|
||||
if parameter.labelselector != _|_ {
|
||||
matchingLabels: parameter.labelselector
|
||||
}
|
||||
if parameter.labelselector == _|_ {
|
||||
matchingLabels: "workflow.oam.dev/name": context.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,15 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/ql"
|
||||
"vela/builtin"
|
||||
"vela/query"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
collect: ql.#CollectServiceEndpoints & {
|
||||
app: {
|
||||
name: *context.name | string
|
||||
namespace: *context.namespace | string
|
||||
if parameter.name != _|_ {
|
||||
name: parameter.name
|
||||
}
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
collect: query.#CollectServiceEndpoints & {
|
||||
$params: app: {
|
||||
name: parameter.name
|
||||
namespace: parameter.namespace
|
||||
filter: {
|
||||
if parameter.components != _|_ {
|
||||
components: parameter.components
|
||||
@@ -39,10 +33,10 @@ spec:
|
||||
outputs: {
|
||||
eps_port_name_filtered: *[] | [...]
|
||||
if parameter.portName == _|_ {
|
||||
eps_port_name_filtered: collect.list
|
||||
eps_port_name_filtered: collect.$returns.list
|
||||
}
|
||||
if parameter.portName != _|_ {
|
||||
eps_port_name_filtered: [for ep in collect.list if parameter.portName == ep.endpoint.portName {ep}]
|
||||
eps_port_name_filtered: [for ep in collect.$returns.list if parameter.portName == ep.endpoint.portName {ep}]
|
||||
}
|
||||
|
||||
eps_port_filtered: *[] | [...]
|
||||
@@ -71,8 +65,8 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: len(outputs.endpoints) > 0
|
||||
wait: builtin.#ConditionalWait & {
|
||||
$params: continue: len(outputs.endpoints) > 0
|
||||
}
|
||||
|
||||
value: {
|
||||
@@ -85,9 +79,9 @@ spec:
|
||||
|
||||
parameter: {
|
||||
// +usage=Specify the name of the application
|
||||
name?: string
|
||||
name: *context.name | string
|
||||
// +usage=Specify the namespace of the application
|
||||
namespace?: string
|
||||
namespace: *context.namespace | string
|
||||
// +usage=Filter the component of the endpoints
|
||||
components?: [...string]
|
||||
// +usage=Filter the port of the endpoints
|
||||
|
||||
@@ -13,28 +13,18 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/config"
|
||||
)
|
||||
|
||||
deploy: op.#CreateConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
if parameter.template != _|_ {
|
||||
template: parameter.template
|
||||
}
|
||||
config: parameter.config
|
||||
deploy: config.#CreateConfig & {
|
||||
$params: parameter
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
namespace: *context.namespace | string
|
||||
|
||||
//+usage=Specify the template of the config.
|
||||
template?: string
|
||||
|
||||
@@ -13,23 +13,17 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/config"
|
||||
)
|
||||
|
||||
deploy: op.#DeleteConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
deploy: config.#DeleteConfig & {
|
||||
$params: parameter
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
namespace: *context.namespace | string
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,13 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
"vela/builtin"
|
||||
"encoding/yaml"
|
||||
)
|
||||
|
||||
dependsOn: op.#Read & {
|
||||
value: {
|
||||
dependsOn: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
metadata: {
|
||||
@@ -27,10 +28,10 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
load: op.#Steps & {
|
||||
if dependsOn.err != _|_ {
|
||||
configMap: op.#Read & {
|
||||
value: {
|
||||
load: {
|
||||
if dependsOn.$returns.err != _|_ {
|
||||
configMap: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
@@ -39,18 +40,18 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
template: configMap.value.data["application"]
|
||||
apply: op.#Apply & {
|
||||
value: yaml.Unmarshal(template)
|
||||
template: configMap.$returns.value.data["application"]
|
||||
apply: kube.#Apply & {
|
||||
$params: value: yaml.Unmarshal(template)
|
||||
}
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: apply.value.status.status == "running"
|
||||
wait: builtin.#ConditionalWait & {
|
||||
$params: continue: apply.$returns.value.status.status == "running"
|
||||
}
|
||||
}
|
||||
|
||||
if dependsOn.err == _|_ {
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: dependsOn.value.status.status == "running"
|
||||
if dependsOn.$returns.err == _|_ {
|
||||
wait: builtin.#ConditionalWait & {
|
||||
$params: continue: dependsOn.$returns.value.status.status == "running"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,19 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/multicluster"
|
||||
"vela/builtin"
|
||||
)
|
||||
|
||||
if parameter.auto == false {
|
||||
suspend: op.#Suspend & {message: "Waiting approval to the deploy step \"\(context.stepName)\""}
|
||||
suspend: builtin.#Suspend & {$params: message: "Waiting approval to the deploy step \"\(context.stepName)\""}
|
||||
}
|
||||
deploy: op.#Deploy & {
|
||||
policies: parameter.policies
|
||||
parallelism: parameter.parallelism
|
||||
ignoreTerraformComponent: parameter.ignoreTerraformComponent
|
||||
deploy: multicluster.#Deploy & {
|
||||
$params: {
|
||||
policies: parameter.policies
|
||||
parallelism: parameter.parallelism
|
||||
ignoreTerraformComponent: parameter.ignoreTerraformComponent
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
//+usage=If set to false, the workflow will suspend automatically before this step, default to be true.
|
||||
|
||||
@@ -16,6 +16,7 @@ spec:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
)
|
||||
|
||||
object: {
|
||||
@@ -46,11 +47,13 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
apply: op.#Steps & {
|
||||
apply: {
|
||||
for p in getPlacements.placements {
|
||||
(p.cluster): op.#Apply & {
|
||||
value: object
|
||||
cluster: p.cluster
|
||||
(p.cluster): kube.#Apply & {
|
||||
$params: {
|
||||
value: object
|
||||
cluster: p.cluster
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ spec:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
)
|
||||
|
||||
meta: {
|
||||
@@ -57,12 +58,14 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
apply: op.#Steps & {
|
||||
apply: {
|
||||
for p in getPlacements.placements {
|
||||
for o in objects {
|
||||
"\(p.cluster)-\(o.kind)": op.#Apply & {
|
||||
value: o
|
||||
cluster: p.cluster
|
||||
"\(p.cluster)-\(o.kind)": kube.#Apply & {
|
||||
$params: {
|
||||
value: o
|
||||
cluster: p.cluster
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,25 +13,27 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
)
|
||||
|
||||
apply: op.#Apply & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: parameter.configName
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
apply: kube.#Apply & {
|
||||
$params: {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: parameter.configName
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
data: parameter.data
|
||||
}
|
||||
data: parameter.data
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the name of the config map
|
||||
|
||||
@@ -13,12 +13,12 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
secret: op.#Steps & {
|
||||
secret: {
|
||||
data: *parameter.data | {}
|
||||
if parameter.kind == "docker-registry" && parameter.dockerRegistry != _|_ {
|
||||
registryData: auths: "\(parameter.dockerRegistry.server)": {
|
||||
@@ -28,28 +28,30 @@ spec:
|
||||
}
|
||||
data: ".dockerconfigjson": json.Marshal(registryData)
|
||||
}
|
||||
apply: op.#Apply & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
if parameter.type == _|_ && parameter.kind == "docker-registry" {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
if parameter.type != _|_ {
|
||||
type: parameter.type
|
||||
}
|
||||
metadata: {
|
||||
name: parameter.secretName
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
apply: kube.#Apply & {
|
||||
$params: {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
if parameter.type == _|_ && parameter.kind == "docker-registry" {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
if parameter.type != _|_ {
|
||||
type: parameter.type
|
||||
}
|
||||
metadata: {
|
||||
name: parameter.secretName
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
}
|
||||
stringData: data
|
||||
}
|
||||
stringData: data
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
|
||||
@@ -13,12 +13,13 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
"vela/util"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
output: op.#Read & {
|
||||
value: {
|
||||
output: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
@@ -29,16 +30,16 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
dbHost: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_HOST"])}
|
||||
dbPort: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PORT"])}
|
||||
dbName: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_NAME"])}
|
||||
username: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_USER"])}
|
||||
password: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PASSWORD"])}
|
||||
dbHost: util.#ConvertString & {$params: bt: base64.Decode(null, output.$returns.value.data["DB_HOST"])}
|
||||
dbPort: util.#ConvertString & {$params: bt: base64.Decode(null, output.$returns.value.data["DB_PORT"])}
|
||||
dbName: util.#ConvertString & {$params: bt: base64.Decode(null, output.$returns.value.data["DB_NAME"])}
|
||||
username: util.#ConvertString & {$params: bt: base64.Decode(null, output.$returns.value.data["DB_USER"])}
|
||||
password: util.#ConvertString & {$params: bt: base64.Decode(null, output.$returns.value.data["DB_PASSWORD"])}
|
||||
|
||||
env: [
|
||||
{name: "url", value: "jdbc://" + dbHost.str + ":" + dbPort.str + "/" + dbName.str + "?characterEncoding=utf8&useSSL=false"},
|
||||
{name: "username", value: username.str},
|
||||
{name: "password", value: password.str},
|
||||
{name: "url", value: "jdbc://" + dbHost.$returns.str + ":" + dbPort.$returns.str + "/" + dbName.$returns.str + "?characterEncoding=utf8&useSSL=false"},
|
||||
{name: "username", value: username.$returns.str},
|
||||
{name: "password", value: password.$returns.str},
|
||||
]
|
||||
|
||||
parameter: {
|
||||
|
||||
@@ -13,22 +13,16 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/config"
|
||||
)
|
||||
|
||||
output: op.#ListConfig & {
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
template: parameter.template
|
||||
output: config.#ListConfig & {
|
||||
$params: parameter
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the template of the config.
|
||||
template: string
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
namespace: *context.namespace | string
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/http"
|
||||
"vela/email"
|
||||
"vela/kube"
|
||||
"vela/util"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
parameter: {
|
||||
@@ -209,17 +213,23 @@ spec:
|
||||
}
|
||||
|
||||
// send webhook notification
|
||||
ding: op.#Steps & {
|
||||
ding: {
|
||||
if parameter.dingding != _|_ {
|
||||
if parameter.dingding.url.value != _|_ {
|
||||
ding1: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
dingUrl: parameter.dingding.url.value
|
||||
ding1: http.#Do & {
|
||||
$params: {
|
||||
method: "POST"
|
||||
url: parameter.dingding.url.value
|
||||
request: {
|
||||
body: json.Marshal(parameter.dingding.message)
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if parameter.dingding.url.secretRef != _|_ && parameter.dingding.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
@@ -229,26 +239,38 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
stringValue: op.#ConvertString & {bt: base64.Decode(null, read.value.data[parameter.dingding.url.secretRef.key])}
|
||||
ding2: op.#DingTalk & {
|
||||
message: parameter.dingding.message
|
||||
dingUrl: stringValue.str
|
||||
stringValue: util.#ConvertString & {$params: bt: base64.Decode(null, read.$returns.value.data[parameter.dingding.url.secretRef.key])}
|
||||
ding2: http.#Do & {
|
||||
$params: {
|
||||
method: "POST"
|
||||
url: stringValue.$returns.str
|
||||
request: {
|
||||
body: json.Marshal(parameter.dingding.message)
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lark: op.#Steps & {
|
||||
lark: {
|
||||
if parameter.lark != _|_ {
|
||||
if parameter.lark.url.value != _|_ {
|
||||
lark1: op.#Lark & {
|
||||
message: parameter.lark.message
|
||||
larkUrl: parameter.lark.url.value
|
||||
lark1: http.#Do & {
|
||||
$params: {
|
||||
method: "POST"
|
||||
url: parameter.lark.message
|
||||
request: {
|
||||
body: json.Marshal(parameter.lark.message)
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if parameter.lark.url.secretRef != _|_ && parameter.lark.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
@@ -258,26 +280,39 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
stringValue: op.#ConvertString & {bt: base64.Decode(null, read.value.data[parameter.lark.url.secretRef.key])}
|
||||
lark2: op.#Lark & {
|
||||
message: parameter.lark.message
|
||||
larkUrl: stringValue.str
|
||||
stringValue: util.#ConvertString & {$params: bt: base64.Decode(null, read.$returns.value.data[parameter.lark.url.secretRef.key])}
|
||||
lark2: http.#Do & {
|
||||
$params: {
|
||||
method: "POST"
|
||||
url: stringValue.$returns.str
|
||||
request: {
|
||||
body: json.Marshal(parameter.lark.message)
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slack: op.#Steps & {
|
||||
slack: {
|
||||
if parameter.slack != _|_ {
|
||||
if parameter.slack.url.value != _|_ {
|
||||
slack1: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
slackUrl: parameter.slack.url.value
|
||||
slack1: http.#Do & {
|
||||
$params: {
|
||||
method: "POST"
|
||||
url: parameter.slack.url.value
|
||||
request: {
|
||||
body: json.Marshal(parameter.slack.message)
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if parameter.slack.url.secretRef != _|_ && parameter.slack.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
@@ -287,36 +322,44 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
stringValue: op.#ConvertString & {bt: base64.Decode(null, read.value.data[parameter.slack.url.secretRef.key])}
|
||||
slack2: op.#Slack & {
|
||||
message: parameter.slack.message
|
||||
slackUrl: stringValue.str
|
||||
stringValue: util.#ConvertString & {$params: bt: base64.Decode(null, read.$returns.value.data[parameter.slack.url.secretRef.key])}
|
||||
slack2: http.#Do & {
|
||||
$params: {
|
||||
method: "POST"
|
||||
url: stringValue.$returns.str
|
||||
request: {
|
||||
body: json.Marshal(parameter.slack.message)
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
email: op.#Steps & {
|
||||
email0: {
|
||||
if parameter.email != _|_ {
|
||||
if parameter.email.from.password.value != _|_ {
|
||||
email1: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
email1: email.#SendEmail & {
|
||||
$params: {
|
||||
from: {
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
}
|
||||
password: parameter.email.from.password.value
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
password: parameter.email.from.password.value
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
|
||||
if parameter.email.from.password.secretRef != _|_ && parameter.email.from.password.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
kind: "Secret"
|
||||
apiVersion: "v1"
|
||||
metadata: {
|
||||
@@ -326,19 +369,21 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
stringValue: op.#ConvertString & {bt: base64.Decode(null, read.value.data[parameter.email.from.password.secretRef.key])}
|
||||
email2: op.#SendEmail & {
|
||||
from: {
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
stringValue: util.#ConvertString & {$params: bt: base64.Decode(null, read.$returns.value.data[parameter.email.from.password.secretRef.key])}
|
||||
email2: email.#SendEmail & {
|
||||
$params: {
|
||||
from: {
|
||||
address: parameter.email.from.address
|
||||
if parameter.email.from.alias != _|_ {
|
||||
alias: parameter.email.from.alias
|
||||
}
|
||||
password: stringValue.str
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
}
|
||||
password: stringValue.str
|
||||
host: parameter.email.from.host
|
||||
port: parameter.email.from.port
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
to: parameter.email.to
|
||||
content: parameter.email.content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/builtin"
|
||||
)
|
||||
|
||||
parameter: message: string
|
||||
|
||||
msg: op.#Message & {
|
||||
message: parameter.message
|
||||
msg: builtin.#Message & {
|
||||
$params: parameter
|
||||
}
|
||||
|
||||
|
||||
@@ -13,23 +13,17 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/config"
|
||||
)
|
||||
|
||||
output: op.#ReadConfig & {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
if parameter.namespace == _|_ {
|
||||
namespace: context.namespace
|
||||
}
|
||||
output: config.#ReadConfig & {
|
||||
$params: parameter
|
||||
}
|
||||
parameter: {
|
||||
//+usage=Specify the name of the config.
|
||||
name: string
|
||||
|
||||
//+usage=Specify the namespace of the config.
|
||||
namespace?: string
|
||||
namespace: *context.namespace | string
|
||||
}
|
||||
|
||||
|
||||
@@ -13,50 +13,31 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
)
|
||||
|
||||
output: {
|
||||
if parameter.apiVersion == _|_ && parameter.kind == _|_ {
|
||||
op.#Read & {
|
||||
value: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
output: kube.#Read & {
|
||||
$params: {
|
||||
cluster: parameter.cluster
|
||||
value: {
|
||||
apiVersion: parameter.apiVersion
|
||||
kind: parameter.kind
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
}
|
||||
if parameter.apiVersion != _|_ || parameter.kind != _|_ {
|
||||
op.#Read & {
|
||||
value: {
|
||||
apiVersion: parameter.apiVersion
|
||||
kind: parameter.kind
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
if parameter.namespace != _|_ {
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
cluster: parameter.cluster
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the apiVersion of the object, defaults to 'core.oam.dev/v1beta1'
|
||||
apiVersion?: string
|
||||
apiVersion: *"core.oam.dev/v1beta1" | string
|
||||
// +usage=Specify the kind of the object, defaults to Application
|
||||
kind?: string
|
||||
kind: *"Application" | string
|
||||
// +usage=Specify the name of the object
|
||||
name: string
|
||||
// +usage=The namespace of the resource you want to read
|
||||
namespace?: *"default" | string
|
||||
namespace: *"default" | string
|
||||
// +usage=The cluster you want to apply the resource to, default is the current control plane cluster
|
||||
cluster: *"" | string
|
||||
}
|
||||
|
||||
@@ -14,30 +14,33 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/http"
|
||||
"vela/builtin"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
http: op.#HTTPDo & {
|
||||
method: parameter.method
|
||||
url: parameter.url
|
||||
request: {
|
||||
if parameter.body != _|_ {
|
||||
body: json.Marshal(parameter.body)
|
||||
}
|
||||
if parameter.header != _|_ {
|
||||
header: parameter.header
|
||||
req: http.#HTTPDo & {
|
||||
$params: {
|
||||
method: parameter.method
|
||||
url: parameter.url
|
||||
request: {
|
||||
if parameter.body != _|_ {
|
||||
body: json.Marshal(parameter.body)
|
||||
}
|
||||
if parameter.header != _|_ {
|
||||
header: parameter.header
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fail: op.#Steps & {
|
||||
if http.response.statusCode > 400 {
|
||||
requestFail: op.#Fail & {
|
||||
message: "request of \(parameter.url) is fail: \(http.response.statusCode)"
|
||||
fail: {
|
||||
if http.$returns.response.statusCode > 400 {
|
||||
requestFail: builtin.#Fail & {
|
||||
$params: message: "request of \(parameter.url) is fail: \(http.response.statusCode)"
|
||||
}
|
||||
}
|
||||
}
|
||||
response: json.Unmarshal(http.response.body)
|
||||
response: json.Unmarshal(http.$returns.response.body)
|
||||
parameter: {
|
||||
url: string
|
||||
method: *"GET" | "POST" | "PUT" | "DELETE"
|
||||
|
||||
@@ -13,16 +13,11 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/builtin"
|
||||
)
|
||||
|
||||
suspend: op.#Suspend & {
|
||||
if parameter.duration != _|_ {
|
||||
duration: parameter.duration
|
||||
}
|
||||
if parameter.message != _|_ {
|
||||
message: parameter.message
|
||||
}
|
||||
suspend: builtin.#Suspend & {
|
||||
$params: parameter
|
||||
}
|
||||
|
||||
parameter: {
|
||||
|
||||
@@ -13,7 +13,9 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/kube"
|
||||
"vela/builtin"
|
||||
"vela/util"
|
||||
)
|
||||
|
||||
mountsArray: [
|
||||
@@ -68,8 +70,8 @@ spec:
|
||||
},
|
||||
]
|
||||
|
||||
job: op.#Apply & {
|
||||
value: {
|
||||
job: kube.#Apply & {
|
||||
$params: value: {
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
metadata: {
|
||||
@@ -103,22 +105,24 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
log: op.#Log & {
|
||||
source: resources: [{labelSelector: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"}]
|
||||
log: util.#Log & {
|
||||
$params: source: resources: [{labelSelector: "workflow.oam.dev/step-name": "\(context.name)-\(context.stepName)"}]
|
||||
}
|
||||
|
||||
fail: op.#Steps & {
|
||||
if job.value.status.failed != _|_ {
|
||||
if job.value.status.failed > 2 {
|
||||
breakWorkflow: op.#Fail & {
|
||||
message: "failed to execute vela command"
|
||||
fail: {
|
||||
if job.$returns.value.status != _|_ if job.$returns.value.status.failed != _|_ {
|
||||
if job.$returns.value.status.failed > 2 {
|
||||
breakWorkflow: builtin.#Fail & {
|
||||
$params: message: "failed to execute vela command"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: job.value.status.succeeded != _|_ && job.value.status.succeeded > 0
|
||||
wait: builtin.#ConditionalWait & {
|
||||
if job.$returns.value.status != _|_ if job.$returns.value.status.succeeded != _|_ {
|
||||
$params: continue: job.$returns.value.status.succeeded > 0
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
|
||||
@@ -13,15 +13,17 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/http"
|
||||
"vela/kube"
|
||||
"vela/util"
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
data: op.#Steps & {
|
||||
data: {
|
||||
if parameter.data == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "Application"
|
||||
metadata: {
|
||||
@@ -30,25 +32,27 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
value: json.Marshal(read.value)
|
||||
value: json.Marshal(read.$returns.value)
|
||||
}
|
||||
if parameter.data != _|_ {
|
||||
value: json.Marshal(parameter.data)
|
||||
}
|
||||
}
|
||||
webhook: op.#Steps & {
|
||||
webhook: {
|
||||
if parameter.url.value != _|_ {
|
||||
http: op.#HTTPPost & {
|
||||
url: parameter.url.value
|
||||
request: {
|
||||
body: data.value
|
||||
header: "Content-Type": "application/json"
|
||||
req: http.#HTTPPost & {
|
||||
$params: {
|
||||
url: parameter.url.value
|
||||
request: {
|
||||
body: data.value
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if parameter.url.secretRef != _|_ && parameter.url.value == _|_ {
|
||||
read: op.#Read & {
|
||||
value: {
|
||||
read: kube.#Read & {
|
||||
$params: value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
@@ -58,12 +62,14 @@ spec:
|
||||
}
|
||||
}
|
||||
|
||||
stringValue: op.#ConvertString & {bt: base64.Decode(null, read.value.data[parameter.url.secretRef.key])}
|
||||
http: op.#HTTPPost & {
|
||||
url: stringValue.str
|
||||
request: {
|
||||
body: data.value
|
||||
header: "Content-Type": "application/json"
|
||||
stringValue: util.#ConvertString & {$params: bt: base64.Decode(null, read.$returns.value.data[parameter.url.secretRef.key])}
|
||||
req: http.#HTTPPost & {
|
||||
$params: {
|
||||
url: stringValue.$returns.str
|
||||
request: {
|
||||
body: data.value
|
||||
header: "Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,8 @@ spec:
|
||||
- "--max-workflow-wait-backoff-time={{ .Values.workflow.backoff.maxTime.waitState }}"
|
||||
- "--max-workflow-failed-backoff-time={{ .Values.workflow.backoff.maxTime.failedState }}"
|
||||
- "--max-workflow-step-error-retry-times={{ .Values.workflow.step.errorRetryTimes }}"
|
||||
- "--enable-external-package-for-default-compiler={{ .Values.workflow.enableExternalPackageForDefaultCompiler }}"
|
||||
- "--enable-external-package-watch-for-default-compiler={{ .Values.workflow.enableExternalPackageWatchForDefaultCompiler }}"
|
||||
- "--feature-gates=EnableSuspendOnFailure={{- .Values.workflow.enableSuspendOnFailure | toString -}}"
|
||||
- "--feature-gates=AuthenticateApplication={{- .Values.authentication.enabled | toString -}}"
|
||||
- "--feature-gates=GzipResourceTracker={{- .Values.featureGates.gzipResourceTracker | toString -}}"
|
||||
|
||||
@@ -24,11 +24,15 @@ controllerArgs:
|
||||
## @section KubeVela workflow parameters
|
||||
|
||||
## @param workflow.enableSuspendOnFailure Enable suspend on workflow failure
|
||||
## @param workflow.enableExternalPackageForDefaultCompiler Enable external package for default cuex compiler
|
||||
## @param workflow.enableExternalPackageWatchForDefaultCompiler Enable external package watch for default cuex compiler
|
||||
## @param workflow.backoff.maxTime.waitState The max backoff time of workflow in a wait condition
|
||||
## @param workflow.backoff.maxTime.failedState The max backoff time of workflow in a failed condition
|
||||
## @param workflow.step.errorRetryTimes The max retry times of a failed workflow step
|
||||
workflow:
|
||||
enableSuspendOnFailure: false
|
||||
enableExternalPackageForDefaultCompiler: true
|
||||
enableExternalPackageWatchForDefaultCompiler: false
|
||||
backoff:
|
||||
maxTime:
|
||||
waitState: 60
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
oamcontroller "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
||||
)
|
||||
|
||||
// CoreOptions contains everything necessary to create and run vela-core
|
||||
@@ -131,6 +132,8 @@ func (s *CoreOptions) Flags() cliflag.NamedFlagSets {
|
||||
gfs.BoolVar(&s.EnableClusterGateway, "enable-cluster-gateway", s.EnableClusterGateway, "Enable cluster-gateway to use multicluster, disabled by default.")
|
||||
gfs.BoolVar(&s.EnableClusterMetrics, "enable-cluster-metrics", s.EnableClusterMetrics, "Enable cluster-metrics-management to collect metrics from clusters with cluster-gateway, disabled by default. When this param is enabled, enable-cluster-gateway should be enabled")
|
||||
gfs.DurationVar(&s.ClusterMetricsInterval, "cluster-metrics-interval", s.ClusterMetricsInterval, "The interval that ClusterMetricsMgr will collect metrics from clusters, default value is 15 seconds.")
|
||||
gfs.BoolVar(&providers.EnableExternalPackageForDefaultCompiler, "enable-external-package-for-default-compiler", providers.EnableExternalPackageForDefaultCompiler, "Enable external package for default compiler")
|
||||
gfs.BoolVar(&providers.EnableExternalPackageWatchForDefaultCompiler, "enable-external-package-watch-for-default-compiler", providers.EnableExternalPackageWatchForDefaultCompiler, "Enable external package watch for default compiler")
|
||||
|
||||
s.ControllerArgs.AddFlags(fss.FlagSet("controllerArgs"), s.ControllerArgs)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
type: webhook
|
||||
outputs:
|
||||
- name: mysecret
|
||||
valueFrom: webhook.http.response.body
|
||||
valueFrom: webhook.req.$returns.response.body
|
||||
properties:
|
||||
url:
|
||||
value: <url>
|
||||
|
||||
@@ -16,9 +16,9 @@ spec:
|
||||
type: read-object
|
||||
outputs:
|
||||
- name: cpu
|
||||
valueFrom: output.value.data["cpu"]
|
||||
valueFrom: output.$returns.value.data["cpu"]
|
||||
- name: memory
|
||||
valueFrom: output.value.data["memory"]
|
||||
valueFrom: output.$returns.value.data["memory"]
|
||||
properties:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -37,7 +37,7 @@ require (
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/imdario/mergo v0.3.16
|
||||
github.com/kubevela/pkg v1.9.2
|
||||
github.com/kubevela/workflow v0.6.1-0.20240727094441-7d94489306a8
|
||||
github.com/kubevela/workflow v0.6.1-0.20240924152948-55f1433fd7f8
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
|
||||
@@ -632,8 +632,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kubevela/pkg v1.9.2 h1:K6pGoJikf6l8vlfehewmb36hyToX0KpUaQP4NVET/S8=
|
||||
github.com/kubevela/pkg v1.9.2/go.mod h1:u/MGuFXVSECxvIWdTKS4AQs1H+USfAMQgi30BUrOb04=
|
||||
github.com/kubevela/workflow v0.6.1-0.20240727094441-7d94489306a8 h1:67JnKJMDcf/nbibuiR+lEFiIEUKwlPm6ufQxbyjQLmQ=
|
||||
github.com/kubevela/workflow v0.6.1-0.20240727094441-7d94489306a8/go.mod h1:/tWZOtO+bp/EUQCJZjL8t3O5YDx5QXnKVllQkDTKKuw=
|
||||
github.com/kubevela/workflow v0.6.1-0.20240924152948-55f1433fd7f8 h1:dvqNMluYo4P9ngpUuoAym9WSr4E8TFZLQ8qc5D/XwU4=
|
||||
github.com/kubevela/workflow v0.6.1-0.20240924152948-55f1433fd7f8/go.mod h1:/tWZOtO+bp/EUQCJZjL8t3O5YDx5QXnKVllQkDTKKuw=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
|
||||
@@ -4545,34 +4545,37 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
"list"
|
||||
"list"
|
||||
"vela/kube"
|
||||
"vela/oam"
|
||||
"vela/util"
|
||||
)
|
||||
|
||||
components: op.#LoadInOrder & {}
|
||||
targetComponent: components.value[0]
|
||||
resources: op.#RenderComponent & {
|
||||
value: targetComponent
|
||||
components: oam.#LoadComponetsInOrder & {}
|
||||
targetComponent: components.$returns.value[0]
|
||||
resources: oam.#RenderComponent & {
|
||||
$params: value: targetComponent
|
||||
}
|
||||
workload: resources.output
|
||||
arr: list.Range(0, parameter.parallelism, 1)
|
||||
patchWorkloads: op.#Steps & {
|
||||
for idx in arr {
|
||||
"\(idx)": op.#PatchK8sObject & {
|
||||
value: workload
|
||||
patch: {
|
||||
// +patchStrategy=retainKeys
|
||||
metadata: name: "\(targetComponent.name)-\(idx)"
|
||||
}
|
||||
}
|
||||
}
|
||||
workload: resources.$returns.output
|
||||
arr: list.Range(0, parameter.parallelism, 1)
|
||||
patchWorkloads: {
|
||||
for idx in arr {
|
||||
"\(idx)": util.#PatchK8sObject & {
|
||||
$params: {
|
||||
value: workload
|
||||
patch: {
|
||||
// +patchStrategy=retainKeys
|
||||
metadata: name: "\(targetComponent.name)-\(idx)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
workloads: [ for patchResult in patchWorkloads {patchResult.result}]
|
||||
apply: op.#ApplyInParallel & {
|
||||
value: workloads
|
||||
workloads: [for patchResult in patchWorkloads {patchResult.$returns.result}]
|
||||
apply: kube.#ApplyInParallel & {
|
||||
$params: value: workloads
|
||||
}
|
||||
parameter: parallelism: int
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/template"
|
||||
)
|
||||
|
||||
@@ -109,7 +109,7 @@ func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context,
|
||||
instance := generateWorkflowInstance(af, app)
|
||||
executor.InitializeWorkflowInstance(instance)
|
||||
runners, err := generator.GenerateRunners(ctx, instance, wfTypes.StepGeneratorOptions{
|
||||
Compiler: providers.Compiler.Get(),
|
||||
Compiler: providers.DefaultCompiler.Get(),
|
||||
ProcessCtx: pCtx,
|
||||
TemplateLoader: template.NewWorkflowStepTemplateRevisionLoader(appRev, h.Client.RESTMapper()),
|
||||
StepConvertor: map[string]func(step workflowv1alpha1.WorkflowStep) (workflowv1alpha1.WorkflowStep, error){
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
cuexv1alpha1 "github.com/kubevela/pkg/apis/cue/v1alpha1"
|
||||
"github.com/kubevela/pkg/util/singleton"
|
||||
terraformv1beta2 "github.com/oam-dev/terraform-controller/api/v1beta2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
@@ -100,15 +102,20 @@ var _ = BeforeSuite(func() {
|
||||
err = scheme.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
terraformv1beta2.AddToScheme(testScheme)
|
||||
|
||||
crdv1.AddToScheme(testScheme)
|
||||
err = terraformv1beta2.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = crdv1.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = cuexv1alpha1.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
singleton.KubeClient.Set(k8sClient)
|
||||
fakeDynamicClient := fake.NewSimpleDynamicClient(testScheme)
|
||||
singleton.DynamicClient.Set(fakeDynamicClient)
|
||||
appParser = appfile.NewApplicationParser(k8sClient)
|
||||
|
||||
reconciler = &Reconciler{
|
||||
|
||||
@@ -808,30 +808,31 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import ("vela/op")
|
||||
|
||||
parameter: {
|
||||
namespace: string
|
||||
}
|
||||
|
||||
import (
|
||||
"vela/kube"
|
||||
"vela/builtin"
|
||||
)
|
||||
|
||||
parameter: namespace: string
|
||||
|
||||
// apply workload to kubernetes cluster
|
||||
apply: op.#Apply & {
|
||||
value: {
|
||||
apiVersion: "example.com/v1"
|
||||
kind: "Foo"
|
||||
metadata: {
|
||||
name: "test-foo"
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
apply: kube.#Apply & {
|
||||
$params: value: {
|
||||
apiVersion: "example.com/v1"
|
||||
kind: "Foo"
|
||||
metadata: {
|
||||
name: "test-foo"
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
// wait until workload.status equal "Running"
|
||||
w: *false | bool
|
||||
if apply.value.spec != _|_ if apply.value.spec.key != "" {
|
||||
w: true
|
||||
if apply.$returns.value.spec != _|_ if apply.$returns.value.spec.key != "" {
|
||||
w: true
|
||||
}
|
||||
wait: op.#ConditionalWait & {
|
||||
continue: w
|
||||
wait: builtin.#ConditionalWait & {
|
||||
$params: continue: w
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
@@ -430,7 +430,7 @@ func (def *Definition) FromCUEString(cueString string, _ *rest.Config) error {
|
||||
return errors.Wrapf(err, "failed to encode template decls to string")
|
||||
}
|
||||
|
||||
inst, err := providers.Compiler.Get().CompileStringWithOptions(context.Background(), metadataString, cuex.DisableResolveProviderFunctions{})
|
||||
inst, err := providers.DefaultCompiler.Get().CompileStringWithOptions(context.Background(), metadataString, cuex.DisableResolveProviderFunctions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -438,7 +438,7 @@ func (def *Definition) FromCUEString(cueString string, _ *rest.Config) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := providers.Compiler.Get().CompileStringWithOptions(context.Background(), templateString+"\n"+velacue.BaseTemplate, cuex.DisableResolveProviderFunctions{}); err != nil {
|
||||
if _, err := providers.DefaultCompiler.Get().CompileStringWithOptions(context.Background(), templateString+"\n"+velacue.BaseTemplate, cuex.DisableResolveProviderFunctions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return def.FromCUE(&inst, templateString)
|
||||
|
||||
@@ -41,6 +41,8 @@ import (
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/kubevela/pkg/util/singleton"
|
||||
|
||||
velacue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
@@ -163,6 +165,19 @@ func (meta *GenMeta) Init(c common.Args, langArgs []string) (err error) {
|
||||
if err != nil {
|
||||
klog.Info("No kubeconfig found, skipping")
|
||||
}
|
||||
|
||||
clt, err := c.GetClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client: %w", err)
|
||||
}
|
||||
singleton.KubeClient.Set(clt)
|
||||
|
||||
dc, err := c.GetDynamicClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get dynamic client: %w", err)
|
||||
}
|
||||
singleton.DynamicClient.Set(dc)
|
||||
|
||||
if _, ok := SupportedLangs[meta.Lang]; !ok {
|
||||
return fmt.Errorf("language %s is not supported", meta.Lang)
|
||||
}
|
||||
@@ -385,7 +400,7 @@ func (g *Generator) GetDefinitionValue(ctx context.Context, cueBytes []byte) (cu
|
||||
return cue.Value{}, "", "", errors.New("definition doesn't include cue schematic")
|
||||
}
|
||||
|
||||
template, err := providers.Compiler.Get().CompileStringWithOptions(ctx, templateString+velacue.BaseTemplate, cuex.DisableResolveProviderFunctions{})
|
||||
template, err := providers.DefaultCompiler.Get().CompileStringWithOptions(ctx, templateString+velacue.BaseTemplate, cuex.DisableResolveProviderFunctions{})
|
||||
if err != nil {
|
||||
return cue.Value{}, "", "", err
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ func (m *GoModuleModifier) addSubGoMod() error {
|
||||
"--rm",
|
||||
"-v", m.apiDir+":/api",
|
||||
"-w", "/api",
|
||||
"golang:1.19-alpine",
|
||||
"golang:1.22-alpine3.18",
|
||||
"go", "get", fmt.Sprintf("%s@%s", m.Package, m.LangArgs.Get(mainModuleVersionKey)),
|
||||
))
|
||||
}
|
||||
@@ -261,7 +261,7 @@ func (m *GoModuleModifier) addSubGoMod() error {
|
||||
"-v", m.apiDir+":/api",
|
||||
"-w", "/api",
|
||||
"--env", "GOPROXY="+m.LangArgs.Get(goProxyKey),
|
||||
"golang:1.19-alpine",
|
||||
"golang:1.22-alpine3.18",
|
||||
"go", "mod", "tidy",
|
||||
))
|
||||
for _, cmd := range cmds {
|
||||
@@ -293,7 +293,7 @@ func (m *GoModuleModifier) tidyMainMod() error {
|
||||
"--rm",
|
||||
"-v", outDir+":/api",
|
||||
"-w", "/api",
|
||||
"golang:1.19-alpine",
|
||||
"golang:1.22-alpine3.18",
|
||||
"go", "mod", "tidy",
|
||||
)
|
||||
if m.Verbose {
|
||||
|
||||
@@ -51,7 +51,7 @@ const ErrGenerateOpenAPIV2JSONSchemaForCapability = "cannot generate OpenAPI v3
|
||||
// ParsePropertiesToSchema parse the properties in cue script to the openapi schema
|
||||
func ParsePropertiesToSchema(ctx context.Context, s string, templateFieldPath ...string) (*openapi3.Schema, error) {
|
||||
t := s + "\n" + BaseTemplate
|
||||
val, err := providers.Compiler.Get().CompileStringWithOptions(ctx, t, cuex.DisableResolveProviderFunctions{})
|
||||
val, err := providers.DefaultCompiler.Get().CompileStringWithOptions(ctx, t, cuex.DisableResolveProviderFunctions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
@@ -32,11 +33,12 @@ import (
|
||||
|
||||
// Args is args for controller-runtime client
|
||||
type Args struct {
|
||||
config *rest.Config
|
||||
rawConfig *api.Config
|
||||
Schema *runtime.Scheme
|
||||
client client.Client
|
||||
dc *discovery.DiscoveryClient
|
||||
config *rest.Config
|
||||
rawConfig *api.Config
|
||||
Schema *runtime.Scheme
|
||||
client client.Client
|
||||
dc *discovery.DiscoveryClient
|
||||
dynamicClient dynamic.Interface
|
||||
}
|
||||
|
||||
// SetConfig insert kubeconfig into Args
|
||||
@@ -151,3 +153,23 @@ func (a *Args) GetDiscoveryClient() (*discovery.DiscoveryClient, error) {
|
||||
a.dc = dc
|
||||
return dc, nil
|
||||
}
|
||||
|
||||
// GetDynamicClient return a dynamic client from cli args
|
||||
func (a *Args) GetDynamicClient() (dynamic.Interface, error) {
|
||||
if a.dynamicClient != nil {
|
||||
return a.dynamicClient, nil
|
||||
}
|
||||
|
||||
cfg, err := a.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynClient, err := dynamic.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.dynamicClient = dynClient
|
||||
return dynClient, nil
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"cuelang.org/go/encoding/openapi"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
cuexv1alpha1 "github.com/kubevela/pkg/apis/cue/v1alpha1"
|
||||
"github.com/oam-dev/terraform-config-inspect/tfconfig"
|
||||
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
@@ -94,6 +95,7 @@ func init() {
|
||||
_ = kruisev1alpha1.AddToScheme(Scheme)
|
||||
_ = gatewayv1beta1.AddToScheme(Scheme)
|
||||
_ = workflowv1alpha1.AddToScheme(Scheme)
|
||||
_ = cuexv1alpha1.AddToScheme(Scheme)
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ func ParseVelaQLFromPath(ctx context.Context, velaQLViewPath string) (*QueryView
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("read view file from %s: %v", velaQLViewPath, err)
|
||||
}
|
||||
val, err := providers.Compiler.Get().CompileString(ctx, string(body))
|
||||
val, err := providers.DefaultCompiler.Get().CompileString(ctx, string(body))
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error when parsing view: %v", err)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cuexv1alpha1 "github.com/kubevela/pkg/apis/cue/v1alpha1"
|
||||
"github.com/kubevela/pkg/util/singleton"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -64,11 +66,16 @@ var _ = BeforeSuite(func() {
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
testScheme := common.Scheme
|
||||
err = cuexv1alpha1.AddToScheme(testScheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
By("new kube client success")
|
||||
singleton.KubeClient.Set(k8sClient)
|
||||
fakeDynamicClient := fake.NewSimpleDynamicClient(testScheme)
|
||||
singleton.DynamicClient.Set(fakeDynamicClient)
|
||||
|
||||
viewHandler = NewViewHandler(k8sClient, cfg)
|
||||
ctx := context.Background()
|
||||
|
||||
+3
-3
@@ -44,7 +44,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/template"
|
||||
)
|
||||
|
||||
@@ -91,7 +91,7 @@ func (handler *ViewHandler) QueryView(ctx context.Context, qv QueryView) (cue.Va
|
||||
if err != nil {
|
||||
return cue.Value{}, fmt.Errorf("failed to load query templates: %w", err)
|
||||
}
|
||||
v, err := providers.Compiler.Get().CompileStringWithOptions(ctx, temp, cuex.WithExtraData("parameter", qv.Parameter))
|
||||
v, err := providers.DefaultCompiler.Get().CompileStringWithOptions(ctx, temp, cuex.WithExtraData("parameter", qv.Parameter))
|
||||
if err != nil {
|
||||
return cue.Value{}, fmt.Errorf("failed to compile query: %w", err)
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func (handler *ViewHandler) delete(ctx context.Context, _ client.Client, _ strin
|
||||
//
|
||||
// For now, we only check 1. cue is valid 2. `status` or `view` field exists
|
||||
func ValidateView(ctx context.Context, viewStr string) error {
|
||||
val, err := providers.Compiler.Get().CompileStringWithOptions(ctx, viewStr, cuex.DisableResolveProviderFunctions{})
|
||||
val, err := providers.DefaultCompiler.Get().CompileStringWithOptions(ctx, viewStr, cuex.DisableResolveProviderFunctions{})
|
||||
if err != nil {
|
||||
return errors.Errorf("error when parsing view: %v", err)
|
||||
}
|
||||
|
||||
@@ -17,30 +17,80 @@ limitations under the License.
|
||||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kubevela/pkg/cue/cuex"
|
||||
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
||||
"github.com/kubevela/pkg/util/runtime"
|
||||
"github.com/kubevela/pkg/util/singleton"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/providers/builtin"
|
||||
"github.com/kubevela/workflow/pkg/providers/email"
|
||||
"github.com/kubevela/workflow/pkg/providers/http"
|
||||
"github.com/kubevela/workflow/pkg/providers/kube"
|
||||
"github.com/kubevela/workflow/pkg/providers/metrics"
|
||||
"github.com/kubevela/workflow/pkg/providers/time"
|
||||
"github.com/kubevela/workflow/pkg/providers/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/config"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/legacy"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/query"
|
||||
legacyquery "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/query"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/query"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
// LegacyProviderName is the name of legacy provider
|
||||
LegacyProviderName = "op"
|
||||
// ConfigProviderName is the name of config provider
|
||||
ConfigProviderName = "config"
|
||||
// QLProviderName is the name of ql provider
|
||||
QLProviderName = "ql"
|
||||
)
|
||||
|
||||
// Compiler is the workflow default compiler
|
||||
var Compiler = singleton.NewSingletonE[*cuex.Compiler](func() (*cuex.Compiler, error) {
|
||||
var (
|
||||
// EnableExternalPackageForDefaultCompiler .
|
||||
EnableExternalPackageForDefaultCompiler = true
|
||||
// EnableExternalPackageWatchForDefaultCompiler .
|
||||
EnableExternalPackageWatchForDefaultCompiler = false
|
||||
)
|
||||
|
||||
// compiler is the workflow default compiler
|
||||
var compiler = singleton.NewSingletonE[*cuex.Compiler](func() (*cuex.Compiler, error) {
|
||||
return cuex.NewCompilerWithInternalPackages(
|
||||
// legacy packages
|
||||
runtime.Must(cuexruntime.NewInternalPackage(LegacyProviderName, legacy.GetLegacyTemplate(), legacy.GetLegacyProviders())),
|
||||
// runtime.Must(cuexruntime.NewInternalPackage(ConfigProviderName, config.GetTemplate(), config.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage(QLProviderName, query.GetTemplate(), query.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage(QLProviderName, legacyquery.GetTemplate(), legacyquery.GetProviders())),
|
||||
|
||||
// workflow internal packages
|
||||
runtime.Must(cuexruntime.NewInternalPackage("email", email.GetTemplate(), email.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("http", http.GetTemplate(), http.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("kube", kube.GetTemplate(), kube.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("metrics", metrics.GetTemplate(), metrics.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("time", time.GetTemplate(), time.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("util", util.GetTemplate(), util.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("builtin", builtin.GetTemplate(), builtin.GetProviders())),
|
||||
|
||||
// kubevela internal packages
|
||||
runtime.Must(cuexruntime.NewInternalPackage("multicluster", multicluster.GetTemplate(), multicluster.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("config", config.GetTemplate(), config.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("oam", oam.GetTemplate(), oam.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("query", query.GetTemplate(), query.GetProviders())),
|
||||
runtime.Must(cuexruntime.NewInternalPackage("terraform", terraform.GetTemplate(), terraform.GetProviders())),
|
||||
), nil
|
||||
})
|
||||
|
||||
// DefaultCompiler compiler for cuex to compile
|
||||
var DefaultCompiler = singleton.NewSingleton[*cuex.Compiler](func() *cuex.Compiler {
|
||||
c := compiler.Get()
|
||||
if EnableExternalPackageForDefaultCompiler {
|
||||
if err := c.LoadExternalPackages(context.Background()); err != nil {
|
||||
klog.Errorf("failed to load external packages for cuex default compiler: ", err.Error())
|
||||
}
|
||||
}
|
||||
if EnableExternalPackageWatchForDefaultCompiler {
|
||||
go c.ListenExternalPackages(nil)
|
||||
}
|
||||
return c
|
||||
})
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// config.cue
|
||||
|
||||
#CreateConfig: {
|
||||
#do: "create"
|
||||
#provider: "config"
|
||||
|
||||
$params: {
|
||||
name: string
|
||||
namespace: string
|
||||
template?: string
|
||||
config: {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#DeleteConfig: {
|
||||
#do: "delete"
|
||||
#provider: "config"
|
||||
|
||||
$params: {
|
||||
name: string
|
||||
namespace: string
|
||||
}
|
||||
}
|
||||
|
||||
#ReadConfig: {
|
||||
#do: "read"
|
||||
#provider: "config"
|
||||
|
||||
$params: {
|
||||
name: string
|
||||
namespace: string
|
||||
}
|
||||
|
||||
$returns: {
|
||||
config: {...}
|
||||
}
|
||||
}
|
||||
|
||||
#ListConfig: {
|
||||
#do: "list"
|
||||
#provider: "config"
|
||||
|
||||
$params: {
|
||||
// Must query with the template
|
||||
template: string
|
||||
namespace: string
|
||||
}
|
||||
|
||||
$returns: {
|
||||
configs: [...{...}]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
Copyright 2023 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ProviderName is provider name
|
||||
ProviderName = "config"
|
||||
)
|
||||
|
||||
// ErrRequestInvalid means the request is invalid
|
||||
var ErrRequestInvalid = errors.New("the request is in valid")
|
||||
|
||||
// CreateConfigProperties the request body for creating a config
|
||||
type CreateConfigProperties struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Template string `json:"template,omitempty"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
}
|
||||
|
||||
// CreateParams is the create params
|
||||
type CreateParams = oamprovidertypes.Params[CreateConfigProperties]
|
||||
|
||||
// CreateConfig creates a config
|
||||
func CreateConfig(ctx context.Context, params *CreateParams) (*any, error) {
|
||||
ccp := params.Params
|
||||
name := ccp.Template
|
||||
namespace := "vela-system"
|
||||
if strings.Contains(ccp.Template, "/") {
|
||||
namespacedName := strings.SplitN(ccp.Template, "/", 2)
|
||||
namespace = namespacedName[0]
|
||||
name = namespacedName[1]
|
||||
}
|
||||
factory := params.ConfigFactory
|
||||
configItem, err := factory.ParseConfig(ctx, config.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}, config.Metadata{
|
||||
NamespacedName: config.NamespacedName{
|
||||
Name: ccp.Name,
|
||||
Namespace: ccp.Namespace,
|
||||
},
|
||||
Properties: ccp.Config,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, factory.CreateOrUpdateConfig(ctx, configItem, ccp.Namespace)
|
||||
}
|
||||
|
||||
// ReadReturnVars is the read return vars
|
||||
type ReadReturnVars struct {
|
||||
Config map[string]any `json:"config"`
|
||||
}
|
||||
|
||||
// ReadReturns is the read returns
|
||||
type ReadReturns = oamprovidertypes.Returns[ReadReturnVars]
|
||||
|
||||
// ReadConfig reads the config
|
||||
func ReadConfig(ctx context.Context, params *oamprovidertypes.Params[config.NamespacedName]) (*ReadReturns, error) {
|
||||
nn := params.Params
|
||||
factory := params.ConfigFactory
|
||||
content, err := factory.ReadConfig(ctx, nn.Namespace, nn.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ReadReturns{
|
||||
Returns: ReadReturnVars{
|
||||
Config: content,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListVars is the list vars
|
||||
type ListVars struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
// ListReturnVars is the list return vars
|
||||
type ListReturnVars struct {
|
||||
Configs []map[string]any `json:"configs"`
|
||||
}
|
||||
|
||||
// ListReturns is the list returns
|
||||
type ListReturns = oamprovidertypes.Returns[ListReturnVars]
|
||||
|
||||
// ListConfig lists the config
|
||||
func ListConfig(ctx context.Context, params *oamprovidertypes.Params[ListVars]) (*ListReturns, error) {
|
||||
template := params.Params.Template
|
||||
namespace := params.Params.Namespace
|
||||
if template == "" || namespace == "" {
|
||||
return nil, ErrRequestInvalid
|
||||
}
|
||||
|
||||
if strings.Contains(template, "/") {
|
||||
namespacedName := strings.SplitN(template, "/", 2)
|
||||
template = namespacedName[1]
|
||||
}
|
||||
factory := params.ConfigFactory
|
||||
configs, err := factory.ListConfigs(ctx, namespace, template, "", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var contents = []map[string]interface{}{}
|
||||
for _, c := range configs {
|
||||
contents = append(contents, map[string]interface{}{
|
||||
"name": c.Name,
|
||||
"alias": c.Alias,
|
||||
"description": c.Description,
|
||||
"config": c.Properties,
|
||||
})
|
||||
}
|
||||
return &ListReturns{
|
||||
Returns: ListReturnVars{
|
||||
Configs: contents,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteConfig deletes a config
|
||||
func DeleteConfig(ctx context.Context, params *oamprovidertypes.Params[config.NamespacedName]) (*any, error) {
|
||||
nn := params.Params
|
||||
factory := params.ConfigFactory
|
||||
return nil, factory.DeleteConfig(ctx, nn.Namespace, nn.Name)
|
||||
}
|
||||
|
||||
//go:embed config.cue
|
||||
var template string
|
||||
|
||||
// GetTemplate returns the cue template.
|
||||
func GetTemplate() string {
|
||||
return template
|
||||
}
|
||||
|
||||
// GetProviders returns the cue providers.
|
||||
func GetProviders() map[string]cuexruntime.ProviderFn {
|
||||
return map[string]cuexruntime.ProviderFn{
|
||||
"create": oamprovidertypes.GenericProviderFn[CreateConfigProperties, any](CreateConfig),
|
||||
"read": oamprovidertypes.GenericProviderFn[config.NamespacedName, ReadReturns](ReadConfig),
|
||||
"list": oamprovidertypes.GenericProviderFn[ListVars, ListReturns](ListConfig),
|
||||
"delete": oamprovidertypes.GenericProviderFn[config.NamespacedName, any](DeleteConfig),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
Copyright 2023 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var scheme = runtime.NewScheme()
|
||||
var factory config.Factory
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecs(t, "Test Config Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
By("Bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.Bool(false),
|
||||
}
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
Expect(clientgoscheme.AddToScheme(scheme)).Should(BeNil())
|
||||
// +kubebuilder:scaffold:scheme
|
||||
By("Create the k8s client")
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
factory = config.NewConfigFactory(k8sClient)
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("Tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
var _ = Describe("Test the config provider", func() {
|
||||
|
||||
It("test creating a config", func() {
|
||||
ctx := context.Background()
|
||||
params := &CreateParams{
|
||||
Params: CreateConfigProperties{
|
||||
Name: "hub-kubevela",
|
||||
Namespace: "default",
|
||||
Template: "default/test-image-registry",
|
||||
Config: map[string]interface{}{
|
||||
"registry": "hub.kubevela.net",
|
||||
},
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
ConfigFactory: factory,
|
||||
},
|
||||
}
|
||||
_, err := CreateConfig(ctx, params)
|
||||
Expect(strings.Contains(err.Error(), "the template does not exist")).Should(BeTrue())
|
||||
|
||||
template, err := factory.ParseTemplate(ctx, "test-image-registry", []byte(templateContent))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(factory.CreateOrUpdateConfigTemplate(ctx, "default", template)).ToNot(HaveOccurred())
|
||||
|
||||
_, err = CreateConfig(ctx, params)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("test creating a config without the template", func() {
|
||||
params := &CreateParams{
|
||||
Params: CreateConfigProperties{
|
||||
Name: "www-kubevela",
|
||||
Namespace: "default",
|
||||
Config: map[string]interface{}{
|
||||
"url": "kubevela.net",
|
||||
},
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
ConfigFactory: factory,
|
||||
},
|
||||
}
|
||||
_, err := CreateConfig(context.Background(), params)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("test listing the config", func() {
|
||||
ctx := context.Background()
|
||||
res, err := ListConfig(ctx, &oamprovidertypes.Params[ListVars]{
|
||||
Params: ListVars{
|
||||
Namespace: "default",
|
||||
Template: "test-image-registry",
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
ConfigFactory: factory,
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
contents := res.Returns.Configs
|
||||
Expect(len(contents)).To(Equal(1))
|
||||
Expect(contents[0]["config"].(map[string]interface{})["registry"]).To(Equal("hub.kubevela.net"))
|
||||
})
|
||||
|
||||
It("test reading the config", func() {
|
||||
ctx := context.Background()
|
||||
res, err := ReadConfig(ctx, &oamprovidertypes.Params[config.NamespacedName]{
|
||||
Params: config.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "hub-kubevela",
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
ConfigFactory: factory,
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(res.Returns.Config["registry"]).To(Equal("hub.kubevela.net"))
|
||||
})
|
||||
|
||||
It("test deleting the config", func() {
|
||||
ctx := context.Background()
|
||||
_, err := DeleteConfig(ctx, &oamprovidertypes.Params[config.NamespacedName]{
|
||||
Params: config.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "hub-kubevela",
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
ConfigFactory: factory,
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
configs, err := factory.ListConfigs(context.Background(), "default", "", "", false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(configs)).To(Equal(1))
|
||||
Expect(configs[0].Properties["url"]).To(Equal("kubevela.net"))
|
||||
})
|
||||
})
|
||||
|
||||
var templateContent = `
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
metadata: {
|
||||
name: "image-registry"
|
||||
alias: "Image Registry"
|
||||
scope: "project"
|
||||
description: "Config information to authenticate image registry"
|
||||
sensitive: false
|
||||
}
|
||||
|
||||
template: {
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: context.namespace
|
||||
labels: {
|
||||
"config.oam.dev/catalog": "velacore-config"
|
||||
"config.oam.dev/type": "image-registry"
|
||||
}
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
type: "kubernetes.io/dockerconfigjson"
|
||||
}
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
"auths": (parameter.registry): {
|
||||
"username": parameter.auth.username
|
||||
"password": parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
"email": parameter.auth.email
|
||||
}
|
||||
"auth": base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
}
|
||||
if parameter.insecure != _|_ {
|
||||
"insecure-skip-verify": strconv.FormatBool(parameter.insecure)
|
||||
}
|
||||
if parameter.useHTTP != _|_ {
|
||||
"protocol-use-http": strconv.FormatBool(parameter.useHTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Image registry FQDN, such as: index.docker.io
|
||||
registry: *"index.docker.io" | string
|
||||
// +usage=Authenticate the image registry
|
||||
auth?: {
|
||||
// +usage=Private Image registry username
|
||||
username: string
|
||||
// +usage=Private Image registry password
|
||||
password: string
|
||||
// +usage=Private Image registry email
|
||||
email?: string
|
||||
}
|
||||
// +usage=For the registry server that uses the self-signed certificate
|
||||
insecure?: bool
|
||||
// +usage=For the registry server that uses the HTTP protocol
|
||||
useHTTP?: bool
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,8 +1,8 @@
|
||||
// config.cue
|
||||
|
||||
#Create: {
|
||||
#CreateConfig: {
|
||||
#do: "create"
|
||||
#provider: "config"
|
||||
#provider: "op"
|
||||
|
||||
name: string
|
||||
namespace: string
|
||||
@@ -12,17 +12,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
#Delete: {
|
||||
#DeleteConfig: {
|
||||
#do: "delete"
|
||||
#provider: "config"
|
||||
#provider: "op"
|
||||
|
||||
name: string
|
||||
namespace: string
|
||||
}
|
||||
|
||||
#Read: {
|
||||
#ReadConfig: {
|
||||
#do: "read"
|
||||
#provider: "config"
|
||||
#provider: "op"
|
||||
|
||||
name: string
|
||||
namespace: string
|
||||
@@ -30,9 +30,9 @@
|
||||
config: {...}
|
||||
}
|
||||
|
||||
#List: {
|
||||
#ListConfig: {
|
||||
#do: "list"
|
||||
#provider: "config"
|
||||
#provider: "op"
|
||||
|
||||
// Must query with the template
|
||||
template: string
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -148,9 +148,9 @@ func GetTemplate() string {
|
||||
// GetProviders returns the cue providers.
|
||||
func GetProviders() map[string]cuexruntime.ProviderFn {
|
||||
return map[string]cuexruntime.ProviderFn{
|
||||
"create-config": oamprovidertypes.OAMGenericProviderFn[CreateConfigProperties, any](CreateConfig),
|
||||
"read-config": oamprovidertypes.OAMGenericProviderFn[config.NamespacedName, ReadResult](ReadConfig),
|
||||
"list-config": oamprovidertypes.OAMGenericProviderFn[ListVars, ListResult](ListConfig),
|
||||
"delete-config": oamprovidertypes.OAMGenericProviderFn[config.NamespacedName, any](DeleteConfig),
|
||||
"create": oamprovidertypes.OAMGenericProviderFn[CreateConfigProperties, any](CreateConfig),
|
||||
"read": oamprovidertypes.OAMGenericProviderFn[config.NamespacedName, ReadResult](ReadConfig),
|
||||
"list": oamprovidertypes.OAMGenericProviderFn[ListVars, ListResult](ListConfig),
|
||||
"delete": oamprovidertypes.OAMGenericProviderFn[config.NamespacedName, any](DeleteConfig),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
wflegacy "github.com/kubevela/workflow/pkg/providers/legacy"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/config"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/terraform"
|
||||
@@ -42,6 +43,7 @@ func GetLegacyProviders() map[string]cuexruntime.ProviderFn {
|
||||
registerProviders(providers, multicluster.GetProviders())
|
||||
registerProviders(providers, oam.GetProviders())
|
||||
registerProviders(providers, terraform.GetProviders())
|
||||
registerProviders(providers, config.GetProviders())
|
||||
registerProviders(providers, wflegacy.GetLegacyProviders())
|
||||
|
||||
return providers
|
||||
@@ -53,6 +55,7 @@ func GetLegacyTemplate() string {
|
||||
multicluster.GetTemplate(),
|
||||
oam.GetTemplate(),
|
||||
terraform.GetTemplate(),
|
||||
config.GetTemplate(),
|
||||
wflegacy.GetLegacyTemplate(),
|
||||
},
|
||||
"\n")
|
||||
|
||||
@@ -45,7 +45,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
// DeployParameter is the parameter of deploy workflow step
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
pkgpolicy "github.com/oam-dev/kubevela/pkg/policy"
|
||||
"github.com/oam-dev/kubevela/pkg/policy/envbinding"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
// Inputs is the inputs for multi cluster
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
func TestMakePlacementDecisions(t *testing.T) {
|
||||
|
||||
@@ -31,7 +31,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"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -35,7 +35,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
func setupClient(ctx context.Context, t *testing.T) client.Client {
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
var _ = Describe("Test query endpoints", func() {
|
||||
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -39,7 +39,7 @@ import (
|
||||
helmapi "github.com/oam-dev/kubevela/pkg/appfile/helm/flux2apis"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
type AppResourcesList struct {
|
||||
|
||||
@@ -45,7 +45,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
func TestPodStatus(t *testing.T) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
// Outputs is the output parameters for Terraform components.
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/legacy/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
func fakeWorkloadRenderer(_ context.Context, comp apicommon.ApplicationComponent) (*appfile.Component, error) {
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package multicluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
pkgmaps "github.com/kubevela/pkg/util/maps"
|
||||
"github.com/kubevela/pkg/util/slices"
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
workflowerrors "github.com/kubevela/workflow/pkg/errors"
|
||||
|
||||
"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"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
pkgpolicy "github.com/oam-dev/kubevela/pkg/policy"
|
||||
"github.com/oam-dev/kubevela/pkg/policy/envbinding"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
// DeployParameter is the parameter of deploy workflow step
|
||||
type DeployParameter struct {
|
||||
// Declare the policies that used for this deployment. If not specified, the components will be deployed to the hub cluster.
|
||||
Policies []string `json:"policies,omitempty"`
|
||||
// Maximum number of concurrent delivered components.
|
||||
Parallelism int64 `json:"parallelism"`
|
||||
// If set false, this step will apply the components with the terraform workload.
|
||||
IgnoreTerraformComponent bool `json:"ignoreTerraformComponent"`
|
||||
// The policies that embeds in the `deploy` step directly
|
||||
InlinePolicies []v1beta1.AppPolicy `json:"inlinePolicies,omitempty"`
|
||||
}
|
||||
|
||||
// DeployWorkflowStepExecutor executor to run deploy workflow step
|
||||
type DeployWorkflowStepExecutor interface {
|
||||
Deploy(ctx context.Context) (healthy bool, reason string, err error)
|
||||
}
|
||||
|
||||
// NewDeployWorkflowStepExecutor .
|
||||
func NewDeployWorkflowStepExecutor(cli client.Client, af *appfile.Appfile, apply oamprovidertypes.ComponentApply, healthCheck oamprovidertypes.ComponentHealthCheck, renderer oamprovidertypes.WorkloadRender, parameter DeployParameter) DeployWorkflowStepExecutor {
|
||||
return &deployWorkflowStepExecutor{
|
||||
cli: cli,
|
||||
af: af,
|
||||
apply: apply,
|
||||
healthCheck: healthCheck,
|
||||
renderer: renderer,
|
||||
parameter: parameter,
|
||||
}
|
||||
}
|
||||
|
||||
type deployWorkflowStepExecutor struct {
|
||||
cli client.Client
|
||||
af *appfile.Appfile
|
||||
apply oamprovidertypes.ComponentApply
|
||||
healthCheck oamprovidertypes.ComponentHealthCheck
|
||||
renderer oamprovidertypes.WorkloadRender
|
||||
parameter DeployParameter
|
||||
}
|
||||
|
||||
// Deploy execute deploy workflow step
|
||||
func (executor *deployWorkflowStepExecutor) Deploy(ctx context.Context) (bool, string, error) {
|
||||
policies, err := selectPolicies(executor.af.Policies, executor.parameter.Policies)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
policies = append(policies, fillInlinePolicyNames(executor.parameter.InlinePolicies)...)
|
||||
components, err := loadComponents(ctx, executor.renderer, executor.cli, executor.af, executor.af.Components, executor.parameter.IgnoreTerraformComponent)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
// Dealing with topology, override and replication policies in order.
|
||||
placements, err := pkgpolicy.GetPlacementsFromTopologyPolicies(ctx, executor.cli, executor.af.Namespace, policies, resourcekeeper.AllowCrossNamespaceResource)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
components, err = overrideConfiguration(policies, components)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
components, err = pkgpolicy.ReplicateComponents(policies, components)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
return applyComponents(ctx, executor.apply, executor.healthCheck, components, placements, int(executor.parameter.Parallelism))
|
||||
}
|
||||
|
||||
func selectPolicies(policies []v1beta1.AppPolicy, policyNames []string) ([]v1beta1.AppPolicy, error) {
|
||||
policyMap := make(map[string]v1beta1.AppPolicy)
|
||||
for _, policy := range policies {
|
||||
policyMap[policy.Name] = policy
|
||||
}
|
||||
var selectedPolicies []v1beta1.AppPolicy
|
||||
for _, policyName := range policyNames {
|
||||
if policy, found := policyMap[policyName]; found {
|
||||
selectedPolicies = append(selectedPolicies, policy)
|
||||
} else {
|
||||
return nil, errors.Errorf("policy %s not found", policyName)
|
||||
}
|
||||
}
|
||||
return selectedPolicies, nil
|
||||
}
|
||||
|
||||
func fillInlinePolicyNames(policies []v1beta1.AppPolicy) []v1beta1.AppPolicy {
|
||||
for i := range policies {
|
||||
if policies[i].Name == "" {
|
||||
policies[i].Name = fmt.Sprintf("inline-%s-policy-%d", policies[i].Type, i)
|
||||
}
|
||||
}
|
||||
return policies
|
||||
}
|
||||
|
||||
func loadComponents(ctx context.Context, render oamprovidertypes.WorkloadRender, cli client.Client, af *appfile.Appfile, components []common.ApplicationComponent, ignoreTerraformComponent bool) ([]common.ApplicationComponent, error) {
|
||||
var loadedComponents []common.ApplicationComponent
|
||||
for _, comp := range components {
|
||||
loadedComp, err := af.LoadDynamicComponent(ctx, cli, comp.DeepCopy())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ignoreTerraformComponent {
|
||||
wl, err := render(ctx, comp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to render component into workload")
|
||||
}
|
||||
if wl.CapabilityCategory == types.TerraformCategory {
|
||||
continue
|
||||
}
|
||||
}
|
||||
loadedComponents = append(loadedComponents, *loadedComp)
|
||||
}
|
||||
return loadedComponents, nil
|
||||
}
|
||||
|
||||
func overrideConfiguration(policies []v1beta1.AppPolicy, components []common.ApplicationComponent) ([]common.ApplicationComponent, error) {
|
||||
var err error
|
||||
for _, policy := range policies {
|
||||
if policy.Type == v1alpha1.OverridePolicyType {
|
||||
if policy.Properties == nil {
|
||||
return nil, fmt.Errorf("override policy %s must not have empty properties", policy.Name)
|
||||
}
|
||||
overrideSpec := &v1alpha1.OverridePolicySpec{}
|
||||
if err := utils.StrictUnmarshal(policy.Properties.Raw, overrideSpec); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse override policy %s", policy.Name)
|
||||
}
|
||||
components, err = envbinding.PatchComponents(components, overrideSpec.Components, overrideSpec.Selector)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to apply override policy %s", policy.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return components, nil
|
||||
}
|
||||
|
||||
type valueBuilder func(s string) cue.Value
|
||||
|
||||
type applyTask struct {
|
||||
component common.ApplicationComponent
|
||||
placement v1alpha1.PlacementDecision
|
||||
healthy *bool
|
||||
}
|
||||
|
||||
func (t *applyTask) key() string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", t.placement.Cluster, t.placement.Namespace, t.component.ReplicaKey, t.component.Name)
|
||||
}
|
||||
|
||||
func (t *applyTask) varKey(v string) string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", t.placement.Cluster, t.placement.Namespace, t.component.ReplicaKey, v)
|
||||
}
|
||||
|
||||
func (t *applyTask) varKeyWithoutReplica(v string) string {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", t.placement.Cluster, t.placement.Namespace, "", v)
|
||||
}
|
||||
|
||||
func (t *applyTask) getVar(from string, cache *pkgmaps.SyncMap[string, cue.Value]) cue.Value {
|
||||
key := t.varKey(from)
|
||||
keyWithNoReplica := t.varKeyWithoutReplica(from)
|
||||
var val cue.Value
|
||||
var ok bool
|
||||
if val, ok = cache.Get(key); !ok {
|
||||
if val, ok = cache.Get(keyWithNoReplica); !ok {
|
||||
return cue.Value{}
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (t *applyTask) fillInputs(inputs *pkgmaps.SyncMap[string, cue.Value], build valueBuilder) error {
|
||||
if len(t.component.Inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
x := component2Value(t.component, build)
|
||||
for _, input := range t.component.Inputs {
|
||||
var inputVal cue.Value
|
||||
if inputVal = t.getVar(input.From, inputs); inputVal == (cue.Value{}) {
|
||||
return fmt.Errorf("input %s is not ready", input)
|
||||
}
|
||||
|
||||
x, err = value.SetValueByScript(x, inputVal, fieldPathToComponent(input.ParameterKey))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fill value to component")
|
||||
}
|
||||
}
|
||||
newComp, err := value2Component(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.component = *newComp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *applyTask) generateOutput(output *unstructured.Unstructured, outputs []*unstructured.Unstructured, cache *pkgmaps.SyncMap[string, cue.Value], build valueBuilder) error {
|
||||
if len(t.component.Outputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cueString string
|
||||
if output != nil {
|
||||
outputJSON, err := output.MarshalJSON()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal output")
|
||||
}
|
||||
cueString += fmt.Sprintf("output:%s\n", string(outputJSON))
|
||||
}
|
||||
componentVal := build(cueString)
|
||||
|
||||
for _, os := range outputs {
|
||||
name := os.GetLabels()[oam.TraitResource]
|
||||
if name != "" {
|
||||
componentVal = componentVal.FillPath(cue.ParsePath(fmt.Sprintf("outputs.%s", name)), os.Object)
|
||||
}
|
||||
}
|
||||
|
||||
for _, o := range t.component.Outputs {
|
||||
pathToSetVar := t.varKey(o.Name)
|
||||
actualOutput := componentVal.LookupPath(cue.ParsePath(o.ValueFrom))
|
||||
if !actualOutput.Exists() {
|
||||
return workflowerrors.LookUpNotFoundErr(o.ValueFrom)
|
||||
}
|
||||
cache.Set(pathToSetVar, actualOutput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *applyTask) allDependsReady(healthyMap map[string]bool) bool {
|
||||
for _, d := range t.component.DependsOn {
|
||||
dKey := fmt.Sprintf("%s/%s/%s/%s", t.placement.Cluster, t.placement.Namespace, t.component.ReplicaKey, d)
|
||||
dKeyWithoutReplica := fmt.Sprintf("%s/%s/%s/%s", t.placement.Cluster, t.placement.Namespace, "", d)
|
||||
if !healthyMap[dKey] && !healthyMap[dKeyWithoutReplica] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *applyTask) allInputReady(cache *pkgmaps.SyncMap[string, cue.Value]) bool {
|
||||
for _, in := range t.component.Inputs {
|
||||
if val := t.getVar(in.From, cache); val == (cue.Value{}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type applyTaskResult struct {
|
||||
healthy bool
|
||||
err error
|
||||
task *applyTask
|
||||
}
|
||||
|
||||
// applyComponents will apply components to placements.
|
||||
func applyComponents(ctx context.Context, apply oamprovidertypes.ComponentApply, healthCheck oamprovidertypes.ComponentHealthCheck, components []common.ApplicationComponent, placements []v1alpha1.PlacementDecision, parallelism int) (bool, string, error) {
|
||||
var tasks []*applyTask
|
||||
var cache = pkgmaps.NewSyncMap[string, cue.Value]()
|
||||
rootValue := cuecontext.New().CompileString("{}")
|
||||
if rootValue.Err() != nil {
|
||||
return false, "", rootValue.Err()
|
||||
}
|
||||
var cueMutex sync.Mutex
|
||||
var makeValue = func(s string) cue.Value {
|
||||
cueMutex.Lock()
|
||||
defer cueMutex.Unlock()
|
||||
return rootValue.Context().CompileString(s)
|
||||
}
|
||||
|
||||
taskHealthyMap := map[string]bool{}
|
||||
for _, comp := range components {
|
||||
for _, pl := range placements {
|
||||
tasks = append(tasks, &applyTask{component: comp, placement: pl})
|
||||
}
|
||||
}
|
||||
unhealthyResults := make([]*applyTaskResult, 0)
|
||||
maxHealthCheckTimes := len(tasks)
|
||||
HealthCheck:
|
||||
for i := 0; i < maxHealthCheckTimes; i++ {
|
||||
checkTasks := make([]*applyTask, 0)
|
||||
for _, task := range tasks {
|
||||
if task.healthy == nil && task.allDependsReady(taskHealthyMap) && task.allInputReady(cache) {
|
||||
task.healthy = new(bool)
|
||||
err := task.fillInputs(cache, makeValue)
|
||||
if err != nil {
|
||||
taskHealthyMap[task.key()] = false
|
||||
unhealthyResults = append(unhealthyResults, &applyTaskResult{healthy: false, err: err, task: task})
|
||||
continue
|
||||
}
|
||||
checkTasks = append(checkTasks, task)
|
||||
}
|
||||
}
|
||||
if len(checkTasks) == 0 {
|
||||
break HealthCheck
|
||||
}
|
||||
checkResults := slices.ParMap[*applyTask, *applyTaskResult](checkTasks, func(task *applyTask) *applyTaskResult {
|
||||
healthy, output, outputs, err := healthCheck(ctx, task.component, nil, task.placement.Cluster, task.placement.Namespace)
|
||||
task.healthy = pointer.Bool(healthy)
|
||||
if healthy {
|
||||
err = task.generateOutput(output, outputs, cache, makeValue)
|
||||
}
|
||||
return &applyTaskResult{healthy: healthy, err: err, task: task}
|
||||
}, slices.Parallelism(parallelism))
|
||||
|
||||
for _, res := range checkResults {
|
||||
taskHealthyMap[res.task.key()] = res.healthy
|
||||
if !res.healthy || res.err != nil {
|
||||
unhealthyResults = append(unhealthyResults, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pendingTasks []*applyTask
|
||||
var todoTasks []*applyTask
|
||||
|
||||
for _, task := range tasks {
|
||||
if healthy, ok := taskHealthyMap[task.key()]; healthy && ok {
|
||||
continue
|
||||
}
|
||||
if task.allDependsReady(taskHealthyMap) && task.allInputReady(cache) {
|
||||
todoTasks = append(todoTasks, task)
|
||||
} else {
|
||||
pendingTasks = append(pendingTasks, task)
|
||||
}
|
||||
}
|
||||
var results []*applyTaskResult
|
||||
if len(todoTasks) > 0 {
|
||||
results = slices.ParMap[*applyTask, *applyTaskResult](todoTasks, func(task *applyTask) *applyTaskResult {
|
||||
err := task.fillInputs(cache, makeValue)
|
||||
if err != nil {
|
||||
return &applyTaskResult{healthy: false, err: err, task: task}
|
||||
}
|
||||
_, _, healthy, err := apply(ctx, task.component, nil, task.placement.Cluster, task.placement.Namespace)
|
||||
if err != nil {
|
||||
return &applyTaskResult{healthy: healthy, err: err, task: task}
|
||||
}
|
||||
return &applyTaskResult{healthy: healthy, err: err, task: task}
|
||||
}, slices.Parallelism(parallelism))
|
||||
}
|
||||
var errs []error
|
||||
var allHealthy = true
|
||||
var reasons []string
|
||||
for _, res := range unhealthyResults {
|
||||
if res.err != nil {
|
||||
errs = append(errs, fmt.Errorf("error health check from %s: %w", res.task.key(), res.err))
|
||||
}
|
||||
}
|
||||
for _, res := range results {
|
||||
if res.err != nil {
|
||||
errs = append(errs, fmt.Errorf("error encountered in cluster %s: %w", res.task.placement.Cluster, res.err))
|
||||
}
|
||||
if !res.healthy {
|
||||
allHealthy = false
|
||||
reasons = append(reasons, fmt.Sprintf("%s is not healthy", res.task.key()))
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range pendingTasks {
|
||||
reasons = append(reasons, fmt.Sprintf("%s is waiting dependents", t.key()))
|
||||
}
|
||||
|
||||
return allHealthy && len(pendingTasks) == 0, strings.Join(reasons, ","), velaerrors.AggregateErrors(errs)
|
||||
}
|
||||
|
||||
func fieldPathToComponent(input string) string {
|
||||
return fmt.Sprintf("properties.%s", strings.TrimSpace(input))
|
||||
}
|
||||
|
||||
func component2Value(comp common.ApplicationComponent, build valueBuilder) cue.Value {
|
||||
x := build("")
|
||||
x = x.FillPath(cue.ParsePath(""), comp)
|
||||
// Component.ReplicaKey have no json tag, so we need to set it manually
|
||||
x = x.FillPath(cue.ParsePath("replicaKey"), comp.ReplicaKey)
|
||||
return x
|
||||
}
|
||||
|
||||
func value2Component(v cue.Value) (*common.ApplicationComponent, error) {
|
||||
var comp common.ApplicationComponent
|
||||
err := value.UnmarshalTo(v, &comp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rk, err := v.LookupPath(cue.ParsePath("replicaKey")).String(); err == nil {
|
||||
comp.ReplicaKey = rk
|
||||
}
|
||||
return &comp, nil
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package multicluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
|
||||
apicommon "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"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
func TestOverrideConfiguration(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Policies []v1beta1.AppPolicy
|
||||
Components []apicommon.ApplicationComponent
|
||||
Outputs []apicommon.ApplicationComponent
|
||||
Error string
|
||||
}{
|
||||
"invalid-policies": {
|
||||
Policies: []v1beta1.AppPolicy{{
|
||||
Name: "override-policy",
|
||||
Type: "override",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`bad value`)},
|
||||
}},
|
||||
Error: "failed to parse override policy",
|
||||
},
|
||||
"empty-policy": {
|
||||
Policies: []v1beta1.AppPolicy{{
|
||||
Name: "override-policy",
|
||||
Type: "override",
|
||||
Properties: nil,
|
||||
}},
|
||||
Error: "empty properties",
|
||||
},
|
||||
"normal": {
|
||||
Policies: []v1beta1.AppPolicy{{
|
||||
Name: "override-policy",
|
||||
Type: "override",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"components":[{"name":"comp","properties":{"x":5}}]}`)},
|
||||
}},
|
||||
Components: []apicommon.ApplicationComponent{{
|
||||
Name: "comp",
|
||||
Traits: []apicommon.ApplicationTrait{},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"x":1}`)},
|
||||
}},
|
||||
Outputs: []apicommon.ApplicationComponent{{
|
||||
Name: "comp",
|
||||
Traits: []apicommon.ApplicationTrait{},
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"x":5}`)},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for name, tt := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
comps, err := overrideConfiguration(tt.Policies, tt.Components)
|
||||
if tt.Error != "" {
|
||||
r.NotNil(err)
|
||||
r.Contains(err.Error(), tt.Error)
|
||||
} else {
|
||||
r.NoError(err)
|
||||
r.Equal(tt.Outputs, comps)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyComponentsDepends(t *testing.T) {
|
||||
r := require.New(t)
|
||||
const n, m = 50, 5
|
||||
var components []apicommon.ApplicationComponent
|
||||
var placements []v1alpha1.PlacementDecision
|
||||
for i := 0; i < n*3; i++ {
|
||||
comp := apicommon.ApplicationComponent{Name: fmt.Sprintf("comp-%d", i)}
|
||||
if i%3 != 0 {
|
||||
comp.DependsOn = append(comp.DependsOn, fmt.Sprintf("comp-%d", i-1))
|
||||
}
|
||||
if i%3 == 2 {
|
||||
comp.DependsOn = append(comp.DependsOn, fmt.Sprintf("comp-%d", i-1))
|
||||
}
|
||||
components = append(components, comp)
|
||||
}
|
||||
for i := 0; i < m; i++ {
|
||||
placements = append(placements, v1alpha1.PlacementDecision{Cluster: fmt.Sprintf("cluster-%d", i)})
|
||||
}
|
||||
|
||||
applyMap := &sync.Map{}
|
||||
apply := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) {
|
||||
time.Sleep(time.Duration(rand.Intn(200)+25) * time.Millisecond)
|
||||
applyMap.Store(fmt.Sprintf("%s/%s", clusterName, comp.Name), true)
|
||||
return nil, nil, true, nil
|
||||
}
|
||||
healthCheck := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (bool, *unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
_, found := applyMap.Load(fmt.Sprintf("%s/%s", clusterName, comp.Name))
|
||||
return found, nil, nil, nil
|
||||
}
|
||||
parallelism := 10
|
||||
|
||||
countMap := func() int {
|
||||
cnt := 0
|
||||
applyMap.Range(func(key, value interface{}) bool {
|
||||
cnt++
|
||||
return true
|
||||
})
|
||||
return cnt
|
||||
}
|
||||
ctx := context.Background()
|
||||
healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.False(healthy)
|
||||
r.Equal(n*m, countMap())
|
||||
|
||||
healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.False(healthy)
|
||||
r.Equal(2*n*m, countMap())
|
||||
|
||||
healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.True(healthy)
|
||||
r.Equal(3*n*m, countMap())
|
||||
}
|
||||
|
||||
func TestApplyComponentsIO(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
var (
|
||||
parallelism = 10
|
||||
applyMap = new(sync.Map)
|
||||
ctx = context.Background()
|
||||
)
|
||||
apply := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) {
|
||||
time.Sleep(time.Duration(rand.Intn(200)+25) * time.Millisecond)
|
||||
applyMap.Store(fmt.Sprintf("%s/%s", clusterName, comp.Name), true)
|
||||
return nil, nil, true, nil
|
||||
}
|
||||
healthCheck := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (bool, *unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
_, found := applyMap.Load(fmt.Sprintf("%s/%s", clusterName, comp.Name))
|
||||
return found, &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"path": fmt.Sprintf("%s/%s", clusterName, comp.Name),
|
||||
},
|
||||
}}, []*unstructured.Unstructured{
|
||||
{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
oam.TraitResource: "obj",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"path": fmt.Sprintf("%s/%s", clusterName, comp.Name),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
resetStore := func() {
|
||||
applyMap = &sync.Map{}
|
||||
}
|
||||
countMap := func() int {
|
||||
cnt := 0
|
||||
applyMap.Range(func(key, value interface{}) bool {
|
||||
cnt++
|
||||
return true
|
||||
})
|
||||
return cnt
|
||||
}
|
||||
|
||||
t.Run("apply components with io successfully", func(t *testing.T) {
|
||||
resetStore()
|
||||
const n, m = 10, 5
|
||||
var components []apicommon.ApplicationComponent
|
||||
var placements []v1alpha1.PlacementDecision
|
||||
for i := 0; i < n; i++ {
|
||||
comp := apicommon.ApplicationComponent{
|
||||
Name: fmt.Sprintf("comp-%d", i),
|
||||
Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"placeholder":%d}`, i))},
|
||||
}
|
||||
if i != 0 {
|
||||
comp.Inputs = workflowv1alpha1.StepInputs{
|
||||
{
|
||||
ParameterKey: "input_slot_1",
|
||||
From: fmt.Sprintf("var-output-%d", i-1),
|
||||
},
|
||||
{
|
||||
ParameterKey: "input_slot_2",
|
||||
From: fmt.Sprintf("var-outputs-%d", i-1),
|
||||
},
|
||||
}
|
||||
}
|
||||
if i != n-1 {
|
||||
comp.Outputs = workflowv1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "output.spec.path",
|
||||
Name: fmt.Sprintf("var-output-%d", i),
|
||||
},
|
||||
{
|
||||
ValueFrom: "outputs.obj.spec.path",
|
||||
Name: fmt.Sprintf("var-outputs-%d", i),
|
||||
},
|
||||
}
|
||||
}
|
||||
components = append(components, comp)
|
||||
}
|
||||
for i := 0; i < m; i++ {
|
||||
placements = append(placements, v1alpha1.PlacementDecision{Cluster: fmt.Sprintf("cluster-%d", i)})
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.Equal((i+1)*m, countMap())
|
||||
if i == n-1 {
|
||||
r.True(healthy)
|
||||
} else {
|
||||
r.False(healthy)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("apply components with io failed", func(t *testing.T) {
|
||||
resetStore()
|
||||
components := []apicommon.ApplicationComponent{
|
||||
{
|
||||
Name: "comp-0",
|
||||
Outputs: workflowv1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "output.spec.error_path",
|
||||
Name: "var1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "comp-1",
|
||||
Inputs: workflowv1alpha1.StepInputs{
|
||||
{
|
||||
ParameterKey: "input_slot_1",
|
||||
From: "var1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
placements := []v1alpha1.PlacementDecision{
|
||||
{Cluster: "cluster-0"},
|
||||
}
|
||||
healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.False(healthy)
|
||||
healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.ErrorContains(err, "failed to lookup value")
|
||||
r.False(healthy)
|
||||
})
|
||||
|
||||
t.Run("apply components with io and replication", func(t *testing.T) {
|
||||
// comp-0 ---> comp1-beijing --> comp2-beijing
|
||||
// |-> comp1-shanghai --> comp2-shanghai
|
||||
resetStore()
|
||||
storeKey := func(clusterName string, comp apicommon.ApplicationComponent) string {
|
||||
return fmt.Sprintf("%s/%s/%s", clusterName, comp.Name, comp.ReplicaKey)
|
||||
}
|
||||
type applyResult struct {
|
||||
output *unstructured.Unstructured
|
||||
outputs []*unstructured.Unstructured
|
||||
}
|
||||
apply := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) {
|
||||
time.Sleep(time.Duration(rand.Intn(200)+25) * time.Millisecond)
|
||||
key := storeKey(clusterName, comp)
|
||||
result := applyResult{
|
||||
output: &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{
|
||||
"path": key,
|
||||
"anotherPath": key,
|
||||
},
|
||||
}}, outputs: []*unstructured.Unstructured{
|
||||
{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"labels": map[string]interface{}{
|
||||
oam.TraitResource: "obj",
|
||||
},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"path": key,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
applyMap.Store(storeKey(clusterName, comp), result)
|
||||
return nil, nil, true, nil
|
||||
}
|
||||
healthCheck := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (bool, *unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
key := storeKey(clusterName, comp)
|
||||
r, found := applyMap.Load(key)
|
||||
result, _ := r.(applyResult)
|
||||
return found, result.output, result.outputs, nil
|
||||
}
|
||||
|
||||
inputSlot := "input_slot"
|
||||
components := []apicommon.ApplicationComponent{
|
||||
{
|
||||
Name: "comp-0",
|
||||
Outputs: workflowv1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "output.spec.path",
|
||||
Name: "var1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "comp-1",
|
||||
Inputs: workflowv1alpha1.StepInputs{
|
||||
{
|
||||
ParameterKey: inputSlot,
|
||||
From: "var1",
|
||||
},
|
||||
},
|
||||
Outputs: workflowv1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "output.spec.anotherPath",
|
||||
Name: "var2",
|
||||
},
|
||||
},
|
||||
ReplicaKey: "beijing",
|
||||
},
|
||||
{
|
||||
Name: "comp-1",
|
||||
Inputs: workflowv1alpha1.StepInputs{
|
||||
{
|
||||
ParameterKey: inputSlot,
|
||||
From: "var1",
|
||||
},
|
||||
},
|
||||
Outputs: workflowv1alpha1.StepOutputs{
|
||||
{
|
||||
ValueFrom: "output.spec.anotherPath",
|
||||
Name: "var2",
|
||||
},
|
||||
},
|
||||
ReplicaKey: "shanghai",
|
||||
},
|
||||
{
|
||||
Name: "comp-2",
|
||||
Inputs: workflowv1alpha1.StepInputs{
|
||||
{
|
||||
ParameterKey: inputSlot,
|
||||
From: "var2",
|
||||
},
|
||||
},
|
||||
ReplicaKey: "beijing",
|
||||
},
|
||||
{
|
||||
Name: "comp-2",
|
||||
Inputs: workflowv1alpha1.StepInputs{
|
||||
{
|
||||
ParameterKey: inputSlot,
|
||||
From: "var2",
|
||||
},
|
||||
},
|
||||
ReplicaKey: "shanghai",
|
||||
},
|
||||
}
|
||||
placements := []v1alpha1.PlacementDecision{
|
||||
{Cluster: "cluster-0"},
|
||||
}
|
||||
healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.False(healthy)
|
||||
|
||||
healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.False(healthy)
|
||||
|
||||
healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism)
|
||||
r.NoError(err)
|
||||
r.True(healthy)
|
||||
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// multicluster.cue
|
||||
|
||||
#ListClusters: {
|
||||
#provider: "multicluster"
|
||||
#do: "list-clusters"
|
||||
|
||||
$returns?: {
|
||||
outputs: {
|
||||
clusters: [...string]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#GetPlacementsFromTmulticlusterologyPolicies: {
|
||||
#provider: "multicluster"
|
||||
#do: "get-placements-from-tmulticlusterology-policies"
|
||||
|
||||
$params: {
|
||||
policies: [...string]
|
||||
}
|
||||
$returns?: {
|
||||
placements: [...{
|
||||
cluster: string
|
||||
namespace: string
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#Deploy: {
|
||||
#provider: "multicluster"
|
||||
#do: "deploy"
|
||||
|
||||
$params: {
|
||||
policies: [...string]
|
||||
parallelism: int
|
||||
ignoreTerraformComponent: bool
|
||||
inlinePolicies: *[] | [...{...}]
|
||||
}
|
||||
$returns?: {...}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package multicluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
pkgpolicy "github.com/oam-dev/kubevela/pkg/policy"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
// Inputs is the inputs for multi cluster
|
||||
type Inputs[T any] struct {
|
||||
Inputs T `json:"inputs"`
|
||||
}
|
||||
|
||||
// Outputs is the outputs for multi cluster
|
||||
type Outputs[T any] struct {
|
||||
Outputs T `json:"outputs"`
|
||||
}
|
||||
|
||||
// PlacementDecisionVars is the vars for make placement decisions
|
||||
type PlacementDecisionVars struct {
|
||||
PolicyName string `json:"policyName"`
|
||||
EnvName string `json:"envName"`
|
||||
Placement *v1alpha1.EnvPlacement `json:"placement,omitempty"`
|
||||
}
|
||||
|
||||
// PlacementDecisionResult is the result for make placement decisions
|
||||
type PlacementDecisionResult struct {
|
||||
Decisions []v1alpha1.PlacementDecision `json:"decisions"`
|
||||
}
|
||||
|
||||
// PlacementDecisionParams is the parameter for make placement decisions
|
||||
type PlacementDecisionParams = oamprovidertypes.OAMParams[Inputs[PlacementDecisionVars]]
|
||||
|
||||
// PlacementDecisionReturns is the return value for make placement decisions
|
||||
type PlacementDecisionReturns = Outputs[PlacementDecisionResult]
|
||||
|
||||
// ApplicationVars is the vars for patching application
|
||||
type ApplicationVars struct {
|
||||
EnvName string `json:"envName"`
|
||||
Patch *v1alpha1.EnvPatch `json:"patch,omitempty"`
|
||||
Selector *v1alpha1.EnvSelector `json:"selector,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationParams is the parameter for patch application
|
||||
type ApplicationParams = oamprovidertypes.OAMParams[Inputs[ApplicationVars]]
|
||||
|
||||
// ClusterParams is the parameter for list clusters
|
||||
type ClusterParams struct {
|
||||
Clusters []string `json:"clusters"`
|
||||
}
|
||||
|
||||
// ClusterReturns is the return value for list clusters
|
||||
type ClusterReturns = oamprovidertypes.Returns[Outputs[ClusterParams]]
|
||||
|
||||
// ListClusters lists clusters
|
||||
func ListClusters(ctx context.Context, params *oamprovidertypes.Params[any]) (*ClusterReturns, error) {
|
||||
secrets, err := multicluster.ListExistingClusterSecrets(ctx, params.KubeClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var clusters []string
|
||||
for _, secret := range secrets {
|
||||
clusters = append(clusters, secret.Name)
|
||||
}
|
||||
return &ClusterReturns{Returns: Outputs[ClusterParams]{Outputs: ClusterParams{Clusters: clusters}}}, nil
|
||||
}
|
||||
|
||||
// DeployParams is the parameter for deploy
|
||||
type DeployParams = oamprovidertypes.Params[DeployParameter]
|
||||
|
||||
// Deploy deploys the application
|
||||
func Deploy(ctx context.Context, params *DeployParams) (*any, error) {
|
||||
if params.Params.Parallelism <= 0 {
|
||||
return nil, errors.Errorf("parallelism cannot be smaller than 1")
|
||||
}
|
||||
executor := NewDeployWorkflowStepExecutor(params.KubeClient, params.Appfile, params.ComponentApply, params.ComponentHealthCheck, params.WorkloadRender, params.Params)
|
||||
healthy, reason, err := executor.Deploy(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !healthy {
|
||||
params.Action.Wait(reason)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// PoliciesVars is the vars for getting placements from topology policies
|
||||
type PoliciesVars struct {
|
||||
Policies []string `json:"policies"`
|
||||
}
|
||||
|
||||
// PoliciesResult is the result for getting placements from topology policies
|
||||
type PoliciesResult struct {
|
||||
Placements []v1alpha1.PlacementDecision `json:"placements"`
|
||||
}
|
||||
|
||||
// PoliciesParams is the params for getting placements from topology policies
|
||||
type PoliciesParams = oamprovidertypes.Params[PoliciesVars]
|
||||
|
||||
// PoliciesReturns is the return value for getting placements from topology policies
|
||||
type PoliciesReturns = oamprovidertypes.Returns[PoliciesResult]
|
||||
|
||||
// GetPlacementsFromTopologyPolicies gets placements from topology policies
|
||||
func GetPlacementsFromTopologyPolicies(ctx context.Context, params *PoliciesParams) (*PoliciesReturns, error) {
|
||||
policyNames := params.Params.Policies
|
||||
policies, err := selectPolicies(params.Appfile.Policies, policyNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
placements, err := pkgpolicy.GetPlacementsFromTopologyPolicies(ctx, params.KubeClient, params.Appfile.Namespace, policies, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PoliciesReturns{Returns: PoliciesResult{Placements: placements}}, nil
|
||||
}
|
||||
|
||||
//go:embed multicluster.cue
|
||||
var template string
|
||||
|
||||
// GetTemplate returns the cue template.
|
||||
func GetTemplate() string {
|
||||
return template
|
||||
}
|
||||
|
||||
// GetProviders returns the cue providers.
|
||||
func GetProviders() map[string]cuexruntime.ProviderFn {
|
||||
return map[string]cuexruntime.ProviderFn{
|
||||
"list-clusters": oamprovidertypes.GenericProviderFn[any, ClusterReturns](ListClusters),
|
||||
"get-placements-from-topology-policies": oamprovidertypes.GenericProviderFn[PoliciesVars, PoliciesReturns](GetPlacementsFromTopologyPolicies),
|
||||
"deploy": oamprovidertypes.GenericProviderFn[DeployParameter, any](Deploy),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package multicluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
func TestListClusters(t *testing.T) {
|
||||
multicluster.ClusterGatewaySecretNamespace = types.DefaultKubeVelaNS
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build()
|
||||
clusterNames := []string{"cluster-a", "cluster-b"}
|
||||
for _, secretName := range clusterNames {
|
||||
secret := &corev1.Secret{}
|
||||
secret.Name = secretName
|
||||
secret.Namespace = multicluster.ClusterGatewaySecretNamespace
|
||||
secret.Labels = map[string]string{clustercommon.LabelKeyClusterCredentialType: string(clusterv1alpha1.CredentialTypeX509Certificate)}
|
||||
r.NoError(cli.Create(context.Background(), secret))
|
||||
}
|
||||
res, err := ListClusters(ctx, &oamprovidertypes.Params[any]{
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
KubeClient: cli,
|
||||
},
|
||||
})
|
||||
r.NoError(err)
|
||||
r.Equal(clusterNames, res.Returns.Outputs.Clusters)
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package oam
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
workflowerrors "github.com/kubevela/workflow/pkg/errors"
|
||||
|
||||
"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"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ProviderName is provider name for install.
|
||||
ProviderName = "oam"
|
||||
)
|
||||
|
||||
// RenderComponent render component
|
||||
func RenderComponent(ctx context.Context, params *oamprovidertypes.Params[cue.Value]) (cue.Value, error) {
|
||||
v := params.Params
|
||||
parameter := v.LookupPath(cue.ParsePath("$params"))
|
||||
if !parameter.Exists() {
|
||||
return cue.Value{}, workflowerrors.LookUpNotFoundErr("$params")
|
||||
}
|
||||
|
||||
comp, patcher, clusterName, overrideNamespace, err := lookUpCompInfo(parameter)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
workload, traits, err := params.ComponentRender(ctx, *comp, patcher, clusterName, overrideNamespace)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
|
||||
if workload != nil {
|
||||
v = v.FillPath(value.FieldPath("$returns", "output"), workload.Object)
|
||||
}
|
||||
|
||||
for _, trait := range traits {
|
||||
name := trait.GetLabels()[oam.TraitResource]
|
||||
if name != "" {
|
||||
v = v.FillPath(value.FieldPath("$returns", "outputs", name), workload.Object)
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ApplyComponent apply component.
|
||||
func ApplyComponent(ctx context.Context, params *oamprovidertypes.Params[cue.Value]) (cue.Value, error) {
|
||||
v := params.Params
|
||||
parameter := v.LookupPath(cue.ParsePath("$params"))
|
||||
if !parameter.Exists() {
|
||||
return cue.Value{}, workflowerrors.LookUpNotFoundErr("$params")
|
||||
}
|
||||
comp, patcher, clusterName, overrideNamespace, err := lookUpCompInfo(parameter)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
workload, traits, healthy, err := params.ComponentApply(ctx, *comp, patcher, clusterName, overrideNamespace)
|
||||
if err != nil {
|
||||
return cue.Value{}, err
|
||||
}
|
||||
|
||||
if workload != nil {
|
||||
v = v.FillPath(value.FieldPath("$returns", "output"), workload.Object)
|
||||
}
|
||||
|
||||
for _, trait := range traits {
|
||||
name := trait.GetLabels()[oam.TraitResource]
|
||||
if name != "" {
|
||||
v = v.FillPath(value.FieldPath("$returns", "outputs", name), trait)
|
||||
}
|
||||
}
|
||||
|
||||
waitHealthy, err := v.LookupPath(cue.ParsePath("waitHealthy")).Bool()
|
||||
if err != nil {
|
||||
waitHealthy = true
|
||||
}
|
||||
|
||||
if waitHealthy && !healthy {
|
||||
params.Action.Wait("wait healthy")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func lookUpCompInfo(v cue.Value) (*common.ApplicationComponent, *cue.Value, string, string, error) {
|
||||
compSettings := v.LookupPath(cue.ParsePath("value"))
|
||||
if !compSettings.Exists() {
|
||||
return nil, nil, "", "", workflowerrors.LookUpNotFoundErr("value")
|
||||
}
|
||||
comp := &common.ApplicationComponent{}
|
||||
|
||||
if err := value.UnmarshalTo(compSettings, comp); err != nil {
|
||||
return nil, nil, "", "", err
|
||||
}
|
||||
var patcherValue *cue.Value
|
||||
patcher := v.LookupPath(cue.ParsePath("patch"))
|
||||
if patcher.Exists() {
|
||||
patcherValue = &patcher
|
||||
}
|
||||
clusterName, err := v.LookupPath(cue.ParsePath("cluster")).String()
|
||||
if err != nil {
|
||||
clusterName = ""
|
||||
}
|
||||
overrideNamespace, err := v.LookupPath(cue.ParsePath("namespace")).String()
|
||||
if err != nil {
|
||||
overrideNamespace = ""
|
||||
}
|
||||
return comp, patcherValue, clusterName, overrideNamespace, nil
|
||||
}
|
||||
|
||||
// LoadVars is the load provider vars.
|
||||
type LoadVars struct {
|
||||
App string `json:"app,omitempty"`
|
||||
}
|
||||
|
||||
// LoadReturnVars is the load provider return vars.
|
||||
type LoadReturnVars struct {
|
||||
Value any `json:"value"`
|
||||
}
|
||||
|
||||
// LoadParams is the load provider params.
|
||||
type LoadParams = oamprovidertypes.Params[LoadVars]
|
||||
|
||||
// LoadReturns is the load provider returns.
|
||||
type LoadReturns = oamprovidertypes.Returns[LoadReturnVars]
|
||||
|
||||
// LoadComponent load component describe info in application.
|
||||
func LoadComponent(ctx context.Context, params *LoadParams) (*LoadReturns, error) {
|
||||
app := &v1beta1.Application{}
|
||||
cli := params.KubeClient
|
||||
// if specify `app`, use specified application otherwise use default application from provider
|
||||
appSettings := params.Params.App
|
||||
if appSettings == "" {
|
||||
app = params.App
|
||||
} else {
|
||||
if err := cli.Get(ctx, types.NamespacedName{Name: appSettings, Namespace: params.App.Namespace}, app); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
comps := make(map[string]*common.ApplicationComponent, 0)
|
||||
for _, _comp := range app.Spec.Components {
|
||||
comp, err := params.Appfile.LoadDynamicComponent(ctx, cli, _comp.DeepCopy())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comp.Inputs = nil
|
||||
comp.Outputs = nil
|
||||
comps[_comp.Name] = comp
|
||||
}
|
||||
return &LoadReturns{Returns: LoadReturnVars{Value: comps}}, nil
|
||||
}
|
||||
|
||||
// LoadComponentInOrder load component describe info in application output will be a list with order defined in application.
|
||||
func LoadComponentInOrder(ctx context.Context, params *LoadParams) (*LoadReturns, error) {
|
||||
app := &v1beta1.Application{}
|
||||
cli := params.KubeClient
|
||||
// if specify `app`, use specified application otherwise use default application from provider
|
||||
appSettings := params.Params.App
|
||||
if appSettings == "" {
|
||||
app = params.App
|
||||
} else {
|
||||
if err := cli.Get(ctx, types.NamespacedName{Name: appSettings, Namespace: params.App.Namespace}, app); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
comps := make([]common.ApplicationComponent, len(app.Spec.Components))
|
||||
for idx, _comp := range app.Spec.Components {
|
||||
comp, err := params.Appfile.LoadDynamicComponent(ctx, cli, _comp.DeepCopy())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comp.Inputs = nil
|
||||
comp.Outputs = nil
|
||||
comps[idx] = *comp
|
||||
}
|
||||
return &LoadReturns{Returns: LoadReturnVars{Value: comps}}, nil
|
||||
}
|
||||
|
||||
// LoadPolicies load policy describe info in application.
|
||||
func LoadPolicies(_ context.Context, params *LoadParams) (*LoadReturns, error) {
|
||||
app := params.App
|
||||
policies := make(map[string]v1beta1.AppPolicy, 0)
|
||||
for _, po := range app.Spec.Policies {
|
||||
policies[po.Name] = po
|
||||
}
|
||||
return &LoadReturns{Returns: LoadReturnVars{Value: policies}}, nil
|
||||
}
|
||||
|
||||
//go:embed oam.cue
|
||||
var template string
|
||||
|
||||
// GetTemplate returns the cue template.
|
||||
func GetTemplate() string {
|
||||
return template
|
||||
}
|
||||
|
||||
// GetProviders returns the cue providers.
|
||||
func GetProviders() map[string]cuexruntime.ProviderFn {
|
||||
return map[string]cuexruntime.ProviderFn{
|
||||
"component-render": oamprovidertypes.NativeProviderFn(RenderComponent),
|
||||
"component-apply": oamprovidertypes.NativeProviderFn(ApplyComponent),
|
||||
"load": oamprovidertypes.GenericProviderFn[LoadVars, LoadReturns](LoadComponent),
|
||||
"load-comps-in-order": oamprovidertypes.GenericProviderFn[LoadVars, LoadReturns](LoadComponentInOrder),
|
||||
"load-policies": oamprovidertypes.GenericProviderFn[LoadVars, LoadReturns](LoadPolicies),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package oam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/stretchr/testify/require"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/kubevela/workflow/pkg/mock"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
func setupClient(ctx context.Context, t *testing.T) client.Client {
|
||||
r := require.New(t)
|
||||
scheme := runtime.NewScheme()
|
||||
r.NoError(v1beta1.AddToScheme(scheme))
|
||||
r.NoError(appsv1.AddToScheme(scheme))
|
||||
cli := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
return cli
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
act := &mock.Action{}
|
||||
cuectx := cuecontext.New()
|
||||
cli := setupClient(ctx, t)
|
||||
|
||||
v := cuectx.CompileString("")
|
||||
_, err := ApplyComponent(ctx, &oamprovidertypes.Params[cue.Value]{
|
||||
Params: v,
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
KubeClient: cli,
|
||||
},
|
||||
})
|
||||
r.Equal(err.Error(), "failed to lookup value: var(path=$params) not exist")
|
||||
v = cuectx.CompileString(`$params: {
|
||||
value: {
|
||||
name: "test",
|
||||
type: "test",
|
||||
}
|
||||
}`)
|
||||
res, err := ApplyComponent(ctx, &oamprovidertypes.Params[cue.Value]{
|
||||
Params: v,
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
Action: act,
|
||||
ComponentApply: oamprovidertypes.ComponentApply(func(ctx context.Context, comp common.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"name": comp.Name,
|
||||
},
|
||||
},
|
||||
}, []*unstructured.Unstructured{
|
||||
{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "service",
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/resource": "service",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, false, nil
|
||||
}),
|
||||
},
|
||||
})
|
||||
r.NoError(err)
|
||||
output, err := res.LookupPath(cue.ParsePath("$returns.output.metadata.name")).String()
|
||||
r.NoError(err)
|
||||
r.Equal(output, "test")
|
||||
|
||||
outputs, err := res.LookupPath(cue.ParsePath("$returns.outputs.service.metadata.name")).String()
|
||||
r.NoError(err)
|
||||
r.Equal(outputs, "service")
|
||||
|
||||
r.Equal(act.Phase, "Wait")
|
||||
}
|
||||
|
||||
func TestLoadComponent(t *testing.T) {
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
act := &mock.Action{}
|
||||
res, err := LoadComponent(ctx, &oamprovidertypes.Params[LoadVars]{
|
||||
Params: LoadVars{},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
Action: act,
|
||||
App: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "c1",
|
||||
Type: "test",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"image": "busybox"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
r.NoError(err)
|
||||
b, err := json.Marshal(res.Returns.Value)
|
||||
r.NoError(err)
|
||||
r.Equal(string(b), `{"c1":{"name":"c1","type":"test","properties":{"image":"busybox"}}}`)
|
||||
|
||||
app2 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "c2",
|
||||
Type: "test",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"image": "nginx"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
cli := setupClient(ctx, t)
|
||||
err = cli.Create(ctx, app2)
|
||||
r.NoError(err)
|
||||
res, err = LoadComponent(ctx, &oamprovidertypes.Params[LoadVars]{
|
||||
Params: LoadVars{
|
||||
App: "test2",
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
Action: act,
|
||||
App: app2,
|
||||
KubeClient: cli,
|
||||
},
|
||||
})
|
||||
r.NoError(err)
|
||||
b, err = json.Marshal(res.Returns.Value)
|
||||
r.NoError(err)
|
||||
r.Equal(string(b), `{"c2":{"name":"c2","type":"test","properties":{"image":"nginx"}}}`)
|
||||
}
|
||||
|
||||
func TestLoadComponentInOrder(t *testing.T) {
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
act := &mock.Action{}
|
||||
res, err := LoadComponentInOrder(ctx, &oamprovidertypes.Params[LoadVars]{
|
||||
Params: LoadVars{},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
Action: act,
|
||||
App: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "c1",
|
||||
Type: "test",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"image": "busybox"}`)},
|
||||
},
|
||||
{
|
||||
Name: "c2",
|
||||
Type: "test",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"image": "busybox"}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
r.NoError(err)
|
||||
b, err := json.Marshal(res.Returns.Value)
|
||||
r.NoError(err)
|
||||
r.Equal(string(b), `[{"name":"c1","type":"test","properties":{"image":"busybox"}},{"name":"c2","type":"test","properties":{"image":"busybox"}}]`)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// oam.cue
|
||||
|
||||
#ApplyComponent: {
|
||||
#provider: "oam"
|
||||
#do: "component-apply"
|
||||
|
||||
$params: {
|
||||
// +usage=The cluster to use
|
||||
cluster: *"" | string
|
||||
// +usage=The env to use
|
||||
env: *"" | string
|
||||
// +usage=The namespace to apply
|
||||
namespace: *"" | string
|
||||
// +usage=Whether to wait healthy of the applied component
|
||||
waitHealthy: *true | bool
|
||||
// +usage=The value of the component resource
|
||||
value: {...}
|
||||
// +usage=The patcher that will be applied to the resource, you can define the strategy of list merge through comments. Reference doc here: https://kubevela.io/docs/platform-engineers/traits/patch-trait#patch-in-workflow-step
|
||||
patch?: {...}
|
||||
}
|
||||
|
||||
$returns: {
|
||||
output?: {...}
|
||||
outputs?: {...}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#RenderComponent: {
|
||||
#provider: "oam"
|
||||
#do: "component-render"
|
||||
|
||||
$params: {
|
||||
cluster: *"" | string
|
||||
env: *"" | string
|
||||
namespace: *"" | string
|
||||
value: {...}
|
||||
patch?: {...}
|
||||
}
|
||||
|
||||
$returns: {
|
||||
output?: {...}
|
||||
outputs?: {...}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#LoadComponets: {
|
||||
#provider: "oam"
|
||||
#do: "load"
|
||||
|
||||
$params: {
|
||||
// +usage=If specify `app`, use specified application to load its component resources otherwise use current application
|
||||
app?: string
|
||||
}
|
||||
|
||||
$returns: {
|
||||
// +usage=The value of the components will be filled in this field after the action is executed, you can use value[componentName] to refer a specified component
|
||||
value?: {...}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#LoadPolicies: {
|
||||
#provider: "oam"
|
||||
#do: "load-policies"
|
||||
|
||||
$params: {
|
||||
// +usage=If specify `app`, use specified application to load its component resources otherwise use current application
|
||||
app?: string
|
||||
}
|
||||
|
||||
$returns: {
|
||||
// +usage=The value of the components will be filled in this field after the action is executed, you can use value[componentName] to refer a specified component
|
||||
value?: {...}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#LoadComponetsInOrder: {
|
||||
#provider: "oam"
|
||||
#do: "load-comps-in-order"
|
||||
|
||||
$params: {
|
||||
// +usage=If specify `app`, use specified application to load its component resources otherwise use current application
|
||||
app?: string
|
||||
}
|
||||
|
||||
$returns: {
|
||||
// +usage=The value of the components will be filled in this field after the action is executed, you can use value[componentName] to refer a specified component
|
||||
value?: [{...}]
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
// This operator will dispatch all the components in parallel when applying an application.
|
||||
// Currently it works for Addon Observability to speed up the installation. It can also works for other applications, which
|
||||
// needs to skip health check for components.
|
||||
#ApplyApplicationInParallel: {
|
||||
load: #LoadComponetsInOrder
|
||||
components: {
|
||||
for name, c in load.$returns.value {
|
||||
"\(name)": #ApplyComponent & {
|
||||
$params: {
|
||||
value: c
|
||||
waitHealthy: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
Copyright 2021. The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"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/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
)
|
||||
|
||||
// AppCollector collect resource created by application
|
||||
type AppCollector struct {
|
||||
k8sClient client.Client
|
||||
opt Option
|
||||
}
|
||||
|
||||
// NewAppCollector create a app collector
|
||||
func NewAppCollector(cli client.Client, opt Option) *AppCollector {
|
||||
return &AppCollector{
|
||||
k8sClient: cli,
|
||||
opt: opt,
|
||||
}
|
||||
}
|
||||
|
||||
const velaVersionNumberToUpgradeVelaQL = "v1.2.0-rc.1"
|
||||
|
||||
// CollectResourceFromApp collect resources created by application
|
||||
func (c *AppCollector) CollectResourceFromApp(ctx context.Context) ([]Resource, error) {
|
||||
app := new(v1beta1.Application)
|
||||
appKey := client.ObjectKey{Name: c.opt.Name, Namespace: c.opt.Namespace}
|
||||
if err := c.k8sClient.Get(ctx, appKey, app); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var currentVersionNumber string
|
||||
if annotations := app.GetAnnotations(); annotations != nil && annotations[oam.AnnotationKubeVelaVersion] != "" {
|
||||
currentVersionNumber = annotations[oam.AnnotationKubeVelaVersion]
|
||||
}
|
||||
velaVersionToUpgradeVelaQL, _ := version.NewVersion(velaVersionNumberToUpgradeVelaQL)
|
||||
currentVersion, err := version.NewVersion(currentVersionNumber)
|
||||
if err != nil {
|
||||
resources, err := c.FindResourceFromResourceTrackerSpec(ctx, app)
|
||||
if err != nil {
|
||||
return c.FindResourceFromAppliedResourcesField(ctx, app)
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
if velaVersionToUpgradeVelaQL.GreaterThan(currentVersion) {
|
||||
return c.FindResourceFromAppliedResourcesField(ctx, app)
|
||||
}
|
||||
return c.FindResourceFromResourceTrackerSpec(ctx, app)
|
||||
}
|
||||
|
||||
// ListApplicationResources list application applied resources from tracker
|
||||
func (c *AppCollector) ListApplicationResources(ctx context.Context, app *v1beta1.Application) ([]types.AppliedResource, error) {
|
||||
rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, c.k8sClient, app)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "list application resource trackers")
|
||||
}
|
||||
var managedResources []types.AppliedResource
|
||||
existResources := make(map[common.ClusterObjectReference]bool, len(app.Spec.Components))
|
||||
if c.opt.Filter.QueryNewest {
|
||||
historyRTs = nil
|
||||
}
|
||||
for _, rt := range append(historyRTs, rootRT, currentRT) {
|
||||
if rt != nil {
|
||||
for _, managedResource := range rt.Spec.ManagedResources {
|
||||
if isResourceInTargetCluster(c.opt.Filter, managedResource.ClusterObjectReference) &&
|
||||
isResourceInTargetComponent(c.opt.Filter, managedResource.Component) &&
|
||||
(c.opt.WithTree || isResourceMatchKindAndVersion(c.opt.Filter, managedResource.Kind, managedResource.APIVersion)) {
|
||||
if c.opt.WithTree {
|
||||
// If we want to query the tree, we only need to query once for the same resource.
|
||||
if _, exist := existResources[managedResource.ClusterObjectReference]; exist {
|
||||
continue
|
||||
}
|
||||
existResources[managedResource.ClusterObjectReference] = true
|
||||
}
|
||||
managedResources = append(managedResources, types.AppliedResource{
|
||||
Cluster: func() string {
|
||||
if managedResource.Cluster != "" {
|
||||
return managedResource.Cluster
|
||||
}
|
||||
return "local"
|
||||
}(),
|
||||
Kind: managedResource.Kind,
|
||||
Component: managedResource.Component,
|
||||
Trait: managedResource.Trait,
|
||||
Name: managedResource.Name,
|
||||
Namespace: managedResource.Namespace,
|
||||
APIVersion: managedResource.APIVersion,
|
||||
ResourceVersion: managedResource.ResourceVersion,
|
||||
UID: managedResource.UID,
|
||||
PublishVersion: oam.GetPublishVersion(rt),
|
||||
DeployVersion: func() string {
|
||||
obj, _ := managedResource.ToUnstructuredWithData()
|
||||
if obj != nil {
|
||||
return oam.GetDeployVersion(obj)
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
Revision: rt.GetLabels()[oam.LabelAppRevision],
|
||||
Latest: currentRT != nil && rt.Name == currentRT.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !c.opt.WithTree {
|
||||
return managedResources, nil
|
||||
}
|
||||
|
||||
// merge user defined customize rule before every request.
|
||||
err = mergeCustomRules(ctx, c.k8sClient)
|
||||
if err != nil {
|
||||
return managedResources, err
|
||||
}
|
||||
|
||||
filter := func(node types.ResourceTreeNode) bool {
|
||||
return isResourceMatchKindAndVersion(c.opt.Filter, node.Kind, node.APIVersion)
|
||||
}
|
||||
var matchedResources []types.AppliedResource
|
||||
// error from leaf nodes won't block the results
|
||||
for i := range managedResources {
|
||||
resource := managedResources[i]
|
||||
root := types.ResourceTreeNode{
|
||||
Cluster: resource.Cluster,
|
||||
APIVersion: resource.APIVersion,
|
||||
Kind: resource.Kind,
|
||||
Namespace: resource.Namespace,
|
||||
Name: resource.Name,
|
||||
UID: resource.UID,
|
||||
}
|
||||
root.LeafNodes, err = iterateListSubResources(ctx, resource.Cluster, c.k8sClient, root, 1, filter)
|
||||
if err != nil {
|
||||
// if the resource has been deleted, continue access next appliedResource don't break the whole request
|
||||
if kerrors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
klog.Errorf("query leaf node resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
if !filter(root) && len(root.LeafNodes) == 0 {
|
||||
continue
|
||||
}
|
||||
rootObject, err := fetchObjectWithResourceTreeNode(ctx, resource.Cluster, c.k8sClient, root)
|
||||
if err != nil {
|
||||
// if the resource has been deleted, continue access next appliedResource don't break the whole request
|
||||
if kerrors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
klog.Errorf("fetch object for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
rootStatus, err := CheckResourceStatus(*rootObject)
|
||||
if err != nil {
|
||||
klog.Errorf("check status for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
root.HealthStatus = *rootStatus
|
||||
addInfo, err := additionalInfo(*rootObject)
|
||||
if err != nil {
|
||||
klog.Errorf("check additionalInfo for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
root.AdditionalInfo = addInfo
|
||||
root.CreationTimestamp = rootObject.GetCreationTimestamp().Time
|
||||
if !rootObject.GetDeletionTimestamp().IsZero() {
|
||||
root.DeletionTimestamp = rootObject.GetDeletionTimestamp().Time
|
||||
}
|
||||
root.Object = rootObject
|
||||
resource.ResourceTree = &root
|
||||
matchedResources = append(matchedResources, resource)
|
||||
}
|
||||
return matchedResources, nil
|
||||
}
|
||||
|
||||
// FindResourceFromResourceTrackerSpec find resources from ResourceTracker spec
|
||||
func (c *AppCollector) FindResourceFromResourceTrackerSpec(ctx context.Context, app *v1beta1.Application) ([]Resource, error) {
|
||||
rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, c.k8sClient, app)
|
||||
if err != nil {
|
||||
klog.Errorf("query the resourcetrackers failure %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
var resources = []Resource{}
|
||||
existResources := make(map[common.ClusterObjectReference]bool, len(app.Spec.Components))
|
||||
for _, rt := range append([]*v1beta1.ResourceTracker{rootRT, currentRT}, historyRTs...) {
|
||||
if rt != nil {
|
||||
for _, managedResource := range rt.Spec.ManagedResources {
|
||||
if isResourceInTargetCluster(c.opt.Filter, managedResource.ClusterObjectReference) &&
|
||||
isResourceInTargetComponent(c.opt.Filter, managedResource.Component) &&
|
||||
isResourceMatchKindAndVersion(c.opt.Filter, managedResource.Kind, managedResource.APIVersion) {
|
||||
if _, exist := existResources[managedResource.ClusterObjectReference]; exist {
|
||||
continue
|
||||
}
|
||||
existResources[managedResource.ClusterObjectReference] = true
|
||||
obj, err := managedResource.ToUnstructuredWithData()
|
||||
if err != nil || c.opt.WithStatus {
|
||||
// For the application with apply once policy, there is no data in RT.
|
||||
// IF the WithStatus is true, get the object from cluster
|
||||
_, obj, err = getObjectCreatedByComponent(ctx, c.k8sClient, managedResource.ObjectReference, managedResource.Cluster)
|
||||
if err != nil {
|
||||
klog.Errorf("get obj from the cluster failure %s", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
clusterName := managedResource.Cluster
|
||||
if clusterName == "" {
|
||||
clusterName = multicluster.ClusterLocalName
|
||||
}
|
||||
resources = append(resources, Resource{
|
||||
Cluster: clusterName,
|
||||
Revision: oam.GetPublishVersion(rt),
|
||||
Component: managedResource.Component,
|
||||
Object: obj,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// FindResourceFromAppliedResourcesField find resources from AppliedResources field
|
||||
func (c *AppCollector) FindResourceFromAppliedResourcesField(ctx context.Context, app *v1beta1.Application) ([]Resource, error) {
|
||||
resources := make([]Resource, 0, len(app.Spec.Components))
|
||||
for _, res := range app.Status.AppliedResources {
|
||||
if !isResourceInTargetCluster(c.opt.Filter, res) {
|
||||
continue
|
||||
}
|
||||
if !isResourceMatchKindAndVersion(c.opt.Filter, res.APIVersion, res.Kind) {
|
||||
continue
|
||||
}
|
||||
compName, obj, err := getObjectCreatedByComponent(ctx, c.k8sClient, res.ObjectReference, res.Cluster)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(compName) != 0 && isResourceInTargetComponent(c.opt.Filter, compName) {
|
||||
resources = append(resources, Resource{
|
||||
Component: compName,
|
||||
Revision: obj.GetLabels()[oam.LabelAppRevision],
|
||||
Cluster: res.Cluster,
|
||||
Object: obj,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
return nil, errors.Errorf("fail to find resources created by application: %v", c.opt.Name)
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// getObjectCreatedByComponent get k8s obj created by components
|
||||
func getObjectCreatedByComponent(ctx context.Context, cli client.Client, objRef corev1.ObjectReference, cluster string) (string, *unstructured.Unstructured, error) {
|
||||
ctx = multicluster.ContextWithClusterName(ctx, cluster)
|
||||
obj := new(unstructured.Unstructured)
|
||||
obj.SetGroupVersionKind(objRef.GroupVersionKind())
|
||||
obj.SetNamespace(objRef.Namespace)
|
||||
obj.SetName(objRef.Name)
|
||||
if err := cli.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return "", nil, nil
|
||||
}
|
||||
return "", nil, err
|
||||
}
|
||||
componentName := obj.GetLabels()[oam.LabelAppComponent]
|
||||
return componentName, obj, nil
|
||||
}
|
||||
|
||||
func getEventFieldSelector(obj *unstructured.Unstructured) fields.Selector {
|
||||
field := fields.Set{}
|
||||
field["involvedObject.name"] = obj.GetName()
|
||||
field["involvedObject.namespace"] = obj.GetNamespace()
|
||||
field["involvedObject.kind"] = obj.GetObjectKind().GroupVersionKind().Kind
|
||||
field["involvedObject.uid"] = string(obj.GetUID())
|
||||
return field.AsSelector()
|
||||
}
|
||||
|
||||
func isResourceInTargetCluster(opt FilterOption, resource common.ClusterObjectReference) bool {
|
||||
if opt.Cluster == "" && opt.ClusterNamespace == "" {
|
||||
return true
|
||||
}
|
||||
if (opt.Cluster == resource.Cluster || (opt.Cluster == "local" && resource.Cluster == "")) &&
|
||||
(opt.ClusterNamespace == resource.ObjectReference.Namespace || opt.ClusterNamespace == "") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isResourceInTargetComponent(opt FilterOption, componentName string) bool {
|
||||
if len(opt.Components) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, component := range opt.Components {
|
||||
if component == componentName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isResourceMatchKindAndVersion(opt FilterOption, kind, version string) bool {
|
||||
if opt.APIVersion != "" && opt.APIVersion != version {
|
||||
return false
|
||||
}
|
||||
if opt.Kind != "" && opt.Kind != kind {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kubevela/pkg/util/slices"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
networkv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
apis "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
)
|
||||
|
||||
// CollectServiceEndpoints generator service endpoints is available for common component type,
|
||||
// such as webservice or helm
|
||||
// it can not support the cloud service component currently
|
||||
func CollectServiceEndpoints(ctx context.Context, params *ListParams) (*ListReturns[querytypes.ServiceEndpoint], error) {
|
||||
opt := params.Params.App
|
||||
cli := params.KubeClient
|
||||
app := new(v1beta1.Application)
|
||||
err := findResource(ctx, cli, app, opt.Name, opt.Namespace, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query app failure %w", err)
|
||||
}
|
||||
serviceEndpoints := make([]querytypes.ServiceEndpoint, 0)
|
||||
var clusterGatewayNodeIP = make(map[string]string)
|
||||
collector := NewAppCollector(cli, opt)
|
||||
resources, err := collector.ListApplicationResources(ctx, app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, resource := range resources {
|
||||
cluster := resources[i].Cluster
|
||||
cachedSelectorNodeIP := func() string {
|
||||
if ip, exist := clusterGatewayNodeIP[cluster]; exist {
|
||||
return ip
|
||||
}
|
||||
ip := selectorNodeIP(ctx, cluster, cli)
|
||||
if ip != "" {
|
||||
clusterGatewayNodeIP[cluster] = ip
|
||||
}
|
||||
return ip
|
||||
}
|
||||
if resource.ResourceTree != nil {
|
||||
serviceEndpoints = append(serviceEndpoints, getEndpointFromNode(ctx, cli, resource.ResourceTree, resource.Component, cachedSelectorNodeIP)...)
|
||||
} else {
|
||||
serviceEndpoints = append(serviceEndpoints, getServiceEndpoints(ctx, cli, resource.GroupVersionKind(), resource.Name, resource.Namespace, resource.Cluster, resource.Component, cachedSelectorNodeIP)...)
|
||||
}
|
||||
|
||||
}
|
||||
return &ListReturns[querytypes.ServiceEndpoint]{Returns: ListReturnVars[querytypes.ServiceEndpoint]{List: serviceEndpoints}}, nil
|
||||
}
|
||||
|
||||
func getEndpointFromNode(ctx context.Context, cli client.Client, node *querytypes.ResourceTreeNode, component string, cachedSelectorNodeIP func() string) []querytypes.ServiceEndpoint {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
var serviceEndpoints []querytypes.ServiceEndpoint
|
||||
serviceEndpoints = append(serviceEndpoints, getServiceEndpoints(ctx, cli, node.GroupVersionKind(), node.Name, node.Namespace, node.Cluster, component, cachedSelectorNodeIP)...)
|
||||
for _, child := range node.LeafNodes {
|
||||
serviceEndpoints = append(serviceEndpoints, getEndpointFromNode(ctx, cli, child, component, cachedSelectorNodeIP)...)
|
||||
}
|
||||
return serviceEndpoints
|
||||
}
|
||||
|
||||
func getServiceEndpoints(ctx context.Context, cli client.Client, gvk schema.GroupVersionKind, name, namespace, cluster, component string, cachedSelectorNodeIP func() string) []querytypes.ServiceEndpoint {
|
||||
var serviceEndpoints []querytypes.ServiceEndpoint
|
||||
switch gvk.Kind {
|
||||
case "Ingress":
|
||||
if gvk.Group == networkv1beta1.GroupName && (gvk.Version == "v1beta1" || gvk.Version == "v1") {
|
||||
var ingress v1.Ingress
|
||||
ingress.SetGroupVersionKind(gvk)
|
||||
if err := findResource(ctx, cli, &ingress, name, namespace, cluster); err != nil {
|
||||
klog.Error(err, fmt.Sprintf("find v1 Ingress %s/%s from cluster %s failure", name, namespace, cluster))
|
||||
return nil
|
||||
}
|
||||
serviceEndpoints = append(serviceEndpoints, generatorFromIngress(ingress, cluster, component)...)
|
||||
} else {
|
||||
klog.Warning("not support ingress version", "version", gvk)
|
||||
}
|
||||
case "Service":
|
||||
var service corev1.Service
|
||||
service.SetGroupVersionKind(gvk)
|
||||
if err := findResource(ctx, cli, &service, name, namespace, cluster); err != nil {
|
||||
klog.Error(err, fmt.Sprintf("find v1 Service %s/%s from cluster %s failure", name, namespace, cluster))
|
||||
return nil
|
||||
}
|
||||
serviceEndpoints = append(serviceEndpoints, generatorFromService(service, cachedSelectorNodeIP, cluster, component, "")...)
|
||||
case "SeldonDeployment":
|
||||
obj := new(unstructured.Unstructured)
|
||||
obj.SetGroupVersionKind(gvk)
|
||||
if err := findResource(ctx, cli, obj, name, namespace, cluster); err != nil {
|
||||
klog.Error(err, fmt.Sprintf("find v1 Seldon Deployment %s/%s from cluster %s failure", name, namespace, cluster))
|
||||
return nil
|
||||
}
|
||||
anno := obj.GetAnnotations()
|
||||
serviceName := "ambassador"
|
||||
serviceNS := apis.DefaultKubeVelaNS
|
||||
if anno != nil {
|
||||
if anno[annoAmbassadorServiceName] != "" {
|
||||
serviceName = anno[annoAmbassadorServiceName]
|
||||
}
|
||||
if anno[annoAmbassadorServiceNamespace] != "" {
|
||||
serviceNS = anno[annoAmbassadorServiceNamespace]
|
||||
}
|
||||
}
|
||||
var service corev1.Service
|
||||
if err := findResource(ctx, cli, &service, serviceName, serviceNS, cluster); err != nil {
|
||||
klog.Error(err, fmt.Sprintf("find v1 Service %s/%s from cluster %s failure", serviceName, serviceNS, cluster))
|
||||
return nil
|
||||
}
|
||||
serviceEndpoints = append(serviceEndpoints, generatorFromService(service, cachedSelectorNodeIP, cluster, component, fmt.Sprintf("/seldon/%s/%s", namespace, name))...)
|
||||
case "HTTPRoute":
|
||||
var route gatewayv1beta1.HTTPRoute
|
||||
route.SetGroupVersionKind(gvk)
|
||||
if err := findResource(ctx, cli, &route, name, namespace, cluster); err != nil {
|
||||
klog.Error(err, fmt.Sprintf("find HTTPRoute %s/%s from cluster %s failure", name, namespace, cluster))
|
||||
return nil
|
||||
}
|
||||
serviceEndpoints = append(serviceEndpoints, generatorFromHTTPRoute(ctx, cli, route, cluster, component)...)
|
||||
}
|
||||
return serviceEndpoints
|
||||
}
|
||||
|
||||
func findResource(ctx context.Context, cli client.Client, obj client.Object, name, namespace, cluster string) error {
|
||||
obj.SetNamespace(namespace)
|
||||
obj.SetName(name)
|
||||
gctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
if err := cli.Get(multicluster.ContextWithClusterName(gctx, cluster),
|
||||
client.ObjectKeyFromObject(obj), obj); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatorFromService(service corev1.Service, selectorNodeIP func() string, cluster, component, path string) []querytypes.ServiceEndpoint {
|
||||
var serviceEndpoints []querytypes.ServiceEndpoint
|
||||
|
||||
var objRef = corev1.ObjectReference{
|
||||
Kind: "Service",
|
||||
Namespace: service.ObjectMeta.Namespace,
|
||||
Name: service.ObjectMeta.Name,
|
||||
UID: service.UID,
|
||||
APIVersion: service.APIVersion,
|
||||
ResourceVersion: service.ResourceVersion,
|
||||
}
|
||||
|
||||
formatEndpoint := func(host, appProtocol string, portName string, portProtocol corev1.Protocol, portNum int32, inner bool) querytypes.ServiceEndpoint {
|
||||
return querytypes.ServiceEndpoint{
|
||||
Endpoint: querytypes.Endpoint{
|
||||
Protocol: portProtocol,
|
||||
AppProtocol: &appProtocol,
|
||||
Host: host,
|
||||
Port: int(portNum),
|
||||
PortName: portName,
|
||||
Path: path,
|
||||
Inner: inner,
|
||||
},
|
||||
Ref: objRef,
|
||||
Cluster: cluster,
|
||||
Component: component,
|
||||
}
|
||||
}
|
||||
switch service.Spec.Type {
|
||||
case corev1.ServiceTypeLoadBalancer:
|
||||
for _, port := range service.Spec.Ports {
|
||||
appp := judgeAppProtocol(port.Port)
|
||||
for _, ingress := range service.Status.LoadBalancer.Ingress {
|
||||
if ingress.Hostname != "" {
|
||||
serviceEndpoints = append(serviceEndpoints, formatEndpoint(ingress.Hostname, appp, port.Name, port.Protocol, port.Port, false))
|
||||
}
|
||||
if ingress.IP != "" {
|
||||
serviceEndpoints = append(serviceEndpoints, formatEndpoint(ingress.IP, appp, port.Name, port.Protocol, port.Port, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
case corev1.ServiceTypeNodePort:
|
||||
for _, port := range service.Spec.Ports {
|
||||
appp := judgeAppProtocol(port.Port)
|
||||
serviceEndpoints = append(serviceEndpoints, formatEndpoint(selectorNodeIP(), appp, port.Name, port.Protocol, port.NodePort, false))
|
||||
}
|
||||
case corev1.ServiceTypeClusterIP, corev1.ServiceTypeExternalName:
|
||||
for _, port := range service.Spec.Ports {
|
||||
appp := judgeAppProtocol(port.Port)
|
||||
serviceEndpoints = append(serviceEndpoints, formatEndpoint(fmt.Sprintf("%s.%s", service.Name, service.Namespace), appp, port.Name, port.Protocol, port.Port, true))
|
||||
}
|
||||
}
|
||||
return serviceEndpoints
|
||||
}
|
||||
|
||||
func generatorFromIngress(ingress v1.Ingress, cluster, component string) (serviceEndpoints []querytypes.ServiceEndpoint) {
|
||||
getAppProtocol := func(host string) string {
|
||||
if len(ingress.Spec.TLS) > 0 {
|
||||
for _, tls := range ingress.Spec.TLS {
|
||||
if len(tls.Hosts) > 0 && slices.Contains(tls.Hosts, host) {
|
||||
return querytypes.HTTPS
|
||||
}
|
||||
if len(tls.Hosts) == 0 {
|
||||
return querytypes.HTTPS
|
||||
}
|
||||
}
|
||||
}
|
||||
return querytypes.HTTP
|
||||
}
|
||||
// It depends on the Ingress Controller
|
||||
getEndpointPort := func(appProtocol string) int {
|
||||
if appProtocol == querytypes.HTTPS {
|
||||
if port, err := strconv.Atoi(ingress.Annotations[apis.AnnoIngressControllerHTTPSPort]); port > 0 && err == nil {
|
||||
return port
|
||||
}
|
||||
return 443
|
||||
}
|
||||
if port, err := strconv.Atoi(ingress.Annotations[apis.AnnoIngressControllerHTTPPort]); port > 0 && err == nil {
|
||||
return port
|
||||
}
|
||||
return 80
|
||||
}
|
||||
|
||||
// The host in rule maybe empty, means access the application by the Gateway Host(IP)
|
||||
getHost := func(host string) string {
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
return ingress.Annotations[apis.AnnoIngressControllerHost]
|
||||
}
|
||||
|
||||
for _, rule := range ingress.Spec.Rules {
|
||||
var appProtocol = getAppProtocol(rule.Host)
|
||||
var appPort = getEndpointPort(appProtocol)
|
||||
if rule.HTTP != nil {
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
serviceEndpoints = append(serviceEndpoints, querytypes.ServiceEndpoint{
|
||||
Endpoint: querytypes.Endpoint{
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
AppProtocol: &appProtocol,
|
||||
Host: getHost(rule.Host),
|
||||
Path: path.Path,
|
||||
Port: appPort,
|
||||
},
|
||||
Ref: corev1.ObjectReference{
|
||||
Kind: "Ingress",
|
||||
Namespace: ingress.ObjectMeta.Namespace,
|
||||
Name: ingress.ObjectMeta.Name,
|
||||
UID: ingress.UID,
|
||||
APIVersion: ingress.APIVersion,
|
||||
ResourceVersion: ingress.ResourceVersion,
|
||||
},
|
||||
Cluster: cluster,
|
||||
Component: component,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return serviceEndpoints
|
||||
}
|
||||
|
||||
func getGatewayPortAndProtocol(ctx context.Context, cli client.Client, defaultNamespace, cluster string, parents []gatewayv1beta1.ParentReference) (string, int) {
|
||||
for _, parent := range parents {
|
||||
if parent.Kind != nil && *parent.Kind == "Gateway" {
|
||||
var gateway gatewayv1beta1.Gateway
|
||||
namespace := defaultNamespace
|
||||
if parent.Namespace != nil {
|
||||
namespace = string(*parent.Namespace)
|
||||
}
|
||||
if err := findResource(ctx, cli, &gateway, string(parent.Name), namespace, cluster); err != nil {
|
||||
klog.Errorf("query the Gateway %s/%s/%s failure %s", cluster, namespace, string(parent.Name), err.Error())
|
||||
}
|
||||
var listener *gatewayv1beta1.Listener
|
||||
if parent.SectionName != nil {
|
||||
for i, lis := range gateway.Spec.Listeners {
|
||||
if lis.Name == *parent.SectionName {
|
||||
listener = &gateway.Spec.Listeners[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if len(gateway.Spec.Listeners) > 0 {
|
||||
listener = &gateway.Spec.Listeners[0]
|
||||
}
|
||||
if listener != nil {
|
||||
var protocol = querytypes.HTTP
|
||||
if listener.Protocol == gatewayv1beta1.HTTPSProtocolType {
|
||||
protocol = querytypes.HTTPS
|
||||
}
|
||||
var port = int(listener.Port)
|
||||
// The gateway listener port may not be the externally exposed port.
|
||||
// For example, the traefik addon has a default port mapping configuration of 8443->443 8000->80
|
||||
// So users could set the `ports-mapping` annotation.
|
||||
if mapping := gateway.Annotations["ports-mapping"]; mapping != "" {
|
||||
for _, portItem := range strings.Split(mapping, ",") {
|
||||
if portMap := strings.Split(portItem, ":"); len(portMap) == 2 {
|
||||
if portMap[0] == fmt.Sprintf("%d", listener.Port) {
|
||||
newPort, err := strconv.Atoi(portMap[1])
|
||||
if err == nil {
|
||||
port = newPort
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return protocol, port
|
||||
}
|
||||
}
|
||||
}
|
||||
return querytypes.HTTP, 80
|
||||
}
|
||||
|
||||
func generatorFromHTTPRoute(ctx context.Context, cli client.Client, route gatewayv1beta1.HTTPRoute, cluster, component string) []querytypes.ServiceEndpoint {
|
||||
existPath := make(map[string]bool)
|
||||
var serviceEndpoints []querytypes.ServiceEndpoint
|
||||
for _, rule := range route.Spec.Rules {
|
||||
for _, host := range route.Spec.Hostnames {
|
||||
appProtocol, appPort := getGatewayPortAndProtocol(ctx, cli, route.Namespace, cluster, route.Spec.ParentRefs)
|
||||
for _, match := range rule.Matches {
|
||||
path := ""
|
||||
if match.Path != nil && (match.Path.Type == nil || string(*match.Path.Type) == string(gatewayv1beta1.PathMatchPathPrefix)) {
|
||||
path = *match.Path.Value
|
||||
}
|
||||
if !existPath[path] {
|
||||
existPath[path] = true
|
||||
serviceEndpoints = append(serviceEndpoints, querytypes.ServiceEndpoint{
|
||||
Endpoint: querytypes.Endpoint{
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
AppProtocol: &appProtocol,
|
||||
Host: string(host),
|
||||
Path: path,
|
||||
Port: appPort,
|
||||
},
|
||||
Ref: corev1.ObjectReference{
|
||||
Kind: route.Kind,
|
||||
Namespace: route.ObjectMeta.Namespace,
|
||||
Name: route.ObjectMeta.Name,
|
||||
UID: route.UID,
|
||||
APIVersion: route.APIVersion,
|
||||
ResourceVersion: route.ResourceVersion,
|
||||
},
|
||||
Cluster: cluster,
|
||||
Component: component,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return serviceEndpoints
|
||||
}
|
||||
|
||||
func selectorNodeIP(ctx context.Context, clusterName string, client client.Client) string {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
var nodes corev1.NodeList
|
||||
if err := client.List(multicluster.ContextWithClusterName(ctx, clusterName), &nodes); err != nil {
|
||||
return ""
|
||||
}
|
||||
if len(nodes.Items) == 0 {
|
||||
return ""
|
||||
}
|
||||
return selectGatewayIP(nodes.Items)
|
||||
}
|
||||
|
||||
// judgeAppProtocol RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
func judgeAppProtocol(port int32) string {
|
||||
switch port {
|
||||
case 80, 8080:
|
||||
return querytypes.HTTP
|
||||
case 443:
|
||||
return querytypes.HTTPS
|
||||
case 3306:
|
||||
return querytypes.Mysql
|
||||
case 6379:
|
||||
return querytypes.Redis
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// selectGatewayIP will choose one gateway IP from all nodes, it will pick up external IP first. If there isn't any, it will pick the first node's internal IP.
|
||||
func selectGatewayIP(nodes []corev1.Node) string {
|
||||
var gatewayNode *corev1.Node
|
||||
var workerNodes []corev1.Node
|
||||
for i, node := range nodes {
|
||||
if _, exist := node.Labels[apis.LabelNodeRoleGateway]; exist {
|
||||
gatewayNode = &nodes[i]
|
||||
break
|
||||
} else if _, exist := node.Labels[apis.LabelNodeRoleWorker]; exist {
|
||||
workerNodes = append(workerNodes, nodes[i])
|
||||
}
|
||||
}
|
||||
var candidates = nodes
|
||||
if gatewayNode != nil {
|
||||
candidates = []corev1.Node{*gatewayNode}
|
||||
} else if len(workerNodes) > 0 {
|
||||
candidates = workerNodes
|
||||
}
|
||||
|
||||
if len(candidates) == 0 {
|
||||
return ""
|
||||
}
|
||||
var addressMaps = make([]map[corev1.NodeAddressType]string, 0)
|
||||
for _, node := range candidates {
|
||||
var addressMap = make(map[corev1.NodeAddressType]string)
|
||||
for _, address := range node.Status.Addresses {
|
||||
addressMap[address.Type] = address.Address
|
||||
}
|
||||
// first get external ip
|
||||
if ip, exist := addressMap[corev1.NodeExternalIP]; exist {
|
||||
return ip
|
||||
}
|
||||
addressMaps = append(addressMaps, addressMap)
|
||||
}
|
||||
return addressMaps[0][corev1.NodeInternalIP]
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
var _ = Describe("Test query endpoints", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
})
|
||||
|
||||
Context("Test Generate Endpoints", func() {
|
||||
It("Test endpoints with additional rules", func() {
|
||||
err := k8sClient.Create(context.TODO(), &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vela-system",
|
||||
},
|
||||
})
|
||||
Expect(err).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
sts := common.AppStatus{
|
||||
AppliedResources: []common.ClusterObjectReference{
|
||||
{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
APIVersion: "machinelearning.seldon.io/v1",
|
||||
Kind: "SeldonDeployment",
|
||||
Namespace: "default",
|
||||
Name: "sdep2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
testApp := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "endpoints-app-2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "endpoints-test-2",
|
||||
Type: "webservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: sts,
|
||||
}
|
||||
Expect(k8sClient.Create(context.TODO(), testApp)).Should(BeNil())
|
||||
var gtapp v1beta1.Application
|
||||
Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: "endpoints-app-2", Namespace: "default"}, >app)).Should(BeNil())
|
||||
gtapp.Status = sts
|
||||
Expect(k8sClient.Status().Update(ctx, >app)).Should(BeNil())
|
||||
var mr []v1beta1.ManagedResource
|
||||
for _, ar := range sts.AppliedResources {
|
||||
smr := v1beta1.ManagedResource{
|
||||
ClusterObjectReference: ar,
|
||||
}
|
||||
smr.Component = "endpoints-test-2"
|
||||
mr = append(mr, smr)
|
||||
}
|
||||
rt := &v1beta1.ResourceTracker{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "endpoints-app-2",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
oam.LabelAppName: testApp.Name,
|
||||
oam.LabelAppNamespace: testApp.Namespace,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
Type: v1beta1.ResourceTrackerTypeRoot,
|
||||
ManagedResources: mr,
|
||||
},
|
||||
}
|
||||
err = k8sClient.Create(context.TODO(), rt)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
By("Prepare configmap for relationship")
|
||||
|
||||
err = k8sClient.Create(context.TODO(), &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "rule-for-seldon-test",
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
oam.LabelResourceRules: "true",
|
||||
oam.LabelResourceRuleFormat: oam.ResourceTopologyFormatJSON,
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"rules": `[
|
||||
{
|
||||
"parentResourceType": {
|
||||
"group": "machinelearning.seldon.io",
|
||||
"kind": "SeldonDeployment"
|
||||
},
|
||||
"childrenResourceType": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service"
|
||||
}
|
||||
]
|
||||
}
|
||||
]`,
|
||||
},
|
||||
})
|
||||
Expect(err).Should(BeNil())
|
||||
testServiceList := []map[string]interface{}{
|
||||
{
|
||||
"name": "clusterip-2",
|
||||
"ports": []corev1.ServicePort{
|
||||
{Port: 80, TargetPort: intstr.FromInt(80), Name: "80port"},
|
||||
{Port: 81, TargetPort: intstr.FromInt(81), Name: "81port"},
|
||||
},
|
||||
"type": corev1.ServiceTypeClusterIP,
|
||||
},
|
||||
{
|
||||
"name": "load-balancer",
|
||||
"ports": []corev1.ServicePort{
|
||||
{Port: 8080, TargetPort: intstr.FromInt(8080), Name: "8080port", NodePort: 30020},
|
||||
},
|
||||
"type": corev1.ServiceTypeLoadBalancer,
|
||||
"status": corev1.ServiceStatus{
|
||||
LoadBalancer: corev1.LoadBalancerStatus{
|
||||
Ingress: []corev1.LoadBalancerIngress{
|
||||
{
|
||||
IP: "2.2.2.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "seldon-ambassador-2",
|
||||
"ports": []corev1.ServicePort{
|
||||
{Port: 80, TargetPort: intstr.FromInt(80), Name: "80port"},
|
||||
},
|
||||
"type": corev1.ServiceTypeLoadBalancer,
|
||||
"status": corev1.ServiceStatus{
|
||||
LoadBalancer: corev1.LoadBalancerStatus{
|
||||
Ingress: []corev1.LoadBalancerIngress{
|
||||
{
|
||||
IP: "1.1.1.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
abgvk := schema.GroupVersionKind{
|
||||
Group: "machinelearning.seldon.io",
|
||||
Version: "v1",
|
||||
Kind: "SeldonDeployment",
|
||||
}
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetName("sdep2")
|
||||
obj.SetNamespace("default")
|
||||
obj.SetAnnotations(map[string]string{
|
||||
annoAmbassadorServiceName: "seldon-ambassador-2",
|
||||
annoAmbassadorServiceNamespace: "default",
|
||||
})
|
||||
obj.SetGroupVersionKind(abgvk)
|
||||
err = k8sClient.Create(context.TODO(), obj)
|
||||
Expect(err).Should(BeNil())
|
||||
abobj := &unstructured.Unstructured{}
|
||||
abobj.SetGroupVersionKind(abgvk)
|
||||
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: "sdep2", Namespace: "default"}, abobj)).Should(BeNil())
|
||||
|
||||
for _, s := range testServiceList {
|
||||
ns := "default"
|
||||
if s["namespace"] != nil {
|
||||
ns = s["namespace"].(string)
|
||||
}
|
||||
service := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: s["name"].(string),
|
||||
Namespace: ns,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{APIVersion: "machinelearning.seldon.io/v1", Kind: "SeldonDeployment", Name: "sdep2", UID: abobj.GetUID()},
|
||||
},
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: s["ports"].([]corev1.ServicePort),
|
||||
Type: s["type"].(corev1.ServiceType),
|
||||
},
|
||||
}
|
||||
|
||||
if s["labels"] != nil {
|
||||
service.Labels = s["labels"].(map[string]string)
|
||||
}
|
||||
err := k8sClient.Create(context.TODO(), service)
|
||||
Expect(err).Should(BeNil())
|
||||
if s["status"] != nil {
|
||||
service.Status = s["status"].(corev1.ServiceStatus)
|
||||
err := k8sClient.Status().Update(context.TODO(), service)
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
}
|
||||
|
||||
params := &ListParams{
|
||||
Params: ListVars{
|
||||
App: Option{
|
||||
Name: "endpoints-app-2",
|
||||
Namespace: "default",
|
||||
Filter: FilterOption{
|
||||
Cluster: "",
|
||||
ClusterNamespace: "default",
|
||||
},
|
||||
WithTree: true,
|
||||
},
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
KubeClient: k8sClient,
|
||||
},
|
||||
}
|
||||
res, err := CollectServiceEndpoints(context.Background(), params)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
urls := []string{
|
||||
"http://1.1.1.1/seldon/default/sdep2",
|
||||
"http://clusterip-2.default",
|
||||
"clusterip-2.default:81",
|
||||
"http://2.2.2.2:8080",
|
||||
"http://1.1.1.1",
|
||||
}
|
||||
for i, e := range (*res).Returns.List {
|
||||
Expect(urls[i]).Should(Equal(e.String()))
|
||||
}
|
||||
})
|
||||
|
||||
It("Test select gateway IP", func() {
|
||||
masterNode := corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-1",
|
||||
Labels: map[string]string{
|
||||
"node-role.kubernetes.io/master": "true",
|
||||
},
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{
|
||||
Type: corev1.NodeInternalIP,
|
||||
Address: "node1-internal-ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
workerNode1 := corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-2",
|
||||
Labels: map[string]string{
|
||||
"node-role.kubernetes.io/worker": "true",
|
||||
},
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{
|
||||
Type: corev1.NodeInternalIP,
|
||||
Address: "node2-internal-ip",
|
||||
},
|
||||
{
|
||||
Type: corev1.NodeExternalIP,
|
||||
Address: "node2-external-ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
workerNode2 := corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-3",
|
||||
Labels: map[string]string{
|
||||
"node-role.kubernetes.io/worker": "true",
|
||||
},
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{
|
||||
Type: corev1.NodeInternalIP,
|
||||
Address: "node3-internal-ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
gatewayNode := corev1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-4",
|
||||
Labels: map[string]string{
|
||||
"node-role.kubernetes.io/gateway": "true",
|
||||
},
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{
|
||||
Type: corev1.NodeInternalIP,
|
||||
Address: "node4-internal-ip",
|
||||
},
|
||||
{
|
||||
Type: corev1.NodeExternalIP,
|
||||
Address: "node4-external-ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
testCase := []struct {
|
||||
note string
|
||||
nodes []corev1.Node
|
||||
wantIP string
|
||||
}{
|
||||
{
|
||||
note: "only master node",
|
||||
nodes: []corev1.Node{masterNode},
|
||||
wantIP: "node1-internal-ip",
|
||||
},
|
||||
{
|
||||
note: "with worker node, select external ip first",
|
||||
nodes: []corev1.Node{masterNode, workerNode1},
|
||||
wantIP: "node2-external-ip",
|
||||
},
|
||||
{
|
||||
note: "with worker node, select worker's internal ip",
|
||||
nodes: []corev1.Node{masterNode, workerNode2},
|
||||
wantIP: "node3-internal-ip",
|
||||
},
|
||||
{
|
||||
note: "with gateway node, gateway node first",
|
||||
nodes: []corev1.Node{masterNode, workerNode1, workerNode1, gatewayNode},
|
||||
wantIP: "node4-external-ip",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCase {
|
||||
By(tc.note)
|
||||
ip := selectGatewayIP(tc.nodes)
|
||||
Expect(ip).Should(Equal(tc.wantIP))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Test get ingress endpoint", func() {
|
||||
It("Test get ingress endpoint with different apiVersion", func() {
|
||||
ingress1 := v1.Ingress{}
|
||||
Expect(yaml.Unmarshal([]byte(ingressYaml1), &ingress1)).Should(BeNil())
|
||||
|
||||
err := k8sClient.Create(ctx, &ingress1)
|
||||
Expect(err).Should(BeNil())
|
||||
gvk := schema.GroupVersionKind{Group: "networking.k8s.io", Version: "v1", Kind: "Ingress"}
|
||||
Eventually(func() error {
|
||||
eps := getServiceEndpoints(ctx, k8sClient, gvk, ingress1.Name, ingress1.Namespace, "", "", nil)
|
||||
if len(eps) != 1 {
|
||||
return fmt.Errorf("result length missmatch")
|
||||
}
|
||||
return nil
|
||||
}, 2*time.Second, 500*time.Millisecond).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
var ingressYaml1 = `
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-1
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /testpath
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: test
|
||||
port:
|
||||
number: 80
|
||||
`
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
Copyright 2021. The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
apimachinerytypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
||||
pkgmulticluster "github.com/kubevela/pkg/multicluster"
|
||||
"github.com/kubevela/workflow/pkg/providers/legacy/kube"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ProviderName is provider name for install.
|
||||
ProviderName = "query"
|
||||
// HelmReleaseKind is the kind of HelmRelease
|
||||
HelmReleaseKind = "HelmRelease"
|
||||
|
||||
annoAmbassadorServiceName = "ambassador.service/name"
|
||||
annoAmbassadorServiceNamespace = "ambassador.service/namespace"
|
||||
)
|
||||
|
||||
// Resource refer to an object with cluster info
|
||||
type Resource struct {
|
||||
Cluster string `json:"cluster"`
|
||||
Component string `json:"component"`
|
||||
Revision string `json:"revision"`
|
||||
Object *unstructured.Unstructured `json:"object"`
|
||||
}
|
||||
|
||||
// Option is the query option
|
||||
type Option struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Filter FilterOption `json:"filter,omitempty"`
|
||||
// WithStatus means query the object from the cluster and get the latest status
|
||||
// This field only suitable for ListResourcesInApp
|
||||
WithStatus bool `json:"withStatus,omitempty"`
|
||||
|
||||
// WithTree means recursively query the resource tree.
|
||||
WithTree bool `json:"withTree,omitempty"`
|
||||
}
|
||||
|
||||
// FilterOption filter resource created by component
|
||||
type FilterOption struct {
|
||||
Cluster string `json:"cluster,omitempty"`
|
||||
ClusterNamespace string `json:"clusterNamespace,omitempty"`
|
||||
Components []string `json:"components,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
QueryNewest bool `json:"queryNewest,omitempty"`
|
||||
}
|
||||
|
||||
// ListVars is the vars for list
|
||||
type ListVars struct {
|
||||
App Option `json:"app"`
|
||||
}
|
||||
|
||||
// ListParams is the params for list
|
||||
type ListParams = oamprovidertypes.Params[ListVars]
|
||||
|
||||
// ListReturnVars is the vars for list return
|
||||
type ListReturnVars[T any] struct {
|
||||
List []T `json:"list"`
|
||||
Error string `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
// ListReturns is the returns for list
|
||||
type ListReturns[T any] oamprovidertypes.Returns[ListReturnVars[T]]
|
||||
|
||||
// ListResourcesInApp lists CRs created by Application, this provider queries the object data.
|
||||
func ListResourcesInApp(ctx context.Context, params *ListParams) (*ListReturns[Resource], error) {
|
||||
collector := NewAppCollector(params.KubeClient, params.Params.App)
|
||||
appResList, err := collector.CollectResourceFromApp(ctx)
|
||||
if err != nil {
|
||||
// nolint:nilerr
|
||||
return &ListReturns[Resource]{Returns: ListReturnVars[Resource]{Error: err.Error()}}, nil
|
||||
}
|
||||
if appResList == nil {
|
||||
appResList = make([]Resource, 0)
|
||||
}
|
||||
return &ListReturns[Resource]{Returns: ListReturnVars[Resource]{List: appResList}}, nil
|
||||
}
|
||||
|
||||
// ListAppliedResources list applied resource from tracker, this provider only queries the metadata.
|
||||
func ListAppliedResources(ctx context.Context, params *ListParams) (*ListReturns[querytypes.AppliedResource], error) {
|
||||
opt := params.Params.App
|
||||
cli := params.KubeClient
|
||||
collector := NewAppCollector(cli, opt)
|
||||
app := new(v1beta1.Application)
|
||||
appKey := client.ObjectKey{Name: opt.Name, Namespace: opt.Namespace}
|
||||
if err := cli.Get(ctx, appKey, app); err != nil {
|
||||
// nolint:nilerr
|
||||
return &ListReturns[querytypes.AppliedResource]{Returns: ListReturnVars[querytypes.AppliedResource]{Error: err.Error()}}, nil
|
||||
}
|
||||
appResList, err := collector.ListApplicationResources(ctx, app)
|
||||
if err != nil {
|
||||
// nolint:nilerr
|
||||
return &ListReturns[querytypes.AppliedResource]{Returns: ListReturnVars[querytypes.AppliedResource]{Error: err.Error()}}, nil
|
||||
}
|
||||
if appResList == nil {
|
||||
appResList = make([]querytypes.AppliedResource, 0)
|
||||
}
|
||||
return &ListReturns[querytypes.AppliedResource]{Returns: ListReturnVars[querytypes.AppliedResource]{List: appResList}}, nil
|
||||
}
|
||||
|
||||
// CollectResources collects resources from the cluster
|
||||
func CollectResources(ctx context.Context, params *ListParams) (*ListReturns[querytypes.ResourceItem], error) {
|
||||
opt := params.Params.App
|
||||
cli := params.KubeClient
|
||||
collector := NewAppCollector(cli, opt)
|
||||
app := new(v1beta1.Application)
|
||||
appKey := client.ObjectKey{Name: opt.Name, Namespace: opt.Namespace}
|
||||
if err := cli.Get(ctx, appKey, app); err != nil {
|
||||
// nolint:nilerr
|
||||
return &ListReturns[querytypes.ResourceItem]{Returns: ListReturnVars[querytypes.ResourceItem]{Error: err.Error()}}, nil
|
||||
}
|
||||
appResList, err := collector.ListApplicationResources(ctx, app)
|
||||
if err != nil {
|
||||
// nolint:nilerr
|
||||
return &ListReturns[querytypes.ResourceItem]{Returns: ListReturnVars[querytypes.ResourceItem]{Error: err.Error()}}, nil
|
||||
}
|
||||
var resources = make([]querytypes.ResourceItem, 0)
|
||||
for _, res := range appResList {
|
||||
if res.ResourceTree != nil {
|
||||
resources = append(resources, buildResourceArray(res, res.ResourceTree, res.ResourceTree, opt.Filter.Kind, opt.Filter.APIVersion)...)
|
||||
} else if res.Kind == opt.Filter.Kind && res.APIVersion == opt.Filter.APIVersion {
|
||||
object := &unstructured.Unstructured{}
|
||||
object.SetAPIVersion(opt.Filter.APIVersion)
|
||||
object.SetKind(opt.Filter.Kind)
|
||||
if err := cli.Get(ctx, apimachinerytypes.NamespacedName{Namespace: res.Namespace, Name: res.Name}, object); err == nil {
|
||||
resources = append(resources, buildResourceItem(res, querytypes.Workload{
|
||||
APIVersion: app.APIVersion,
|
||||
Kind: app.Kind,
|
||||
Name: app.Name,
|
||||
Namespace: app.Namespace,
|
||||
}, object))
|
||||
} else {
|
||||
klog.Errorf("failed to get the service:%s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ListReturns[querytypes.ResourceItem]{Returns: ListReturnVars[querytypes.ResourceItem]{List: resources}}, nil
|
||||
}
|
||||
|
||||
// SearchVars is the vars for search
|
||||
type SearchVars struct {
|
||||
Value *unstructured.Unstructured `json:"value"`
|
||||
Cluster string `json:"cluster"`
|
||||
}
|
||||
|
||||
// SearchParams is the params for search
|
||||
type SearchParams = oamprovidertypes.Params[SearchVars]
|
||||
|
||||
// SearchEvents searches events
|
||||
func SearchEvents(ctx context.Context, params *SearchParams) (*ListReturns[corev1.Event], error) {
|
||||
obj := params.Params.Value
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("please provide a object value to search events")
|
||||
}
|
||||
cluster := params.Params.Cluster
|
||||
cli := params.KubeClient
|
||||
|
||||
listCtx := multicluster.ContextWithClusterName(ctx, cluster)
|
||||
fieldSelector := getEventFieldSelector(obj)
|
||||
eventList := corev1.EventList{}
|
||||
listOpts := []client.ListOption{
|
||||
client.InNamespace(obj.GetNamespace()),
|
||||
client.MatchingFieldsSelector{
|
||||
Selector: fieldSelector,
|
||||
},
|
||||
}
|
||||
if err := cli.List(listCtx, &eventList, listOpts...); err != nil {
|
||||
// nolint:nilerr
|
||||
return &ListReturns[corev1.Event]{Returns: ListReturnVars[corev1.Event]{Error: err.Error()}}, nil
|
||||
}
|
||||
return &ListReturns[corev1.Event]{Returns: ListReturnVars[corev1.Event]{List: eventList.Items}}, nil
|
||||
}
|
||||
|
||||
// LogVars is the vars for log
|
||||
type LogVars struct {
|
||||
Cluster string `json:"cluster"`
|
||||
Namespace string `json:"namespace"`
|
||||
Pod string `json:"pod"`
|
||||
Options *corev1.PodLogOptions `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
// LogParams is the params for log
|
||||
type LogParams = oamprovidertypes.Params[LogVars]
|
||||
|
||||
// LogReturnVars is the log return vars
|
||||
type LogReturnVars struct {
|
||||
Outputs map[string]interface{} `json:"outputs"`
|
||||
}
|
||||
|
||||
// LogReturns is the log returns
|
||||
type LogReturns = oamprovidertypes.Returns[LogReturnVars]
|
||||
|
||||
// CollectLogsInPod collects logs in pod
|
||||
func CollectLogsInPod(ctx context.Context, params *LogParams) (*LogReturns, error) {
|
||||
cluster := params.Params.Cluster
|
||||
namespace := params.Params.Namespace
|
||||
pod := params.Params.Pod
|
||||
if pod == "" {
|
||||
return nil, fmt.Errorf("please provide a pod name to collect logs")
|
||||
}
|
||||
opts := params.Params.Options
|
||||
if opts == nil || opts.Container == "" {
|
||||
return nil, fmt.Errorf("please provide the container name to collect logs")
|
||||
}
|
||||
cliCtx := multicluster.ContextWithClusterName(ctx, cluster)
|
||||
cfg := params.KubeConfig
|
||||
cfg.Wrap(pkgmulticluster.NewTransportWrapper())
|
||||
clientSet, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create kubernetes client")
|
||||
}
|
||||
var defaultOutputs = make(map[string]interface{})
|
||||
var errMsg string
|
||||
podInst, err := clientSet.CoreV1().Pods(namespace).Get(cliCtx, pod, v1.GetOptions{})
|
||||
if err != nil {
|
||||
errMsg += fmt.Sprintf("failed to get pod: %s; ", err.Error())
|
||||
}
|
||||
req := clientSet.CoreV1().Pods(namespace).GetLogs(pod, opts)
|
||||
readCloser, err := req.Stream(cliCtx)
|
||||
if err != nil {
|
||||
errMsg += fmt.Sprintf("failed to get stream logs %s; ", err.Error())
|
||||
}
|
||||
if readCloser != nil && podInst != nil {
|
||||
r := bufio.NewReader(readCloser)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
var readErr error
|
||||
defer func() {
|
||||
_ = readCloser.Close()
|
||||
}()
|
||||
for {
|
||||
s, err := r.ReadString('\n')
|
||||
buffer.WriteString(s)
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
readErr = err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
toDate := v1.Now()
|
||||
var fromDate v1.Time
|
||||
// nolint
|
||||
if opts.SinceTime != nil {
|
||||
fromDate = *opts.SinceTime
|
||||
} else if opts.SinceSeconds != nil {
|
||||
fromDate = v1.NewTime(toDate.Add(time.Duration(-(*opts.SinceSeconds) * int64(time.Second))))
|
||||
} else {
|
||||
fromDate = podInst.CreationTimestamp
|
||||
}
|
||||
// the cue string can not support the special characters
|
||||
logs := base64.StdEncoding.EncodeToString(buffer.Bytes())
|
||||
defaultOutputs = map[string]interface{}{
|
||||
"logs": logs,
|
||||
"info": map[string]interface{}{
|
||||
"fromDate": fromDate,
|
||||
"toDate": toDate,
|
||||
},
|
||||
}
|
||||
if readErr != nil {
|
||||
errMsg += readErr.Error()
|
||||
}
|
||||
}
|
||||
if errMsg != "" {
|
||||
klog.Warningf(errMsg)
|
||||
defaultOutputs["err"] = errMsg
|
||||
}
|
||||
return &LogReturns{Returns: LogReturnVars{Outputs: defaultOutputs}}, nil
|
||||
}
|
||||
|
||||
//go:embed ql.cue
|
||||
var qlTemplate string
|
||||
|
||||
// GetTemplate returns the cue template.
|
||||
func GetTemplate() string {
|
||||
return strings.Join([]string{qlTemplate, kube.GetTemplate()}, "\n")
|
||||
}
|
||||
|
||||
// GetProviders returns the cue providers.
|
||||
func GetProviders() map[string]cuexruntime.ProviderFn {
|
||||
qlProvider := map[string]cuexruntime.ProviderFn{
|
||||
"listResourcesInApp": oamprovidertypes.GenericProviderFn[ListVars, ListReturns[Resource]](ListResourcesInApp),
|
||||
"listAppliedResources": oamprovidertypes.GenericProviderFn[ListVars, ListReturns[querytypes.AppliedResource]](ListAppliedResources),
|
||||
"collectResources": oamprovidertypes.GenericProviderFn[ListVars, ListReturns[querytypes.ResourceItem]](CollectResources),
|
||||
"searchEvents": oamprovidertypes.GenericProviderFn[SearchVars, ListReturns[corev1.Event]](SearchEvents),
|
||||
"collectLogsInPod": oamprovidertypes.GenericProviderFn[LogVars, LogReturns](CollectLogsInPod),
|
||||
"collectServiceEndpoints": oamprovidertypes.GenericProviderFn[ListVars, ListReturns[querytypes.ServiceEndpoint]](CollectServiceEndpoints),
|
||||
}
|
||||
kubeProviders := kube.GetProviders()
|
||||
for k, v := range kubeProviders {
|
||||
qlProvider[k] = v
|
||||
}
|
||||
return qlProvider
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,233 @@
|
||||
#ListResourcesInApp: {
|
||||
#do: "listResourcesInApp"
|
||||
#provider: "query"
|
||||
$params: {
|
||||
app: {
|
||||
name: string
|
||||
namespace: string
|
||||
filter?: {
|
||||
cluster?: string
|
||||
clusterNamespace?: string
|
||||
components?: [...string]
|
||||
kind?: string
|
||||
apiVersion?: string
|
||||
}
|
||||
withStatus?: bool
|
||||
}
|
||||
}
|
||||
|
||||
$returns: {
|
||||
list?: [...{
|
||||
cluster: string
|
||||
component: string
|
||||
revision: string
|
||||
object: {...}
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#ListAppliedResources: {
|
||||
#do: "listAppliedResources"
|
||||
#provider: "query"
|
||||
|
||||
$params: {
|
||||
app: {
|
||||
name: string
|
||||
namespace: string
|
||||
filter?: {
|
||||
cluster?: string
|
||||
clusterNamespace?: string
|
||||
components?: [...string]
|
||||
kind?: string
|
||||
apiVersion?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$returns: {
|
||||
list?: [...{
|
||||
name: string
|
||||
namespace?: string
|
||||
cluster?: string
|
||||
component?: string
|
||||
trait?: string
|
||||
kind?: string
|
||||
uid?: string
|
||||
apiVersion?: string
|
||||
resourceVersion?: string
|
||||
publishVersion?: string
|
||||
deployVersion?: string
|
||||
revision?: string
|
||||
latest?: bool
|
||||
resourceTree?: {
|
||||
...
|
||||
}
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#CollectPods: {
|
||||
#do: "collectResources"
|
||||
#provider: "query"
|
||||
|
||||
$params: {
|
||||
app: {
|
||||
name: string
|
||||
namespace: string
|
||||
filter?: {
|
||||
cluster?: string
|
||||
clusterNamespace?: string
|
||||
components?: [...string]
|
||||
kind: "Pod"
|
||||
apiVersion: "v1"
|
||||
}
|
||||
withTree: true
|
||||
}
|
||||
}
|
||||
$returns: {
|
||||
list: [...{...}]
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#CollectServices: {
|
||||
#do: "collectResources"
|
||||
#provider: "query"
|
||||
$params: {
|
||||
app: {
|
||||
name: string
|
||||
namespace: string
|
||||
filter?: {
|
||||
cluster?: string
|
||||
clusterNamespace?: string
|
||||
components?: [...string]
|
||||
kind: "Service"
|
||||
apiVersion: "v1"
|
||||
}
|
||||
withTree: true
|
||||
}
|
||||
}
|
||||
$returns: {
|
||||
list: [...{...}]
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#SearchEvents: {
|
||||
#do: "searchEvents"
|
||||
#provider: "query"
|
||||
|
||||
$params: {
|
||||
value: {...}
|
||||
cluster: string
|
||||
}
|
||||
$returns: {
|
||||
list: [...{...}]
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#CollectLogsInPod: {
|
||||
#do: "collectLogsInPod"
|
||||
#provider: "query"
|
||||
|
||||
$params: {
|
||||
cluster: string
|
||||
namespace: string
|
||||
pod: string
|
||||
options: {
|
||||
container: string
|
||||
previous: *false | bool
|
||||
sinceSeconds: *null | int
|
||||
sinceTime: *null | string
|
||||
timestamps: *false | bool
|
||||
tailLines: *null | int
|
||||
limitBytes: *null | int
|
||||
}
|
||||
}
|
||||
|
||||
$returns: {
|
||||
outputs?: {
|
||||
logs?: string
|
||||
err?: string
|
||||
info?: {
|
||||
fromDate: string
|
||||
toDate: string
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#CollectServiceEndpoints: {
|
||||
#do: "collectServiceEndpoints"
|
||||
#provider: "query"
|
||||
|
||||
$params: {
|
||||
app: {
|
||||
name: string
|
||||
namespace: string
|
||||
filter?: {
|
||||
cluster?: string
|
||||
clusterNamespace?: string
|
||||
components?: [...string]
|
||||
}
|
||||
withTree: true
|
||||
}
|
||||
}
|
||||
|
||||
$returns: {
|
||||
list?: [...{
|
||||
endpoint: {
|
||||
protocol: string
|
||||
appProtocol?: string
|
||||
host?: string
|
||||
port: int
|
||||
portName?: string
|
||||
path?: string
|
||||
inner?: bool
|
||||
}
|
||||
ref: {...}
|
||||
cluster?: string
|
||||
component?: string
|
||||
...
|
||||
}]
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
#GetApplicationTree: {
|
||||
#do: "listAppliedResources"
|
||||
#provider: "query"
|
||||
app: {
|
||||
name: string
|
||||
namespace: string
|
||||
filter?: {
|
||||
cluster?: string
|
||||
clusterNamespace?: string
|
||||
components?: [...string]
|
||||
queryNewest?: bool
|
||||
}
|
||||
withTree: true
|
||||
}
|
||||
list?: [...{
|
||||
name: string
|
||||
namespace?: string
|
||||
cluster?: string
|
||||
component?: string
|
||||
trait?: string
|
||||
kind?: string
|
||||
uid?: string
|
||||
apiVersion?: string
|
||||
resourceVersion?: string
|
||||
publishVersion?: string
|
||||
deployVersion?: string
|
||||
revision?: string
|
||||
latest?: bool
|
||||
...
|
||||
}]
|
||||
...
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2021. The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cuexv1alpha1 "github.com/kubevela/pkg/apis/cue/v1alpha1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var ctx context.Context
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
By("bootstrapping test environment")
|
||||
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.Bool(false),
|
||||
CRDDirectoryPaths: []string{
|
||||
"./testdata/gateway/crds",
|
||||
"../../../../charts/vela-core/crds",
|
||||
"./testdata/machinelearning.seldon.io_seldondeployments.yaml",
|
||||
"./testdata/helm-release-crd.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
|
||||
scheme := common.Scheme
|
||||
err = batchv1.AddToScheme(scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = cuexv1alpha1.AddToScheme(scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
ctx = context.Background()
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
func TestQueryProvider(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "VelaQL Suite")
|
||||
}
|
||||
+1435
File diff suppressed because it is too large
Load Diff
Vendored
+3711
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
annotations:
|
||||
oam.dev/kubevela-version: v1.5.0-alpha.2
|
||||
ports-mapping: "8000:80,8443:443"
|
||||
labels:
|
||||
addons.oam.dev/name: velaux
|
||||
addons.oam.dev/registry: KubeVela
|
||||
addons.oam.dev/version: v1.5.0-alpha.3
|
||||
app.oam.dev/app-revision-hash: 33e813ddfe9a34be
|
||||
app.oam.dev/appRevision: addon-velaux-v36
|
||||
app.oam.dev/cluster: ""
|
||||
app.oam.dev/component: velaux
|
||||
app.oam.dev/name: addon-velaux
|
||||
app.oam.dev/namespace: vela-system
|
||||
app.oam.dev/resourceType: TRAIT
|
||||
app.oam.dev/revision: velaux-v16
|
||||
oam.dev/render-hash: e7be271bfad2cb55
|
||||
trait.oam.dev/resource: gateway
|
||||
trait.oam.dev/type: https-route
|
||||
name: velaux-gateway-tls
|
||||
namespace: vela-system
|
||||
spec:
|
||||
gatewayClassName: traefik
|
||||
listeners:
|
||||
- allowedRoutes:
|
||||
namespaces:
|
||||
from: Same
|
||||
name: kubevela
|
||||
port: 8443
|
||||
protocol: HTTPS
|
||||
tls:
|
||||
certificateRefs:
|
||||
- group: ""
|
||||
kind: Secret
|
||||
name: kubevela
|
||||
namespace: vela-system
|
||||
mode: Terminate
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: traefik-gateway
|
||||
namespace: vela-system
|
||||
annotations:
|
||||
ports-mapping: "8000:80,8443:443"
|
||||
spec:
|
||||
gatewayClassName: traefik
|
||||
listeners:
|
||||
- allowedRoutes:
|
||||
namespaces:
|
||||
from: All
|
||||
name: web
|
||||
port: 8000
|
||||
protocol: HTTP
|
||||
@@ -0,0 +1,40 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: http-test-route
|
||||
namespace: default
|
||||
spec:
|
||||
hostnames:
|
||||
- gateway.domain
|
||||
parentRefs:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: traefik-gateway
|
||||
namespace: vela-system
|
||||
sectionName: web
|
||||
rules:
|
||||
- backendRefs:
|
||||
- group: ""
|
||||
kind: Service
|
||||
name: game2048
|
||||
port: 80
|
||||
weight: 1
|
||||
matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
- group: ""
|
||||
kind: Service
|
||||
name: game2048
|
||||
port: 80
|
||||
weight: 1
|
||||
- backendRefs:
|
||||
- group: ""
|
||||
kind: Service
|
||||
name: game2048-2
|
||||
port: 80
|
||||
weight: 1
|
||||
matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /api
|
||||
@@ -0,0 +1,41 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
annotations:
|
||||
oam.dev/kubevela-version: v1.5.0-alpha.2
|
||||
labels:
|
||||
addons.oam.dev/name: velaux
|
||||
addons.oam.dev/registry: KubeVela
|
||||
addons.oam.dev/version: v1.5.0-alpha.3
|
||||
app.oam.dev/app-revision-hash: 33e813ddfe9a34be
|
||||
app.oam.dev/appRevision: addon-velaux-v36
|
||||
app.oam.dev/cluster: ""
|
||||
app.oam.dev/component: velaux
|
||||
app.oam.dev/name: addon-velaux
|
||||
app.oam.dev/namespace: vela-system
|
||||
app.oam.dev/resourceType: TRAIT
|
||||
app.oam.dev/revision: velaux-v16
|
||||
oam.dev/render-hash: 2e8aa179bec2b4ec
|
||||
trait.oam.dev/resource: httpsRoute
|
||||
trait.oam.dev/type: https-route
|
||||
name: velaux-ssl
|
||||
namespace: default
|
||||
spec:
|
||||
hostnames:
|
||||
- demo.kubevela.net
|
||||
parentRefs:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: Gateway
|
||||
name: velaux-gateway-tls
|
||||
namespace: vela-system
|
||||
rules:
|
||||
- backendRefs:
|
||||
- group: ""
|
||||
kind: Service
|
||||
name: velaux
|
||||
port: 80
|
||||
weight: 1
|
||||
matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
@@ -0,0 +1,736 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: helmreleases.helm.toolkit.fluxcd.io
|
||||
spec:
|
||||
conversion:
|
||||
strategy: None
|
||||
group: helm.toolkit.fluxcd.io
|
||||
names:
|
||||
kind: HelmRelease
|
||||
listKind: HelmReleaseList
|
||||
plural: helmreleases
|
||||
shortNames:
|
||||
- hr
|
||||
singular: helmrelease
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].status
|
||||
name: Ready
|
||||
type: string
|
||||
- jsonPath: .status.conditions[?(@.type=="Ready")].message
|
||||
name: Status
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v2beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: HelmRelease is the Schema for the helmreleases API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: HelmReleaseSpec defines the desired state of a Helm release.
|
||||
properties:
|
||||
chart:
|
||||
description: Chart defines the template of the v1beta1.HelmChart that
|
||||
should be created for this HelmRelease.
|
||||
properties:
|
||||
spec:
|
||||
description: Spec holds the template for the v1beta1.HelmChartSpec
|
||||
for this HelmRelease.
|
||||
properties:
|
||||
chart:
|
||||
description: The name or path the Helm chart is available
|
||||
at in the SourceRef.
|
||||
type: string
|
||||
interval:
|
||||
description: Interval at which to check the v1beta1.Source
|
||||
for updates. Defaults to 'HelmReleaseSpec.Interval'.
|
||||
type: string
|
||||
sourceRef:
|
||||
description: The name and namespace of the v1beta1.Source
|
||||
the chart is available at.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: APIVersion of the referent.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referent.
|
||||
enum:
|
||||
- HelmRepository
|
||||
- GitRepository
|
||||
- Bucket
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referent.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the referent.
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
valuesFile:
|
||||
description: Alternative values file to use as the default
|
||||
chart values, expected to be a relative path in the SourceRef.
|
||||
Deprecated in favor of ValuesFiles, for backwards compatibility
|
||||
the file defined here is merged before the ValuesFiles items.
|
||||
Ignored when omitted.
|
||||
type: string
|
||||
valuesFiles:
|
||||
description: Alternative list of values files to use as the
|
||||
chart values (values.yaml is not included by default), expected
|
||||
to be a relative path in the SourceRef. Values files are
|
||||
merged in the order of this list with the last file overriding
|
||||
the first. Ignored when omitted.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
version:
|
||||
default: '*'
|
||||
description: Version semver expression, ignored for charts
|
||||
from v1beta1.GitRepository and v1beta1.Bucket sources. Defaults
|
||||
to latest when omitted.
|
||||
type: string
|
||||
required:
|
||||
- chart
|
||||
- sourceRef
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
dependsOn:
|
||||
description: DependsOn may contain a dependency.CrossNamespaceDependencyReference
|
||||
slice with references to HelmRelease resources that must be ready
|
||||
before this HelmRelease can be reconciled.
|
||||
items:
|
||||
description: CrossNamespaceDependencyReference holds the reference
|
||||
to a dependency.
|
||||
properties:
|
||||
name:
|
||||
description: Name holds the name reference of a dependency.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace holds the namespace reference of a dependency.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
install:
|
||||
description: Install holds the configuration for Helm install actions
|
||||
for this HelmRelease.
|
||||
properties:
|
||||
crds:
|
||||
description: "CRDs upgrade CRDs from the Helm Chart's crds directory
|
||||
according to the CRD upgrade policy provided here. Valid values
|
||||
are `Skip`, `Create` or `CreateReplace`. Default is `Create`
|
||||
and if omitted CRDs are installed but not updated. \n Skip:
|
||||
do neither install nor replace (update) any CRDs. \n Create:
|
||||
new CRDs are created, existing CRDs are neither updated nor
|
||||
deleted. \n CreateReplace: new CRDs are created, existing CRDs
|
||||
are updated (replaced) but not deleted. \n By default, CRDs
|
||||
are applied (installed) during Helm install action. With this
|
||||
option users can opt-in to CRD replace existing CRDs on Helm
|
||||
install actions, which is not (yet) natively supported by Helm.
|
||||
https://helm.sh/docs/chart_best_practices/custom_resource_definitions."
|
||||
enum:
|
||||
- Skip
|
||||
- Create
|
||||
- CreateReplace
|
||||
type: string
|
||||
createNamespace:
|
||||
description: CreateNamespace tells the Helm install action to
|
||||
create the HelmReleaseSpec.TargetNamespace if it does not exist
|
||||
yet. On uninstall, the namespace will not be garbage collected.
|
||||
type: boolean
|
||||
disableHooks:
|
||||
description: DisableHooks prevents hooks from running during the
|
||||
Helm install action.
|
||||
type: boolean
|
||||
disableOpenAPIValidation:
|
||||
description: DisableOpenAPIValidation prevents the Helm install
|
||||
action from validating rendered templates against the Kubernetes
|
||||
OpenAPI Schema.
|
||||
type: boolean
|
||||
disableWait:
|
||||
description: DisableWait disables the waiting for resources to
|
||||
be ready after a Helm install has been performed.
|
||||
type: boolean
|
||||
disableWaitForJobs:
|
||||
description: DisableWaitForJobs disables waiting for jobs to complete
|
||||
after a Helm install has been performed.
|
||||
type: boolean
|
||||
remediation:
|
||||
description: Remediation holds the remediation configuration for
|
||||
when the Helm install action for the HelmRelease fails. The
|
||||
default is to not perform any action.
|
||||
properties:
|
||||
ignoreTestFailures:
|
||||
description: IgnoreTestFailures tells the controller to skip
|
||||
remediation when the Helm tests are run after an install
|
||||
action but fail. Defaults to 'Test.IgnoreFailures'.
|
||||
type: boolean
|
||||
remediateLastFailure:
|
||||
description: RemediateLastFailure tells the controller to
|
||||
remediate the last failure, when no retries remain. Defaults
|
||||
to 'false'.
|
||||
type: boolean
|
||||
retries:
|
||||
description: Retries is the number of retries that should
|
||||
be attempted on failures before bailing. Remediation, using
|
||||
an uninstall, is performed between each attempt. Defaults
|
||||
to '0', a negative integer equals to unlimited retries.
|
||||
type: integer
|
||||
type: object
|
||||
replace:
|
||||
description: Replace tells the Helm install action to re-use the
|
||||
'ReleaseName', but only if that name is a deleted release which
|
||||
remains in the history.
|
||||
type: boolean
|
||||
skipCRDs:
|
||||
description: "SkipCRDs tells the Helm install action to not install
|
||||
any CRDs. By default, CRDs are installed if not already present.
|
||||
\n Deprecated use CRD policy (`crds`) attribute with value `Skip`
|
||||
instead."
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during the performance of a
|
||||
Helm install action. Defaults to 'HelmReleaseSpec.Timeout'.
|
||||
type: string
|
||||
type: object
|
||||
interval:
|
||||
description: Interval at which to reconcile the Helm release.
|
||||
type: string
|
||||
kubeConfig:
|
||||
description: KubeConfig for reconciling the HelmRelease on a remote
|
||||
cluster. When specified, KubeConfig takes precedence over ServiceAccountName.
|
||||
properties:
|
||||
secretRef:
|
||||
description: SecretRef holds the name to a secret that contains
|
||||
a 'value' key with the kubeconfig file as the value. It must
|
||||
be in the same namespace as the HelmRelease. It is recommended
|
||||
that the kubeconfig is self-contained, and the secret is regularly
|
||||
updated if credentials such as a cloud-access-token expire.
|
||||
Cloud specific `cmd-path` auth helpers will not function without
|
||||
adding binaries and credentials to the Pod that is responsible
|
||||
for reconciling the HelmRelease.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
maxHistory:
|
||||
description: MaxHistory is the number of revisions saved by Helm for
|
||||
this HelmRelease. Use '0' for an unlimited number of revisions;
|
||||
defaults to '10'.
|
||||
type: integer
|
||||
postRenderers:
|
||||
description: PostRenderers holds an array of Helm PostRenderers, which
|
||||
will be applied in order of their definition.
|
||||
items:
|
||||
description: PostRenderer contains a Helm PostRenderer specification.
|
||||
properties:
|
||||
kustomize:
|
||||
description: Kustomization to apply as PostRenderer.
|
||||
properties:
|
||||
images:
|
||||
description: Images is a list of (image name, new name,
|
||||
new tag or digest) for changing image names, tags or digests.
|
||||
This can also be achieved with a patch, but this operator
|
||||
is simpler to specify.
|
||||
items:
|
||||
description: Image contains an image name, a new name,
|
||||
a new tag or digest, which will replace the original
|
||||
name and tag.
|
||||
properties:
|
||||
digest:
|
||||
description: Digest is the value used to replace the
|
||||
original image tag. If digest is present NewTag
|
||||
value is ignored.
|
||||
type: string
|
||||
name:
|
||||
description: Name is a tag-less image name.
|
||||
type: string
|
||||
newName:
|
||||
description: NewName is the value used to replace
|
||||
the original name.
|
||||
type: string
|
||||
newTag:
|
||||
description: NewTag is the value used to replace the
|
||||
original tag.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
patchesJson6902:
|
||||
description: JSON 6902 patches, defined as inline YAML objects.
|
||||
items:
|
||||
description: JSON6902Patch contains a JSON6902 patch and
|
||||
the target the patch should be applied to.
|
||||
properties:
|
||||
patch:
|
||||
description: Patch contains the JSON6902 patch document
|
||||
with an array of operation objects.
|
||||
items:
|
||||
description: JSON6902 is a JSON6902 operation object.
|
||||
https://tools.ietf.org/html/rfc6902#section-4
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
op:
|
||||
enum:
|
||||
- test
|
||||
- remove
|
||||
- add
|
||||
- replace
|
||||
- move
|
||||
- copy
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
value:
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
required:
|
||||
- op
|
||||
- path
|
||||
type: object
|
||||
type: array
|
||||
target:
|
||||
description: Target points to the resources that the
|
||||
patch document should be applied to.
|
||||
properties:
|
||||
annotationSelector:
|
||||
description: AnnotationSelector is a string that
|
||||
follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
|
||||
It matches with the resource annotations.
|
||||
type: string
|
||||
group:
|
||||
description: Group is the API group to select
|
||||
resources from. Together with Version and Kind
|
||||
it is capable of unambiguously identifying and/or
|
||||
selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the API Group to select resources
|
||||
from. Together with Group and Version it is
|
||||
capable of unambiguously identifying and/or
|
||||
selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md
|
||||
type: string
|
||||
labelSelector:
|
||||
description: LabelSelector is a string that follows
|
||||
the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
|
||||
It matches with the resource labels.
|
||||
type: string
|
||||
name:
|
||||
description: Name to match resources with.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace to select resources from.
|
||||
type: string
|
||||
version:
|
||||
description: Version of the API Group to select
|
||||
resources from. Together with Group and Kind
|
||||
it is capable of unambiguously identifying and/or
|
||||
selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- patch
|
||||
- target
|
||||
type: object
|
||||
type: array
|
||||
patchesStrategicMerge:
|
||||
description: Strategic merge patches, defined as inline
|
||||
YAML objects.
|
||||
items:
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
releaseName:
|
||||
description: ReleaseName used for the Helm release. Defaults to a
|
||||
composition of '[TargetNamespace-]Name'.
|
||||
maxLength: 53
|
||||
minLength: 1
|
||||
type: string
|
||||
rollback:
|
||||
description: Rollback holds the configuration for Helm rollback actions
|
||||
for this HelmRelease.
|
||||
properties:
|
||||
cleanupOnFail:
|
||||
description: CleanupOnFail allows deletion of new resources created
|
||||
during the Helm rollback action when it fails.
|
||||
type: boolean
|
||||
disableHooks:
|
||||
description: DisableHooks prevents hooks from running during the
|
||||
Helm rollback action.
|
||||
type: boolean
|
||||
disableWait:
|
||||
description: DisableWait disables the waiting for resources to
|
||||
be ready after a Helm rollback has been performed.
|
||||
type: boolean
|
||||
disableWaitForJobs:
|
||||
description: DisableWaitForJobs disables waiting for jobs to complete
|
||||
after a Helm rollback has been performed.
|
||||
type: boolean
|
||||
force:
|
||||
description: Force forces resource updates through a replacement
|
||||
strategy.
|
||||
type: boolean
|
||||
recreate:
|
||||
description: Recreate performs pod restarts for the resource if
|
||||
applicable.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during the performance of a
|
||||
Helm rollback action. Defaults to 'HelmReleaseSpec.Timeout'.
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountName:
|
||||
description: The name of the Kubernetes service account to impersonate
|
||||
when reconciling this HelmRelease.
|
||||
type: string
|
||||
storageNamespace:
|
||||
description: StorageNamespace used for the Helm storage. Defaults
|
||||
to the namespace of the HelmRelease.
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
type: string
|
||||
suspend:
|
||||
description: Suspend tells the controller to suspend reconciliation
|
||||
for this HelmRelease, it does not apply to already started reconciliations.
|
||||
Defaults to false.
|
||||
type: boolean
|
||||
targetNamespace:
|
||||
description: TargetNamespace to target when performing operations
|
||||
for the HelmRelease. Defaults to the namespace of the HelmRelease.
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
type: string
|
||||
test:
|
||||
description: Test holds the configuration for Helm test actions for
|
||||
this HelmRelease.
|
||||
properties:
|
||||
enable:
|
||||
description: Enable enables Helm test actions for this HelmRelease
|
||||
after an Helm install or upgrade action has been performed.
|
||||
type: boolean
|
||||
ignoreFailures:
|
||||
description: IgnoreFailures tells the controller to skip remediation
|
||||
when the Helm tests are run but fail. Can be overwritten for
|
||||
tests run after install or upgrade actions in 'Install.IgnoreTestFailures'
|
||||
and 'Upgrade.IgnoreTestFailures'.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation during the performance of a Helm test action. Defaults
|
||||
to 'HelmReleaseSpec.Timeout'.
|
||||
type: string
|
||||
type: object
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during the performance of a Helm
|
||||
action. Defaults to '5m0s'.
|
||||
type: string
|
||||
uninstall:
|
||||
description: Uninstall holds the configuration for Helm uninstall
|
||||
actions for this HelmRelease.
|
||||
properties:
|
||||
disableHooks:
|
||||
description: DisableHooks prevents hooks from running during the
|
||||
Helm rollback action.
|
||||
type: boolean
|
||||
keepHistory:
|
||||
description: KeepHistory tells Helm to remove all associated resources
|
||||
and mark the release as deleted, but retain the release history.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during the performance of a
|
||||
Helm uninstall action. Defaults to 'HelmReleaseSpec.Timeout'.
|
||||
type: string
|
||||
type: object
|
||||
upgrade:
|
||||
description: Upgrade holds the configuration for Helm upgrade actions
|
||||
for this HelmRelease.
|
||||
properties:
|
||||
cleanupOnFail:
|
||||
description: CleanupOnFail allows deletion of new resources created
|
||||
during the Helm upgrade action when it fails.
|
||||
type: boolean
|
||||
crds:
|
||||
description: "CRDs upgrade CRDs from the Helm Chart's crds directory
|
||||
according to the CRD upgrade policy provided here. Valid values
|
||||
are `Skip`, `Create` or `CreateReplace`. Default is `Skip` and
|
||||
if omitted CRDs are neither installed nor upgraded. \n Skip:
|
||||
do neither install nor replace (update) any CRDs. \n Create:
|
||||
new CRDs are created, existing CRDs are neither updated nor
|
||||
deleted. \n CreateReplace: new CRDs are created, existing CRDs
|
||||
are updated (replaced) but not deleted. \n By default, CRDs
|
||||
are not applied during Helm upgrade action. With this option
|
||||
users can opt-in to CRD upgrade, which is not (yet) natively
|
||||
supported by Helm. https://helm.sh/docs/chart_best_practices/custom_resource_definitions."
|
||||
enum:
|
||||
- Skip
|
||||
- Create
|
||||
- CreateReplace
|
||||
type: string
|
||||
disableHooks:
|
||||
description: DisableHooks prevents hooks from running during the
|
||||
Helm upgrade action.
|
||||
type: boolean
|
||||
disableOpenAPIValidation:
|
||||
description: DisableOpenAPIValidation prevents the Helm upgrade
|
||||
action from validating rendered templates against the Kubernetes
|
||||
OpenAPI Schema.
|
||||
type: boolean
|
||||
disableWait:
|
||||
description: DisableWait disables the waiting for resources to
|
||||
be ready after a Helm upgrade has been performed.
|
||||
type: boolean
|
||||
disableWaitForJobs:
|
||||
description: DisableWaitForJobs disables waiting for jobs to complete
|
||||
after a Helm upgrade has been performed.
|
||||
type: boolean
|
||||
force:
|
||||
description: Force forces resource updates through a replacement
|
||||
strategy.
|
||||
type: boolean
|
||||
preserveValues:
|
||||
description: PreserveValues will make Helm reuse the last release's
|
||||
values and merge in overrides from 'Values'. Setting this flag
|
||||
makes the HelmRelease non-declarative.
|
||||
type: boolean
|
||||
remediation:
|
||||
description: Remediation holds the remediation configuration for
|
||||
when the Helm upgrade action for the HelmRelease fails. The
|
||||
default is to not perform any action.
|
||||
properties:
|
||||
ignoreTestFailures:
|
||||
description: IgnoreTestFailures tells the controller to skip
|
||||
remediation when the Helm tests are run after an upgrade
|
||||
action but fail. Defaults to 'Test.IgnoreFailures'.
|
||||
type: boolean
|
||||
remediateLastFailure:
|
||||
description: RemediateLastFailure tells the controller to
|
||||
remediate the last failure, when no retries remain. Defaults
|
||||
to 'false' unless 'Retries' is greater than 0.
|
||||
type: boolean
|
||||
retries:
|
||||
description: Retries is the number of retries that should
|
||||
be attempted on failures before bailing. Remediation, using
|
||||
'Strategy', is performed between each attempt. Defaults
|
||||
to '0', a negative integer equals to unlimited retries.
|
||||
type: integer
|
||||
strategy:
|
||||
description: Strategy to use for failure remediation. Defaults
|
||||
to 'rollback'.
|
||||
enum:
|
||||
- rollback
|
||||
- uninstall
|
||||
type: string
|
||||
type: object
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during the performance of a
|
||||
Helm upgrade action. Defaults to 'HelmReleaseSpec.Timeout'.
|
||||
type: string
|
||||
type: object
|
||||
values:
|
||||
description: Values holds the values for this Helm release.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
valuesFrom:
|
||||
description: ValuesFrom holds references to resources containing Helm
|
||||
values for this HelmRelease, and information about how they should
|
||||
be merged.
|
||||
items:
|
||||
description: ValuesReference contains a reference to a resource
|
||||
containing Helm values, and optionally the key they can be found
|
||||
at.
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the values referent, valid values are ('Secret',
|
||||
'ConfigMap').
|
||||
enum:
|
||||
- Secret
|
||||
- ConfigMap
|
||||
type: string
|
||||
name:
|
||||
description: Name of the values referent. Should reside in the
|
||||
same namespace as the referring resource.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
optional:
|
||||
description: Optional marks this ValuesReference as optional.
|
||||
When set, a not found error for the values reference is ignored,
|
||||
but any ValuesKey, TargetPath or transient error will still
|
||||
result in a reconciliation failure.
|
||||
type: boolean
|
||||
targetPath:
|
||||
description: TargetPath is the YAML dot notation path the value
|
||||
should be merged at. When set, the ValuesKey is expected to
|
||||
be a single flat value. Defaults to 'None', which results
|
||||
in the values getting merged at the root.
|
||||
type: string
|
||||
valuesKey:
|
||||
description: ValuesKey is the data key where the values.yaml
|
||||
or a specific value can be found at. Defaults to 'values.yaml'.
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- chart
|
||||
- interval
|
||||
type: object
|
||||
status:
|
||||
description: HelmReleaseStatus defines the observed state of a HelmRelease.
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions holds the conditions for the HelmRelease.
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
type FooStatus struct{ // Represents the observations of a
|
||||
foo's current state. // Known .status.conditions.type are:
|
||||
\"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type
|
||||
\ // +patchStrategy=merge // +listType=map // +listMapKey=type
|
||||
\ Conditions []metav1.Condition `json:\"conditions,omitempty\"
|
||||
patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`
|
||||
\n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
failures:
|
||||
description: Failures is the reconciliation failure count against
|
||||
the latest desired state. It is reset after a successful reconciliation.
|
||||
format: int64
|
||||
type: integer
|
||||
helmChart:
|
||||
description: HelmChart is the namespaced name of the HelmChart resource
|
||||
created by the controller for the HelmRelease.
|
||||
type: string
|
||||
installFailures:
|
||||
description: InstallFailures is the install failure count against
|
||||
the latest desired state. It is reset after a successful reconciliation.
|
||||
format: int64
|
||||
type: integer
|
||||
lastAppliedRevision:
|
||||
description: LastAppliedRevision is the revision of the last successfully
|
||||
applied source.
|
||||
type: string
|
||||
lastAttemptedRevision:
|
||||
description: LastAttemptedRevision is the revision of the last reconciliation
|
||||
attempt.
|
||||
type: string
|
||||
lastAttemptedValuesChecksum:
|
||||
description: LastAttemptedValuesChecksum is the SHA1 checksum of the
|
||||
values of the last reconciliation attempt.
|
||||
type: string
|
||||
lastHandledReconcileAt:
|
||||
description: LastHandledReconcileAt holds the value of the most recent
|
||||
reconcile request value, so a change can be detected.
|
||||
type: string
|
||||
lastReleaseRevision:
|
||||
description: LastReleaseRevision is the revision of the last successful
|
||||
Helm release.
|
||||
type: integer
|
||||
observedGeneration:
|
||||
description: ObservedGeneration is the last observed generation.
|
||||
format: int64
|
||||
type: integer
|
||||
upgradeFailures:
|
||||
description: UpgradeFailures is the upgrade failure count against
|
||||
the latest desired state. It is reset after a successful reconciliation.
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
+10752
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2022. The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
)
|
||||
|
||||
func buildResourceArray(res querytypes.AppliedResource, parent, node *querytypes.ResourceTreeNode, kind string, apiVersion string) (pods []querytypes.ResourceItem) {
|
||||
if node.LeafNodes != nil {
|
||||
for _, subNode := range node.LeafNodes {
|
||||
pods = append(pods, buildResourceArray(res, node, subNode, kind, apiVersion)...)
|
||||
}
|
||||
} else if node.Kind == kind && node.APIVersion == apiVersion {
|
||||
pods = append(pods, buildResourceItem(res, querytypes.Workload{
|
||||
APIVersion: parent.APIVersion,
|
||||
Kind: parent.Kind,
|
||||
Name: parent.Name,
|
||||
Namespace: parent.Namespace,
|
||||
}, node.Object))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func buildResourceItem(res querytypes.AppliedResource, workload querytypes.Workload, object *unstructured.Unstructured) querytypes.ResourceItem {
|
||||
return querytypes.ResourceItem{
|
||||
Cluster: res.Cluster,
|
||||
Workload: workload,
|
||||
Component: res.Component,
|
||||
Object: object,
|
||||
PublishVersion: func() string {
|
||||
if object.GetAnnotations()[oam.AnnotationPublishVersion] != "" {
|
||||
return object.GetAnnotations()[oam.AnnotationPublishVersion]
|
||||
}
|
||||
return res.PublishVersion
|
||||
}(),
|
||||
DeployVersion: func() string {
|
||||
if object.GetAnnotations()[oam.AnnotationDeployVersion] != "" {
|
||||
return object.GetAnnotations()[oam.AnnotationDeployVersion]
|
||||
}
|
||||
return res.DeployVersion
|
||||
}(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Copyright 2022. The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package query
|
||||
@@ -0,0 +1,28 @@
|
||||
// terraform.cue
|
||||
#LoadTerraformComponents: {
|
||||
#provider: "terraform"
|
||||
#do: "load-terraform-components"
|
||||
|
||||
$returns: {
|
||||
outputs: {
|
||||
components: [...#Component]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#GetConnectionStatus: {
|
||||
#provider: "terraform"
|
||||
#do: "get-connection-status"
|
||||
|
||||
$params: {
|
||||
inputs: {
|
||||
componentName: string
|
||||
}
|
||||
}
|
||||
|
||||
$returns: {
|
||||
outputs: {
|
||||
healthy?: bool
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
cuexruntime "github.com/kubevela/pkg/cue/cuex/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
// Outputs is the output parameters for Terraform components.
|
||||
type Outputs[T any] struct {
|
||||
Outputs T `json:"outputs"`
|
||||
}
|
||||
|
||||
// Inputs is the input parameters for Terraform components.
|
||||
type Inputs[T any] struct {
|
||||
Inputs T `json:"inputs"`
|
||||
}
|
||||
|
||||
// ComponentVars is the input parameters for LoadTerraformComponents.
|
||||
type ComponentVars struct {
|
||||
Components []common.ApplicationComponent `json:"components"`
|
||||
}
|
||||
|
||||
// ComponentReturns is the return value for LoadTerraformComponents.
|
||||
type ComponentReturns = oamprovidertypes.Returns[Outputs[ComponentVars]]
|
||||
|
||||
// LoadTerraformComponents loads Terraform components.
|
||||
func LoadTerraformComponents(ctx context.Context, params *oamprovidertypes.Params[any]) (*ComponentReturns, error) {
|
||||
res := &ComponentReturns{
|
||||
Returns: Outputs[ComponentVars]{
|
||||
Outputs: ComponentVars{
|
||||
Components: make([]common.ApplicationComponent, 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, comp := range params.App.Spec.Components {
|
||||
wl, err := params.WorkloadRender(ctx, comp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to render component into workload")
|
||||
}
|
||||
if wl.CapabilityCategory != types.TerraformCategory {
|
||||
continue
|
||||
}
|
||||
res.Returns.Outputs.Components = append(res.Returns.Outputs.Components, comp)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ComponentNameVars is the input parameters for GetConnectionStatus.
|
||||
type ComponentNameVars struct {
|
||||
ComponentName string `json:"componentName"`
|
||||
}
|
||||
|
||||
// ConnectionParams is the input parameters for GetConnectionStatus.
|
||||
type ConnectionParams = oamprovidertypes.Params[Inputs[ComponentNameVars]]
|
||||
|
||||
// ConnectionResult is the result for connection status.
|
||||
type ConnectionResult struct {
|
||||
Healthy bool `json:"healthy"`
|
||||
}
|
||||
|
||||
// ConnectionReturns is the return value for connection status.
|
||||
type ConnectionReturns = oamprovidertypes.Returns[Outputs[ConnectionResult]]
|
||||
|
||||
// GetConnectionStatus returns the connection status of a component.
|
||||
func GetConnectionStatus(_ context.Context, params *ConnectionParams) (*ConnectionReturns, error) {
|
||||
app := params.RuntimeParams.App
|
||||
componentName := params.Params.Inputs.ComponentName
|
||||
if componentName == "" {
|
||||
return nil, fmt.Errorf("componentName is required")
|
||||
}
|
||||
for _, svc := range app.Status.Services {
|
||||
if svc.Name == componentName {
|
||||
return &ConnectionReturns{
|
||||
Returns: Outputs[ConnectionResult]{
|
||||
Outputs: ConnectionResult{
|
||||
Healthy: svc.Healthy,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return &ConnectionReturns{
|
||||
Returns: Outputs[ConnectionResult]{
|
||||
Outputs: ConnectionResult{
|
||||
Healthy: false,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
//go:embed terraform.cue
|
||||
var template string
|
||||
|
||||
// GetTemplate returns the cue template.
|
||||
func GetTemplate() string {
|
||||
return template
|
||||
}
|
||||
|
||||
// GetProviders returns the cue providers.
|
||||
func GetProviders() map[string]cuexruntime.ProviderFn {
|
||||
return map[string]cuexruntime.ProviderFn{
|
||||
"load-terraform-components": oamprovidertypes.GenericProviderFn[any, ComponentReturns](LoadTerraformComponents),
|
||||
"get-connection-status": oamprovidertypes.GenericProviderFn[Inputs[ComponentNameVars], ConnectionReturns](GetConnectionStatus),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
)
|
||||
|
||||
func fakeWorkloadRenderer(_ context.Context, comp apicommon.ApplicationComponent) (*appfile.Component, error) {
|
||||
if strings.HasPrefix(comp.Name, "error") {
|
||||
return nil, errors.New(comp.Name)
|
||||
}
|
||||
if strings.HasPrefix(comp.Name, "terraform") {
|
||||
return &appfile.Component{CapabilityCategory: types.TerraformCategory}, nil
|
||||
}
|
||||
return &appfile.Component{CapabilityCategory: types.CUECategory}, nil
|
||||
}
|
||||
|
||||
func TestLoadTerraformComponents(t *testing.T) {
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
r.NoError(v1beta1.AddToScheme(scheme))
|
||||
cli := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
terraformCD := &v1beta1.ComponentDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "terraform"},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Schematic: &apicommon.Schematic{
|
||||
Terraform: &apicommon.Terraform{},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, cli.Create(ctx, terraformCD))
|
||||
cueCD := &v1beta1.ComponentDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cue"},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Schematic: &apicommon.Schematic{
|
||||
CUE: &apicommon.CUE{},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, cli.Create(ctx, cueCD))
|
||||
|
||||
testCases := []struct {
|
||||
Inputs []apicommon.ApplicationComponent
|
||||
HasError bool
|
||||
Outputs []apicommon.ApplicationComponent
|
||||
}{
|
||||
{
|
||||
Inputs: []apicommon.ApplicationComponent{{Name: "error"}},
|
||||
HasError: true,
|
||||
},
|
||||
{
|
||||
Inputs: []apicommon.ApplicationComponent{
|
||||
{Name: "terraform-1", Type: "terraform"},
|
||||
{Name: "cue", Type: "cue"},
|
||||
{Name: "terraform-2", Type: "terraform"},
|
||||
},
|
||||
Outputs: []apicommon.ApplicationComponent{
|
||||
{Name: "terraform-1", Type: "terraform"},
|
||||
{Name: "terraform-2", Type: "terraform"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Inputs: []apicommon.ApplicationComponent{{Name: "cue", Type: "cue"}},
|
||||
Outputs: []apicommon.ApplicationComponent{},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
app := &v1beta1.Application{}
|
||||
app.Spec.Components = testCase.Inputs
|
||||
res, err := LoadTerraformComponents(ctx, &oamprovidertypes.Params[any]{
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
WorkloadRender: fakeWorkloadRenderer,
|
||||
App: app,
|
||||
},
|
||||
})
|
||||
if testCase.HasError {
|
||||
r.Error(err)
|
||||
continue
|
||||
}
|
||||
r.NoError(err)
|
||||
r.Equal(testCase.Outputs, res.Returns.Outputs.Components)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConnectionStatus(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
r := require.New(t)
|
||||
testCases := []struct {
|
||||
ComponentName string
|
||||
Services []apicommon.ApplicationComponentStatus
|
||||
Healthy bool
|
||||
Error string
|
||||
}{{
|
||||
ComponentName: "",
|
||||
Error: "componentName is required",
|
||||
}, {
|
||||
ComponentName: "comp",
|
||||
Services: []apicommon.ApplicationComponentStatus{{
|
||||
Name: "not-comp",
|
||||
Healthy: true,
|
||||
}},
|
||||
Healthy: false,
|
||||
}, {
|
||||
ComponentName: "comp",
|
||||
Services: []apicommon.ApplicationComponentStatus{{
|
||||
Name: "not-comp",
|
||||
Healthy: true,
|
||||
}, {
|
||||
Name: "comp",
|
||||
Healthy: true,
|
||||
}},
|
||||
Healthy: true,
|
||||
}, {
|
||||
ComponentName: "comp",
|
||||
Services: []apicommon.ApplicationComponentStatus{{
|
||||
Name: "not-comp",
|
||||
Healthy: true,
|
||||
}, {
|
||||
Name: "comp",
|
||||
Healthy: false,
|
||||
}},
|
||||
Healthy: false,
|
||||
}}
|
||||
for _, testCase := range testCases {
|
||||
app := &v1beta1.Application{}
|
||||
app.Status.Services = testCase.Services
|
||||
res, err := GetConnectionStatus(ctx, &ConnectionParams{
|
||||
Params: Inputs[ComponentNameVars]{
|
||||
Inputs: ComponentNameVars{
|
||||
ComponentName: testCase.ComponentName,
|
||||
},
|
||||
},
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
App: app,
|
||||
},
|
||||
})
|
||||
if testCase.Error != "" {
|
||||
r.Error(err)
|
||||
r.Contains(err.Error(), testCase.Error)
|
||||
continue
|
||||
}
|
||||
r.NoError(err)
|
||||
r.Equal(testCase.Healthy, res.Returns.Outputs.Healthy)
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ type RuntimeParams struct {
|
||||
KubeHandlers *providertypes.KubeHandlers
|
||||
KubeClient client.Client
|
||||
KubeConfig *rest.Config
|
||||
FieldLabel string
|
||||
}
|
||||
|
||||
// OAMParams is the legacy oam input parameters of a provider.
|
||||
@@ -80,6 +81,44 @@ type OAMParams[T any] struct {
|
||||
RuntimeParams
|
||||
}
|
||||
|
||||
// Params is the input parameters of a provider.
|
||||
type Params[T any] struct {
|
||||
Params T `json:"$params"`
|
||||
RuntimeParams
|
||||
}
|
||||
|
||||
// Returns is the returns of a provider.
|
||||
type Returns[T any] struct {
|
||||
Returns T `json:"$returns"`
|
||||
}
|
||||
|
||||
// GenericProviderFn is the provider function
|
||||
type GenericProviderFn[T any, U any] func(context.Context, *Params[T]) (*U, error)
|
||||
|
||||
// Call marshal value into json and decode into underlying function input
|
||||
// parameters, then fill back the returned output value
|
||||
func (fn GenericProviderFn[T, U]) Call(ctx context.Context, value cue.Value) (cue.Value, error) {
|
||||
type p struct {
|
||||
Params T `json:"$params"`
|
||||
}
|
||||
params := new(p)
|
||||
bs, err := value.MarshalJSON()
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
if err = json.Unmarshal(bs, params); err != nil {
|
||||
return value, err
|
||||
}
|
||||
runtimeParams := RuntimeParamsFrom(ctx)
|
||||
label, _ := value.Label()
|
||||
runtimeParams.FieldLabel = label
|
||||
ret, err := fn(ctx, &Params[T]{Params: params.Params, RuntimeParams: runtimeParams})
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value.FillPath(cue.ParsePath(""), ret), nil
|
||||
}
|
||||
|
||||
// OAMGenericProviderFn is the legacy oam provider function
|
||||
type OAMGenericProviderFn[T any, U any] func(context.Context, *OAMParams[T]) (*U, error)
|
||||
|
||||
@@ -95,6 +134,8 @@ func (fn OAMGenericProviderFn[T, U]) Call(ctx context.Context, value cue.Value)
|
||||
return value, err
|
||||
}
|
||||
runtimeParams := RuntimeParamsFrom(ctx)
|
||||
label, _ := value.Label()
|
||||
runtimeParams.FieldLabel = label
|
||||
ret, err := fn(ctx, &OAMParams[T]{Params: *params, RuntimeParams: runtimeParams})
|
||||
if err != nil {
|
||||
return value, err
|
||||
@@ -102,6 +143,16 @@ func (fn OAMGenericProviderFn[T, U]) Call(ctx context.Context, value cue.Value)
|
||||
return value.FillPath(cue.ParsePath(""), ret), nil
|
||||
}
|
||||
|
||||
// NativeProviderFn is the legacy native provider function
|
||||
type NativeProviderFn func(context.Context, *Params[cue.Value]) (cue.Value, error)
|
||||
|
||||
// Call marshal value into json and decode into underlying function input
|
||||
// parameters, then fill back the returned output value
|
||||
func (fn NativeProviderFn) Call(ctx context.Context, value cue.Value) (cue.Value, error) {
|
||||
runtimeParams := RuntimeParamsFrom(ctx)
|
||||
return fn(ctx, &Params[cue.Value]{Params: value, RuntimeParams: runtimeParams})
|
||||
}
|
||||
|
||||
// OAMNativeProviderFn is the legacy oam native provider function
|
||||
type OAMNativeProviderFn func(context.Context, *OAMParams[cue.Value]) (cue.Value, error)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user