diff --git a/docs/en/end-user/definition-revision.md b/docs/en/end-user/definition-revision.md
new file mode 100644
index 000000000..be09f394f
--- /dev/null
+++ b/docs/en/end-user/definition-revision.md
@@ -0,0 +1,110 @@
+---
+title: Specify Definition Revision in Application
+---
+
+Each time the platform provider update ComponentDefinition/TraitDefinition, a corresponding DefinitionRevision will be generated.
+And the DefinitionRevision can be regarded as a snapshot of ComponentDefinition/TraitDefinition.
+
+In this section, we will introduce how to specify a revision in Application.
+
+## Usage of Definition
+
+Suppose we need a `worker` to run a background service. And the platform provider has already provided a `worker`
+ComponentDefinition for us(The ComponentDefinition `worker` may have been updated multiple times).
+
+
+Assume the platform provider registered the `v1` version of the `worker` like below:
+
+**Click to see how to register the v1 version of the worker**
+
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/definition-revision/worker-v1.yaml
+```
+
+
+
+We can use `kubectl vela show` to see the specification doc of the `worker`.
+
+```shell
+$ kubectl vela show worker
+# Properties
++-------+----------------------------------------------------+----------+----------+---------+
+| NAME | DESCRIPTION | TYPE | REQUIRED | DEFAULT |
++-------+----------------------------------------------------+----------+----------+---------+
+| cmd | Commands to run in the container | []string | false | |
+| image | Which image would you like to use for your service | string | true | |
++-------+----------------------------------------------------+----------+----------+---------+
+```
+
+Assume the platform provider has updated the version of the `worker` to `v2` like below.
+
+**Click to see how to update**
+
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/oam-dev/kubevela/master/docs/examples/definition-revision/worker-v2.yaml
+```
+
+
+
+The latest worker ComponentDefinition adds the `port` parameter, allowing users to specify the port exposed by the container.
+
+```shell
+$ kubectl vela show worker
+# Properties
++-------+----------------------------------------------------+----------+----------+---------+
+| NAME | DESCRIPTION | TYPE | REQUIRED | DEFAULT |
++-------+----------------------------------------------------+----------+----------+---------+
+| cmd | Commands to run in the container | []string | false | |
+| image | Which image would you like to use for your service | string | true | |
+| port | Which port do you want customer traffic sent to | int | true | |
++-------+----------------------------------------------------+----------+----------+---------+
+```
+
+When the platform provider updated the two versions of the worker, the corresponding DefinitionRevision will
+be generated to store the snapshot information.
+
+```shell
+$ kubectl get definitionrevision -l="componentdefinition.oam.dev/name=worker"
+NAME REVISION HASH TYPE
+worker-v1 1 76486234845427dc Component
+worker-v2 2 cb22fdc3b037702e Component
+```
+
+## Specify Definition Version in Application
+
+We can specify the Component to use a specific version of the Definition in Application,
+By default, the application will always use the latest Definition to render the Component.
+
+But the latest Definition may not be compatible with user's application, so we can specify the old version like `v1` of the worker to render the Component.
+we can specify the version of Definition in format `definitionName@version`.
+
+```yaml
+# testapp.yaml
+apiVersion: core.oam.dev/v1beta1
+kind: Application
+metadata:
+ name: testapp
+spec:
+ components:
+ - name: backend
+ type: worker@v1
+ properties:
+ image: crccheck/hello-world
+```
+
+```shell
+kubectl apply -f testapp.yaml
+```
+
+The `v1` version of the `worker` only allows the container to expose port `8000`.
+
+```shell
+$ kubectl get deployment
+NAME READY UP-TO-DATE AVAILABLE AGE
+backend 1/1 1 1 3m7s
+
+$ kubectl get deployment backend -o jsonpath="{.spec.template.spec.containers[0].ports[0].containerPort}"
+8000
+```
\ No newline at end of file
diff --git a/docs/examples/definition-revision/worker-v1.yaml b/docs/examples/definition-revision/worker-v1.yaml
new file mode 100644
index 000000000..113ddcde3
--- /dev/null
+++ b/docs/examples/definition-revision/worker-v1.yaml
@@ -0,0 +1,48 @@
+apiVersion: core.oam.dev/v1beta1
+kind: ComponentDefinition
+metadata:
+ name: worker
+ namespace: default
+ 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:
+ workload:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ schematic:
+ cue:
+ template: |
+ output: {
+ apiVersion: "apps/v1"
+ kind: "Deployment"
+ spec: {
+ selector: matchLabels: {
+ "app.oam.dev/component": context.name
+ }
+ template: {
+ metadata: labels: {
+ "app.oam.dev/component": context.name
+ }
+ spec: {
+ containers: [{
+ name: context.name
+ image: parameter.image
+
+ if parameter["cmd"] != _|_ {
+ command: parameter.cmd
+ }
+ ports: [{
+ containerPort: 8000
+ }]
+ }]
+ }
+ }
+ }
+ }
+ parameter: {
+ // +usage=Which image would you like to use for your service
+ image: string
+ // +usage=Commands to run in the container
+ cmd?: [...string]
+ }
\ No newline at end of file
diff --git a/docs/examples/definition-revision/worker-v2.yaml b/docs/examples/definition-revision/worker-v2.yaml
new file mode 100644
index 000000000..130502e74
--- /dev/null
+++ b/docs/examples/definition-revision/worker-v2.yaml
@@ -0,0 +1,50 @@
+apiVersion: core.oam.dev/v1beta1
+kind: ComponentDefinition
+metadata:
+ name: worker
+ namespace: default
+ 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:
+ workload:
+ definition:
+ apiVersion: apps/v1
+ kind: Deployment
+ schematic:
+ cue:
+ template: |
+ output: {
+ apiVersion: "apps/v1"
+ kind: "Deployment"
+ spec: {
+ selector: matchLabels: {
+ "app.oam.dev/component": context.name
+ }
+ template: {
+ metadata: labels: {
+ "app.oam.dev/component": context.name
+ }
+ spec: {
+ containers: [{
+ name: context.name
+ image: parameter.image
+
+ if parameter["cmd"] != _|_ {
+ command: parameter.cmd
+ }
+ ports: [{
+ containerPort: parameter.port
+ }]
+ }]
+ }
+ }
+ }
+ }
+ parameter: {
+ // +usage=Which image would you like to use for your service
+ image: string
+ // +usage=Commands to run in the container
+ cmd?: [...string]
+ // +usage=Which port do you want customer traffic sent to
+ port: int
+ }
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 4c5319175..8d73e2911 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -43,6 +43,7 @@ module.exports = {
'end-user/traits/more',
]
},
+ 'end-user/definition-revision',
'end-user/scopes/appdeploy',
'end-user/scopes/rollout-plan',
{
diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go
index 09fc971fe..9a22d7810 100644
--- a/pkg/appfile/parser.go
+++ b/pkg/appfile/parser.go
@@ -112,10 +112,15 @@ func (p *Parser) parseWorkload(ctx context.Context, comp v1beta1.ApplicationComp
if err != nil {
return nil, errors.WithMessagef(err, "fail to parse settings for %s", comp.Name)
}
+
+ wlType, err := util.ConvertDefinitionRevName(comp.Type)
+ if err != nil {
+ wlType = comp.Type
+ }
workload := &Workload{
Traits: []*Trait{},
Name: comp.Name,
- Type: comp.Type,
+ Type: wlType,
CapabilityCategory: templ.CapabilityCategory,
FullTemplate: templ,
Params: settings,
diff --git a/pkg/appfile/template.go b/pkg/appfile/template.go
index c448299e9..22e7a25f4 100644
--- a/pkg/appfile/template.go
+++ b/pkg/appfile/template.go
@@ -70,7 +70,7 @@ func LoadTemplate(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli c
switch capType {
case types.TypeComponentDefinition:
cd := new(v1beta1.ComponentDefinition)
- err := oamutil.GetDefinition(ctx, cli, cd, capName)
+ err := oamutil.GetCapabilityDefinition(ctx, cli, cd, capName)
if err != nil {
if kerrors.IsNotFound(err) {
wd := new(v1beta1.WorkloadDefinition)
@@ -101,7 +101,7 @@ func LoadTemplate(ctx context.Context, dm discoverymapper.DiscoveryMapper, cli c
case types.TypeTrait:
td := new(v1beta1.TraitDefinition)
- err := oamutil.GetDefinition(ctx, cli, td, capName)
+ err := oamutil.GetCapabilityDefinition(ctx, cli, td, capName)
if err != nil {
return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", capName)
}
diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go
index 8c84ab3af..a88567dc4 100644
--- a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go
+++ b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go
@@ -721,7 +721,7 @@ func (h *appHandler) handleRollout(ctx context.Context) (reconcile.Result, error
// targetRevision should always points to LatestRevison
targetRevision := h.app.Status.LatestRevision.Name
var srcRevision string
- target, _ := oamutil.ExtractRevisionNum(targetRevision)
+ target, _ := oamutil.ExtractRevisionNum(targetRevision, "-")
// if target == 1 this is a initial scale operation, sourceRevision should be empty
// otherwise source revision always is targetRevision - 1
if target > 1 {
diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/revision.go b/pkg/controller/core.oam.dev/v1alpha2/application/revision.go
index 03259068a..68b0dea0a 100644
--- a/pkg/controller/core.oam.dev/v1alpha2/application/revision.go
+++ b/pkg/controller/core.oam.dev/v1alpha2/application/revision.go
@@ -369,7 +369,7 @@ func (h historiesByRevision) Len() int { return len(h) }
func (h historiesByRevision) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h historiesByRevision) Less(i, j int) bool {
// the appRevision is generated by vela, the error always is nil, so ignore it
- ir, _ := util.ExtractRevisionNum(h[i].Name)
- ij, _ := util.ExtractRevisionNum(h[j].Name)
+ ir, _ := util.ExtractRevisionNum(h[i].Name, "-")
+ ij, _ := util.ExtractRevisionNum(h[j].Name, "-")
return ir < ij
}
diff --git a/pkg/dsl/process/handle.go b/pkg/dsl/process/handle.go
index cadae997a..0f23098f7 100644
--- a/pkg/dsl/process/handle.go
+++ b/pkg/dsl/process/handle.go
@@ -163,7 +163,7 @@ func (ctx *templateContext) BaseContextFile() string {
buff += fmt.Sprintf(ContextAppName+": \"%s\"\n", ctx.appName)
buff += fmt.Sprintf(ContextAppRevision+": \"%s\"\n", ctx.appRevision)
// the appRevision is generated by vela, the error always is nil, so ignore it
- revNum, _ := util.ExtractRevisionNum(ctx.appRevision)
+ revNum, _ := util.ExtractRevisionNum(ctx.appRevision, "-")
buff += fmt.Sprintf(ContextAppRevisionNum+": %d\n", revNum)
buff += fmt.Sprintf(ContextNamespace+": \"%s\"\n", ctx.namespace)
diff --git a/pkg/oam/util/helper.go b/pkg/oam/util/helper.go
index f2e49917b..a36b6a24d 100644
--- a/pkg/oam/util/helper.go
+++ b/pkg/oam/util/helper.go
@@ -145,6 +145,9 @@ type ConditionedObject interface {
oam.Conditioned
}
+// ErrBadRevisionName represents an error when the revision name is not standardized
+var ErrBadRevisionName = fmt.Errorf("bad revision name")
+
// LocateParentAppConfig locate the parent application configuration object
func LocateParentAppConfig(ctx context.Context, client client.Client, oamObject oam.Object) (oam.Object, error) {
@@ -333,6 +336,55 @@ func GetDefinition(ctx context.Context, cli client.Reader, definition runtime.Ob
return nil
}
+// GetCapabilityDefinition can get different versions of ComponentDefinition/TraitDefinition
+func GetCapabilityDefinition(ctx context.Context, cli client.Reader, definition runtime.Object,
+ definitionName string) error {
+ isLatestRevision, defRev, err := fetchDefinitionRev(ctx, cli, definitionName)
+ if err != nil {
+ return err
+ }
+ if isLatestRevision {
+ return GetDefinition(ctx, cli, definition, definitionName)
+ }
+ switch def := definition.(type) {
+ case *v1beta1.ComponentDefinition:
+ *def = defRev.Spec.ComponentDefinition
+ case *v1beta1.TraitDefinition:
+ *def = defRev.Spec.TraitDefinition
+ default:
+ }
+ return nil
+}
+
+func fetchDefinitionRev(ctx context.Context, cli client.Reader, definitionName string) (bool, *v1beta1.DefinitionRevision, error) {
+ defRevName, err := ConvertDefinitionRevName(definitionName)
+ if err != nil {
+ if errors.As(err, &ErrBadRevisionName) {
+ return true, nil, nil
+ }
+ return false, nil, err
+ }
+ defRev := new(v1beta1.DefinitionRevision)
+ if err = GetDefinition(ctx, cli, defRev, defRevName); err != nil {
+ return false, nil, err
+ }
+ return false, defRev, err
+}
+
+// ConvertDefinitionRevName can help convert definition type defined in Application to DefinitionRevision Name
+// e.g., worker@v2 will be convert to worker-v2
+func ConvertDefinitionRevName(definitionName string) (string, error) {
+ revNum, err := ExtractRevisionNum(definitionName, "@")
+ if err != nil {
+ return "", err
+ }
+ defName := strings.TrimSuffix(definitionName, fmt.Sprintf("@v%d", revNum))
+ if defName == "" {
+ return "", fmt.Errorf("invalid definition defName %s", definitionName)
+ }
+ return fmt.Sprintf("%s-v%d", defName, revNum), nil
+}
+
// when get a namespaced scope object without namespace, would get an error request namespace
func checkRequestNamespaceError(err error) bool {
return err != nil && err.Error() == "an empty namespace may not be set when a resource name is provided"
@@ -732,16 +784,16 @@ func ConvertComponentDef2WorkloadDef(dm discoverymapper.DiscoveryMapper, compone
return nil
}
-// ExtractRevisionNum extract revision number from appRevision name
-func ExtractRevisionNum(appRevision string) (int, error) {
- splits := strings.Split(appRevision, "-")
+// ExtractRevisionNum extract revision number
+func ExtractRevisionNum(appRevision string, delimiter string) (int, error) {
+ splits := strings.Split(appRevision, delimiter)
// check some bad appRevision name, eg:v1, appv2
if len(splits) == 1 {
- return 0, fmt.Errorf("bad revison name")
+ return 0, ErrBadRevisionName
}
// check some bad appRevision name, eg:myapp-a1
if !strings.HasPrefix(splits[len(splits)-1], "v") {
- return 0, fmt.Errorf("bad revison name")
+ return 0, ErrBadRevisionName
}
return strconv.Atoi(strings.TrimPrefix(splits[len(splits)-1], "v"))
}
diff --git a/pkg/oam/util/helper_test.go b/pkg/oam/util/helper_test.go
index 8b4d73e99..f9e1ab990 100644
--- a/pkg/oam/util/helper_test.go
+++ b/pkg/oam/util/helper_test.go
@@ -2057,47 +2057,110 @@ spec:
func TestExtractRevisionNum(t *testing.T) {
testcases := []struct {
- appRevision string
+ revName string
wantRevisionNum int
+ delimiter string
hasError bool
}{{
- appRevision: "myapp-v1",
+ revName: "myapp-v1",
wantRevisionNum: 1,
+ delimiter: "-",
hasError: false,
}, {
- appRevision: "new-app-v2",
+ revName: "new-app-v2",
wantRevisionNum: 2,
+ delimiter: "-",
hasError: false,
}, {
- appRevision: "v1-v10",
+ revName: "v1-v10",
wantRevisionNum: 10,
+ delimiter: "-",
hasError: false,
}, {
- appRevision: "v10-v1-v1",
+ revName: "v10-v1-v1",
wantRevisionNum: 1,
+ delimiter: "-",
hasError: false,
}, {
- appRevision: "myapp-v1-v2",
+ revName: "myapp-v1-v2",
wantRevisionNum: 2,
+ delimiter: "-",
hasError: false,
}, {
- appRevision: "myapp-v1-vv",
+ revName: "myapp-v1-vv",
wantRevisionNum: 0,
+ delimiter: "-",
hasError: true,
}, {
- appRevision: "v1",
+ revName: "v1",
wantRevisionNum: 0,
+ delimiter: "-",
hasError: true,
}, {
- appRevision: "myapp-a1",
+ revName: "myapp-a1",
wantRevisionNum: 0,
+ delimiter: "-",
+ hasError: true,
+ }, {
+ revName: "worker@v1",
+ wantRevisionNum: 1,
+ delimiter: "@",
+ hasError: false,
+ }, {
+ revName: "worke@10r@v1",
+ wantRevisionNum: 1,
+ delimiter: "@",
+ hasError: false,
+ }, {
+ revName: "webservice@a10",
+ wantRevisionNum: 0,
+ delimiter: "@",
hasError: true,
}}
for _, tt := range testcases {
- revision, err := util.ExtractRevisionNum(tt.appRevision)
+ revision, err := util.ExtractRevisionNum(tt.revName, tt.delimiter)
hasError := err != nil
assert.Equal(t, tt.wantRevisionNum, revision)
assert.Equal(t, tt.hasError, hasError)
}
}
+
+func TestExtractDefinitionRevName(t *testing.T) {
+ testcases := []struct {
+ defName string
+ wantRevName string
+ hasError bool
+ }{{
+ defName: "worker@v2",
+ wantRevName: "worker-v2",
+ hasError: false,
+ }, {
+ defName: "worker@v10",
+ wantRevName: "worker-v10",
+ hasError: false,
+ }, {
+ defName: "worker",
+ wantRevName: "",
+ hasError: true,
+ }, {
+ defName: "webservice@@v2",
+ wantRevName: "webservice@-v2",
+ hasError: false,
+ }, {
+ defName: "webservice@v10@v3",
+ wantRevName: "webservice@v10-v3",
+ hasError: false,
+ }, {
+ defName: "@v10",
+ wantRevName: "",
+ hasError: true,
+ }}
+
+ for _, tt := range testcases {
+ revName, err := util.ConvertDefinitionRevName(tt.defName)
+ hasError := err != nil
+ assert.Equal(t, tt.wantRevName, revName)
+ assert.Equal(t, tt.hasError, hasError)
+ }
+}
diff --git a/pkg/oam/util/test_utils.go b/pkg/oam/util/test_utils.go
index cd8e91bac..d19c1f7e2 100644
--- a/pkg/oam/util/test_utils.go
+++ b/pkg/oam/util/test_utils.go
@@ -197,7 +197,7 @@ func CheckAppRevision(revs []v1beta1.ApplicationRevision, collection []int) (boo
}
var revNums []int
for _, rev := range revs {
- num, err := ExtractRevisionNum(rev.Name)
+ num, err := ExtractRevisionNum(rev.Name, "-")
if err != nil {
return false, err
}
diff --git a/test/e2e-test/definition_revision_test.go b/test/e2e-test/definition_revision_test.go
new file mode 100644
index 000000000..3f4798b64
--- /dev/null
+++ b/test/e2e-test/definition_revision_test.go
@@ -0,0 +1,1040 @@
+/*
+ Copyright 2021. The KubeVela Authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package controllers_test
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/ghodss/yaml"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ appsv1 "k8s.io/api/apps/v1"
+ batchv1 "k8s.io/api/batch/v1"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/utils/pointer"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
+ "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
+ "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
+ "github.com/oam-dev/kubevela/pkg/oam"
+ "github.com/oam-dev/kubevela/pkg/oam/util"
+)
+
+var _ = Describe("Test application of the specified definition version", func() {
+ ctx := context.Background()
+
+ var namespace string
+ var ns corev1.Namespace
+
+ BeforeEach(func() {
+ namespace = randomNamespaceName("defrev-e2e-test")
+ ns = corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
+
+ Eventually(func() error {
+ return k8sClient.Create(ctx, &ns)
+ }, time.Second*3, time.Microsecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
+
+ labelV1 := labelWithNoTemplate.DeepCopy()
+ labelV1.Spec.Schematic.CUE.Template = labelV1Template
+ labelV1.SetNamespace(namespace)
+ Expect(k8sClient.Create(ctx, labelV1)).Should(Succeed())
+ Eventually(func() error {
+ err := k8sClient.Get(ctx, client.ObjectKey{Name: "label", Namespace: namespace}, labelV1)
+ if err != nil {
+ return err
+ }
+ labelV1.Spec.Schematic.CUE.Template = labelV2Template
+ return k8sClient.Update(ctx, labelV1)
+ }, 15*time.Second, time.Second).Should(BeNil())
+ labelDefRevList := new(v1beta1.DefinitionRevisionList)
+ labelDefRevListOpts := []client.ListOption{
+ client.InNamespace(namespace),
+ client.MatchingLabels{
+ oam.LabelTraitDefinitionName: "label",
+ },
+ }
+ Eventually(func() error {
+ err := k8sClient.List(ctx, labelDefRevList, labelDefRevListOpts...)
+ if err != nil {
+ return err
+ }
+ if len(labelDefRevList.Items) != 2 {
+ return fmt.Errorf("error defRevison number wants %d, actually %d", 2, len(labelDefRevList.Items))
+ }
+ return nil
+ }, 20*time.Second, time.Second).Should(BeNil())
+
+ })
+
+ AfterEach(func() {
+ By("Clean up resources after a test")
+ k8sClient.DeleteAllOf(ctx, &v1beta1.Application{}, client.InNamespace(namespace))
+ k8sClient.DeleteAllOf(ctx, &v1beta1.ComponentDefinition{}, client.InNamespace(namespace))
+ k8sClient.DeleteAllOf(ctx, &v1beta1.WorkloadDefinition{}, client.InNamespace(namespace))
+ k8sClient.DeleteAllOf(ctx, &v1beta1.TraitDefinition{}, client.InNamespace(namespace))
+ k8sClient.DeleteAllOf(ctx, &v1beta1.DefinitionRevision{}, client.InNamespace(namespace))
+
+ By(fmt.Sprintf("Delete the entire namespaceName %s", ns.Name))
+ Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
+ })
+
+ It("Test deploy application which containing cue rendering module", func() {
+ var (
+ appName = "test-website-app"
+ comp1Name = "front"
+ comp2Name = "backend"
+ )
+
+ workerV1 := workerWithNoTemplate.DeepCopy()
+ workerV1.Spec.Workload = common.WorkloadTypeDescriptor{
+ Definition: common.WorkloadGVK{
+ APIVersion: "batch/v1",
+ Kind: "Job",
+ },
+ }
+ workerV1.Spec.Schematic.CUE.Template = workerV1Template
+ workerV1.SetNamespace(namespace)
+ Expect(k8sClient.Create(ctx, workerV1)).Should(Succeed())
+ Eventually(func() error {
+ err := k8sClient.Get(ctx, client.ObjectKey{Name: "worker", Namespace: namespace}, workerV1)
+ if err != nil {
+ return err
+ }
+ workerV1.Spec.Workload = common.WorkloadTypeDescriptor{
+ Definition: common.WorkloadGVK{
+ APIVersion: "apps/v1",
+ Kind: "Deployment",
+ },
+ }
+ workerV1.Spec.Schematic.CUE.Template = workerV2Template
+ return k8sClient.Update(ctx, workerV1)
+ }, 15*time.Second, time.Second).Should(BeNil())
+ workerDefRevList := new(v1beta1.DefinitionRevisionList)
+ workerDefRevListOpts := []client.ListOption{
+ client.InNamespace(namespace),
+ client.MatchingLabels{
+ oam.LabelComponentDefinitionName: "worker",
+ },
+ }
+ Eventually(func() error {
+ err := k8sClient.List(ctx, workerDefRevList, workerDefRevListOpts...)
+ if err != nil {
+ return err
+ }
+ if len(workerDefRevList.Items) != 2 {
+ return fmt.Errorf("error defRevison number wants %d, actually %d", 2, len(workerDefRevList.Items))
+ }
+ return nil
+ }, 20*time.Second, time.Second).Should(BeNil())
+
+ webserviceV1 := webServiceWithNoTemplate.DeepCopy()
+ webserviceV1.Spec.Schematic.CUE.Template = webServiceV1Template
+ webserviceV1.SetNamespace(namespace)
+ Expect(k8sClient.Create(ctx, webserviceV1)).Should(Succeed())
+ Eventually(func() error {
+ err := k8sClient.Get(ctx, client.ObjectKey{Name: "webservice", Namespace: namespace}, webserviceV1)
+ if err != nil {
+ return err
+ }
+ webserviceV1.Spec.Schematic.CUE.Template = webServiceV2Template
+ return k8sClient.Update(ctx, webserviceV1)
+ }, 15*time.Second, time.Second).Should(BeNil())
+
+ webserviceDefRevList := new(v1beta1.DefinitionRevisionList)
+ webserviceDefRevListOpts := []client.ListOption{
+ client.InNamespace(namespace),
+ client.MatchingLabels{
+ oam.LabelComponentDefinitionName: "webservice",
+ },
+ }
+ Eventually(func() error {
+ err := k8sClient.List(ctx, webserviceDefRevList, webserviceDefRevListOpts...)
+ if err != nil {
+ return err
+ }
+ if len(webserviceDefRevList.Items) != 2 {
+ return fmt.Errorf("error defRevison number wants %d, actually %d", 2, len(webserviceDefRevList.Items))
+ }
+ return nil
+ }, 20*time.Second, time.Second).Should(BeNil())
+
+ app := v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: comp1Name,
+ Type: "webservice",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "nginx",
+ }),
+ Traits: []v1beta1.ApplicationTrait{
+ {
+ Type: "label",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "labels": map[string]string{
+ "hello": "world",
+ },
+ }),
+ },
+ },
+ },
+ {
+ Name: comp2Name,
+ Type: "worker",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "busybox",
+ "cmd": []string{"sleep", "1000"},
+ }),
+ },
+ },
+ },
+ }
+
+ By("Create application")
+ Expect(k8sClient.Create(ctx, &app)).Should(Succeed())
+
+ ac := &v1alpha2.ApplicationContext{}
+ acName := appName
+ By("Verify the ApplicationContext is created & reconciled successfully")
+ Eventually(func() bool {
+ if err := k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac); err != nil {
+ return false
+ }
+ return len(ac.Status.Workloads) > 0
+ }, 60*time.Second, time.Second).Should(BeTrue())
+
+ By("Verify the workload(deployment) is created successfully")
+ Expect(len(ac.Status.Workloads)).Should(Equal(len(app.Spec.Components)))
+ webServiceDeploy := &appsv1.Deployment{}
+ deployName := ac.Status.Workloads[0].Reference.Name
+ Eventually(func() error {
+ return k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, webServiceDeploy)
+ }, 30*time.Second, 3*time.Second).Should(Succeed())
+
+ workerDeploy := &appsv1.Deployment{}
+ deployName = ac.Status.Workloads[1].Reference.Name
+ Eventually(func() error {
+ return k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, workerDeploy)
+ }, 30*time.Second, 3*time.Second).Should(Succeed())
+
+ By("Verify trait is applied to the workload")
+ webserviceLabels := webServiceDeploy.GetLabels()
+ Expect(webserviceLabels["hello"]).Should(Equal("world"))
+
+ By("Update Application and Specify the Definition version in Application")
+ app = v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: comp1Name,
+ Type: "webservice@v1",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "nginx",
+ }),
+ Traits: []v1beta1.ApplicationTrait{
+ {
+ Type: "label@v1",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "labels": map[string]string{
+ "hello": "kubevela",
+ },
+ }),
+ },
+ },
+ },
+ {
+ Name: comp2Name,
+ Type: "worker@v1",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "busybox",
+ "cmd": []string{"sleep", "1000"},
+ }),
+ },
+ },
+ },
+ }
+ Expect(k8sClient.Patch(ctx, &app, client.Merge)).Should(Succeed())
+
+ By("Verify the ApplicationContext is update successfully")
+ Eventually(func() bool {
+ if err := k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac); err != nil {
+ return false
+ }
+ return ac.Generation == 2
+ }, 10*time.Second, time.Second).Should(BeTrue())
+
+ By("Verify the workload(deployment) is created successfully")
+ Expect(len(ac.Status.Workloads)).Should(Equal(len(app.Spec.Components)))
+ webServiceV1Deploy := &appsv1.Deployment{}
+ deployName = ac.Status.Workloads[0].Reference.Name
+ Eventually(func() error {
+ return k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, webServiceV1Deploy)
+ }, 30*time.Second, 3*time.Second).Should(Succeed())
+
+ By("Verify the workload(job) is created successfully")
+ workerJob := &batchv1.Job{}
+ jobName := ac.Status.Workloads[1].Reference.Name
+ Eventually(func() error {
+ return k8sClient.Get(ctx, client.ObjectKey{Name: jobName, Namespace: namespace}, workerJob)
+ }, 30*time.Second, 3*time.Second).Should(Succeed())
+
+ By("Verify trait is applied to the workload")
+ webserviceV1Labels := webServiceV1Deploy.GetLabels()
+ Expect(webserviceV1Labels["hello"]).Should(Equal("kubevela"))
+
+ By("Check Application is rendered by the specified version of the Definition")
+ Expect(webServiceV1Deploy.Labels["componentdefinition.oam.dev/version"]).Should(Equal("v1"))
+ Expect(webServiceV1Deploy.Labels["traitdefinition.oam.dev/version"]).Should(Equal("v1"))
+
+ By("Application specifies the wrong version of the Definition, it will raise an error")
+ app = v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: comp1Name,
+ Type: "webservice@v10",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "nginx",
+ }),
+ },
+ },
+ },
+ }
+ Expect(k8sClient.Patch(ctx, &app, client.Merge)).Should(Succeed())
+
+ apprev := &v1beta1.ApplicationRevision{}
+ Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: fmt.Sprintf("%s-v3", appName)}, apprev)).Should(HaveOccurred())
+ })
+
+ It("Test deploy application which containing helm module", func() {
+ var (
+ appName = "test-helm"
+ compName = "worker"
+ )
+
+ helmworkerV1 := HELMWorker.DeepCopy()
+ helmworkerV1.SetNamespace(namespace)
+ helmworkerV1.Spec.Workload.Definition = common.WorkloadGVK{
+ APIVersion: "batch/v1beta1",
+ Kind: "CronJob",
+ }
+ helmworkerV1.Spec.Schematic = &common.Schematic{
+ HELM: &common.Helm{
+ Release: util.Object2RawExtension(map[string]interface{}{
+ "chart": map[string]interface{}{
+ "spec": map[string]interface{}{
+ "chart": "podinfo",
+ "version": "5.1.4",
+ },
+ },
+ }),
+ Repository: util.Object2RawExtension(map[string]interface{}{
+ "url": "https://stefanprodan.github.io/podinfo",
+ }),
+ },
+ }
+ Expect(k8sClient.Create(ctx, helmworkerV1)).Should(Succeed())
+ Eventually(func() error {
+ err := k8sClient.Get(ctx, client.ObjectKey{Name: "helm-worker", Namespace: namespace}, helmworkerV1)
+ if err != nil {
+ return err
+ }
+ helmworkerV1.Spec.Workload.Definition = common.WorkloadGVK{
+ APIVersion: "apps/v1",
+ Kind: "Deployment",
+ }
+ helmworkerV1.Spec.Schematic = &common.Schematic{
+ HELM: &common.Helm{
+ Release: util.Object2RawExtension(map[string]interface{}{
+ "chart": map[string]interface{}{
+ "spec": map[string]interface{}{
+ "chart": "podinfo",
+ "version": "5.2.0",
+ },
+ },
+ }),
+ Repository: util.Object2RawExtension(map[string]interface{}{
+ "url": "https://stefanprodan.github.io/podinfo",
+ }),
+ },
+ }
+ return k8sClient.Update(ctx, helmworkerV1)
+ }, 15*time.Second, time.Second).Should(BeNil())
+
+ helmworkerDefRevList := new(v1beta1.DefinitionRevisionList)
+ helmworkerDefRevListOpts := []client.ListOption{
+ client.InNamespace(namespace),
+ client.MatchingLabels{
+ oam.LabelComponentDefinitionName: "helm-worker",
+ },
+ }
+ Eventually(func() error {
+ err := k8sClient.List(ctx, helmworkerDefRevList, helmworkerDefRevListOpts...)
+ if err != nil {
+ return err
+ }
+ if len(helmworkerDefRevList.Items) != 2 {
+ return fmt.Errorf("error defRevison number wants %d, actually %d", 2, len(helmworkerDefRevList.Items))
+ }
+ return nil
+ }, 20*time.Second, time.Second).Should(BeNil())
+
+ app := v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: compName,
+ Type: "helm-worker",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": map[string]interface{}{
+ "tag": "5.1.2",
+ },
+ }),
+ Traits: []v1beta1.ApplicationTrait{
+ {
+ Type: "label",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "labels": map[string]string{
+ "hello": "world",
+ },
+ }),
+ },
+ },
+ },
+ },
+ },
+ }
+
+ By("Create application")
+ Expect(k8sClient.Create(ctx, &app)).Should(Succeed())
+
+ ac := &v1alpha2.ApplicationContext{}
+ acName := appName
+ By("Verify the ApplicationContext is created successfully")
+ Eventually(func() error {
+ return k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac)
+ }, 30*time.Second, time.Second).Should(Succeed())
+
+ By("Verify the workload(deployment) is created successfully by Helm")
+ deploy := &appsv1.Deployment{}
+ deployName := fmt.Sprintf("%s-%s-podinfo", appName, compName)
+ Eventually(func() bool {
+ err := k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy)
+ if err != nil {
+ return false
+ }
+ DeployLabels := deploy.GetLabels()
+ return DeployLabels["helm.sh/chart"] == "podinfo-5.2.0"
+ }, 120*time.Second, 5*time.Second).Should(BeTrue())
+
+ By("Verify trait is applied to the workload")
+ Eventually(func() bool {
+ requestReconcileNow(ctx, ac)
+ deploy := &appsv1.Deployment{}
+ if err := k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy); err != nil {
+ return false
+ }
+ By("Verify patch trait is applied")
+ templateLabels := deploy.GetLabels()
+ return templateLabels["hello"] != "world"
+ }, 120*time.Second, 10*time.Second).Should(BeTrue())
+
+ app = v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: compName,
+ Type: "helm-worker@v1",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": map[string]interface{}{
+ "tag": "5.1.2",
+ },
+ }),
+ },
+ },
+ },
+ }
+
+ By("Create application")
+ Expect(k8sClient.Patch(ctx, &app, client.Merge)).Should(Succeed())
+
+ By("Verify the ApplicationContext is updated")
+ Eventually(func() bool {
+ ac = &v1alpha2.ApplicationContext{}
+ if err := k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac); err != nil {
+ return false
+ }
+ return ac.GetGeneration() == 2
+ }, 15*time.Second, 3*time.Second).Should(BeTrue())
+
+ By("Verify the workload(deployment) is update successfully by Helm")
+ deploy = &appsv1.Deployment{}
+ Eventually(func() bool {
+ err := k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy)
+ if err != nil {
+ return false
+ }
+ DeployLabels := deploy.GetLabels()
+ return DeployLabels["helm.sh/chart"] != "podinfo-5.1.4"
+ }, 120*time.Second, 5*time.Second).Should(BeTrue())
+ })
+
+ It("Test deploy application which containing kube module", func() {
+ var (
+ appName = "test-kube-app"
+ compName = "worker"
+ )
+
+ kubeworkerV1 := KUBEWorker.DeepCopy()
+ kubeworkerV1.Spec.Workload.Definition = common.WorkloadGVK{
+ APIVersion: "apps/v1",
+ Kind: "Deployment",
+ }
+ kubeworkerV1.Spec.Schematic = &common.Schematic{
+ KUBE: &common.Kube{
+ Template: generateTemplate(KUBEWorkerV1Template),
+ Parameters: []common.KubeParameter{
+ {
+ Name: "image",
+ ValueType: common.StringType,
+ FieldPaths: []string{"spec.template.spec.containers[0].image"},
+ Required: pointer.BoolPtr(true),
+ Description: pointer.StringPtr("test description"),
+ },
+ },
+ },
+ }
+ kubeworkerV1.SetNamespace(namespace)
+ Expect(k8sClient.Create(ctx, kubeworkerV1)).Should(Succeed())
+ Eventually(func() error {
+ err := k8sClient.Get(ctx, client.ObjectKey{Name: "kube-worker", Namespace: namespace}, kubeworkerV1)
+ if err != nil {
+ return err
+ }
+ kubeworkerV1.Spec.Workload.Definition = common.WorkloadGVK{
+ APIVersion: "batch/v1",
+ Kind: "Job",
+ }
+ kubeworkerV1.Spec.Schematic = &common.Schematic{
+ KUBE: &common.Kube{
+ Template: generateTemplate(KUBEWorkerV2Template),
+ Parameters: []common.KubeParameter{
+ {
+ Name: "image",
+ ValueType: common.StringType,
+ FieldPaths: []string{"spec.template.spec.containers[0].image"},
+ Required: pointer.BoolPtr(true),
+ Description: pointer.StringPtr("test description"),
+ },
+ },
+ },
+ }
+ return k8sClient.Update(ctx, kubeworkerV1)
+ }, 15*time.Second, time.Second).Should(BeNil())
+
+ kubeworkerDefRevList := new(v1beta1.DefinitionRevisionList)
+ kubeworkerDefRevListOpts := []client.ListOption{
+ client.InNamespace(namespace),
+ client.MatchingLabels{
+ oam.LabelComponentDefinitionName: "kube-worker",
+ },
+ }
+ Eventually(func() error {
+ err := k8sClient.List(ctx, kubeworkerDefRevList, kubeworkerDefRevListOpts...)
+ if err != nil {
+ return err
+ }
+ if len(kubeworkerDefRevList.Items) != 2 {
+ return fmt.Errorf("error defRevison number wants %d, actually %d", 2, len(kubeworkerDefRevList.Items))
+ }
+ return nil
+ }, 20*time.Second, time.Second).Should(BeNil())
+
+ app := v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: compName,
+ Type: "kube-worker",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "busybox",
+ }),
+ Traits: []v1beta1.ApplicationTrait{
+ {
+ Type: "label",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "labels": map[string]string{
+ "hello": "world",
+ },
+ }),
+ },
+ },
+ },
+ },
+ },
+ }
+
+ By("Create application")
+ Expect(k8sClient.Create(ctx, &app)).Should(Succeed())
+
+ ac := &v1alpha2.ApplicationContext{}
+ acName := appName
+ By("Verify the ApplicationContext is created & reconciled successfully")
+ Eventually(func() bool {
+ if err := k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac); err != nil {
+ return false
+ }
+ return len(ac.Status.Workloads) > 0
+ }, 60*time.Second, time.Second).Should(BeTrue())
+
+ By("Verify the workload(job) is created successfully")
+ job := &batchv1.Job{}
+ jobName := ac.Status.Workloads[0].Reference.Name
+ Eventually(func() error {
+ return k8sClient.Get(ctx, client.ObjectKey{Name: jobName, Namespace: namespace}, job)
+ }, 30*time.Second, 3*time.Second).Should(Succeed())
+
+ By("Verify trait is applied to the workload")
+ Labels := job.GetLabels()
+ Expect(Labels["hello"]).Should(Equal("world"))
+
+ By("Update Application and Specify the Definition version in Application")
+ app = v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: compName,
+ Type: "kube-worker@v1",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "nginx",
+ }),
+ Traits: []v1beta1.ApplicationTrait{
+ {
+ Type: "label",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "labels": map[string]string{
+ "hello": "kubevela",
+ },
+ }),
+ },
+ },
+ },
+ },
+ },
+ }
+ Expect(k8sClient.Patch(ctx, &app, client.Merge)).Should(Succeed())
+
+ By("Verify the ApplicationContext is update successfully")
+ Eventually(func() bool {
+ if err := k8sClient.Get(ctx, client.ObjectKey{Name: acName, Namespace: namespace}, ac); err != nil {
+ return false
+ }
+ return ac.Generation == 2
+ }, 10*time.Second, time.Second).Should(BeTrue())
+
+ By("Verify the workload(deployment) is created successfully")
+ Expect(len(ac.Status.Workloads)).Should(Equal(len(app.Spec.Components)))
+ deploy := &appsv1.Deployment{}
+ deployName := ac.Status.Workloads[0].Reference.Name
+ Eventually(func() error {
+ return k8sClient.Get(ctx, client.ObjectKey{Name: deployName, Namespace: namespace}, deploy)
+ }, 30*time.Second, 3*time.Second).Should(Succeed())
+
+ By("Verify trait is applied to the workload")
+ webserviceV1Labels := deploy.GetLabels()
+ Expect(webserviceV1Labels["hello"]).Should(Equal("kubevela"))
+
+ By("Application specifies the wrong version of the Definition, it will raise an error")
+ app = v1beta1.Application{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: appName,
+ Namespace: namespace,
+ },
+ Spec: v1beta1.ApplicationSpec{
+ Components: []v1beta1.ApplicationComponent{
+ {
+ Name: compName,
+ Type: "kube-worker@a1",
+ Properties: util.Object2RawExtension(map[string]interface{}{
+ "image": "nginx",
+ }),
+ },
+ },
+ },
+ }
+ Expect(k8sClient.Patch(ctx, &app, client.Merge)).Should(Succeed())
+
+ apprev := &v1beta1.ApplicationRevision{}
+ Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: fmt.Sprintf("%s-v3", appName)}, apprev)).Should(HaveOccurred())
+ })
+
+})
+
+var webServiceWithNoTemplate = &v1beta1.ComponentDefinition{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ComponentDefinition",
+ APIVersion: "core.oam.dev/v1beta1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "webservice",
+ },
+ Spec: v1beta1.ComponentDefinitionSpec{
+ Workload: common.WorkloadTypeDescriptor{
+ Definition: common.WorkloadGVK{
+ APIVersion: "apps/v1",
+ Kind: "Deployment",
+ },
+ },
+ Schematic: &common.Schematic{
+ CUE: &common.CUE{
+ Template: "",
+ },
+ },
+ },
+}
+
+var workerWithNoTemplate = &v1beta1.ComponentDefinition{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ComponentDefinition",
+ APIVersion: "core.oam.dev/v1beta1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "worker",
+ },
+ Spec: v1beta1.ComponentDefinitionSpec{
+ Schematic: &common.Schematic{
+ CUE: &common.CUE{
+ Template: "",
+ },
+ },
+ },
+}
+
+var KUBEWorker = &v1beta1.ComponentDefinition{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ComponentDefinition",
+ APIVersion: "core.oam.dev/v1beta1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "kube-worker",
+ },
+}
+
+var HELMWorker = &v1beta1.ComponentDefinition{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ComponentDefinition",
+ APIVersion: "core.oam.dev/v1beta1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "helm-worker",
+ },
+}
+
+var labelWithNoTemplate = &v1beta1.TraitDefinition{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "TraitDefinition",
+ APIVersion: "core.oam.dev/v1beta1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "label",
+ },
+ Spec: v1beta1.TraitDefinitionSpec{
+ Schematic: &common.Schematic{
+ CUE: &common.CUE{
+ Template: "",
+ },
+ },
+ },
+}
+
+func generateTemplate(template string) runtime.RawExtension {
+ b, _ := yaml.YAMLToJSON([]byte(template))
+ return runtime.RawExtension{Raw: b}
+}
+
+var webServiceV1Template = `output: {
+ apiVersion: "apps/v1"
+ kind: "Deployment"
+ metadata: labels: {
+ "componentdefinition.oam.dev/version": "v1"
+ }
+ 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: {
+ image: string
+ cmd?: [...string]
+ port: *80 | int
+ env?: [...{
+ name: string
+ value?: string
+ valueFrom?: {
+ secretKeyRef: {
+ name: string
+ key: string
+ }
+ }
+ }]
+ cpu?: string
+}
+`
+
+var webServiceV2Template = `output: {
+ apiVersion: "apps/v1"
+ kind: "Deployment"
+ spec: {
+ selector: matchLabels: {
+ "app.oam.dev/component": context.name
+ if parameter.addRevisionLabel {
+ "app.oam.dev/appRevision": context.appRevision
+ }
+ }
+ template: {
+ metadata: labels: {
+ "app.oam.dev/component": context.name
+ if parameter.addRevisionLabel {
+ "app.oam.dev/appRevision": context.appRevision
+ }
+ }
+ 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: {
+ image: string
+ cmd?: [...string]
+ port: *80 | int
+ env?: [...{
+ name: string
+ value?: string
+ valueFrom?: {
+ secretKeyRef: {
+ name: string
+ key: string
+ }
+ }
+ }]
+ cpu?: string
+ addRevisionLabel: *false | bool
+}
+`
+
+var workerV1Template = `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: {
+ count: *1 | int
+ image: string
+ restart: *"Never" | string
+ cmd?: [...string]
+}
+`
+
+var workerV2Template = `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]
+}
+`
+
+var labelV1Template = `patch: {
+ metadata: labels: {
+ for k, v in parameter.labels {
+ "\(k)": v
+ }
+ "traitdefinition.oam.dev/version": "v1"
+ }
+}
+parameter: {
+ labels: [string]: string
+}
+`
+
+var labelV2Template = `patch: {
+ metadata: labels: {
+ for k, v in parameter.labels {
+ "\(k)": v
+ }
+ }
+}
+parameter: {
+ labels: [string]: string
+}
+`
+
+var KUBEWorkerV1Template = `apiVersion: apps/v1
+kind: Deployment
+spec:
+ selector:
+ matchLabels:
+ app: nginx
+ template:
+ metadata:
+ labels:
+ app: nginx
+ spec:
+ containers:
+ - name: nginx
+ ports:
+ - containerPort: 80
+`
+
+var KUBEWorkerV2Template = `apiVersion: "batch/v1"
+kind: "Job"
+spec:
+ parallelism: 1
+ completions: 1
+ template:
+ spec:
+ restartPolicy: "Never"
+ containers:
+ - name: "job"
+ image: "busybox"
+ command:
+ - "sleep"
+ - "1000"
+`