From 4cf4fdff300250d1fdb741a7630463796c1739a1 Mon Sep 17 00:00:00 2001 From: Jianbo Sun Date: Fri, 12 Mar 2021 10:46:02 +0800 Subject: [PATCH] align workload/trait definition schema with v0.2.2 spec (#1175) --- apis/core.oam.dev/v1alpha2/core_types.go | 29 +- .../v1alpha2/zz_generated.deepcopy.go | 45 ++ .../crds/core.oam.dev_traitdefinitions.yaml | 19 +- .../core.oam.dev_workloaddefinitions.yaml | 16 +- .../templates/defwithtemplate/ingress.yaml | 96 ++--- .../defwithtemplate/manualscale.yaml | 30 +- .../templates/defwithtemplate/task.yaml | 74 ++-- .../templates/defwithtemplate/webservice.yaml | 162 ++++---- .../templates/defwithtemplate/worker.yaml | 74 ++-- config/samples/app-with-status/template.yaml | 183 +++++---- docs/en/application.md | 110 +++++ docs/en/cue/basic.md | 383 ++++++++++++++++++ docs/en/cue/workload-type.md | 221 ++++++++++ docs/en/platform-engineers/advanced-cue.md | 46 ++- hack/vela-templates/definitions/ingress.yaml | 4 +- .../definitions/manualscale.yaml | 4 +- hack/vela-templates/definitions/task.yaml | 4 +- .../definitions/webservice.yaml | 4 +- hack/vela-templates/definitions/worker.yaml | 4 +- hack/vela-templates/gen_definitions.sh | 2 +- .../crds/core.oam.dev_traitdefinitions.yaml | 19 +- .../core.oam.dev_workloaddefinitions.yaml | 16 +- .../application_controller_test.go | 182 +++++---- pkg/oam/util/template.go | 14 +- pkg/oam/util/template_test.go | 104 ++--- pkg/plugins/capcenter.go | 4 +- pkg/plugins/cluster.go | 20 +- pkg/utils/apply/apply.go | 1 - .../traitdefinition/validating_handler.go | 2 +- test/e2e-test/health_scope_test.go | 3 +- 30 files changed, 1355 insertions(+), 520 deletions(-) create mode 100644 docs/en/application.md create mode 100644 docs/en/cue/basic.md create mode 100644 docs/en/cue/workload-type.md diff --git a/apis/core.oam.dev/v1alpha2/core_types.go b/apis/core.oam.dev/v1alpha2/core_types.go index 66cf0ff83..7e441d1d5 100644 --- a/apis/core.oam.dev/v1alpha2/core_types.go +++ b/apis/core.oam.dev/v1alpha2/core_types.go @@ -24,6 +24,20 @@ import ( runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" ) +// CUE defines the encapsulation in CUE format +type CUE struct { + // Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. + // Template is a required field if CUE is defined in Capability Definition. + Template string `json:"template"` +} + +// Schematic defines the encapsulation of this capability(workload/trait/scope), +// the encapsulation can be defined in different ways, e.g. CUE/HCL(terraform)/KUBE(K8s Object)/HELM, etc... +type Schematic struct { + CUE *CUE `json:"cue,omitempty"` + // TODO(wonderflow): support HCL(terraform)/KUBE(K8s Object)/HELM here. +} + // A DefinitionReference refers to a CustomResourceDefinition by name. type DefinitionReference struct { // Name of the referenced CustomResourceDefinition. @@ -73,10 +87,9 @@ type WorkloadDefinitionSpec struct { // +optional Template string `json:"template,omitempty"` - // TemplateType defines the data format of the template, by default it's CUE format - // Terraform HCL, Helm Chart will also be candidates in the near future. + // Schematic defines the data format and template of the encapsulation of the workload // +optional - TemplateType string `json:"templateType,omitempty"` + Schematic *Schematic `json:"schematic,omitempty"` // Extension is used for extension needs by OAM platform builders // +optional @@ -150,15 +163,9 @@ type TraitDefinitionSpec struct { // +optional ConflictsWith []string `json:"conflictsWith,omitempty"` - // Template defines the abstraction template data of the workload, it will replace the old template in extension field. - // the data format depends on templateType, by default it's CUE + // Schematic defines the data format and template of the encapsulation of the trait // +optional - Template string `json:"template,omitempty"` - - // TemplateType defines the data format of the template, by default it's CUE format - // Terraform HCL, Helm Chart will also be candidates in the near future. - // +optional - TemplateType string `json:"templateType,omitempty"` + Schematic *Schematic `json:"schematic,omitempty"` // Status defines the custom health policy and status message for trait // +optional diff --git a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go index 2e198892b..2611c0c56 100644 --- a/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/v1alpha2/zz_generated.deepcopy.go @@ -488,6 +488,21 @@ func (in *CPUResources) DeepCopy() *CPUResources { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CUE) DeepCopyInto(out *CUE) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CUE. +func (in *CUE) DeepCopy() *CUE { + if in == nil { + return nil + } + out := new(CUE) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ChildResourceKind) DeepCopyInto(out *ChildResourceKind) { *out = *in @@ -1556,6 +1571,26 @@ func (in *Revision) DeepCopy() *Revision { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Schematic) DeepCopyInto(out *Schematic) { + *out = *in + if in.CUE != nil { + in, out := &in.CUE, &out.CUE + *out = new(CUE) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Schematic. +func (in *Schematic) DeepCopy() *Schematic { + if in == nil { + return nil + } + out := new(Schematic) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScopeDefinition) DeepCopyInto(out *ScopeDefinition) { *out = *in @@ -1767,6 +1802,11 @@ func (in *TraitDefinitionSpec) DeepCopyInto(out *TraitDefinitionSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Schematic != nil { + in, out := &in.Schematic, &out.Schematic + *out = new(Schematic) + (*in).DeepCopyInto(*out) + } if in.Status != nil { in, out := &in.Status, &out.Status *out = new(Status) @@ -1925,6 +1965,11 @@ func (in *WorkloadDefinitionSpec) DeepCopyInto(out *WorkloadDefinitionSpec) { *out = new(Status) **out = **in } + if in.Schematic != nil { + in, out := &in.Schematic, &out.Schematic + *out = new(Schematic) + (*in).DeepCopyInto(*out) + } if in.Extension != nil { in, out := &in.Extension, &out.Extension *out = new(runtime.RawExtension) diff --git a/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml b/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml index bd0d4b4e3..cc7d53c8e 100644 --- a/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml +++ b/charts/vela-core/crds/core.oam.dev_traitdefinitions.yaml @@ -68,6 +68,19 @@ spec: revisionEnabled: description: Revision indicates whether a trait is aware of component revision type: boolean + schematic: + description: Schematic defines the data format and template of the encapsulation of the trait + properties: + cue: + description: CUE defines the encapsulation in CUE format + properties: + template: + description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition. + type: string + required: + - template + type: object + type: object status: description: Status defines the custom health policy and status message for trait properties: @@ -78,12 +91,6 @@ spec: description: HealthPolicy defines the health check policy for the abstraction type: string type: object - template: - description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE - type: string - templateType: - description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future. - type: string workloadRefPath: description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object type: string diff --git a/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml b/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml index 440113abf..51a59d457 100644 --- a/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml +++ b/charts/vela-core/crds/core.oam.dev_workloaddefinitions.yaml @@ -82,6 +82,19 @@ spec: revisionLabel: description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods). type: string + schematic: + description: Schematic defines the data format and template of the encapsulation of the workload + properties: + cue: + description: CUE defines the encapsulation in CUE format + properties: + template: + description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition. + type: string + required: + - template + type: object + type: object status: description: Status defines the custom health policy and status message for workload properties: @@ -95,9 +108,6 @@ spec: template: description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE type: string - templateType: - description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future. - type: string required: - definitionRef type: object diff --git a/charts/vela-core/templates/defwithtemplate/ingress.yaml b/charts/vela-core/templates/defwithtemplate/ingress.yaml index d8be40db0..ff63daff5 100644 --- a/charts/vela-core/templates/defwithtemplate/ingress.yaml +++ b/charts/vela-core/templates/defwithtemplate/ingress.yaml @@ -21,50 +21,52 @@ spec: appliesToWorkloads: - webservice - worker - template: | - parameter: { - domain: string - http: [string]: int - } - - // trait template can have multiple outputs in one trait - outputs: service: { - apiVersion: "v1" - kind: "Service" - metadata: - name: context.name - spec: { - selector: - "app.oam.dev/component": 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 - } - }, - ] - } - }] - } - } - + schematic: + cue: + template: | + parameter: { + domain: string + http: [string]: int + } + + // trait template can have multiple outputs in one trait + outputs: service: { + apiVersion: "v1" + kind: "Service" + metadata: + name: context.name + spec: { + selector: + "app.oam.dev/component": 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 + } + }, + ] + } + }] + } + } + diff --git a/charts/vela-core/templates/defwithtemplate/manualscale.yaml b/charts/vela-core/templates/defwithtemplate/manualscale.yaml index aae75edf5..7ec8c115c 100644 --- a/charts/vela-core/templates/defwithtemplate/manualscale.yaml +++ b/charts/vela-core/templates/defwithtemplate/manualscale.yaml @@ -13,17 +13,19 @@ spec: definitionRef: name: manualscalertraits.core.oam.dev workloadRefPath: spec.workloadRef - template: | - output: { - apiVersion: "core.oam.dev/v1alpha2" - kind: "ManualScalerTrait" - spec: { - replicaCount: parameter.replicas - } - } - parameter: { - //+short=r - //+usage=Replicas of the workload - replicas: *1 | int - } - + schematic: + cue: + template: | + output: { + apiVersion: "core.oam.dev/v1alpha2" + kind: "ManualScalerTrait" + spec: { + replicaCount: parameter.replicas + } + } + parameter: { + //+short=r + //+usage=Replicas of the workload + replicas: *1 | int + } + diff --git a/charts/vela-core/templates/defwithtemplate/task.yaml b/charts/vela-core/templates/defwithtemplate/task.yaml index 7cc5f1868..2e9f82cce 100644 --- a/charts/vela-core/templates/defwithtemplate/task.yaml +++ b/charts/vela-core/templates/defwithtemplate/task.yaml @@ -9,39 +9,41 @@ metadata: spec: definitionRef: name: jobs.batch - template: | - output: { - apiVersion: "batch/v1" - kind: "Job" - spec: { - parallelism: parameter.count - completions: parameter.count - template: spec: { - restartPolicy: parameter.restart - containers: [{ - name: context.name - image: parameter.image - - if parameter["cmd"] != _|_ { - command: parameter.cmd - } - }] - } - } - } - parameter: { - // +usage=specify number of tasks to run in parallel - // +short=c - count: *1 | int - - // +usage=Which image would you like to use for your service - // +short=i - image: string - - // +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never. - restart: *"Never" | string - - // +usage=Commands to run in the container - cmd?: [...string] - } - + schematic: + cue: + template: | + output: { + apiVersion: "batch/v1" + kind: "Job" + spec: { + parallelism: parameter.count + completions: parameter.count + template: spec: { + restartPolicy: parameter.restart + containers: [{ + name: context.name + image: parameter.image + + if parameter["cmd"] != _|_ { + command: parameter.cmd + } + }] + } + } + } + parameter: { + // +usage=specify number of tasks to run in parallel + // +short=c + count: *1 | int + + // +usage=Which image would you like to use for your service + // +short=i + image: string + + // +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never. + restart: *"Never" | string + + // +usage=Commands to run in the container + cmd?: [...string] + } + diff --git a/charts/vela-core/templates/defwithtemplate/webservice.yaml b/charts/vela-core/templates/defwithtemplate/webservice.yaml index 0f8056e81..06f319902 100644 --- a/charts/vela-core/templates/defwithtemplate/webservice.yaml +++ b/charts/vela-core/templates/defwithtemplate/webservice.yaml @@ -10,83 +10,85 @@ metadata: spec: definitionRef: name: deployments.apps - 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 - } - } - }] - } - } - } - } - 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] - - // +usage=Which port do you want customer traffic sent to - // +short=p - port: *80 | int - // +usage=Define arguments by using environment variables - env?: [...{ - // +usage=Environment variable name - name: string - // +usage=The value of the environment variable - value?: string - // +usage=Specifies a source the value of this var should come from - valueFrom?: { - // +usage=Selects a key of a secret in the pod's namespace - secretKeyRef: { - // +usage=The name of the secret in the pod's namespace to select from - name: string - // +usage=The key of the secret to select from. Must be a valid secret key - key: string - } - } - }] - // +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core) - cpu?: string - } - + 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 + } + } + }] + } + } + } + } + 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] + + // +usage=Which port do you want customer traffic sent to + // +short=p + port: *80 | int + // +usage=Define arguments by using environment variables + env?: [...{ + // +usage=Environment variable name + name: string + // +usage=The value of the environment variable + value?: string + // +usage=Specifies a source the value of this var should come from + valueFrom?: { + // +usage=Selects a key of a secret in the pod's namespace + secretKeyRef: { + // +usage=The name of the secret in the pod's namespace to select from + name: string + // +usage=The key of the secret to select from. Must be a valid secret key + key: string + } + } + }] + // +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core) + cpu?: string + } + diff --git a/charts/vela-core/templates/defwithtemplate/worker.yaml b/charts/vela-core/templates/defwithtemplate/worker.yaml index f1f15fd52..a3be591ff 100644 --- a/charts/vela-core/templates/defwithtemplate/worker.yaml +++ b/charts/vela-core/templates/defwithtemplate/worker.yaml @@ -9,39 +9,41 @@ metadata: spec: definitionRef: name: deployments.apps - 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 - } - }] - } - } - } - } - - 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] - } - + 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 + } + }] + } + } + } + } + + 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] + } + diff --git a/config/samples/app-with-status/template.yaml b/config/samples/app-with-status/template.yaml index c32773dcb..273c071ba 100644 --- a/config/samples/app-with-status/template.yaml +++ b/config/samples/app-with-status/template.yaml @@ -12,57 +12,60 @@ spec: isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas) customStatus: |- message: "type: " + context.output.spec.template.spec.containers[0].image + ",\t enemies:" + context.outputs.gameconfig.data.enemies - template: | - output: { - apiVersion: "apps/v1" - kind: "Deployment" - spec: { - selector: matchLabels: { - "app.oam.dev/component": context.name - } + 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 - } + template: { + metadata: labels: { + "app.oam.dev/component": context.name + } - spec: { - containers: [{ - name: context.name - image: parameter.image - envFrom: [{ - configMapRef: name: context.name + "game-config" - }] - if parameter["cmd"] != _|_ { - command: parameter.cmd - } - }] - } - } - } - } + spec: { + containers: [{ + name: context.name + image: parameter.image + 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 - } - } + 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 + } - 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 - } --- apiVersion: core.oam.dev/v1alpha2 @@ -75,45 +78,47 @@ spec: message: "type: "+ context.outputs.service.spec.type +",\t clusterIP:"+ context.outputs.service.spec.clusterIP+",\t ports:"+ "\(context.outputs.service.spec.ports[0].port)"+",\t domain"+context.outputs.ingress.spec.rules[0].host healthPolicy: | isHealth: len(context.outputs.service.spec.clusterIP) > 0 - 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 - } - }, - ] - } - }] - } - } + schematic: + cue: + 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 + } + }, + ] + } + }] + } + } diff --git a/docs/en/application.md b/docs/en/application.md new file mode 100644 index 000000000..b2986258b --- /dev/null +++ b/docs/en/application.md @@ -0,0 +1,110 @@ +# Designing Application + +Application encapsulation and abstraction is achieved by the `Application` custom resource. + +## Example + +The sample application below claimed a `backend` component with *Worker* workload type, and a `frontend` component with *Web Service* workload type. + +Moreover, the `frontend` component claimed `sidecar` and `autoscaler` traits which means the workload will be automatically injected with a `fluentd` sidecar and scale from 1-100 replicas triggered by CPU usage. + +> For detailed definition about `Application` *workload type* and *traits*, please read the [core concepts](/en/concepts.md#application) documentation. + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: Application +metadata: + name: website +spec: + components: + - name: backend + type: worker + settings: + image: busybox + cmd: + - sleep + - '1000' + - name: frontend + type: webservice + settings: + image: nginx + traits: + - name: autoscaler + properties: + min: 1 + max: 10 + cpuPercent: 60 + - name: sidecar + properties: + name: "sidecar-test" + image: "fluentd" +``` + +The `type: worker` means the specification of this workload (claimed in following `settings` section) will be enforced by a `WorkloadDefinition` object named `worker` as below: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: worker + annotations: + definition.oam.dev/description: "Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic." +spec: + 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 + } + }] + } + } + } + } + parameter: { + image: string + cmd?: [...string] + } +``` + + +Hence, the `settings` section of `backend` only supports two parameters: `image` and `cmd`, this is enforced by the `parameter` list of the `.spec.template` field of the definition. + +The similar extensible abstraction mechanism also applies to traits. For example, `name: autoscaler` in `frontend` means its trait specification (i.e. `properties` section) will be enforced by a `TraitDefinition` object named `autoscaler` as below: + +> TBD: a autoscaler TraitDefinition (HPA) + +All the definition objects are expected to be defined and installed by platform team. The end users will only focus on `Application` resource (either render it by tools or author it manually). + +## Conventions and "Standard Contract" + +After the `Application` resource is applied to Kubernetes cluster, the KubeVela runtime will generate and manage the underlying resources instances following below "standard contract" and conventions. + + +| Label | Description | +| :--: | :---------: | +|`workload.oam.dev/type=` | The name of its corresponding `WorkloadDefinition` | +|`trait.oam.dev/type=` | The name of its corresponding `TraitDefinition` | +|`app.oam.dev/name=` | The name of the application it belongs to | +|`app.oam.dev/component=` | The name of the component it belongs to | +|`trait.oam.dev/resource=` | The name of trait resource instance | + +> TBD: the revision names and labels for resource instances are currently work in progress. + +> TBD: a demo for kubectl apply above Application CR and show full detailed underlying resources. diff --git a/docs/en/cue/basic.md b/docs/en/cue/basic.md new file mode 100644 index 000000000..a295f7650 --- /dev/null +++ b/docs/en/cue/basic.md @@ -0,0 +1,383 @@ +# CUE Basic + +This document will explain how to use [CUE](https://cuelang.org/) as templating module in KubeVela. Please make sure you have already learned about `Application` custom resource and how it leverage templating modules for application encapsulation and abstraction. + +## Why CUE? + +The reasons for KubeVela supports CUE as first class templating solution can be concluded as below: + +- **CUE is designed for large scale configuration.** CUE has the ability to understand a + configuration worked on by engineers across a whole company and to safely change a value that modifies thousands of objects in a configuration. This aligns very well with KubeVela's original goal to define and ship production level applications at web scale. +- **CUE supports first-class code generation and automation.** CUE can integrate with existing tools and workflows naturally while other tools would have to build complex custom solutions. For example, generate OpenAPI schemas wigh Go code. This is how KubeVela build developer tools and GUI interfaces based on the CUE templates. +- **CUE integrates very well with Go.** + KubeVela is built with GO just like most projects in Kubernetes system. CUE is also implemented in and exposes a rich API in Go. KubeVela integrates with CUE as its core library and works as a Kubernetes controller. With the help of CUE, KubeVela can easily handle data constraint problems. + +> Pleas also check [The Configuration Complexity Curse](https://blog.cedriccharly.com/post/20191109-the-configuration-complexity-curse/) and [The Logic of CUE](https://cuelang.org/docs/concepts/logic/) for more details. + +## Parameter and Template + +A very simple `WorkloadDefinition` is like below: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: mydeploy +spec: + definitionRef: + name: deployments.apps + 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 + }] + } + } + } + } +``` + +The `template` field in this definition is a CUE module, it defines two keywords for KubeVela to build the application abstraction: +- The `parameter` defines the input parameters from end user, i.e. the configurable fields in the abstraction. +- The `output` defines the template for the abstraction. + +## CUE Template Step by Step + +Let's say as the platform team, we only want to allow end user configure `image` and `name` fields in the `Application` abstraction, and automatically generate all rest of the fields. How can we use CUE to achieve this? + +We can start from the final resource we envision the platform will generate based on user inputs, for example: + +```yaml +apiVersion: apps/v1 +kind: Deployment +meadata: + name: mytest # user inputs +spec: + template: + spec: + containers: + - name: mytest # user inputs + env: + - name: a + value: b + image: nginx:v1 # user inputs + metadata: + labels: + app.oam.dev/component: mytest # generate by user inputs + selector: + matchLabels: + app.oam.dev/component: mytest # generate by user inputs +``` + +Then we can just convert this YAML to JSON and put the whole JSON object into the `output` keyword field: + +```cue +output: { + apiVersion: "apps/v1" + kind: "Deployment" + metadata: name: "mytest" + spec: { + selector: matchLabels: { + "app.oam.dev/component": "mytest" + } + template: { + metadata: labels: { + "app.oam.dev/component": "mytest" + } + spec: { + containers: [{ + name: "mytest" + image: "nginx:v1" + env: [{name:"a",value:"b"}] + }] + } + } + } +} +``` + +Since CUE as a superset of JSON, we can use: + +* C style comments, +* quotes may be omitted from field names without special characters, +* commas at the end of fields are optional, +* comma after last element in list is allowed, +* outer curly braces are optional. + +After that, we can then add `parameter` keyword, and use it as a variable reference, this is the very basic CUE feature for templating. + +```cue +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 + }] + } + } + } +} +``` + +Finally, you can put the above CUE module in the `template` field of `WorkloadDefinition` object and give it a name. Then end users can now author `Application` resource reference this definition as workload type and only have `name` and `image` as configurable parameters. + +## Advanced CUE Templating + +In this section, we will introduce advanced CUE templating features supports in KubeVela. + +### Structural Parameter + +This is the most commonly used feature. It enables us to expose complex data structure for end users. For example, environment variable list. + +A simple guide is as below: + +1. Define a type in the CUE template, it includes a struct (`other`), a string and an integer. + + ``` + #Config: { + name: string + value: int + other: { + key: string + value: string + } + } + ``` + +2. In the `parameter` section, reference above type and define it as `[...#Config]`. Then it can accept inputs from end users as an array list. + + ``` + parameter: { + name: string + image: string + configSingle: #Config + config: [...#Config] # array list parameter + } + ``` + +3. In the `output` section, simply do templating as other parameters. + + ``` + output: { + ... + spec: { + containers: [{ + name: parameter.name + image: parameter.image + env: parameter.config + }] + } + ... + } + ``` + +4. As long as you install a workload definition object (e.g. `mydeploy`) with above template in the system, a new field `config` will be available to use like below: + + ```yaml + apiVersion: core.oam.dev/v1alpha2 + kind: Application + metadata: + name: website + spec: + components: + - name: backend + type: mydeploy + settings: + image: crccheck/hello-world + name: mysvc + config: # a complex parameter + - name: a + value: 1 + other: + key: mykey + value: myvalue + ``` + + +### Conditional Parameter + +Conditional parameter can be used to do `if..else` logic in template. + +Below is an example that when `useENV=true`, it will render env section, otherwise, it will not. + +``` +parameter: { + name: string + image: string + useENV: bool +} +output: { + ... + spec: { + containers: [{ + name: parameter.name + image: parameter.image + if parameter.useENV == true { + env: [{name: "my-env", value: "my-value"}] + } + }] + } + ... +} +``` + +### Optional and Default Value + +Optional parameter can be skipped, that usually works together with conditional logic. + +Specifically, if some field does not exit, the CUE grammar is `if _variable_ != _|_`, the example is like below: + +``` +parameter: { + name: string + image: string + config?: [...#Config] +} +output: { + ... + spec: { + containers: [{ + name: parameter.name + image: parameter.image + if parameter.config != _|_ { + config: parameter.config + } + }] + } + ... +} +``` + +Default Value is marked with a `*` prefix. It's used like + +``` +parameter: { + name: string + image: *"nginx:v1" | string + port: *80 | int + number: *123.4 | float +} +output: { + ... + spec: { + containers: [{ + name: parameter.name + image: parameter.image + }] + } + ... +} +``` + +So if a parameter field is neither a parameter with default value nor a conditional field, it's a required value. + +### Loop + +#### Loop for Map + +```cue +parameter: { + name: string + image: string + env: [string]: string +} +output: { + spec: { + containers: [{ + name: parameter.name + image: parameter.image + env: [ + for k, v in parameter.env { + name: k + value: v + }, + ] + }] + } +} +``` + +#### Loop for Slice + +```cue +parameter: { + name: string + image: string + env: [...{name:string,value:string}] +} +output: { + ... + spec: { + containers: [{ + name: parameter.name + image: parameter.image + env: [ + for _, v in parameter.env { + name: v.name + value: v.value + }, + ] + }] + } +} +``` + +### Import CUE Internal Packages + +CUE has many [internal packages](https://pkg.go.dev/cuelang.org/go@v0.2.2/pkg) which also can be used in KubeVela. + +Below is an example that use `strings.Join` to `concat` string list to one string. + +```cue +import ("strings") + +parameter: { + outputs: [{ip: "1.1.1.1", hostname: "xxx.com"}, {ip: "2.2.2.2", hostname: "yyy.com"}] +} +output: { + spec: { + if len(parameter.outputs) > 0 { + _x: [ for _, v in parameter.outputs { + "\(v.ip) \(v.hostname)" + }] + message: "Visiting URL: " + strings.Join(_x, "") + } + } +} +``` + +## Summary + +Overall, CUE is a very powerful templating language which could help platform team create extensible application encapsulation and abstraction with ease. diff --git a/docs/en/cue/workload-type.md b/docs/en/cue/workload-type.md new file mode 100644 index 000000000..72698db63 --- /dev/null +++ b/docs/en/cue/workload-type.md @@ -0,0 +1,221 @@ +# Defining Workload Types + +In this section, we will introduce more examples of using CUE to define workload types. + +## Basic Usage + +The very basic usage of CUE in workload is to extend a Kubernetes resource as a workload type(via `WorkloadDefinition`) and expose configurable parameters to users. + +A Deployment as workload type: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: worker +spec: + definitionRef: + name: deployments.apps + 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 + }] + }}} + } +``` + +A Job as workload type: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: task + annotations: + definition.oam.dev/description: "Describes jobs that run code or a script to completion." +spec: + definitionRef: + name: jobs.batch + 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] + } +``` + +## Context + +When you want to reference the runtime instance name for an app, you can use the `conext` keyword to define `parameter`. + +KubeVela runtime provides a `context` struct including app name(`context.appName`) and component name(`context.name`). + +```cue +context: { + appName: string + name: string +} +``` + +Values of the context will be automatically generated before the underlying resources are applied. +This is why you can reference the context variable as value in the template. + +```yaml +parameter: { + image: string +} +output: { + ... + spec: { + containers: [{ + name: context.name + image: parameter.image + }] + } + ... +} +``` + +## Composition + +A workload type can contain multiple Kubernetes resources, for example, we can define a `webserver` workload type that is composed by Deployment and Service. + +Note that in this case, you MUST define the template of component instance in `output` section, and leave all the other templates in `outputs` with resource name claimed. The format MUST be `outputs::`. + +> This is how KubeVela know which resource is the running instance of the application component. + +Below is the example: + +```yaml +apiVersion: core.oam.dev/v1alpha2 +kind: WorkloadDefinition +metadata: + name: webserver + annotations: + definition.oam.dev/description: "webserver is a combo of Deployment + Service" +spec: + definitionRef: + name: deployments.apps + 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 + } +``` + +> TBD: a generated resource example for above workload definition. + diff --git a/docs/en/platform-engineers/advanced-cue.md b/docs/en/platform-engineers/advanced-cue.md index 61c56713c..a07432f53 100644 --- a/docs/en/platform-engineers/advanced-cue.md +++ b/docs/en/platform-engineers/advanced-cue.md @@ -335,30 +335,32 @@ 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: {} + schematic: + cue: + template: | + parameter: { + serviceURL: string } - } - } - patch: { - data: token: processing.output.token - } + 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 + } ``` diff --git a/hack/vela-templates/definitions/ingress.yaml b/hack/vela-templates/definitions/ingress.yaml index 4ab7c0b48..ba8931cf5 100644 --- a/hack/vela-templates/definitions/ingress.yaml +++ b/hack/vela-templates/definitions/ingress.yaml @@ -20,4 +20,6 @@ spec: appliesToWorkloads: - webservice - worker - template: | + schematic: + cue: + template: | diff --git a/hack/vela-templates/definitions/manualscale.yaml b/hack/vela-templates/definitions/manualscale.yaml index d8595d6f6..ef948b052 100644 --- a/hack/vela-templates/definitions/manualscale.yaml +++ b/hack/vela-templates/definitions/manualscale.yaml @@ -12,4 +12,6 @@ spec: definitionRef: name: manualscalertraits.core.oam.dev workloadRefPath: spec.workloadRef - template: | + schematic: + cue: + template: | diff --git a/hack/vela-templates/definitions/task.yaml b/hack/vela-templates/definitions/task.yaml index 3fe5cc5a7..90c7e257c 100644 --- a/hack/vela-templates/definitions/task.yaml +++ b/hack/vela-templates/definitions/task.yaml @@ -8,4 +8,6 @@ metadata: spec: definitionRef: name: jobs.batch - template: | + schematic: + cue: + template: | diff --git a/hack/vela-templates/definitions/webservice.yaml b/hack/vela-templates/definitions/webservice.yaml index 98e02ae94..5c0204a73 100644 --- a/hack/vela-templates/definitions/webservice.yaml +++ b/hack/vela-templates/definitions/webservice.yaml @@ -9,4 +9,6 @@ metadata: spec: definitionRef: name: deployments.apps - template: | + schematic: + cue: + template: | diff --git a/hack/vela-templates/definitions/worker.yaml b/hack/vela-templates/definitions/worker.yaml index e0498d660..71be4079e 100644 --- a/hack/vela-templates/definitions/worker.yaml +++ b/hack/vela-templates/definitions/worker.yaml @@ -8,4 +8,6 @@ metadata: spec: definitionRef: name: deployments.apps - template: | + schematic: + cue: + template: | diff --git a/hack/vela-templates/gen_definitions.sh b/hack/vela-templates/gen_definitions.sh index 1ee5ece1e..901e30dc7 100755 --- a/hack/vela-templates/gen_definitions.sh +++ b/hack/vela-templates/gen_definitions.sh @@ -16,7 +16,7 @@ echo "# Code generated by KubeVela templates. DO NOT EDIT." >> tmpC for filename in `ls cue`; do cat "cue/${filename}" > tmp echo "" >> tmp - sed -i.bak 's/^/ /' tmp + sed -i.bak 's/^/ /' tmp nameonly="${filename%.*}" diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml index ade7f5edd..59d919ecd 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_traitdefinitions.yaml @@ -67,6 +67,19 @@ spec: revisionEnabled: description: Revision indicates whether a trait is aware of component revision type: boolean + schematic: + description: Schematic defines the data format and template of the encapsulation of the trait + properties: + cue: + description: CUE defines the encapsulation in CUE format + properties: + template: + description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition. + type: string + required: + - template + type: object + type: object status: description: Status defines the custom health policy and status message for trait properties: @@ -77,12 +90,6 @@ spec: description: HealthPolicy defines the health check policy for the abstraction type: string type: object - template: - description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE - type: string - templateType: - description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future. - type: string workloadRefPath: description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object type: string diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml index 0ae854f2a..48b5cab90 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workloaddefinitions.yaml @@ -81,6 +81,19 @@ spec: revisionLabel: description: RevisionLabel indicates which label for underlying resources(e.g. pods) of this workload can be used by trait to create resource selectors(e.g. label selector for pods). type: string + schematic: + description: Schematic defines the data format and template of the encapsulation of the workload + properties: + cue: + description: CUE defines the encapsulation in CUE format + properties: + template: + description: Template defines the abstraction template data of the capability, it will replace the old CUE template in extension field. Template is a required field if CUE is defined in Capability Definition. + type: string + required: + - template + type: object + type: object status: description: Status defines the custom health policy and status message for workload properties: @@ -94,9 +107,6 @@ spec: template: description: Template defines the abstraction template data of the workload, it will replace the old template in extension field. the data format depends on templateType, by default it's CUE type: string - templateType: - description: TemplateType defines the data format of the template, by default it's CUE format Terraform HCL, Helm Chart will also be candidates in the near future. - type: string required: - definitionRef type: object diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go index f67850253..ada545e7e 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go @@ -1231,57 +1231,59 @@ spec: isHealth: (context.output.status.readyReplicas > 0) && (context.output.status.readyReplicas == context.output.status.replicas) customStatus: |- message: "type: " + context.output.spec.template.spec.containers[0].image + ",\t enemies:" + context.outputs.gameconfig.data.enemies - template: | - output: { - apiVersion: "apps/v1" - kind: "Deployment" - spec: { - selector: matchLabels: { - "app.oam.dev/component": context.name - } + 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 - } + template: { + metadata: labels: { + "app.oam.dev/component": context.name + } - spec: { - containers: [{ - name: context.name - image: parameter.image - envFrom: [{ - configMapRef: name: context.name + "game-config" - }] - if parameter["cmd"] != _|_ { - command: parameter.cmd - } - }] - } - } - } - } + spec: { + containers: [{ + name: context.name + image: parameter.image + 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 - } - } + 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 - } + 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 + } ` tDDefYaml = ` apiVersion: core.oam.dev/v1alpha2 @@ -1401,48 +1403,50 @@ spec: message: "type: "+ context.outputs.service.spec.type +",\t clusterIP:"+ context.outputs.service.spec.clusterIP+",\t ports:"+ "\(context.outputs.service.spec.ports[0].port)"+",\t domain"+context.outputs.ingress.spec.rules[0].host healthPolicy: | isHealth: len(context.outputs.service.spec.clusterIP) > 0 - 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 - } - }, - ] - } - }] - } - } + schematic: + cue: + 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 + } + }, + ] + } + }] + } + } ` ) diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go index c66677c33..67f94ce54 100644 --- a/pkg/oam/util/template.go +++ b/pkg/oam/util/template.go @@ -49,7 +49,7 @@ func LoadTemplate(ctx context.Context, cli client.Reader, key string, kd types.C if wd.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := NewTemplate(wd.Spec.Template, wd.Spec.Status, wd.Spec.Extension) + tmpl, err := NewTemplate(wd.Spec.Schematic, wd.Spec.Status, wd.Spec.Extension) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -69,7 +69,7 @@ func LoadTemplate(ctx context.Context, cli client.Reader, key string, kd types.C if td.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := NewTemplate(td.Spec.Template, td.Spec.Status, td.Spec.Extension) + tmpl, err := NewTemplate(td.Spec.Schematic, td.Spec.Status, td.Spec.Extension) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -85,7 +85,11 @@ func LoadTemplate(ctx context.Context, cli client.Reader, key string, kd types.C } // NewTemplate will create CUE template for inner AbstractEngine using. -func NewTemplate(template string, status *v1alpha2.Status, raw *runtime.RawExtension) (*Template, error) { +func NewTemplate(schematic *v1alpha2.Schematic, status *v1alpha2.Status, raw *runtime.RawExtension) (*Template, error) { + var template string + if schematic != nil && schematic.CUE != nil { + template = schematic.CUE.Template + } extension := map[string]interface{}{} tmp := &Template{ TemplateStr: template, @@ -108,9 +112,9 @@ func NewTemplate(template string, status *v1alpha2.Status, raw *runtime.RawExten } // ConvertTemplateJSON2Object convert spec.extension to object -func ConvertTemplateJSON2Object(in *runtime.RawExtension, specTemplate string) (types.Capability, error) { +func ConvertTemplateJSON2Object(in *runtime.RawExtension, schematic *v1alpha2.Schematic) (types.Capability, error) { var t types.Capability - capTemplate, err := NewTemplate(specTemplate, nil, in) + capTemplate, err := NewTemplate(schematic, nil, in) if err != nil { return t, errors.Wrapf(err, "parse cue template") } diff --git a/pkg/oam/util/template_test.go b/pkg/oam/util/template_test.go index 63257f4cd..a6b443f57 100644 --- a/pkg/oam/util/template_test.go +++ b/pkg/oam/util/template_test.go @@ -116,53 +116,53 @@ spec: func TestLoadTraitTemplate(t *testing.T) { cueTemplate := ` - parameter: { - domain: string - http: [string]: int - } - context: { - name: "test" - } - // trait template can have multiple outputs in one trait - outputs: service: { - apiVersion: "v1" - kind: "Service" - metadata: - name: context.name - spec: { - selector: - "app.oam.dev/component": 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 - } - }, - ] - } - }] - } - } + parameter: { + domain: string + http: [string]: int + } + context: { + name: "test" + } + // trait template can have multiple outputs in one trait + outputs: service: { + apiVersion: "v1" + kind: "Service" + metadata: + name: context.name + spec: { + selector: + "app.oam.dev/component": 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 + } + }, + ] + } + }] + } + } ` var traitDefintion = ` @@ -188,7 +188,9 @@ spec: appliesToWorkloads: - webservice - worker - template: | + schematic: + cue: + template: | ` + cueTemplate // Create mock client @@ -232,13 +234,13 @@ spec: func TestNewTemplate(t *testing.T) { testCases := map[string]struct { - tmp string + tmp *v1alpha2.Schematic status *v1alpha2.Status ext *runtime.RawExtension exp *Template }{ "only tmp": { - tmp: "t1", + tmp: &v1alpha2.Schematic{CUE: &v1alpha2.CUE{Template: "t1"}}, exp: &Template{ TemplateStr: "t1", }, @@ -256,7 +258,7 @@ func TestNewTemplate(t *testing.T) { }, }, "tmp with status": { - tmp: "t1", + tmp: &v1alpha2.Schematic{CUE: &v1alpha2.CUE{Template: "t1"}}, status: &v1alpha2.Status{ CustomStatus: "s1", HealthPolicy: "h1", diff --git a/pkg/plugins/capcenter.go b/pkg/plugins/capcenter.go index 96b5f67bd..f28f2197b 100644 --- a/pkg/plugins/capcenter.go +++ b/pkg/plugins/capcenter.go @@ -175,14 +175,14 @@ func ParseAndSyncCapability(data []byte, syncDir string) (types.Capability, erro if err != nil { return types.Capability{}, err } - return HandleDefinition(rd.Name, syncDir, rd.Spec.Reference.Name, rd.Annotations, rd.Spec.Extension, types.TypeWorkload, nil, rd.Spec.Template) + return HandleDefinition(rd.Name, syncDir, rd.Spec.Reference.Name, rd.Annotations, rd.Spec.Extension, types.TypeWorkload, nil, rd.Spec.Schematic) case "TraitDefinition": var td v1alpha2.TraitDefinition err = yaml.Unmarshal(data, &td) if err != nil { return types.Capability{}, err } - return HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Template) + return HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic) case "ScopeDefinition": // TODO(wonderflow): support scope definition here. } diff --git a/pkg/plugins/cluster.go b/pkg/plugins/cluster.go index 52e09e5ee..97d8752d9 100644 --- a/pkg/plugins/cluster.go +++ b/pkg/plugins/cluster.go @@ -61,7 +61,7 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c types.Args var templateErrors []error for _, wd := range workloadDefs.Items { - tmp, err := HandleDefinition(wd.Name, syncDir, wd.Spec.Reference.Name, wd.Annotations, wd.Spec.Extension, types.TypeWorkload, nil, wd.Spec.Template) + tmp, err := HandleDefinition(wd.Name, syncDir, wd.Spec.Reference.Name, wd.Annotations, wd.Spec.Extension, types.TypeWorkload, nil, wd.Spec.Schematic) if err != nil { templateErrors = append(templateErrors, errors.Wrapf(err, "handle workload template `%s` failed", wd.Name)) continue @@ -93,7 +93,7 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c types.Args, s var templateErrors []error for _, td := range traitDefs.Items { - tmp, err := HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Template) + tmp, err := HandleDefinition(td.Name, syncDir, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic) if err != nil { templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name)) continue @@ -134,9 +134,9 @@ func validateCapabilities(tmp types.Capability, dm discoverymapper.DiscoveryMapp } // HandleDefinition will handle definition to capability -func HandleDefinition(name, syncDir, crdName string, annotation map[string]string, extension *runtime.RawExtension, tp types.CapType, applyTo []string, template string) (types.Capability, error) { +func HandleDefinition(name, syncDir, crdName string, annotation map[string]string, extension *runtime.RawExtension, tp types.CapType, applyTo []string, schematic *corev1alpha2.Schematic) (types.Capability, error) { var tmp types.Capability - tmp, err := HandleTemplate(extension, template, name, syncDir) + tmp, err := HandleTemplate(extension, schematic, name, syncDir) if err != nil { return types.Capability{}, err } @@ -162,15 +162,15 @@ func GetDescription(annotation map[string]string) string { } // HandleTemplate will handle definition template to capability -func HandleTemplate(in *runtime.RawExtension, specTemplate, name, syncDir string) (types.Capability, error) { - tmp, err := util.ConvertTemplateJSON2Object(in, specTemplate) +func HandleTemplate(in *runtime.RawExtension, schematic *corev1alpha2.Schematic, name, syncDir string) (types.Capability, error) { + tmp, err := util.ConvertTemplateJSON2Object(in, schematic) if err != nil { return types.Capability{}, err } tmp.Name = name // if spec.template is not empty it should has the highest priority - if specTemplate != "" { - tmp.CueTemplate = specTemplate + if schematic != nil && schematic.CUE != nil { + tmp.CueTemplate = schematic.CUE.Template tmp.CueTemplateURI = "" } if tmp.CueTemplateURI != "" { @@ -245,7 +245,7 @@ func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir } if foundCapability { template, err := HandleDefinition(capabilityName, localDefinitionDir, workloadDef.Spec.Reference.Name, - workloadDef.Annotations, workloadDef.Spec.Extension, types.TypeWorkload, nil, workloadDef.Spec.Template) + workloadDef.Annotations, workloadDef.Spec.Extension, types.TypeWorkload, nil, workloadDef.Spec.Schematic) if err == nil { return &template, nil } @@ -259,7 +259,7 @@ func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir } if foundCapability { template, err := HandleDefinition(capabilityName, localDefinitionDir, traitDef.Spec.Reference.Name, - traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil, workloadDef.Spec.Template) + traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil, workloadDef.Spec.Schematic) if err == nil { return &template, nil } diff --git a/pkg/utils/apply/apply.go b/pkg/utils/apply/apply.go index a0b4a671b..c85201b30 100644 --- a/pkg/utils/apply/apply.go +++ b/pkg/utils/apply/apply.go @@ -4,7 +4,6 @@ import ( "context" "github.com/crossplane/crossplane-runtime/pkg/logging" - "github.com/pkg/errors" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go index 8469d1cce..61268149d 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/traitdefinition/validating_handler.go @@ -124,7 +124,7 @@ func ValidateDefinitionReference(_ context.Context, td v1alpha2.TraitDefinition) if len(td.Spec.Reference.Name) > 0 { return nil } - tmp, err := util.NewTemplate(td.Spec.Template, td.Spec.Status, td.Spec.Extension) + tmp, err := util.NewTemplate(td.Spec.Schematic, td.Spec.Status, td.Spec.Extension) if err != nil { return errors.Wrap(err, errValidateDefRef) } diff --git a/test/e2e-test/health_scope_test.go b/test/e2e-test/health_scope_test.go index 40840b6cd..01b4f70c3 100644 --- a/test/e2e-test/health_scope_test.go +++ b/test/e2e-test/health_scope_test.go @@ -16,9 +16,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/oam-dev/kubevela/pkg/oam/util" - "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + "github.com/oam-dev/kubevela/pkg/oam/util" ) var (