diff --git a/docs/en/platform-engineers/advanced-cue.md b/docs/en/platform-engineers/advanced-cue.md new file mode 100644 index 000000000..e34d96684 --- /dev/null +++ b/docs/en/platform-engineers/advanced-cue.md @@ -0,0 +1,697 @@ +# Using CUE to Extend Trait in advanced way + +> WARNINIG: you are now reading a platform builder/administrator oriented documentation. + +In the following tutorial, you will learn how to add a trait in a more advanced way without writing any CRD controller. +In general, the advanced way can help you build abstraction by composition or decomposition. + +## Trait generate multiple resources + +With the help of CUE template, we can combine multiple K8s resources into one trait. + +You can use the keyword `outputs` to create multiple K8s objects. The format MUST be `outputs::`. + +Let's look at an example, assume you hope to make a combo for K8s service and ingress, naming it as `ingress`. + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + name: ingress +spec: + extension: + template: | + parameter: { + domain: string + http: [string]: int + } + + // trait template can have multiple outputs in one trait + outputs: service: { + apiVersion: "v1" + kind: "Service" + spec: { + selector: + app: context.name + ports: [ + for k, v in parameter.http { + port: v + targetPort: v + } + ] + } + } + + outputs: ingress: { + apiVersion: "networking.k8s.io/v1beta1" + kind: "Ingress" + metadata: + name: context.name + spec: { + rules: [{ + host: parameter.domain + http: { + paths: [ + for k, v in parameter.http { + path: k + backend: { + serviceName: context.name + servicePort: v + } + } + ] + } + }] + } + } +``` + +Apply this newly defined TraitDefinition into our system: + +```shell script +kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/advanced-cue/newroute.yaml +``` + +You can check it by using the application object like below: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + scopes: + healthscopes.core.oam.dev: testapp-default-health + settings: + cmd: + - node + - server.js + image: oamdev/testapp:v1 + port: 8080 + traits: + - name: ingress + properties: + domain: test.my.domain + http: + "/api": 8080 + type: webservice +``` + +Apply it: + +```shell script +kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/advanced-cue/app1.yaml +``` + +Then you will see the deployment behind webservice along with the K8s service and ingress behind the ingress trait created. + +### Generate multiple resources by using for loop + +You can use a for-loop to generate multiple resources if you want. +The key point is you should define the for-loop inside the outputs, +the type of parameter field used in the for-loop must be a map. + +Below is an example that will generate multiple K8s services in one trait: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + name: expose +spec: + extension: + 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 + }] + } + } + } + } +``` + + +Apply this newly defined TraitDefinition into our system: + +```shell script +kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/advanced-cue/for-loop.yaml +``` + +Use the newly created trait like below: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + type: webservice + settings: + cmd: + - node + - server.js + image: oamdev/testapp:v1 + port: 8080 + traits: + - name: expose + properties: + http: + myservice1: 8080 + myservice2: 8081 +``` + +Apply it: + +```shell script +kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/advanced-cue/app2.yaml +``` + +Then you will see the deployment behind webservice along with two K8s services created. + + +## Patch Trait + +For the purpose of separate of concerns, we usually won't do decomposition some fields out as trait from the underlying workload. + +For example the [webservice workload] is implemented by K8s Deployment, but the workload doesn't care about the `replicas` field. +In this case, you can write a [ManualScalerTrait](https://github.com/oam-dev/kubevela/tree/master/pkg/controller/core.oam.dev/v1alpha2/core/traits/manualscalertrait) +CRD controller to control the `replicas` field after the deployment created. + +But now, you are more encouraged to use patch trait in KubeVela. With the help of patch trait, you don't need to write CRD +controller for this case anymore. + +The keyword is `patch`, object describe after the keyword will be patched into the workload. + +Below is an example: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Manually scale the app" + name: scaler +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: replicas: parameter.replicas + } + parameter: { + replicas: *1 | int + } +``` + +The patch trait rely on the workload object will always match the structure `spec.replicas` and the type of the field. +So we usually use it with the field `appliesToWorkloads` which can limit the trait can only be used by these specified workloads. + +By default, the patch implemented in KubeVela relies on the CUE merge operation. It has these constraints: + +* New field will be added only when the schema doesn't conflict with each other, and the value not finalized. + +For example, if the workload already define the `spec.replicas` is `5`, then the patch trait replicas value `1` will fail to patch. + +* Array list in the patch will be merged into the workload by the order of index, you need to use strategy patch. + + +### Strategy Patch Trait + +`strategy patch` is a special patch logic for patching array list supported in KubeVela, it's not native CUElang feature, +so you need to write annotation for using it. + +The annotation keyword is `//+patchKey=`. + +By adding this annotation, merging logic of two array list will not follow the CUE rule, instead of that, it will +regard the element type of the array list will always be object, and compare the object field with the specified key name. +If the value of the key name equal, then the patch data will merge into that, if no equal found, the patch will append into the array list. + +The example of strategy patch trait will like below: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "add sidecar to the app" + name: sidecar +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + // +patchKey=name + spec: template: spec: containers: [parameter] + } + parameter: { + name: string + image: string + command?: [...string] + } +``` + +The patchKey is `name` which represents the container name in this example. In this case, if the workload already has +a container with the same name of this `sidecar` trait, it will be a merge operation. If the workload don't have the container +with same name, it will be a sidecar container append into the `spec.template.spec.containers` array list. + +### Patch works with output + +Patch can also work with output, if patch and output both exist in one trait, the patch part will execute first and then +the output object will be rendered out. + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "service the app" + name: kservice +spec: + appliesToWorkloads: + - webservice + - worker + definitionRef: + name: services + extension: + template: |- + patch: {spec: template: metadata: labels: app: context.name} + output: { + apiVersion: "v1" + kind: "Service" + metadata: name: context.name + spec: { + selector: app: context.name + ports: [ + for k, v in parameter.http { + port: v + targetPort: v + } + ] + } + } + parameter: { + http: [string]: int + } +``` + +## Processing Trait + +A KubeVela trait can also help you to do some processing job. Currently, we have supported http request. + +The keyword is `processing`, inside the `processing`, there are two keywords `output` and `http`. + +You can define http request `method`, `url`, `body`, `header` and `trailer` in the `http` section. +KubeVela will send a request using this information, the requested server shall output a **json result**. + +The `output` section will used to match with the `json result`, correlate fields by name will be automatically filled into it. +Then you can use the requested data from `processing.output` into `patch` or `output/outputs`. + +Below is an example: + +```yaml +apiVersion: core.oam.dev/v1alpha1 +kind: TraitDefinition +metadata: + name: auth-service +spec: + template: | + parameter: { + serviceURL: string + } + + processing: { + output: { + token?: string + } + # task shall output a json result and output will correlate fields by name. + http: { + method: *"GET" | string + url: parameter.serviceURL + request: { + body ?: bytes + header: {} + trailer: {} + } + } + } + + patch: { + data: token: processing.output.token + } +``` + + +## More Useful Trait Use Cases + +Patch trait can be powerful, let me show more interesting use cases for you. + +### Add labels + +When you want to add some common labels into the pod template, for example, use the label as a virtual group. + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Add virtual group labels" + name: virtualgroup +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: { + metadata: labels: { + if parameter.type == "namespace" { + "app.namespace.virtual.group": parameter.group + } + if parameter.type == "cluster" { + "app.cluster.virtual.group": parameter.group + } + } + } + } + parameter: { + group: *"default" | string + type: *"namespace" | string + } +``` + +Then it could be used like: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: Application +spec: + ... + traits: + - name: virtualgroup + properties: + group: "my-group1" + type: "cluster" +``` + +In this example, different type will use different label key. + +### Add Annotations + +Similar to common labels, you may want to add some information into the controller for some extension. + +Below is an example that represents auto scale bound by using annotation. The annotation value will be a json string. + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Specify auto scale by annotation" + name: kautoscale +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + import "encoding/json" + + patch: { + metadata: annotations: { + "my.custom.autoscale.annotation": json.Marshal({ + "minReplicas": parameter.min + "maxReplicas": parameter.max + }) + } + } + parameter: { + min: *1 | int + max: *3 | int + } +``` + +### Add Pod Env + +Inject some system environments into pod is also very common use case. + +The example could be like below, this case rely on strategy merge patch, so don't forget add `+patchKey=name` like below: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "add env into your pods" + name: env +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: spec: { + // +patchKey=name + containers: [{ + name: context.name + // +patchKey=name + env: [ + for k, v in parameter.env { + name: k + value: v + }, + ] + }] + } + } + + parameter: { + env: [string]: string + } +``` + +### Dynamically Pod Service Account + +In this example, the service account was dynamically requested from an authentication service and patched into the service. + +This example put uid token in http header, you can also use request body. +You may refer to [processing](#Processing-Trait) section for more details. + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "dynamically specify service account" + name: service-account +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + processing: { + output: { + credentials?: string + } + http: { + method: *"GET" | string + url: parameter.serviceURL + request: { + header: { + "authorization.token": parameter.uidtoken + } + } + } + } + patch: { + spec: template: spec: serviceAccountName: processing.output.credentials + } + + parameter: { + uidtoken: string + serviceURL: string + } +``` + +### Add init container and share volume + +A more general way to do some logic before the real business logic is to use init container. +You can define any operations in an image and run it as init container, after that use a shared volume to mount into the pod. + +Here is an example for this use case, it's same with the [K8s init container demo](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-initialization/#create-a-pod-that-has-an-init-container) + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "add an init container and use shared volume with pod" + name: init-container +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: spec: { + // +patchKey=name + containers: [{ + name: context.name + // +patchKey=name + volumeMounts: [{ + name: parameter.mountName + mountPath: parameter.appMountPath + }] + }] + initContainers: [{ + name: parameter.name + image: parameter.image + command: parameter.command + // +patchKey=name + volumeMounts: [{ + name: parameter.mountName + mountPath: parameter.initMountPath + }] + }] + // +patchKey=name + volumes: [{ + name: parameter.mountName + emptyDir: {} + }] + } + } + + parameter: { + name: string + image: string + command?: [...string] + mountName: *"workdir" | string + appMountPath: string + initMountPath: string + } +``` + +This case must rely on the strategy merge patch, for every array list, we add a `// +patchKey=name` annotation to avoid conflict. + +The usage could be: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + type: webservice + settings: + image: oamdev/testapp:v1 + traits: + - name: "init-container" + properties: + name: "install-container" + image: "busybox" + command: + - wget + - "-O" + - "/work-dir/index.html" + - http://info.cern.ch + mountName: "workdir" + appMountPath: "/usr/share/nginx/html" + initMountPath: "/work-dir" +``` + +### Node affinity and anti-affinity + +Node affinity and anti-affinity is also common trait: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "affinity specify node affinity and toleration" + name: node-affinity +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: spec: { + if parameter.affinity != _|_ { + affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [{ + matchExpressions: [ + for k, v in parameter.affinity { + key: k + operator: "In" + values: v + }, + ]}] + } + if parameter.tolerations != _|_ { + tolerations: [ + for k, v in parameter.tolerations { + effect: "NoSchedule" + key: k + operator: "Equal" + value: v + }] + } + } + } + + parameter: { + affinity?: [string]: [...string] + tolerations?: [string]: string + } +``` + +You can use it like: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + type: webservice + settings: + image: oamdev/testapp:v1 + traits: + - name: "node-affinity" + properties: + affinity: + server-owner: ["owner1","owner2"] + resource-pool: ["pool1","pool2","pool3"] + tolerations: + resource-pool: "broken-pool1" + server-owner: "old-owner" +``` \ No newline at end of file diff --git a/docs/examples/advanced-cue/app1.yaml b/docs/examples/advanced-cue/app1.yaml new file mode 100644 index 000000000..dadec4dc6 --- /dev/null +++ b/docs/examples/advanced-cue/app1.yaml @@ -0,0 +1,20 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + settings: + cmd: + - node + - server.js + image: oamdev/testapp:v1 + port: 8080 + traits: + - name: ingress + properties: + domain: test.my.domain + http: + "/api": 8080 + type: webservice \ No newline at end of file diff --git a/docs/examples/advanced-cue/app2.yaml b/docs/examples/advanced-cue/app2.yaml new file mode 100644 index 000000000..3b073ab6e --- /dev/null +++ b/docs/examples/advanced-cue/app2.yaml @@ -0,0 +1,20 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + type: webservice + settings: + cmd: + - node + - server.js + image: oamdev/testapp:v1 + port: 8080 + traits: + - name: expose + properties: + http: + myservice1: 8080 + myservice2: 8081 \ No newline at end of file diff --git a/docs/examples/advanced-cue/app3.yaml b/docs/examples/advanced-cue/app3.yaml new file mode 100644 index 000000000..5a0b9a90c --- /dev/null +++ b/docs/examples/advanced-cue/app3.yaml @@ -0,0 +1,27 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: testapp +spec: + components: + - name: express-server + type: webservice + settings: + cmd: + - node + - server.js + image: oamdev/testapp:v1 + port: 8080 + traits: + - name: "init-container" + properties: + name: "install-container" + image: "busybox" + command: + - wget + - "-O" + - "/work-dir/index.html" + - http://info.cern.ch + mountName: "workdir" + appMountPath: "/usr/share/nginx/html" + initMountPath: "/work-dir" \ No newline at end of file diff --git a/docs/examples/advanced-cue/dynamic-sa.yaml b/docs/examples/advanced-cue/dynamic-sa.yaml new file mode 100644 index 000000000..a771e421e --- /dev/null +++ b/docs/examples/advanced-cue/dynamic-sa.yaml @@ -0,0 +1,34 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "dynamically specify service account" + name: service-account +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + processing: { + output: { + credentials?: string + } + http: { + method: *"GET" | string + url: parameter.serviceURL + request: { + header: { + "authorization.token": parameter.uidtoken + } + } + } + } + patch: { + spec: template: spec: serviceAccountName: processing.output.credentials + } + + parameter: { + uidtoken: string + serviceURL: string + } diff --git a/docs/examples/advanced-cue/for-loop.yaml b/docs/examples/advanced-cue/for-loop.yaml new file mode 100644 index 000000000..cfeaf9a0c --- /dev/null +++ b/docs/examples/advanced-cue/for-loop.yaml @@ -0,0 +1,28 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + name: expose +spec: + extension: + template: | + parameter: { + domain: string + 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 + }] + } + } + } + } \ No newline at end of file diff --git a/docs/examples/advanced-cue/ingress.yaml b/docs/examples/advanced-cue/ingress.yaml new file mode 100644 index 000000000..f66af09ea --- /dev/null +++ b/docs/examples/advanced-cue/ingress.yaml @@ -0,0 +1,50 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + name: ingress +spec: + extension: + template: | + parameter: { + domain: string + http: [string]: int + } + + // trait template can have multiple outputs in one trait + outputs: service: { + apiVersion: "v1" + kind: "Service" + spec: { + selector: + app: context.name + ports: [ + for k, v in parameter.http { + port: v + targetPort: v + } + ] + } + } + + outputs: ingress: { + apiVersion: "networking.k8s.io/v1beta1" + kind: "Ingress" + metadata: + name: context.name + spec: { + rules: [{ + host: parameter.domain + http: { + paths: [ + for k, v in parameter.http { + path: k + backend: { + serviceName: context.name + servicePort: v + } + } + ] + } + }] + } + } \ No newline at end of file diff --git a/docs/examples/advanced-cue/initcontainer.yaml b/docs/examples/advanced-cue/initcontainer.yaml new file mode 100644 index 000000000..dabbd599f --- /dev/null +++ b/docs/examples/advanced-cue/initcontainer.yaml @@ -0,0 +1,49 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "add an init container and use shared volume with pod" + name: init-container +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: spec: { + // +patchKey=name + containers: [{ + name: context.name + // +patchKey=name + volumeMounts: [{ + name: parameter.mountName + mountPath: parameter.appMountPath + }] + }] + initContainers: [{ + name: parameter.name + image: parameter.image + command: parameter.command + // +patchKey=name + volumeMounts: [{ + name: parameter.mountName + mountPath: parameter.initMountPath + }] + }] + // +patchKey=name + volumes: [{ + name: parameter.mountName + emptyDir: {} + }] + } + } + + parameter: { + name: string + image: string + command?: [...string] + mountName: *"workdir" | string + appMountPath: string + initMountPath: string + } diff --git a/docs/examples/advanced-cue/kautoscale.yaml b/docs/examples/advanced-cue/kautoscale.yaml new file mode 100644 index 000000000..08e0cc786 --- /dev/null +++ b/docs/examples/advanced-cue/kautoscale.yaml @@ -0,0 +1,24 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Specify auto scale by annotation" + name: kautoscale +spec: + appliesToWorkloads: + - webservice # this should be some knative like workload + extension: + template: |- + import "encoding/json" + patch: { + metadata: annotations: { + "my.autoscale.ann": json.Marshal({ + "minReplicas": parameter.min + "maxReplicas": parameter.max + }) + } + } + parameter: { + min: *1 | int + max: *3 | int + } \ No newline at end of file diff --git a/docs/examples/advanced-cue/node-affinity.yaml b/docs/examples/advanced-cue/node-affinity.yaml new file mode 100644 index 000000000..c86a037f5 --- /dev/null +++ b/docs/examples/advanced-cue/node-affinity.yaml @@ -0,0 +1,40 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "affinity specify node affinity and toleration" + name: node-affinity +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: spec: { + if parameter.affinity != _|_ { + affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [{ + matchExpressions: [ + for k, v in parameter.affinity { + key: k + operator: "In" + values: v + }, + ]}] + } + if parameter.tolerations != _|_ { + tolerations: [ + for k, v in parameter.tolerations { + effect: "NoSchedule" + key: k + operator: "Equal" + value: v + }] + } + } + } + + parameter: { + affinity?: [string]: [...string] + tolerations?: [string]: string + } diff --git a/docs/examples/advanced-cue/patch-replica.yaml b/docs/examples/advanced-cue/patch-replica.yaml new file mode 100644 index 000000000..d0a9d2d54 --- /dev/null +++ b/docs/examples/advanced-cue/patch-replica.yaml @@ -0,0 +1,19 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Manually scale the app" + name: scaler +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: replicas: parameter.replicas + } + parameter: { + //+short=r + replicas: *1 | int + } \ No newline at end of file diff --git a/docs/examples/advanced-cue/podenv.yaml b/docs/examples/advanced-cue/podenv.yaml new file mode 100644 index 000000000..8f507bf7c --- /dev/null +++ b/docs/examples/advanced-cue/podenv.yaml @@ -0,0 +1,32 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "add env into your pods" + name: env +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: spec: { + // +patchKey=name + containers: [{ + name: context.name + // +patchKey=name + env: [ + for k, v in parameter.env { + name: k + value: v + }, + ] + }] + } + } + + parameter: { + env: [string]: string + } + diff --git a/docs/examples/advanced-cue/virtualgroup.yaml b/docs/examples/advanced-cue/virtualgroup.yaml new file mode 100644 index 000000000..214cb3f66 --- /dev/null +++ b/docs/examples/advanced-cue/virtualgroup.yaml @@ -0,0 +1,28 @@ +apiVersion: core.oam.dev/v1alpha2 +kind: TraitDefinition +metadata: + annotations: + definition.oam.dev/description: "Add virtual group labels" + name: virtualgroup +spec: + appliesToWorkloads: + - webservice + - worker + extension: + template: |- + patch: { + spec: template: { + metadata: labels: { + if parameter.type == "namespace" { + "app.namespace.virtual.group": parameter.group + } + if parameter.type == "cluster" { + "app.cluster.virtual.group": parameter.group + } + } + } + } + parameter: { + group: *"default" | string + type: *"namespace" | string + } diff --git a/pkg/dsl/definition/template.go b/pkg/dsl/definition/template.go index 54361a666..0190b2086 100644 --- a/pkg/dsl/definition/template.go +++ b/pkg/dsl/definition/template.go @@ -218,7 +218,6 @@ func (td *traitDef) Complete(ctx process.Context) error { } ctx.PutAssistants(process.Assistant{Ins: other, Type: td.name}) } - } patcher := inst.Lookup("patch") @@ -232,7 +231,6 @@ func (td *traitDef) Complete(ctx process.Context) error { return err } } - } return nil }