KubeVela篇09:KubeVela工作流步骤CUE模版和Go代码是怎么关联的

原创 吴就业 184 0 2023-07-23

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/9e545bff4bd84b3ba88cbcfd0bf24de2

作者:吴就业
链接:https://wujiuye.com/article/9e545bff4bd84b3ba88cbcfd0bf24de2
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

从generator#GenerateApplicationSetps生成应用工作流步骤方法可以看到下面这样一段代码:

handlerProviders := providers.NewProviders()
kube.Install(handlerProviders, h.r.Client, appLabels, &kube.Handlers{
    Apply:  h.Dispatch,
    Delete: h.Delete,
})
configprovider.Install(handlerProviders, h.r.Client, func(ctx context.Context, resources []*unstructured.Unstructured, applyOptions []apply.ApplyOption) error {
    for _, res := range resources {
       res.SetLabels(util.MergeMapOverrideWithDst(res.GetLabels(), appLabels))
    }
    return h.resourceKeeper.Dispatch(ctx, resources, applyOptions)
})
oamProvider.Install(handlerProviders, app, af, h.r.Client, h.applyComponentFunc(
    appParser, appRev, af), h.renderComponentFunc(appParser, appRev, af))
pCtx := velaprocess.NewContext(generateContextDataFromApp(app, appRev.Name))
renderer := func(ctx context.Context, comp common.ApplicationComponent) (*appfile.Workload, error) {
    return appParser.ParseWorkloadFromRevisionAndClient(ctx, comp, appRev)
}
multiclusterProvider.Install(handlerProviders, h.r.Client, app, af,
    h.applyComponentFunc(appParser, appRev, af),
    h.checkComponentHealth(appParser, appRev, af),
    renderer)
terraformProvider.Install(handlerProviders, app, renderer)
query.Install(handlerProviders, h.r.Client, nil)

terraformProvider、multiclusterProvider、oamProvider、configprovider、kube这些provider的Install方法注册了很多操作处理方法。这些方法就是提供给CUE中调用的方法。

创建工作流步骤Task的流程

创建工作流步骤Task的流程: 创建工作流步骤Task的流程

创建一个TaskDiscover,调用TaskDiscover的GetTaskGenerator方法生成TaskGenerator。TaskDiscover则根据工作流步骤指定的Type,通过LoadTemplate加载WorkflowStepDefinition。

apply-component工作流步骤的定义:

# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/apply-component.cue
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
  annotations:
    custom.definition.oam.dev/category: Application Delivery
    definition.oam.dev/description: Apply a specific component and its corresponding traits in application
  labels:
    custom.definition.oam.dev/scope: Application
  name: apply-component
  namespace: vela-system
spec:
  schematic:
    cue:
      template: |
        parameter: {
            // +usage=Specify the component name to apply
            component: string
            // +usage=Specify the cluster
            cluster: *"" | string
        }

这个apply-component工作流步骤比较特殊,从generator#GenerateApplicationSetps方法也可以看出,apply-component会被转换为builtin-apply-component工作流步骤,代码如下:

StepConvertor: map[string]func(step workflowv1alpha1.WorkflowStep) (workflowv1alpha1.WorkflowStep, error){
    wfTypes.WorkflowStepTypeApplyComponent: func(lstep workflowv1alpha1.WorkflowStep) (workflowv1alpha1.WorkflowStep, error) {
       copierStep := lstep.DeepCopy()
       if err := convertStepProperties(copierStep, app); err != nil {
          return lstep, errors.WithMessage(err, "convert [apply-component]")
       }
       copierStep.Type = wfTypes.WorkflowStepTypeBuiltinApplyComponent
       return *copierStep, nil
    },
},

加载模版的流程:

generator#GenerateApplicationSetps方法中,调用generator#GenerateRunners方法,构造wfTypes.StepGeneratorOptions参数指定的TemplateLoader实现类为WorkflowStepTemplateRevisionLoader,代码如下:

runners, err := generator.GenerateRunners(ctx, instance, wfTypes.StepGeneratorOptions{
    ......
    // 指定模版加载器
    TemplateLoader: template.NewWorkflowStepTemplateRevisionLoader(appRev, h.r.dm),
    ......
})

LoadTemplate方法:

1.如果工作流步骤的类型为apply-component,转为builtin-apply-component工作流步骤,从源码目录的/workflow/template/static目录下加载CUE模版代码。

2.其它则调用loadCapabilityDefinition方法加载CUE模版代码:

return &WorkflowStepLoader{
         loadCapabilityDefinition: func(ctx context.Context, capName string) (*appfile.Template, error) {
         return appfile.LoadTemplateFromRevision(capName, types.TypeWorkflowStep, rev, dm)
        },
}

加载到CUE模版代码后,创建一个任务生成器(TaskGenerator),这个TaskGenerator做的事情:

1.根据模版,从工作流步骤解析出模版参数

2.创建任务:taskRunner

taskRunner做的事情:

1.执行一个pre hooks

2.执行工作流步骤

3.执行一些post hooks

Task执行流程

任务taskRunner中调用doSteps方法执行工作流步骤:

一、从CUE模版(工作流步骤都是CUE模版)中获取#do指定的方法,以及#provider指定的提供者。

那么怎么知道#do#provider指定的是什么呢?我们以apply-component工作流步骤和deploy工作流步骤为例,看看它们的#do#provider是什么。

apply-component工作流步骤CUE模版:

import (
  "vela/op"
)

oam: op.oam
// apply component and traits
apply: oam.#ApplyComponent & {
  value:   parameter.value
  cluster: parameter.cluster
}

if apply.output != _|_ {
  output: apply.output
}

if apply.outputs != _|_ {
  outputs: apply.outputs
}
parameter: {
  value: {...}
  cluster: *"" | string
}

deploy工作流步骤CUE模版:

import (
    "vela/op"
)

if parameter.auto == false {
    suspend: op.#Suspend & {message: "Waiting approval to the deploy step "(context.stepName)""}
}
deploy: op.#Deploy & {
    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.
    auto: *true | bool
    //+usage=Declare the policies that used for this deployment. If not specified, the components will be deployed to the hub cluster.
    policies: *[] | [...string]
    //+usage=Maximum number of concurrent delivered components.
    parallelism: *5 | int
    //+usage=If set false, this step will apply the components with the terraform workload.
    ignoreTerraformComponent: *true | bool
}

从两个CUE模版中,我们都看不到#provider#do,不过这两个模版都import了一个“vela/op”模版,我们先要找到这个vela/op模版。

我们找到kubevela源码项目的pkg/stdlib目录,这里就是vela/op的实现:

pkg/stdlib
-- pkgs
  -- multicluster.cue
  -- oam.cue
  -- query.cue
  -- terraform.cue
-- op.cue
-- packages.go
-- ql.cue

Import vela/op导入的不仅是op.cue这个模版,packages.go中将op.cuepkgs/multicluster.cuepkgs/oam.cuepkgs/terraform.cue这几个文件合并成一个后,形成vela/op

因此完整的代码应该是这样的:

#GetPlacementsFromTopologyPolicies: multicluster.#GetPlacementsFromTopologyPolicies

#Deploy: multicluster.#Deploy

#ApplyApplication: #Steps & {
        load:       oam.#LoadComponetsInOrder @step(1)
        components: #Steps & {
                for name, c in load.value {
                        "(name)": oam.#ApplyComponent & {
                                value: c
                        }
                }
        } @step(2)
}

// 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: #Steps & {
        load:       oam.#LoadComponetsInOrder @step(1)
        components: #Steps & {
                for name, c in load.value {
                        "(name)": oam.#ApplyComponent & {
                                value:       c
                                waitHealthy: false
                        }
                }
        } @step(2)
}

#ApplyComponent: oam.#ApplyComponent

#RenderComponent: oam.#RenderComponent

#ApplyComponentRemaining: #Steps & {
        // exceptions specify the resources not to apply.
        exceptions: [...string]
        exceptions_: {for c in exceptions {"(c)": true}}
        component: string

        load:   oam.#LoadComponets @step(1)
        render: #Steps & {
                rendered: oam.#RenderComponent & {
                        value: load.value[component]
                }
                comp: kube.#Apply & {
                        value: rendered.output
                }
                for name, c in rendered.outputs {
                        if exceptions_[name] == _|_ {
                                "(name)": kube.#Apply & {
                                        value: c
                                }
                        }
                }
        } @step(2)
}

#ApplyRemaining: #Steps & {
        // exceptions specify the resources not to apply.
        exceptions: [...string]
        exceptions_: {for c in exceptions {"(c)": true}}

        load:       oam.#LoadComponets @step(1)
        components: #Steps & {
                for name, c in load.value {
                        if exceptions_[name] == _|_ {
                                "(name)": oam.#ApplyComponent & {
                                        value: c
                                }
                        }

                }
        } @step(2)
}

#ApplyEnvBindApp: multicluster.#ApplyEnvBindApp

#DeployCloudResource: terraform.#DeployCloudResource

#ShareCloudResource: terraform.#ShareCloudResource

#LoadPolicies: oam.#LoadPolicies

#ListClusters: multicluster.#ListClusters

#MakePlacementDecisions: multicluster.#MakePlacementDecisions

#PatchApplication: multicluster.#PatchApplication

#Load: oam.#LoadComponets

#LoadInOrder: oam.#LoadComponetsInOrder

multicluster: {
// deprecated
#Placement: {
        clusterSelector?: {
                labels?: [string]: string
                name?: string
        }
        namespaceSelector?: {
                labels?: [string]: string
                name?: string
        }
}

// deprecated
#PlacementDecision: {
        namespace?: string
        cluster?:   string
}

// deprecated
#Component: {
        name?: string
        type?: string
        properties?: {...}
        traits?: [...{
                type:     string
                disable?: bool
                properties: {...}
        }]
        externalRevision?: string
        dependsOn?: [...string]
}

// deprecated
#ReadPlacementDecisions: {
        #provider: "multicluster"
        #do:       "read-placement-decisions"

        inputs: {
                policyName: string
                envName:    string
        }

        outputs: {
                decisions?: [...#PlacementDecision]
        }
}

// deprecated
#MakePlacementDecisions: {
        #provider: "multicluster"
        #do:       "make-placement-decisions"

        inputs: {
                policyName: string
                envName:    string
                placement:  #Placement
        }

        outputs: {
                decisions: [...#PlacementDecision]
        }
}

// deprecated
#PatchApplication: {
        #provider: "multicluster"
        #do:       "patch-application"

        inputs: {
                envName: string
                patch?: components: [...#Component]
                selector?: components: [...string]
        }

        outputs: {...}
        ...
}

// deprecated
#LoadEnvBindingEnv: #Steps & {
        inputs: {
                env:    string
                policy: string
        }

        loadPolicies: oam.#LoadPolicies @step(1)
        policy_:      string
        envBindingPolicies: []
        if inputs.policy == "" && loadPolicies.value != _|_ {
                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["(inputs.env)"]

        outputs: {
                policy:    policy_
                envConfig: envConfig_
        }
}

// deprecated
#PrepareEnvBinding: #Steps & {
        inputs: {
                env:    string
                policy: string
        }
        env_:    inputs.env
        policy_: inputs.policy

        loadEnv: #LoadEnvBindingEnv & {
                inputs: {
                        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: #PatchApplication & {
                inputs: {
                        envName: env_
                        if envConfig.selector != _|_ {
                                selector: envConfig.selector
                        }
                        if envConfig.patch != _|_ {
                                patch: envConfig.patch
                        }
                }
        } @step(3)

        outputs: {
                components: patchedApp.outputs.spec.components
                decisions:  placementDecisions.outputs.decisions
        }
}

// deprecated
#ApplyComponentsToEnv: #Steps & {
        inputs: {
                decisions: [...#PlacementDecision]
                components: [...#Component]
                env:         string
                waitHealthy: bool
        } @step(1)

        outputs: #Steps & {
                for decision in inputs.decisions {
                        for key, comp in inputs.components {
                                "(decision.cluster)-(decision.namespace)-(key)": #ApplyComponent & {
                                        value: comp
                                        if decision.cluster != _|_ {
                                                cluster: decision.cluster
                                        }
                                        if decision.namespace != _|_ {
                                                namespace: decision.namespace
                                        }
                                        waitHealthy: inputs.waitHealthy
                                        env:         inputs.env
                                } @step(1)
                        }
                }
        } @step(2)
}

// deprecated
#ApplyEnvBindApp: {
        #do: "steps"

        env:       string
        policy:    string
        app:       string
        namespace: string
        parallel:  bool

        env_:    env
        policy_: policy
        prepare: #PrepareEnvBinding & {
                inputs: {
                        env:    env_
                        policy: policy_
                }
        } @step(1)

        apply: #ApplyComponentsToEnv & {
                inputs: {
                        decisions:   prepare.outputs.decisions
                        components:  prepare.outputs.components
                        env:         env_
                        waitHealthy: !parallel
                }
        } @step(2)

        if parallel {
                wait: #ApplyComponentsToEnv & {
                        inputs: {
                                decisions:   prepare.outputs.decisions
                                components:  prepare.outputs.components
                                env:         env_
                                waitHealthy: true
                        }
                } @step(3)
        }
}

#ListClusters: {
        #provider: "multicluster"
        #do:       "list-clusters"

        outputs: {
                clusters: [...string]
        }
}

#GetPlacementsFromTopologyPolicies: {
        #provider: "multicluster"
        #do:       "get-placements-from-topology-policies"
        policies: [...string]
        placements: [...{
                cluster:   string
                namespace: string
        }]
}

#Deploy: {
        #provider: "multicluster"
        #do:       "deploy"
        policies: [...string]
        parallelism:              int
        ignoreTerraformComponent: bool
        inlinePolicies:           *[] | [...{...}]
}

}
oam: {
#ApplyComponent: {
        #provider: "oam"
        #do:       "component-apply"

        // +usage=The cluster to use
        cluster: *"" | string
        // +usage=The env to use
        env: *"" | string
        // +usage=The namespace to apply
        namespace: *"" | string
        // +usage=Whether to wait healthy of the applied component
        waitHealthy: *true | bool
        // +usage=The value of the component resource
        value: {...}
        // +usage=The patcher that will be applied to the resource, you can define the strategy of list merge through comments. Reference doc here: https://kubevela.io/docs/platform-engineers/traits/patch-trait#patch-in-workflow-step
        patch?: {...}
        ...
}

#RenderComponent: {
        #provider: "oam"
        #do:       "component-render"
        cluster:   *"" | string
        env:       *"" | string
        namespace: *"" | string
        value: {...}
        patch?: {...}
        output?: {...}
        outputs?: {...}
        ...
}

#LoadComponets: {
        #provider: "oam"
        #do:       "load"

        // +usage=If specify `app`, use specified application to load its component resources otherwise use current application
        app?: string
        // +usage=The value of the components will be filled in this field after the action is executed, you can use value[componentName] to refer a specified component
        value?: {...}
        ...
}

#LoadPolicies: {
        #provider: "oam"
        #do:       "load-policies"
        value?: {...}
        ...
}

#LoadComponetsInOrder: {
        #provider: "oam"
        #do:       "load-comps-in-order"
        ...
}

}
query: {
#ListResourcesInApp: {
        #do:       "listResourcesInApp"
        #provider: "query"
        app: {
                name:      string
                namespace: string
                filter?: {
                        cluster?:          string
                        clusterNamespace?: string
                        components?: [...string]
                        kind?:       string
                        apiVersion?: string
                }
                withStatus?: bool
        }
        list?: [...{
                cluster:   string
                component: string
                revision:  string
                object: {...}
        }]
        ...
}

#ListAppliedResources: {
        #do:       "listAppliedResources"
        #provider: "query"
        app: {
                name:      string
                namespace: string
                filter?: {
                        cluster?:          string
                        clusterNamespace?: string
                        components?: [...string]
                        kind?:       string
                        apiVersion?: string
                }
        }
        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"
        app: {
                name:      string
                namespace: string
                filter?: {
                        cluster?:          string
                        clusterNamespace?: string
                        components?: [...string]
                        kind:       "Pod"
                        apiVersion: "v1"
                }
                withTree: true
        }
        list: [...{...}]
        ...
}

#CollectServices: {
        #do:       "collectResources"
        #provider: "query"
        app: {
                name:      string
                namespace: string
                filter?: {
                        cluster?:          string
                        clusterNamespace?: string
                        components?: [...string]
                        kind:       "Service"
                        apiVersion: "v1"
                }
                withTree: true
        }
        list: [...{...}]
        ...
}

#SearchEvents: {
        #do:       "searchEvents"
        #provider: "query"
        value: {...}
        cluster: string
        ...
}

#CollectLogsInPod: {
        #do:       "collectLogsInPod"
        #provider: "query"
        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
        }
        outputs?: {
                logs?: string
                err?:  string
                info?: {
                        fromDate: string
                        toDate:   string
                }
                ...
        }
        ...
}

#CollectServiceEndpoints: {
        #do:       "collectServiceEndpoints"
        #provider: "query"
        app: {
                name:      string
                namespace: string
                filter?: {
                        cluster?:          string
                        clusterNamespace?: string
                        components?: [...string]
                }
                withTree: true
        }
        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]
                }
                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
                ...
        }]
        ...
}

}
terraform: {
#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)
}

}

现在我们再看apply-component工作流步骤的CUE模版,其中“oam.#ApplyComponent”,就是指调用vela/op#ApplyComponent方法,该方法CUE代码如下:

#ApplyComponent: {
  #provider: "oam"
  #do:       "component-apply"

  // +usage=The cluster to use
  cluster: *"" | string
  // +usage=The env to use
  env: *"" | string
  // +usage=The namespace to apply
  namespace: *"" | string
  // +usage=Whether to wait healthy of the applied component
  waitHealthy: *true | bool
  // +usage=The value of the component resource
  value: {...}
  // +usage=The patcher that will be applied to the resource, you can define the strategy of list merge through comments. Reference doc here: https://kubevela.io/docs/platform-engineers/traits/patch-trait#patch-in-workflow-step
  patch?: {...}
  ...
}

至此,我们找到了apply-component工作流步骤模版的#provider#do的值,apply-component工作流步骤的执行就是调用oam提供者的component-apply方法。

// Install register handlers to provider discover.
func Install(p wfTypes.Providers, app *v1beta1.Application, af *appfile.Appfile, cli client.Client, apply ComponentApply, render ComponentRender) {
    prd := &provider{
       render: render,
       apply:  apply,
       app:    app.DeepCopy(),
       af:     af,
       cli:    cli,
    }
    p.Register(ProviderName, map[string]wfTypes.Handler{
       "component-render":    prd.RenderComponent,
       "component-apply":     prd.ApplyComponent,
       "load":                prd.LoadComponent,
       "load-policies":       prd.LoadPolicies,
       "load-comps-in-order": prd.LoadComponentInOrder,
    })
}

以同样的方法找出deploy模版的op.#Deploy指向的就是vela/opmulticluster.#Deploy方法,CUE代码如下:

#Deploy: {
        #provider: "multicluster"
        #do:       "deploy"
        policies: [...string]
        parallelism:              int
        ignoreTerraformComponent: bool
        inlinePolicies:           *[] | [...{...}]
}

因此,deploy工作流步骤的执行就是调用multicluster提供者的deploy方法。

// Install register handlers to provider discover.
func Install(p wfTypes.Providers, c client.Client, app *v1beta1.Application, af *appfile.Appfile, apply oamProvider.ComponentApply, healthCheck oamProvider.ComponentHealthCheck, renderer oamProvider.WorkloadRenderer) {
    prd := &provider{Client: c, app: app, af: af, apply: apply, healthCheck: healthCheck, renderer: renderer}
    p.Register(ProviderName, map[string]wfTypes.Handler{
       "read-placement-decisions":              prd.ReadPlacementDecisions,
       "make-placement-decisions":              prd.MakePlacementDecisions,
       "patch-application":                     prd.PatchApplication,
       "list-clusters":                         prd.ListClusters,
       "get-placements-from-topology-policies": prd.GetPlacementsFromTopologyPolicies,
       "deploy":                                prd.Deploy,
    })
}

理解了apply-component工作流步骤和deploy工作流步骤是如何与Go代码关联的,我们就很容易理解插件CUE代码做的是什么事情,也容器阅读原理去理解组件的Install细节了,更能帮忙我们以CUE写好插件。

#云原生

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

KubeVela篇12:自定义工作流步骤以及踩坑经验

官方提供的工作流步骤有限,另外,对于自研的PaaS平台,我们需要借助工作流步骤实现一些例如存量项目基础设施导入、项目环境初始化、平台组件共享基础设施需要解决的差异对比审核、基础设施漂移等。

KubeVela篇11:可持续测试应用部署之Mock基础设施

我们基于KubeVela开发的云原生应用交付平台,提供如初始化基础设施导入、中间件部署共用基础设施等相关能力的测试,需要依赖基础设施。虽然terraform是面向公司内部的混合云平台,但是测试都要跨部门配置效率太低了,而且这种模式无法支持持续测试。

KubeVela篇10:KubeVela 私有云Terraform Provider Addon插件开发指南

如何Debug Terraform Controller;如何让Configuration可以指向私有仓库;为云资源编写ComponentDefinition;验证流程是否跑通。

KubeVela篇08:一个组件的输出作为另一个组件的输入案例及传递原理

kubevela安装一个Application的过程,就是执行工作流上的每个步骤的过程,并且当我们未配置工作流,kubevela会自动为组件的部署生成一个工作流步骤。

KubeVela篇07:terraform controller实现原理

erraform-controller是一个专门负责terraform一类的组件"安装"的Operator,通过打包成helm,再封装成kubevela的Addon,由kubevela安装到管控集群,为其它terraform provider插件提供模块定义支持。

KubeVela篇06:Kubevela Addon插件安装原理

terraform插件的安装过程;terraform provider插件的安装过程。