mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-27 16:17:34 +00:00
Extend application's capabilities by trait definition (#742)
* add dsl pkg * realize context capabilities for application * fix golint * fix check-diff * upgrade vela server sample * comment group * Definition Reference be Optional & fix three-part trait crd apply without namespace * Improve samples
This commit is contained in:
@@ -97,7 +97,7 @@ type WorkloadDefinitionList struct {
|
||||
// A TraitDefinitionSpec defines the desired state of a TraitDefinition.
|
||||
type TraitDefinitionSpec struct {
|
||||
// Reference to the CustomResourceDefinition that defines this trait kind.
|
||||
Reference DefinitionReference `json:"definitionRef"`
|
||||
Reference DefinitionReference `json:"definitionRef,omitempty"`
|
||||
|
||||
// Revision indicates whether a trait is aware of component revision
|
||||
// +optional
|
||||
|
||||
@@ -66,8 +66,6 @@ spec:
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object
|
||||
type: string
|
||||
required:
|
||||
- definitionRef
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# Application Example
|
||||
|
||||
In this Demo, Application application-sample will be converted to appconfig and component
|
||||
|
||||
The fields in the application spec come from the parametes defined in the definition template
|
||||
, so we must install Definition at first
|
||||
|
||||
Step 1: Install Workload Definition & Trait Definition
|
||||
```
|
||||
kubectl apply -f template.yaml
|
||||
```
|
||||
Step 2: Create a sample application in the cluster
|
||||
```
|
||||
kubectl apply -f application-sample.yaml
|
||||
```
|
||||
Step 3: View the application status
|
||||
```
|
||||
kubectl get -f application-sample.yaml -oyaml
|
||||
|
||||
// You can see the following
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"apiVersion":"core.oam.dev/v1alpha2","kind":"Application","metadata":{"annotations":{},"name":"application-sample","namespace":"oam-test"},"spec":{"template":"services:\n myweb:\n type: worker\n image: \"busybox\"\n cmd:\n - sleep\n - \"1000\"\n scaler:\n replicas: 10"}}
|
||||
name: application-sample
|
||||
namespace: oam-test
|
||||
spec:
|
||||
services:
|
||||
myweb:
|
||||
cmd:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
scaler:
|
||||
replicas: 10
|
||||
service:
|
||||
http:
|
||||
server: 80
|
||||
sidecar:
|
||||
command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: test-sidecar
|
||||
type: worker
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2020-12-02T12:12:52Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Parsed
|
||||
- lastTransitionTime: "2020-12-02T12:12:52Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Built
|
||||
- lastTransitionTime: "2020-12-02T12:12:52Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Applied
|
||||
status: running
|
||||
|
||||
```
|
||||
|
||||
Step 4: View the oam CR generated by application
|
||||
|
||||
```
|
||||
kubectl get appconfig/application-sample -oyaml
|
||||
|
||||
// appconfig is as follows
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: ApplicationConfiguration
|
||||
metadata:
|
||||
labels:
|
||||
application.oam.dev: application-sample
|
||||
name: application-sample
|
||||
namespace: oam-test
|
||||
ownerReferences:
|
||||
- apiVersion: core.oam.dev/v1alpha2
|
||||
controller: true
|
||||
kind: Application
|
||||
name: application-sample
|
||||
uid: dca7acc3-664c-422b-aa52-4fe012e37974
|
||||
spec:
|
||||
components:
|
||||
- componentName: myweb
|
||||
traits:
|
||||
- trait:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: myweb
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: myweb
|
||||
|
||||
kubectl get component/myweb -oyaml
|
||||
|
||||
// component is as follows
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Component
|
||||
metadata:
|
||||
labels:
|
||||
application.oam.dev: application-sample
|
||||
name: myweb
|
||||
namespace: oam-test
|
||||
ownerReferences:
|
||||
- apiVersion: core.oam.dev/v1alpha2
|
||||
controller: true
|
||||
kind: Application
|
||||
name: application-sample
|
||||
uid: dca7acc3-664c-422b-aa52-4fe012e37974
|
||||
spec:
|
||||
workload:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
replicas: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
app.oam.dev/component: myweb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: myweb
|
||||
app.oam.dev/component: myweb
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: myweb
|
||||
- command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: test-sidecar
|
||||
```
|
||||
|
||||
@@ -1,146 +1,332 @@
|
||||
# Vela Server
|
||||
## example
|
||||
# Definition Docs
|
||||
|
||||
In this Demo, Application application-sample will be converted to appconfig and component
|
||||
## Reserved word
|
||||
### patch
|
||||
Perform the CUE AND operation with the content declared by 'patch' and workload cr
|
||||
|
||||
The fields in the application spec come from the parametes defined in the definition template
|
||||
, so we must install Definition at first
|
||||
### output
|
||||
Generate a new cr, which is generally associated with workload cr
|
||||
|
||||
Step 1: Install Workload Definition & Trait Definition
|
||||
## Workload Definition
|
||||
The following workload definition is to generate a deployment
|
||||
```
|
||||
kubectl apply -f template.yaml
|
||||
```
|
||||
Step 2: Create a sample application in the cluster
|
||||
```
|
||||
kubectl apply -f application-sample.yaml
|
||||
```
|
||||
Step 3: View the application status
|
||||
```
|
||||
kubectl get -f application-sample.yaml -oyaml
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: WorkloadDefinition
|
||||
metadata:
|
||||
name: worker
|
||||
annotations:
|
||||
definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
|
||||
spec:
|
||||
definitionRef:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
template: |
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
"app.oam.dev/component": context.name
|
||||
}
|
||||
|
||||
// You can see the following
|
||||
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
|
||||
|
||||
cmd?: [...string]
|
||||
}
|
||||
```
|
||||
|
||||
If defined an application as follows
|
||||
```
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
kubectl.kubernetes.io/last-applied-configuration: |
|
||||
{"apiVersion":"core.oam.dev/v1alpha2","kind":"Application","metadata":{"annotations":{},"name":"application-sample","namespace":"oam-test"},"spec":{"template":"services:\n myweb:\n type: worker\n image: \"busybox\"\n cmd:\n - sleep\n - \"1000\"\n scaler:\n replicas: 10"}}
|
||||
name: application-sample
|
||||
namespace: oam-test
|
||||
spec:
|
||||
services:
|
||||
myweb:
|
||||
type: worker
|
||||
image: "busybox"
|
||||
cmd:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
scaler:
|
||||
replicas: 10
|
||||
type: worker
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2020-12-02T12:12:52Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Parsed
|
||||
- lastTransitionTime: "2020-12-02T12:12:52Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Built
|
||||
- lastTransitionTime: "2020-12-02T12:12:52Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Applied
|
||||
status: running
|
||||
|
||||
```
|
||||
|
||||
Step 4: View the oam CR generated by application
|
||||
|
||||
we will get a deployment
|
||||
```
|
||||
kubectl get appconfig/application-sample -oyaml
|
||||
|
||||
// appconfig is as follows
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: ApplicationConfiguration
|
||||
metadata:
|
||||
labels:
|
||||
application.oam.dev: application-sample
|
||||
name: application-sample
|
||||
namespace: oam-test
|
||||
ownerReferences:
|
||||
- apiVersion: core.oam.dev/v1alpha2
|
||||
controller: true
|
||||
kind: Application
|
||||
name: application-sample
|
||||
uid: dca7acc3-664c-422b-aa52-4fe012e37974
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
components:
|
||||
- componentName: myweb
|
||||
traits:
|
||||
- trait:
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: ManualScalerTrait
|
||||
spec:
|
||||
replicaCount: 10
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2020-12-02T12:12:52Z"
|
||||
reason: Successfully reconciled resource
|
||||
status: "True"
|
||||
type: Synced
|
||||
dependency: {}
|
||||
workloads:
|
||||
- componentName: myweb
|
||||
componentRevisionName: myweb-v1
|
||||
traits:
|
||||
- traitRef:
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: ManualScalerTrait
|
||||
name: myweb-trait-78fdd467d6
|
||||
workloadRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: myweb
|
||||
|
||||
kubectl get component/myweb -oyaml
|
||||
|
||||
// component is as follows
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Component
|
||||
metadata:
|
||||
labels:
|
||||
application.oam.dev: application-sample
|
||||
name: myweb
|
||||
namespace: oam-test
|
||||
ownerReferences:
|
||||
- apiVersion: core.oam.dev/v1alpha2
|
||||
controller: true
|
||||
kind: Application
|
||||
name: application-sample
|
||||
uid: dca7acc3-664c-422b-aa52-4fe012e37974
|
||||
spec:
|
||||
workload:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
selector:
|
||||
matchLabels:
|
||||
app.oam.dev/component: myweb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.oam.dev/component: myweb
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.oam.dev/component: myweb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.oam.dev/component: myweb
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: myweb
|
||||
status:
|
||||
latestRevision:
|
||||
name: myweb-v1
|
||||
revision: 1
|
||||
containers:
|
||||
- command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: myweb
|
||||
```
|
||||
## Service Trait Definition
|
||||
|
||||
Define a trait Definition that appends service to workload(worker) , as shown below
|
||||
```
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "service the app"
|
||||
name: service
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worker
|
||||
definitionRef:
|
||||
name: service.v1
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
If add service capability to the application, as follows
|
||||
```
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: application-sample
|
||||
spec:
|
||||
services:
|
||||
myweb:
|
||||
type: worker
|
||||
image: "busybox"
|
||||
cmd:
|
||||
- sleep
|
||||
- "1000"
|
||||
service:
|
||||
http:
|
||||
server: 80
|
||||
```
|
||||
|
||||
we will get a new deployment and service
|
||||
```
|
||||
// origin deployment template add labels
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.oam.dev/component: myweb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
// add label app
|
||||
app: myweb
|
||||
app.oam.dev/component: myweb
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: myweb
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: myweb
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: myweb
|
||||
```
|
||||
|
||||
## Scaler Trait Definition
|
||||
|
||||
Define a trait Definition that scale workload(worker) replicas
|
||||
```
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "Manually scale the app"
|
||||
name: scaler
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worke
|
||||
extension:
|
||||
template: |-
|
||||
patch: {
|
||||
spec: replicas: parameter.replicas
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
replicas: *1 | int
|
||||
}
|
||||
```
|
||||
If add scaler capability to the application, as follows
|
||||
```
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: application-sample
|
||||
spec:
|
||||
services:
|
||||
myweb:
|
||||
type: worker
|
||||
image: "busybox"
|
||||
cmd:
|
||||
- sleep
|
||||
- "1000"
|
||||
service:
|
||||
http:
|
||||
server: 80
|
||||
scaler:
|
||||
replicas: 10
|
||||
```
|
||||
|
||||
The deployment replicas will be scale to 10
|
||||
```
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.oam.dev/component: myweb
|
||||
// scale to 10
|
||||
replicas: 10
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
// add label app
|
||||
app: myweb
|
||||
app.oam.dev/component: myweb
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: myweb
|
||||
```
|
||||
|
||||
## Sidecar Trait Definition
|
||||
|
||||
Define a trait Definition that append containers to workload(worker)
|
||||
```
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "add sidecar to the app"
|
||||
name: sidecar
|
||||
spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worke
|
||||
extension:
|
||||
template: |-
|
||||
_containers: context.input.spec.template.spec.containers+[parameter]
|
||||
patch: {
|
||||
spec: template: spec: containers: _containers
|
||||
}
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
command?: [...string]
|
||||
}
|
||||
```
|
||||
|
||||
If add sidercar capability to the application, as follows
|
||||
```
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Application
|
||||
metadata:
|
||||
name: application-sample
|
||||
spec:
|
||||
services:
|
||||
myweb:
|
||||
type: worker
|
||||
image: "busybox"
|
||||
cmd:
|
||||
- sleep
|
||||
- "1000"
|
||||
service:
|
||||
http:
|
||||
server: 80
|
||||
scaler:
|
||||
replicas: 10
|
||||
sidercar:
|
||||
name: "sidecar-test"
|
||||
image: "nginx"
|
||||
```
|
||||
The deployment updated as follows
|
||||
```
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.oam.dev/component: myweb
|
||||
// scale to 10
|
||||
replicas: 10
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
// add label app
|
||||
app: myweb
|
||||
app.oam.dev/component: myweb
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- sleep
|
||||
- "1000"
|
||||
image: busybox
|
||||
name: myweb
|
||||
- name: sidecar-test
|
||||
image: nginx
|
||||
```
|
||||
@@ -11,4 +11,10 @@ spec:
|
||||
- sleep
|
||||
- "1000"
|
||||
scaler:
|
||||
replicas: 10
|
||||
replicas: 10
|
||||
sidercar:
|
||||
name: "sidecar-test"
|
||||
image: "nginx"
|
||||
service:
|
||||
http:
|
||||
server: 80
|
||||
@@ -58,19 +58,76 @@ spec:
|
||||
appliesToWorkloads:
|
||||
- webservice
|
||||
- worker
|
||||
definitionRef:
|
||||
name: manualscalertraits.core.oam.dev
|
||||
workloadRefPath: spec.workloadRef
|
||||
extension:
|
||||
template: |-
|
||||
output: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: parameter.replicas
|
||||
}
|
||||
patch: {
|
||||
spec: replicas: parameter.replicas
|
||||
}
|
||||
parameter: {
|
||||
//+short=r
|
||||
replicas: *1 | int
|
||||
}
|
||||
---
|
||||
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: |-
|
||||
_containers: context.input.spec.template.spec.containers+[parameter]
|
||||
patch: {
|
||||
spec: template: spec: containers: _containers
|
||||
}
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
command?: [...string]
|
||||
}
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: "service the app"
|
||||
name: service
|
||||
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
|
||||
}
|
||||
---
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
name: services
|
||||
namespace: default
|
||||
spec:
|
||||
definitionRef:
|
||||
name: services
|
||||
|
||||
@@ -65,8 +65,6 @@ spec:
|
||||
workloadRefPath:
|
||||
description: WorkloadRefPath indicates where/if a trait accepts a workloadRef object
|
||||
type: string
|
||||
required:
|
||||
- definitionRef
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha2
|
||||
|
||||
@@ -19,6 +19,7 @@ package application
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
"github.com/go-logr/logr"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -69,6 +70,8 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (result ctrl.Result, gerr error
|
||||
app.Status.Phase = v1alpha2.ApplicationRendering
|
||||
handler := &reter{r, app, applog}
|
||||
|
||||
app.Status.Conditions = []v1alpha1.Condition{}
|
||||
|
||||
applog.Info("parse template")
|
||||
// parse template
|
||||
appParser := parser.NewParser(template.GetHanler(fclient.NewDefinitionClient(r.Client)))
|
||||
|
||||
@@ -9,9 +9,11 @@ import (
|
||||
"cuelang.org/go/cue/build"
|
||||
cueparser "cuelang.org/go/cue/parser"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/parser"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/process"
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
@@ -26,7 +28,7 @@ const (
|
||||
// Build template to applicationConfig & Component
|
||||
func Build(ns string, app *parser.Appfile) (*v1alpha2.ApplicationConfiguration, []*v1alpha2.Component, error) {
|
||||
b := &builder{app}
|
||||
return b.Complete(ns)
|
||||
return b.CompleteWithContext(ns)
|
||||
}
|
||||
|
||||
// Complete: builder complete rendering
|
||||
@@ -44,7 +46,6 @@ func (b *builder) Complete(ns string) (*v1alpha2.ApplicationConfiguration, []*v1
|
||||
|
||||
componets := []*v1alpha2.Component{}
|
||||
for _, wl := range b.app.Services() {
|
||||
|
||||
compCtx := map[string]string{"name": wl.Name()}
|
||||
|
||||
component, err := wl.Eval(newLoader(compCtx))
|
||||
@@ -78,6 +79,75 @@ func (b *builder) Complete(ns string) (*v1alpha2.ApplicationConfiguration, []*v1
|
||||
return appconfig, componets, nil
|
||||
}
|
||||
|
||||
func (b *builder) CompleteWithContext(ns string) (*v1alpha2.ApplicationConfiguration, []*v1alpha2.Component, error) {
|
||||
appconfig := &v1alpha2.ApplicationConfiguration{}
|
||||
appconfig.SetGroupVersionKind(v1alpha2.ApplicationConfigurationGroupVersionKind)
|
||||
appconfig.Name = b.app.Name()
|
||||
appconfig.Namespace = ns
|
||||
appconfig.Spec.Components = []v1alpha2.ApplicationConfigurationComponent{}
|
||||
|
||||
if appconfig.Labels == nil {
|
||||
appconfig.Labels = map[string]string{}
|
||||
}
|
||||
appconfig.Labels[OamApplicationLabel] = b.app.Name()
|
||||
|
||||
componets := []*v1alpha2.Component{}
|
||||
for _, wl := range b.app.Services() {
|
||||
pCtx := process.NewContext(wl.Name())
|
||||
if err := wl.EvalContext(pCtx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, tr := range wl.Traits() {
|
||||
if err := tr.EvalContext(pCtx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
comp, acComp, err := generateOAM(pCtx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
comp.Name = wl.Name()
|
||||
acComp.ComponentName = comp.Name
|
||||
|
||||
comp.Namespace = ns
|
||||
if comp.Labels == nil {
|
||||
comp.Labels = map[string]string{}
|
||||
}
|
||||
comp.Labels[OamApplicationLabel] = b.app.Name()
|
||||
comp.SetGroupVersionKind(v1alpha2.ComponentGroupVersionKind)
|
||||
|
||||
componets = append(componets, comp)
|
||||
appconfig.Spec.Components = append(appconfig.Spec.Components, *acComp)
|
||||
}
|
||||
|
||||
return appconfig, componets, nil
|
||||
}
|
||||
|
||||
func generateOAM(pCtx process.Context) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) {
|
||||
base, assists := pCtx.Output()
|
||||
componetWorkload, err := base.Object(nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
component := &v1alpha2.Component{}
|
||||
component.Spec.Workload.Object = componetWorkload
|
||||
|
||||
acComponent := &v1alpha2.ApplicationConfigurationComponent{}
|
||||
acComponent.Traits = []v1alpha2.ComponentTrait{}
|
||||
for _, assist := range assists {
|
||||
traitRef, err := assist.Object(nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
acComponent.Traits = append(acComponent.Traits, v1alpha2.ComponentTrait{
|
||||
Trait: runtime.RawExtension{
|
||||
Object: traitRef,
|
||||
},
|
||||
})
|
||||
}
|
||||
return component, acComponent, nil
|
||||
}
|
||||
|
||||
type loader struct {
|
||||
files map[string]*ast.File
|
||||
err error
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/template"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/definition"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/process"
|
||||
)
|
||||
|
||||
// Render is cue render
|
||||
@@ -67,6 +69,11 @@ func (wl *Workload) Eval(render Render) (*v1alpha2.Component, error) {
|
||||
return component, nil
|
||||
}
|
||||
|
||||
// EvalContext eval workload template and set result to context
|
||||
func (wl *Workload) EvalContext(ctx process.Context) error {
|
||||
return definition.NewWDTemplater("-", wl.template).Params(wl.params).Complete(ctx)
|
||||
}
|
||||
|
||||
// Trait is ComponentTrait
|
||||
type Trait struct {
|
||||
name string
|
||||
@@ -120,6 +127,11 @@ func (trait *Trait) Eval(render Render) ([]v1alpha2.ComponentTrait, error) {
|
||||
return compTraits, nil
|
||||
}
|
||||
|
||||
// EvalContext eval trait template and set result to context
|
||||
func (trait *Trait) EvalContext(ctx process.Context) error {
|
||||
return definition.NewTDTemplater("-", trait.template).Params(trait.params).Complete(ctx)
|
||||
}
|
||||
|
||||
// Appfile describle application
|
||||
type Appfile struct {
|
||||
name string
|
||||
|
||||
@@ -229,6 +229,7 @@ func (r *components) renderTrait(ctx context.Context, ct v1alpha2.ComponentTrait
|
||||
traitDef, err := util.FetchTraitDefinition(ctx, r.client, r.dm, t)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
t.SetNamespace(ac.GetNamespace())
|
||||
return t, util.GetDummyTraitDefinition(t), nil
|
||||
}
|
||||
return nil, nil, errors.Wrapf(err, errFmtGetTraitDefinition, t.GetAPIVersion(), t.GetKind(), t.GetName())
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package definition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/build"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/model"
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/process"
|
||||
)
|
||||
|
||||
// Template defines Definition's Render interface
|
||||
type Template interface {
|
||||
Params(params interface{}) Template
|
||||
Complete(ctx process.Context) error
|
||||
}
|
||||
|
||||
type def struct {
|
||||
name string
|
||||
templ string
|
||||
params interface{}
|
||||
}
|
||||
|
||||
type workloadDef struct {
|
||||
def
|
||||
}
|
||||
|
||||
// NewWDTemplater create Workload Definition templater
|
||||
func NewWDTemplater(name, templ string) Template {
|
||||
return &workloadDef{
|
||||
def: def{
|
||||
name: name,
|
||||
templ: templ,
|
||||
params: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Params set definition's params
|
||||
func (wd *workloadDef) Params(params interface{}) Template {
|
||||
wd.params = params
|
||||
return wd
|
||||
}
|
||||
|
||||
// Complete do workload definition's rendering
|
||||
func (wd *workloadDef) Complete(ctx process.Context) error {
|
||||
bi := build.NewContext().NewInstance("", nil)
|
||||
if err := bi.AddFile("-", wd.templ); err != nil {
|
||||
return err
|
||||
}
|
||||
if wd.params != nil {
|
||||
bt, _ := json.Marshal(wd.params)
|
||||
if err := bi.AddFile("parameter", fmt.Sprintf("parameter: %s", string(bt))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := bi.AddFile("-", ctx.Compile("context")); err != nil {
|
||||
return err
|
||||
}
|
||||
insts := cue.Build([]*build.Instance{bi})
|
||||
for _, inst := range insts {
|
||||
if err := inst.Value().Err(); err != nil {
|
||||
return errors.WithMessagef(err, "workloadDef %s eval", wd.name)
|
||||
}
|
||||
output := inst.Lookup("output")
|
||||
base, err := model.NewBase(output)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "workloadDef %s new base", wd.name)
|
||||
}
|
||||
ctx.SetBase(base)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type traitDef struct {
|
||||
def
|
||||
}
|
||||
|
||||
// NewTDTemplater create Trait Definition templater
|
||||
func NewTDTemplater(name, templ string) Template {
|
||||
return &traitDef{
|
||||
def: def{
|
||||
name: name,
|
||||
templ: templ,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Params set definition's params
|
||||
func (td *traitDef) Params(params interface{}) Template {
|
||||
td.params = params
|
||||
return td
|
||||
}
|
||||
|
||||
// Complete do trait definition's rendering
|
||||
func (td *traitDef) Complete(ctx process.Context) error {
|
||||
bi := build.NewContext().NewInstance("", nil)
|
||||
if err := bi.AddFile("-", td.templ); err != nil {
|
||||
return err
|
||||
}
|
||||
if td.params != nil {
|
||||
bt, _ := json.Marshal(td.params)
|
||||
if err := bi.AddFile("parameter", fmt.Sprintf("parameter: %s", string(bt))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := bi.AddFile("f", ctx.Compile("context")); err != nil {
|
||||
return err
|
||||
}
|
||||
insts := cue.Build([]*build.Instance{bi})
|
||||
for _, inst := range insts {
|
||||
|
||||
if err := inst.Value().Err(); err != nil {
|
||||
return errors.WithMessagef(err, "traitDef %s build", td.name)
|
||||
}
|
||||
|
||||
output := inst.Lookup("output")
|
||||
if output.Exists() {
|
||||
other, err := model.NewOther(output)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "traitDef %s new Assist", td.name)
|
||||
}
|
||||
ctx.PutAssistants(other)
|
||||
}
|
||||
|
||||
outputs := inst.Lookup("outputs")
|
||||
st, err := outputs.Struct()
|
||||
if err == nil {
|
||||
for i := 0; i < st.Len(); i++ {
|
||||
fieldInfo := st.Field(i)
|
||||
if fieldInfo.IsDefinition || fieldInfo.IsHidden || fieldInfo.IsOptional {
|
||||
continue
|
||||
}
|
||||
other, err := model.NewOther(fieldInfo.Value)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "traitDef %s new Assists(%s)", td.name, fieldInfo.Name)
|
||||
}
|
||||
ctx.PutAssistants(other)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
patcher := inst.Lookup("patch")
|
||||
if patcher.Exists() {
|
||||
base, _ := ctx.Output()
|
||||
p, err := model.NewOther(patcher)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "traitDef %s patcher NewOther", td.name)
|
||||
}
|
||||
if err := base.Unity(p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package definition
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/process"
|
||||
)
|
||||
|
||||
func TestWDTemplate(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
templ string
|
||||
params map[string]interface{}
|
||||
expectObj runtime.Object
|
||||
}{
|
||||
{
|
||||
templ: `
|
||||
output:{
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: context.name
|
||||
spec: replicas: parameter.replicas
|
||||
}
|
||||
|
||||
parameter: {
|
||||
replicas: *1 | int
|
||||
}
|
||||
`,
|
||||
params: map[string]interface{}{
|
||||
"replicas": 2,
|
||||
},
|
||||
expectObj: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"replicas": int64(2)}}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range testCases {
|
||||
ctx := process.NewContext("test")
|
||||
wt := NewWDTemplater("-", v.templ)
|
||||
if err := wt.Params(v.params).Complete(ctx); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
base, assists := ctx.Output()
|
||||
assert.Equal(t, 0, len(assists))
|
||||
assert.Equal(t, false, base == nil)
|
||||
baseObj, err := base.Object(nil)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, v.expectObj, baseObj)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTDTemplate(t *testing.T) {
|
||||
baseTemplate := `
|
||||
output:{
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: context.name
|
||||
spec: {
|
||||
replicas: parameter.replicas
|
||||
template: spec: {
|
||||
containers: [{image: "website:0.1",name:"main"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
replicas: *1 | int
|
||||
}
|
||||
`
|
||||
ctx := process.NewContext("test")
|
||||
wt := NewWDTemplater("-", baseTemplate)
|
||||
if err := wt.Params(map[string]interface{}{
|
||||
"replicas": 2,
|
||||
}).Complete(ctx); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
tds := []struct {
|
||||
templ string
|
||||
params map[string]interface{}
|
||||
}{
|
||||
{
|
||||
templ: `
|
||||
patch: {
|
||||
_containers: context.input.spec.template.spec.containers+[parameter]
|
||||
sepc: template: spec: containers: _containers
|
||||
}
|
||||
|
||||
parameter: {
|
||||
name: string
|
||||
image: string
|
||||
command?: [...string]
|
||||
}
|
||||
`,
|
||||
params: map[string]interface{}{
|
||||
"name": "sidecar",
|
||||
"image": "metrics-agent:0.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range tds {
|
||||
td := NewTDTemplater("-", v.templ)
|
||||
if err := td.Params(v.params).Complete(ctx); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
base, assists := ctx.Output()
|
||||
assert.Equal(t, 0, len(assists))
|
||||
assert.Equal(t, false, base == nil)
|
||||
obj, err := base.Object(nil)
|
||||
assert.Equal(t, nil, err)
|
||||
expect := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{"name": "test"}, "sepc": map[string]interface{}{"template": map[string]interface{}{"spec": map[string]interface{}{"containers": []interface{}{map[string]interface{}{"image": "website:0.1", "name": "main"}, map[string]interface{}{"image": "metrics-agent:0.2", "name": "sidecar"}}}}}, "spec": map[string]interface{}{"replicas": int64(2), "template": map[string]interface{}{"spec": map[string]interface{}{"containers": []interface{}{map[string]interface{}{"image": "website:0.1", "name": "main"}}}}}}}
|
||||
assert.Equal(t, expect, obj)
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/cue/ast"
|
||||
"cuelang.org/go/cue/format"
|
||||
"cuelang.org/go/cue/token"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Instance defines Model Interface
|
||||
type Instance interface {
|
||||
String() string
|
||||
Object(m *runtime.Scheme) (runtime.Object, error)
|
||||
IsBase() bool
|
||||
Unity(other Instance) error
|
||||
}
|
||||
|
||||
type instance struct {
|
||||
v string
|
||||
base bool
|
||||
}
|
||||
|
||||
// String return instance's cue format string
|
||||
func (inst *instance) String() string {
|
||||
return inst.v
|
||||
}
|
||||
|
||||
// IsBase indicate whether the instance is base model
|
||||
func (inst *instance) IsBase() bool {
|
||||
return inst.base
|
||||
}
|
||||
|
||||
// Object convert to runtime.Object
|
||||
func (inst *instance) Object(m *runtime.Scheme) (runtime.Object, error) {
|
||||
var r cue.Runtime
|
||||
cueInst, err := r.Compile("-", inst.v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o := new(unstructured.Unstructured)
|
||||
jsonv, err := cueInst.Value().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := o.UnmarshalJSON(jsonv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m != nil {
|
||||
object, err := m.New(o.GetObjectKind().GroupVersionKind())
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(jsonv, object); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return object, nil
|
||||
}
|
||||
if !runtime.IsNotRegisteredError(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Unity implement unity operations between instances
|
||||
func (inst *instance) Unity(other Instance) error {
|
||||
var r cue.Runtime
|
||||
raw, err := r.Compile("-", inst.v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o, err := r.Compile("-", other.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pv, err := print(raw.Value().Unify(o.Value()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inst.v = pv
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewBase create a base instance
|
||||
func NewBase(v cue.Value) (Instance, error) {
|
||||
vs, err := openPrint(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance{
|
||||
v: vs,
|
||||
base: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewOther create a non-base instance
|
||||
func NewOther(v cue.Value) (Instance, error) {
|
||||
vs, err := openPrint(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance{
|
||||
v: vs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func print(v cue.Value) (string, error) {
|
||||
v = v.Eval()
|
||||
syopts := []cue.Option{cue.All(), cue.DisallowCycles(true), cue.ResolveReferences(true)}
|
||||
|
||||
var w bytes.Buffer
|
||||
useSep := false
|
||||
format := func(name string, n ast.Node) error {
|
||||
if name != "" {
|
||||
// TODO: make this relative to DIR
|
||||
fmt.Fprintf(&w, "// %s\n", filepath.Base(name))
|
||||
} else if useSep {
|
||||
fmt.Println("// ---")
|
||||
}
|
||||
useSep = true
|
||||
|
||||
b, err := format.Node(toFile(n))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := format("", v.Syntax(syopts...)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
instStr := w.String()
|
||||
return instStr, nil
|
||||
}
|
||||
|
||||
func toFile(n ast.Node) *ast.File {
|
||||
switch x := n.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *ast.StructLit:
|
||||
return &ast.File{Decls: x.Elts}
|
||||
case ast.Expr:
|
||||
ast.SetRelPos(x, token.NoSpace)
|
||||
return &ast.File{Decls: []ast.Decl{&ast.EmbedDecl{Expr: x}}}
|
||||
case *ast.File:
|
||||
return x
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported node type %T", x))
|
||||
}
|
||||
}
|
||||
|
||||
func openPrint(v cue.Value) (string, error) {
|
||||
sysopts := []cue.Option{cue.All(), cue.DisallowCycles(true), cue.ResolveReferences(true)}
|
||||
f := toFile(v.Syntax(sysopts...))
|
||||
for _, decl := range f.Decls {
|
||||
listOpen(decl)
|
||||
}
|
||||
ret, err := format.Node(f)
|
||||
return string(ret), err
|
||||
}
|
||||
|
||||
func listOpen(expr ast.Node) {
|
||||
switch v := expr.(type) {
|
||||
case *ast.Field:
|
||||
listOpen(v.Value)
|
||||
case *ast.StructLit:
|
||||
for _, elt := range v.Elts {
|
||||
listOpen(elt)
|
||||
}
|
||||
case *ast.BinaryExpr:
|
||||
listOpen(v.X)
|
||||
listOpen(v.Y)
|
||||
case *ast.EmbedDecl:
|
||||
listOpen(v.Expr)
|
||||
case *ast.Comprehension:
|
||||
listOpen(v.Value)
|
||||
case *ast.ListLit:
|
||||
for _, elt := range v.Elts {
|
||||
listOpen(elt)
|
||||
}
|
||||
if len(v.Elts) > 0 {
|
||||
if _, ok := v.Elts[len(v.Elts)-1].(*ast.Ellipsis); !ok {
|
||||
v.Elts = append(v.Elts, &ast.Ellipsis{})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/bmizerany/assert"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func TestInstance(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
src string
|
||||
gvk schema.GroupVersionKind
|
||||
}{{
|
||||
src: `apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: "test"
|
||||
`,
|
||||
gvk: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
}},
|
||||
}
|
||||
|
||||
for _, v := range testCases {
|
||||
var r cue.Runtime
|
||||
inst, err := r.Compile("-", v.src)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
base, err := NewBase(inst.Value())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
baseObj, err := base.Object(nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, v.gvk, baseObj.GetObjectKind().GroupVersionKind())
|
||||
assert.Equal(t, true, base.IsBase())
|
||||
|
||||
other, err := NewOther(inst.Value())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
otherObj, err := other.Object(nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, v.gvk, otherObj.GetObjectKind().GroupVersionKind())
|
||||
assert.Equal(t, false, other.IsBase())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/model"
|
||||
)
|
||||
|
||||
// Context defines Rendering Context Interface
|
||||
type Context interface {
|
||||
SetBase(base model.Instance)
|
||||
PutAssistants(insts ...model.Instance)
|
||||
Output() (model.Instance, []model.Instance)
|
||||
Compile(label string) string
|
||||
}
|
||||
|
||||
type context struct {
|
||||
name string
|
||||
configs map[string]interface{}
|
||||
base model.Instance
|
||||
assistants []model.Instance
|
||||
}
|
||||
|
||||
// NewContext create render context
|
||||
func NewContext(name string) Context {
|
||||
return &context{
|
||||
name: name,
|
||||
configs: map[string]interface{}{},
|
||||
assistants: []model.Instance{},
|
||||
}
|
||||
}
|
||||
|
||||
// SetBase set context base model
|
||||
func (ctx *context) SetBase(base model.Instance) {
|
||||
ctx.base = base
|
||||
}
|
||||
|
||||
// PutAssistants add Assist model to context
|
||||
func (ctx *context) PutAssistants(insts ...model.Instance) {
|
||||
ctx.assistants = append(ctx.assistants, insts...)
|
||||
}
|
||||
|
||||
// Compile return cue format string of context
|
||||
func (ctx *context) Compile(label string) string {
|
||||
var buff string
|
||||
buff += fmt.Sprintf("name: \"%s\"\n", ctx.name)
|
||||
|
||||
if ctx.base != nil {
|
||||
buff += fmt.Sprintf("input: %s\n", structMashal(ctx.base.String()))
|
||||
}
|
||||
|
||||
if len(ctx.configs) > 0 {
|
||||
bt, _ := json.Marshal(ctx.configs)
|
||||
buff += "configs: " + string(bt)
|
||||
}
|
||||
|
||||
if label != "" {
|
||||
buff = fmt.Sprintf("%s: %s", label, structMashal(buff))
|
||||
}
|
||||
|
||||
return buff
|
||||
}
|
||||
|
||||
// Output return models of context
|
||||
func (ctx *context) Output() (model.Instance, []model.Instance) {
|
||||
return ctx.base, ctx.assistants
|
||||
}
|
||||
|
||||
func structMashal(v string) string {
|
||||
skip := false
|
||||
v = strings.TrimFunc(v, func(r rune) bool {
|
||||
if !skip {
|
||||
if unicode.IsSpace(r) {
|
||||
return true
|
||||
}
|
||||
skip = true
|
||||
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if strings.HasPrefix(v, "{") {
|
||||
return v
|
||||
}
|
||||
return fmt.Sprintf("{%s}", v)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/bmizerany/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/dsl/model"
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
baseTemplate := `
|
||||
image: "myserver"
|
||||
`
|
||||
|
||||
var r cue.Runtime
|
||||
inst, err := r.Compile("-", baseTemplate)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
base, err := model.NewBase(inst.Value())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := NewContext("myctx")
|
||||
ctx.SetBase(base)
|
||||
ctxInst, err := r.Compile("-", ctx.Compile("context"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
gName, err := ctxInst.Lookup("context", "name").String()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "myctx", gName)
|
||||
inputJs, err := ctxInst.Lookup("context", "input").MarshalJSON()
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, `{"image":"myserver"}`, string(inputJs))
|
||||
}
|
||||
Reference in New Issue
Block a user