mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 12:36:34 +00:00
* Feat: add support for json-patch and json-merge-patch Signed-off-by: Somefive <yd219913@alibaba-inc.com> * Fix: add e2e test Signed-off-by: Somefive <yd219913@alibaba-inc.com> * Fix: refactor json-patch field Signed-off-by: Somefive <yd219913@alibaba-inc.com>
1449 lines
42 KiB
Go
1449 lines
42 KiB
Go
/*
|
|
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 definition
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"github.com/oam-dev/kubevela/apis/types"
|
|
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
|
"github.com/oam-dev/kubevela/pkg/cue/process"
|
|
)
|
|
|
|
func TestWorkloadTemplateComplete(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
workloadTemplate string
|
|
params map[string]interface{}
|
|
expectObj runtime.Object
|
|
expAssObjs map[string]runtime.Object
|
|
category types.CapabilityCategory
|
|
hasCompileErr bool
|
|
}{
|
|
"only contain an output": {
|
|
workloadTemplate: `
|
|
output:{
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
metadata: name: context.name
|
|
spec: replicas: parameter.replicas
|
|
}
|
|
parameter: {
|
|
replicas: *1 | int
|
|
type: string
|
|
host: string
|
|
}
|
|
`,
|
|
params: map[string]interface{}{
|
|
"replicas": 2,
|
|
"type": "ClusterIP",
|
|
"host": "example.com",
|
|
},
|
|
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)},
|
|
}},
|
|
hasCompileErr: false,
|
|
},
|
|
"contain output and outputs": {
|
|
workloadTemplate: `
|
|
output:{
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
metadata: name: context.name
|
|
spec: replicas: parameter.replicas
|
|
}
|
|
outputs: service: {
|
|
apiVersion: "v1"
|
|
kind: "Service"
|
|
metadata: name: context.name
|
|
spec: type: parameter.type
|
|
}
|
|
outputs: ingress: {
|
|
apiVersion: "extensions/v1beta1"
|
|
kind: "Ingress"
|
|
metadata: name: context.name
|
|
spec: rules: [{host: parameter.host}]
|
|
}
|
|
|
|
parameter: {
|
|
replicas: *1 | int
|
|
type: string
|
|
host: string
|
|
}
|
|
`,
|
|
params: map[string]interface{}{
|
|
"replicas": 2,
|
|
"type": "ClusterIP",
|
|
"host": "example.com",
|
|
},
|
|
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),
|
|
},
|
|
},
|
|
},
|
|
expAssObjs: map[string]runtime.Object{
|
|
"service": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1", "kind": "Service", "metadata": map[string]interface{}{"name": "test"},
|
|
"spec": map[string]interface{}{"type": "ClusterIP"},
|
|
},
|
|
},
|
|
"ingress": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": map[string]interface{}{
|
|
"name": "test",
|
|
}, "spec": map[string]interface{}{
|
|
"rules": []interface{}{
|
|
map[string]interface{}{
|
|
"host": "example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
hasCompileErr: false,
|
|
},
|
|
"output needs context appRevision": {
|
|
workloadTemplate: `
|
|
output:{
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
metadata: {
|
|
name: context.name
|
|
annotations: "revision.oam.dev": context.appRevision
|
|
}
|
|
spec: replicas: parameter.replicas
|
|
}
|
|
parameter: {
|
|
replicas: *1 | int
|
|
type: string
|
|
host: string
|
|
}
|
|
`,
|
|
params: map[string]interface{}{
|
|
"replicas": 2,
|
|
"type": "ClusterIP",
|
|
"host": "example.com",
|
|
},
|
|
expectObj: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{
|
|
"name": "test", "annotations": map[string]interface{}{
|
|
"revision.oam.dev": "myapp-v1",
|
|
},
|
|
}, "spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
},
|
|
},
|
|
},
|
|
hasCompileErr: false,
|
|
},
|
|
"output needs context replicas": {
|
|
workloadTemplate: `
|
|
output:{
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
metadata: {
|
|
name: context.name
|
|
}
|
|
spec: replicas: parameter.replicas
|
|
}
|
|
parameter: {
|
|
replicas: *1 | int
|
|
}
|
|
`,
|
|
params: nil,
|
|
expectObj: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1", "kind": "Deployment", "metadata": map[string]interface{}{"name": "test"},
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(1),
|
|
},
|
|
},
|
|
},
|
|
hasCompileErr: false,
|
|
},
|
|
"parameter type doesn't match will raise error": {
|
|
workloadTemplate: `
|
|
output:{
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
metadata: name: context.name
|
|
spec: replicas: parameter.replicas
|
|
}
|
|
parameter: {
|
|
replicas: *1 | int
|
|
type: string
|
|
host: string
|
|
}
|
|
`,
|
|
params: map[string]interface{}{
|
|
"replicas": "2",
|
|
"type": "ClusterIP",
|
|
"host": "example.com",
|
|
},
|
|
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)},
|
|
}},
|
|
hasCompileErr: true,
|
|
},
|
|
}
|
|
|
|
for _, v := range testCases {
|
|
ctx := process.NewContext(process.ContextData{
|
|
AppName: "myapp",
|
|
CompName: "test",
|
|
Namespace: "default",
|
|
AppRevisionName: "myapp-v1",
|
|
})
|
|
wt := NewWorkloadAbstractEngine("testWorkload", &packages.PackageDiscover{})
|
|
err := wt.Complete(ctx, v.workloadTemplate, v.params)
|
|
hasError := err != nil
|
|
assert.Equal(t, v.hasCompileErr, hasError)
|
|
if v.hasCompileErr {
|
|
continue
|
|
}
|
|
base, assists := ctx.Output()
|
|
assert.Equal(t, len(v.expAssObjs), len(assists))
|
|
assert.NotNil(t, base)
|
|
baseObj, err := base.Unstructured()
|
|
assert.Equal(t, nil, err)
|
|
assert.Equal(t, v.expectObj, baseObj)
|
|
for _, ss := range assists {
|
|
assert.Equal(t, AuxiliaryWorkload, ss.Type)
|
|
got, err := ss.Ins.Unstructured()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, got, v.expAssObjs[ss.Name])
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestTraitTemplateComplete(t *testing.T) {
|
|
|
|
tds := map[string]struct {
|
|
traitName string
|
|
traitTemplate string
|
|
params map[string]interface{}
|
|
expWorkload *unstructured.Unstructured
|
|
expAssObjs map[string]runtime.Object
|
|
hasCompileErr bool
|
|
}{
|
|
"patch trait": {
|
|
traitTemplate: `
|
|
patch: {
|
|
// +patchKey=name
|
|
spec: template: spec: containers: [parameter]
|
|
}
|
|
|
|
parameter: {
|
|
name: string
|
|
image: string
|
|
command?: [...string]
|
|
}`,
|
|
params: map[string]interface{}{
|
|
"name": "sidecar",
|
|
"image": "metrics-agent:0.2",
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}},
|
|
map[string]interface{}{"image": "metrics-agent:0.2", "name": "sidecar"}}}}}},
|
|
},
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
},
|
|
},
|
|
|
|
"patch trait with strategic merge": {
|
|
traitTemplate: `
|
|
patch: {
|
|
// +patchKey=name
|
|
spec: template: spec: {
|
|
// +patchStrategy=retainKeys
|
|
containers: [{
|
|
name: "main"
|
|
image: parameter.image
|
|
ports: [{containerPort: parameter.port}]
|
|
envFrom: [{
|
|
configMapRef: name: context.name + "game-config"
|
|
}]
|
|
if parameter["command"] != _|_ {
|
|
command: parameter.command
|
|
}
|
|
}]
|
|
}
|
|
}
|
|
|
|
parameter: {
|
|
image: string
|
|
port: int
|
|
command?: [...string]
|
|
}
|
|
`,
|
|
params: map[string]interface{}{
|
|
"image": "website:0.2",
|
|
"port": 8080,
|
|
"command": []string{"server", "start"},
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.2",
|
|
"name": "main",
|
|
"command": []interface{}{"server", "start"},
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(8080)}}},
|
|
}}}}},
|
|
},
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
},
|
|
},
|
|
"patch trait with json merge patch": {
|
|
traitTemplate: `
|
|
parameter: {...}
|
|
// +patchStrategy=jsonMergePatch
|
|
patch: parameter
|
|
`,
|
|
params: map[string]interface{}{
|
|
"spec": map[string]interface{}{
|
|
"replicas": 5,
|
|
"template": map[string]interface{}{
|
|
"spec": nil,
|
|
},
|
|
},
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(5),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
}}}},
|
|
},
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
},
|
|
},
|
|
"patch trait with json patch": {
|
|
traitTemplate: `
|
|
parameter: {operations: [...{...}]}
|
|
// +patchStrategy=jsonPatch
|
|
patch: parameter
|
|
`,
|
|
params: map[string]interface{}{
|
|
"operations": []map[string]interface{}{
|
|
{"op": "replace", "path": "/spec/replicas", "value": 5},
|
|
{"op": "remove", "path": "/spec/template/spec"},
|
|
},
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(5),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
}}}},
|
|
},
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
},
|
|
},
|
|
"patch trait with invalid json patch": {
|
|
traitTemplate: `
|
|
parameter: {patch: [...{...}]}
|
|
// +patchStrategy=jsonPatch
|
|
patch: parameter
|
|
`,
|
|
params: map[string]interface{}{
|
|
"patch": []map[string]interface{}{
|
|
{"op": "what", "path": "/spec/replicas", "value": 5},
|
|
},
|
|
},
|
|
hasCompileErr: true,
|
|
},
|
|
"patch trait with replace": {
|
|
traitTemplate: `
|
|
parameter: {
|
|
name: string
|
|
ports: [...int]
|
|
}
|
|
patch: spec: template: spec: {
|
|
// +patchKey=name
|
|
containers: [{
|
|
name: parameter.name
|
|
// +patchStrategy=replace
|
|
ports: [for k in parameter.ports {containerPort: k}]
|
|
}]
|
|
}
|
|
`,
|
|
params: map[string]interface{}{
|
|
"name": "main",
|
|
"ports": []int{80, 8443},
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{
|
|
map[string]interface{}{"containerPort": int64(80)},
|
|
map[string]interface{}{"containerPort": int64(8443)},
|
|
}},
|
|
}}}}},
|
|
},
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
},
|
|
},
|
|
"output trait": {
|
|
traitTemplate: `
|
|
outputs: service: {
|
|
apiVersion: "v1"
|
|
kind: "Service"
|
|
metadata: name: context.name
|
|
spec: type: parameter.type
|
|
}
|
|
parameter: {
|
|
type: string
|
|
}`,
|
|
params: map[string]interface{}{
|
|
"type": "ClusterIP",
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}}}}}}},
|
|
},
|
|
traitName: "t1",
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
"t1service": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"type": "ClusterIP"}}},
|
|
},
|
|
},
|
|
"outputs trait": {
|
|
traitTemplate: `
|
|
outputs: service: {
|
|
apiVersion: "v1"
|
|
kind: "Service"
|
|
metadata: name: context.name
|
|
spec: type: parameter.type
|
|
}
|
|
outputs: ingress: {
|
|
apiVersion: "extensions/v1beta1"
|
|
kind: "Ingress"
|
|
metadata: name: context.name
|
|
spec: rules: [{host: parameter.host}]
|
|
}
|
|
parameter: {
|
|
type: string
|
|
host: string
|
|
}`,
|
|
params: map[string]interface{}{
|
|
"type": "ClusterIP",
|
|
"host": "example.com",
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}}}}}}},
|
|
},
|
|
traitName: "t2",
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
"t2service": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"type": "ClusterIP"}}},
|
|
"t2ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"rules": []interface{}{map[string]interface{}{
|
|
"host": "example.com",
|
|
}}}}},
|
|
},
|
|
},
|
|
"outputs trait with context appRevision": {
|
|
traitTemplate: `
|
|
outputs: service: {
|
|
apiVersion: "v1"
|
|
kind: "Service"
|
|
metadata: {
|
|
name: context.name
|
|
annotations: "revision.oam.dev": context.appRevision
|
|
}
|
|
spec: type: parameter.type
|
|
}
|
|
outputs: ingress: {
|
|
apiVersion: "extensions/v1beta1"
|
|
kind: "Ingress"
|
|
metadata: name: context.name
|
|
spec: rules: [{host: parameter.host}]
|
|
}
|
|
parameter: {
|
|
type: string
|
|
host: string
|
|
}`,
|
|
params: map[string]interface{}{
|
|
"type": "ClusterIP",
|
|
"host": "example.com",
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}}}}}}},
|
|
},
|
|
traitName: "t2",
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
"t2service": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "metadata": map[string]interface{}{"name": "test", "annotations": map[string]interface{}{
|
|
"revision.oam.dev": "myapp-v1",
|
|
}}, "spec": map[string]interface{}{"type": "ClusterIP"}}},
|
|
"t2ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"rules": []interface{}{map[string]interface{}{
|
|
"host": "example.com",
|
|
}}}}},
|
|
},
|
|
},
|
|
"simple data passing": {
|
|
traitTemplate: `
|
|
parameter: {
|
|
domain: string
|
|
path: string
|
|
exposePort: int
|
|
}
|
|
// trait template can have multiple outputs in one trait
|
|
outputs: service: {
|
|
apiVersion: "v1"
|
|
kind: "Service"
|
|
spec: {
|
|
selector:
|
|
app: context.name
|
|
ports: [
|
|
{
|
|
port: parameter.exposePort
|
|
targetPort: context.output.spec.template.spec.containers[0].ports[0].containerPort
|
|
}
|
|
]
|
|
}
|
|
}
|
|
outputs: ingress: {
|
|
apiVersion: "networking.k8s.io/v1beta1"
|
|
kind: "Ingress"
|
|
metadata:
|
|
name: context.name
|
|
labels: config: context.outputs.gameconfig.data.enemies
|
|
spec: {
|
|
rules: [{
|
|
host: parameter.domain
|
|
http: {
|
|
paths: [{
|
|
path: parameter.path
|
|
backend: {
|
|
serviceName: context.name
|
|
servicePort: parameter.exposePort
|
|
}
|
|
}]
|
|
}
|
|
}]
|
|
}
|
|
}`,
|
|
params: map[string]interface{}{
|
|
"domain": "example.com",
|
|
"path": "ping",
|
|
"exposePort": 1080,
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}}}}}}},
|
|
},
|
|
traitName: "t3",
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
"t3service": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service", "spec": map[string]interface{}{"ports": []interface{}{map[string]interface{}{"port": int64(1080), "targetPort": int64(443)}}, "selector": map[string]interface{}{"app": "test"}}}},
|
|
"t3ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "networking.k8s.io/v1beta1", "kind": "Ingress", "labels": map[string]interface{}{"config": "enemies-data"}, "metadata": map[string]interface{}{"name": "test"}, "spec": map[string]interface{}{"rules": []interface{}{map[string]interface{}{"host": "example.com", "http": map[string]interface{}{"paths": []interface{}{map[string]interface{}{"backend": map[string]interface{}{"serviceName": "test", "servicePort": int64(1080)}, "path": "ping"}}}}}}}},
|
|
},
|
|
},
|
|
"outputs trait with schema": {
|
|
traitTemplate: `
|
|
#Service:{
|
|
apiVersion: string
|
|
kind: string
|
|
}
|
|
#Ingress:{
|
|
apiVersion: string
|
|
kind: string
|
|
}
|
|
outputs:{
|
|
service: #Service
|
|
ingress: #Ingress
|
|
}
|
|
outputs: service: {
|
|
apiVersion: "v1"
|
|
kind: "Service"
|
|
}
|
|
outputs: ingress: {
|
|
apiVersion: "extensions/v1beta1"
|
|
kind: "Ingress"
|
|
}
|
|
parameter: {
|
|
type: string
|
|
host: string
|
|
}`,
|
|
params: map[string]interface{}{
|
|
"type": "ClusterIP",
|
|
"host": "example.com",
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}}}}}}},
|
|
},
|
|
traitName: "t2",
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
"t2service": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "v1", "kind": "Service"}},
|
|
"t2ingress": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "extensions/v1beta1", "kind": "Ingress"}},
|
|
},
|
|
},
|
|
"outputs trait with no params": {
|
|
traitTemplate: `
|
|
outputs: hpa: {
|
|
apiVersion: "autoscaling/v2beta2"
|
|
kind: "HorizontalPodAutoscaler"
|
|
metadata: name: context.name
|
|
spec: {
|
|
minReplicas: parameter.min
|
|
maxReplicas: parameter.max
|
|
}
|
|
}
|
|
parameter: {
|
|
min: *1 | int
|
|
max: *10 | int
|
|
}`,
|
|
params: nil,
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}}}}}}},
|
|
},
|
|
traitName: "t2",
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config"}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
"t2hpa": &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "autoscaling/v2beta2", "kind": "HorizontalPodAutoscaler",
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"spec": map[string]interface{}{"maxReplicas": int64(10), "minReplicas": int64(1)}}},
|
|
},
|
|
},
|
|
"parameter type doesn't match will raise error": {
|
|
traitTemplate: `
|
|
parameter: {
|
|
exposePort: int
|
|
}
|
|
// trait template can have multiple outputs in one trait
|
|
outputs: service: {
|
|
apiVersion: "v1"
|
|
kind: "Service"
|
|
spec: {
|
|
selector:
|
|
app: context.name
|
|
ports: [
|
|
{
|
|
port: parameter.exposePort
|
|
targetPort: parameter.exposePort
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`,
|
|
params: map[string]interface{}{
|
|
"exposePort": "1080",
|
|
},
|
|
hasCompileErr: true,
|
|
},
|
|
|
|
"trait patch trait": {
|
|
traitTemplate: `
|
|
patch: {
|
|
context: outputs: gameconfig: {
|
|
metadata: annotations: parameter
|
|
}
|
|
}
|
|
|
|
parameter: [string]: string`,
|
|
params: map[string]interface{}{
|
|
"patch-by": "trait",
|
|
},
|
|
expWorkload: &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"context": map[string]interface{}{
|
|
"outputs": map[string]interface{}{
|
|
"gameconfig": map[string]interface{}{
|
|
"metadata": map[string]interface{}{"annotations": map[string]interface{}{"patch-by": "trait"}},
|
|
},
|
|
},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(2),
|
|
"selector": map[string]interface{}{
|
|
"matchLabels": map[string]interface{}{
|
|
"app.oam.dev/component": "test"}},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{"app.oam.dev/component": "test"},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{map[string]interface{}{
|
|
"envFrom": []interface{}{map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{"name": "testgame-config"},
|
|
}},
|
|
"image": "website:0.1",
|
|
"name": "main",
|
|
"ports": []interface{}{map[string]interface{}{"containerPort": int64(443)}}}}}}}},
|
|
},
|
|
expAssObjs: map[string]runtime.Object{
|
|
"AuxiliaryWorkloadgameconfig": &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{"name": "testgame-config", "annotations": map[string]interface{}{"patch-by": "trait"}}, "data": map[string]interface{}{"enemies": "enemies-data", "lives": "lives-data"}},
|
|
},
|
|
},
|
|
},
|
|
|
|
// errors
|
|
"invalid template(space-separated labels) will raise error": {
|
|
traitTemplate: `
|
|
a b: c`,
|
|
params: map[string]interface{}{},
|
|
hasCompileErr: true,
|
|
},
|
|
"reference a non-existent variable will raise error": {
|
|
traitTemplate: `
|
|
patch: {
|
|
metadata: name: none
|
|
}
|
|
|
|
parameter: [string]: string`,
|
|
params: map[string]interface{}{},
|
|
hasCompileErr: true,
|
|
},
|
|
"incorrect use of the map field in patch will raise error": {
|
|
traitTemplate: `
|
|
patch: {
|
|
metadata: annotations: parameter.none
|
|
}
|
|
|
|
parameter: [string]: string`,
|
|
params: map[string]interface{}{},
|
|
hasCompileErr: true,
|
|
},
|
|
"out-of-scope variables in patch will raise error": {
|
|
traitTemplate: `
|
|
patch: {
|
|
x : "out of scope"
|
|
context: outputs: gameconfig: {
|
|
metadata: name: x
|
|
}
|
|
}
|
|
|
|
parameter: [string]: string`,
|
|
params: map[string]interface{}{},
|
|
hasCompileErr: true,
|
|
},
|
|
"using the wrong keyword in the parameter will raise error": {
|
|
traitTemplate: `
|
|
patch: {
|
|
metadata: annotations: parameter
|
|
}
|
|
|
|
parameter: [string]: string`,
|
|
params: map[string]interface{}{
|
|
"wrong-keyword": "_|_ //",
|
|
},
|
|
hasCompileErr: true,
|
|
},
|
|
"using errs": {
|
|
traitTemplate: `
|
|
errs: parameter.errs
|
|
parameter: { errs: [...string] }`,
|
|
params: map[string]interface{}{
|
|
"errs": []string{"has error"},
|
|
},
|
|
hasCompileErr: true,
|
|
},
|
|
}
|
|
|
|
for cassinfo, v := range tds {
|
|
baseTemplate := `
|
|
output: {
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
spec: {
|
|
selector: matchLabels: {
|
|
"app.oam.dev/component": context.name
|
|
}
|
|
replicas: parameter.replicas
|
|
template: {
|
|
metadata: labels: {
|
|
"app.oam.dev/component": context.name
|
|
}
|
|
spec: {
|
|
containers: [{
|
|
name: "main"
|
|
image: parameter.image
|
|
ports: [{containerPort: parameter.port}]
|
|
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
|
|
}
|
|
}
|
|
|
|
parameter: {
|
|
// +usage=Which image would you like to use for your service
|
|
// +short=i
|
|
image: *"website:0.1" | string
|
|
// +usage=Commands to run in the container
|
|
cmd?: [...string]
|
|
replicas: *1 | int
|
|
lives: string
|
|
enemies: string
|
|
port: int
|
|
}
|
|
|
|
`
|
|
ctx := process.NewContext(process.ContextData{
|
|
AppName: "myapp",
|
|
CompName: "test",
|
|
Namespace: "default",
|
|
AppRevisionName: "myapp-v1",
|
|
})
|
|
wt := NewWorkloadAbstractEngine("-", &packages.PackageDiscover{})
|
|
if err := wt.Complete(ctx, baseTemplate, map[string]interface{}{
|
|
"replicas": 2,
|
|
"enemies": "enemies-data",
|
|
"lives": "lives-data",
|
|
"port": 443,
|
|
}); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
td := NewTraitAbstractEngine(v.traitName, &packages.PackageDiscover{})
|
|
err := td.Complete(ctx, v.traitTemplate, v.params)
|
|
hasError := err != nil
|
|
assert.Equal(t, v.hasCompileErr, hasError)
|
|
if v.hasCompileErr {
|
|
continue
|
|
}
|
|
base, assists := ctx.Output()
|
|
assert.Equal(t, len(v.expAssObjs), len(assists), cassinfo)
|
|
assert.NotNil(t, base)
|
|
obj, err := base.Unstructured()
|
|
assert.NoError(t, err, base.String())
|
|
assert.Equal(t, v.expWorkload, obj, cassinfo)
|
|
for _, ss := range assists {
|
|
got, err := ss.Ins.Unstructured()
|
|
assert.NoError(t, err, cassinfo)
|
|
assert.Equal(t, v.expAssObjs[ss.Type+ss.Name], got, "case %s , type: %s name: %s", cassinfo, ss.Type, ss.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWorkloadTemplateCompleteRenderOrder(t *testing.T) {
|
|
testcases := map[string]struct {
|
|
template string
|
|
order []struct {
|
|
name string
|
|
content string
|
|
}
|
|
}{
|
|
"dict-order": {
|
|
template: `
|
|
output: {
|
|
kind: "Deployment"
|
|
}
|
|
|
|
outputs: configMap :{
|
|
name: "test-configMap"
|
|
}
|
|
|
|
outputs: ingress :{
|
|
name: "test-ingress"
|
|
}
|
|
|
|
outputs: service :{
|
|
name: "test-service"
|
|
}
|
|
`,
|
|
order: []struct {
|
|
name string
|
|
content string
|
|
}{{
|
|
name: "configMap",
|
|
content: "name: \"test-configMap\"\n",
|
|
}, {
|
|
name: "ingress",
|
|
content: "name: \"test-ingress\"\n",
|
|
}, {
|
|
name: "service",
|
|
content: "name: \"test-service\"\n",
|
|
}},
|
|
},
|
|
"non-dict-order": {
|
|
template: `
|
|
output: {
|
|
name: "base"
|
|
}
|
|
outputs: route :{
|
|
name: "test-route"
|
|
}
|
|
|
|
outputs: service :{
|
|
name: "test-service"
|
|
}
|
|
`,
|
|
order: []struct {
|
|
name string
|
|
content string
|
|
}{{
|
|
name: "route",
|
|
content: "name: \"test-route\"\n",
|
|
}, {
|
|
name: "service",
|
|
content: "name: \"test-service\"\n",
|
|
}},
|
|
},
|
|
}
|
|
for k, v := range testcases {
|
|
wd := NewWorkloadAbstractEngine(k, &packages.PackageDiscover{})
|
|
ctx := process.NewContext(process.ContextData{
|
|
AppName: "myapp",
|
|
CompName: k,
|
|
Namespace: "default",
|
|
AppRevisionName: "myapp-v1",
|
|
})
|
|
err := wd.Complete(ctx, v.template, map[string]interface{}{})
|
|
assert.NoError(t, err)
|
|
_, assists := ctx.Output()
|
|
for i, ss := range assists {
|
|
assert.Equal(t, ss.Name, v.order[i].name)
|
|
assert.Equal(t, ss.Ins.String(), v.order[i].content)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTraitTemplateCompleteRenderOrder(t *testing.T) {
|
|
testcases := map[string]struct {
|
|
template string
|
|
order []struct {
|
|
name string
|
|
content string
|
|
}
|
|
}{
|
|
"dict-order": {
|
|
template: `
|
|
outputs: abc :{
|
|
name: "test-abc"
|
|
}
|
|
|
|
outputs: def :{
|
|
name: "test-def"
|
|
}
|
|
|
|
outputs: ghi :{
|
|
name: "test-ghi"
|
|
}
|
|
`,
|
|
order: []struct {
|
|
name string
|
|
content string
|
|
}{{
|
|
name: "abc",
|
|
content: "name: \"test-abc\"\n",
|
|
}, {
|
|
name: "def",
|
|
content: "name: \"test-def\"\n",
|
|
}, {
|
|
name: "ghi",
|
|
content: "name: \"test-ghi\"\n",
|
|
}},
|
|
},
|
|
"non-dict-order": {
|
|
template: `
|
|
outputs: zyx :{
|
|
name: "test-zyx"
|
|
}
|
|
|
|
outputs: lmn :{
|
|
name: "test-lmn"
|
|
}
|
|
|
|
outputs: abc :{
|
|
name: "test-abc"
|
|
}
|
|
`,
|
|
order: []struct {
|
|
name string
|
|
content string
|
|
}{{
|
|
name: "zyx",
|
|
content: "name: \"test-zyx\"\n",
|
|
}, {
|
|
name: "lmn",
|
|
content: "name: \"test-lmn\"\n",
|
|
}, {
|
|
name: "abc",
|
|
content: "name: \"test-abc\"\n",
|
|
}},
|
|
},
|
|
}
|
|
for k, v := range testcases {
|
|
td := NewTraitAbstractEngine(k, &packages.PackageDiscover{})
|
|
ctx := process.NewContext(process.ContextData{
|
|
AppName: "myapp",
|
|
CompName: k,
|
|
Namespace: "default",
|
|
AppRevisionName: "myapp-v1",
|
|
})
|
|
err := td.Complete(ctx, v.template, map[string]interface{}{})
|
|
assert.NoError(t, err)
|
|
_, assists := ctx.Output()
|
|
for i, ss := range assists {
|
|
assert.Equal(t, ss.Name, v.order[i].name)
|
|
assert.Equal(t, ss.Ins.String(), v.order[i].content)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheckHealth(t *testing.T) {
|
|
cases := map[string]struct {
|
|
tpContext map[string]interface{}
|
|
healthTemp string
|
|
exp bool
|
|
}{
|
|
"normal-equal": {
|
|
tpContext: map[string]interface{}{
|
|
"output": map[string]interface{}{
|
|
"status": map[string]interface{}{
|
|
"readyReplicas": 4,
|
|
"replicas": 4,
|
|
},
|
|
},
|
|
},
|
|
healthTemp: "isHealth: context.output.status.readyReplicas == context.output.status.replicas",
|
|
exp: true,
|
|
},
|
|
"normal-false": {
|
|
tpContext: map[string]interface{}{
|
|
"output": map[string]interface{}{
|
|
"status": map[string]interface{}{
|
|
"readyReplicas": 4,
|
|
"replicas": 5,
|
|
},
|
|
},
|
|
},
|
|
healthTemp: "isHealth: context.output.status.readyReplicas == context.output.status.replicas",
|
|
exp: false,
|
|
},
|
|
"array-case-equal": {
|
|
tpContext: map[string]interface{}{
|
|
"output": map[string]interface{}{
|
|
"status": map[string]interface{}{
|
|
"conditions": []interface{}{
|
|
map[string]interface{}{"status": "True"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
healthTemp: `isHealth: context.output.status.conditions[0].status == "True"`,
|
|
exp: true,
|
|
},
|
|
}
|
|
for message, ca := range cases {
|
|
healthy, err := checkHealth(ca.tpContext, ca.healthTemp)
|
|
assert.NoError(t, err, message)
|
|
assert.Equal(t, ca.exp, healthy, message)
|
|
}
|
|
}
|
|
|
|
func TestGetStatus(t *testing.T) {
|
|
cases := map[string]struct {
|
|
tpContext map[string]interface{}
|
|
parameter interface{}
|
|
statusTemp string
|
|
expMessage string
|
|
}{
|
|
"field-with-array-and-outputs": {
|
|
tpContext: map[string]interface{}{
|
|
"outputs": map[string]interface{}{
|
|
"service": map[string]interface{}{
|
|
"spec": map[string]interface{}{
|
|
"type": "NodePort",
|
|
"clusterIP": "10.0.0.1",
|
|
"ports": []interface{}{
|
|
map[string]interface{}{
|
|
"port": 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"ingress": map[string]interface{}{
|
|
"rules": []interface{}{
|
|
map[string]interface{}{
|
|
"host": "example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
statusTemp: `message: "type: " + context.outputs.service.spec.type + " clusterIP:" + context.outputs.service.spec.clusterIP + " ports:" + "\(context.outputs.service.spec.ports[0].port)" + " domain:" + context.outputs.ingress.rules[0].host`,
|
|
expMessage: "type: NodePort clusterIP:10.0.0.1 ports:80 domain:example.com",
|
|
},
|
|
"complex status": {
|
|
tpContext: map[string]interface{}{
|
|
"outputs": map[string]interface{}{
|
|
"ingress": map[string]interface{}{
|
|
"spec": map[string]interface{}{
|
|
"rules": []interface{}{
|
|
map[string]interface{}{
|
|
"host": "example.com",
|
|
},
|
|
},
|
|
},
|
|
"status": map[string]interface{}{
|
|
"loadBalancer": map[string]interface{}{
|
|
"ingress": []interface{}{
|
|
map[string]interface{}{
|
|
"ip": "10.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
statusTemp: `if len(context.outputs.ingress.status.loadBalancer.ingress) > 0 {
|
|
message: "Visiting URL: " + context.outputs.ingress.spec.rules[0].host + ", IP: " + context.outputs.ingress.status.loadBalancer.ingress[0].ip
|
|
}
|
|
if len(context.outputs.ingress.status.loadBalancer.ingress) == 0 {
|
|
message: "No loadBalancer found, visiting by using 'vela port-forward " + context.appName + " --route'\n"
|
|
}`,
|
|
expMessage: "Visiting URL: example.com, IP: 10.0.0.1",
|
|
},
|
|
"status use parameter field": {
|
|
tpContext: map[string]interface{}{
|
|
"outputs": map[string]interface{}{
|
|
"test-name": map[string]interface{}{
|
|
"spec": map[string]interface{}{
|
|
"type": "NodePort",
|
|
"clusterIP": "10.0.0.1",
|
|
"ports": []interface{}{
|
|
map[string]interface{}{
|
|
"port": 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
parameter: map[string]interface{}{
|
|
"configInfo": map[string]string{
|
|
"name": "test-name",
|
|
},
|
|
},
|
|
statusTemp: `message: parameter.configInfo.name + ".type: " + context.outputs["\(parameter.configInfo.name)"].spec.type`,
|
|
expMessage: "test-name.type: NodePort",
|
|
},
|
|
"import package in template": {
|
|
tpContext: map[string]interface{}{
|
|
"outputs": map[string]interface{}{
|
|
"service": map[string]interface{}{
|
|
"spec": map[string]interface{}{
|
|
"type": "NodePort",
|
|
"clusterIP": "10.0.0.1",
|
|
"ports": []interface{}{
|
|
map[string]interface{}{
|
|
"port": 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"ingress": map[string]interface{}{
|
|
"rules": []interface{}{
|
|
map[string]interface{}{
|
|
"host": "example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
statusTemp: `import "strconv"
|
|
message: "ports: " + strconv.FormatInt(context.outputs.service.spec.ports[0].port,10)`,
|
|
expMessage: "ports: 80",
|
|
},
|
|
}
|
|
for message, ca := range cases {
|
|
gotMessage, err := getStatusMessage(&packages.PackageDiscover{}, ca.tpContext, ca.statusTemp, ca.parameter)
|
|
assert.NoError(t, err, message)
|
|
assert.Equal(t, ca.expMessage, gotMessage, message)
|
|
}
|
|
}
|