--- title: How-to --- In this section, it will introduce how to use [CUE](https://cuelang.org/) to declare app components via `ComponentDefinition`. > Before reading this part, please make sure you've learned the [Definition CRD](../platform-engineers/definition-and-templates) in KubeVela. ## Declare `ComponentDefinition` Here is a CUE based `ComponentDefinition` example which provides a abstraction for stateless workload type: ```yaml apiVersion: core.oam.dev/v1beta1 kind: ComponentDefinition metadata: name: stateless spec: workload: definition: apiVersion: apps/v1 kind: Deployment schematic: cue: template: | parameter: { name: string image: string } output: { apiVersion: "apps/v1" kind: "Deployment" spec: { selector: matchLabels: { "app.oam.dev/component": parameter.name } template: { metadata: labels: { "app.oam.dev/component": parameter.name } spec: { containers: [{ name: parameter.name image: parameter.image }] } } } } ``` In detail: - `.spec.workload` is required to indicate the workload type of this component. - `.spec.schematic.cue.template` is a CUE template, specifically: * The `output` filed defines the template for the abstraction. * The `parameter` filed defines the template parameters, i.e. the configurable properties exposed in the `Application`abstraction (and JSON schema will be automatically generated based on them). Let's declare another component named `task`, i.e. an abstraction for run-to-completion workload. ```yaml apiVersion: core.oam.dev/v1beta1 kind: ComponentDefinition metadata: name: task annotations: definition.oam.dev/description: "Describes jobs that run code or a script to completion." spec: workload: definition: apiVersion: batch/v1 kind: Job schematic: cue: template: | output: { apiVersion: "batch/v1" kind: "Job" spec: { parallelism: parameter.count completions: parameter.count template: spec: { restartPolicy: parameter.restart containers: [{ image: parameter.image if parameter["cmd"] != _|_ { command: parameter.cmd } }] } } } parameter: { count: *1 | int image: string restart: *"Never" | string cmd?: [...string] } ``` Save above `ComponentDefintion` objects to files and install them to your Kubernetes cluster by `$ kubectl apply -f stateless-def.yaml task-def.yaml` ## Declare an `Application` The `ComponentDefinition` can be instantiated in `Application` abstraction as below: ```yaml apiVersion: core.oam.dev/v1alpha2 kind: Application metadata: name: website spec: components: - name: hello type: stateless properties: image: crccheck/hello-world name: mysvc - name: countdown type: task properties: image: centos:7 cmd: - "bin/bash" - "-c" - "for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done" ``` ### Under The Hood
Above application resource will generate and manage following Kubernetes resources in your target cluster based on the `output` in CUE template and user input in `Application` properties. ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend ... # skip tons of metadata info spec: template: spec: containers: - name: mysvc image: crccheck/hello-world metadata: labels: app.oam.dev/component: mysvc selector: matchLabels: app.oam.dev/component: mysvc --- apiVersion: batch/v1 kind: Job metadata: name: countdown ... # skip tons of metadata info spec: parallelism: 1 completions: 1 template: metadata: name: countdown spec: containers: - name: countdown image: 'centos:7' command: - bin/bash - '-c' - for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done restartPolicy: Never ```
## CUE `Context` KubeVela allows you to reference the runtime information of your application via `conext` keyword. The most widely used context is application name(`context.appName`) component name(`context.name`). ```cue context: { appName: string name: string } ``` For example, let's say you want to use the component name filled in by users as the container name in the workload instance: ```cue parameter: { image: string } output: { ... spec: { containers: [{ name: context.name image: parameter.image }] } ... } ``` > Note that `context` information are auto-injected before resources are applied to target cluster. ### Full available information in CUE `context` | Context Variable | Description | | :--: | :---------: | | `context.appRevision` | The revision of the application | | `context.appName` | The name of the application | | `context.name` | The name of the component of the application | | `context.namespace` | The namespace of the application | | `context.output` | The rendered workload API resource of the component, this usually used in trait | | `context.outputs.` | The rendered trait API resource of the component, this usually used in trait | ## Composition It's common that a component definition is composed by multiple API resources, for example, a `webserver` component that is composed by a Deployment and a Service. CUE is a great solution to achieve this in simplified primitives. > Another approach to do composition in KubeVela of course is [using Helm](/docs/helm/component). ## How-to KubeVela requires you to define the template of workload type in `output` section, and leave all the other resource templates in `outputs` section with format as below: ```cue outputs: : ``` > The reason for this requirement is KubeVela needs to know it is currently rendering a workload so it could do some "magic" like patching annotations/labels or other data during it. Below is the example for `webserver` definition: ```yaml apiVersion: core.oam.dev/v1beta1 kind: ComponentDefinition metadata: name: webserver annotations: definition.oam.dev/description: "webserver is a combo of Deployment + Service" 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 if parameter["cmd"] != _|_ { command: parameter.cmd } if parameter["env"] != _|_ { env: parameter.env } if context["config"] != _|_ { env: context.config } ports: [{ containerPort: parameter.port }] if parameter["cpu"] != _|_ { resources: { limits: cpu: parameter.cpu requests: cpu: parameter.cpu } } }] } } } } // an extra template outputs: service: { apiVersion: "v1" kind: "Service" spec: { selector: { "app.oam.dev/component": context.name } ports: [ { port: parameter.port targetPort: parameter.port }, ] } } parameter: { image: string cmd?: [...string] port: *80 | int env?: [...{ name: string value?: string valueFrom?: { secretKeyRef: { name: string key: string } } }] cpu?: string } ``` The user could now declare an `Application` with it: ```yaml apiVersion: core.oam.dev/v1beta1 kind: Application metadata: name: webserver-demo namespace: default spec: components: - name: hello-world type: webserver properties: image: crccheck/hello-world port: 8000 env: - name: "foo" value: "bar" cpu: "100m" ``` It will generate and manage below API resources in target cluster: ```shell $ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE hello-world-v1 1/1 1 1 15s $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-world-trait-7bdcff98f7 ClusterIP 8000/TCP 32s ``` ## What's Next Please check the [Learning CUE](./basic) documentation about why we support CUE as first-class templating solution and more details about using CUE efficiently.