Version: v1.3

Advanced Features

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

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:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. name: expose
  5. spec:
  6. schematic:
  7. cue:
  8. template: |
  9. parameter: {
  10. http: [string]: int
  11. }
  12. outputs: {
  13. for k, v in parameter.http {
  14. "\(k)": {
  15. apiVersion: "v1"
  16. kind: "Service"
  17. spec: {
  18. selector:
  19. app: context.name
  20. ports: [{
  21. port: v
  22. targetPort: v
  23. }]
  24. }
  25. }
  26. }
  27. }

The usage of this trait could be:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: Application
  3. metadata:
  4. name: testapp
  5. spec:
  6. components:
  7. - name: express-server
  8. type: webservice
  9. properties:
  10. ...
  11. traits:
  12. - type: expose
  13. properties:
  14. http:
  15. myservice1: 8080
  16. myservice2: 8081

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:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: TraitDefinition
  3. metadata:
  4. name: auth-service
  5. spec:
  6. schematic:
  7. cue:
  8. template: |
  9. parameter: {
  10. serviceURL: string
  11. }
  12. processing: {
  13. output: {
  14. token?: string
  15. }
  16. // The target server will return a JSON data with `token` as key.
  17. http: {
  18. method: *"GET" | string
  19. url: parameter.serviceURL
  20. request: {
  21. body?: bytes
  22. header: {}
  23. trailer: {}
  24. }
  25. }
  26. }
  27. patch: {
  28. data: token: processing.output.token
  29. }

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

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:

  1. apiVersion: core.oam.dev/v1beta1
  2. kind: ComponentDefinition
  3. metadata:
  4. name: worker
  5. spec:
  6. workload:
  7. definition:
  8. apiVersion: apps/v1
  9. kind: Deployment
  10. schematic:
  11. cue:
  12. template: |
  13. output: {
  14. apiVersion: "apps/v1"
  15. kind: "Deployment"
  16. spec: {
  17. selector: matchLabels: {
  18. "app.oam.dev/component": context.name
  19. }
  20. template: {
  21. metadata: labels: {
  22. "app.oam.dev/component": context.name
  23. }
  24. spec: {
  25. containers: [{
  26. name: context.name
  27. image: parameter.image
  28. ports: [{containerPort: parameter.port}]
  29. envFrom: [{
  30. configMapRef: name: context.name + "game-config"
  31. }]
  32. if parameter["cmd"] != _|_ {
  33. command: parameter.cmd
  34. }
  35. }]
  36. }
  37. }
  38. }
  39. }
  40. outputs: gameconfig: {
  41. apiVersion: "v1"
  42. kind: "ConfigMap"
  43. metadata: {
  44. name: context.name + "game-config"
  45. }
  46. data: {
  47. enemies: parameter.enemies
  48. lives: parameter.lives
  49. }
  50. }
  51. parameter: {
  52. // +usage=Which image would you like to use for your service
  53. // +short=i
  54. image: string
  55. // +usage=Commands to run in the container
  56. cmd?: [...string]
  57. lives: string
  58. enemies: string
  59. port: int
  60. }
  61. ---
  62. apiVersion: core.oam.dev/v1beta1
  63. kind: TraitDefinition
  64. metadata:
  65. name: ingress
  66. spec:
  67. schematic:
  68. cue:
  69. template: |
  70. parameter: {
  71. domain: string
  72. path: string
  73. exposePort: int
  74. }
  75. // trait template can have multiple outputs in one trait
  76. outputs: service: {
  77. apiVersion: "v1"
  78. kind: "Service"
  79. spec: {
  80. selector:
  81. app: context.name
  82. ports: [{
  83. port: parameter.exposePort
  84. targetPort: context.output.spec.template.spec.containers[0].ports[0].containerPort
  85. }]
  86. }
  87. }
  88. outputs: ingress: {
  89. apiVersion: "networking.k8s.io/v1beta1"
  90. kind: "Ingress"
  91. metadata:
  92. name: context.name
  93. labels: config: context.outputs.gameconfig.data.enemies
  94. spec: {
  95. rules: [{
  96. host: parameter.domain
  97. http: {
  98. paths: [{
  99. path: parameter.path
  100. backend: {
  101. serviceName: context.name
  102. servicePort: parameter.exposePort
  103. }
  104. }]
  105. }
  106. }]
  107. }
  108. }

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.

Last updated on Nov 1, 2022 by Tianxin Dong