mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
Feat: envbinding support cloud resource deploy and share (#2734)
* Feat: envbinding support cloud resource deploy and bind * Fix: change bind-cloud-resource to share-cloud-resource
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/deploy-cloud-resource.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Deploy cloud resource and bind secret to clusters
|
||||
name: deploy-cloud-resource
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
app: op.#DeployCloudResource & {
|
||||
env: parameter.env
|
||||
policy: parameter.policy
|
||||
// context.namespace indicates the namespace of the app
|
||||
namespace: context.namespace
|
||||
// context.namespace indicates the name of the app
|
||||
name: context.name
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ spec:
|
||||
namespace: context.namespace
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Declare the name of the policy
|
||||
policy: string
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/share-cloud-resource.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Sync secrets created by terraform component to runtime clusters so that runtime clusters can share the created cloud resource.
|
||||
name: share-cloud-resource
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
app: op.#ShareCloudResource & {
|
||||
env: parameter.env
|
||||
policy: parameter.policy
|
||||
placements: parameter.placements
|
||||
// context.namespace indicates the namespace of the app
|
||||
namespace: context.namespace
|
||||
// context.namespace indicates the name of the app
|
||||
name: context.name
|
||||
}
|
||||
parameter: {
|
||||
env: string
|
||||
// +usage=Declare the location to bind
|
||||
placements: [...{
|
||||
namespace?: string
|
||||
cluster?: string
|
||||
}]
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/bind-cloud-resource.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Sync secrets created by terraform component to runtime clusters
|
||||
name: bind-cloud-resource
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
app: op.#BindCloudResource & {
|
||||
env: parameter.env
|
||||
policy: parameter.policy
|
||||
placements: parameter.placements
|
||||
// context.namespace indicates the namespace of the app
|
||||
namespace: context.namespace
|
||||
// context.namespace indicates the name of the app
|
||||
name: context.name
|
||||
}
|
||||
parameter: {
|
||||
env: string
|
||||
// +usage=Declare the location to bind
|
||||
placements: [...{
|
||||
namespace?: string
|
||||
cluster?: string
|
||||
}]
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/deploy-cloud-resource.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Deploy cloud resource and bind secret to clusters
|
||||
name: deploy-cloud-resource
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
app: op.#DeployCloudResource & {
|
||||
env: parameter.env
|
||||
policy: parameter.policy
|
||||
// context.namespace indicates the namespace of the app
|
||||
namespace: context.namespace
|
||||
// context.namespace indicates the name of the app
|
||||
name: context.name
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
|
||||
# Definition source cue file: vela-templates/definitions/internal/share-cloud-resource.cue
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: WorkflowStepDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Sync secrets created by terraform component to runtime clusters so that runtime clusters can share the created cloud resource.
|
||||
name: share-cloud-resource
|
||||
namespace: {{.Values.systemDefinitionNamespace}}
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
app: op.#ShareCloudResource & {
|
||||
env: parameter.env
|
||||
policy: parameter.policy
|
||||
placements: parameter.placements
|
||||
// context.namespace indicates the namespace of the app
|
||||
namespace: context.namespace
|
||||
// context.namespace indicates the name of the app
|
||||
name: context.name
|
||||
}
|
||||
parameter: {
|
||||
env: string
|
||||
// +usage=Declare the location to bind
|
||||
placements: [...{
|
||||
namespace?: string
|
||||
cluster?: string
|
||||
}]
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: rds-app
|
||||
namespace: project-1
|
||||
spec:
|
||||
components:
|
||||
- name: db
|
||||
type: alibaba-rds
|
||||
properties:
|
||||
instance_name: db
|
||||
account_name: kubevela
|
||||
password: my-password
|
||||
writeConnectionSecretToRef:
|
||||
name: project-1-rds-conn-credential
|
||||
policies:
|
||||
- name: env-policy
|
||||
type: env-binding
|
||||
properties:
|
||||
envs:
|
||||
# 部署 RDS 给杭州集群
|
||||
- name: hangzhou
|
||||
placement:
|
||||
clusterSelector:
|
||||
name: cluster-hangzhou
|
||||
patch:
|
||||
components:
|
||||
- name: db
|
||||
type: alibaba-rds
|
||||
properties:
|
||||
# region: hangzhou
|
||||
instance_name: hangzhou_db
|
||||
# 部署 RDS 给香港集群
|
||||
- name: hongkong
|
||||
placement:
|
||||
clusterSelector:
|
||||
name: cluster-hongkong
|
||||
namespaceSelector:
|
||||
name: hk-project-1
|
||||
patch:
|
||||
components:
|
||||
- name: db
|
||||
type: alibaba-rds
|
||||
properties:
|
||||
# region: hongkong
|
||||
instance_name: hongkong_db
|
||||
writeConnectionSecretToRef:
|
||||
name: hk-project-rds-credential
|
||||
|
||||
workflow:
|
||||
steps:
|
||||
# 部署 RDS 给杭州区用
|
||||
- name: deploy-hangzhou-rds
|
||||
type: deploy-cloud-resource
|
||||
properties:
|
||||
env: hangzhou
|
||||
# 将给杭州区用的 RDS 共享给北京区
|
||||
- name: share-hangzhou-rds-to-beijing
|
||||
type: share-cloud-resource
|
||||
properties:
|
||||
env: hangzhou
|
||||
placements:
|
||||
- cluster: cluster-beijing
|
||||
# 部署 RDS 给香港区用
|
||||
- name: deploy-hongkong-rds
|
||||
type: deploy-cloud-resource
|
||||
properties:
|
||||
env: hongkong
|
||||
# 将给香港区用的 RDS 共享给香港区其他项目用
|
||||
- name: share-hongkong-rds-to-other-namespace
|
||||
type: share-cloud-resource
|
||||
properties:
|
||||
env: hongkong
|
||||
placements:
|
||||
- cluster: cluster-hongkong
|
||||
namespace: hk-project-2
|
||||
- cluster: cluster-hongkong
|
||||
namespace: hk-project-3
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
common2 "github.com/oam-dev/kubevela/pkg/controller/common"
|
||||
core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/assemble"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
@@ -88,7 +89,7 @@ type Reconciler struct {
|
||||
// Reconcile process app event
|
||||
// nolint:gocyclo
|
||||
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
ctx, cancel := context.WithTimeout(ctx, common2.ReconcileTimeout)
|
||||
defer cancel()
|
||||
|
||||
logCtx := monitorContext.NewTraceContext(ctx, "").AddTag("application", req.String(), "controller", "application")
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/kube"
|
||||
multiclusterProvider "github.com/oam-dev/kubevela/pkg/workflow/providers/multicluster"
|
||||
oamProvider "github.com/oam-dev/kubevela/pkg/workflow/providers/oam"
|
||||
terraformProvider "github.com/oam-dev/kubevela/pkg/workflow/providers/terraform"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/tasks"
|
||||
wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types"
|
||||
)
|
||||
@@ -54,6 +55,9 @@ func (h *AppHandler) GenerateApplicationSteps(ctx context.Context,
|
||||
appParser, appRev, af), h.renderComponentFunc(appParser, appRev, af))
|
||||
taskDiscover := tasks.NewTaskDiscover(handlerProviders, h.r.pd, h.r.Client, h.r.dm)
|
||||
multiclusterProvider.Install(handlerProviders, h.r.Client, app)
|
||||
terraformProvider.Install(handlerProviders, app, func(comp common.ApplicationComponent) (*appfile.Workload, error) {
|
||||
return appParser.ParseWorkloadFromRevision(comp, appRev)
|
||||
})
|
||||
var tasks []wfTypes.TaskRunner
|
||||
for _, step := range af.WorkflowSteps {
|
||||
options := &wfTypes.GeneratorOptions{
|
||||
|
||||
@@ -107,6 +107,10 @@ import (
|
||||
|
||||
#ApplyEnvBindApp: multicluster.#ApplyEnvBindApp
|
||||
|
||||
#DeployCloudResource: terraform.#DeployCloudResource
|
||||
|
||||
#ShareCloudResource: terraform.#ShareCloudResource
|
||||
|
||||
#LoadPolicies: oam.#LoadPolicies
|
||||
|
||||
#ListClusters: multicluster.#ListClusters
|
||||
|
||||
@@ -68,35 +68,64 @@
|
||||
...
|
||||
}
|
||||
|
||||
#ApplyEnvBindApp: {
|
||||
#do: "steps"
|
||||
|
||||
env: string
|
||||
policy: string
|
||||
app: string
|
||||
namespace: string
|
||||
#LoadEnvBindingEnv: #Steps & {
|
||||
inputs: {
|
||||
env: string
|
||||
policy: string
|
||||
}
|
||||
|
||||
loadPolicies: oam.#LoadPolicies @step(1)
|
||||
loadPolicy: loadPolicies.value["\(policy)"]
|
||||
policy_: string
|
||||
if inputs.policy == "" {
|
||||
envBindingPolicies: [ for k, v in loadPolicies.value if v.type == "env-binding" {k}]
|
||||
policy_: envBindingPolicies[0]
|
||||
}
|
||||
if inputs.policy != "" {
|
||||
policy_: inputs.policy
|
||||
}
|
||||
|
||||
loadPolicy: loadPolicies.value["\(policy_)"]
|
||||
envMap: {
|
||||
for ev in loadPolicy.properties.envs {
|
||||
"\(ev.name)": ev
|
||||
}
|
||||
...
|
||||
}
|
||||
envConfig: envMap["\(env)"]
|
||||
envConfig_: envMap["\(inputs.env)"]
|
||||
|
||||
placementDecisions: multicluster.#MakePlacementDecisions & {
|
||||
outputs: {
|
||||
policy: policy_
|
||||
envConfig: envConfig_
|
||||
}
|
||||
}
|
||||
|
||||
#PrepareEnvBinding: #Steps & {
|
||||
inputs: {
|
||||
env: string
|
||||
policy: string
|
||||
}
|
||||
env_: inputs.env
|
||||
policy_: inputs.policy
|
||||
|
||||
loadEnv: #LoadEnvBindingEnv & {
|
||||
inputs: {
|
||||
policyName: policy
|
||||
envName: env
|
||||
env: env_
|
||||
policy: policy_
|
||||
}
|
||||
} @step(1)
|
||||
envConfig: loadEnv.outputs.envConfig
|
||||
|
||||
placementDecisions: #MakePlacementDecisions & {
|
||||
inputs: {
|
||||
policyName: loadEnv.outputs.policy
|
||||
envName: env_
|
||||
placement: envConfig.placement
|
||||
}
|
||||
} @step(2)
|
||||
|
||||
patchedApp: multicluster.#PatchApplication & {
|
||||
patchedApp: #PatchApplication & {
|
||||
inputs: {
|
||||
envName: env
|
||||
envName: env_
|
||||
if envConfig.selector != _|_ {
|
||||
selector: envConfig.selector
|
||||
}
|
||||
@@ -106,10 +135,32 @@
|
||||
}
|
||||
} @step(3)
|
||||
|
||||
components: patchedApp.outputs.spec.components
|
||||
apply: #Steps & {
|
||||
for decision in placementDecisions.outputs.decisions {
|
||||
for key, comp in components {
|
||||
outputs: {
|
||||
components: patchedApp.outputs.spec.components
|
||||
decisions: placementDecisions.outputs.decisions
|
||||
}
|
||||
}
|
||||
|
||||
#ApplyEnvBindApp: {
|
||||
#do: "steps"
|
||||
|
||||
env: string
|
||||
policy: string
|
||||
app: string
|
||||
namespace: string
|
||||
|
||||
env_: env
|
||||
policy_: policy
|
||||
prepare: #PrepareEnvBinding & {
|
||||
inputs: {
|
||||
env: env_
|
||||
policy: policy_
|
||||
}
|
||||
} @step(1)
|
||||
|
||||
apply: #Steps & {
|
||||
for decision in prepare.outputs.decisions {
|
||||
for key, comp in prepare.outputs.components {
|
||||
"\(decision.cluster)-\(decision.namespace)-\(key)": #ApplyComponent & {
|
||||
value: comp
|
||||
if decision.cluster != _|_ {
|
||||
@@ -118,7 +169,7 @@
|
||||
if decision.namespace != _|_ {
|
||||
namespace: decision.namespace
|
||||
}
|
||||
} @step(4)
|
||||
} @step(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
#LoadTerraformComponents: {
|
||||
#provider: "terraform"
|
||||
#do: "load-terraform-components"
|
||||
|
||||
outputs: {
|
||||
components: [...multicluster.#Component]
|
||||
}
|
||||
}
|
||||
|
||||
#GetConnectionStatus: {
|
||||
#provider: "terraform"
|
||||
#do: "get-connection-status"
|
||||
|
||||
inputs: {
|
||||
componentName: string
|
||||
}
|
||||
|
||||
outputs: {
|
||||
healthy?: bool
|
||||
}
|
||||
}
|
||||
|
||||
#PrepareTerraformEnvBinding: #Steps & {
|
||||
inputs: {
|
||||
env: string
|
||||
policy: string
|
||||
}
|
||||
env_: inputs.env
|
||||
policy_: inputs.policy
|
||||
|
||||
prepare: multicluster.#PrepareEnvBinding & {
|
||||
inputs: {
|
||||
env: env_
|
||||
policy: policy_
|
||||
}
|
||||
} @step(1)
|
||||
loadTerraformComponents: #LoadTerraformComponents @step(2)
|
||||
terraformComponentMap: {
|
||||
for _, comp in loadTerraformComponents.outputs.components {
|
||||
"\(comp.name)": comp
|
||||
}
|
||||
...
|
||||
}
|
||||
components_: [ for comp in prepare.outputs.components if terraformComponentMap["\(comp.name)"] != _|_ {comp}]
|
||||
outputs: {
|
||||
components: components_
|
||||
decisions: prepare.outputs.decisions
|
||||
}
|
||||
}
|
||||
|
||||
#loadSecretInfo: {
|
||||
component: {...}
|
||||
appNamespace: string
|
||||
name: string
|
||||
namespace: string
|
||||
env: string
|
||||
if component.properties != _|_ && component.properties.writeConnectionSecretToRef != _|_ {
|
||||
if component.properties.writeConnectionSecretToRef.name != _|_ {
|
||||
name: component.properties.writeConnectionSecretToRef.name
|
||||
}
|
||||
if component.properties.writeConnectionSecretToRef.name == _|_ {
|
||||
name: component.name
|
||||
}
|
||||
if component.properties.writeConnectionSecretToRef.namespace != _|_ {
|
||||
namespace: component.properties.writeConnectionSecretToRef.namespace
|
||||
}
|
||||
if component.properties.writeConnectionSecretToRef.namespace == _|_ {
|
||||
namespace: appNamespace
|
||||
}
|
||||
}
|
||||
envName: "\(name)-\(env)"
|
||||
}
|
||||
|
||||
#bindTerraformComponentToCluster: #Steps & {
|
||||
comp: {...}
|
||||
secret: {...}
|
||||
env: string
|
||||
decisions: [...{...}]
|
||||
|
||||
status: terraform.#GetConnectionStatus & {
|
||||
inputs: componentName: "\(comp.name)-\(env)"
|
||||
} @step(1)
|
||||
|
||||
read: kube.#Read & {
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: secret.envName
|
||||
namespace: secret.namespace
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
} @step(2)
|
||||
|
||||
wait: {
|
||||
#do: "wait"
|
||||
continue: status.outputs.healthy && read.err == _|_
|
||||
} @step(3)
|
||||
|
||||
sync: #Steps & {
|
||||
for decision in decisions {
|
||||
"\(decision.cluster)-\(decision.namespace)": kube.#Apply & {
|
||||
cluster: decision.cluster
|
||||
value: {
|
||||
apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
metadata: {
|
||||
name: secret.name
|
||||
if decision.namespace != _|_ && decision.namespace != "" {
|
||||
namespace: decision.namespace
|
||||
}
|
||||
if decision.namespace == _|_ || decision.namespace == "" {
|
||||
namespace: secret.namespace
|
||||
}
|
||||
...
|
||||
}
|
||||
type: "Opaque"
|
||||
data: read.value.data
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
} @step(4)
|
||||
}
|
||||
|
||||
#DeployCloudResource: {
|
||||
#do: "steps"
|
||||
|
||||
env: string
|
||||
name: string
|
||||
policy: string
|
||||
namespace: string
|
||||
|
||||
env_: env
|
||||
policy_: policy
|
||||
prepareDeploy: #PrepareTerraformEnvBinding & {
|
||||
inputs: {
|
||||
env: env_
|
||||
policy: policy_
|
||||
}
|
||||
} @step(1)
|
||||
|
||||
deploy: #Steps & {
|
||||
for comp in prepareDeploy.outputs.components {
|
||||
"\(comp.name)": #Steps & {
|
||||
|
||||
secretMeta: #loadSecretInfo & {
|
||||
component: comp
|
||||
env: env_
|
||||
appNamespace: namespace
|
||||
}
|
||||
|
||||
apply: #ApplyComponent & {
|
||||
value: {
|
||||
name: "\(comp.name)-\(env)"
|
||||
properties: {
|
||||
writeConnectionSecretToRef: {
|
||||
name: secretMeta.envName
|
||||
namespace: secretMeta.namespace
|
||||
}
|
||||
if comp.properties != _|_ {
|
||||
for k, v in comp.properties {
|
||||
if k != "writeConnectionSecretToRef" {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
for k, v in comp {
|
||||
if k != "name" && k != "properties" {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
} @step(1)
|
||||
|
||||
comp_: comp
|
||||
bind: #bindTerraformComponentToCluster & {
|
||||
comp: comp_
|
||||
secret: secretMeta
|
||||
env: env_
|
||||
decisions: prepareDeploy.outputs.decisions
|
||||
} @step(2)
|
||||
|
||||
secret: bind.read.value
|
||||
|
||||
update: kube.#Apply & {
|
||||
value: {
|
||||
metadata: {
|
||||
for k, v in secret.metadata {
|
||||
if k != "labels" {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
labels: {
|
||||
"app.oam.dev/name": name
|
||||
"app.oam.dev/namespace": namespace
|
||||
"app.oam.dev/component": comp.name
|
||||
"app.oam.dev/env-name": env
|
||||
"app.oam.dev/sync-alias": secretMeta.name
|
||||
if secret.metadata.labels != _|_ {
|
||||
for k, v in secret.metadata.labels {
|
||||
if k != "app.oam.dev/name" && k != "app.oam.dev/sync-alias" && k != "app.oam.dev/env-name" {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
for k, v in secret {
|
||||
if k != "metadata" {
|
||||
"\(k)": v
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
} @step(6)
|
||||
}
|
||||
}
|
||||
...
|
||||
} @step(2)
|
||||
}
|
||||
|
||||
#ShareCloudResource: {
|
||||
#do: "steps"
|
||||
|
||||
env: string
|
||||
name: string
|
||||
policy: string
|
||||
namespace: string
|
||||
namespace_: namespace
|
||||
placements: [...multicluster.#PlacementDecision]
|
||||
|
||||
env_: env
|
||||
policy_: policy
|
||||
prepareBind: #PrepareTerraformEnvBinding & {
|
||||
inputs: {
|
||||
env: env_
|
||||
policy: policy_
|
||||
}
|
||||
} @step(1)
|
||||
|
||||
decisions_: [ for placement in placements {
|
||||
namespace: *"" | string
|
||||
if placement.namespace != _|_ {
|
||||
namespace: placement.namespace
|
||||
}
|
||||
if placement.namespace == _|_ {
|
||||
namespace: namespace_
|
||||
}
|
||||
cluster: *"local" | string
|
||||
if placement.cluster != _|_ {
|
||||
cluster: placement.cluster
|
||||
}
|
||||
}]
|
||||
|
||||
deploy: #Steps & {
|
||||
for comp in prepareBind.outputs.components {
|
||||
"\(comp.name)": #Steps & {
|
||||
secretMeta: #loadSecretInfo & {
|
||||
component: comp
|
||||
env: env_
|
||||
appNamespace: namespace
|
||||
}
|
||||
comp_: comp
|
||||
bind: #bindTerraformComponentToCluster & {
|
||||
comp: comp_
|
||||
secret: secretMeta
|
||||
env: env_
|
||||
decisions: decisions_
|
||||
} @step(1)
|
||||
}
|
||||
}
|
||||
} @step(2)
|
||||
}
|
||||
@@ -19,12 +19,11 @@ package kube
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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 (
|
||||
"github.com/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/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
wfContext "github.com/oam-dev/kubevela/pkg/workflow/context"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
||||
wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ProviderName is provider name for install.
|
||||
ProviderName = "terraform"
|
||||
)
|
||||
|
||||
// WorkloadRenderer renderer to render application component into workload
|
||||
type WorkloadRenderer func(comp common.ApplicationComponent) (*appfile.Workload, error)
|
||||
|
||||
type provider struct {
|
||||
app *v1beta1.Application
|
||||
renderer WorkloadRenderer
|
||||
}
|
||||
|
||||
func (p *provider) LoadTerraformComponents(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error {
|
||||
var components []common.ApplicationComponent
|
||||
for _, comp := range p.app.Spec.Components {
|
||||
wl, err := p.renderer(comp)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to render component into workload")
|
||||
}
|
||||
if wl.CapabilityCategory != types.TerraformCategory {
|
||||
continue
|
||||
}
|
||||
components = append(components, comp)
|
||||
}
|
||||
return v.FillObject(components, "outputs", "components")
|
||||
}
|
||||
|
||||
func (p *provider) GetConnectionStatus(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error {
|
||||
componentName, err := v.GetString("inputs", "componentName")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get component name")
|
||||
}
|
||||
for _, svc := range p.app.Status.Services {
|
||||
if svc.Name == componentName {
|
||||
return v.FillObject(svc.Healthy, "outputs", "healthy")
|
||||
}
|
||||
}
|
||||
return v.FillObject(false, "outputs", "healthy")
|
||||
}
|
||||
|
||||
// Install register handlers to provider discover.
|
||||
func Install(p providers.Providers, app *v1beta1.Application, renderer WorkloadRenderer) {
|
||||
prd := &provider{app: app, renderer: renderer}
|
||||
p.Register(ProviderName, map[string]providers.Handler{
|
||||
"load-terraform-components": prd.LoadTerraformComponents,
|
||||
"get-connection-status": prd.GetConnectionStatus,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
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"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/mock"
|
||||
)
|
||||
|
||||
func fakeWorkloadRenderer(comp apicommon.ApplicationComponent) (*appfile.Workload, error) {
|
||||
if strings.HasPrefix(comp.Name, "error") {
|
||||
return nil, errors.New(comp.Name)
|
||||
}
|
||||
if strings.HasPrefix(comp.Name, "terraform") {
|
||||
return &appfile.Workload{CapabilityCategory: types.TerraformCategory}, nil
|
||||
}
|
||||
return &appfile.Workload{CapabilityCategory: types.CUECategory}, nil
|
||||
}
|
||||
|
||||
func TestLoadTerraformComponents(t *testing.T) {
|
||||
r := require.New(t)
|
||||
testCases := []struct {
|
||||
Inputs []apicommon.ApplicationComponent
|
||||
HasError bool
|
||||
Outputs []apicommon.ApplicationComponent
|
||||
}{{
|
||||
Inputs: []apicommon.ApplicationComponent{{Name: "error"}},
|
||||
HasError: true,
|
||||
}, {
|
||||
Inputs: []apicommon.ApplicationComponent{{Name: "terraform-1"}, {Name: "cue"}, {Name: "terraform-2"}},
|
||||
Outputs: []apicommon.ApplicationComponent{{Name: "terraform-1"}, {Name: "terraform-2"}},
|
||||
}, {
|
||||
Inputs: []apicommon.ApplicationComponent{{Name: "cue"}},
|
||||
Outputs: []apicommon.ApplicationComponent{},
|
||||
}}
|
||||
for _, testCase := range testCases {
|
||||
app := &v1beta1.Application{}
|
||||
app.Spec.Components = testCase.Inputs
|
||||
p := &provider{
|
||||
app: app,
|
||||
renderer: fakeWorkloadRenderer,
|
||||
}
|
||||
act := &mock.Action{}
|
||||
v, err := value.NewValue("", nil, "")
|
||||
r.NoError(err)
|
||||
err = p.LoadTerraformComponents(nil, v, act)
|
||||
if testCase.HasError {
|
||||
r.Error(err)
|
||||
continue
|
||||
}
|
||||
r.NoError(err)
|
||||
outputs, err := v.LookupValue("outputs", "components")
|
||||
r.NoError(err)
|
||||
var comps []apicommon.ApplicationComponent
|
||||
r.NoError(outputs.UnmarshalTo(&comps))
|
||||
r.Equal(testCase.Outputs, comps)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConnectionStatus(t *testing.T) {
|
||||
r := require.New(t)
|
||||
testCases := []struct {
|
||||
ComponentName string
|
||||
Services []apicommon.ApplicationComponentStatus
|
||||
Healthy bool
|
||||
Error string
|
||||
}{{
|
||||
ComponentName: "",
|
||||
Error: "failed to get component name",
|
||||
}, {
|
||||
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
|
||||
p := &provider{
|
||||
app: app,
|
||||
renderer: fakeWorkloadRenderer,
|
||||
}
|
||||
act := &mock.Action{}
|
||||
v, err := value.NewValue("", nil, "")
|
||||
r.NoError(err)
|
||||
if testCase.ComponentName != "" {
|
||||
r.NoError(v.FillObject(map[string]string{"componentName": testCase.ComponentName}, "inputs"))
|
||||
}
|
||||
err = p.GetConnectionStatus(nil, v, act)
|
||||
if testCase.Error != "" {
|
||||
r.Error(err)
|
||||
r.Contains(err.Error(), testCase.Error)
|
||||
continue
|
||||
}
|
||||
r.NoError(err)
|
||||
healthy, err := v.GetBool("outputs", "healthy")
|
||||
r.NoError(err)
|
||||
r.Equal(testCase.Healthy, healthy)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
"deploy-cloud-resource": {
|
||||
type: "workflow-step"
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: "Deploy cloud resource and bind secret to clusters"
|
||||
}
|
||||
template: {
|
||||
app: op.#DeployCloudResource & {
|
||||
env: parameter.env
|
||||
policy: parameter.policy
|
||||
// context.namespace indicates the namespace of the app
|
||||
namespace: context.namespace
|
||||
// context.namespace indicates the name of the app
|
||||
name: context.name
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ template: {
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Declare the name of the policy
|
||||
policy: string
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
"share-cloud-resource": {
|
||||
type: "workflow-step"
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: "Sync secrets created by terraform component to runtime clusters so that runtime clusters can share the created cloud resource."
|
||||
}
|
||||
template: {
|
||||
app: op.#ShareCloudResource & {
|
||||
env: parameter.env
|
||||
policy: parameter.policy
|
||||
placements: parameter.placements
|
||||
// context.namespace indicates the namespace of the app
|
||||
namespace: context.namespace
|
||||
// context.namespace indicates the name of the app
|
||||
name: context.name
|
||||
}
|
||||
|
||||
parameter: {
|
||||
env: string
|
||||
// +usage=Declare the location to bind
|
||||
placements: [...{
|
||||
namespace?: string
|
||||
cluster?: string
|
||||
}]
|
||||
// +usage=Declare the name of the env-binding policy, if empty, the first env-binding policy will be used
|
||||
policy: *"" | string
|
||||
// +usage=Declare the name of the env in policy
|
||||
env: string
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user