kubevela.github.io/docs/cue/advanced.md

6.4 KiB

title
Advanced Features

As a Data Configuration Language, CUE allows you to do some advanced templating magic in definition objects.

Render Multiple Resources With a Loop

You can define the for-loop inside the outputs.

Note that in this case the type of parameter field used in the for-loop must be a map.

Below is an example that will render multiple Kubernetes Services in one trait:

apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
  name: expose
spec:
  schematic:
    cue:
      template: |
        parameter: {
          http: [string]: int
        }

        outputs: {
          for k, v in parameter.http {
            "\(k)": {
              apiVersion: "v1"
              kind:       "Service"
              spec: {
                selector:
                  app: context.name
                ports: [{
                  port:       v
                  targetPort: v
                }]
              }
            }
          }
        }        

The usage of this trait could be:

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: testapp
spec:
  components:
    - name: express-server
      type: webservice
      properties:
        ...
      traits:
        - type: expose
          properties:
            http:
              myservice1: 8080
              myservice2: 8081

Execute HTTP Request in Trait Definition

The trait definition can send a HTTP request and capture the response to help you rendering the resource with keyword processing.

You can define HTTP request method, url, body, header and trailer in the processing.http section, and the returned data will be stored in processing.output.

Please ensure the target HTTP server returns a JSON data. output.

Then you can reference the returned data from processing.output in patch or output/outputs.

Below is an example:

apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
  name: auth-service
spec:
  schematic:
    cue:
      template: |
        parameter: {
          serviceURL: string
        }

        processing: {
          output: {
            token?: string
          }
          // The target server will return a JSON data with `token` as key.
          http: {
            method: *"GET" | string
            url:    parameter.serviceURL
            request: {
              body?: bytes
              header: {}
              trailer: {}
            }
          }
        }

        patch: {
          data: token: processing.output.token
        }        

In above example, this trait definition will send request to get the token data, and then patch the data to given component instance.

Data Passing

A trait definition can read the generated API resources (rendered from output and outputs) of given component definition.

KubeVela will ensure the component definitions are always rendered before traits definitions.

Specifically, the context.output contains the rendered workload API resource (whose GVK is indicated by spec.workloadin component definition), and use context.outputs.<xx> to contain all the other rendered API resources.

Below is an example for data passing:

apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
  name: worker
spec:
  workload:
    definition:
      apiVersion: apps/v1
      kind: Deployment
  schematic:
    cue:
      template: |
        output: {
          apiVersion: "apps/v1"
          kind:       "Deployment"
          spec: {
            selector: matchLabels: {
              "app.oam.dev/component": context.name
            }

            template: {
              metadata: labels: {
                "app.oam.dev/component": context.name
              }
              spec: {
                containers: [{
                  name:  context.name
                  image: parameter.image
                  ports: [{containerPort: parameter.port}]
                  envFrom: [{
                    configMapRef: name: context.name + "game-config"
                  }]
                  if parameter["cmd"] != _|_ {
                    command: parameter.cmd
                  }
                }]
              }
            }
          }
        }

        outputs: gameconfig: {
          apiVersion: "v1"
          kind:       "ConfigMap"
          metadata: {
            name: context.name + "game-config"
          }
          data: {
            enemies: parameter.enemies
            lives:   parameter.lives
          }
        }

        parameter: {
          // +usage=Which image would you like to use for your service
          // +short=i
          image: string
          // +usage=Commands to run in the container
          cmd?: [...string]
          lives:   string
          enemies: string
          port:    int
        }        


---
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
  name: ingress
spec:
  schematic:
    cue:
      template: |
        parameter: {
          domain:     string
          path:       string
          exposePort: int
        }
        // trait template can have multiple outputs in one trait
        outputs: service: {
          apiVersion: "v1"
          kind:       "Service"
          spec: {
            selector:
              app: context.name
            ports: [{
              port:       parameter.exposePort
              targetPort: context.output.spec.template.spec.containers[0].ports[0].containerPort
            }]
          }
        }
        outputs: ingress: {
          apiVersion: "networking.k8s.io/v1beta1"
          kind:       "Ingress"
          metadata:
              name: context.name
          labels: config: context.outputs.gameconfig.data.enemies
          spec: {
            rules: [{
              host: parameter.domain
              http: {
                paths: [{
                  path: parameter.path
                  backend: {
                    serviceName: context.name
                    servicePort: parameter.exposePort
                  }
                }]
              }
            }]
          }
        }        

In detail, during rendering worker ComponentDefinition:

  1. the rendered Kubernetes Deployment resource will be stored in the context.output,
  2. all other rendered resources will be stored in context.outputs.<xx>, with <xx> is the unique name in every template.outputs.

Thus, in TraitDefinition, it can read the rendered API resources (e.g. context.outputs.gameconfig.data.enemies) from the context.