Compare commits

...

27 Commits

Author SHA1 Message Date
github-actions[bot]
29ecc5c0df Feat: support vela show for workflow step definition (#3161)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 23852b3d10)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-01-25 15:05:46 +08:00
github-actions[bot]
20c11f2b84 fix: vela addnon enable cannot support = (#3158)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit ddefb8cb4e)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-01-25 13:58:24 +08:00
github-actions[bot]
e9f7cf7c23 Fix: add context parameters into the error message (#3159)
Signed-off-by: zeed-w-beez <zeed.w.zhao@gmail.com>
(cherry picked from commit 170888063d)

Co-authored-by: zeed-w-beez <zeed.w.zhao@gmail.com>
2022-01-25 13:28:01 +08:00
github-actions[bot]
cebeff867a [Backport release-1.2] Feat: addon parameter support ui-shcema (#3155)
* Feat: addon parameter support ui-shcema

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix ci

(cherry picked from commit 8e2bf9c68d)

* add more tests

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 661d9f7a47)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-01-25 10:47:07 +08:00
github-actions[bot]
7902a19aae [Backport release-1.2] Fix: retrieve Terraform variables from variables.tf (#3153)
* Fix: retrieve Terraform variables from variables.tf

If Terraform modules/resources are stored in remote git repos, get
variables from file `varialbes.tf`, or from `main.tf`.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
(cherry picked from commit b257825af6)

* set the required field per the variables' property

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
(cherry picked from commit 4b151582c5)

* fix ut issue

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
(cherry picked from commit b52145c8a4)

* fix ut issue

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
(cherry picked from commit 410acc8ae2)

Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-24 13:56:59 +08:00
github-actions[bot]
f98b8c7d8a Fix: add providerRef in generated ComponentDefinition (#3151)
If the provider is not provider, append the providerRef section in
the generated ComponentDefinition

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
(cherry picked from commit 34ecc40aad)

Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-24 11:00:41 +08:00
github-actions[bot]
ee8773e1cf [Backport release-1.2] Fix: handle workflow cache reconcile (#3148)
* Fix: handle workflow cache reconcile

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 12df87ac11)

* fix return and move backoff to memory

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit ee876f53c3)

* handle failed to patch case

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit eac4a1b370)

* add store in err case

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 32825c5c41)

* make reviewable

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 02b9c60922)

* fix ut

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit bff156cbe6)

* do cleanup in ut

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 463bd96e78)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-01-24 10:37:48 +08:00
github-actions[bot]
8cf2f20846 [Backport release-1.2] Fix: workflow skip executing all steps occasionally (#3147)
* fix asi

Signed-off-by: Jian.Li <lj176172@alibaba-inc.com>
(cherry picked from commit 2c26caedd3)

* fix lint

Signed-off-by: Jian.Li <lj176172@alibaba-inc.com>
(cherry picked from commit b63e892eb3)

* add trace tag

(cherry picked from commit 1a6d79642e)

* add args for this feature

Signed-off-by: Jian.Li <lj176172@alibaba-inc.com>
(cherry picked from commit b9181b9fc0)

* enable-asi-compatibility

Signed-off-by: Jian.Li <lj176172@alibaba-inc.com>
(cherry picked from commit 5d013db0bd)

Co-authored-by: Jian.Li <lj176172@alibaba-inc.com>
2022-01-23 10:13:59 +08:00
github-actions[bot]
2c6e8e7de7 [Backport release-1.2] Feat: extend gateway trait to set class in spec (#3146)
* Feat: extend gateway trait to set class in spec

`kubernetes.io/ingress.class` annotation is deprecated in favor of
`.spec.ingressClassName`. However, there is no way to set it through
the gateway trait for now.

This commit extends the gateway trait by adding `classInSpec` to
parameter. Forcing the use of `.spec.ingressClassName` makes sense, but
some ingress controllers (including old versions) may not recognize
this field. Therefore, set default value of `classInSpec` to `false`
for backward compatibility.

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>
(cherry picked from commit ad8bd1c1c4)

* Chore: update classInSpec usage

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>
(cherry picked from commit 721f879e3d)

Co-authored-by: Sunghoon Kang <hoon@linecorp.com>
2022-01-23 10:13:18 +08:00
github-actions[bot]
dbfd6a1d10 [Backport release-1.2] Fix: prioritize namespace flag for vela up (#3137)
* Fix: prioritize namespace flag for `vela up`

Currently, CLI applies application to `default` namespace when there is
no explicit namespace in application spec, even if the namespace flag
is set.

This commit fixes issue by overriding the namespace if the namespace
flag is set.

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>
(cherry picked from commit 4cf07dcd97)

* Test: add test cases for overriding namespaces

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>
(cherry picked from commit 5ba93541e4)

Co-authored-by: Sunghoon Kang <hoon@linecorp.com>
2022-01-21 11:27:11 +08:00
github-actions[bot]
ce8e652802 Fix: support generate Terraform ComponentDefinition from local HCL file (#3134)
Besides generating a Terraform ComponentDefinition from a remote git repo,
support generate one from local HCL file

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
(cherry picked from commit bc06a5343e)

Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-20 14:59:24 +08:00
github-actions[bot]
9968211163 add imagePullSecrets for helm templates to support private docker registry (#3129)
Signed-off-by: StevenLeiZhang <zhangleiic@163.com>
(cherry picked from commit cf9eb0ded9)

Co-authored-by: StevenLeiZhang <zhangleiic@163.com>
2022-01-19 13:00:45 +08:00
github-actions[bot]
c86776cca1 [Backport release-1.2] Feat: add port name in webservice (#3120)
* Feat: add port name in webservice

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 76789b0222)

* fix port name in container

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit b486a7c74d)

* generate port name if not specified

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 4eec002916)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-01-18 19:43:18 +08:00
github-actions[bot]
11a1b2fa72 Fix: add app samples for Terraform definition (#3119)
Added application samples for all Terraform typed ComponentDefinition,
and also localize the title for AWS and AZure

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
(cherry picked from commit 670a327161)

Co-authored-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-18 18:10:16 +08:00
github-actions[bot]
cd036b87ae Feat: support wild match for env patch (#3116)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
(cherry picked from commit fe0d33c6d9)

Co-authored-by: Somefive <yd219913@alibaba-inc.com>
2022-01-18 18:07:05 +08:00
github-actions[bot]
7e447c6532 fix revision will change when add new trait with skiprevisionaffect to application (#3117)
Signed-off-by: chwetion <chwetion@foxmail.com>
(cherry picked from commit 86f6fef216)

Co-authored-by: chwetion <chwetion@foxmail.com>
2022-01-18 13:41:48 +08:00
github-actions[bot]
b836f86484 Fix: trait/comp command output without a new line (#3115)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
(cherry picked from commit 1ba3e1dd62)

Co-authored-by: qiaozp <chivalry.pp@gmail.com>
2022-01-18 13:05:23 +08:00
github-actions[bot]
d34372bf47 [Backport release-1.2] Feat: add JFrog webhook trigger (#3114)
* add jfrog webhook to update application image

Signed-off-by: chwetion <chwetion@foxmail.com>
(cherry picked from commit 962fce6870)

* edit jfrog default request header

Signed-off-by: chwetion <chwetion@foxmail.com>
(cherry picked from commit a977bfc9af)

Co-authored-by: chwetion <chwetion@foxmail.com>
2022-01-18 11:51:15 +08:00
Somefive
12f392cd92 Fix: rollout workload namespace not aligned with rollout (#3107)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-01-17 20:06:27 +08:00
StevenLeiZhang
af27e6a776 ignore vela-system, which is specified in needNamespace for addon metadata information (#3109)
Signed-off-by: StevenLeiZhang <zhangleiic@163.com>
2022-01-17 20:02:05 +08:00
StevenLeiZhang
f57815a5bf ignore files under the addon path of github addon registry (#3099)
Signed-off-by: StevenLeiZhang <zhangleiic@163.com>
2022-01-17 20:01:46 +08:00
Somefive
69527b257c Feat: support external revision in patch component (#3106)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-01-17 19:29:28 +08:00
barnettZQG
d88d4d8eca Fix: clear old data in mongodb unit test case (#3103)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-01-17 19:06:29 +08:00
Zheng Xi Zhou
4e881b44af Fix: support generate cloud resource docs in Chinese (#3079)
* Fix: support generate cloud resource docs in Chinese

`vela def doc-gen` will also generate Chinese cloud resource docs

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* Continue the development

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* add ut

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* add ut and fix linting issue

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* Address comments

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* Fix `Should not use dot imports. (ST1001) ` issue

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* add copyright

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-01-17 16:18:59 +08:00
wyike
800b50cf0b fix acr image no version (#3100)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-01-17 16:09:21 +08:00
Jianbo Sun
3d9e1c7d80 Fix: use personel token of vela-bot instead of github token for homebrew update (#3096)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-01-17 11:45:01 +08:00
barnettZQG
fccc5df25e Fix: can't query data from the MongoDB (#3095)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-01-17 11:25:09 +08:00
73 changed files with 2459 additions and 508 deletions

View File

@@ -57,7 +57,7 @@ jobs:
- name: Build & Pushing vela-core for ACR
run: |
docker build --build-arg GOPROXY=https://proxy.golang.org -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }} .
docker build --build-arg GOPROXY=https://proxy.golang.org --build-arg VERSION=${{ steps.get_version.outputs.VERSION }} --build-arg GITVERSION=git-${{ steps.vars.outputs.git_revision }} -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }} .
docker push kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
- uses: docker/build-push-action@v2
name: Build & Pushing vela-core for Dockerhub and GHCR
@@ -79,7 +79,7 @@ jobs:
- name: Build & Pushing vela-apiserver for ACR
run: |
docker build --build-arg GOPROXY=https://proxy.golang.org -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }} -f Dockerfile.apiserver .
docker build --build-arg GOPROXY=https://proxy.golang.org --build-arg VERSION=${{ steps.get_version.outputs.VERSION }} --build-arg GITVERSION=git-${{ steps.vars.outputs.git_revision }} -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }} -f Dockerfile.apiserver .
docker push kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
- uses: docker/build-push-action@v2
name: Build & Pushing vela-apiserver for Dockerhub and GHCR
@@ -101,7 +101,7 @@ jobs:
- name: Build & Pushing vela runtime rollout for ACR
run: |
docker build --build-arg GOPROXY=https://proxy.golang.org -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }} .
docker build --build-arg GOPROXY=https://proxy.golang.org --build-arg VERSION=${{ steps.get_version.outputs.VERSION }} --build-arg GITVERSION=git-${{ steps.vars.outputs.git_revision }} -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }} .
docker push kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
- uses: docker/build-push-action@v2
name: Build & Pushing runtime rollout for Dockerhub and GHCR

View File

@@ -142,7 +142,7 @@ jobs:
- name: Update Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.HOMEBREW_TOKEN }}
formula: kubevela
tag: ${{ github.ref }}
revision: ${{ github.sha }}

View File

@@ -213,6 +213,8 @@ const (
WorkflowStateFinished WorkflowState = "finished"
// WorkflowStateExecuting means workflow is still running or waiting some steps.
WorkflowStateExecuting WorkflowState = "executing"
// WorkflowStateSkipping means it will skip this reconcile and let next reconcile to handle it.
WorkflowStateSkipping WorkflowState = "skipping"
)
// ApplicationComponentStatus record the health status of App component

View File

@@ -45,10 +45,11 @@ func (in *EnvTraitPatch) ToApplicationTrait() *common.ApplicationTrait {
// EnvComponentPatch is the patch to component
type EnvComponentPatch struct {
Name string `json:"name"`
Type string `json:"type"`
Properties *runtime.RawExtension `json:"properties,omitempty"`
Traits []EnvTraitPatch `json:"traits,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Properties *runtime.RawExtension `json:"properties,omitempty"`
Traits []EnvTraitPatch `json:"traits,omitempty"`
ExternalRevision string `json:"externalRevision,omitempty"`
}
// ToApplicationComponent convert EnvComponentPatch into ApplicationComponent

View File

@@ -17,7 +17,10 @@
package v1beta1
import (
"encoding/json"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
@@ -138,3 +141,36 @@ func (app *Application) GetComponent(workloadType string) *common.ApplicationCom
}
return nil
}
// Unstructured convert application to unstructured.Unstructured.
func (app *Application) Unstructured() (*unstructured.Unstructured, error) {
var obj = &unstructured.Unstructured{}
app.SetGroupVersionKind(ApplicationKindVersionKind)
bt, err := json.Marshal(app)
if err != nil {
return nil, err
}
if err := obj.UnmarshalJSON(bt); err != nil {
return nil, err
}
if app.Status.Services == nil {
if err := unstructured.SetNestedSlice(obj.Object, []interface{}{}, "status", "services"); err != nil {
return nil, err
}
}
if app.Status.AppliedResources == nil {
if err := unstructured.SetNestedSlice(obj.Object, []interface{}{}, "status", "appliedResources"); err != nil {
return nil, err
}
}
if wfStatus := app.Status.Workflow; wfStatus != nil && wfStatus.Steps == nil {
if err := unstructured.SetNestedSlice(obj.Object, []interface{}{}, "status", "workflow", "steps"); err != nil {
return nil, err
}
}
return obj, nil
}

View File

@@ -22,6 +22,10 @@ spec:
app: {{ template "kubevela.name" . }}-admission-create
{{- include "kubevela.labels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: create
image: {{ .Values.imageRegistry }}{{ .Values.admissionWebhooks.patch.image.repository }}:{{ .Values.admissionWebhooks.patch.image.tag }}

View File

@@ -22,6 +22,10 @@ spec:
app: {{ template "kubevela.name" . }}-admission-patch
{{- include "kubevela.labels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: patch
image: {{ .Values.imageRegistry }}{{ .Values.admissionWebhooks.patch.image.repository }}:{{ .Values.admissionWebhooks.patch.image.tag }}

View File

@@ -198,6 +198,10 @@ spec:
app: {{ template "kubevela.fullname" . }}-cluster-gateway-tls-secret-create
{{- include "kubevela.labels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: create
image: {{ .Values.imageRegistry }}{{ .Values.admissionWebhooks.patch.image.repository }}:{{ .Values.admissionWebhooks.patch.image.tag }}
@@ -241,6 +245,10 @@ spec:
app: {{ template "kubevela.fullname" . }}-cluster-gateway-tls-secret-patch
{{- include "kubevela.labels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: patch
image: {{ .Values.imageRegistry }}{{ .Values.multicluster.clusterGateway.image.repository }}:{{ .Values.multicluster.clusterGateway.image.tag }}

View File

@@ -32,21 +32,30 @@ spec:
kind: "Ingress"
metadata: {
name: context.name
annotations: "kubernetes.io/ingress.class": parameter.class
annotations: {
if !parameter.classInSpec {
"kubernetes.io/ingress.class": parameter.class
}
}
}
spec: {
if parameter.classInSpec {
ingressClassName: parameter.class
}
rules: [{
host: parameter.domain
http: paths: [
for k, v in parameter.http {
path: k
pathType: "ImplementationSpecific"
backend: service: {
name: context.name
port: number: v
}
},
]
}]
}
spec: rules: [{
host: parameter.domain
http: paths: [
for k, v in parameter.http {
path: k
pathType: "ImplementationSpecific"
backend: service: {
name: context.name
port: number: v
}
},
]
}]
}
parameter: {
// +usage=Specify the domain you want to expose
@@ -57,6 +66,9 @@ spec:
// +usage=Specify the class of ingress to use
class: *"nginx" | string
// +usage=Set ingress class in '.spec.ingressClassName' instead of 'kubernetes.io/ingress.class' annotation.
classInSpec: *false | bool
}
status:
customStatus: |-

View File

@@ -11,6 +11,10 @@ spec:
schematic:
cue:
template: |
import (
"strconv"
)
mountsArray: {
pvc: *[
for v in parameter.volumeMounts.pvc {
@@ -152,6 +156,12 @@ spec:
{
containerPort: v.port
protocol: v.protocol
if v.name != _|_ {
name: v.name
}
if v.name == _|_ {
name: "port-" + strconv.FormatInt(v.port, 10)
}
}}]
}
@@ -262,6 +272,12 @@ spec:
for v in parameter.ports if v.expose == true {
port: v.port
targetPort: v.port
if v.name != _|_ {
name: v.name
}
if v.name == _|_ {
name: "port-" + strconv.FormatInt(v.port, 10)
}
},
]
outputs: {
@@ -304,6 +320,8 @@ spec:
ports?: [...{
// +usage=Number of port to expose on the pod's IP address
port: int
// +usage=Name of the port
name?: string
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
protocol: *"TCP" | "UDP" | "SCTP"
// +usage=Specify if the port should be exposed

View File

@@ -32,21 +32,30 @@ spec:
kind: "Ingress"
metadata: {
name: context.name
annotations: "kubernetes.io/ingress.class": parameter.class
annotations: {
if !parameter.classInSpec {
"kubernetes.io/ingress.class": parameter.class
}
}
}
spec: {
if parameter.classInSpec {
ingressClassName: parameter.class
}
rules: [{
host: parameter.domain
http: paths: [
for k, v in parameter.http {
path: k
pathType: "ImplementationSpecific"
backend: service: {
name: context.name
port: number: v
}
},
]
}]
}
spec: rules: [{
host: parameter.domain
http: paths: [
for k, v in parameter.http {
path: k
pathType: "ImplementationSpecific"
backend: service: {
name: context.name
port: number: v
}
},
]
}]
}
parameter: {
// +usage=Specify the domain you want to expose
@@ -57,6 +66,9 @@ spec:
// +usage=Specify the class of ingress to use
class: *"nginx" | string
// +usage=Set ingress class in '.spec.ingressClassName' instead of 'kubernetes.io/ingress.class' annotation.
classInSpec: *false | bool
}
status:
customStatus: |-

View File

@@ -11,6 +11,10 @@ spec:
schematic:
cue:
template: |
import (
"strconv"
)
mountsArray: {
pvc: *[
for v in parameter.volumeMounts.pvc {
@@ -152,6 +156,12 @@ spec:
{
containerPort: v.port
protocol: v.protocol
if v.name != _|_ {
name: v.name
}
if v.name == _|_ {
name: "port-" + strconv.FormatInt(v.port, 10)
}
}}]
}
@@ -262,6 +272,12 @@ spec:
for v in parameter.ports if v.expose == true {
port: v.port
targetPort: v.port
if v.name != _|_ {
name: v.name
}
if v.name == _|_ {
name: "port-" + strconv.FormatInt(v.port, 10)
}
},
]
outputs: {
@@ -304,6 +320,8 @@ spec:
ports?: [...{
// +usage=Number of port to expose on the pod's IP address
port: int
// +usage=Name of the port
name?: string
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
protocol: *"TCP" | "UDP" | "SCTP"
// +usage=Specify if the port should be exposed

View File

@@ -129,6 +129,7 @@ func main() {
flag.DurationVar(&retryPeriod, "leader-election-retry-period", 2*time.Second,
"The duration the LeaderElector clients should wait between tries of actions")
flag.BoolVar(&enableClusterGateway, "enable-cluster-gateway", false, "Enable cluster-gateway to use multicluster, disabled by default.")
flag.BoolVar(&controllerArgs.EnableCompatibility, "enable-asi-compatibility", false, "enable compatibility for asi")
standardcontroller.AddOptimizeFlags()
flag.Parse()

View File

@@ -14,8 +14,12 @@ spec:
ports:
- port: 8000
- port: 8001
name: exposeport1
protocol: UDP
expose: true
expose: true
- port: 8002
protocol: UDP
expose: true
volumeMounts:
pvc:
- name: my-mount

View File

@@ -0,0 +1,74 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: example-app
namespace: default
spec:
components:
- name: podinfo
type: webservice
properties:
image: stefanprodan/podinfo
traits:
- type: scaler
properties:
replicas: 1
- name: hello-world
type: webservice
properties:
image: crccheck/hello-world
traits:
- type: scaler
properties:
replicas: 1
- name: nginx
type: worker
properties:
image: nginx
traits:
- type: scaler
properties:
replicas: 1
policies:
- name: example-multi-env-policy
type: env-binding
properties:
envs:
- name: test
placement:
clusterSelector:
name: local
namespaceSelector:
name: test
patch:
components:
- name: podinfo # patch to component named podinfo, no type check
traits:
- type: scaler
properties:
replicas: 2
- name: staging
placement:
clusterSelector:
name: remote
patch:
components: # patch to all webservice components
- type: webservice
traits:
- type: scaler
properties:
replicas: 3
- name: prod
placement:
clusterSelector:
name: remote
namespaceSelector:
name: prod
patch:
components: # patch to all components
- traits:
- type: scaler
properties:
replicas: 3

View File

@@ -467,6 +467,10 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
}
app.Labels = util.MergeMapOverrideWithDst(app.Labels, map[string]string{oam.LabelAddonName: addon.Name})
for _, namespace := range addon.NeedNamespace {
// vela-system must exist before rendering vela addon
if namespace == types.DefaultKubeVelaNS {
continue
}
comp := common2.ApplicationComponent{
Type: "raw",
Name: fmt.Sprintf("%s-namespace", namespace),

View File

@@ -226,6 +226,17 @@ func TestRenderApp(t *testing.T) {
assert.Equal(t, len(app.Spec.Components), 2)
}
func TestRenderAppWithNeedNamespace(t *testing.T) {
addon := baseAddon
addon.NeedNamespace = append(addon.NeedNamespace, types.DefaultKubeVelaNS)
app, err := RenderApp(ctx, &addon, nil, nil, map[string]interface{}{})
assert.NoError(t, err, "render app fail")
assert.Equal(t, len(app.Spec.Components), 2)
for _, c := range app.Spec.Components {
assert.NotEqual(t, types.DefaultKubeVelaNS+"-namespace", c.Name, "namespace vela-system should not be rendered")
}
}
func TestRenderDeploy2RuntimeAddon(t *testing.T) {
addonDeployToRuntime := baseAddon
addonDeployToRuntime.Meta.DeployTo = &DeployTo{

View File

@@ -48,6 +48,9 @@ func (g *gitReader) ListAddonMeta() (map[string]SourceMeta, error) {
}
for _, item := range items {
// single addon
if item.GetType() != DirType {
continue
}
addonName := path.Base(item.GetPath())
addonMeta, err := g.listAddonMeta(g.RelativePath(item))
if err != nil {

View File

@@ -41,6 +41,9 @@ var (
// ErrIndexInvalid Error that entity index is invalid
ErrIndexInvalid = NewDBError(fmt.Errorf("entity index is invalid"))
// ErrEntityInvalid Error that entity is invalid
ErrEntityInvalid = NewDBError(fmt.Errorf("entity is invalid"))
)
// DBError datastore error

View File

@@ -37,6 +37,9 @@ type mongodb struct {
database string
}
// PrimaryKey primary key
const PrimaryKey = "_name"
// New new mongodb datastore instance
func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error) {
if !strings.HasPrefix(cfg.URL, "mongodb://") {
@@ -67,8 +70,13 @@ func (m *mongodb) Add(ctx context.Context, entity datastore.Entity) error {
if err := m.Get(ctx, entity); err == nil {
return datastore.ErrRecordExist
}
model, err := convertToMap(entity)
if err != nil {
return datastore.ErrEntityInvalid
}
model[PrimaryKey] = entity.PrimaryKey()
collection := m.client.Database(m.database).Collection(entity.TableName())
_, err := collection.InsertOne(ctx, entity)
_, err = collection.InsertOne(ctx, model)
if err != nil {
return datastore.NewDBError(err)
}
@@ -203,7 +211,7 @@ func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datasto
if entity.Index() != nil {
for k, v := range entity.Index() {
filter = append(filter, bson.E{
Key: k,
Key: strings.ToLower(k),
Value: v,
})
}
@@ -279,9 +287,21 @@ func (m *mongodb) Count(ctx context.Context, entity datastore.Entity, filterOpti
}
func makeNameFilter(name string) bson.D {
return bson.D{{Key: "name", Value: name}}
return bson.D{{Key: PrimaryKey, Value: name}}
}
func makeEntityUpdate(entity interface{}) bson.M {
return bson.M{"$set": entity}
}
func convertToMap(model interface{}) (bson.M, error) {
b, err := bson.Marshal(model)
if err != nil {
return nil, err
}
var re = make(bson.M)
if err := bson.Unmarshal(b, &re); err != nil {
return nil, err
}
return re, nil
}

View File

@@ -26,6 +26,8 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
@@ -35,7 +37,11 @@ var mongodbDriver datastore.DataStore
var _ = BeforeSuite(func(done Done) {
rand.Seed(time.Now().UnixNano())
By("bootstrapping mongodb test environment")
var err error
clientOpts := options.Client().ApplyURI("mongodb://localhost:27017")
client, err := mongo.Connect(context.TODO(), clientOpts)
Expect(err).ToNot(HaveOccurred())
client.Database("kubevela").Drop(context.TODO())
mongodbDriver, err = New(context.TODO(), datastore.Config{
URL: "mongodb://localhost:27017",
Database: "kubevela",
@@ -65,6 +71,8 @@ var _ = Describe("Test mongodb datastore driver", func() {
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
&model.Workflow{Name: "kubevela-app-workflow", AppPrimaryKey: "kubevela-app-2", Description: "this is workflow"},
&model.ApplicationTrigger{Name: "kubevela-app-trigger", AppPrimaryKey: "kubevela-app-2", Token: "token-test", Description: "this is demo 4"},
}
err := mongodbDriver.BatchAdd(context.TODO(), datas)
Expect(err).ToNot(HaveOccurred())
@@ -84,6 +92,12 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(err).Should(BeNil())
diff := cmp.Diff(app.Description, "default")
Expect(diff).Should(BeEmpty())
workflow := &model.Workflow{Name: "kubevela-app-workflow", AppPrimaryKey: "kubevela-app-2"}
err = mongodbDriver.Get(context.TODO(), workflow)
Expect(err).Should(BeNil())
diff = cmp.Diff(workflow.Description, "this is workflow")
Expect(diff).Should(BeEmpty())
})
It("Test put function", func() {
@@ -111,6 +125,14 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 4)
Expect(diff).Should(BeEmpty())
var workflow = model.Workflow{
AppPrimaryKey: "kubevela-app-2",
}
list, err = mongodbDriver.List(context.TODO(), &workflow, nil)
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 1)
Expect(diff).Should(BeEmpty())
})
It("Test list clusters with sort and fuzzy query", func() {
@@ -213,5 +235,13 @@ var _ = Describe("Test mongodb datastore driver", func() {
err = mongodbDriver.Delete(context.TODO(), &app)
equal := cmp.Equal(err, datastore.ErrRecordNotExist, cmpopts.EquateErrors())
Expect(equal).Should(BeTrue())
workflow := model.Workflow{Name: "kubevela-app-workflow", AppPrimaryKey: "kubevela-app-2", Description: "this is workflow"}
err = mongodbDriver.Delete(context.TODO(), &workflow)
Expect(err).ShouldNot(HaveOccurred())
trigger := model.ApplicationTrigger{Name: "kubevela-app-trigger", AppPrimaryKey: "kubevela-app-2", Token: "token-test", Description: "this is demo 4"}
err = mongodbDriver.Delete(context.TODO(), &trigger)
Expect(err).ShouldNot(HaveOccurred())
})
})

View File

@@ -324,6 +324,8 @@ const (
PayloadTypeACR = "acr"
// PayloadTypeHarbor is the payload type harbor
PayloadTypeHarbor = "harbor"
// PayloadTypeJFrog is the payload type jfrog
PayloadTypeJFrog = "jfrog"
// ComponentTypeWebservice is the component type webservice
ComponentTypeWebservice = "webservice"
@@ -336,6 +338,10 @@ const (
const (
// HarborEventTypePushArtifact is the event type PUSH_ARTIFACT
HarborEventTypePushArtifact = "PUSH_ARTIFACT"
// JFrogEventTypePush is push event type of jfrog webhook
JFrogEventTypePush = "pushed"
// JFrogDomainDocker is webhook domain of jfrog docker
JFrogDomainDocker = "docker"
)
// TableName return custom table name

View File

@@ -464,6 +464,24 @@ type DockerHubRepository struct {
Status string `json:"status"`
}
// HandleApplicationTriggerJFrogRequest application trigger JFrog webhook request
type HandleApplicationTriggerJFrogRequest struct {
Domain string `json:"domain"`
EventType string `json:"event_type"`
Data JFrogWebhookData `json:"data"`
}
// JFrogWebhookData is the data of JFrog webhook request
type JFrogWebhookData struct {
URL string
ImageName string `json:"image_name"`
Name string `json:"name"`
Path string `json:"path"`
RepoKey string `json:"repo_key"`
Digest string `json:"sha256"`
Tag string `json:"tag"`
}
// EnvBinding application env binding
type EnvBinding struct {
Name string `json:"name" validate:"checkname"`

View File

@@ -18,6 +18,7 @@ package usecase
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
@@ -25,6 +26,8 @@ import (
"sync"
"time"
k8stypes "k8s.io/apimachinery/pkg/types"
v1 "k8s.io/api/core/v1"
errors2 "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -39,6 +42,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/apply"
@@ -155,7 +159,9 @@ func (u *defaultAddonHandler) GetAddon(ctx context.Context, name string, registr
if addon == nil {
return nil, bcode.ErrAddonNotExist
}
addon.UISchema = renderDefaultUISchema(addon.APISchema)
addon.UISchema = renderAddonCustomUISchema(ctx, u.kubeClient, name, renderDefaultUISchema(addon.APISchema))
a, err := AddonImpl2AddonRes(addon)
if err != nil {
return nil, err
@@ -455,6 +461,29 @@ func convertAppStateToAddonPhase(state common2.ApplicationPhase) apis.AddonPhase
}
}
func renderAddonCustomUISchema(ctx context.Context, cli client.Client, addonName string, defaultSchema []*utils.UIParameter) []*utils.UIParameter {
var cm v1.ConfigMap
if err := cli.Get(ctx, k8stypes.NamespacedName{
Namespace: types.DefaultKubeVelaNS,
Name: fmt.Sprintf("addon-uischema-%s", addonName),
}, &cm); err != nil {
if !errors2.IsNotFound(err) {
log.Logger.Errorf("find uischema configmap from cluster failure %s", err.Error())
}
return defaultSchema
}
data, ok := cm.Data[types.UISchema]
if !ok {
return defaultSchema
}
schema := []*utils.UIParameter{}
if err := json.Unmarshal([]byte(data), &schema); err != nil {
log.Logger.Errorf("unmarshal ui schema failure %s", err.Error())
return defaultSchema
}
return patchSchema(defaultSchema, schema)
}
// ConvertAddonRegistryModel2AddonRegistryMeta will convert from model to AddonRegistry
func ConvertAddonRegistryModel2AddonRegistryMeta(r pkgaddon.Registry) apis.AddonRegistry {
return apis.AddonRegistry{

View File

@@ -0,0 +1,106 @@
/*
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 usecase
import (
"context"
"fmt"
"io/ioutil"
"github.com/oam-dev/kubevela/pkg/oam/util"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
)
var _ = Describe("addon usecase test", func() {
var ctx context.Context
BeforeEach(func() {
ctx = context.Background()
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: types.DefaultKubeVelaNS}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
})
It("Test render customize ui-schema", func() {
schemaData, err := ioutil.ReadFile("testdata/addon-uischema-test.yaml")
addonName := "test"
Expect(err).Should(BeNil())
jsonData, err := yaml.YAMLToJSON(schemaData)
Expect(err).Should(BeNil())
cm := v1.ConfigMap{
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "ConfigMap"},
ObjectMeta: metav1.ObjectMeta{Namespace: types.DefaultKubeVelaNS, Name: fmt.Sprintf("addon-uischema-%s", addonName)},
Data: map[string]string{
types.UISchema: string(jsonData),
}}
Expect(k8sClient.Create(ctx, &cm)).Should(BeNil())
defaultSchema := []*utils.UIParameter{
{
JSONKey: "version",
Sort: 3,
},
{
JSONKey: "domain",
Sort: 8,
},
}
res := renderAddonCustomUISchema(ctx, k8sClient, addonName, defaultSchema)
Expect(len(res)).Should(BeEquivalentTo(2))
for _, re := range res {
if re.JSONKey == "version" {
Expect(re.Validate.DefaultValue.(string)).Should(BeEquivalentTo("1.2.0-rc1"))
Expect(re.Sort).Should(BeEquivalentTo(1))
}
if re.JSONKey == "domain" {
Expect(re.Sort).Should(BeEquivalentTo(9))
}
}
})
It("Test render without ui-schema", func() {
addonName := "test-without-schema"
defaultSchema := []*utils.UIParameter{
{
JSONKey: "version",
Sort: 3,
},
{
JSONKey: "domain",
Sort: 8,
},
}
res := renderAddonCustomUISchema(ctx, k8sClient, addonName, defaultSchema)
Expect(len(res)).Should(BeEquivalentTo(2))
for _, re := range res {
if re.JSONKey == "version" {
Expect(re.Validate).Should(BeNil())
Expect(re.Sort).Should(BeEquivalentTo(3))
}
if re.JSONKey == "domain" {
Expect(re.Sort).Should(BeEquivalentTo(8))
}
}
})
})

View File

@@ -0,0 +1,18 @@
- jsonKey: version
validate:
defaultValue: 1.2.0-rc1
sort: 1
- jsonKey: dbType
label: DBType
validate:
defaultValue: kubeapi
sort: 3
- jsonKey: dbURL
label: DBURL
sort: 5
- jsonKey: database
sort: 7
validate:
defaultValue: kubevela
- jsonKey: domain
sort: 9

View File

@@ -62,6 +62,7 @@ func registerHandlers() {
new(acrHandlerImpl).install()
new(dockerHubHandlerImpl).install()
new(harborHandlerImpl).install()
new(jfrogHandlerImpl).install()
}
type webhookHandler interface {
@@ -160,6 +161,11 @@ func (c *webhookUsecaseImpl) HandleApplicationWebhook(ctx context.Context, token
if err != nil {
return nil, err
}
case model.PayloadTypeJFrog:
handler, err = c.newJFrogHandler(req)
if err != nil {
return nil, err
}
default:
return nil, bcode.ErrInvalidWebhookPayloadType
}
@@ -423,3 +429,74 @@ func (c *harborHandlerImpl) handle(ctx context.Context, webhookTrigger *model.Ap
},
})
}
type jfrogHandlerImpl struct {
req apisv1.HandleApplicationTriggerJFrogRequest
w *webhookUsecaseImpl
}
func (c *webhookUsecaseImpl) newJFrogHandler(req *restful.Request) (webhookHandler, error) {
var jfrogReq apisv1.HandleApplicationTriggerJFrogRequest
if err := req.ReadEntity(&jfrogReq); err != nil {
return nil, bcode.ErrInvalidWebhookPayloadBody
}
if jfrogReq.Domain != model.JFrogDomainDocker || jfrogReq.EventType != model.JFrogEventTypePush {
return nil, bcode.ErrInvalidWebhookPayloadBody
}
// jfrog should use request header to give URL, it is not exist in request body
jfrogReq.Data.URL = req.HeaderParameter("X-JFrogURL")
return &jfrogHandlerImpl{
req: jfrogReq,
w: c,
}, nil
}
func (j *jfrogHandlerImpl) handle(ctx context.Context, webhookTrigger *model.ApplicationTrigger, app *model.Application) (interface{}, error) {
jfrogReq := j.req
comp := &model.ApplicationComponent{
AppPrimaryKey: webhookTrigger.AppPrimaryKey,
}
comps, err := j.w.ds.List(ctx, comp, &datastore.ListOptions{})
if err != nil {
return nil, err
}
if len(comps) == 0 {
return nil, bcode.ErrApplicationComponetNotExist
}
// use the first component as the target component
component := comps[0].(*model.ApplicationComponent)
image := fmt.Sprintf("%s/%s:%s", jfrogReq.Data.RepoKey, jfrogReq.Data.ImageName, jfrogReq.Data.Tag)
if jfrogReq.Data.URL != "" {
image = fmt.Sprintf("%s/%s", jfrogReq.Data.URL, image)
}
if err := j.w.patchComponentProperties(ctx, component, &runtime.RawExtension{
Raw: []byte(fmt.Sprintf(`{"image": "%s"}`, image)),
}); err != nil {
return nil, err
}
return j.w.applicationUsecase.Deploy(ctx, app, apisv1.ApplicationDeployRequest{
WorkflowName: webhookTrigger.WorkflowName,
Note: "triggered by webhook jfrog",
TriggerType: apisv1.TriggerTypeWebhook,
Force: true,
ImageInfo: &model.ImageInfo{
Type: model.PayloadTypeHarbor,
Resource: &model.ImageResource{
Digest: jfrogReq.Data.Digest,
Tag: jfrogReq.Data.Tag,
URL: image,
},
Repository: &model.ImageRepository{
Name: jfrogReq.Data.ImageName,
Namespace: jfrogReq.Data.RepoKey,
FullName: fmt.Sprintf("%s/%s", jfrogReq.Data.RepoKey, jfrogReq.Data.ImageName),
},
},
})
}
func (j *jfrogHandlerImpl) install() {
WebhookHandlers = append(WebhookHandlers, model.PayloadTypeJFrog)
}

View File

@@ -256,5 +256,45 @@ var _ = Describe("Test application usecase function", func() {
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("docker.io/test-namespace/test-repo:test-tag"))
By("Test HandleApplicationWebhook function with jfrog payload without header of X-JFrogURL")
jfrogTrigger, err := appUsecase.CreateApplicationTrigger(context.TODO(), appModel, apisv1.CreateApplicationTriggerRequest{
Name: "test-jfrog",
PayloadType: "jfrog",
Type: "webhook",
ComponentName: "component-name-webhook",
})
Expect(err).Should(BeNil())
jfrogBody := apisv1.HandleApplicationTriggerJFrogRequest{
Domain: "docker",
EventType: "pushed",
Data: apisv1.JFrogWebhookData{
ImageName: "test-image",
RepoKey: "test-repo",
Digest: "test-digest",
Tag: "test-tag",
},
}
body, err = json.Marshal(jfrogBody)
Expect(err).Should(BeNil())
httpreq, err = http.NewRequest("post", "/", bytes.NewBuffer(body))
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
Expect(err).Should(BeNil())
_, err = webhookUsecase.HandleApplicationWebhook(context.TODO(), jfrogTrigger.Token, restful.NewRequest(httpreq))
Expect(err).Should(BeNil())
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("test-repo/test-image:test-tag"))
By("Test HandleApplicationWebhook function with jfrog payload with header of X-JFrogURL")
httpreq, err = http.NewRequest("post", "/", bytes.NewBuffer(body))
Expect(err).Should(BeNil())
httpreq.Header.Add(restful.HEADER_ContentType, "application/json")
httpreq.Header.Add("X-JFrogURL", "test-addr")
_, err = webhookUsecase.HandleApplicationWebhook(context.TODO(), jfrogTrigger.Token, restful.NewRequest(httpreq))
Expect(err).Should(BeNil())
comp, err = appUsecase.GetApplicationComponent(context.TODO(), appModel, "component-name-webhook")
Expect(err).Should(BeNil())
Expect((*comp.Properties)["image"]).Should(Equal("test-addr/test-repo/test-image:test-tag"))
})
})

View File

@@ -35,7 +35,7 @@ func (p *Parser) ValidateCUESchematicAppfile(a *Appfile) error {
}
pCtx, err := newValidationProcessContext(wl, a.Name, a.AppRevisionName, a.Namespace)
if err != nil {
return errors.WithMessage(err, "cannot create validationg process context")
return errors.WithMessagef(err, "cannot create the validation process context of app=%s in namespace=%s", a.Name, a.Namespace)
}
for _, tr := range wl.Traits {
if tr.CapabilityCategory != types.CUECategory {

View File

@@ -80,4 +80,7 @@ type Args struct {
// OAMSpecVer is the oam spec version controller want to setup
OAMSpecVer string
// EnableCompatibility indicates that will change some functions of controller to adapt to multiple platforms, such as asi.
EnableCompatibility bool
}

View File

@@ -81,12 +81,17 @@ var (
// Reconciler reconciles an Application object
type Reconciler struct {
client.Client
dm discoverymapper.DiscoveryMapper
pd *packages.PackageDiscover
Scheme *runtime.Scheme
Recorder event.Recorder
dm discoverymapper.DiscoveryMapper
pd *packages.PackageDiscover
Scheme *runtime.Scheme
Recorder event.Recorder
options
}
type options struct {
appRevisionLimit int
concurrentReconciles int
disableStatusUpdate bool
}
// +kubebuilder:rbac:groups=core.oam.dev,resources=applications,verbs=get;list;watch;create;update;patch;delete
@@ -121,6 +126,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
if annotations := app.GetAnnotations(); annotations == nil || annotations[oam.AnnotationKubeVelaVersion] == "" {
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationKubeVelaVersion, version.VelaVersion)
}
logCtx.AddTag("publish_version", app.GetAnnotations()[oam.AnnotationKubeVelaVersion])
appParser := appfile.NewApplicationParser(r.Client, r.dm, r.pd)
handler, err := NewAppHandler(logCtx, r, app, appParser)
if err != nil {
@@ -238,6 +245,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
if status := app.Status.Workflow; status != nil && status.Terminated {
return r.result(nil).ret()
}
case common.WorkflowStateSkipping:
logCtx.Info("Skip this reconcile")
return ctrl.Result{}, nil
}
var phase = common.ApplicationRunning
@@ -286,7 +296,7 @@ func (r *Reconciler) gcResourceTrackers(logCtx monitorContext.Context, handler *
return r.endWithNegativeCondition(logCtx, handler.app, condition.ReconcileError(err), phase)
}
if !finished {
logCtx.Info("GarbageCollecting resourcetrackers")
logCtx.Info("GarbageCollecting resourcetrackers unfinished")
cond := condition.Deleting()
if len(waiting) > 0 {
cond.Message = fmt.Sprintf("Waiting for %s to delete. (At least %d resources are deleting.)", waiting[0].DisplayName(), len(waiting))
@@ -382,13 +392,31 @@ func (r *Reconciler) endWithNegativeCondition(ctx context.Context, app *v1beta1.
func (r *Reconciler) patchStatus(ctx context.Context, app *v1beta1.Application, phase common.ApplicationPhase) error {
app.Status.Phase = phase
updateObservedGeneration(app)
return r.Status().Patch(ctx, app, client.Merge)
if err := r.Status().Patch(ctx, app, client.Merge); err != nil {
// set to -1 to re-run workflow if status is failed to patch
workflow.StepStatusCache.Store(fmt.Sprintf("%s-%s", app.Name, app.Namespace), -1)
return err
}
return nil
}
func (r *Reconciler) updateStatus(ctx context.Context, app *v1beta1.Application, phase common.ApplicationPhase) error {
app.Status.Phase = phase
updateObservedGeneration(app)
return r.Status().Update(ctx, app)
if !r.disableStatusUpdate {
return r.Status().Update(ctx, app)
}
obj, err := app.Unstructured()
if err != nil {
return err
}
if err := r.Status().Update(ctx, obj); err != nil {
// set to -1 to re-run workflow if status is failed to update
workflow.StepStatusCache.Store(fmt.Sprintf("%s-%s", app.Name, app.Namespace), -1)
return err
}
return nil
}
func (r *Reconciler) doWorkflowFinish(app *v1beta1.Application, wf workflow.Workflow) error {
@@ -501,13 +529,12 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
// Setup adds a controller that reconciles AppRollout.
func Setup(mgr ctrl.Manager, args core.Args) error {
reconciler := Reconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: event.NewAPIRecorder(mgr.GetEventRecorderFor("Application")),
dm: args.DiscoveryMapper,
pd: args.PackageDiscover,
appRevisionLimit: args.AppRevisionLimit,
concurrentReconciles: args.ConcurrentReconciles,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: event.NewAPIRecorder(mgr.GetEventRecorderFor("Application")),
dm: args.DiscoveryMapper,
pd: args.PackageDiscover,
options: parseOptions(args),
}
return reconciler.SetupWithManager(mgr)
}
@@ -557,3 +584,11 @@ func timeReconcile(app *v1beta1.Application) func() {
metrics.ApplicationReconcileTimeHistogram.WithLabelValues(beginPhase, string(app.Status.Phase)).Observe(v)
}
}
func parseOptions(args core.Args) options {
return options{
disableStatusUpdate: args.EnableCompatibility,
appRevisionLimit: args.AppRevisionLimit,
concurrentReconciles: args.ConcurrentReconciles,
}
}

View File

@@ -311,7 +311,7 @@ func ComputeAppRevisionHash(appRevision *v1beta1.ApplicationRevision) (string, e
}
appRevisionHash.ComponentDefinitionHash[key] = hash
}
for key, td := range appRevision.Spec.TraitDefinitions {
for key, td := range filterSkipAffectAppRevTraitDefinitions(appRevision.Spec.TraitDefinitions) {
hash, err := utils.ComputeSpecHash(&td.Spec)
if err != nil {
return "", err
@@ -376,7 +376,11 @@ func (h *AppHandler) currentAppRevIsNew(ctx context.Context) (bool, bool, error)
// diff the latest revision first
if h.app.Status.LatestRevision.RevisionHash == h.currentRevHash && DeepEqualRevision(h.latestAppRev, h.currentAppRev) {
appSpec := h.currentAppRev.Spec.Application.Spec
traitDef := h.currentAppRev.Spec.TraitDefinitions
h.currentAppRev = h.latestAppRev.DeepCopy()
h.currentAppRev.Spec.Application.Spec = appSpec
h.currentAppRev.Spec.TraitDefinitions = traitDef
return false, false, nil
}
@@ -409,7 +413,9 @@ func DeepEqualRevision(old, new *v1beta1.ApplicationRevision) bool {
if len(old.Spec.WorkloadDefinitions) != len(new.Spec.WorkloadDefinitions) {
return false
}
if len(old.Spec.TraitDefinitions) != len(new.Spec.TraitDefinitions) {
oldTraitDefinitions := filterSkipAffectAppRevTraitDefinitions(old.Spec.TraitDefinitions)
newTraitDefinitions := filterSkipAffectAppRevTraitDefinitions(new.Spec.TraitDefinitions)
if len(oldTraitDefinitions) != len(newTraitDefinitions) {
return false
}
if len(old.Spec.ComponentDefinitions) != len(new.Spec.ComponentDefinitions) {
@@ -428,8 +434,8 @@ func DeepEqualRevision(old, new *v1beta1.ApplicationRevision) bool {
return false
}
}
for key, td := range new.Spec.TraitDefinitions {
if !apiequality.Semantic.DeepEqual(old.Spec.TraitDefinitions[key].Spec, td.Spec) {
for key, td := range newTraitDefinitions {
if !apiequality.Semantic.DeepEqual(oldTraitDefinitions[key].Spec, td.Spec) {
return false
}
}
@@ -820,6 +826,17 @@ func filterSkipAffectAppRevTrait(appSpec v1beta1.ApplicationSpec, tds map[string
return *res
}
// before computing hash or deepEqual, filterSkipAffectAppRevTraitDefinitions filter can ignore `SkipAffectRevision` trait definition from appRev
func filterSkipAffectAppRevTraitDefinitions(tds map[string]v1beta1.TraitDefinition) map[string]v1beta1.TraitDefinition {
res := make(map[string]v1beta1.TraitDefinition)
for key, td := range tds {
if !td.Spec.SkipRevisionAffect {
res[key] = td
}
}
return res
}
type historiesByRevision []v1beta1.ApplicationRevision
func (h historiesByRevision) Len() int { return len(h) }
@@ -837,7 +854,7 @@ func cleanUpWorkflowComponentRevision(ctx context.Context, h *AppHandler) error
}
// collect component revision in use
compRevisionInUse := map[string]map[string]struct{}{}
for _, resource := range h.app.Status.AppliedResources {
for i, resource := range h.app.Status.AppliedResources {
compName := resource.Name
ns := resource.Namespace
r := &unstructured.Unstructured{}
@@ -846,7 +863,7 @@ func cleanUpWorkflowComponentRevision(ctx context.Context, h *AppHandler) error
err := h.r.Get(_ctx, ktypes.NamespacedName{Name: compName, Namespace: ns}, r)
notFound := apierrors.IsNotFound(err)
if err != nil && !notFound {
return err
return errors.WithMessagef(err, "get applied resource index=%d", i)
}
if compRevisionInUse[compName] == nil {
compRevisionInUse[compName] = map[string]struct{}{}

View File

@@ -151,7 +151,6 @@ var _ = Describe("test generate revision ", func() {
appRevision1.Spec.ComponentDefinitions[cd.Name] = cd
appRevision1.Spec.WorkloadDefinitions[wd.Name] = wd
appRevision1.Spec.TraitDefinitions[td.Name] = td
appRevision1.Spec.TraitDefinitions[rolloutTd.Name] = rolloutTd
appRevision1.Spec.ScopeDefinitions[sd.Name] = sd
appRevision2 = *appRevision1.DeepCopy()
@@ -168,6 +167,10 @@ var _ = Describe("test generate revision ", func() {
Expect(k8sClient.Delete(context.TODO(), &ns)).Should(Succeed())
})
verifyDeepEqualRevision := func() {
Expect(DeepEqualRevision(&appRevision1, &appRevision2)).Should(BeTrue())
}
verifyEqual := func() {
appHash1, err := ComputeAppRevisionHash(&appRevision1)
Expect(err).Should(Succeed())
@@ -228,7 +231,7 @@ var _ = Describe("test generate revision ", func() {
verifyNotEqual()
})
It("Test appliction contain a SkipAppRevision tait will have same hash", func() {
It("Test appliction contain a SkipAppRevision tait will have same hash and revision will equal", func() {
rolloutTrait := common.ApplicationTrait{
Type: "rollout",
Properties: &runtime.RawExtension{
@@ -236,7 +239,10 @@ var _ = Describe("test generate revision ", func() {
},
}
appRevision2.Spec.Application.Spec.Components[0].Traits = append(appRevision2.Spec.Application.Spec.Components[0].Traits, rolloutTrait)
// appRevision will have no traitDefinition of rollout
appRevision2.Spec.TraitDefinitions[rolloutTd.Name] = rolloutTd
verifyEqual()
verifyDeepEqualRevision()
})
It("Test apply success for none rollout case", func() {

View File

@@ -137,13 +137,14 @@ var _ = BeforeSuite(func(done Done) {
appParser = appfile.NewApplicationParser(k8sClient, dm, pd)
reconciler = &Reconciler{
Client: k8sClient,
Scheme: testScheme,
dm: dm,
pd: pd,
Recorder: event.NewAPIRecorder(recorder),
appRevisionLimit: appRevisionLimit,
Client: k8sClient,
Scheme: testScheme,
dm: dm,
pd: pd,
Recorder: event.NewAPIRecorder(recorder),
}
reconciler.appRevisionLimit = appRevisionLimit
// setup the controller manager since we need the component handler to run in the background
mgr, err = ctrl.NewManager(cfg, ctrl.Options{
Scheme: testScheme,

View File

@@ -221,10 +221,8 @@ func (h *handler) handleRolloutModified() {
// setWorkloadBaseInfo set base workload base info, which is such as workload name and component revision label
func (h *handler) setWorkloadBaseInfo() {
if len(h.targetWorkload.GetNamespace()) == 0 {
h.targetWorkload.SetNamespace(h.rollout.Namespace)
}
if h.sourceWorkload != nil && len(h.sourceWorkload.GetNamespace()) == 0 {
h.targetWorkload.SetNamespace(h.rollout.Namespace)
if h.sourceWorkload != nil {
h.sourceWorkload.SetNamespace(h.rollout.Namespace)
}

View File

@@ -205,12 +205,17 @@ func GetTerraformConfigurationFromRemote(name, remoteURL, remotePath string) (st
return "", err
}
tfPath := filepath.Join(tmpPath, remotePath, "main.tf")
tfPath := filepath.Join(tmpPath, remotePath, "variables.tf")
if _, err := os.Stat(tfPath); err != nil {
tfPath = filepath.Join(tmpPath, remotePath, "main.tf")
if _, err := os.Stat(tfPath); err != nil {
return "", errors.Wrap(err, "failed to find main.tf or variables.tf in Terraform configurations of the remote repository")
}
}
conf, err := ioutil.ReadFile(filepath.Clean(tfPath))
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to read Terraform configuration")
}
if err := os.RemoveAll(tmpPath); err != nil {
return "", err
}

View File

@@ -382,20 +382,28 @@ func TestGetTerraformConfigurationFromRemote(t *testing.T) {
// panic: permission denied
type want struct {
config string
err error
errMsg string
}
type args struct {
name string
url string
path string
data []byte
variableFile string
// mockWorkingPath will create `/tmp/terraform`
mockWorkingPath bool
}
cases := map[string]struct {
name string
url string
path string
data []byte
args args
want want
}{
"valid": {
name: "valid",
url: "https://github.com/kubevela-contrib/terraform-modules.git",
path: "",
data: []byte(`
args: args{
name: "valid",
url: "https://github.com/kubevela-contrib/terraform-modules.git",
path: "",
data: []byte(`
variable "aaa" {
type = list(object({
type = string
@@ -404,6 +412,8 @@ variable "aaa" {
}))
default = []
}`),
variableFile: "main.tf",
},
want: want{
config: `
variable "aaa" {
@@ -414,28 +424,51 @@ variable "aaa" {
}))
default = []
}`,
err: nil,
},
},
"working path exists": {
args: args{
variableFile: "main.tf",
mockWorkingPath: true,
},
want: want{
errMsg: "failed to remove the directory",
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
if tc.args.mockWorkingPath {
err := os.MkdirAll("./tmp/terraform", 0755)
assert.NilError(t, err)
defer os.RemoveAll("./tmp/terraform")
patch1 := ApplyFunc(os.Remove, func(_ string) error {
return errors.New("failed")
})
defer patch1.Reset()
patch2 := ApplyFunc(os.Open, func(_ string) (*os.File, error) {
return nil, errors.New("failed")
})
defer patch2.Reset()
}
patch := ApplyFunc(git.PlainCloneContext, func(ctx context.Context, path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) {
tmpPath := filepath.Join("./tmp/terraform", tc.name)
tmpPath := filepath.Join("./tmp/terraform", tc.args.name)
err := os.MkdirAll(tmpPath, os.ModePerm)
assert.NilError(t, err)
err = ioutil.WriteFile(filepath.Clean(filepath.Join(tmpPath, "main.tf")), tc.data, 0644)
err = ioutil.WriteFile(filepath.Clean(filepath.Join(tmpPath, tc.args.variableFile)), tc.args.data, 0644)
assert.NilError(t, err)
return nil, nil
})
defer patch.Reset()
conf, err := GetTerraformConfigurationFromRemote(tc.name, tc.url, tc.path)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nGetTerraformConfigurationFromRemote(...): -want error, +got error:\n%s", name, diff)
}
if tc.want.err == nil {
conf, err := GetTerraformConfigurationFromRemote(tc.args.name, tc.args.url, tc.args.path)
if tc.want.errMsg != "" {
if err != nil && !strings.Contains(err.Error(), tc.want.errMsg) {
t.Errorf("\n%s\nGetTerraformConfigurationFromRemote(...): -want error %v, +got error:%s", name, err, tc.want.errMsg)
}
} else {
assert.Equal(t, tc.want.config, conf)
}
})

View File

@@ -22,17 +22,34 @@ import (
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/build"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/cue/model"
"github.com/oam-dev/kubevela/pkg/cue/packages"
)
// GetParameters get parameter from cue template
func GetParameters(templateStr string) ([]types.Parameter, error) {
r := cue.Runtime{}
template, err := r.Compile("", templateStr+BaseTemplate)
if err != nil {
return nil, err
func GetParameters(templateStr string, pd *packages.PackageDiscover) ([]types.Parameter, error) {
var template *cue.Instance
var err error
if pd != nil {
bi := build.NewContext().NewInstance("", nil)
err := bi.AddFile("-", templateStr+BaseTemplate)
if err != nil {
return nil, err
}
template, err = pd.ImportPackagesAndBuildInstance(bi)
if err != nil {
return nil, err
}
} else {
r := cue.Runtime{}
template, err = r.Compile("", templateStr+BaseTemplate)
if err != nil {
return nil, err
}
}
tempStruct, err := template.Value().Struct()
if err != nil {

View File

@@ -28,7 +28,7 @@ import (
func TestGetParameter(t *testing.T) {
data, _ := os.ReadFile("testdata/workloads/metrics.cue")
params, err := GetParameters(string(data))
params, err := GetParameters(string(data), nil)
assert.NoError(t, err)
assert.Equal(t, params, []types.Parameter{
{Name: "format", Required: false, Default: "prometheus", Usage: "format of the metrics, " +
@@ -38,7 +38,7 @@ func TestGetParameter(t *testing.T) {
{Name: "selector", Required: false, Usage: "the label selector for the pods, default is the workload labels", Type: cue.StructKind},
})
data, _ = os.ReadFile("testdata/workloads/deployment.cue")
params, err = GetParameters(string(data))
params, err = GetParameters(string(data), nil)
assert.NoError(t, err)
assert.Equal(t, []types.Parameter{
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
@@ -50,7 +50,7 @@ func TestGetParameter(t *testing.T) {
params)
data, _ = os.ReadFile("testdata/workloads/test-param.cue")
params, err = GetParameters(string(data))
params, err = GetParameters(string(data), nil)
assert.NoError(t, err)
assert.Equal(t, []types.Parameter{
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
@@ -61,13 +61,13 @@ func TestGetParameter(t *testing.T) {
{Name: "fval", Default: 64.3, Type: cue.FloatKind},
{Name: "nval", Default: float64(0), Required: true, Type: cue.NumberKind}}, params)
data, _ = os.ReadFile("testdata/workloads/empty.cue")
params, err = GetParameters(string(data))
params, err = GetParameters(string(data), nil)
assert.NoError(t, err)
var exp []types.Parameter
assert.Equal(t, exp, params)
data, _ = os.ReadFile("testdata/workloads/webservice.cue") // test cue parameter with "// +ignore" annotation
params, err = GetParameters(string(data)) // Only test for func RetrieveComments
params, err = GetParameters(string(data), nil) // Only test for func RetrieveComments
assert.NoError(t, err)
var flag bool
for _, para := range params {

View File

@@ -66,6 +66,11 @@ func MergeComponent(base *common.ApplicationComponent, patch *v1alpha1.EnvCompon
return nil, errors.Wrapf(err, "failed to merge component properties")
}
// merge component external revision
if patch.ExternalRevision != "" {
newComponent.ExternalRevision = patch.ExternalRevision
}
// prepare traits
traitMaps := map[string]*common.ApplicationTrait{}
var traitOrders []string
@@ -141,8 +146,17 @@ func PatchApplication(base *v1beta1.Application, patch *v1alpha1.EnvPatch, selec
var errs errors2.ErrorList
var err error
for _, comp := range patch.Components {
if baseComp, exists := compMaps[comp.Name]; exists {
if baseComp.Type != comp.Type {
if comp.Name == "" {
for compName, baseComp := range compMaps {
if comp.Type == "" || comp.Type == baseComp.Type {
compMaps[compName], err = MergeComponent(baseComp, comp.DeepCopy())
if err != nil {
errs = append(errs, errors.Wrapf(err, "failed to merge component %s", compName))
}
}
}
} else if baseComp, exists := compMaps[comp.Name]; exists {
if baseComp.Type != comp.Type && comp.Type != "" {
compMaps[comp.Name] = comp.ToApplicationComponent()
} else {
compMaps[comp.Name], err = MergeComponent(baseComp, comp.DeepCopy())

View File

@@ -19,7 +19,7 @@ package envbinding
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
@@ -29,111 +29,68 @@ import (
)
func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) {
testcases := []struct {
testCases := map[string]struct {
baseApp *v1beta1.Application
envName string
envPatch v1alpha1.EnvPatch
expectedApp *v1beta1.Application
selector *v1alpha1.EnvSelector
}{{
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []v1alpha1.EnvTraitPatch{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "newTestsvc.example.com",
}),
}},
}},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
}{
"normal-test": {
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
"port": 8000,
}),
Traits: []common.ApplicationTrait{{
Traits: []v1alpha1.EnvTraitPatch{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "newTestsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}},
}},
},
},
}, {
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-server",
Type: "webservice",
Traits: []v1alpha1.EnvTraitPatch{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}, {
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
"cmd": []string{"sleep", "1000"},
}),
Traits: []v1alpha1.EnvTraitPatch{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
"port": 8000,
}),
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "newTestsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}},
}},
},
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
},
"add-component": {
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
"port": 8000,
}),
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "testsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}, {
Traits: []v1alpha1.EnvTraitPatch{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
@@ -146,7 +103,7 @@ func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) {
"image": "busybox",
"cmd": []string{"sleep", "1000"},
}),
Traits: []common.ApplicationTrait{{
Traits: []v1alpha1.EnvTraitPatch{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
@@ -154,67 +111,92 @@ func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) {
}},
}},
},
},
}, {
// Test Disable Trait
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-server",
Type: "webservice",
Traits: []v1alpha1.EnvTraitPatch{{
Type: "ingress-1-20",
Disable: true,
}},
}},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
"port": 8000,
}),
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "testsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}, {
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}, {
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
"cmd": []string{"sleep", "1000"},
}),
Traits: []common.ApplicationTrait{{
Type: "labels",
Properties: util.Object2RawExtension(map[string]interface{}{
"test": "label",
}),
}},
}},
},
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
},
"disable-trait": {
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
"port": 8000,
}),
Traits: []common.ApplicationTrait{},
Traits: []v1alpha1.EnvTraitPatch{{
Type: "ingress-1-20",
Disable: true,
}},
}},
},
},
}, {
// Test component selector
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
}},
},
selector: &v1alpha1.EnvSelector{
Components: []string{"new-server"},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
"port": 8000,
}),
Traits: []common.ApplicationTrait{},
}},
},
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
},
"component-selector": {
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
@@ -222,41 +204,339 @@ func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) {
}),
}},
},
},
}, {
// Test empty component selector
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
}},
},
selector: &v1alpha1.EnvSelector{
Components: []string{},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
selector: &v1alpha1.EnvSelector{
Components: []string{"new-server"},
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
}},
},
},
},
}}
"empty-component-selector": {
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "new-server",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
}},
},
selector: &v1alpha1.EnvSelector{
Components: []string{},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{},
},
},
},
"patch-external-revision": {
baseApp: baseApp,
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-server",
Type: "webservice",
ExternalRevision: "external-rev",
}},
},
expectedApp: &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1beta1",
Kind: "Application",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
"port": 8000,
}),
ExternalRevision: "external-rev",
Traits: []common.ApplicationTrait{{
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "testsvc.example.com",
"http": map[string]interface{}{
"/": 8000,
},
}),
}},
}},
},
},
},
"patch-all-comp": {
baseApp: &v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a.example.com",
}),
}, {
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a-1-20.example.com",
}),
}},
}, {
Name: "express-worker",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
}},
},
},
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "",
Type: "",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []v1alpha1.EnvTraitPatch{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "b.example.com",
}),
}},
}},
},
expectedApp: &v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "b.example.com",
}),
}, {
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a-1-20.example.com",
}),
}},
}, {
Name: "express-worker",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "b.example.com",
}),
}},
}},
},
},
},
"patch-type-comp": {
baseApp: &v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a.example.com",
}),
}, {
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a-1-20.example.com",
}),
}},
}, {
Name: "express-worker",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
}},
},
},
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []v1alpha1.EnvTraitPatch{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "b.example.com",
}),
}},
}},
},
expectedApp: &v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "b.example.com",
}),
}, {
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a-1-20.example.com",
}),
}},
}, {
Name: "express-worker",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
}},
},
},
},
"patch-without-type-specified": {
baseApp: &v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a.example.com",
}),
}, {
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a-1-20.example.com",
}),
}},
}, {
Name: "express-worker",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
}},
},
},
envName: "prod",
envPatch: v1alpha1.EnvPatch{
Components: []v1alpha1.EnvComponentPatch{{
Name: "express-worker",
Type: "",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []v1alpha1.EnvTraitPatch{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "b.example.com",
}),
}},
}},
},
expectedApp: &v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "express-server",
Type: "webservice",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "crccheck/hello-world",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a.example.com",
}),
}, {
Type: "ingress-1-20",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "a-1-20.example.com",
}),
}},
}, {
Name: "express-worker",
Type: "worker",
Properties: util.Object2RawExtension(map[string]interface{}{
"image": "busybox",
}),
Traits: []common.ApplicationTrait{{
Type: "ingress",
Properties: util.Object2RawExtension(map[string]interface{}{
"domain": "b.example.com",
}),
}},
}},
},
},
},
}
for _, testcase := range testcases {
app, err := PatchApplication(testcase.baseApp, &testcase.envPatch, testcase.selector)
assert.NoError(t, err)
assert.Equal(t, testcase.expectedApp, app)
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
app, err := PatchApplication(tc.baseApp, &tc.envPatch, tc.selector)
r := require.New(t)
r.NoError(err)
r.Equal(tc.expectedApp, app)
})
}
}

View File

@@ -15,14 +15,15 @@
}
#Component: {
name: string
type: string
name?: string
type?: string
properties?: {...}
traits?: [...{
type: string
disable?: bool
properties: {...}
}]
externalRevision?: string
}
#ReadPlacementDecisions: {

View File

@@ -30,6 +30,7 @@ import (
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/format"
"cuelang.org/go/encoding/openapi"
"github.com/AlecAivazis/survey/v2"
@@ -62,6 +63,7 @@ import (
"github.com/oam-dev/kubevela/apis/types"
velacue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/model"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/oam"
)
@@ -146,11 +148,26 @@ func HTTPGet(ctx context.Context, url string) ([]byte, error) {
}
// GetCUEParameterValue converts definitions to cue format
func GetCUEParameterValue(cueStr string) (cue.Value, error) {
r := cue.Runtime{}
template, err := r.Compile("", cueStr+velacue.BaseTemplate)
if err != nil {
return cue.Value{}, err
func GetCUEParameterValue(cueStr string, pd *packages.PackageDiscover) (cue.Value, error) {
var template *cue.Instance
var err error
if pd != nil {
bi := build.NewContext().NewInstance("", nil)
err := bi.AddFile("-", cueStr+velacue.BaseTemplate)
if err != nil {
return cue.Value{}, err
}
template, err = pd.ImportPackagesAndBuildInstance(bi)
if err != nil {
return cue.Value{}, err
}
} else {
r := cue.Runtime{}
template, err = r.Compile("", cueStr+velacue.BaseTemplate)
if err != nil {
return cue.Value{}, err
}
}
tempStruct, err := template.Value().Struct()
if err != nil {

View File

@@ -127,7 +127,7 @@ output: {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
_, err := GetCUEParameterValue(tc.cueStr)
_, err := GetCUEParameterValue(tc.cueStr, nil)
if tc.want.err != nil {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nGenOpenAPIFromFile(...): -want error, +got error:\n%s", tc.reason, diff)
@@ -162,7 +162,7 @@ name
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
_, err := GetCUEParameterValue(tc.cueStr)
_, err := GetCUEParameterValue(tc.cueStr, nil)
if diff := cmp.Diff(tc.want.errMsg, err.Error(), test.EquateConditions()); diff != "" {
t.Errorf("\n%s\nGenOpenAPIFromFile(...): -want error, +got error:\n%s", tc.reason, diff)
}

View File

@@ -85,11 +85,24 @@ func (c ViewContext) GetMutableValue(paths ...string) string {
func (c ViewContext) SetMutableValue(data string, paths ...string) {
}
// IncreaseMutableCountValue increase mutable count in workflow context.
func (c ViewContext) IncreaseMutableCountValue(paths ...string) int {
// IncreaseCountValueInMemory increase count in workflow context memory store.
func (c ViewContext) IncreaseCountValueInMemory(paths ...string) int {
return 0
}
// SetValueInMemory set data in workflow context memory store.
func (c ViewContext) SetValueInMemory(data interface{}, paths ...string) {
}
// GetValueInMemory get data in workflow context memory store.
func (c ViewContext) GetValueInMemory(paths ...string) (interface{}, bool) {
return "", true
}
// DeleteValueInMemory delete data in workflow context memory store.
func (c ViewContext) DeleteValueInMemory(paths ...string) {
}
// DeleteMutableValue delete mutable data in workflow context.
func (c ViewContext) DeleteMutableValue(paths ...string) {
}

View File

@@ -20,8 +20,8 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"
"time"
"cuelang.org/go/cue"
@@ -48,13 +48,18 @@ const (
AnnotationStartTimestamp = "vela.io/startTime"
)
var (
workflowMemoryCache sync.Map
)
// WorkflowContext is workflow context.
type WorkflowContext struct {
cli client.Client
store *corev1.ConfigMap
components map[string]*ComponentManifest
vars *value.Value
modified bool
cli client.Client
store *corev1.ConfigMap
memoryStore *sync.Map
components map[string]*ComponentManifest
vars *value.Value
modified bool
}
// GetComponent Get ComponentManifest from workflow context.
@@ -105,7 +110,7 @@ func (wf *WorkflowContext) SetVar(v *value.Value, paths ...string) error {
return nil
}
// GetStore get configmap of workflow context.
// GetStore get store of workflow context.
func (wf *WorkflowContext) GetStore() *corev1.ConfigMap {
return wf.store
}
@@ -121,23 +126,6 @@ func (wf *WorkflowContext) SetMutableValue(data string, paths ...string) {
wf.modified = true
}
// IncreaseMutableCountValue increase mutable count in workflow context.
func (wf *WorkflowContext) IncreaseMutableCountValue(paths ...string) int {
c := wf.GetMutableValue(paths...)
if c == "" {
wf.SetMutableValue("0", paths...)
return 0
}
count, err := strconv.Atoi(c)
if err != nil {
wf.SetMutableValue("0", paths...)
return 0
}
count++
wf.SetMutableValue(strconv.Itoa(count), paths...)
return count
}
// DeleteMutableValue delete mutable data in workflow context.
func (wf *WorkflowContext) DeleteMutableValue(paths ...string) {
key := strings.Join(paths, ".")
@@ -147,6 +135,39 @@ func (wf *WorkflowContext) DeleteMutableValue(paths ...string) {
}
}
// IncreaseCountValueInMemory increase count in workflow context memory store.
func (wf *WorkflowContext) IncreaseCountValueInMemory(paths ...string) int {
key := strings.Join(paths, ".")
c, ok := wf.memoryStore.Load(key)
if !ok {
wf.memoryStore.Store(key, 0)
return 0
}
count, ok := c.(int)
if !ok {
wf.memoryStore.Store(key, 0)
return 0
}
count++
wf.memoryStore.Store(key, count)
return count
}
// SetValueInMemory set data in workflow context memory store.
func (wf *WorkflowContext) SetValueInMemory(data interface{}, paths ...string) {
wf.memoryStore.Store(strings.Join(paths, "."), data)
}
// GetValueInMemory get data in workflow context memory store.
func (wf *WorkflowContext) GetValueInMemory(paths ...string) (interface{}, bool) {
return wf.memoryStore.Load(strings.Join(paths, "."))
}
// DeleteValueInMemory delete data in workflow context memory store.
func (wf *WorkflowContext) DeleteValueInMemory(paths ...string) {
wf.memoryStore.Delete(strings.Join(paths, "."))
}
// MakeParameter make 'value' with interface{}
func (wf *WorkflowContext) MakeParameter(parameter interface{}) (*value.Value, error) {
var s = "{}"
@@ -317,6 +338,11 @@ func NewContext(cli client.Client, ns, app string, appUID types.UID) (Context, e
return wfCtx, wfCtx.Commit()
}
// CleanupMemoryStore cleans up memory store.
func CleanupMemoryStore(app, ns string) {
workflowMemoryCache.Delete(fmt.Sprintf("%s-%s", app, ns))
}
func newContext(cli client.Client, ns, app string, appUID types.UID) (*WorkflowContext, error) {
var (
ctx = context.Background()
@@ -347,11 +373,13 @@ func newContext(cli client.Client, ns, app string, appUID types.UID) (*WorkflowC
store.Annotations = map[string]string{
AnnotationStartTimestamp: time.Now().String(),
}
memCache := getMemoryStore(fmt.Sprintf("%s-%s", app, ns))
wfCtx := &WorkflowContext{
cli: cli,
store: &store,
components: map[string]*ComponentManifest{},
modified: true,
cli: cli,
store: &store,
memoryStore: memCache,
components: map[string]*ComponentManifest{},
modified: true,
}
var err error
wfCtx.vars, err = value.NewValue("", nil, "")
@@ -359,6 +387,20 @@ func newContext(cli client.Client, ns, app string, appUID types.UID) (*WorkflowC
return wfCtx, err
}
func getMemoryStore(key string) *sync.Map {
memCache := &sync.Map{}
mc, ok := workflowMemoryCache.Load(key)
if !ok {
workflowMemoryCache.Store(key, memCache)
} else {
memCache, ok = mc.(*sync.Map)
if !ok {
workflowMemoryCache.Store(key, memCache)
}
}
return memCache
}
// LoadContext load workflow context from store.
func LoadContext(cli client.Client, ns, app string) (Context, error) {
var store corev1.ConfigMap
@@ -372,9 +414,11 @@ func LoadContext(cli client.Client, ns, app string) (Context, error) {
}, &store); err != nil {
return nil, err
}
memCache := getMemoryStore(fmt.Sprintf("%s-%s", app, ns))
ctx := &WorkflowContext{
cli: cli,
store: &store,
cli: cli,
store: &store,
memoryStore: memCache,
}
if err := ctx.LoadFromConfigMap(store); err != nil {
return nil, err

View File

@@ -275,14 +275,33 @@ func TestMutableValue(t *testing.T) {
wfCtx.DeleteMutableValue("test", "key")
v = wfCtx.GetMutableValue("test", "key")
r.Equal(v, "")
}
wfCtx.SetMutableValue("value", "test", "key")
count := wfCtx.IncreaseMutableCountValue("test", "key")
func TestMemoryValue(t *testing.T) {
cli := newCliForTest(t, nil)
r := require.New(t)
wfCtx, err := NewContext(cli, "default", "app-v1", "testuid")
r.NoError(err)
err = wfCtx.Commit()
r.NoError(err)
wfCtx.SetValueInMemory("value", "test", "key")
v, ok := wfCtx.GetValueInMemory("test", "key")
r.Equal(ok, true)
r.Equal(v.(string), "value")
wfCtx.DeleteValueInMemory("test", "key")
_, ok = wfCtx.GetValueInMemory("test", "key")
r.Equal(ok, false)
wfCtx.SetValueInMemory("value", "test", "key")
count := wfCtx.IncreaseCountValueInMemory("test", "key")
r.Equal(count, 0)
count = wfCtx.IncreaseMutableCountValue("notfound", "key")
count = wfCtx.IncreaseCountValueInMemory("notfound", "key")
r.Equal(count, 0)
wfCtx.SetMutableValue("10", "number", "key")
count = wfCtx.IncreaseMutableCountValue("number", "key")
wfCtx.SetValueInMemory(10, "number", "key")
count = wfCtx.IncreaseCountValueInMemory("number", "key")
r.Equal(count, 11)
}

View File

@@ -32,8 +32,11 @@ type Context interface {
GetStore() *corev1.ConfigMap
GetMutableValue(path ...string) string
SetMutableValue(data string, path ...string)
IncreaseMutableCountValue(paths ...string) int
DeleteMutableValue(paths ...string)
IncreaseCountValueInMemory(paths ...string) int
SetValueInMemory(data interface{}, paths ...string)
GetValueInMemory(paths ...string) (interface{}, bool)
DeleteValueInMemory(paths ...string)
Commit() error
MakeParameter(parameter interface{}) (*value.Value, error)
StoreRef() *corev1.ObjectReference

View File

@@ -33,9 +33,6 @@ type Workflow interface {
// Trace record workflow state in controllerRevision.
Trace() error
// CleanupCountersInContext cleans up the temporary counters in workflow context.
CleanupCountersInContext(ctx monitorContext.Context)
// GetBackoffWaitTime returns the wait time for next retry.
GetBackoffWaitTime() time.Duration
}

View File

@@ -286,7 +286,7 @@ func (exec *executor) err(ctx wfContext.Context, err error, reason string) {
}
func (exec *executor) checkErrorTimes(ctx wfContext.Context) {
times := ctx.IncreaseMutableCountValue(wfTypes.ContextPrefixFailedTimes, exec.wfStatus.ID)
times := ctx.IncreaseCountValueInMemory(wfTypes.ContextPrefixFailedTimes, exec.wfStatus.ID)
if times >= MaxErrorTimes {
exec.wait = false
exec.failedAfterRetries = true

View File

@@ -243,6 +243,7 @@ close({
r.Equal(operation.Waiting, true)
r.Equal(status.Phase, common.WorkflowStepPhaseFailed)
case "failed-after-retries":
wfContext.CleanupMemoryStore("app-v1", "default")
newCtx := newWorkflowContextForTest(t)
for i := 0; i < MaxErrorTimes; i++ {
status, operation, err = run.Run(newCtx, &types.TaskRunOptions{})

View File

@@ -20,8 +20,7 @@ import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"sync"
"time"
"github.com/pkg/errors"
@@ -45,6 +44,8 @@ import (
var (
// DisableRecorder optimize workflow by disable recorder
DisableRecorder = false
// StepStatusCache cache the step status
StepStatusCache sync.Map
)
const (
@@ -123,11 +124,16 @@ func (w *workflow) ExecuteSteps(ctx monitorContext.Context, appRev *oamcore.Appl
}
}
w.app.Status.Conditions = reservedConditions
StepStatusCache.Delete(fmt.Sprintf("%s-%s", w.app.Name, w.app.Namespace))
wfContext.CleanupMemoryStore(w.app.Name, w.app.Namespace)
return common.WorkflowStateInitializing, nil
}
wfStatus := w.app.Status.Workflow
cacheKey := fmt.Sprintf("%s-%s", w.app.Name, w.app.Namespace)
if wfStatus.Finished {
StepStatusCache.Delete(cacheKey)
return common.WorkflowStateFinished, nil
}
if wfStatus.Terminated {
@@ -148,7 +154,13 @@ func (w *workflow) ExecuteSteps(ctx monitorContext.Context, appRev *oamcore.Appl
return common.WorkflowStateExecuting, err
}
w.wfCtx = wfCtx
w.checkDuplicateID(ctx)
if cacheValue, ok := StepStatusCache.Load(cacheKey); ok {
// handle cache resource
if len(wfStatus.Steps) < cacheValue.(int) {
return common.WorkflowStateSkipping, nil
}
}
e := &engine{
status: wfStatus,
@@ -161,17 +173,19 @@ func (w *workflow) ExecuteSteps(ctx monitorContext.Context, appRev *oamcore.Appl
err = e.run(taskRunners)
if err != nil {
ctx.Error(err, "run steps")
StepStatusCache.Store(cacheKey, len(wfStatus.Steps))
wfStatus.Message = string(common.WorkflowStateExecuting)
return common.WorkflowStateExecuting, err
}
e.checkWorkflowStatusMessage(wfStatus)
StepStatusCache.Store(cacheKey, len(wfStatus.Steps))
if wfStatus.Terminated {
w.CleanupCountersInContext(ctx)
wfContext.CleanupMemoryStore(e.app.Name, e.app.Namespace)
return common.WorkflowStateTerminated, nil
}
if wfStatus.Suspend {
w.CleanupCountersInContext(ctx)
wfContext.CleanupMemoryStore(e.app.Name, e.app.Namespace)
return common.WorkflowStateSuspended, nil
}
if w.allDone(taskRunners) {
@@ -194,31 +208,13 @@ func (w *workflow) Trace() error {
return recorder.With(w.cli, w.app).Save("", data).Limit(10).Error()
}
func (w *workflow) CleanupCountersInContext(ctx monitorContext.Context) {
ctxCM := w.wfCtx.GetStore()
for k := range ctxCM.Data {
if strings.HasPrefix(k, wfTypes.ContextPrefixFailedTimes) ||
strings.HasPrefix(k, wfTypes.ContextPrefixBackoffTimes) ||
strings.HasPrefix(k, wfTypes.ContextKeyLastExecuteTime) ||
strings.HasPrefix(k, wfTypes.ContextKeyNextExecuteTime) {
delete(ctxCM.Data, k)
}
}
if err := w.wfCtx.Commit(); err != nil {
ctx.Error(err, "failed to commit workflow context", "application", w.app.Name, "config map", ctxCM.Name)
}
}
func (w *workflow) GetBackoffWaitTime() time.Duration {
nextTime := w.wfCtx.GetMutableValue(wfTypes.ContextKeyNextExecuteTime)
if nextTime == "" {
nextTime, ok := w.wfCtx.GetValueInMemory(wfTypes.ContextKeyNextExecuteTime)
if !ok {
return time.Second
}
unix, err := strconv.ParseInt(nextTime, 10, 64)
if err != nil {
unix, ok := nextTime.(int64)
if !ok {
return time.Second
}
next := time.Unix(unix, 0)
@@ -285,32 +281,15 @@ func (w *workflow) setMetadataToContext(wfCtx wfContext.Context) error {
return wfCtx.SetVar(metadata, wfTypes.ContextKeyMetadata)
}
func (w *workflow) checkDuplicateID(ctx monitorContext.Context) {
if len(w.app.Status.Workflow.Steps) > 0 {
return
}
ctxCM := w.wfCtx.GetStore()
found := false
for k := range ctxCM.Data {
if strings.HasPrefix(k, wfTypes.ContextPrefixBackoffTimes) {
found = true
}
}
if found {
w.CleanupCountersInContext(ctx)
}
}
func getBackoffWaitTime(wfCtx wfContext.Context) int {
ctxCM := wfCtx.GetStore()
func (e *engine) getBackoffWaitTime() int {
// the default value of min times reaches the max workflow backoff wait time
minTimes := 15
found := false
for k, v := range ctxCM.Data {
if strings.HasPrefix(k, wfTypes.ContextPrefixBackoffTimes) {
for _, step := range e.status.Steps {
if v, ok := e.wfCtx.GetValueInMemory(wfTypes.ContextPrefixBackoffTimes, step.ID); ok {
found = true
times, err := strconv.Atoi(v)
if err != nil {
times, ok := v.(int)
if !ok {
times = 0
}
if times < minTimes {
@@ -333,19 +312,19 @@ func getBackoffWaitTime(wfCtx wfContext.Context) int {
}
func (e *engine) setNextExecuteTime() {
interval := getBackoffWaitTime(e.wfCtx)
lastExecuteTime := e.wfCtx.GetMutableValue(wfTypes.ContextKeyLastExecuteTime)
if lastExecuteTime == "" {
interval := e.getBackoffWaitTime()
lastExecuteTime, ok := e.wfCtx.GetValueInMemory(wfTypes.ContextKeyLastExecuteTime)
if !ok {
e.monitorCtx.Error(fmt.Errorf("failed to get last execute time"), "application", e.app.Name)
}
last, err := strconv.ParseInt(lastExecuteTime, 10, 64)
if err != nil {
e.monitorCtx.Error(err, "failed to parse last execute time", "lastExecuteTime", lastExecuteTime)
last, ok := lastExecuteTime.(int64)
if !ok {
e.monitorCtx.Error(fmt.Errorf("failed to parse last execute time to int64"), "lastExecuteTime", lastExecuteTime)
}
next := last + int64(interval)
e.wfCtx.SetMutableValue(strconv.FormatInt(next, 10), wfTypes.ContextKeyNextExecuteTime)
e.wfCtx.SetValueInMemory(next, wfTypes.ContextKeyNextExecuteTime)
if err := e.wfCtx.Commit(); err != nil {
e.monitorCtx.Error(err, "failed to commit next execute time", "nextExecuteTime", next)
}
@@ -376,7 +355,7 @@ func (e *engine) runAsDAG(taskRunners []wfTypes.TaskRunner) error {
}
todoTasks = append(todoTasks, tRunner)
} else {
wfCtx.DeleteMutableValue(wfTypes.ContextPrefixBackoffTimes, stepID)
wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, stepID)
}
}
if done {
@@ -461,7 +440,7 @@ func (e *engine) steps(taskRunners []wfTypes.TaskRunner) error {
e.failedAfterRetries = e.failedAfterRetries || operation.FailedAfterRetries
e.waiting = e.waiting || operation.Waiting
if status.Phase != common.WorkflowStepPhaseSucceeded {
wfCtx.IncreaseMutableCountValue(wfTypes.ContextPrefixBackoffTimes, status.ID)
wfCtx.IncreaseCountValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID)
if err := wfCtx.Commit(); err != nil {
return errors.WithMessage(err, "commit workflow context")
}
@@ -471,7 +450,7 @@ func (e *engine) steps(taskRunners []wfTypes.TaskRunner) error {
e.checkFailedAfterRetries()
return nil
}
wfCtx.DeleteMutableValue(wfTypes.ContextPrefixBackoffTimes, status.ID)
wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID)
if err := wfCtx.Commit(); err != nil {
return errors.WithMessage(err, "commit workflow context")
}
@@ -511,7 +490,7 @@ func (e *engine) updateStepStatus(status common.WorkflowStepStatus) {
now = metav1.NewTime(time.Now())
)
e.wfCtx.SetMutableValue(strconv.FormatInt(now.Unix(), 10), wfTypes.ContextKeyLastExecuteTime)
e.wfCtx.SetValueInMemory(now.Unix(), wfTypes.ContextKeyLastExecuteTime)
status.LastExecuteTime = now
for i := range e.status.Steps {
if e.status.Steps[i].Name == status.Name {

View File

@@ -142,7 +142,6 @@ var _ = Describe("Test Workflow", func() {
Phase: common.WorkflowStepPhaseSucceeded,
}},
})).Should(BeEquivalentTo(""))
})
It("Workflow test for failed after retries", func() {
@@ -237,7 +236,6 @@ var _ = Describe("Test Workflow", func() {
Phase: common.WorkflowStepPhaseSucceeded,
}},
})).Should(BeEquivalentTo(""))
})
It("Test get backoff time and clean", func() {
@@ -251,40 +249,48 @@ var _ = Describe("Test Workflow", func() {
wf := NewWorkflow(app, k8sClient, common.WorkflowModeDAG)
_, err := wf.ExecuteSteps(ctx, revision, runners)
Expect(err).ToNot(HaveOccurred())
_, err = wf.ExecuteSteps(ctx, revision, runners)
Expect(err).ToNot(HaveOccurred())
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
Expect(err).ToNot(HaveOccurred())
e := &engine{
status: app.Status.Workflow,
wfCtx: wfCtx,
}
interval := e.getBackoffWaitTime()
Expect(interval).Should(BeEquivalentTo(minWorkflowBackoffWaitTime))
By("Test get backoff time")
for i := 0; i < 5; i++ {
for i := 0; i < 4; i++ {
_, err = wf.ExecuteSteps(ctx, revision, runners)
Expect(err).ToNot(HaveOccurred())
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
Expect(err).ToNot(HaveOccurred())
interval := getBackoffWaitTime(wfCtx)
interval := e.getBackoffWaitTime()
Expect(interval).Should(BeEquivalentTo(minWorkflowBackoffWaitTime))
}
for i := 0; i < 9; i++ {
_, err = wf.ExecuteSteps(ctx, revision, runners)
Expect(err).ToNot(HaveOccurred())
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
Expect(err).ToNot(HaveOccurred())
interval := getBackoffWaitTime(wfCtx)
interval := e.getBackoffWaitTime()
Expect(interval).Should(BeEquivalentTo(int(0.05 * math.Pow(2, float64(i+5)))))
}
_, err = wf.ExecuteSteps(ctx, revision, runners)
Expect(err).ToNot(HaveOccurred())
wfCtx, err := wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
Expect(err).ToNot(HaveOccurred())
interval := getBackoffWaitTime(wfCtx)
interval = e.getBackoffWaitTime()
Expect(interval).Should(BeEquivalentTo(maxWorkflowBackoffWaitTime))
By("Test get backoff time after clean")
wf.CleanupCountersInContext(ctx)
wfContext.CleanupMemoryStore(app.Name, app.Namespace)
_, err = wf.ExecuteSteps(ctx, revision, runners)
Expect(err).ToNot(HaveOccurred())
wfCtx, err = wfContext.LoadContext(k8sClient, app.Namespace, app.Name)
Expect(err).ToNot(HaveOccurred())
interval = getBackoffWaitTime(wfCtx)
e = &engine{
status: app.Status.Workflow,
wfCtx: wfCtx,
}
interval = e.getBackoffWaitTime()
Expect(interval).Should(BeEquivalentTo(minWorkflowBackoffWaitTime))
})

View File

@@ -234,11 +234,11 @@ func parseToMap(args []string) (map[string]interface{}, error) {
res := map[string]interface{}{}
for _, pair := range args {
line := strings.Split(pair, "=")
if len(line) != 2 {
if len(line) < 2 {
return nil, fmt.Errorf("parameter format should be foo=bar, %s not match", pair)
}
k := strings.TrimSpace(line[0])
v := strings.TrimSpace(line[1])
v := strings.TrimSpace(strings.Join(line[1:], "="))
if k != "" && v != "" {
res[k] = v
}

View File

@@ -0,0 +1,60 @@
/*
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 cli
import (
"testing"
"gotest.tools/assert"
)
func TestParseMap(t *testing.T) {
testcase := []struct {
args []string
res map[string]interface{}
nilError bool
}{
{
args: []string{"key1=value1"},
res: map[string]interface{}{
"key1": "value1",
},
nilError: true,
},
{
args: []string{"dbUrl=mongodb=mgset-58800212"},
res: map[string]interface{}{
"dbUrl": "mongodb=mgset-58800212",
},
nilError: true,
},
{
args: []string{"errorparameter"},
res: nil,
nilError: false,
},
}
for _, s := range testcase {
r, err := parseToMap(s.args)
assert.DeepEqual(t, s.res, r)
if s.nilError {
assert.NilError(t, err)
} else {
assert.Error(t, err, "parameter format should be foo=bar, errorparameter not match")
}
}
}

View File

@@ -44,6 +44,8 @@ const (
FlagProvider = "provider"
// FlagGit command flag to specify which git repository the configuration(HCL) is stored in
FlagGit = "git"
// FlagLocal command flag to specify the local path of Terraform module or resource HCL file
FlagLocal = "local"
// FlagPath command flag to specify which path the configuration(HCL) is stored in the Git repository
FlagPath = "path"
// FlagNamespace command flag to specify which namespace to use

View File

@@ -244,6 +244,6 @@ func PrintInstalledCompDef(c common2.Args, io cmdutil.IOStreams, filter filterFu
}
table.AddRow(capa.Name, capa.CrdName)
}
io.Infof(table.String())
io.Info(table.String())
return nil
}

View File

@@ -21,6 +21,7 @@ import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
@@ -31,6 +32,7 @@ import (
"cuelang.org/go/cue"
"cuelang.org/go/encoding/gocode/gocodec"
crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
@@ -167,8 +169,10 @@ func NewDefinitionInitCommand(c common.Args) *cobra.Command {
"> vela def init my-def -i --output ./my-def.cue\n" +
"# Command below initiate a ComponentDefinition named my-webservice with the template parsed from ./template.yaml.\n" +
"> vela def init my-webservice -i --template-yaml ./template.yaml\n" +
"# Command below initiate a typed ComponentDefinition named vswitch from Alibaba Cloud.\n" +
"> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch",
"# Initiate a Terraform ComponentDefinition named vswitch from Github for Alibaba Cloud.\n" +
"> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch\n" +
"# Initiate a Terraform ComponentDefinition named redis from local file for AWS.\n" +
"> vela def init redis --type component --provider aws --desc \"Terraform configuration for AWS Redis\" --local redis.tf",
Args: cobra.ExactValidArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var defStr string
@@ -280,6 +284,7 @@ func NewDefinitionInitCommand(c common.Args) *cobra.Command {
cmd.Flags().BoolP(FlagInteractive, "i", false, "Specify whether use interactive process to help generate definitions.")
cmd.Flags().StringP(FlagProvider, "p", "", "Specify which provider the cloud resource definition belongs to. Only `alibaba`, `aws`, `azure` are supported.")
cmd.Flags().StringP(FlagGit, "", "", "Specify which git repository the configuration(HCL) is stored in. Valid when --provider/-p is set.")
cmd.Flags().StringP(FlagLocal, "", "", "Specify the local path of the configuration(HCL) file. Valid when --provider/-p is set.")
cmd.Flags().StringP(FlagPath, "", "", "Specify which path the configuration(HCL) is stored in the Git repository. Valid when --git is set.")
return cmd
}
@@ -290,18 +295,42 @@ func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, p
}
switch provider {
case "aws", "azure", "alibaba":
case "aws", "azure", "alibaba", "tencent":
var terraform *commontype.Terraform
git, err := cmd.Flags().GetString(FlagGit)
if err != nil {
return "", errors.Wrapf(err, "failed to get `%s`", FlagGit)
}
if !strings.HasPrefix(git, "https://") || !strings.HasSuffix(git, ".git") {
return "", errors.Errorf("invalid git url: %s", git)
local, err := cmd.Flags().GetString(FlagLocal)
if err != nil {
return "", errors.Wrapf(err, "failed to get `%s`", FlagLocal)
}
if git != "" && local != "" {
return "", errors.New("only one of --git and --local can be set")
}
gitPath, err := cmd.Flags().GetString(FlagPath)
if err != nil {
return "", errors.Wrapf(err, "failed to get `%s`", FlagPath)
}
if git != "" {
if !strings.HasPrefix(git, "https://") || !strings.HasSuffix(git, ".git") {
return "", errors.Errorf("invalid git url: %s", git)
}
terraform = &commontype.Terraform{
Configuration: git,
Type: "remote",
Path: gitPath,
}
} else if local != "" {
hcl, err := ioutil.ReadFile(filepath.Clean(local))
if err != nil {
return "", errors.Wrapf(err, "failed to read Terraform configuration from file %s", local)
}
terraform = &commontype.Terraform{
Configuration: string(hcl),
}
}
def := v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
APIVersion: "core.oam.dev/v1beta1",
@@ -325,14 +354,16 @@ func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, p
},
},
Schematic: &commontype.Schematic{
Terraform: &commontype.Terraform{
Configuration: git,
Type: "remote",
Path: gitPath,
},
Terraform: terraform,
},
},
}
if provider != "alibaba" {
def.Spec.Schematic.Terraform.ProviderReference = &crossplane.Reference{
Name: provider,
Namespace: "default",
}
}
var out bytes.Buffer
err = json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, json.SerializerOptions{Yaml: true}).Encode(&def, &out)
if err != nil {
@@ -432,12 +463,20 @@ func NewDefinitionGenDocCommand(c common.Args) *cobra.Command {
ref := &plugins.MarkdownReference{}
ctx := context.Background()
ref.DefinitionName = args[0]
path := plugins.KubeVelaIOTerraformPath
if err := ref.GenerateReferenceDocs(ctx, c, path, namespace); err != nil {
pathEn := plugins.KubeVelaIOTerraformPath
ref.I18N = plugins.En
if err := ref.GenerateReferenceDocs(ctx, c, pathEn, namespace); err != nil {
return errors.Wrap(err, "failed to generate reference docs")
}
cmd.Printf("Generated docs for %s in ./%s/%s.md\n", args[0], path, args[0])
cmd.Printf("Generated docs in English for %s in %s/%s.md\n", args[0], pathEn, args[0])
pathZh := plugins.KubeVelaIOTerraformPathZh
ref.I18N = plugins.Zh
if err := ref.GenerateReferenceDocs(ctx, c, pathZh, namespace); err != nil {
return errors.Wrap(err, "failed to generate reference docs")
}
cmd.Printf("Generated docs in Chinese for %s in %s/%s.md\n", args[0], pathZh, args[0])
return nil
},
}

View File

@@ -204,10 +204,14 @@ func TestNewDefinitionInitCommand(t *testing.T) {
}
func TestNewDefinitionInitCommand4Terraform(t *testing.T) {
const defFileName = "alibaba-vswitch.yaml"
const (
defVswitchFileName = "alibaba-vswitch.yaml"
defRedisFileName = "tencent-redis.yaml"
)
testcases := []struct {
name string
args []string
output string
errMsg string
want string
}{
@@ -216,8 +220,95 @@ func TestNewDefinitionInitCommand4Terraform(t *testing.T) {
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "xxx", "--git", "https://github.com/kubevela-contrib/terraform-modules.git", "--path", "alibaba/vswitch"},
},
{
name: "print in a file",
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "xxx", "--git", "https://github.com/kubevela-contrib/terraform-modules.git", "--path", "alibaba/vswitch", "--output", defFileName},
name: "normal from local",
args: []string{"vswitch", "-t", "component", "--provider", "tencent", "--desc", "xxx", "--local", "test-data/redis.tf", "--output", defRedisFileName},
output: defRedisFileName,
want: `apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: xxx
creationTimestamp: null
labels:
type: terraform
name: tencent-vswitch
namespace: vela-system
spec:
schematic:
terraform:
configuration: |
terraform {
required_providers {
tencentcloud = {
source = "tencentcloudstack/tencentcloud"
}
}
}
resource "tencentcloud_redis_instance" "main" {
type_id = 8
availability_zone = var.availability_zone
name = var.instance_name
password = var.user_password
mem_size = var.mem_size
port = var.port
}
output "DB_IP" {
value = tencentcloud_redis_instance.main.ip
}
output "DB_PASSWORD" {
value = var.user_password
}
output "DB_PORT" {
value = var.port
}
variable "availability_zone" {
description = "The available zone ID of an instance to be created."
type = string
default = "ap-chengdu-1"
}
variable "instance_name" {
description = "redis instance name"
type = string
default = "sample"
}
variable "user_password" {
description = "redis instance password"
type = string
default = "IEfewjf2342rfwfwYYfaked"
}
variable "mem_size" {
description = "redis instance memory size"
type = number
default = 1024
}
variable "port" {
description = "The port used to access a redis instance."
type = number
default = 6379
}
providerRef:
name: tencent
namespace: default
workload:
definition:
apiVersion: terraform.core.oam.dev/v1beta1
kind: Configuration
status: {}
`,
},
{
name: "print in a file",
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "xxx", "--git", "https://github.com/kubevela-contrib/terraform-modules.git", "--path", "alibaba/vswitch", "--output", defVswitchFileName},
output: defVswitchFileName,
want: `apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
@@ -255,6 +346,16 @@ status: {}`,
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "test", "--git", "xxx"},
errMsg: "invalid git url",
},
{
name: "git and local could be set at the same time",
args: []string{"vswitch", "-t", "component", "--provider", "alibaba", "--desc", "test", "--git", "xxx", "--local", "yyy"},
errMsg: "only one of --git and --local can be set",
},
{
name: "local file doesn't exist",
args: []string{"vswitch", "-t", "component", "--provider", "tencent", "--desc", "xxx", "--local", "test-data/redis2.tf"},
errMsg: "failed to read Terraform configuration from file",
},
}
for _, tc := range testcases {
@@ -267,8 +368,8 @@ status: {}`,
if err != nil && !strings.Contains(err.Error(), tc.errMsg) {
t.Fatalf("unexpected error when executing init command: %v", err)
} else if tc.want != "" {
data, err := os.ReadFile(defFileName)
defer os.Remove(defFileName)
data, err := os.ReadFile(tc.output)
defer os.Remove(tc.output)
assert.Nil(t, err)
if !strings.Contains(string(data), tc.want) {
t.Fatalf("unexpected output: %s", string(data))

View File

@@ -762,14 +762,14 @@ func ParseCapability(mapper discoverymapper.DiscoveryMapper, data []byte) (types
}
workloadDefinitionRef = ref.Name
}
return plugins.HandleDefinition(cd.Name, workloadDefinitionRef, cd.Annotations, cd.Labels, cd.Spec.Extension, types.TypeComponentDefinition, nil, cd.Spec.Schematic)
return plugins.HandleDefinition(cd.Name, workloadDefinitionRef, cd.Annotations, cd.Labels, cd.Spec.Extension, types.TypeComponentDefinition, nil, cd.Spec.Schematic, nil)
case "TraitDefinition":
var td v1beta1.TraitDefinition
err = yaml.Unmarshal(data, &td)
if err != nil {
return types.Capability{}, err
}
return plugins.HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Labels, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic)
return plugins.HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Labels, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic, nil)
case "ScopeDefinition":
// TODO(wonderflow): support scope definition here.
}

View File

@@ -32,6 +32,7 @@ import (
"github.com/spf13/cobra"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/system"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
@@ -62,8 +63,8 @@ var webSite bool
func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Short: "Show the reference doc for a component type or trait.",
Long: "Show the reference doc for component or trait types.",
Short: "Show the reference doc for a component, trait or workflow.",
Long: "Show the reference doc for component, trait or workflow types.",
Example: `show webservice`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
@@ -126,7 +127,7 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
}
}
if !capabilityIsValid {
return fmt.Errorf("%s is not a valid component type or trait", capabilityName)
return fmt.Errorf("%s is not a valid component, trait or workflow", capabilityName)
}
cli, err := c.GetClient()
@@ -139,7 +140,15 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
},
}
if err := ref.CreateMarkdown(ctx, capabilities, docsPath, plugins.ReferenceSourcePath); err != nil {
config, err := c.GetConfig()
if err != nil {
return err
}
pd, err := packages.NewPackageDiscover(config)
if err != nil {
return err
}
if err := ref.CreateMarkdown(ctx, capabilities, docsPath, plugins.ReferenceSourcePath, pd); err != nil {
return err
}
@@ -171,6 +180,8 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
case types.TypeScope:
case types.TypeComponentDefinition:
capabilityPath = plugins.ComponentDefinitionTypePath
case types.TypeWorkflowStep:
capabilityPath = plugins.WorkflowStepPath
default:
return fmt.Errorf("unsupported type: %v", capabilityType)
}
@@ -216,7 +227,7 @@ func launch(server *http.Server, errChan chan<- error) {
func generateSideBar(capabilities []types.Capability, docsPath string) error {
sideBar := filepath.Join(docsPath, SideBar)
components, traits := getComponentsAndTraits(capabilities)
components, traits, workflowsteps := getDefinitions(capabilities)
f, err := os.Create(sideBar)
if err != nil {
return err
@@ -237,6 +248,14 @@ func generateSideBar(capabilities []types.Capability, docsPath string) error {
return err
}
}
if _, err := f.WriteString("- Workflow Steps\n"); err != nil {
return err
}
for _, t := range workflowsteps {
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, plugins.WorkflowStepPath, t)); err != nil {
return err
}
}
return nil
}
@@ -303,12 +322,12 @@ func generateREADME(capabilities []types.Capability, docsPath string) error {
if err != nil {
return err
}
if _, err := f.WriteString("# KubeVela Reference Docs for Component Types and Traits\n" +
"Click the navigation bar on the left or the links below to look into the detailed referennce of a Workload type or a Trait.\n"); err != nil {
if _, err := f.WriteString("# KubeVela Reference Docs for Component Types, Traits and WorkflowSteps\n" +
"Click the navigation bar on the left or the links below to look into the detailed reference of a Workload type, Trait or Workflow Step.\n"); err != nil {
return err
}
workloads, traits := getComponentsAndTraits(capabilities)
workloads, traits, workflowsteps := getDefinitions(capabilities)
if _, err := f.WriteString("## Component Types\n"); err != nil {
return err
@@ -328,28 +347,47 @@ func generateREADME(capabilities []types.Capability, docsPath string) error {
return err
}
}
if _, err := f.WriteString("## Workflow Steps\n"); err != nil {
return err
}
for _, t := range workflowsteps {
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, plugins.WorkflowStepPath, t)); err != nil {
return err
}
}
return nil
}
func getComponentsAndTraits(capabilities []types.Capability) ([]string, []string) {
var components, traits []string
func getDefinitions(capabilities []types.Capability) ([]string, []string, []string) {
var components, traits, workflowSteps []string
for _, c := range capabilities {
switch c.Type {
case types.TypeComponentDefinition:
components = append(components, c.Name)
case types.TypeTrait:
traits = append(traits, c.Name)
case types.TypeWorkflowStep:
workflowSteps = append(workflowSteps, c.Name)
case types.TypeScope:
case types.TypeWorkload:
default:
}
}
return components, traits
return components, traits, workflowSteps
}
// ShowReferenceConsole will show capability reference in console
func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string, ns string) error {
capability, err := plugins.GetCapabilityByName(ctx, c, capabilityName, ns)
config, err := c.GetConfig()
if err != nil {
return err
}
pd, err := packages.NewPackageDiscover(config)
if err != nil {
return err
}
capability, err := plugins.GetCapabilityByName(ctx, c, capabilityName, ns, pd)
if err != nil {
return err
}
@@ -377,7 +415,7 @@ func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.
return err
}
case types.CUECategory:
propertyConsole, err = ref.GenerateCUETemplateProperties(capability)
propertyConsole, err = ref.GenerateCUETemplateProperties(capability, pd)
if err != nil {
return err
}

View File

@@ -190,7 +190,7 @@ func TestGetWorkloadAndTraits(t *testing.T) {
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
gotWorkloads, gotTraits := getComponentsAndTraits(tc.capabilities)
gotWorkloads, gotTraits, _ := getDefinitions(tc.capabilities)
assert.Equal(t, tc.want, want{workloads: gotWorkloads, traits: gotTraits})
})
}

View File

@@ -0,0 +1,58 @@
terraform {
required_providers {
tencentcloud = {
source = "tencentcloudstack/tencentcloud"
}
}
}
resource "tencentcloud_redis_instance" "main" {
type_id = 8
availability_zone = var.availability_zone
name = var.instance_name
password = var.user_password
mem_size = var.mem_size
port = var.port
}
output "DB_IP" {
value = tencentcloud_redis_instance.main.ip
}
output "DB_PASSWORD" {
value = var.user_password
}
output "DB_PORT" {
value = var.port
}
variable "availability_zone" {
description = "The available zone ID of an instance to be created."
type = string
default = "ap-chengdu-1"
}
variable "instance_name" {
description = "redis instance name"
type = string
default = "sample"
}
variable "user_password" {
description = "redis instance password"
type = string
default = "IEfewjf2342rfwfwYYfaked"
}
variable "mem_size" {
description = "redis instance memory size"
type = number
default = 1024
}
variable "port" {
description = "The port used to access a redis instance."
type = number
default = 6379
}

View File

@@ -245,7 +245,7 @@ func PrintInstalledTraitDef(c common2.Args, io cmdutil.IOStreams, filter filterF
}
table.AddRow(capa.Name, capa.AppliesTo)
}
io.Infof(table.String())
io.Info(table.String())
return nil
}

View File

@@ -68,6 +68,12 @@ func NewUpCommand(c common2.Args, order string, ioStream cmdutil.IOStreams) *cob
if err != nil {
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
}
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
if namespace != "" && namespace != types.DefaultAppNamespace {
app.SetNamespace(namespace)
}
err = common.ApplyApplication(app, ioStream, kubecli)
if err != nil {
return err

View File

@@ -17,12 +17,21 @@ limitations under the License.
package cli
import (
"bytes"
"context"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/common"
)
@@ -34,3 +43,105 @@ func TestUp(t *testing.T) {
assert.Contains(t, msg, "App has been deployed")
assert.Contains(t, msg, fmt.Sprintf("App status: vela status %s", app.Name))
}
func TestUpOverrideNamespace(t *testing.T) {
cases := map[string]struct {
application string
applicationName string
namespace string
expectedNamespace string
}{
"use default namespace if not set": {
application: `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: first-vela-app
spec:
components: []
`,
applicationName: "first-vela-app",
namespace: "",
expectedNamespace: types.DefaultAppNamespace,
},
"override namespace": {
application: `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: first-vela-app
spec:
components: []
`,
applicationName: "first-vela-app",
namespace: "overridden-namespace",
expectedNamespace: "overridden-namespace",
},
"use application namespace": {
application: `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: first-vela-app
namespace: vela-apps
spec:
components: []
`,
applicationName: "first-vela-app",
namespace: "",
expectedNamespace: "vela-apps",
},
"override application namespace": {
application: `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: first-vela-app
namespace: vela-apps
spec:
components: []
`,
applicationName: "first-vela-app",
namespace: "overridden-namespace",
expectedNamespace: "overridden-namespace",
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
args := initArgs()
kc, err := args.GetClient()
require.NoError(t, err)
af, err := os.CreateTemp(os.TempDir(), "up-override-namespace-*.yaml")
require.NoError(t, err)
defer func() {
_ = af.Close()
_ = os.Remove(af.Name())
}()
_, err = af.WriteString(c.application)
require.NoError(t, err)
// Ensure namespace
require.NoError(t, kc.Create(context.TODO(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: c.expectedNamespace},
}))
var buf bytes.Buffer
cmd := NewUpCommand(args, "", util.IOStreams{In: os.Stdin, Out: &buf, ErrOut: &buf})
if c.namespace != "" {
require.NoError(t, cmd.Flags().Set(FlagNamespace, c.namespace))
}
require.NoError(t, cmd.Flags().Set("file", af.Name()))
require.NoError(t, cmd.Execute())
var app v1beta1.Application
require.NoError(t, kc.Get(context.TODO(), client.ObjectKey{
Name: c.applicationName,
Namespace: c.expectedNamespace,
}, &app))
require.Equal(t, c.expectedNamespace, app.Namespace)
require.Equal(t, c.applicationName, app.Name)
})
}
}

View File

@@ -35,6 +35,7 @@ import (
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/common"
@@ -212,9 +213,9 @@ func validateCapabilities(tmp *types.Capability, dm discoverymapper.DiscoveryMap
// HandleDefinition will handle definition to capability
func HandleDefinition(name, crdName string, annotation, labels map[string]string, extension *runtime.RawExtension, tp types.CapType,
applyTo []string, schematic *commontypes.Schematic) (types.Capability, error) {
applyTo []string, schematic *commontypes.Schematic, pd *packages.PackageDiscover) (types.Capability, error) {
var tmp types.Capability
tmp, err := HandleTemplate(extension, schematic, name)
tmp, err := HandleTemplate(extension, schematic, name, pd)
if err != nil {
return types.Capability{}, err
}
@@ -242,7 +243,7 @@ func GetDescription(annotation map[string]string) string {
}
// HandleTemplate will handle definition template to capability
func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, name string) (types.Capability, error) {
func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, name string, pd *packages.PackageDiscover) (types.Capability, error) {
tmp, err := appfile.ConvertTemplateJSON2Object(name, in, schematic)
if err != nil {
return types.Capability{}, err
@@ -282,7 +283,7 @@ func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic,
}
return types.Capability{}, errors.New("template not exist in definition")
}
tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate)
tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate, pd)
if err != nil {
return types.Capability{}, err
}
@@ -291,7 +292,7 @@ func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic,
}
// GetCapabilityByName gets capability by definition name
func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName string, ns string) (*types.Capability, error) {
func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName string, ns string, pd *packages.PackageDiscover) (*types.Capability, error) {
var (
foundCapability bool
capability *types.Capability
@@ -357,6 +358,25 @@ func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName stri
}
return capability, nil
}
var wfStepDef v1beta1.WorkflowStepDefinition
err = newClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: capabilityName}, &wfStepDef)
if err == nil {
foundCapability = true
} else if kerrors.IsNotFound(err) {
err = newClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: capabilityName}, &wfStepDef)
if err == nil {
foundCapability = true
}
}
if foundCapability {
capability, err = GetCapabilityByWorkflowStepDefinitionObject(wfStepDef, pd)
if err != nil {
return nil, err
}
return capability, nil
}
if ns == types.DefaultKubeVelaNS {
return nil, fmt.Errorf("could not find %s in namespace %s", capabilityName, ns)
}
@@ -366,7 +386,7 @@ func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName stri
// GetCapabilityByComponentDefinitionObject gets capability by ComponentDefinition object
func GetCapabilityByComponentDefinitionObject(componentDef v1beta1.ComponentDefinition, referenceName string) (*types.Capability, error) {
capability, err := HandleDefinition(componentDef.Name, referenceName, componentDef.Annotations, componentDef.Labels,
componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic)
componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to handle ComponentDefinition")
}
@@ -381,10 +401,25 @@ func GetCapabilityByTraitDefinitionObject(traitDef v1beta1.TraitDefinition) (*ty
err error
)
capability, err = HandleDefinition(traitDef.Name, traitDef.Spec.Reference.Name, traitDef.Annotations, traitDef.Labels,
traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic)
traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to handle TraitDefinition")
}
capability.Namespace = traitDef.Namespace
return &capability, nil
}
// GetCapabilityByWorkflowStepDefinitionObject gets capability by WorkflowStepDefinition object
func GetCapabilityByWorkflowStepDefinitionObject(wfStepDef v1beta1.WorkflowStepDefinition, pd *packages.PackageDiscover) (*types.Capability, error) {
var (
capability types.Capability
err error
)
capability, err = HandleDefinition(wfStepDef.Name, wfStepDef.Spec.Reference.Name, wfStepDef.Annotations, wfStepDef.Labels,
nil, types.TypeWorkflowStep, nil, wfStepDef.Spec.Schematic, pd)
if err != nil {
return nil, errors.Wrap(err, "failed to handle WorkflowStepDefinition")
}
capability.Namespace = wfStepDef.Namespace
return &capability, nil
}

View File

@@ -227,15 +227,15 @@ var _ = Describe("test GetCapabilityByName", func() {
It("get capability", func() {
Context("ComponentDefinition is in the current namespace", func() {
_, err := GetCapabilityByName(ctx, c, component1, ns)
_, err := GetCapabilityByName(ctx, c, component1, ns, nil)
Expect(err).Should(BeNil())
})
Context("ComponentDefinition is in the default namespace", func() {
_, err := GetCapabilityByName(ctx, c, component2, ns)
_, err := GetCapabilityByName(ctx, c, component2, ns, nil)
Expect(err).Should(BeNil())
})
Context("ComponentDefinition is in the default namespace", func() {
cap, err := GetCapabilityByName(ctx, c, component3, ns)
cap, err := GetCapabilityByName(ctx, c, component3, ns, nil)
Expect(err).Should(BeNil())
jsontmp, err := json.Marshal(cap.KubeParameter)
Expect(err).Should(BeNil())
@@ -245,20 +245,20 @@ var _ = Describe("test GetCapabilityByName", func() {
Expect(string(jsontmp)).Should(ContainSubstring("the specific container port num which can accept external request."))
})
Context("ComponentDefinition's workload type is AutoDetectWorkloadDefinition", func() {
_, err := GetCapabilityByName(ctx, c, component4, ns)
_, err := GetCapabilityByName(ctx, c, component4, ns, nil)
Expect(err).Should(BeNil())
})
Context("TraitDefinition is in the current namespace", func() {
_, err := GetCapabilityByName(ctx, c, trait1, ns)
_, err := GetCapabilityByName(ctx, c, trait1, ns, nil)
Expect(err).Should(BeNil())
})
Context("TraitDefinition is in the default namespace", func() {
_, err := GetCapabilityByName(ctx, c, trait2, ns)
_, err := GetCapabilityByName(ctx, c, trait2, ns, nil)
Expect(err).Should(BeNil())
})
Context("TraitDefinition is in the default namespace", func() {
cap, err := GetCapabilityByName(ctx, c, trait3, ns)
cap, err := GetCapabilityByName(ctx, c, trait3, ns, nil)
Expect(err).Should(BeNil())
jsontmp, err := json.Marshal(cap.KubeParameter)
Expect(err).Should(BeNil())
@@ -267,7 +267,7 @@ var _ = Describe("test GetCapabilityByName", func() {
})
Context("capability cloud not be found", func() {
_, err := GetCapabilityByName(ctx, c, "a-component-definition-not-existed", ns)
_, err := GetCapabilityByName(ctx, c, "a-component-definition-not-existed", ns, nil)
Expect(err).Should(HaveOccurred())
})
})

135
references/plugins/i18n.go Normal file
View File

@@ -0,0 +1,135 @@
/*
Copyright 2022 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 plugins
// Language is used to define the language
type Language string
const (
// En is English, the default language
En Language = "English"
// Zh is Chinese
Zh Language = "Chinese"
)
// Definitions are all the words and phrases for internationalization in cli and docs
var Definitions = map[string]map[Language]string{
"Description": {
Zh: "描述",
En: "Description",
},
"Samples": {
Zh: "示例",
En: "Samples",
},
"Specification": {
Zh: "参数说明",
En: "Specification",
},
"AlibabaCloud": {
Zh: "阿里云",
En: "Alibaba Cloud",
},
"AWS": {
Zh: "AWS",
En: "AWS",
},
"Azure": {
Zh: "Azure",
En: "Azure",
},
"Name": {
Zh: "名称",
En: "Name",
},
"Type": {
Zh: "类型",
En: "Type",
},
"Required": {
Zh: "是否必须",
En: "Required",
},
"Default": {
Zh: "默认值",
En: "Default",
},
"WriteConnectionSecretToRefIntroduction": {
Zh: "如果设置了 `writeConnectionSecretToRef`,一个 Kubernetes Secret 将会被创建并且它的数据里有这些键key",
En: "If `writeConnectionSecretToRef` is set, a secret will be generated with these keys as below:",
},
"Outputs": {
Zh: "输出",
En: "Outputs",
},
"Properties": {
Zh: "属性",
En: "Properties",
},
"Terraform_configuration_for_Alibaba_Cloud_ACK_cluster": {
Zh: "用于部署阿里云 ACK 集群的组件说明",
En: "Terraform configuration for Alibaba Cloud ACK cluster",
},
"Terraform_configuration_for_Alibaba_Cloud_Serverless_Kubernetes_(ASK)": {
Zh: "用于部署阿里云 Serverless Kubernetes (ASK) 的组件说明",
En: "Terraform configuration for Alibaba Cloud Serverless Kubernetes (ASK)",
},
"Terraform_configuration_for_Alibaba_Cloud_Elastic_IP": {
Zh: "用于部署阿里云弹性 IP 的组件说明",
En: "Terraform configuration for Alibaba Cloud Elastic IP",
},
"Terraform_configuration_for_Alibaba_Cloud_OSS_object": {
Zh: "用于部署阿里云 OSS 的组件说明",
En: "Terraform configuration for Alibaba Cloud OSS",
},
"Terraform_configuration_for_Alibaba_Cloud_RDS_object": {
Zh: "用于部署阿里云 RDS 的组件说明",
En: "Terraform configuration for Alibaba Cloud RDS",
},
"Terraform_configuration_for_Alibaba_Cloud_Redis": {
Zh: "用于部署阿里云 Redis 的组件说明",
En: "Terraform configuration for Alibaba Cloud Redis",
},
"Terraform_configuration_for_Alibaba_Cloud_SLS_Project": {
Zh: "用于部署阿里云 SLS Project 的组件说明",
En: "Terraform configuration for Alibaba Cloud SLS Project",
},
"Terraform_configuration_for_Alibaba_Cloud_SLS_Store": {
Zh: "用于部署阿里云 SLS Store 的组件说明",
En: "Terraform configuration for Alibaba Cloud SLS Store",
},
"Terraform_configuration_for_Alibaba_Cloud_VPC": {
Zh: "用于部署阿里云 VPC 的组件说明",
En: "Terraform configuration for Alibaba Cloud VPC",
},
"Terraform_configuration_for_Alibaba_Cloud_VSwitch": {
Zh: "用于部署阿里云 VSwitch 的组件说明",
En: "Terraform configuration for Alibaba Cloud VSwitch",
},
"Terraform_configuration_for_AWS_S3": {
Zh: "用于部署 AWS S3 的组件说明",
En: "Terraform configuration for AWS S3",
},
"Terraform_configuration_for_Azure_Database_Mariadb": {
Zh: "用于部署 Azure mariadb 数据库的组件说明",
En: "Terraform configuration for Azure Database Mariadb",
},
"Terraform_configuration_for_Azure_Blob_Storage_Account": {
Zh: "用于部署 Azure Blob Storage 账号的的组件说明",
En: "Terraform configuration for Azure Blob Storage Account",
},
}

View File

@@ -43,6 +43,12 @@ func TestCreateRefTestDir(t *testing.T) {
}
func TestCreateMarkdown(t *testing.T) {
ctx := context.Background()
ref := &MarkdownReference{}
refZh := &MarkdownReference{}
refZh.I18N = Zh
workloadName := "workload1"
traitName := "trait1"
scopeName := "scope1"
@@ -86,11 +92,13 @@ variable "acl" {
cases := map[string]struct {
reason string
ref *MarkdownReference
capabilities []types.Capability
want error
}{
"WorkloadTypeAndTraitCapability": {
reason: "valid capabilities",
ref: ref,
capabilities: []types.Capability{
{
Name: workloadName,
@@ -115,6 +123,7 @@ variable "acl" {
},
"ScopeTypeCapability": {
reason: "invalid capabilities",
ref: ref,
capabilities: []types.Capability{
{
Name: scopeName,
@@ -123,12 +132,24 @@ variable "acl" {
},
want: fmt.Errorf("the type of the capability is not right"),
},
"TerraformCapabilityInChinese": {
reason: "terraform capability",
ref: refZh,
capabilities: []types.Capability{
{
Name: workloadName2,
TerraformConfiguration: configuration,
Type: types.TypeWorkload,
Category: types.TerraformCategory,
},
},
want: nil,
},
}
ref := &MarkdownReference{}
ctx := context.Background()
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
got := ref.CreateMarkdown(ctx, tc.capabilities, RefTestDir, ReferenceSourcePath)
got := tc.ref.CreateMarkdown(ctx, tc.capabilities, RefTestDir, ReferenceSourcePath, nil)
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nCreateMakrdown(...): -want error, +got error:\n%s", tc.reason, diff)
}
@@ -476,7 +497,7 @@ func TestPrepareTerraformOutputs(t *testing.T) {
tableName: "abc",
parameterList: []ReferenceParameter{param},
},
expect: "\n\nabc\n\nName | Description\n------------ | ------------- \n ID | Identity of the cloud resource\n",
expect: "\n\nabc\n\n Name | Description \n ------------ | ------------- \n ID | Identity of the cloud resource\n",
},
}
ref := &MarkdownReference{}
@@ -492,8 +513,15 @@ func TestPrepareTerraformOutputs(t *testing.T) {
func TestMakeReadableTitle(t *testing.T) {
type args struct {
ref *MarkdownReference
title string
}
ref := &MarkdownReference{}
refZh := &MarkdownReference{}
refZh.I18N = Zh
testcases := []struct {
args args
want string
@@ -501,25 +529,49 @@ func TestMakeReadableTitle(t *testing.T) {
{
args: args{
title: "abc",
ref: ref,
},
want: "Abc",
},
{
args: args{
title: "abc-def",
ref: ref,
},
want: "Abc-Def",
},
{
args: args{
title: "alibaba-def-ghi",
ref: ref,
},
want: "Alibaba Cloud DEF-GHI",
},
{
args: args{
title: "alibaba-def-ghi",
ref: refZh,
},
want: "阿里云 DEF-GHI",
},
{
args: args{
title: "aws-jk",
ref: refZh,
},
want: "AWS JK",
},
{
args: args{
title: "azure-jk",
ref: refZh,
},
want: "Azure JK",
},
}
for _, tc := range testcases {
t.Run("", func(t *testing.T) {
title := makeReadableTitle(tc.args.title)
title := tc.args.ref.makeReadableTitle(tc.args.title)
if title != tc.want {
t.Errorf("makeReadableTitle(...): -want, +got:\n%s\n", cmp.Diff(tc.want, title))
}

View File

@@ -36,6 +36,7 @@ import (
"github.com/oam-dev/kubevela/pkg/controller/utils"
velacue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/model"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/terraform"
)
@@ -44,7 +45,9 @@ const (
// BaseRefPath is the target path for reference docs
BaseRefPath = "docs/en/end-user"
// KubeVelaIOTerraformPath is the target path for kubevela.io terraform docs
KubeVelaIOTerraformPath = "kubevela.io/docs/end-user/components/cloud-services/terraform"
KubeVelaIOTerraformPath = "../kubevela.io/docs/end-user/components/cloud-services/terraform"
// KubeVelaIOTerraformPathZh is the target path for kubevela.io terraform docs in Chinese
KubeVelaIOTerraformPathZh = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/components/cloud-services/terraform"
// ReferenceSourcePath is the location for source reference
ReferenceSourcePath = "hack/references"
// ComponentDefinitionTypePath is the URL path for component typed capability
@@ -53,6 +56,8 @@ const (
WorkloadTypePath = "workload-types"
// TraitPath is the URL path for trait typed capability
TraitPath = "traits"
// WorkflowStepPath is the URL path for workflow step typed capability
WorkflowStepPath = "workflowsteps"
)
// Int64Type is int64 type
@@ -72,6 +77,7 @@ type Reference interface {
// ParseReference is used to include the common function `parseParameter`
type ParseReference struct {
Client client.Client
I18N Language `json:"i18n"`
}
// MarkdownReference is the struct for capability information in
@@ -293,6 +299,7 @@ spec:
name: bar
key: bar
`,
"worker": `
apiVersion: core.oam.dev/v1beta1
kind: Application
@@ -324,6 +331,188 @@ spec:
writeConnectionSecretToRef:
name: vpc-conn
`,
"alibaba-rds": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: rds-cloud-source
spec:
components:
- name: sample-db
type: alibaba-rds
properties:
instance_name: sample-db
account_name: oamtest
password: U34rfwefwefffaked
writeConnectionSecretToRef:
name: db-conn
`,
"alibaba-ack": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: ack-cloud-source
spec:
components:
- name: ack-cluster
type: alibaba-ack
properties:
writeConnectionSecretToRef:
name: ack-conn
namespace: vela-system
`,
"alibaba-eip": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: provision-cloud-resource-eip
spec:
components:
- name: sample-eip
type: alibaba-eip
properties:
writeConnectionSecretToRef:
name: eip-conn
`,
"alibaba-oss": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: oss-cloud-source
spec:
components:
- name: sample-oss
type: alibaba-oss
properties:
bucket: vela-website
acl: private
writeConnectionSecretToRef:
name: oss-conn
`,
"alibaba-redis": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: redis-cloud-source
spec:
components:
- name: sample-redis
type: alibaba-redis
properties:
instance_name: oam-redis
account_name: oam
password: Xyfff83jfewGGfaked
writeConnectionSecretToRef:
name: redis-conn
`,
"aws-s3": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: s3-cloud-source
spec:
components:
- name: sample-s3
type: aws-s3
properties:
bucket: vela-website-20211019
acl: private
writeConnectionSecretToRef:
name: s3-conn
`,
"azure-database-mariadb": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: mariadb-backend
spec:
components:
- name: mariadb-backend
type: azure-database-mariadb
properties:
resource_group: "kubevela-group"
location: "West Europe"
server_name: "kubevela"
db_name: "backend"
username: "acctestun"
password: "H@Sh1CoR3!Faked"
writeConnectionSecretToRef:
name: azure-db-conn
namespace: vela-system
`,
"azure-storage-account": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: storage-account-dev
spec:
components:
- name: storage-account-dev
type: azure-storage-account
properties:
create_rsg: false
resource_group_name: "weursgappdev01"
location: "West Europe"
name: "appdev01"
tags: |
{
ApplicationName = "Application01"
Terraform = "Yes"
}
static_website: |
[{
index_document = "index.html"
error_404_document = "index.html"
}]
writeConnectionSecretToRef:
name: storage-account-dev
namespace: vela-system
`,
"alibaba-sls-project": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-sls-project-sample
spec:
components:
- name: sample-sls-project
type: alibaba-sls-project
properties:
name: kubevela-1112
description: "Managed by KubeVela"
writeConnectionSecretToRef:
name: sls-project-conn
`,
"alibaba-sls-store": `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-sls-store-sample
spec:
components:
- name: sample-sls-store
type: alibaba-sls-store
properties:
store_name: kubevela-1111
store_retention_period: 30
store_shard_count: 2
store_max_split_shard_count: 2
writeConnectionSecretToRef:
name: sls-store-conn
`,
}
// BaseOpenAPIV3Template is Standard OpenAPIV3 Template
@@ -370,6 +559,14 @@ func (ref *MarkdownReference) GenerateReferenceDocs(ctx context.Context, c commo
caps []types.Capability
err error
)
config, err := c.GetConfig()
if err != nil {
return err
}
pd, err := packages.NewPackageDiscover(config)
if err != nil {
return err
}
if ref.DefinitionName == "" {
caps, err = LoadAllInstalledCapability("default", c)
@@ -377,20 +574,25 @@ func (ref *MarkdownReference) GenerateReferenceDocs(ctx context.Context, c commo
return fmt.Errorf("failed to get all capabilityes: %w", err)
}
} else {
cap, err := GetCapabilityByName(ctx, c, ref.DefinitionName, namespace)
cap, err := GetCapabilityByName(ctx, c, ref.DefinitionName, namespace, pd)
if err != nil {
return fmt.Errorf("failed to get capability capability %s: %w", ref.DefinitionName, err)
}
caps = []types.Capability{*cap}
}
return ref.CreateMarkdown(ctx, caps, baseRefPath, ReferenceSourcePath)
return ref.CreateMarkdown(ctx, caps, baseRefPath, ReferenceSourcePath, pd)
}
// CreateMarkdown creates markdown based on capabilities
func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.Capability, baseRefPath, referenceSourcePath string) error {
func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.Capability, baseRefPath, referenceSourcePath string, pd *packages.PackageDiscover) error {
setDisplayFormat("markdown")
for i, c := range caps {
var (
description string
sample string
specification string
)
if c.Type != types.TypeWorkload && c.Type != types.TypeComponentDefinition && c.Type != types.TypeTrait {
return fmt.Errorf("the type of the capability is not right")
}
@@ -412,10 +614,14 @@ func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.C
}
capName := c.Name
refContent = ""
capNameInTitle := makeReadableTitle(capName)
lang := ref.I18N
if lang == "" {
lang = En
}
capNameInTitle := ref.makeReadableTitle(capName)
switch c.Category {
case types.CUECategory:
cueValue, err := common.GetCUEParameterValue(c.CueTemplate)
cueValue, err := common.GetCUEParameterValue(c.CueTemplate, pd)
if err != nil {
return fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err)
}
@@ -448,14 +654,18 @@ func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.C
return fmt.Errorf("unsupport capability category %s", c.Category)
}
title := fmt.Sprintf("---\ntitle: %s\n---", capNameInTitle)
description := fmt.Sprintf("\n\n## Description\n\n%s", c.Description)
var sample string
sampleContent := ref.generateSample(capName)
if sampleContent != "" {
sample = fmt.Sprintf("\n\n## Samples\n\n%s", sampleContent)
descriptionI18N := c.Description
des := strings.ReplaceAll(c.Description, " ", "_")
if v, ok := Definitions[des]; ok {
descriptionI18N = v[lang]
}
specification := fmt.Sprintf("\n\n## Specification\n%s", refContent)
description = fmt.Sprintf("\n\n## %s\n\n%s", Definitions["Description"][lang], descriptionI18N)
if sampleContent != "" {
sample = fmt.Sprintf("\n\n## %s\n\n%s", Definitions["Samples"][lang], sampleContent)
}
specification = fmt.Sprintf("\n\n## %s\n%s", Definitions["Specification"][lang], refContent)
// it's fine if the conflict info files not found
conflictWithAndMoreSection, _ := ref.generateConflictWithAndMore(capName, referenceSourcePath)
@@ -471,20 +681,38 @@ func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.C
return nil
}
func makeReadableTitle(title string) string {
const alibabaCloud = "alibaba-"
if strings.HasPrefix(title, alibabaCloud) {
cloudResource := strings.Replace(title, alibabaCloud, "", 1)
return "Alibaba Cloud " + strings.ToUpper(cloudResource)
func (ref *MarkdownReference) makeReadableTitle(title string) string {
if ref.I18N == "" {
ref.I18N = En
}
return strings.Title(title)
if !strings.Contains(title, "-") {
return strings.Title(title)
}
var name string
provider := strings.Split(title, "-")[0]
switch provider {
case "alibaba":
name = "AlibabaCloud"
case "aws":
name = "AWS"
case "azure":
name = "Azure"
default:
return strings.Title(title)
}
cloudResource := strings.Replace(title, provider+"-", "", 1)
return fmt.Sprintf("%s %s", Definitions[name][ref.I18N], strings.ToUpper(cloudResource))
}
// prepareParameter prepares the table content for each property
func (ref *MarkdownReference) prepareParameter(tableName string, parameterList []ReferenceParameter, category types.CapabilityCategory) string {
refContent := fmt.Sprintf("\n\n%s\n\n", tableName)
refContent += "Name | Description | Type | Required | Default \n"
refContent += "------------ | ------------- | ------------- | ------------- | ------------- \n"
lang := ref.I18N
if lang == "" {
lang = En
}
refContent += fmt.Sprintf(" %s | %s | %s | %s | %s \n", Definitions["Name"][lang], Definitions["Description"][lang], Definitions["Type"][lang], Definitions["Required"][lang], Definitions["Default"][lang])
refContent += " ------------ | ------------- | ------------- | ------------- | ------------- \n"
switch category {
case types.CUECategory:
for _, p := range parameterList {
@@ -519,8 +747,12 @@ func (ref *MarkdownReference) prepareTerraformOutputs(tableName string, paramete
return ""
}
refContent := fmt.Sprintf("\n\n%s\n\n", tableName)
refContent += "Name | Description\n"
refContent += "------------ | ------------- \n"
lang := ref.I18N
if lang == "" {
lang = En
}
refContent += fmt.Sprintf(" %s | %s \n", Definitions["Name"][lang], Definitions["Description"][lang])
refContent += " ------------ | ------------- \n"
for _, p := range parameterList {
refContent += fmt.Sprintf(" %s | %s\n", p.Name, p.Usage)
@@ -600,7 +832,7 @@ func (ref *ParseReference) parseParameters(paraValue cue.Value, paramKey string,
param.Type = val.IncompleteKind()
switch val.IncompleteKind() {
case cue.StructKind:
if subField, _ := val.Struct(); subField.Len() == 0 { // err cannot be not nil,so ignore it
if subField, err := val.Struct(); err == nil && subField.Len() == 0 { // err cannot be not nil,so ignore it
if mapValue, ok := val.Elem(); ok {
// In the future we could recursive call to surpport complex map-value(struct or list)
param.PrintableType = fmt.Sprintf("map[string]%s", mapValue.IncompleteKind().String())
@@ -711,11 +943,11 @@ func (ref *MarkdownReference) generateConflictWithAndMore(capabilityName string,
}
// GenerateCUETemplateProperties get all properties of a capability
func (ref *ConsoleReference) GenerateCUETemplateProperties(capability *types.Capability) ([]ConsoleReference, error) {
func (ref *ConsoleReference) GenerateCUETemplateProperties(capability *types.Capability, pd *packages.PackageDiscover) ([]ConsoleReference, error) {
setDisplayFormat("console")
capName := capability.Name
cueValue, err := common.GetCUEParameterValue(capability.CueTemplate)
cueValue, err := common.GetCUEParameterValue(capability.CueTemplate, pd)
if err != nil {
return nil, fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", capName, err)
}
@@ -784,7 +1016,15 @@ func (ref *ParseReference) parseTerraformCapabilityParameters(capability types.C
err error
outputsList []ReferenceParameter
outputsTables []ReferenceParameterTable
propertiesTitle string
outputsTableName string
)
lang := ref.I18N
if lang == "" {
lang = En
}
outputsTableName = fmt.Sprintf("%s %s\n\n%s", strings.Repeat("#", 3), Definitions["Outputs"][lang], Definitions["WriteConnectionSecretToRefIntroduction"][lang])
propertiesTitle = Definitions["Properties"][lang]
writeConnectionSecretToRefReferenceParameter.Name = terraform.TerraformWriteConnectionSecretToRefName
writeConnectionSecretToRefReferenceParameter.PrintableType = terraform.TerraformWriteConnectionSecretToRefType
@@ -809,12 +1049,12 @@ func (ref *ParseReference) parseTerraformCapabilityParameters(capability types.C
refParam.Name = v.Name
refParam.PrintableType = v.Type
refParam.Usage = v.Description
refParam.Required = true
refParam.Required = v.Required
refParameterList = append(refParameterList, refParam)
}
refParameterList = append(refParameterList, writeConnectionSecretToRefReferenceParameter)
propertiesTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 3), "Properties")
propertiesTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 3), propertiesTitle)
tables = append(tables, ReferenceParameterTable{
Name: propertiesTableName,
Parameters: refParameterList,
@@ -852,8 +1092,6 @@ func (ref *ParseReference) parseTerraformCapabilityParameters(capability types.C
outputsList = append(outputsList, refParam)
}
outputsTableName := fmt.Sprintf("%s %s\n\nIf `writeConnectionSecretToRef` is set, a secret will be generated with these keys as below:", strings.Repeat("#", 3), "Outputs")
outputsTables = append(outputsTables, ReferenceParameterTable{
Name: outputsTableName,
Parameters: outputsList,

View File

@@ -1,3 +1,7 @@
import (
"strconv"
)
webservice: {
type: "component"
annotations: {}
@@ -186,6 +190,12 @@ template: {
{
containerPort: v.port
protocol: v.protocol
if v.name != _|_ {
name: v.name
}
if v.name == _|_ {
name: "port-" + strconv.FormatInt(v.port, 10)
}
}}]
}
@@ -297,6 +307,12 @@ template: {
for v in parameter.ports if v.expose == true {
port: v.port
targetPort: v.port
if v.name != _|_ {
name: v.name
}
if v.name == _|_ {
name: "port-" + strconv.FormatInt(v.port, 10)
}
},
]
@@ -341,6 +357,8 @@ template: {
ports?: [...{
// +usage=Number of port to expose on the pod's IP address
port: int
// +usage=Name of the port
name?: string
// +usage=Protocol for port. Must be UDP, TCP, or SCTP
protocol: *"TCP" | "UDP" | "SCTP"
// +usage=Specify if the port should be exposed

View File

@@ -1,4 +1,4 @@
"gateway": {
gateway: {
type: "trait"
annotations: {}
labels: {}
@@ -49,22 +49,29 @@ template: {
metadata: {
name: context.name
annotations: {
"kubernetes.io/ingress.class": parameter.class
if !parameter.classInSpec {
"kubernetes.io/ingress.class": parameter.class
}
}
}
spec: rules: [{
host: parameter.domain
http: paths: [
for k, v in parameter.http {
path: k
pathType: "ImplementationSpecific"
backend: service: {
name: context.name
port: number: v
}
},
]
}]
spec: {
if parameter.classInSpec {
ingressClassName: parameter.class
}
rules: [{
host: parameter.domain
http: paths: [
for k, v in parameter.http {
path: k
pathType: "ImplementationSpecific"
backend: service: {
name: context.name
port: number: v
}
},
]
}]
}
}
parameter: {
@@ -76,5 +83,8 @@ template: {
// +usage=Specify the class of ingress to use
class: *"nginx" | string
// +usage=Set ingress class in '.spec.ingressClassName' instead of 'kubernetes.io/ingress.class' annotation.
classInSpec: *false | bool
}
}