Compare commits

...

60 Commits

Author SHA1 Message Date
Tianxin Dong
62ecc70ade Fix: fix token invalid after the server restarted (#3658)
* Fix: fix token invalid after the server restarted

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix lint

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Pending test temporary

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Pending test temporary

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-04-14 22:25:43 +08:00
Somefive
5857aa8790 Fix: vela status tree show cluster alias & raw format (#3659)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-14 19:35:47 +08:00
ZhongsJie
49646ddc8e Feat: enhance storage trait to support multi-mountToEnv config Signed-off-by: Shijie Zhong <zhongsjie@cmbchina.com> (#3657)
Signed-off-by: ZhongsJie <zhongsjie@gmail.com>
2022-04-14 19:34:12 +08:00
Jianbo Sun
707905d877 Fix: add label from inner system in CR can prevent sync (#3655)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-04-14 19:32:53 +08:00
barnettZQG
7d3ef0595a Fix: duplicately list pods in velaQL (#3650)
* Fix: duplicately list pods in velaQL

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: the create time of synced app is empty

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-04-14 17:43:54 +08:00
Zheng Xi Zhou
af6dc4bda3 Fix: failed to deploy application when no there is no avaiable (#3652)
When there are configs, but not in the project where the appliation
is about to deploy, the sync application will hit an issue. It will
lead to block the deploy of an application.

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-04-14 17:25:34 +08:00
Zheng Xi Zhou
f44bd7c6dd Fix: refine the config sync logic (#3602)
* Fix: refine config management

- Refine the config sync logics

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

* address comments

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-04-14 13:03:59 +08:00
wyike
eaec8348d9 Fix: try to fix CVE (#3641)
* try to fix cve

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

* add parse input func

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

fix lint

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

fix lint

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

* use santize

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-04-14 10:03:15 +08:00
wyike
2849dfc1fb Fix: clear info when addon version cannot meet require (#3642)
* first

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

version miss match erro for addon

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

add log

* add test for this

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

small fix
2022-04-14 10:02:47 +08:00
Somefive
d657ea4daf Feat: rework vela up to support specified revision (#3634)
* Feat: rework vela up to support specified revision

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Fix: add legacy compatibility

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Feat: fix test

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-13 22:20:07 +08:00
Zhiyu Wang
68500b3f17 Fix: verify password valid (#3545)
Signed-off-by: Zhiyu Wang <zhiyuwang.newbis@gmail.com>
2022-04-13 19:39:00 +08:00
Somefive
c33eaa0609 Fix: enhance vela status tree print (#3639)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-13 17:46:55 +08:00
Somefive
7a0d2b552b Feat: support alias in cluster (#3630)
* Feat: support alias in cluster

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Fix: add test for cluster alias

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-13 13:58:35 +08:00
Zheng Xi Zhou
385b2462e9 Feat: refine config creation and provide config list (#3637)
- Make the api of creation a config to be async
- In listing config page, show the status of a config

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-04-13 13:45:41 +08:00
Kunshuai Zhu
0c35753530 Feat: allow select resource by resource type for gc policy (#3598)
* Feat: allow select resource by resource type for gc policy

Signed-off-by: zhukunshuai <jookunshuai@gmail.com>

* Modify match label key

Signed-off-by: zhukunshuai <jookunshuai@gmail.com>

* Unified label key

Signed-off-by: zhukunshuai <jookunshuai@gmail.com>
2022-04-12 18:51:07 +08:00
Somefive
0e97aa2291 Feat: vela status --tree (#3609)
* Feat: vela status --tree

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Feat: support show not-deployed clusters

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Fix: add tests

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Fix: add multicluster e2e coverage

Signed-off-by: Somefive <yd219913@alibaba-inc.com>

* Chore: minor fix

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-12 17:58:45 +08:00
Tianxin Dong
7fcb89906c Fix: fix dex login with existed email (#3623)
* Fix: fix dex login with existed email

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* add dex connector check

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* unset users' alias

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix ut

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix ut

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-04-12 16:29:21 +08:00
Somefive
86ef2d68e0 Fix: flags for controller (#3627)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-12 16:12:50 +08:00
wyike
2e57be1022 Feat: support basic auth private helm repo (#3595)
* support auth

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

* add test

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

fix check diff

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

fix test

fix

add comments

fix test

* add tests

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

fix

add more test

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

* add more test

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

* extract set auth info as a global func

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

* return bcode

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-04-12 14:33:44 +08:00
Gallardot
ad01f3062a Feat: add HealthProbe for sidecar (#3629)
* Feat: add HealthProbe for sidecar

Signed-off-by: Gallardot <tttick@163.com>

* Feat: add HealthProbe for sidecar

Signed-off-by: Gallardot <tttick@163.com>
2022-04-12 13:21:51 +08:00
Somefive
b6fac3f4d5 Fix: cli default switch on feature flags (#3625)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-11 20:40:13 +08:00
Min Kim
2eb7826070 bump cluster-gateway to 1.3.2 (#3619)
Signed-off-by: yue9944882 <291271447@qq.com>
2022-04-11 19:31:51 +08:00
Zheng Xi Zhou
5f7371815c Feat: add api of listing configs for project when creating a target (#3581)
* Feat: add api of listing configs for project

In a project, list configs by its type

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

* address comments

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

* fix ci

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

* add query parameter definition

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

* Update pkg/apiserver/rest/webservice/project.go

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-04-11 19:05:18 +08:00
wyike
d6b96fee5a Fix: add e2e apiserver test for addon (#3607)
* add e2e apiserver test for addon

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

delete comment

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

fix

* fix test

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

* close the reponse body

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

* cover list enabled addon

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

fix e2e test

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

fix test

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

fix test

* fix test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-04-11 17:46:46 +08:00
Jianbo Sun
85c673a574 Fix: reuse chart values in vela install (#3616)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-04-11 09:50:18 +08:00
qiaozp
7e6d9ccc73 Fix: vela logs without specified resource name (#3605)
* Fix: vela logs without specified resource name

Signed-off-by: qiaozp <chivalry.pp@gmail.com>

* add unittest

Signed-off-by: qiaozp <chivalry.pp@gmail.com>

* reviewable

Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2022-04-08 17:22:05 +08:00
Avery
e65dcf12db add sorting for properties, outputs, writeSecretRefParameters in vela def doc-gen (#3593)
Signed-off-by: Nicola115 <2225992901@qq.com>
2022-04-08 15:27:35 +08:00
Diego Pinheiro
fd5faed71a Chore: Nominate s4rd1nh4 as a Reviewer member (#3603)
Signed-off-by: GitHub <noreply@github.com>
2022-04-08 13:21:45 +08:00
Zheng Xi Zhou
b1823084af Fix: add terraform aws provider without AWS_SESSION_TOKEN (#3590)
* Fix: add terraform aws provider without AWS_SESSION_TOKEN

Fix #3589 and refine prompts for cli

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-04-07 13:41:29 +08:00
namo
83fe4a160e Feat(lang): add addons gitlab support (#3543)
* add addons gitlab support

Signed-off-by: Namo <lgj112113@163.com>

* add addons gitlab support

Signed-off-by: Namo <lgj112113@163.com>

* test file edit

Signed-off-by: Namo <lgj112113@163.com>

* typo edit

Signed-off-by: Namo <lgj112113@163.com>

* notes edit

Signed-off-by: Namo <lgj112113@163.com>

* move third party imports block

Signed-off-by: Namo <lgj112113@163.com>

* code format edit

Signed-off-by: Namo <lgj112113@163.com>

* notes edit

Signed-off-by: Namo <lgj112113@163.com>

* create addon registry bug fix

Signed-off-by: Namo <lgj112113@163.com>

* add gitlab addon registry bug fix

Signed-off-by: Namo <lgj112113@163.com>

* add addon gitlab support test file

Signed-off-by: Namo <lgj112113@163.com>

* add addon gitlab support test note edit

Signed-off-by: Namo <lgj112113@163.com>

* add addon gitlab branch support and fix bug

Signed-off-by: Namo <lgj112113@163.com>

* addon gitlab registry repo name invalid

Signed-off-by: Namo <lgj112113@163.com>

Co-authored-by: Namo <lgj112113@163.com>
2022-04-07 11:05:14 +08:00
Somefive
8af3dec0df Fix: add feature-gates to command-line args (#3591)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-04-06 17:00:44 +08:00
Tianxin Dong
edebcc6c59 Fix: fix refresh code expired bcode (#3582)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-04-06 14:36:05 +08:00
Jianbo Sun
32382ba6be Chore: add new code flow for kubevela milestone (#3586)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-04-06 13:34:30 +08:00
Zheng Xi Zhou
46ef6f9df4 Fix: add response struct to config deletion api (#3579)
Added an empty struct in the API of config deletion

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-04-06 13:32:49 +08:00
Jianbo Sun
aea98ff5bf Chore: try fix lint (#3571)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-04-06 10:19:53 +08:00
wyike
c093676575 aglin config secret label (#3576)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix not update label

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

fix addon cannot update label bug

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

revert
2022-04-01 16:59:15 +08:00
wyike
ed05b4b035 aglin config secret label (#3574)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-04-01 16:13:12 +08:00
Zheng Xi Zhou
3aa4412a0f Fix: remove config image registry (#3572)
Temporarily removed image registry config

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-04-01 01:07:43 +08:00
wyike
ef4b9816e1 fix bug (#3569)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix bugs

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

fix

fix
2022-04-01 01:04:29 +08:00
Tianxin Dong
1c5aab1852 Fix: fix dex config field (#3568)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-04-01 00:51:17 +08:00
Zheng Xi Zhou
966dbc1c74 Feat: add config management apis (#3562)
* Feat: add config management apis

Added some APIs for config management

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

* fix check-diff

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

* fix ci issue

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

* fix config sync

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

* fix static check

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

* fix sync

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

* Fix: sync config bug

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-04-01 00:15:03 +08:00
qiaozp
4eafb46c87 Chore: bump test k8s to 1.20 (#3567)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2022-03-31 22:12:55 +08:00
Tianxin Dong
a97a4d0ed7 Feat: add update dex config in apiserver (#3548)
* Feat: add update dex config in apiserver

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* use get connectors to get dex connectors

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* lint the code

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Fix: seperate dex config from a component to an application

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Fix: use dex config from secret

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix not found

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix restart dex

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix system info

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix restart

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-31 18:42:18 +08:00
Jianbo Sun
77c02f9eec Chore: add video records in readme for chinese community call (#3565)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-31 18:05:22 +08:00
Jianbo Sun
3157efd421 Chore: refine chart readme and notes (#3563)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-31 17:33:06 +08:00
wyike
8ff93b33e2 Feat: add helm repo list endpoint (#3564)
* add helm repo list

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

* fix commit

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

* fix comments

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

* build swagger

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-31 17:18:54 +08:00
qiaozp
c6b9abe4c4 Chore: bump k8s version contraint (#3560)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2022-03-31 16:42:14 +08:00
Somefive
150ef6e99e Fix: livediff minor bug (#3558)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-31 13:13:55 +08:00
wyike
0ada407fbe optimize the ux of addon (#3557)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix

fix
2022-03-31 10:24:20 +08:00
Somefive
c4af1ba643 Fix: topology use original resource namespace when not specify (#3554)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-30 19:52:10 +08:00
qiaozp
de84421487 Feat: add name param in vela logs (#3556)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2022-03-30 17:56:59 +08:00
yangs
38a8a7f88a Fix: fix the componentDefinition webservice parameter error (#3553)
Signed-off-by: yangsoon <songyang.song@alibaba-inc.com>

Co-authored-by: yangsoon <songyang.song@alibaba-inc.com>
2022-03-30 15:55:49 +08:00
Xiangbo Ma
b4ddf0e4c3 Feat: a new ComponentDefinition cron-task Signed-off-by: Xiangbo Ma <maxiangboo@cmbchina.com> (#3541)
Signed-off-by: fourierr <maxiangboo@qq.com>
2022-03-30 13:27:21 +08:00
wyike
47050c90b6 Fix: cli addon multicluster bug (#3547)
* fix addon multicluster bug

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

delete useless test

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

asd

asd

* fix test race condition

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-29 23:03:57 +08:00
barnettZQG
685d73a20c Feat: support with metadata as context render cue file in the addon (#3549)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-29 22:13:56 +08:00
Somefive
dde8a8e4c0 Fix: addon support empty components (#3542)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-29 16:42:43 +08:00
Min Kim
943af3ddf6 bump cluster-gateway to v1.3.0 (#3540)
Signed-off-by: yue9944882 <291271447@qq.com>
2022-03-29 16:18:38 +08:00
Somefive
a5c2edf777 Feat: allow select resource by component name for gc policy (#3539)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-29 14:00:50 +08:00
wyike
83d8022ce9 quick fix addon registry (#3537)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-29 12:40:25 +08:00
Zheng Xi Zhou
6d63014c6f Feat: support config management (#3430)
* Feat: componentDefinitions for Config management

Added ComponentDefinitions for config management
- helm chart repository
- image registry
- Dex connector
- Terraform provider

Co-authored-by: Tianxin Dong <wuwuglu19@gmail.com>
Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* Update vela-templates/definitions/internal/component/config-image-registry.cue

Co-authored-by: Jianbo Sun <wonderflow@icloud.com>

* address comments again

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

Co-authored-by: Tianxin Dong <wuwuglu19@gmail.com>
Co-authored-by: Jianbo Sun <wonderflow@icloud.com>
2022-03-29 10:28:07 +08:00
151 changed files with 8314 additions and 716 deletions

View File

@@ -65,7 +65,7 @@ jobs:
- name: Setup Kind Cluster (Worker)
run: |
kind delete cluster --name worker
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
kubectl version
kubectl cluster-info
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
@@ -74,7 +74,7 @@ jobs:
- name: Setup Kind Cluster (Hub)
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info

View File

@@ -60,7 +60,7 @@ jobs:
- name: Setup Kind Cluster (Worker)
run: |
kind delete cluster --name worker
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
kubectl version
kubectl cluster-info
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
@@ -69,7 +69,7 @@ jobs:
- name: Setup Kind Cluster (Hub)
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info
@@ -96,7 +96,7 @@ jobs:
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: /tmp/e2e-profile.out
files: /tmp/e2e-profile.out,/tmp/e2e_multicluster_test.out
flags: e2e-multicluster-test
name: codecov-umbrella

View File

@@ -60,7 +60,7 @@ jobs:
- name: Setup Kind Cluster
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info

View File

@@ -60,7 +60,7 @@ jobs:
- name: Setup Kind Cluster
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info

View File

@@ -71,6 +71,11 @@ jobs:
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Checkout
uses: actions/checkout@v2
with:
@@ -88,7 +93,7 @@ jobs:
# version, but we prefer this action because it leaves 'annotations' (i.e.
# it comments on PRs to point out linter violations).
- name: Lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
version: ${{ env.GOLANGCI_VERSION }}

View File

@@ -10,6 +10,7 @@ Reviewers:
- devholic
- fourierr
- JooKS-me
- s4rd1nh4
Approvers:
- Somefive (Multi-Cluster)

View File

@@ -51,7 +51,8 @@ Full documentation is available on the [KubeVela website](https://kubevela.io/).
- Wechat Group (*Chinese*): Broker wechat to add you into the user group.
<img src="https://static.kubevela.net/images/barnett-wechat.jpg" width="200" />
- Bi-weekly Community Call: [Meeting Notes](https://docs.google.com/document/d/1nqdFEyULekyksFHtFvgvFAYE-0AMHKoS3RMnaKsarjs)
- Bi-weekly Community Call: [Meeting Notes](https://docs.google.com/document/d/1nqdFEyULekyksFHtFvgvFAYE-0AMHKoS3RMnaKsarjs).
- Bi-weekly Chinese Community Call: [Video Records](https://space.bilibili.com/180074935/channel/seriesdetail?sid=1842207).
## Talks and Conferences

View File

@@ -45,11 +45,13 @@ type GarbageCollectPolicyRule struct {
}
// GarbageCollectPolicyRuleSelector select the targets of the rule
// if both traitTypes and componentTypes are specified, combination logic is OR
// if one resources are specified with conflict strategy, strategy as component go first.
// if both traitTypes, oamTypes and componentTypes are specified, combination logic is OR
// if one resource is specified with conflict strategies, strategy as component go first.
type GarbageCollectPolicyRuleSelector struct {
TraitTypes []string `json:"traitTypes"`
CompTypes []string `json:"componentTypes"`
CompNames []string `json:"componentNames"`
CompTypes []string `json:"componentTypes"`
OAMResourceTypes []string `json:"oamTypes"`
TraitTypes []string `json:"traitTypes"`
}
// GarbageCollectStrategy the strategy for target resource to recycle
@@ -68,27 +70,24 @@ const (
// FindStrategy find gc strategy for target resource
func (in GarbageCollectPolicySpec) FindStrategy(manifest *unstructured.Unstructured) *GarbageCollectStrategy {
for _, rule := range in.Rules {
var (
compType string
traitType string
)
if manifest.GetLabels() != nil {
traitType = manifest.GetLabels()[oam.TraitTypeLabel]
compType = manifest.GetLabels()[oam.WorkloadTypeLabel]
var compName, compType, oamType, traitType string
if labels := manifest.GetLabels(); labels != nil {
compName = labels[oam.LabelAppComponent]
compType = labels[oam.WorkloadTypeLabel]
oamType = labels[oam.LabelOAMResourceType]
traitType = labels[oam.TraitTypeLabel]
}
if compType != "" {
for _, _compType := range rule.Selector.CompTypes {
if _compType == compType {
return &rule.Strategy
}
match := func(src []string, val string) (found bool) {
for _, _val := range src {
found = found || _val == val
}
return val != "" && found
}
if traitType != "" {
for _, _traitType := range rule.Selector.TraitTypes {
if _traitType == traitType {
return &rule.Strategy
}
}
if match(rule.Selector.CompNames, compName) ||
match(rule.Selector.CompTypes, compType) ||
match(rule.Selector.OAMResourceTypes, oamType) ||
match(rule.Selector.TraitTypes, traitType) {
return &rule.Strategy
}
}
return nil

View File

@@ -32,7 +32,7 @@ func TestGarbageCollectPolicySpec_FindStrategy(t *testing.T) {
notFound bool
expectStrategy GarbageCollectStrategy
}{
"trait rule match": {
"trait type rule match": {
rules: []GarbageCollectPolicyRule{{
Selector: GarbageCollectPolicyRuleSelector{TraitTypes: []string{"a"}},
Strategy: GarbageCollectStrategyNever,
@@ -44,7 +44,7 @@ func TestGarbageCollectPolicySpec_FindStrategy(t *testing.T) {
}},
expectStrategy: GarbageCollectStrategyNever,
},
"trait rule mismatch": {
"trait type rule mismatch": {
rules: []GarbageCollectPolicyRule{{
Selector: GarbageCollectPolicyRuleSelector{TraitTypes: []string{"a"}},
Strategy: GarbageCollectStrategyNever,
@@ -52,7 +52,7 @@ func TestGarbageCollectPolicySpec_FindStrategy(t *testing.T) {
input: &unstructured.Unstructured{Object: map[string]interface{}{}},
notFound: true,
},
"trait rule multiple match": {
"trait type rule multiple match": {
rules: []GarbageCollectPolicyRule{{
Selector: GarbageCollectPolicyRuleSelector{TraitTypes: []string{"a"}},
Strategy: GarbageCollectStrategyOnAppDelete,
@@ -67,7 +67,7 @@ func TestGarbageCollectPolicySpec_FindStrategy(t *testing.T) {
}},
expectStrategy: GarbageCollectStrategyOnAppDelete,
},
"component rule match": {
"component type rule match": {
rules: []GarbageCollectPolicyRule{{
Selector: GarbageCollectPolicyRuleSelector{CompTypes: []string{"comp"}},
Strategy: GarbageCollectStrategyNever,
@@ -79,7 +79,7 @@ func TestGarbageCollectPolicySpec_FindStrategy(t *testing.T) {
}},
expectStrategy: GarbageCollectStrategyNever,
},
"rule match both component and trait, component first": {
"rule match both component type and trait type, component type first": {
rules: []GarbageCollectPolicyRule{
{
Selector: GarbageCollectPolicyRuleSelector{CompTypes: []string{"comp"}},
@@ -97,6 +97,30 @@ func TestGarbageCollectPolicySpec_FindStrategy(t *testing.T) {
}},
expectStrategy: GarbageCollectStrategyNever,
},
"component name rule match": {
rules: []GarbageCollectPolicyRule{{
Selector: GarbageCollectPolicyRuleSelector{CompNames: []string{"comp-name"}},
Strategy: GarbageCollectStrategyNever,
}},
input: &unstructured.Unstructured{Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{oam.LabelAppComponent: "comp-name"},
},
}},
expectStrategy: GarbageCollectStrategyNever,
},
"resource type rule match": {
rules: []GarbageCollectPolicyRule{{
Selector: GarbageCollectPolicyRuleSelector{OAMResourceTypes: []string{"TRAIT"}},
Strategy: GarbageCollectStrategyNever,
}},
input: &unstructured.Unstructured{Object: map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{oam.LabelOAMResourceType: "TRAIT"},
},
}},
expectStrategy: GarbageCollectStrategyNever,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {

View File

@@ -47,7 +47,7 @@ var (
// Workflow meta
var (
WorkflowKind = "Workflow"
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(PolicyKind)
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(WorkflowKind)
)
func init() {

View File

@@ -281,8 +281,8 @@ func (in *GarbageCollectPolicyRule) DeepCopy() *GarbageCollectPolicyRule {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GarbageCollectPolicyRuleSelector) DeepCopyInto(out *GarbageCollectPolicyRuleSelector) {
*out = *in
if in.TraitTypes != nil {
in, out := &in.TraitTypes, &out.TraitTypes
if in.CompNames != nil {
in, out := &in.CompNames, &out.CompNames
*out = make([]string, len(*in))
copy(*out, *in)
}
@@ -291,6 +291,16 @@ func (in *GarbageCollectPolicyRuleSelector) DeepCopyInto(out *GarbageCollectPoli
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.OAMResourceTypes != nil {
in, out := &in.OAMResourceTypes, &out.OAMResourceTypes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TraitTypes != nil {
in, out := &in.TraitTypes, &out.TraitTypes
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GarbageCollectPolicyRuleSelector.

View File

@@ -16,7 +16,10 @@ limitations under the License.
package types
import "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
import (
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
"github.com/oam-dev/cluster-gateway/pkg/config"
)
const (
// CredentialTypeInternal identifies the virtual cluster from internal kubevela system
@@ -29,3 +32,8 @@ const (
// ClustersArg indicates the argument for specific clusters to install addon
ClustersArg = "clusters"
)
var (
// AnnotationClusterAlias the annotation key for cluster alias
AnnotationClusterAlias = config.MetaApiGroupName + "/cluster-alias"
)

View File

@@ -61,6 +61,22 @@ const (
AnnoIngressControllerHTTPSPort = "ingress.controller/https-port"
// AnnoIngressControllerHTTPPort define ingress controller listen port for http
AnnoIngressControllerHTTPPort = "ingress.controller/http-port"
// LabelConfigType is the label for config type
LabelConfigType = "config.oam.dev/type"
// LabelConfigCatalog is the label for config catalog
LabelConfigCatalog = "config.oam.dev/catalog"
// LabelConfigSubType is the sub-type for a config type
LabelConfigSubType = "config.oam.dev/sub-type"
// LabelConfigProject is the label for config project
LabelConfigProject = "config.oam.dev/project"
// LabelConfigSyncToMultiCluster is the label to decide whether a config will be synchronized to multi-cluster
LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster"
// LabelConfigIdentifier is the label for config identifier
LabelConfigIdentifier = "config.oam.dev/identifier"
// LabelConfigDescription is the label for config description
LabelConfigDescription = "config.oam.dev/description"
// AnnotationConfigAlias is the annotation for config alias
AnnotationConfigAlias = "config.oam.dev/alias"
)
const (
@@ -118,3 +134,17 @@ var DefaultFilterAnnots = []string{
oam.AnnotationFilterAnnotationKeys,
oam.AnnotationLastAppliedConfiguration,
}
// ConfigType is the type of config
type ConfigType string
const (
// TerraformProvider is the config type for terraform provider
TerraformProvider = "terraform-provider"
// DexConnector is the config type for dex connector
DexConnector = "config-dex-connector"
// ImageRegistry is the config type for image registry
ImageRegistry = "config-image-registry"
// HelmRepository is the config type for Helm chart repository
HelmRepository = "config-helm-repository"
)

View File

@@ -86,7 +86,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
| `multicluster.clusterGateway.replicaCount` | ClusterGateway replica count | `1` |
| `multicluster.clusterGateway.port` | ClusterGateway port | `9443` |
| `multicluster.clusterGateway.image.repository` | ClusterGateway image repository | `oamdev/cluster-gateway` |
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.1.7` |
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.3.2` |
| `multicluster.clusterGateway.image.pullPolicy` | ClusterGateway image pull policy | `IfNotPresent` |
| `multicluster.clusterGateway.resources.limits.cpu` | ClusterGateway cpu limit | `100m` |
| `multicluster.clusterGateway.resources.limits.memory` | ClusterGateway memory limit | `200Mi` |
@@ -125,18 +125,20 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
| `kubeClient.burst` | The burst for reconcile clients, default is 100 | `100` |
## Uninstalling the Chart
## Uninstallation
To uninstall/delete the KubeVela helm release
### Vela CLI
To uninstall KubeVela, you can just run the following command by vela CLI:
```shell
$ helm uninstall -n vela-system kubevela
vela uninstall --force
```
The command removes all the Kubernetes components associated with kubevela and deletes the release.
### Helm CLI
**Notice**: You must disable all the addons before uninstallation, this is a script for convenience.
**Notice**: If you enable fluxcd addon when install the chart by set `enableFluxcdAddon=true` .Uninstall wouldn't disable the fluxcd addon ,and it will be kept in the cluster.Please guarantee there is no application in cluster use this addon and disable it firstly before uninstall the helm chart.
You can use this script to disable all addons.
```shell
#! /bin/sh
addon=$(vela addon list|grep enabled|awk {'print $1'})
@@ -156,6 +158,15 @@ if [ $fluxcd ]; then
fi
```
To uninstall the KubeVela helm release:
```shell
$ helm uninstall -n vela-system kubevela
```
Finally, this command will remove all the Kubernetes resources associated with KubeVela and remove this chart release.

View File

@@ -27,9 +27,5 @@ Welcome to use the KubeVela! Enjoy your shipping application journey!
| . \| |_| || |_) || __/ \ V /| __/| || (_| |
|_|\_\\__,_||_.__/ \___| \_/ \___||_| \__,_|
** Please note before uninstalling **
If you enable fluxcd addon when install the chart by set `enableFluxcdAddon=true` .
Uninstall wouldn't disable the fluxcd addon ,and it will be kept in the cluster.
Please guarantee there is no application in cluster using this addon and disable it firstly before uninstall the helm chart.
And you can find the script of one-short disable all addons from the uninstalling section of https://github.com/oam-dev/kubevela/blob/master/charts/vela-core/README.md.
You can refer to https://kubevela.io for more details.

View File

@@ -8,7 +8,7 @@ data:
"KubeVela":{
"name": "KubeVela",
"helm": {
"url": "https://addons.kubevela.net",
"url": "https://addons.kubevela.net"
}
}
}'

View File

@@ -0,0 +1,69 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/config-image-registry.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Image Registry
definition.oam.dev/description: Config information to authenticate image registry
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
custom.definition.oam.dev/type.config.oam.dev: image-registry
name: config-image-registry
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
import (
"encoding/base64"
"encoding/json"
)
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: context.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "image-registry"
"config.oam.dev/multi-cluster": "true"
"config.oam.dev/identifier": parameter.registry
"config.oam.dev/sub-type": "auth"
}
}
type: "kubernetes.io/dockerconfigjson"
stringData: {
if parameter.auth != _|_ {
".dockerconfigjson": json.Marshal({
auths: "\(parameter.registry)": {
username: parameter.auth.username
password: parameter.auth.password
if parameter.auth.email != _|_ {
email: parameter.auth.email
}
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
}
})
}
}
}
parameter: {
// +usage=Image registry FQDN
registry: string
// +usage=Authenticate the image registry
auth?: {
// +usage=Private Image registry username
username: string
// +usage=Private Image registry password
password: string
// +usage=Private Image registry email
email?: string
}
}
workload:
type: autodetects.core.oam.dev

View File

@@ -0,0 +1,308 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/cron-task.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: Describes cron jobs that run code or a script to completion.
name: cron-task
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
output: {
apiVersion: "batch/v1beta1"
kind: "CronJob"
spec: {
schedule: parameter.schedule
concurrencyPolicy: parameter.concurrencyPolicy
suspend: parameter.suspend
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
if parameter.startingDeadlineSeconds != _|_ {
startingDeadlineSeconds: parameter.startingDeadlineSeconds
}
jobTemplate: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
parallelism: parameter.count
completions: parameter.count
if parameter.ttlSecondsAfterFinished != _|_ {
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
}
if parameter.activeDeadlineSeconds != _|_ {
activeDeadlineSeconds: parameter.activeDeadlineSeconds
}
backoffLimit: parameter.backoffLimit
template: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
restartPolicy: parameter.restart
containers: [{
name: context.name
image: parameter.image
if parameter["imagePullPolicy"] != _|_ {
imagePullPolicy: parameter.imagePullPolicy
}
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
if parameter["env"] != _|_ {
env: parameter.env
}
if parameter["cpu"] != _|_ {
resources: {
limits: cpu: parameter.cpu
requests: cpu: parameter.cpu
}
}
if parameter["memory"] != _|_ {
resources: {
limits: memory: parameter.memory
requests: memory: parameter.memory
}
}
if parameter["volumes"] != _|_ {
volumeMounts: [ for v in parameter.volumes {
{
mountPath: v.mountPath
name: v.name
}}]
}
}]
if parameter["volumes"] != _|_ {
volumes: [ for v in parameter.volumes {
{
name: v.name
if v.type == "pvc" {
persistentVolumeClaim: claimName: v.claimName
}
if v.type == "configMap" {
configMap: {
defaultMode: v.defaultMode
name: v.cmName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "secret" {
secret: {
defaultMode: v.defaultMode
secretName: v.secretName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "emptyDir" {
emptyDir: medium: v.medium
}
}}]
}
if parameter["imagePullSecrets"] != _|_ {
imagePullSecrets: [ for v in parameter.imagePullSecrets {
name: v
},
]
}
if parameter.hostAliases != _|_ {
hostAliases: [ for v in parameter.hostAliases {
ip: v.ip
hostnames: v.hostnames
},
]
}
}
}
}
}
}
}
parameter: {
// +usage=Specify the labels in the workload
labels?: [string]: string
// +usage=Specify the annotations in the workload
annotations?: [string]: string
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
schedule: string
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
startingDeadlineSeconds?: int
// +usage=suspend subsequent executions
suspend: *false | bool
// +usage=Specifies how to treat concurrent executions of a Job
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
// +usage=The number of successful finished jobs to retain
successfulJobsHistoryLimit: *3 | int
// +usage=The number of failed finished jobs to retain
failedJobsHistoryLimit: *1 | int
// +usage=Specify number of tasks to run in parallel
// +short=c
count: *1 | int
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Specify image pull policy for your service
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
// +usage=Specify image pull secrets for your service
imagePullSecrets?: [...string]
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
restart: *"Never" | string
// +usage=Commands to run in the container
cmd?: [...string]
// +usage=Define arguments by using environment variables
env?: [...{
// +usage=Environment variable name
name: string
// +usage=The value of the environment variable
value?: string
// +usage=Specifies a source the value of this var should come from
valueFrom?: {
// +usage=Selects a key of a secret in the pod's namespace
secretKeyRef: {
// +usage=The name of the secret in the pod's namespace to select from
name: string
// +usage=The key of the secret to select from. Must be a valid secret key
key: string
}
// +usage=Selects a key of a config map in the pod's namespace
configMapKeyRef: {
// +usage=The name of the config map in the pod's namespace to select from
name: string
// +usage=The key of the config map to select from. Must be a valid secret key
key: string
}
}
}]
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
cpu?: string
// +usage=Specifies the attributes of the memory resource required for the container.
memory?: string
// +usage=Declare volumes and volumeMounts
volumes?: [...{
name: string
mountPath: string
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir"
type: "pvc" | "configMap" | "secret" | "emptyDir"
if type == "pvc" {
claimName: string
}
if type == "configMap" {
defaultMode: *420 | int
cmName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "secret" {
defaultMode: *420 | int
secretName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "emptyDir" {
medium: *"" | "Memory"
}
}]
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
hostAliases?: [...{
ip: string
hostnames: [...string]
}]
// +usage=Limits the lifetime of a Job that has finished
ttlSecondsAfterFinished?: int
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
activeDeadlineSeconds?: int
// +usage=The number of retries before marking this job failed
backoffLimit: *6 | int
// +usage=Instructions for assessing whether the container is alive.
livenessProbe?: #HealthProbe
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
}
#HealthProbe: {
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
exec?: {
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
command: [...string]
}
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
httpGet?: {
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
path: string
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
port: int
httpHeaders?: [...{
name: string
value: string
}]
}
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
tcpSocket?: {
// +usage=The TCP socket within the container that should be probed to assess container health.
port: int
}
// +usage=Number of seconds after the container is started before the first probe is initiated.
initialDelaySeconds: *0 | int
// +usage=How often, in seconds, to execute the probe.
periodSeconds: *10 | int
// +usage=Number of seconds after which the probe times out.
timeoutSeconds: *1 | int
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
successThreshold: *1 | int
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}
workload:
definition:
apiVersion: batch/v1beta1
kind: CronJob
type: cronjobs.batch

View File

@@ -35,6 +35,13 @@ spec:
}
}]
}
if parameter["livenessProbe"] != _|_ {
livenessProbe: parameter.livenessProbe
}
if parameter["readinessProbe"] != _|_ {
readinessProbe: parameter.readinessProbe
}
}]
}
parameter: {
@@ -55,5 +62,52 @@ spec:
name: string
path: string
}]
// +usage=Instructions for assessing whether the container is alive.
livenessProbe?: #HealthProbe
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
}
#HealthProbe: {
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
exec?: {
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
command: [...string]
}
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
httpGet?: {
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
path: string
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
port: int
httpHeaders?: [...{
name: string
value: string
}]
}
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
tcpSocket?: {
// +usage=The TCP socket within the container that should be probed to assess container health.
port: int
}
// +usage=Number of seconds after the container is started before the first probe is initiated.
initialDelaySeconds: *0 | int
// +usage=How often, in seconds, to execute the probe.
periodSeconds: *10 | int
// +usage=Number of seconds after which the probe times out.
timeoutSeconds: *1 | int
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
successThreshold: *1 | int
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}

View File

@@ -87,6 +87,17 @@ spec:
}
},
] | []
configMountToEnvsList: *[
for v in parameter.configMap if v.mountToEnvs != _|_ for k in v.mountToEnvs {
{
name: k.envName
valueFrom: configMapKeyRef: {
name: v.name
key: k.configMapKey
}
}
},
] | []
secretVolumeMountsList: *[
for v in parameter.secret if v.mountPath != _|_ {
{
@@ -106,6 +117,17 @@ spec:
}
},
] | []
secretMountToEnvsList: *[
for v in parameter.secret if v.mountToEnvs != _|_ for k in v.mountToEnvs {
{
name: k.envName
valueFrom: secretKeyRef: {
name: v.name
key: k.secretKey
}
}
},
] | []
emptyDirVolumeMountsList: *[
for v in parameter.emptyDir {
{
@@ -128,7 +150,7 @@ spec:
containers: [{
// +patchKey=name
env: configMapEnvMountsList + secretEnvMountsList
env: configMapEnvMountsList + secretEnvMountsList + configMountToEnvsList + secretMountToEnvsList
// +patchKey=name
volumeDevices: volumeDevicesList
// +patchKey=name
@@ -248,6 +270,10 @@ spec:
envName: string
configMapKey: string
}
mountToEnvs?: [...{
envName: string
configMapKey: string
}]
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool
@@ -267,6 +293,10 @@ spec:
envName: string
secretKey: string
}
mountToEnvs?: [...{
envName: string
secretKey: string
}]
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool

View File

@@ -453,6 +453,12 @@ spec:
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
#HealthProbe: {
@@ -494,12 +500,6 @@ spec:
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
status:
customStatus: |-

View File

@@ -104,7 +104,7 @@ multicluster:
port: 9443
image:
repository: oamdev/cluster-gateway
tag: v1.1.7
tag: v1.3.2
pullPolicy: IfNotPresent
resources:
limits:

View File

@@ -105,7 +105,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-minimal --
| `multicluster.clusterGateway.replicaCount` | ClusterGateway replica count | `1` |
| `multicluster.clusterGateway.port` | ClusterGateway port | `9443` |
| `multicluster.clusterGateway.image.repository` | ClusterGateway image repository | `oamdev/cluster-gateway` |
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.1.7` |
| `multicluster.clusterGateway.image.tag` | ClusterGateway image tag | `v1.3.2` |
| `multicluster.clusterGateway.image.pullPolicy` | ClusterGateway image pull policy | `IfNotPresent` |
| `multicluster.clusterGateway.resources.limits.cpu` | ClusterGateway cpu limit | `100m` |
| `multicluster.clusterGateway.resources.limits.memory` | ClusterGateway memory limit | `200Mi` |

View File

@@ -0,0 +1,69 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/config-image-registry.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Image Registry
definition.oam.dev/description: Config information to authenticate image registry
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
custom.definition.oam.dev/type.config.oam.dev: image-registry
name: config-image-registry
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
import (
"encoding/base64"
"encoding/json"
)
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: context.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "image-registry"
"config.oam.dev/multi-cluster": "true"
"config.oam.dev/identifier": parameter.registry
"config.oam.dev/sub-type": "auth"
}
}
type: "kubernetes.io/dockerconfigjson"
stringData: {
if parameter.auth != _|_ {
".dockerconfigjson": json.Marshal({
auths: "\(parameter.registry)": {
username: parameter.auth.username
password: parameter.auth.password
if parameter.auth.email != _|_ {
email: parameter.auth.email
}
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
}
})
}
}
}
parameter: {
// +usage=Image registry FQDN
registry: string
// +usage=Authenticate the image registry
auth?: {
// +usage=Private Image registry username
username: string
// +usage=Private Image registry password
password: string
// +usage=Private Image registry email
email?: string
}
}
workload:
type: autodetects.core.oam.dev

View File

@@ -0,0 +1,308 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/cron-task.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: Describes cron jobs that run code or a script to completion.
name: cron-task
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
output: {
apiVersion: "batch/v1beta1"
kind: "CronJob"
spec: {
schedule: parameter.schedule
concurrencyPolicy: parameter.concurrencyPolicy
suspend: parameter.suspend
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
if parameter.startingDeadlineSeconds != _|_ {
startingDeadlineSeconds: parameter.startingDeadlineSeconds
}
jobTemplate: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
parallelism: parameter.count
completions: parameter.count
if parameter.ttlSecondsAfterFinished != _|_ {
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
}
if parameter.activeDeadlineSeconds != _|_ {
activeDeadlineSeconds: parameter.activeDeadlineSeconds
}
backoffLimit: parameter.backoffLimit
template: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
restartPolicy: parameter.restart
containers: [{
name: context.name
image: parameter.image
if parameter["imagePullPolicy"] != _|_ {
imagePullPolicy: parameter.imagePullPolicy
}
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
if parameter["env"] != _|_ {
env: parameter.env
}
if parameter["cpu"] != _|_ {
resources: {
limits: cpu: parameter.cpu
requests: cpu: parameter.cpu
}
}
if parameter["memory"] != _|_ {
resources: {
limits: memory: parameter.memory
requests: memory: parameter.memory
}
}
if parameter["volumes"] != _|_ {
volumeMounts: [ for v in parameter.volumes {
{
mountPath: v.mountPath
name: v.name
}}]
}
}]
if parameter["volumes"] != _|_ {
volumes: [ for v in parameter.volumes {
{
name: v.name
if v.type == "pvc" {
persistentVolumeClaim: claimName: v.claimName
}
if v.type == "configMap" {
configMap: {
defaultMode: v.defaultMode
name: v.cmName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "secret" {
secret: {
defaultMode: v.defaultMode
secretName: v.secretName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "emptyDir" {
emptyDir: medium: v.medium
}
}}]
}
if parameter["imagePullSecrets"] != _|_ {
imagePullSecrets: [ for v in parameter.imagePullSecrets {
name: v
},
]
}
if parameter.hostAliases != _|_ {
hostAliases: [ for v in parameter.hostAliases {
ip: v.ip
hostnames: v.hostnames
},
]
}
}
}
}
}
}
}
parameter: {
// +usage=Specify the labels in the workload
labels?: [string]: string
// +usage=Specify the annotations in the workload
annotations?: [string]: string
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
schedule: string
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
startingDeadlineSeconds?: int
// +usage=suspend subsequent executions
suspend: *false | bool
// +usage=Specifies how to treat concurrent executions of a Job
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
// +usage=The number of successful finished jobs to retain
successfulJobsHistoryLimit: *3 | int
// +usage=The number of failed finished jobs to retain
failedJobsHistoryLimit: *1 | int
// +usage=Specify number of tasks to run in parallel
// +short=c
count: *1 | int
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Specify image pull policy for your service
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
// +usage=Specify image pull secrets for your service
imagePullSecrets?: [...string]
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
restart: *"Never" | string
// +usage=Commands to run in the container
cmd?: [...string]
// +usage=Define arguments by using environment variables
env?: [...{
// +usage=Environment variable name
name: string
// +usage=The value of the environment variable
value?: string
// +usage=Specifies a source the value of this var should come from
valueFrom?: {
// +usage=Selects a key of a secret in the pod's namespace
secretKeyRef: {
// +usage=The name of the secret in the pod's namespace to select from
name: string
// +usage=The key of the secret to select from. Must be a valid secret key
key: string
}
// +usage=Selects a key of a config map in the pod's namespace
configMapKeyRef: {
// +usage=The name of the config map in the pod's namespace to select from
name: string
// +usage=The key of the config map to select from. Must be a valid secret key
key: string
}
}
}]
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
cpu?: string
// +usage=Specifies the attributes of the memory resource required for the container.
memory?: string
// +usage=Declare volumes and volumeMounts
volumes?: [...{
name: string
mountPath: string
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir"
type: "pvc" | "configMap" | "secret" | "emptyDir"
if type == "pvc" {
claimName: string
}
if type == "configMap" {
defaultMode: *420 | int
cmName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "secret" {
defaultMode: *420 | int
secretName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "emptyDir" {
medium: *"" | "Memory"
}
}]
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
hostAliases?: [...{
ip: string
hostnames: [...string]
}]
// +usage=Limits the lifetime of a Job that has finished
ttlSecondsAfterFinished?: int
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
activeDeadlineSeconds?: int
// +usage=The number of retries before marking this job failed
backoffLimit: *6 | int
// +usage=Instructions for assessing whether the container is alive.
livenessProbe?: #HealthProbe
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
}
#HealthProbe: {
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
exec?: {
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
command: [...string]
}
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
httpGet?: {
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
path: string
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
port: int
httpHeaders?: [...{
name: string
value: string
}]
}
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
tcpSocket?: {
// +usage=The TCP socket within the container that should be probed to assess container health.
port: int
}
// +usage=Number of seconds after the container is started before the first probe is initiated.
initialDelaySeconds: *0 | int
// +usage=How often, in seconds, to execute the probe.
periodSeconds: *10 | int
// +usage=Number of seconds after which the probe times out.
timeoutSeconds: *1 | int
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
successThreshold: *1 | int
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}
workload:
definition:
apiVersion: batch/v1beta1
kind: CronJob
type: cronjobs.batch

View File

@@ -35,6 +35,13 @@ spec:
}
}]
}
if parameter["livenessProbe"] != _|_ {
livenessProbe: parameter.livenessProbe
}
if parameter["readinessProbe"] != _|_ {
readinessProbe: parameter.readinessProbe
}
}]
}
parameter: {
@@ -55,5 +62,52 @@ spec:
name: string
path: string
}]
// +usage=Instructions for assessing whether the container is alive.
livenessProbe?: #HealthProbe
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
}
#HealthProbe: {
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
exec?: {
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
command: [...string]
}
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
httpGet?: {
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
path: string
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
port: int
httpHeaders?: [...{
name: string
value: string
}]
}
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
tcpSocket?: {
// +usage=The TCP socket within the container that should be probed to assess container health.
port: int
}
// +usage=Number of seconds after the container is started before the first probe is initiated.
initialDelaySeconds: *0 | int
// +usage=How often, in seconds, to execute the probe.
periodSeconds: *10 | int
// +usage=Number of seconds after which the probe times out.
timeoutSeconds: *1 | int
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
successThreshold: *1 | int
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}

View File

@@ -87,6 +87,17 @@ spec:
}
},
] | []
configMountToEnvsList: *[
for v in parameter.configMap if v.mountToEnvs != _|_ for k in v.mountToEnvs {
{
name: k.envName
valueFrom: configMapKeyRef: {
name: v.name
key: k.configMapKey
}
}
},
] | []
secretVolumeMountsList: *[
for v in parameter.secret if v.mountPath != _|_ {
{
@@ -106,6 +117,17 @@ spec:
}
},
] | []
secretMountToEnvsList: *[
for v in parameter.secret if v.mountToEnvs != _|_ for k in v.mountToEnvs {
{
name: k.envName
valueFrom: secretKeyRef: {
name: v.name
key: k.secretKey
}
}
},
] | []
emptyDirVolumeMountsList: *[
for v in parameter.emptyDir {
{
@@ -128,7 +150,7 @@ spec:
containers: [{
// +patchKey=name
env: configMapEnvMountsList + secretEnvMountsList
env: configMapEnvMountsList + secretEnvMountsList + configMountToEnvsList + secretMountToEnvsList
// +patchKey=name
volumeDevices: volumeDevicesList
// +patchKey=name
@@ -248,6 +270,10 @@ spec:
envName: string
configMapKey: string
}
mountToEnvs?: [...{
envName: string
configMapKey: string
}]
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool
@@ -267,6 +293,10 @@ spec:
envName: string
secretKey: string
}
mountToEnvs?: [...{
envName: string
secretKey: string
}]
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool

View File

@@ -453,6 +453,12 @@ spec:
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
#HealthProbe: {
@@ -494,12 +500,6 @@ spec:
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
status:
customStatus: |-

View File

@@ -107,7 +107,7 @@ multicluster:
port: 9443
image:
repository: oamdev/cluster-gateway
tag: v1.1.7
tag: v1.3.2
pullPolicy: IfNotPresent
resources:
limits:

View File

@@ -19,7 +19,6 @@ package main
import (
"context"
"errors"
"flag"
"fmt"
"io"
"net/http"
@@ -30,6 +29,8 @@ import (
"strings"
"time"
flag "github.com/spf13/pflag"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
"k8s.io/klog/v2/klogr"
ctrl "sigs.k8s.io/controller-runtime"
@@ -147,6 +148,7 @@ func main() {
flag.IntVar(&workflow.MaxWorkflowWaitBackoffTime, "max-workflow-wait-backoff-time", 60, "Set the max workflow wait backoff time, default is 60")
flag.IntVar(&workflow.MaxWorkflowFailedBackoffTime, "max-workflow-failed-backoff-time", 300, "Set the max workflow wait backoff time, default is 300")
flag.IntVar(&custom.MaxWorkflowStepErrorRetryTimes, "max-workflow-step-error-retry-times", 10, "Set the max workflow step error retry times, default is 10")
utilfeature.DefaultMutableFeatureGate.AddFlag(flag.CommandLine)
flag.Parse()
// setup logging

View File

@@ -7,4 +7,5 @@ coverage:
default:
target: 70%
ignore:
- "**/zz_generated.deepcopy.go"
- "**/zz_generated.deepcopy.go"
- "references/"

View File

@@ -6,3 +6,4 @@ This directory contains guides for contributors to the KubeVela project.
* [Developer guide](./developer-guide.md)
* [Triage issues](./triage-issues.md)
* [Code conventions](./coding-conventions.md)
* [Develop Code Flow](./develop-code-flow.pdf)

Binary file not shown.

View File

@@ -5,7 +5,7 @@ This guide helps you get started developing KubeVela.
## Prerequisites
1. Golang version 1.17+
2. Kubernetes version v1.18+ with `~/.kube/config` configured.
2. Kubernetes version v1.20+ with `~/.kube/config` configured.
3. ginkgo 1.14.0+ (just for [E2E test](./developer-guide.md#e2e-test))
4. golangci-lint 1.38.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
5. kubebuilder v3.1.0+ and you need to manually install the dependency tools for unit test.
@@ -177,7 +177,7 @@ To execute the e2e test of the API module, the mongodb service needs to exist lo
# save your config
mv ~/.kube/config ~/.kube/config.save
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
kind get kubeconfig --name worker > /tmp/worker.client.kubeconfig

View File

@@ -3337,6 +3337,284 @@
}
}
},
"/api/v1/config_types": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"config"
],
"summary": "list all config types",
"operationId": "listConfigTypes",
"parameters": [
{
"type": "string",
"description": "Fuzzy search based on name and description.",
"name": "query",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/v1.ConfigType"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/config_types/{configType}": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"config"
],
"summary": "get a config type",
"operationId": "getConfigType",
"parameters": [
{
"type": "string",
"description": "identifier of the config type",
"name": "configType",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.ConfigType"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
},
"post": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"config"
],
"summary": "create or update a config",
"operationId": "createConfig",
"parameters": [
{
"type": "string",
"description": "identifier of the config type",
"name": "configType",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.CreateConfigRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.EmptyResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/config_types/{configType}/configs": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"config"
],
"summary": "get configs from a config type",
"operationId": "getConfigs",
"parameters": [
{
"type": "string",
"description": "identifier of the config",
"name": "configType",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/*v1.Config"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/config_types/{configType}/configs/{name}": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"config"
],
"summary": "get a config from a config type",
"operationId": "getConfig",
"parameters": [
{
"type": "string",
"description": "identifier of the config type",
"name": "configType",
"in": "path",
"required": true
},
{
"type": "string",
"description": "identifier of the config",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/*v1.Config"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
},
"delete": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"config"
],
"summary": "delete a config",
"operationId": "deleteConfig",
"parameters": [
{
"type": "string",
"description": "identifier of the config type",
"name": "configType",
"in": "path",
"required": true
},
{
"type": "string",
"description": "identifier of the config",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.EmptyResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/definitions": {
"get": {
"consumes": [
@@ -3862,6 +4140,55 @@
}
}
},
"/api/v1/projects/{projectName}/configs": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"project"
],
"summary": "get configs which are in a project",
"operationId": "getConfigs",
"parameters": [
{
"type": "string",
"description": "config type",
"name": "configType",
"in": "query"
},
{
"type": "string",
"description": "identifier of the project",
"name": "projectName",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/*v1.Config"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/projects/{projectName}/permissions": {
"get": {
"consumes": [
@@ -4304,6 +4631,49 @@
}
}
},
"/api/v1/repository/chart_repos": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"repository",
"helm"
],
"summary": "list chart repo",
"operationId": "listRepo",
"parameters": [
{
"type": "string",
"description": "the config project",
"name": "project",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/repository/charts": {
"get": {
"consumes": [
@@ -4326,6 +4696,12 @@
"description": "helm repository url",
"name": "repoUrl",
"in": "query"
},
{
"type": "string",
"description": "secret of the repo",
"name": "secretName",
"in": "query"
}
],
"responses": {
@@ -4369,6 +4745,12 @@
"description": "helm repository url",
"name": "repoUrl",
"in": "query"
},
{
"type": "string",
"description": "secret of the repo",
"name": "secretName",
"in": "query"
}
],
"responses": {
@@ -4409,6 +4791,12 @@
"description": "helm repository url",
"name": "repoUrl",
"in": "query"
},
{
"type": "string",
"description": "secret of the repo",
"name": "secretName",
"in": "query"
}
],
"responses": {
@@ -5241,6 +5629,7 @@
},
"definitions": {
"*v1.ApplicationTriggerBase": {},
"*v1.Config": {},
"*v1.EmptyResponse": {},
"addon.Dependency": {
"properties": {
@@ -5293,6 +5682,22 @@
}
}
},
"addon.GitlabAddonSource": {
"properties": {
"path": {
"type": "string"
},
"repo": {
"type": "string"
},
"token": {
"type": "string"
},
"url": {
"type": "string"
}
}
},
"addon.HelmSource": {
"properties": {
"url": {
@@ -6562,34 +6967,6 @@
}
}
},
"model.SystemInfo": {
"required": [
"createTime",
"updateTime",
"installID",
"enableCollection",
"loginType"
],
"properties": {
"createTime": {
"type": "string",
"format": "date-time"
},
"enableCollection": {
"type": "boolean"
},
"installID": {
"type": "string"
},
"loginType": {
"type": "string"
},
"updateTime": {
"type": "string",
"format": "date-time"
}
}
},
"model.WorkflowStepStatus": {
"required": [
"id",
@@ -6978,6 +7355,9 @@
"gitee": {
"$ref": "#/definitions/addon.GiteeAddonSource"
},
"gitlab": {
"$ref": "#/definitions/addon.GitlabAddonSource"
},
"helm": {
"$ref": "#/definitions/addon.HelmSource"
},
@@ -7148,12 +7528,12 @@
},
"v1.ApplicationDeployResponse": {
"required": [
"note",
"envName",
"triggerType",
"createTime",
"version",
"note",
"status",
"envName",
"triggerType"
"status"
],
"properties": {
"codeInfo": {
@@ -7687,6 +8067,31 @@
}
}
},
"v1.ConfigType": {
"required": [
"definitions",
"alias",
"name",
"description"
],
"properties": {
"alias": {
"type": "string"
},
"definitions": {
"type": "array",
"items": {
"type": "string"
}
},
"description": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"v1.ConnectCloudClusterRequest": {
"required": [
"accessKeyID",
@@ -7736,6 +8141,9 @@
"gitee": {
"$ref": "#/definitions/addon.GiteeAddonSource"
},
"gitlab": {
"$ref": "#/definitions/addon.GitlabAddonSource"
},
"helm": {
"$ref": "#/definitions/addon.HelmSource"
},
@@ -8032,6 +8440,31 @@
}
}
},
"v1.CreateConfigRequest": {
"required": [
"name",
"alias",
"project",
"componentType"
],
"properties": {
"alias": {
"type": "string"
},
"componentType": {
"type": "string"
},
"name": {
"type": "string"
},
"project": {
"type": "string"
},
"properties": {
"type": "string"
}
}
},
"v1.CreateEnvRequest": {
"required": [
"name",
@@ -8243,11 +8676,11 @@
},
"v1.DetailAddonResponse": {
"required": [
"name",
"icon",
"invisible",
"version",
"description",
"invisible",
"name",
"icon",
"schema",
"uiSchema",
"definitions",
@@ -8327,13 +8760,13 @@
},
"v1.DetailApplicationResponse": {
"required": [
"alias",
"icon",
"project",
"description",
"createTime",
"name",
"project",
"alias",
"updateTime",
"icon",
"policies",
"envBindings",
"applicationType",
@@ -8394,19 +8827,19 @@
},
"v1.DetailClusterResponse": {
"required": [
"updateTime",
"kubeConfig",
"name",
"icon",
"reason",
"provider",
"apiServerURL",
"name",
"alias",
"icon",
"dashboardURL",
"createTime",
"alias",
"description",
"labels",
"status",
"updateTime",
"apiServerURL",
"kubeConfig",
"labels",
"kubeConfigSecret",
"resourceInfo"
],
@@ -8465,14 +8898,14 @@
},
"v1.DetailComponentResponse": {
"required": [
"name",
"updateTime",
"appPrimaryKey",
"createTime",
"creator",
"alias",
"name",
"main",
"createTime",
"type",
"main",
"appPrimaryKey",
"definition"
],
"properties": {
@@ -8574,13 +9007,13 @@
},
"v1.DetailPolicyResponse": {
"required": [
"name",
"type",
"description",
"creator",
"properties",
"createTime",
"updateTime",
"name",
"type"
"updateTime"
],
"properties": {
"createTime": {
@@ -8610,17 +9043,17 @@
},
"v1.DetailRevisionResponse": {
"required": [
"envName",
"reason",
"deployUser",
"updateTime",
"status",
"note",
"triggerType",
"workflowName",
"updateTime",
"version",
"reason",
"createTime",
"appPrimaryKey",
"version"
"status",
"deployUser",
"note",
"workflowName",
"envName",
"appPrimaryKey"
],
"properties": {
"appPrimaryKey": {
@@ -8674,10 +9107,10 @@
},
"v1.DetailTargetResponse": {
"required": [
"name",
"createTime",
"project",
"updateTime"
"createTime",
"updateTime",
"name"
],
"properties": {
"alias": {
@@ -8717,11 +9150,11 @@
},
"v1.DetailUserResponse": {
"required": [
"lastLoginTime",
"name",
"email",
"disabled",
"createTime",
"lastLoginTime",
"projects",
"roles"
],
@@ -8762,12 +9195,12 @@
},
"v1.DetailWorkflowRecordResponse": {
"required": [
"name",
"namespace",
"workflowName",
"workflowAlias",
"applicationRevision",
"status",
"name",
"namespace",
"deployTime",
"deployUser",
"note",
@@ -8819,14 +9252,14 @@
},
"v1.DetailWorkflowResponse": {
"required": [
"description",
"enable",
"createTime",
"updateTime",
"name",
"alias",
"enable",
"default",
"envName"
"envName",
"createTime",
"alias",
"description"
],
"properties": {
"alias": {
@@ -9021,8 +9454,8 @@
},
"v1.EnvBindingTarget": {
"required": [
"name",
"alias"
"alias",
"name"
],
"properties": {
"alias": {
@@ -9405,11 +9838,11 @@
},
"v1.LoginUserInfoResponse": {
"required": [
"createTime",
"lastLoginTime",
"name",
"email",
"disabled",
"createTime",
"lastLoginTime",
"projects",
"platformPermissions",
"projectPermissions"
@@ -9717,6 +10150,24 @@
}
}
},
"v1.SystemInfo": {
"required": [
"installID",
"enableCollection",
"loginType"
],
"properties": {
"enableCollection": {
"type": "boolean"
},
"installID": {
"type": "string"
},
"loginType": {
"type": "string"
}
}
},
"v1.SystemInfoRequest": {
"required": [
"enableCollection",
@@ -9728,23 +10179,20 @@
},
"loginType": {
"type": "string"
},
"velaAddress": {
"type": "string"
}
}
},
"v1.SystemInfoResponse": {
"required": [
"createTime",
"updateTime",
"installID",
"enableCollection",
"loginType",
"systemVersion"
],
"properties": {
"createTime": {
"type": "string",
"format": "date-time"
},
"enableCollection": {
"type": "boolean"
},
@@ -9756,10 +10204,6 @@
},
"systemVersion": {
"$ref": "#/definitions/v1.SystemVersion"
},
"updateTime": {
"type": "string",
"format": "date-time"
}
}
},
@@ -9828,6 +10272,9 @@
"gitee": {
"$ref": "#/definitions/addon.GiteeAddonSource"
},
"gitlab": {
"$ref": "#/definitions/addon.GitlabAddonSource"
},
"helm": {
"$ref": "#/definitions/addon.HelmSource"
},

View File

@@ -2,6 +2,8 @@
By leveraging the garbage-collect policy, users can persist some resources, which skip the normal garbage-collect process when application is updated.
### traitTypes
Take the following app as an example, in the garbage-collect policy, a rule is added which marks all the resources created by the `expose` trait to use the `onAppDelete` strategy. This will keep those services until application is deleted.
```shell
$ cat <<EOF | kubectl apply -f -
@@ -78,6 +80,8 @@ hello-world ClusterIP 10.96.160.208 <none> 8000/TCP 5m56s
hello-world-new ClusterIP 10.96.20.4 <none> 8000/TCP 13s
```
### componentTypes
Users can also keep component if they are deploying job-like components. Resources dispatched by `job-like-component` type component will be kept after application is deleted.
```yaml
@@ -95,7 +99,105 @@ spec:
properties:
rules:
- selector:
componentTypes:
- webservice
strategy: never
componentTypes:
- webservice
strategy: never
```
### componentNames
A more straightforward way is to specify `compNames` to match specified components.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: create-ns-app
spec:
components:
- name: example-addon-namespace
type: k8s-objects
properties:
objects:
- apiVersion: v1
kind: Namespace
policies:
- name: garbage-collect
type: garbage-collect
properties:
rules:
- selector:
componentNames:
- example-addon-namespace
strategy: never
```
### oamTypes
Users can also persist resources using `oamTypes`, where the values of `oamTypes` can be `TRAIT` and `WORKLOAD`.
```shell
$ cat <<EOF | kubectl apply -f -
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: garbage-collect-app
spec:
components:
- name: hello-world
type: webservice
properties:
image: crccheck/hello-world
traits:
- type: expose
properties:
port: [8000]
policies:
- name: garbage-collect
type: garbage-collect
properties:
rules:
- selector:
oamTypes:
- TRAIT
strategy: onAppDelete
EOF
```
And then, let's modify the component name.
```shell
$ cat <<EOF | kubectl apply -f -
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: garbage-collect-app
spec:
components:
- name: hello-world-new
type: webservice
properties:
image: crccheck/hello-world
traits:
- type: expose
properties:
port: [8000]
policies:
- name: garbage-collect
type: garbage-collect
properties:
rules:
- selector:
oamTypes:
- TRAIT
strategy: onAppDelete
EOF
```
List the service in cluster, you will find:
```shell
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-world ClusterIP 10.96.31.209 <none> 8000/TCP 31s
hello-world-new ClusterIP 10.96.17.103 <none> 8000/TCP 5s
```

View File

@@ -0,0 +1,21 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: config-dex-connector-dev
namespace: vela-system
labels:
"app.oam.dev/source-of-truth": "from-inner-system"
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "config-dex-connector"
"config.oam.dev/sub-type": "github"
project: abc
spec:
components:
- name: dev
type: config-dex-connector
properties:
type: github
github:
clientID: "aa"
clientSecret: "bb"
redirectURI: "http://localhost:8080/callback"

View File

@@ -0,0 +1,102 @@
# How to store and use configurations
## General
- list all configuration types
```shell
$ vela components --label custom.definition.oam.dev/catalog.config.oam.dev=velacore-config
NAME DEFINITION
config-dex-connector autodetects.core.oam.dev
config-helm-repository autodetects.core.oam.dev
config-image-registry autodetects.core.oam.dev
terraform-azure autodetects.core.oam.dev
terraform-baidu autodetects.core.oam.dev
```
```json
# Get http://127.0.0.1:8000/api/v1/configs
[
{
"definitions": [
"config-dex-connector"
],
"name": "Dex Connectors",
"type": "dex-connector"
},
{
"definitions": [
"config-helm-repository"
],
"name": "Helm Repository",
"type": "helm-repository"
},
{
"definitions": [
"config-image-registry"
],
"name": "Image Registry",
"type": "image-registry"
},
null,
{
"definitions": [
"terraform-baidu"
],
"name": "Terraform Cloud Provider",
"type": "terraform-provider"
}
]
```
- list all configurations
```shell
$ kubectl get secret -n vela-system -l=config.oam.dev/catalog=velacore-config
NAME TYPE DATA AGE
image-registry-dev kubernetes.io/dockerconfigjson 1 3h51m
```
## Image registry
- Create a config for an image registry
```shell
$ vela up -f app-config-image-registry-account-auth.yaml
Applying an application in vela K8s object format...
I0323 10:45:25.347102 85930 apply.go:107] "creating object" name="config-image-registry-account-auth-dev" resource="core.oam.dev/v1beta1, Kind=Application"
✅ App has been deployed 🚀🚀🚀
Port forward: vela port-forward config-image-registry-account-auth-dev
SSH: vela exec config-image-registry-account-auth-dev
Logging: vela logs config-image-registry-account-auth-dev
App status: vela status config-image-registry-account-auth-dev
Endpoint: vela status config-image-registry-account-auth-dev
--endpoint%
$ kubectl get secret -n vela-system -l=config.oam.dev/catalog=velacore-config
NAME TYPE DATA AGE
image-registry-dev kubernetes.io/dockerconfigjson 1 77s
```
- Deliver the config secret to working cluster
```shell
$ vela cluster list
CLUSTER TYPE ENDPOINT ACCEPTED LABELS
local Internal - true
bj X509Certificate https://123.57.73.107:6443 true
$ vela up -f app-deliever-secret.yaml
```
- Deploy an application who needs to pull images from the private image registry
```shell
$ export KUBECONFIG=~/.kube/config-bj
$ kubectl get secret -n vela-system -l=config.oam.dev/catalog=velacore-config
NAME TYPE DATA AGE
image-registry-dev kubernetes.io/dockerconfigjson 1 120s
$ vela up -f app-validate-imagePullSecret.yaml
```

View File

@@ -0,0 +1,20 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: image-dev
namespace: vela-system
labels:
"app.oam.dev/source-of-truth": "from-inner-system"
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "config-image-registry"
project: abc
spec:
components:
- name: image-dev
type: config-image-registry
properties:
registry: "registry.cn-beijing.aliyuncs.com"
auth:
username: "xxx"
password: "PfwrjwifjFaked"
email: "a@gmail.com"

View File

@@ -0,0 +1,22 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: config-project1
namespace: vela-system
labels:
config.oam.dev/catalog: "velacore-config"
config.oam.dev/type: "helm-repository"
spec:
components:
- name: deliver-secret
type: ref-objects
properties:
objects:
- name: reg-demo
resource: secret
policies:
- type: topology
name: dev
properties:
clusters: ["bj"]
namespace: default

View File

@@ -0,0 +1,14 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-sample
namespace: ns1
spec:
components:
- name: sample
type: webservice
properties:
image: registry.cn-beijing.aliyuncs.com/vela/nginx:latest
imagePullPolicy: Always
imagePullSecrets:
- image-registry-dev

View File

@@ -0,0 +1,14 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-validate-image-pull-secret
namespace: vela-system
spec:
components:
- name: validate
type: webservice
properties:
image: registry.cn-beijing.aliyuncs.com/vela/nginx:latest
imagePullPolicy: Always
imagePullSecrets:
- image-registry-dev

View File

@@ -32,6 +32,9 @@ spec:
mountToEnv:
envName: TEST_ENV
configMapKey: key1
mountToEnvs:
- envName: TEST_CM_ENV
configMapKey: key2
data:
key1: value1
key2: value2
@@ -49,9 +52,15 @@ spec:
mountToEnv:
envName: TEST_SECRET
secretKey: key1
mountToEnvs:
- envName: TEST_SECRET_ENV_2
secretKey: key2
- envName: TEST_SECRET_ENV_3
secretKey: key3
data:
key1: dmFsdWUx
key2: dmFsdWUy
key3: dmFsdWUz
emptyDir:
- name: test1
mountPath: /test/mount/emptydir

View File

@@ -22,6 +22,8 @@ import (
"strings"
"time"
v1 "k8s.io/api/core/v1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -62,6 +64,7 @@ var _ = Describe("Addon Test", func() {
Expect(output).To(ContainSubstring("Successfully disable addon"))
Eventually(func(g Gomega) {
g.Expect(apierrors.IsNotFound(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-test-addon", Namespace: "vela-system"}, &v1beta1.Application{}))).Should(BeTrue())
g.Expect(apierrors.IsNotFound(k8sClient.Get(context.Background(), types.NamespacedName{Name: "addon-secret-test-addon", Namespace: "vela-system"}, &v1.Secret{}))).Should(BeTrue())
}, 60*time.Second).Should(Succeed())
})

View File

@@ -129,7 +129,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
Expect(err).NotTo(HaveOccurred())
Expect(output).Should(ContainSubstring(showTdResult))
})
It("Test show componentDefinition use Helm Charts as Workload", func() {
PIt("Test show componentDefinition use Helm Charts as Workload", func() {
Eventually(func() string {
cdName := "test-webapp-chart"
output, _ := e2e.Exec(fmt.Sprintf("kubectl-vela show %s -n default", cdName))

29
go.mod
View File

@@ -64,8 +64,8 @@ require (
go.mongodb.org/mongo-driver v1.5.1
go.uber.org/zap v1.18.1
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/tools v0.1.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
@@ -96,6 +96,18 @@ require (
sigs.k8s.io/yaml v1.2.0
)
require (
github.com/docker/distribution v2.8.0-beta.1+incompatible // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/xanzy/go-gitlab v0.60.0
github.com/xanzy/ssh-agent v0.3.0 // indirect
golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)
require (
cloud.google.com/go v0.81.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@@ -139,12 +151,12 @@ require (
github.com/cyphar/filepath-securejoin v0.2.2 // indirect
github.com/deislabs/oras v0.11.1 // indirect
github.com/docker/cli v20.10.5+incompatible // indirect
github.com/docker/distribution v2.8.0-beta.1+incompatible // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.3 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/emicklei/proto v1.6.15 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
@@ -167,7 +179,7 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
@@ -182,7 +194,6 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/klauspost/compress v1.11.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/pty v1.1.8 // indirect
@@ -233,7 +244,6 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.0.2 // indirect
github.com/xdg-go/stringprep v1.0.2 // indirect
@@ -248,17 +258,14 @@ require (
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
google.golang.org/grpc v1.38.0 // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect

40
go.sum
View File

@@ -420,8 +420,9 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -780,8 +781,9 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -894,8 +896,12 @@ github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPA
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.12.2 h1:F1fdYblUEsxKiailtkhCCG2g4bipEgaHiDc8vffNpD4=
github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@@ -905,6 +911,9 @@ github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
@@ -1617,6 +1626,8 @@ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/wonderflow/cert-manager-api v1.0.3 h1:xQQMkJNQ12oYyy00jOQUlSKgdraApaURxv3PHFdVTfA=
github.com/wonderflow/cert-manager-api v1.0.3/go.mod h1:1Se7MSg11/eNYlo4fWv6vOM55/jTBMOzg2DN1kVFiSc=
github.com/xanzy/go-gitlab v0.60.0 h1:HaIlc14k4t9eJjAhY0Gmq2fBHgKd1MthBn3+vzDtsbA=
github.com/xanzy/go-gitlab v0.60.0/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
@@ -1907,9 +1918,12 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1921,8 +1935,9 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -2046,13 +2061,14 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -2063,8 +2079,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -2074,8 +2091,9 @@ golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -2245,6 +2263,7 @@ google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
@@ -2353,8 +2372,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=

View File

@@ -48,6 +48,7 @@ ADDONSERVER = $(shell pgrep vela_addon_mock_server)
e2e-apiserver-test:
pkill vela_addon_mock_server || true
go run ./e2e/addon/mock/vela_addon_mock_server.go &
sleep 15
go test -v -coverpkg=./... -coverprofile=/tmp/e2e_apiserver_test.out ./test/e2e-apiserver-test
@$(OK) tests pass

View File

@@ -36,6 +36,7 @@ import (
"github.com/google/go-github/v32/github"
"github.com/hashicorp/go-version"
"github.com/pkg/errors"
"github.com/xanzy/go-gitlab"
"golang.org/x/oauth2"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
@@ -447,6 +448,15 @@ func createGiteeHelper(content *utils.Content, token string) *giteeHelper {
}
}
func createGitlabHelper(content *utils.Content, token string) (*gitlabHelper, error) {
newClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(content.GitlabContent.Host))
return &gitlabHelper{
Client: newClient,
Meta: content,
}, err
}
// readRepo will read relative path (relative to Meta.Path)
func (h *gitHelper) readRepo(relativePath string) (*github.RepositoryContent, []*github.RepositoryContent, error) {
file, items, _, err := h.Client.Repositories.GetContents(context.Background(), h.Meta.GithubContent.Owner, h.Meta.GithubContent.Repo, path.Join(h.Meta.GithubContent.Path, relativePath), nil)
@@ -567,7 +577,7 @@ func renderResources(addon *InstallPackage, args map[string]interface{}) ([]comm
}
for _, tmpl := range addon.CUETemplates {
comp, err := renderCUETemplate(tmpl, addon.Parameters, args)
comp, err := renderCUETemplate(tmpl, addon.Parameters, args, addon.Meta)
if err != nil {
return nil, NewAddonError(fmt.Sprintf("fail to render cue template %s", err.Error()))
}
@@ -589,6 +599,9 @@ func formatAppFramework(addon *InstallPackage) *v1beta1.Application {
},
}
}
if app.Spec.Components == nil {
app.Spec.Components = []common2.ApplicationComponent{}
}
app.Name = Convert2AppName(addon.Name)
// force override the namespace defined vela with DefaultVelaNS,this value can be modified by Env
app.SetNamespace(types.DefaultKubeVelaNS)
@@ -929,17 +942,28 @@ func renderSchemaConfigmap(elem ElementFile) (*unstructured.Unstructured, error)
}
// renderCUETemplate will return a component from cue template
func renderCUETemplate(elem ElementFile, parameters string, args map[string]interface{}) (*common2.ApplicationComponent, error) {
func renderCUETemplate(elem ElementFile, parameters string, args map[string]interface{}, metadata Meta) (*common2.ApplicationComponent, error) {
bt, err := json.Marshal(args)
if err != nil {
return nil, err
}
var contextFile = strings.Builder{}
var paramFile = cuemodel.ParameterFieldName + ": {}"
if string(bt) != "null" {
paramFile = fmt.Sprintf("%s: %s", cuemodel.ParameterFieldName, string(bt))
}
param := fmt.Sprintf("%s\n%s", paramFile, parameters)
v, err := value.NewValue(param, nil, "")
// addon metadata context
metadataJSON, err := json.Marshal(metadata)
if err != nil {
return nil, err
}
contextFile.WriteString(fmt.Sprintf("context: metadata: %s\n", string(metadataJSON)))
// parameter definition
contextFile.WriteString(paramFile + "\n")
// user custom parameter
contextFile.WriteString(parameters + "\n")
v, err := value.NewValue(contextFile.String(), nil, "")
if err != nil {
return nil, err
}
@@ -1056,7 +1080,7 @@ func (h *Installer) enableAddon(addon *InstallPackage) error {
h.addon = addon
err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc)
if err != nil {
return ErrVersionMismatch
return VersionUnMatchError{addonName: addon.Name, err: err}
}
if err = h.installDependency(addon); err != nil {
@@ -1174,6 +1198,8 @@ func (h *Installer) createOrUpdate(app *v1beta1.Application) error {
return err
}
getapp.Spec = app.Spec
getapp.Labels = app.Labels
getapp.Annotations = app.Annotations
err = h.cli.Update(h.ctx, &getapp)
if err != nil {
klog.Errorf("fail to create application: %v", err)
@@ -1328,7 +1354,7 @@ func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequireme
return err
}
if !res {
return fmt.Errorf("vela cli/ux version: %s cannot meet requirement", version2.VelaVersion)
return fmt.Errorf("vela cli/ux version: %s require: %s", version2.VelaVersion, require.VelaVersion)
}
}
@@ -1345,7 +1371,7 @@ func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequireme
return err
}
if !res {
return fmt.Errorf("the vela core controller: %s cannot meet requirement ", imageVersion)
return fmt.Errorf("the vela core controller: %s require: %s", imageVersion, require.VelaVersion)
}
}
@@ -1366,7 +1392,7 @@ func checkAddonVersionMeetRequired(ctx context.Context, require *SystemRequireme
}
if !res {
return fmt.Errorf("the kubernetes version %s cannot meet requirement", k8sVersion.GitVersion)
return fmt.Errorf("the kubernetes version %s require: %s", k8sVersion.GitVersion, require.KubernetesVersion)
}
}

View File

@@ -323,6 +323,45 @@ var _ = Describe("Test render addon with specified clusters", func() {
})
})
var _ = Describe("func addon update ", func() {
It("test update addon app label", func() {
app_test_update := v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(addonUpdateAppYaml), &app_test_update)).Should(BeNil())
Expect(k8sClient.Create(ctx, &app_test_update)).Should(BeNil())
Eventually(func() error {
var err error
appCheck := v1beta1.Application{}
err = k8sClient.Get(ctx, types2.NamespacedName{Namespace: "vela-system", Name: "addon-test-update"}, &appCheck)
if err != nil {
return err
}
if appCheck.Labels["addons.oam.dev/version"] != "v1.2.0" {
return fmt.Errorf("label missmatch")
}
return nil
}, time.Millisecond*500, 30*time.Second).Should(BeNil())
pkg := &InstallPackage{Meta: Meta{Name: "test-update", Version: "1.3.0"}}
h := NewAddonInstaller(context.Background(), k8sClient, nil, nil, nil, &Registry{Name: "test"}, nil, nil)
h.addon = pkg
Expect(h.dispatchAddonResource(pkg)).Should(BeNil())
Eventually(func() error {
var err error
appCheck := v1beta1.Application{}
err = k8sClient.Get(context.Background(), types2.NamespacedName{Namespace: "vela-system", Name: "addon-test-update"}, &appCheck)
if err != nil {
return err
}
if appCheck.Labels["addons.oam.dev/version"] != "1.3.0" {
return fmt.Errorf("label missmatch")
}
return nil
}, time.Second*3, 300*time.Second).Should(BeNil())
})
})
const (
appYaml = `apiVersion: core.oam.dev/v1beta1
kind: Application
@@ -400,4 +439,21 @@ spec:
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30`
addonUpdateAppYaml = `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: addon-test-update
namespace: vela-system
labels:
addons.oam.dev/version: v1.2.0
spec:
components:
- name: express-server
type: webservice
properties:
image: crccheck/hello-world
port: 8000
`
)

View File

@@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"net/http"
"net/http/httptest"
@@ -35,7 +36,7 @@ import (
v1alpha12 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -146,7 +147,7 @@ func TestGetAddonData(t *testing.T) {
server := httptest.NewServer(ossHandler)
defer server.Close()
reader, err := NewAsyncReader(server.URL, "", "", "", ossType)
reader, err := NewAsyncReader(server.URL, "", "", "", "", ossType)
assert.NoError(t, err)
testReaderFunc(t, reader)
}
@@ -303,7 +304,7 @@ func TestGetAddonStatus(t *testing.T) {
getFunc := test.MockGetFn(func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
switch key.Name {
case "addon-disabled", "disabled":
return errors.NewNotFound(schema.GroupResource{Group: "apiVersion: core.oam.dev/v1beta1", Resource: "app"}, key.Name)
return kerrors.NewNotFound(schema.GroupResource{Group: "apiVersion: core.oam.dev/v1beta1", Resource: "app"}, key.Name)
case "addon-suspend":
o := obj.(*v1beta1.Application)
app := &v1beta1.Application{}
@@ -616,9 +617,9 @@ func TestRenderApp4ObservabilityWithK8sData(t *testing.T) {
}
func TestGetPatternFromItem(t *testing.T) {
ossR, err := NewAsyncReader("http://ep.beijing", "some-bucket", "some-sub-path", "", ossType)
ossR, err := NewAsyncReader("http://ep.beijing", "some-bucket", "", "some-sub-path", "", ossType)
assert.NoError(t, err)
gitR, err := NewAsyncReader("https://github.com/oam-dev/catalog", "", "addons", "", gitType)
gitR, err := NewAsyncReader("https://github.com/oam-dev/catalog", "", "", "addons", "", gitType)
assert.NoError(t, err)
gitItemName := "parameter.cue"
gitItemType := FileType
@@ -656,7 +657,7 @@ func TestGetPatternFromItem(t *testing.T) {
}
func TestGitLabReaderNotPanic(t *testing.T) {
_, err := NewAsyncReader("https://gitlab.com/test/catalog", "", "addons", "", gitType)
_, err := NewAsyncReader("https://gitlab.com/test/catalog", "", "", "addons", "", gitType)
assert.EqualError(t, err, "git type repository only support github for now")
}
@@ -880,3 +881,28 @@ func TestReadDefFile(t *testing.T) {
// verify
assert.True(t, len(uiData.Definitions) == 1)
}
func TestRenderCUETemplate(t *testing.T) {
fileDate, err := os.ReadFile("./testdata/example/resources/configmap.cue")
assert.NoError(t, err)
component, err := renderCUETemplate(ElementFile{Data: string(fileDate), Name: "configmap.cue"}, "{\"example\": \"\"}", map[string]interface{}{
"example": "render",
}, Meta{
Version: "1.0.1",
})
assert.NoError(t, err)
assert.True(t, component.Type == "raw")
var config = make(map[string]interface{})
err = json.Unmarshal(component.Properties.Raw, &config)
assert.NoError(t, err)
assert.True(t, component.Type == "raw")
assert.True(t, config["metadata"].(map[string]interface{})["labels"].(map[string]interface{})["version"] == "1.0.1")
}
func TestCheckEnableAddonErrorWhenMissMatch(t *testing.T) {
version2.VelaVersion = "v1.3.0"
i := InstallPackage{Meta: Meta{SystemRequirements: &SystemRequirements{VelaVersion: ">=1.4.0"}}}
installer := &Installer{}
err := installer.enableAddon(&i)
assert.Equal(t, errors.As(err, &VersionUnMatchError{}), true)
}

View File

@@ -22,6 +22,8 @@ import (
"sync"
"time"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
)
@@ -106,7 +108,7 @@ func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
addon, err = versionedRegistry.GetAddonUIData(context.Background(), addonName, version)
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", utils.Sanitize(r.Name), err)
return nil, err
}
}

View File

@@ -17,6 +17,8 @@ limitations under the License.
package addon
import (
"fmt"
"github.com/google/go-github/v32/github"
"github.com/pkg/errors"
)
@@ -35,9 +37,6 @@ var (
// ErrNotExist means addon not exists
ErrNotExist = NewAddonError("addon not exist")
// ErrVersionMismatch means addon version requirement mismatch
ErrVersionMismatch = NewAddonError("addon version requirements mismatch")
)
// WrapErrRateLimit return ErrRateLimit if is the situation, or return error directly
@@ -48,3 +47,13 @@ func WrapErrRateLimit(err error) error {
}
return err
}
// VersionUnMatchError means addon system requirement cannot meet requirement
type VersionUnMatchError struct {
err error
addonName string
}
func (v VersionUnMatchError) Error() string {
return fmt.Sprintf("addon %s system requirement miss match: %v", v.addonName, v.err)
}

150
pkg/addon/reader_gitlab.go Normal file
View File

@@ -0,0 +1,150 @@
/*
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 addon
import (
"encoding/base64"
"github.com/xanzy/go-gitlab"
"github.com/oam-dev/kubevela/pkg/utils"
)
var _ AsyncReader = &gitlabReader{}
// gitlabReader helps get addon's file by git
type gitlabReader struct {
h *gitlabHelper
}
// gitlabHelper helps get addon's file by git
type gitlabHelper struct {
Client *gitlab.Client
Meta *utils.Content
}
// GitLabItem addon's sub item
type GitLabItem struct {
basePath string
tp string
path string
name string
}
// GetType get addon's sub item type
func (g GitLabItem) GetType() string {
return g.tp
}
// GetPath get addon's sub item path
func (g GitLabItem) GetPath() string {
return g.path[len(g.basePath)+1:]
}
// GetName get addon's sub item name
func (g GitLabItem) GetName() string {
return g.name
}
// GetRef ref is empty , use default branch master
func (g *gitlabReader) GetRef() string {
var ref = "master"
if g.h.Meta.GitlabContent.Ref != "" {
return g.h.Meta.GitlabContent.Ref
}
return ref
}
// GetProjectID get gitlab project id
func (g *gitlabReader) GetProjectID() int {
return g.h.Meta.GitlabContent.PId
}
// GetProjectPath get gitlab project path
func (g *gitlabReader) GetProjectPath() string {
return g.h.Meta.GitlabContent.Path
}
// ListAddonMeta relative path to repoURL/basePath
func (g *gitlabReader) ListAddonMeta() (addonCandidates map[string]SourceMeta, err error) {
addonCandidates = make(map[string]SourceMeta)
path := g.GetProjectPath()
ref := g.GetRef()
tree, _, err := g.h.Client.Repositories.ListTree(g.GetProjectID(), &gitlab.ListTreeOptions{Path: &path, Ref: &ref})
if err != nil {
return nil, err
}
for _, node := range tree {
if node.Type == TreeType {
items, err := g.listAddonItem(make([]Item, 0), node.Path)
if err != nil {
return nil, err
}
addonCandidates[node.Name] = SourceMeta{
Name: node.Name,
Items: items,
}
}
}
return addonCandidates, nil
}
func (g *gitlabReader) listAddonItem(item []Item, path string) ([]Item, error) {
ref := g.GetRef()
tree, _, err := g.h.Client.Repositories.ListTree(g.GetProjectID(), &gitlab.ListTreeOptions{Path: &path, Ref: &ref})
if err != nil {
return item, err
}
for _, node := range tree {
switch node.Type {
case TreeType:
item, err = g.listAddonItem(item, node.Path)
if err != nil {
return nil, err
}
case BlobType:
item = append(item, &GitLabItem{
basePath: g.GetProjectPath(),
tp: FileType,
path: node.Path,
name: node.Name,
})
}
}
return item, nil
}
// ReadFile read file content from gitlab
func (g *gitlabReader) ReadFile(path string) (content string, err error) {
ref := g.GetRef()
getFile, _, err := g.h.Client.RepositoryFiles.GetFile(g.GetProjectID(), g.GetProjectPath()+"/"+path, &gitlab.GetFileOptions{Ref: &ref})
if err != nil {
return "", err
}
decodeString, err := base64.StdEncoding.DecodeString(getFile.Content)
if err != nil {
return "", err
}
return string(decodeString), nil
}
func (g *gitlabReader) RelativePath(item Item) string {
return item.GetPath()
}

View File

@@ -0,0 +1,113 @@
/*
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 addon
import (
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"path"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/xanzy/go-gitlab"
"github.com/oam-dev/kubevela/pkg/utils"
)
var baseUrl = "/api/v4"
func gitlabSetup() (client *gitlab.Client, mux *http.ServeMux, teardown func()) {
// mux is the HTTP request multiplexer used with the test server.
mux = http.NewServeMux()
apiHandler := http.NewServeMux()
apiHandler.Handle(baseUrl+"/", http.StripPrefix(baseUrl, mux))
// server is a test HTTP server used to provide mock API responses.
server := httptest.NewServer(apiHandler)
// client is the Gitlab client being tested and is
// configured to use test server.
client, err := gitlab.NewClient("", gitlab.WithBaseURL(server.URL+baseUrl+"/"))
if err != nil {
return
}
return client, mux, server.Close
}
func TestGitlabReader(t *testing.T) {
client, mux, teardown := gitlabSetup()
gitlabPattern := "/projects/9999/repository/files/"
mux.HandleFunc(gitlabPattern, func(rw http.ResponseWriter, req *http.Request) {
queryPath := strings.TrimPrefix(req.URL.Path, gitlabPattern)
localPath := path.Join(testdataPrefix, queryPath)
file, err := testdata.ReadFile(localPath)
// test if it's a file
if err == nil {
content := &gitlab.File{
FilePath: localPath,
FileName: path.Base(queryPath),
Size: *Int(len(file)),
Encoding: "base64",
Ref: "master",
Content: base64.StdEncoding.EncodeToString(file),
}
res, _ := json.Marshal(content)
rw.Write(res)
return
}
// otherwise, it could be directory
dir, err := testdata.ReadDir(localPath)
if err == nil {
contents := make([]*gitlab.TreeNode, 0)
for _, item := range dir {
tp := "file"
if item.IsDir() {
tp = "dir"
}
contents = append(contents, &gitlab.TreeNode{
ID: "",
Name: item.Name(),
Type: tp,
Path: localPath + "/" + item.Name(),
Mode: "",
})
}
dRes, _ := json.Marshal(contents)
rw.Write(dRes)
return
}
rw.Write([]byte("invalid gitlab query"))
})
defer teardown()
gith := &gitlabHelper{
Client: client,
Meta: &utils.Content{GitlabContent: utils.GitlabContent{
PId: 9999,
}},
}
var r AsyncReader = &gitlabReader{gith}
_, err := r.ReadFile("example/metadata.yaml")
assert.NoError(t, err)
}

View File

@@ -37,10 +37,11 @@ const registriesKey = "registries"
type Registry struct {
Name string `json:"name"`
Helm *HelmSource `json:"helm,omitempty"`
Git *GitAddonSource `json:"git,omitempty"`
OSS *OSSAddonSource `json:"oss,omitempty"`
Gitee *GiteeAddonSource `json:"gitee,omitempty"`
Helm *HelmSource `json:"helm,omitempty"`
Git *GitAddonSource `json:"git,omitempty"`
OSS *OSSAddonSource `json:"oss,omitempty"`
Gitee *GiteeAddonSource `json:"gitee,omitempty"`
Gitlab *GitlabAddonSource `json:"gitlab,omitempty"`
}
// RegistryDataStore CRUD addon registry data in configmap

View File

@@ -24,6 +24,7 @@ import (
"github.com/go-resty/resty/v2"
"github.com/pkg/errors"
"github.com/xanzy/go-gitlab"
"github.com/oam-dev/kubevela/pkg/utils"
)
@@ -35,6 +36,10 @@ const (
DirType = "dir"
// FileType means a file
FileType = "file"
// BlobType means a blob
BlobType = "blob"
// TreeType means a tree
TreeType = "tree"
bucketTmpl = "%s://%s.%s"
singleOSSFileTmpl = "%s/%s"
@@ -63,6 +68,14 @@ type GiteeAddonSource struct {
Token string `json:"token,omitempty"`
}
// GitlabAddonSource defines the information about the Gitlab as addon source
type GitlabAddonSource struct {
URL string `json:"url,omitempty" validate:"required"`
Repo string `json:"repo,omitempty" validate:"required"`
Path string `json:"path,omitempty"`
Token string `json:"token,omitempty"`
}
// HelmSource defines the information about the helm repo addon source
type HelmSource struct {
URL string `json:"url,omitempty" validate:"required"`
@@ -122,15 +135,16 @@ func pathWithParent(subPath, parent string) string {
type ReaderType string
const (
gitType ReaderType = "git"
ossType ReaderType = "oss"
giteeType ReaderType = "gitee"
gitType ReaderType = "git"
ossType ReaderType = "oss"
giteeType ReaderType = "gitee"
gitlabType ReaderType = "gitlab"
)
// NewAsyncReader create AsyncReader from
// 1. GitHub url and directory
// 2. OSS endpoint and bucket
func NewAsyncReader(baseURL, bucket, subPath, token string, rdType ReaderType) (AsyncReader, error) {
func NewAsyncReader(baseURL, bucket, repo, subPath, token string, rdType ReaderType) (AsyncReader, error) {
switch rdType {
case gitType:
@@ -182,23 +196,63 @@ func NewAsyncReader(baseURL, bucket, subPath, token string, rdType ReaderType) (
return &giteeReader{
h: gitee,
}, nil
case gitlabType:
baseURL = strings.TrimSuffix(baseURL, ".git")
u, err := url.Parse(baseURL)
if err != nil {
return nil, errors.New("addon registry invalid")
}
_, content, err := utils.ParseGitlab(u.String(), repo)
content.GitlabContent.Path = subPath
if err != nil {
return nil, err
}
gitlabHelper, err := createGitlabHelper(content, token)
if err != nil {
return nil, errors.New("addon registry connect fail")
}
err = gitlabHelper.getGitlabProject(content)
if err != nil {
return nil, err
}
return &gitlabReader{
h: gitlabHelper,
}, nil
}
return nil, fmt.Errorf("invalid addon registry type '%s'", rdType)
}
// getGitlabProject get gitlab project , set project id
func (h *gitlabHelper) getGitlabProject(content *utils.Content) error {
projectURL := content.GitlabContent.Owner + "/" + content.GitlabContent.Repo
projects, _, err := h.Client.Projects.GetProject(projectURL, &gitlab.GetProjectOptions{})
if err != nil {
return err
}
content.GitlabContent.PId = projects.ID
return nil
}
// BuildReader will build a AsyncReader from registry, AsyncReader are needed to read addon files
func (r *Registry) BuildReader() (AsyncReader, error) {
if r.OSS != nil {
o := r.OSS
return NewAsyncReader(o.Endpoint, o.Bucket, o.Path, "", ossType)
return NewAsyncReader(o.Endpoint, o.Bucket, "", o.Path, "", ossType)
}
if r.Git != nil {
g := r.Git
return NewAsyncReader(g.URL, "", g.Path, g.Token, gitType)
return NewAsyncReader(g.URL, "", "", g.Path, g.Token, gitType)
}
if r.Gitee != nil {
g := r.Gitee
return NewAsyncReader(g.URL, "", g.Path, g.Token, giteeType)
return NewAsyncReader(g.URL, "", "", g.Path, g.Token, giteeType)
}
if r.Gitlab != nil {
g := r.Gitlab
return NewAsyncReader(g.URL, "", g.Repo, g.Path, g.Token, gitlabType)
}
return nil, errors.New("registry don't have enough info to build a reader")
}

View File

@@ -47,7 +47,7 @@ func TestPathWithParent(t *testing.T) {
func TestConvert2OssItem(t *testing.T) {
subPath := "sub-addons"
reader, err := NewAsyncReader("ep-beijing.com", "bucket", subPath, "", ossType)
reader, err := NewAsyncReader("ep-beijing.com", "bucket", "", subPath, "", ossType)
assert.NoError(t, err)

View File

@@ -1,5 +1,5 @@
name: example
version: 1.0.0
version: 1.0.1
description: Extended workload to do continuous and progressive delivery
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
url: https://fluxcd.io

View File

@@ -6,6 +6,9 @@ output: {
metadata: {
name: "exampleinput"
namespace: "default"
labels: {
version: context.metadata.version
}
}
data: input: parameter.example
}

View File

@@ -52,7 +52,7 @@ type versionedRegistry struct {
}
func (i *versionedRegistry) ListAddon() ([]*UIData, error) {
chartIndex, err := i.h.GetIndexInfo(i.url, false)
chartIndex, err := i.h.GetIndexInfo(i.url, false, nil)
if err != nil {
return nil, err
}
@@ -107,7 +107,7 @@ func (i *versionedRegistry) resolveAddonListFromIndex(repoName string, index *re
}
func (i versionedRegistry) loadAddon(ctx context.Context, name, version string) (*WholeAddonPackage, error) {
versions, err := i.h.ListVersions(i.url, name, false)
versions, err := i.h.ListVersions(i.url, name, false, nil)
if err != nil {
return nil, err
}
@@ -131,7 +131,7 @@ func (i versionedRegistry) loadAddon(ctx context.Context, name, version string)
return nil, fmt.Errorf("specified version %s not exist", version)
}
for _, chartURL := range addonVersion.URLs {
archive, err := common.HTTPGet(ctx, chartURL)
archive, err := common.HTTPGetWithOption(ctx, chartURL, nil)
if err != nil {
continue
}

View File

@@ -30,9 +30,38 @@ const (
// SystemInfo systemInfo model
type SystemInfo struct {
BaseModel
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
DexConfig DexConfig `json:"dexConfig,omitempty"`
}
// DexConfig dex config
type DexConfig struct {
Issuer string `json:"issuer"`
Web DexWeb `json:"web"`
Storage DexStorage `json:"storage"`
StaticClients []DexStaticClient `json:"staticClients"`
Connectors []interface{} `json:"connectors,omitempty"`
EnablePasswordDB bool `json:"enablePasswordDB"`
}
// DexStorage dex storage
type DexStorage struct {
Type string `json:"type"`
}
// DexWeb dex web
type DexWeb struct {
HTTP string `json:"http"`
}
// DexStaticClient dex static client
type DexStaticClient struct {
ID string `json:"id"`
Name string `json:"name"`
Secret string `json:"secret"`
RedirectURIs []string `json:"redirectURIs"`
}
// TableName return custom table name

View File

@@ -74,28 +74,31 @@ type NameAlias struct {
// CreateAddonRegistryRequest defines the format for addon registry create request
type CreateAddonRegistryRequest struct {
Name string `json:"name" validate:"checkname"`
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty" `
Oss *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
Name string `json:"name" validate:"checkname"`
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty" `
Oss *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
Gitlab *addon.GitlabAddonSource `json:"gitlab,omitempty" `
}
// UpdateAddonRegistryRequest defines the format for addon registry update request
type UpdateAddonRegistryRequest struct {
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty"`
Oss *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty"`
Oss *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
Gitlab *addon.GitlabAddonSource `json:"gitlab,omitempty" `
}
// AddonRegistry defines the format for a single addon registry
type AddonRegistry struct {
Name string `json:"name" validate:"required"`
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty"`
OSS *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
Name string `json:"name" validate:"required"`
Helm *addon.HelmSource `json:"helm,omitempty"`
Git *addon.GitAddonSource `json:"git,omitempty"`
OSS *addon.OSSAddonSource `json:"oss,omitempty"`
Gitee *addon.GiteeAddonSource `json:"gitee,omitempty" `
Gitlab *addon.GitlabAddonSource `json:"gitlab,omitempty" `
}
// ListAddonRegistryResponse list addon registry
@@ -184,6 +187,28 @@ type AddonArgsResponse struct {
Args map[string]string `json:"args"`
}
// ConfigType define the format for listing configuration types
type ConfigType struct {
Definitions []string `json:"definitions"`
Alias string `json:"alias"`
Name string `json:"name"`
Description string `json:"description"`
}
// Config define the metadata of a config
type Config struct {
ConfigType string `json:"configType"`
ConfigTypeAlias string `json:"configTypeAlias"`
Name string `json:"name"`
Project string `json:"project"`
Identifier string `json:"identifier"`
Description string `json:"description"`
CreatedTime *time.Time `json:"createdTime"`
UpdatedTime *time.Time `json:"updatedTime"`
ApplicationStatus common.ApplicationPhase `json:"applicationStatus"`
Status string `json:"status"`
}
// AccessKeyRequest request parameters to access cloud provider
type AccessKeyRequest struct {
AccessKeyID string `json:"accessKeyID"`
@@ -382,6 +407,15 @@ type CreateApplicationRequest struct {
Component *CreateComponentRequest `json:"component"`
}
// CreateConfigRequest is the request body to creates a config
type CreateConfigRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias"`
Project string `json:"project"`
ComponentType string `json:"componentType" validate:"checkname"`
Properties string `json:"properties,omitempty"`
}
// UpdateApplicationRequest update application base config
type UpdateApplicationRequest struct {
Alias string `json:"alias" validate:"checkalias" optional:"true"`
@@ -1081,14 +1115,22 @@ type DetailRevisionResponse struct {
// SystemInfoResponse get SystemInfo
type SystemInfoResponse struct {
model.SystemInfo
SystemInfo
SystemVersion SystemVersion `json:"systemVersion"`
}
// SystemInfo system info
type SystemInfo struct {
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
}
// SystemInfoRequest request by update SystemInfo
type SystemInfoRequest struct {
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
VelaAddress string `json:"velaAddress,omitempty"`
}
// SystemVersion contains KubeVela version
@@ -1277,3 +1319,14 @@ type LoginUserInfoResponse struct {
PlatformPermissions []PermissionBase `json:"platformPermissions"`
ProjectPermissions map[string][]PermissionBase `json:"projectPermissions"`
}
// ChartRepoResponse the response body of chart repo
type ChartRepoResponse struct {
URL string `json:"url"`
SecretName string `json:"secretName"`
}
// ChartRepoResponseList the response body of list chart repo
type ChartRepoResponseList struct {
ChartRepoResponse []*ChartRepoResponse `json:"repos"`
}

View File

@@ -313,11 +313,12 @@ func (u *defaultAddonHandler) CreateAddonRegistry(ctx context.Context, req apis.
func convertAddonRegistry(r pkgaddon.Registry) *apis.AddonRegistry {
return &apis.AddonRegistry{
Name: r.Name,
Git: r.Git,
Gitee: r.Gitee,
OSS: r.OSS,
Helm: r.Helm,
Name: r.Name,
Git: r.Git,
Gitee: r.Gitee,
OSS: r.OSS,
Helm: r.Helm,
Gitlab: r.Gitlab,
}
}
@@ -343,6 +344,8 @@ func (u defaultAddonHandler) UpdateAddonRegistry(ctx context.Context, name strin
r.OSS = req.Oss
case req.Helm != nil:
r.Helm = req.Helm
case req.Gitlab != nil:
r.Gitlab = req.Gitlab
}
err = u.addonRegistryDS.UpdateRegistry(ctx, r)
@@ -398,7 +401,8 @@ func (u *defaultAddonHandler) EnableAddon(ctx context.Context, name string, args
}
// wrap this error with special bcode
if errors.Is(err, pkgaddon.ErrVersionMismatch) {
if errors.As(err, &pkgaddon.VersionUnMatchError{}) {
log.Logger.Error(err)
return bcode.ErrAddonSystemVersionMismatch
}
// except `addon not found`, other errors should return directly
@@ -464,7 +468,7 @@ func (u *defaultAddonHandler) UpdateAddon(ctx context.Context, name string, args
}
// wrap this error with special bcode
if errors.Is(err, pkgaddon.ErrVersionMismatch) {
if errors.As(err, &pkgaddon.VersionUnMatchError{}) {
return bcode.ErrAddonSystemVersionMismatch
}
// except `addon not found`, other errors should return directly
@@ -475,11 +479,12 @@ func (u *defaultAddonHandler) UpdateAddon(ctx context.Context, name string, args
func addonRegistryModelFromCreateAddonRegistryRequest(req apis.CreateAddonRegistryRequest) pkgaddon.Registry {
return pkgaddon.Registry{
Name: req.Name,
Git: req.Git,
OSS: req.Oss,
Gitee: req.Gitee,
Helm: req.Helm,
Name: req.Name,
Git: req.Git,
OSS: req.Oss,
Gitee: req.Gitee,
Helm: req.Helm,
Gitlab: req.Gitlab,
}
}

View File

@@ -33,18 +33,18 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
velatypes "github.com/oam-dev/kubevela/apis/types"
apisv1 "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"
@@ -686,7 +686,7 @@ func (c *applicationUsecaseImpl) DetailPolicy(ctx context.Context, app *model.Ap
}, nil
}
// Deploy deploy app to cluster
// Deploy deploys app to cluster
// means to render oam application config and apply to cluster.
// An event record is generated for each deploy.
func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Application, req apisv1.ApplicationDeployRequest) (*apisv1.ApplicationDeployResponse, error) {
@@ -704,6 +704,11 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
return nil, err
}
// sync configs to clusters
if err := c.syncConfigs4Application(ctx, oamApp, app.Project, workflow.EnvName); err != nil {
return nil, err
}
// step2: check and create deploy event
if !req.Force {
var lastVersion = model.ApplicationRevision{
@@ -792,6 +797,44 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
}, nil
}
// sync configs to clusters
func (c *applicationUsecaseImpl) syncConfigs4Application(ctx context.Context, app *v1beta1.Application, projectName, envName string) error {
var areTerraformComponents = true
for _, m := range app.Spec.Components {
d := &v1beta1.ComponentDefinition{}
if err := c.kubeClient.Get(ctx, client.ObjectKey{Namespace: velatypes.DefaultKubeVelaNS, Name: m.Type}, d); err != nil {
klog.ErrorS(err, "failed to get config type", "ComponentDefinition", m.Type)
}
// check the type of the componentDefinition is Terraform
if d.Spec.Schematic != nil && d.Spec.Schematic.Terraform == nil {
areTerraformComponents = false
}
}
// skip configs sync
if areTerraformComponents {
return nil
}
env, err := c.envUsecase.GetEnv(ctx, envName)
if err != nil {
return err
}
var clusterTargets []*model.ClusterTarget
for _, t := range env.Targets {
target, err := c.targetUsecase.GetTarget(ctx, t)
if err != nil {
return err
}
if target.Cluster != nil {
clusterTargets = append(clusterTargets, target.Cluster)
}
}
if err := SyncConfigs(ctx, c.kubeClient, projectName, clusterTargets); err != nil {
return fmt.Errorf("sync config failure %w", err)
}
return nil
}
func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appModel *model.Application, reqWorkflowName, version string) (*v1beta1.Application, error) {
// Priority 1 uses the requested workflow as release .
// Priority 2 uses the default workflow as release .

View File

@@ -18,6 +18,7 @@ package usecase
import (
"context"
"encoding/json"
"errors"
"net/http"
"time"
@@ -25,26 +26,30 @@ import (
"github.com/coreos/go-oidc"
"github.com/form3tech-oss/jwt-go"
"golang.org/x/oauth2"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "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"
)
const (
secretDexConfig = "dex-config"
keyDex = "dex"
dexConfigName = "dex-config"
secretDexConfigKey = "config.yaml"
dexAddonName = "addon-dex"
jwtIssuer = "vela-issuer"
signedKey = "vela-singned"
// GrantTypeAccess is the grant type for access token
GrantTypeAccess = "access"
@@ -52,12 +57,15 @@ const (
GrantTypeRefresh = "refresh"
)
var signedKey = ""
// AuthenticationUsecase is the usecase of authentication
type AuthenticationUsecase interface {
Login(ctx context.Context, loginReq apisv1.LoginRequest) (*apisv1.LoginResponse, error)
RefreshToken(ctx context.Context, refreshToken string) (*apisv1.RefreshTokenResponse, error)
GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error)
GetLoginType(ctx context.Context) (*apisv1.GetLoginTypeResponse, error)
UpdateDexConfig(ctx context.Context) error
}
type authenticationUsecaseImpl struct {
@@ -86,7 +94,6 @@ type authHandler interface {
}
type dexHandlerImpl struct {
token *oauth2.Token
idToken *oidc.IDToken
ds datastore.DataStore
}
@@ -127,7 +134,6 @@ func (a *authenticationUsecaseImpl) newDexHandler(ctx context.Context, req apisv
return nil, err
}
return &dexHandlerImpl{
token: token,
idToken: idToken,
ds: a.ds,
}, nil
@@ -148,7 +154,7 @@ func (a *authenticationUsecaseImpl) newLocalHandler(req apisv1.LoginRequest) (*l
func (a *authenticationUsecaseImpl) Login(ctx context.Context, loginReq apisv1.LoginRequest) (*apisv1.LoginResponse, error) {
var handler authHandler
var err error
sysInfo, err := a.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := a.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}
@@ -202,13 +208,15 @@ func (a *authenticationUsecaseImpl) generateJWTToken(username, grantType string,
GrantType: grantType,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(signedKey))
}
func (a *authenticationUsecaseImpl) RefreshToken(ctx context.Context, refreshToken string) (*apisv1.RefreshTokenResponse, error) {
claim, err := ParseToken(refreshToken)
if err != nil {
if errors.Is(err, bcode.ErrTokenExpired) {
return nil, bcode.ErrRefreshTokenExpired
}
return nil, err
}
if claim.GrantType == GrantTypeRefresh {
@@ -253,33 +261,10 @@ func ParseToken(tokenString string) (*model.CustomClaims, error) {
}
func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error) {
secret := &v1.Secret{}
if err := a.kubeClient.Get(ctx, types.NamespacedName{
Name: secretDexConfig,
Namespace: velatypes.DefaultKubeVelaNS,
}, secret); err != nil {
if apierrors.IsNotFound(err) {
return nil, bcode.ErrDexConfigNotFound
}
log.Logger.Errorf("failed to get dex config: %s", err.Error())
config, err := getDexConfig(ctx, a.kubeClient)
if err != nil {
return nil, err
}
var config struct {
Issuer string `json:"issuer"`
StaticClients []struct {
ID string `json:"id"`
Secret string `json:"secret"`
RedirectURIs []string `json:"redirectURIs"`
} `json:"staticClients"`
}
if err := yaml.Unmarshal(secret.Data[secretDexConfigKey], &config); err != nil {
log.Logger.Errorf("failed to unmarshal dex config: %s", err.Error())
return nil, bcode.ErrInvalidDexConfig
}
if len(config.StaticClients) < 1 || len(config.StaticClients[0].RedirectURIs) < 1 {
return nil, bcode.ErrInvalidDexConfig
}
return &apisv1.DexConfigResponse{
Issuer: config.Issuer,
ClientID: config.StaticClients[0].ID,
@@ -288,8 +273,121 @@ func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.D
}, nil
}
func (a *authenticationUsecaseImpl) UpdateDexConfig(ctx context.Context) error {
connectors, err := utils.GetDexConnectors(ctx, a.kubeClient)
if err != nil {
return err
}
dexConfig := &corev1.Secret{}
if err := a.kubeClient.Get(ctx, types.NamespacedName{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
}, dexConfig); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
}
config := &model.JSONStruct{}
if dexConfig == nil || dexConfig.Data == nil {
(*config)["connectors"] = connectors
c, err := yaml.Marshal(config)
if err != nil {
return err
}
if err := a.kubeClient.Create(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
secretDexConfigKey: c,
},
}); err != nil {
return err
}
} else {
err = yaml.Unmarshal(dexConfig.Data[secretDexConfigKey], config)
if err != nil {
return err
}
(*config)["connectors"] = connectors
c, err := yaml.Marshal(config)
if err != nil {
return err
}
dexConfig.Data[secretDexConfigKey] = c
if err := a.kubeClient.Update(ctx, dexConfig); err != nil {
return err
}
}
return restartDex(ctx, a.kubeClient)
}
func restartDex(ctx context.Context, kubeClient client.Client) error {
dexApp := &v1beta1.Application{}
if err := kubeClient.Get(ctx, types.NamespacedName{
Name: dexAddonName,
Namespace: velatypes.DefaultKubeVelaNS,
}, dexApp); err != nil {
if kerrors.IsNotFound(err) {
return bcode.ErrDexNotFound
}
return err
}
for i, comp := range dexApp.Spec.Components {
if comp.Name == keyDex {
var v model.JSONStruct
err := json.Unmarshal(comp.Properties.Raw, &v)
if err != nil {
return err
}
// restart the dex server
if _, ok := v["values"]; ok {
v["values"].(map[string]interface{})["env"] = map[string]string{
"TIME_STAMP": time.Now().Format(time.RFC3339),
}
}
dexApp.Spec.Components[i].Properties = v.RawExtension()
if err := kubeClient.Update(ctx, dexApp); err != nil {
return err
}
break
}
}
return nil
}
func getDexConfig(ctx context.Context, kubeClient client.Client) (*model.DexConfig, error) {
dexConfigSecret := &corev1.Secret{}
if err := kubeClient.Get(ctx, types.NamespacedName{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
}, dexConfigSecret); err != nil {
if kerrors.IsNotFound(err) {
return nil, bcode.ErrDexConfigNotFound
}
return nil, err
}
if dexConfigSecret.Data == nil {
return nil, bcode.ErrInvalidDexConfig
}
config := &model.DexConfig{}
if err := yaml.Unmarshal(dexConfigSecret.Data[secretDexConfigKey], config); err != nil {
log.Logger.Errorf("failed to unmarshal dex config: %s", err.Error())
return nil, bcode.ErrInvalidDexConfig
}
if len(config.StaticClients) < 1 || len(config.StaticClients[0].RedirectURIs) < 1 {
return nil, bcode.ErrInvalidDexConfig
}
return config, nil
}
func (a *authenticationUsecaseImpl) GetLoginType(ctx context.Context) (*apisv1.GetLoginTypeResponse, error) {
sysInfo, err := a.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := a.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}
@@ -312,19 +410,18 @@ func (d *dexHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error) {
}
user := &model.User{Email: claims.Email}
userBase := &apisv1.UserBase{Email: claims.Email, Name: claims.Name}
users, err := d.ds.List(ctx, user, &datastore.ListOptions{})
if err != nil {
return nil, err
}
if len(users) > 0 {
u := users[0].(*model.User)
if u.Name != claims.Name {
u.Name = claims.Name
}
u.LastLoginTime = time.Now()
if err := d.ds.Put(ctx, u); err != nil {
return nil, err
}
userBase.Name = u.Name
} else if err := d.ds.Add(ctx, &model.User{
Email: claims.Email,
Name: claims.Name,
@@ -333,10 +430,7 @@ func (d *dexHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error) {
return nil, err
}
return &apisv1.UserBase{
Name: claims.Name,
Email: claims.Email,
}, nil
return userBase, nil
}
func (l *localHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error) {

View File

@@ -19,6 +19,7 @@ package usecase
import (
"context"
"encoding/json"
"io/ioutil"
"reflect"
"strconv"
"time"
@@ -27,13 +28,18 @@ import (
"github.com/coreos/go-oidc"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"golang.org/x/oauth2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
@@ -51,7 +57,7 @@ var _ = Describe("Test authentication usecase functions", func() {
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
authUsecase = &authenticationUsecaseImpl{kubeClient: k8sClient, ds: ds}
sysUsecase = &systemInfoUsecaseImpl{ds: ds}
sysUsecase = &systemInfoUsecaseImpl{ds: ds, kubeClient: k8sClient}
userUsecase = &userUsecaseImpl{ds: ds, sysUsecase: sysUsecase}
})
It("Test Dex login", func() {
@@ -61,10 +67,6 @@ var _ = Describe("Test authentication usecase functions", func() {
})
defer patch.Reset()
dexHandler := dexHandlerImpl{
token: &oauth2.Token{
AccessToken: "access-token",
RefreshToken: "refresh-token",
},
idToken: testIDToken,
ds: ds,
}
@@ -79,6 +81,20 @@ var _ = Describe("Test authentication usecase functions", func() {
err = ds.Get(context.Background(), user)
Expect(err).Should(BeNil())
Expect(user.Email).Should(Equal("test@test.com"))
existUser := &model.User{
Name: "test",
}
err = ds.Delete(context.Background(), existUser)
Expect(err).Should(BeNil())
existUser.Name = "exist-user"
existUser.Email = "test@test.com"
err = ds.Add(context.Background(), existUser)
Expect(err).Should(BeNil())
resp, err = dexHandler.login(context.Background())
Expect(err).Should(BeNil())
Expect(resp.Email).Should(Equal("test@test.com"))
Expect(resp.Name).Should(Equal("exist-user"))
})
It("Test local login", func() {
@@ -99,37 +115,87 @@ var _ = Describe("Test authentication usecase functions", func() {
Expect(resp.Name).Should(Equal("test-login"))
})
It("Test get dex config", func() {
It("Test update dex config", func() {
err := k8sClient.Create(context.Background(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "vela-system",
},
})
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
err = k8sClient.Create(context.Background(), &corev1.Secret{
webserver, err := ioutil.ReadFile("./testdata/dex-config-def.yaml")
Expect(err).Should(Succeed())
var cd v1beta1.ComponentDefinition
err = yaml.Unmarshal(webserver, &cd)
Expect(err).Should(Succeed())
err = k8sClient.Create(context.Background(), &cd)
Expect(err).Should(Succeed())
err = k8sClient.Create(context.Background(), &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: secretDexConfig,
Name: "addon-dex",
Namespace: "vela-system",
},
StringData: map[string]string{
secretDexConfigKey: `
issuer: https://dex.oam.dev
staticClients:
- id: client-id
secret: client-secret
redirectURIs:
- http://localhost:8080/auth/callback
`,
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "dex",
// only for test here
Type: "dex-config",
Properties: &runtime.RawExtension{Raw: []byte(`{"values":{"test":"test"}}`)},
Traits: []common.ApplicationTrait{},
Scopes: map[string]string{},
},
},
},
})
Expect(err).Should(BeNil())
config, err := authUsecase.GetDexConfig(context.Background())
err = k8sClient.Create(context.Background(), &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "a",
Namespace: "vela-system",
Labels: map[string]string{
"app.oam.dev/source-of-truth": "from-inner-system",
"config.oam.dev/catalog": "velacore-config",
"config.oam.dev/type": "config-dex-connector",
"config.oam.dev/sub-type": "ldap",
"project": "abc",
},
},
StringData: map[string]string{
"ldap": `{"clientID":"clientID","clientSecret":"clientSecret"}`,
},
Type: corev1.SecretTypeOpaque,
})
Expect(err).Should(BeNil())
By("try to update dex config without config secret")
err = authUsecase.UpdateDexConfig(context.Background())
Expect(err).Should(BeNil())
dexConfigSecret := &corev1.Secret{}
err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "dex-config", Namespace: "vela-system"}, dexConfigSecret)
Expect(err).Should(BeNil())
config := &model.DexConfig{}
err = yaml.Unmarshal(dexConfigSecret.Data[secretDexConfigKey], config)
Expect(err).Should(BeNil())
Expect(len(config.Connectors)).Should(Equal(1))
By("try to update dex config with config secret")
err = authUsecase.UpdateDexConfig(context.Background())
Expect(err).Should(BeNil())
Expect(config.Issuer).Should(Equal("https://dex.oam.dev"))
Expect(config.ClientID).Should(Equal("client-id"))
Expect(config.ClientSecret).Should(Equal("client-secret"))
Expect(config.RedirectURL).Should(Equal("http://localhost:8080/auth/callback"))
})
It("Test get dex config", func() {
_, err := authUsecase.GetDexConfig(context.Background())
Expect(err).Should(Equal(bcode.ErrInvalidDexConfig))
err = ds.Add(context.Background(), &model.User{Name: "admin", Email: "test@test.com"})
Expect(err).Should(BeNil())
_, err = sysUsecase.UpdateSystemInfo(context.Background(), apisv1.SystemInfoRequest{
LoginType: model.LoginTypeDex,
VelaAddress: "http://velaux.com",
})
Expect(err).Should(BeNil())
config, err := authUsecase.GetDexConfig(context.Background())
Expect(err).Should(BeNil())
Expect(config.Issuer).Should(Equal("http://velaux.com/dex"))
Expect(config.ClientID).Should(Equal("velaux"))
Expect(config.ClientSecret).Should(Equal("velaux-secret"))
Expect(config.RedirectURL).Should(Equal("http://velaux.com/callback"))
})
})

View File

@@ -0,0 +1,478 @@
/*
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 usecase
import (
"context"
"encoding/json"
"fmt"
set "github.com/deckarep/golang-set"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/definition"
)
const (
definitionAlias = definition.UserPrefix + "alias.config.oam.dev"
definitionType = definition.UserPrefix + "type.config.oam.dev"
velaCoreConfig = "velacore-config"
configIsReady = "Ready"
configIsNotReady = "Not ready"
terraformProviderAlias = "Terraform Cloud Provider"
configSyncProjectPrefix = "config-sync"
)
// ConfigHandler handle CRUD of configs
type ConfigHandler interface {
ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error)
GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error)
CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error
GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error)
GetConfig(ctx context.Context, configType, name string) (*apis.Config, error)
DeleteConfig(ctx context.Context, configType, name string) error
}
// NewConfigUseCase returns a config use case
func NewConfigUseCase(authenticationUseCase AuthenticationUsecase) ConfigHandler {
k8sClient, err := clients.GetKubeClient()
if err != nil {
panic(err)
}
return &configUseCaseImpl{
authenticationUseCase: authenticationUseCase,
kubeClient: k8sClient,
}
}
type configUseCaseImpl struct {
kubeClient client.Client
authenticationUseCase AuthenticationUsecase
}
// ListConfigTypes returns all config types
func (u *configUseCaseImpl) ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error) {
defs := &v1beta1.ComponentDefinitionList{}
if err := u.kubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig}); err != nil {
return nil, err
}
var tfDefs []v1beta1.ComponentDefinition
var configTypes []*apis.ConfigType
for _, d := range defs.Items {
if d.Labels[definitionType] == types.TerraformProvider {
tfDefs = append(tfDefs, d)
continue
}
configTypes = append(configTypes, &apis.ConfigType{
Alias: d.Annotations[definitionAlias],
Name: d.Name,
Definitions: []string{d.Name},
Description: d.Annotations[types.AnnoDefinitionDescription],
})
}
if len(tfDefs) > 0 {
tfType := &apis.ConfigType{
Alias: terraformProviderAlias,
Name: types.TerraformProvider,
}
definitions := make([]string, len(tfDefs))
for i, tf := range tfDefs {
definitions[i] = tf.Name
}
tfType.Definitions = definitions
return append(configTypes, tfType), nil
}
return configTypes, nil
}
// GetConfigType returns a config type
func (u *configUseCaseImpl) GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error) {
d := &v1beta1.ComponentDefinition{}
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: configType}, d); err != nil {
return nil, errors.Wrap(err, "failed to get config type")
}
t := &apis.ConfigType{
Alias: d.Annotations[definitionAlias],
Name: configType,
Description: d.Annotations[types.AnnoDefinitionDescription],
}
return t, nil
}
func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error {
app := v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: req.Name,
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
types.AnnotationConfigAlias: req.Alias,
},
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromInner,
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigType: req.ComponentType,
types.LabelConfigProject: req.Project,
},
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: req.Name,
Type: req.ComponentType,
Properties: &runtime.RawExtension{Raw: []byte(req.Properties)},
},
},
},
}
return u.kubeClient.Create(ctx, &app)
}
func (u *configUseCaseImpl) GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error) {
switch configType {
case types.TerraformProvider:
defs := &v1beta1.ComponentDefinitionList{}
if err := u.kubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
definition.UserPrefix + "type.config.oam.dev": types.TerraformProvider,
}); err != nil {
return nil, err
}
var configs []*apis.Config
for _, d := range defs.Items {
subConfigs, err := u.getConfigsByConfigType(ctx, d.Name)
if err != nil {
return nil, err
}
configs = append(configs, subConfigs...)
}
return configs, nil
default:
return u.getConfigsByConfigType(ctx, configType)
}
}
func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configType string) ([]*apis.Config, error) {
var apps = &v1beta1.ApplicationList{}
if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
model.LabelSourceOfTruth: model.FromInner,
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigType: configType,
}); err != nil {
return nil, err
}
configs := make([]*apis.Config, len(apps.Items))
for i, a := range apps.Items {
configs[i] = &apis.Config{
ConfigType: a.Labels[types.LabelConfigType],
Name: a.Name,
Project: a.Labels[types.LabelConfigProject],
CreatedTime: &(a.CreationTimestamp.Time),
}
switch a.Status.Phase {
case common.ApplicationRunning:
configs[i].Status = configIsReady
default:
configs[i].Status = configIsNotReady
}
}
return configs, nil
}
func (u *configUseCaseImpl) GetConfig(ctx context.Context, configType, name string) (*apis.Config, error) {
var a = &v1beta1.Application{}
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
return nil, err
}
config := &apis.Config{
ConfigType: a.Labels[types.LabelConfigType],
Name: a.Name,
Project: a.Labels[types.LabelConfigProject],
CreatedTime: &a.CreationTimestamp.Time,
}
return config, nil
}
func (u *configUseCaseImpl) DeleteConfig(ctx context.Context, configType, name string) error {
var a = &v1beta1.Application{}
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
return err
}
return u.kubeClient.Delete(ctx, a)
}
// ApplicationDeployTarget is the struct of application deploy target
type ApplicationDeployTarget struct {
Namespace string `json:"namespace"`
Clusters []string `json:"clusters"`
}
// SyncConfigs will sync configs to working clusters
func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, targets []*model.ClusterTarget) error {
name := fmt.Sprintf("%s-%s", configSyncProjectPrefix, project)
// get all configs which can be synced to working clusters in the project
var secrets v1.SecretList
if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigSyncToMultiCluster: "true",
}); err != nil {
return err
}
if len(secrets.Items) == 0 {
return nil
}
var objects []map[string]string
for _, s := range secrets.Items {
if s.Labels[types.LabelConfigProject] == "" || s.Labels[types.LabelConfigProject] == project {
objects = append(objects, map[string]string{
"name": s.Name,
"resource": "secret",
})
}
}
if len(objects) == 0 {
klog.InfoS("no configs need to sync to working clusters", "project", project)
return nil
}
objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects})
if err != nil {
return err
}
var app = &v1beta1.Application{}
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
// config sync application doesn't exist, create one
clusterTargets := convertClusterTargets(targets)
if len(clusterTargets) == 0 {
errMsg := "no policy (no targets found) to sync configs"
klog.InfoS(errMsg, "project", project)
return errors.New(errMsg)
}
policies := make([]v1beta1.AppPolicy, len(clusterTargets))
for i, t := range clusterTargets {
properties, err := json.Marshal(t)
if err != nil {
return err
}
policies[i] = v1beta1.AppPolicy{
Type: "topology",
Name: t.Namespace,
Properties: &runtime.RawExtension{
Raw: properties,
},
}
}
scratch := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromInner,
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigProject: project,
},
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: name,
Type: "ref-objects",
Properties: &runtime.RawExtension{Raw: objectsBytes},
},
},
Policies: policies,
},
}
return k8sClient.Create(ctx, scratch)
}
// config sync application exists, update it
app.Spec.Components = []common.ApplicationComponent{
{
Name: name,
Type: "ref-objects",
Properties: &runtime.RawExtension{Raw: objectsBytes},
},
}
currentTargets := make([]ApplicationDeployTarget, len(app.Spec.Policies))
for i, p := range app.Spec.Policies {
var t ApplicationDeployTarget
if err := json.Unmarshal(p.Properties.Raw, &t); err != nil {
return err
}
currentTargets[i] = t
}
mergedTarget := mergeTargets(currentTargets, targets)
if len(mergedTarget) == 0 {
errMsg := "no policy (no targets found) to sync configs"
klog.InfoS(errMsg, "project", project)
return errors.New(errMsg)
}
mergedPolicies := make([]v1beta1.AppPolicy, len(mergedTarget))
for i, t := range mergedTarget {
properties, err := json.Marshal(t)
if err != nil {
return err
}
mergedPolicies[i] = v1beta1.AppPolicy{
Type: "topology",
Name: t.Namespace,
Properties: &runtime.RawExtension{
Raw: properties,
},
}
}
app.Spec.Policies = mergedPolicies
return k8sClient.Update(ctx, app)
}
func mergeTargets(currentTargets []ApplicationDeployTarget, targets []*model.ClusterTarget) []ApplicationDeployTarget {
var (
mergedTargets []ApplicationDeployTarget
// make sure the clusters of target with same namespace are merged
clusterTargets = convertClusterTargets(targets)
)
for _, c := range currentTargets {
var hasSameNamespace bool
for _, t := range clusterTargets {
if c.Namespace == t.Namespace {
hasSameNamespace = true
clusters := set.NewSetFromSlice(stringToInterfaceSlice(t.Clusters))
for _, cluster := range c.Clusters {
clusters.Add(cluster)
}
mergedTargets = append(mergedTargets, ApplicationDeployTarget{Namespace: c.Namespace,
Clusters: interfaceToStringSlice(clusters.ToSlice())})
}
}
if !hasSameNamespace {
mergedTargets = append(mergedTargets, c)
}
}
for _, t := range clusterTargets {
var hasSameNamespace bool
for _, c := range currentTargets {
if c.Namespace == t.Namespace {
hasSameNamespace = true
}
}
if !hasSameNamespace {
mergedTargets = append(mergedTargets, t)
}
}
return mergedTargets
}
func convertClusterTargets(targets []*model.ClusterTarget) []ApplicationDeployTarget {
type Target struct {
Namespace string `json:"namespace"`
Clusters []interface{} `json:"clusters"`
}
var (
clusterTargets []Target
namespaceSet = set.NewSet()
)
for i := 0; i < len(targets); i++ {
clusters := set.NewSet(targets[i].ClusterName)
for j := i + 1; j < len(targets); j++ {
if targets[i].Namespace == targets[j].Namespace {
clusters.Add(targets[j].ClusterName)
}
}
if namespaceSet.Contains(targets[i].Namespace) {
continue
}
clusterTargets = append(clusterTargets, Target{
Namespace: targets[i].Namespace,
Clusters: clusters.ToSlice(),
})
namespaceSet.Add(targets[i].Namespace)
}
t := make([]ApplicationDeployTarget, len(clusterTargets))
for i, ct := range clusterTargets {
t[i] = ApplicationDeployTarget{
Namespace: ct.Namespace,
Clusters: interfaceToStringSlice(ct.Clusters),
}
}
return t
}
func interfaceToStringSlice(i []interface{}) []string {
var s []string
for _, v := range i {
s = append(s, v.(string))
}
return s
}
func stringToInterfaceSlice(i []string) []interface{} {
var s []interface{}
for _, v := range i {
s = append(s, v)
}
return s
}
// destroySyncConfigsApp will delete the application which is used to sync configs
func destroySyncConfigsApp(ctx context.Context, k8sClient client.Client, project string) error {
name := fmt.Sprintf("%s-%s", configSyncProjectPrefix, project)
var app = &v1beta1.Application{}
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
}
return k8sClient.Delete(ctx, app)
}

View File

@@ -0,0 +1,589 @@
/*
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 usecase
import (
"context"
"encoding/json"
"sort"
"strings"
"testing"
. "github.com/agiledragon/gomonkey/v2"
"gotest.tools/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/definition"
"github.com/oam-dev/kubevela/pkg/multicluster"
)
func TestListConfigTypes(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
def1 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def1",
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
definitionType: types.TerraformProvider,
},
},
}
def2 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def2",
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build()
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
return k8sClient, nil, nil
})
defer patches.Reset()
h := NewConfigUseCase(nil)
type args struct {
h ConfigHandler
}
type want struct {
configTypes []*apis.ConfigType
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "success",
args: args{
h: h,
},
want: want{
configTypes: []*apis.ConfigType{
{
Name: "def2",
Alias: "Def2",
Definitions: []string{"def2"},
},
{
Alias: "Terraform Cloud Provider",
Name: types.TerraformProvider,
Definitions: []string{
"def1",
},
},
},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.args.h.ListConfigTypes(ctx, "")
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
assert.DeepEqual(t, got, tc.want.configTypes)
})
}
}
func TestGetConfigType(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
def2 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def2",
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def2).Build()
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
return k8sClient, nil, nil
})
defer patches.Reset()
h := NewConfigUseCase(nil)
type args struct {
h ConfigHandler
name string
}
type want struct {
configType *apis.ConfigType
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "success",
args: args{
h: h,
name: "def2",
},
want: want{
configType: &apis.ConfigType{
Alias: "Def2",
Name: "def2",
},
},
},
{
name: "error",
args: args{
h: h,
name: "def99",
},
want: want{
errMsg: "failed to get config type",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.args.h.GetConfigType(ctx, tc.args.name)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
assert.DeepEqual(t, got, tc.want.configType)
})
}
}
func TestCreateConfig(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
h := &configUseCaseImpl{kubeClient: k8sClient}
type args struct {
h ConfigHandler
req apis.CreateConfigRequest
}
type want struct {
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "delete config when it's not ready",
args: args{
h: h,
req: apis.CreateConfigRequest{
Name: "a",
ComponentType: "b",
Project: "c",
},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
err := tc.args.h.CreateConfig(ctx, tc.args.req)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
})
}
}
func TestGetConfigs(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
def1 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def1",
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
definitionType: types.TerraformProvider,
},
},
}
def2 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def2",
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build()
h := &configUseCaseImpl{kubeClient: k8sClient}
type args struct {
configType string
h ConfigHandler
}
type want struct {
configs []*apis.Config
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "success",
args: args{
configType: types.TerraformProvider,
h: h,
},
want: want{
configs: nil,
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.args.h.GetConfigs(ctx, tc.args.configType)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
assert.DeepEqual(t, got, tc.want.configs)
})
}
}
func TestMergeTargets(t *testing.T) {
currentTargets := []ApplicationDeployTarget{
{
Namespace: "n1",
Clusters: []string{"c1", "c2"},
}, {
Namespace: "n2",
Clusters: []string{"c3"},
},
}
targets := []*model.ClusterTarget{
{
Namespace: "n3",
ClusterName: "c4",
}, {
Namespace: "n1",
ClusterName: "c5",
},
{
Namespace: "n2",
ClusterName: "c3",
},
}
expected := []ApplicationDeployTarget{
{
Namespace: "n1",
Clusters: []string{"c1", "c2", "c5"},
}, {
Namespace: "n2",
Clusters: []string{"c3"},
}, {
Namespace: "n3",
Clusters: []string{"c4"},
},
}
got := mergeTargets(currentTargets, targets)
for i, g := range got {
clusters := g.Clusters
sort.SliceStable(clusters, func(i, j int) bool {
return clusters[i] < clusters[j]
})
got[i].Clusters = clusters
}
assert.DeepEqual(t, expected, got)
}
func TestConvert(t *testing.T) {
targets := []*model.ClusterTarget{
{
Namespace: "n3",
ClusterName: "c4",
}, {
Namespace: "n1",
ClusterName: "c5",
},
{
Namespace: "n2",
ClusterName: "c3",
},
{
Namespace: "n3",
ClusterName: "c5",
},
}
expected := []ApplicationDeployTarget{
{
Namespace: "n3",
Clusters: []string{"c4", "c5"},
},
{
Namespace: "n1",
Clusters: []string{"c5"},
}, {
Namespace: "n2",
Clusters: []string{"c3"},
},
}
got := convertClusterTargets(targets)
for i, g := range got {
clusters := g.Clusters
sort.SliceStable(clusters, func(i, j int) bool {
return clusters[i] < clusters[j]
})
got[i].Clusters = clusters
}
assert.DeepEqual(t, expected, got)
}
func TestDestroySyncConfigsApp(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
app1 := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "config-sync-p1",
Namespace: types.DefaultKubeVelaNS,
},
}
k8sClient1 := fake.NewClientBuilder().WithScheme(s).WithObjects(app1).Build()
k8sClient2 := fake.NewClientBuilder().Build()
type args struct {
project string
k8sClient client.Client
}
type want struct {
errMsg string
}
ctx := context.Background()
testcases := map[string]struct {
args args
want want
}{
"found": {
args: args{
project: "p1",
k8sClient: k8sClient1,
},
},
"not found": {
args: args{
project: "p1",
k8sClient: k8sClient2,
},
want: want{
errMsg: "no kind is registered for the type v1beta1.Application",
},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
err := destroySyncConfigsApp(ctx, tc.args.k8sClient, tc.args.project)
if err != nil || tc.want.errMsg != "" {
if !strings.Contains(err.Error(), tc.want.errMsg) {
assert.ErrorContains(t, err, tc.want.errMsg)
}
}
})
}
}
func TestSyncConfigs(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
secret1 := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "s1",
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigProject: "p1",
types.LabelConfigSyncToMultiCluster: "true",
},
},
}
policies := []ApplicationDeployTarget{{
Namespace: "n9",
Clusters: []string{"c19"},
}}
properties, _ := json.Marshal(policies)
app1 := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "config-sync-p2",
Namespace: types.DefaultKubeVelaNS,
},
Spec: v1beta1.ApplicationSpec{
Policies: []v1beta1.AppPolicy{{
Name: "c19",
Type: "topology",
Properties: &runtime.RawExtension{Raw: properties},
}},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(secret1, app1).Build()
type args struct {
project string
targets []*model.ClusterTarget
}
type want struct {
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "create",
args: args{
project: "p1",
targets: []*model.ClusterTarget{{
ClusterName: "c1",
Namespace: "n1",
}},
},
},
{
name: "update",
args: args{
project: "p2",
targets: []*model.ClusterTarget{{
ClusterName: "c1",
Namespace: "n1",
}},
},
},
{
name: "skip config sync",
args: args{
project: "p3",
targets: []*model.ClusterTarget{{
ClusterName: "c1",
Namespace: "n1",
}},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
err := SyncConfigs(ctx, k8sClient, tc.args.project, tc.args.targets)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
})
}
}

View File

@@ -20,57 +20,108 @@ import (
"context"
"strconv"
"helm.sh/helm/v3/pkg/repo"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
v1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/helm"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types2 "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"helm.sh/helm/v3/pkg/repo"
)
// NewHelmUsecase return a helmHandler
func NewHelmUsecase() HelmHandler {
c, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kube client failure %s", err.Error())
}
return defaultHelmHandler{
helper: helm.NewHelperWithCache(),
helper: helm.NewHelperWithCache(),
k8sClient: c,
}
}
// HelmHandler responsible handle helm related interface
type HelmHandler interface {
ListChartNames(ctx context.Context, url string, skipCache bool) ([]string, error)
ListChartVersions(ctx context.Context, url string, chartName string, skipCache bool) (repo.ChartVersions, error)
GetChartValues(ctx context.Context, url string, chartName string, version string, skipCache bool) (map[string]interface{}, error)
ListChartNames(ctx context.Context, url string, secretName string, skipCache bool) ([]string, error)
ListChartVersions(ctx context.Context, url string, chartName string, secretName string, skipCache bool) (repo.ChartVersions, error)
GetChartValues(ctx context.Context, url string, chartName string, version string, secretName string, skipCache bool) (map[string]interface{}, error)
ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error)
}
type defaultHelmHandler struct {
helper *helm.Helper
helper *helm.Helper
k8sClient client.Client
}
func (d defaultHelmHandler) ListChartNames(ctx context.Context, url string, skipCache bool) ([]string, error) {
charts, err := d.helper.ListChartsFromRepo(url, skipCache)
func (d defaultHelmHandler) ListChartNames(ctx context.Context, repoURL string, secretName string, skipCache bool) ([]string, error) {
if !utils.IsValidURL(repoURL) {
return nil, bcode.ErrRepoInvalidURL
}
var opts *common.HTTPOption
var err error
if len(secretName) != 0 {
opts, err = helm.SetBasicAuthInfo(ctx, d.k8sClient, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: secretName})
if err != nil {
return nil, bcode.ErrRepoBasicAuth
}
}
charts, err := d.helper.ListChartsFromRepo(repoURL, skipCache, opts)
if err != nil {
log.Logger.Errorf("cannot fetch charts repo: %s, error: %s", url, err.Error())
log.Logger.Errorf("cannot fetch charts repo: %s, error: %s", utils.Sanitize(repoURL), err.Error())
return nil, bcode.ErrListHelmChart
}
return charts, nil
}
func (d defaultHelmHandler) ListChartVersions(ctx context.Context, url string, chartName string, skipCache bool) (repo.ChartVersions, error) {
chartVersions, err := d.helper.ListVersions(url, chartName, skipCache)
func (d defaultHelmHandler) ListChartVersions(ctx context.Context, repoURL string, chartName string, secretName string, skipCache bool) (repo.ChartVersions, error) {
if !utils.IsValidURL(repoURL) {
return nil, bcode.ErrRepoInvalidURL
}
var opts *common.HTTPOption
var err error
if len(secretName) != 0 {
opts, err = helm.SetBasicAuthInfo(ctx, d.k8sClient, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: secretName})
if err != nil {
return nil, bcode.ErrRepoBasicAuth
}
}
chartVersions, err := d.helper.ListVersions(repoURL, chartName, skipCache, opts)
if err != nil {
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s error: %s", url, chartName, err.Error())
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s error: %s", utils.Sanitize(repoURL), utils.Sanitize(chartName), err.Error())
return nil, bcode.ErrListHelmVersions
}
if len(chartVersions) == 0 {
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s", url, chartName)
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s", utils.Sanitize(repoURL), utils.Sanitize(chartName))
return nil, bcode.ErrChartNotExist
}
return chartVersions, nil
}
func (d defaultHelmHandler) GetChartValues(ctx context.Context, url string, chartName string, version string, skipCache bool) (map[string]interface{}, error) {
v, err := d.helper.GetValuesFromChart(url, chartName, version, skipCache)
func (d defaultHelmHandler) GetChartValues(ctx context.Context, repoURL string, chartName string, version string, secretName string, skipCache bool) (map[string]interface{}, error) {
if !utils.IsValidURL(repoURL) {
return nil, bcode.ErrRepoInvalidURL
}
var opts *common.HTTPOption
var err error
if len(secretName) != 0 {
opts, err = helm.SetBasicAuthInfo(ctx, d.k8sClient, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: secretName})
if err != nil {
return nil, bcode.ErrRepoBasicAuth
}
}
v, err := d.helper.GetValuesFromChart(repoURL, chartName, version, skipCache, opts)
if err != nil {
log.Logger.Errorf("cannot fetch chart values repo: %s, chart: %s, version: %s, error: %s", url, chartName, version, err.Error())
log.Logger.Errorf("cannot fetch chart values repo: %s, chart: %s, version: %s, error: %s", utils.Sanitize(repoURL), utils.Sanitize(chartName), utils.Sanitize(version), err.Error())
return nil, bcode.ErrGetChartValues
}
res := make(map[string]interface{}, len(v))
@@ -78,6 +129,50 @@ func (d defaultHelmHandler) GetChartValues(ctx context.Context, url string, char
return res, nil
}
func (d defaultHelmHandler) ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error) {
var res []*v1.ChartRepoResponse
var err error
if len(projectName) != 0 {
projectSecrets := corev1.SecretList{}
opts := []client.ListOption{
client.MatchingLabels{oam.LabelConfigType: "config-helm-repository", types.LabelConfigProject: projectName},
client.InNamespace(types.DefaultKubeVelaNS),
}
err = d.k8sClient.List(ctx, &projectSecrets, opts...)
if err != nil {
return nil, err
}
for _, item := range projectSecrets.Items {
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
}
}
globalSecrets := corev1.SecretList{}
selector := metav1.LabelSelector{
MatchLabels: map[string]string{oam.LabelConfigType: "config-helm-repository"},
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: types.LabelConfigProject, Operator: metav1.LabelSelectorOpDoesNotExist},
},
}
ls, _ := metav1.LabelSelectorAsSelector(&selector)
err = d.k8sClient.List(ctx, &globalSecrets, &client.ListOptions{
LabelSelector: ls,
Namespace: types.DefaultKubeVelaNS,
})
if err != nil {
return nil, err
}
for _, item := range globalSecrets.Items {
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
}
return &v1.ChartRepoResponseList{ChartRepoResponse: res}, nil
}
// this func will flatten a nested map, the key will flatten with separator "." and the value's type will be keep
// src is the map you want to flatten the output will be set in dest map
// eg : src is {a:{b:{c:true}}} , the dest is {a.b.c:true}

View File

@@ -17,9 +17,23 @@ limitations under the License.
package usecase
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/oam-dev/kubevela/pkg/oam/util"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
"github.com/stretchr/testify/assert"
)
@@ -37,6 +51,147 @@ func TestFlattenKeyFunc(t *testing.T) {
assert.Equal(t, dstMap, res)
}
var _ = Describe("Test helm repo list", func() {
ctx := context.Background()
var pSec, gSec v1.Secret
BeforeEach(func() {
pSec = v1.Secret{}
gSec = v1.Secret{}
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(yaml.Unmarshal([]byte(projectSecret), &pSec)).Should(BeNil())
Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil())
Expect(k8sClient.Create(ctx, &pSec)).Should(BeNil())
Expect(k8sClient.Create(ctx, &gSec)).Should(BeNil())
})
AfterEach(func() {
Expect(k8sClient.Delete(ctx, &gSec)).Should(BeNil())
Expect(k8sClient.Delete(ctx, &pSec)).Should(BeNil())
})
It("Test list with project ", func() {
u := NewHelmUsecase()
list, err := u.ListChartRepo(ctx, "my-project")
Expect(err).Should(BeNil())
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(2))
found := 0
for _, response := range list.ChartRepoResponse {
if response.SecretName == "project-helm-repo" {
Expect(response.URL).Should(BeEquivalentTo("https://kedacore.github.io/charts"))
found++
}
if response.SecretName == "global-helm-repo" {
Expect(response.URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
found++
}
}
Expect(found).Should(BeEquivalentTo(2))
})
It("Test list func with not exist project", func() {
u := NewHelmUsecase()
list, err := u.ListChartRepo(ctx, "not-exist-project")
Expect(err).Should(BeNil())
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
Expect(list.ChartRepoResponse[0].URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
Expect(list.ChartRepoResponse[0].SecretName).Should(BeEquivalentTo("global-helm-repo"))
})
It("Test list func without project", func() {
u := NewHelmUsecase()
list, err := u.ListChartRepo(ctx, "")
Expect(err).Should(BeNil())
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
Expect(list.ChartRepoResponse[0].URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
Expect(list.ChartRepoResponse[0].SecretName).Should(BeEquivalentTo("global-helm-repo"))
})
})
var _ = Describe("test helm usecasae", func() {
ctx := context.Background()
var repoSec v1.Secret
BeforeEach(func() {
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
repoSec = v1.Secret{}
Expect(yaml.Unmarshal([]byte(repoSecret), &repoSec)).Should(BeNil())
Expect(k8sClient.Create(ctx, &repoSec)).Should(BeNil())
})
AfterEach(func() {
Expect(k8sClient.Delete(ctx, &repoSec)).Should(BeNil())
})
It("helm associated usecase interface test", func() {
var mockServer *httptest.Server
handler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
u, p, ok := request.BasicAuth()
if !ok || u != "admin" || p != "admin" {
writer.WriteHeader(401)
return
}
switch {
case request.URL.Path == "/index.yaml":
index, err := ioutil.ReadFile("./testdata/helm/index.yaml")
indexFile := string(index)
indexFile = strings.ReplaceAll(indexFile, "server-url", mockServer.URL)
if err != nil {
writer.Write([]byte(err.Error()))
return
}
writer.Write([]byte(indexFile))
return
case strings.Contains(request.URL.Path, "mysql-8.8.23.tgz"):
pkg, err := ioutil.ReadFile("./testdata/helm/mysql-8.8.23.tgz")
if err != nil {
writer.Write([]byte(err.Error()))
return
}
writer.Write(pkg)
return
default:
writer.Write([]byte("404 page not found"))
}
})
mockServer = httptest.NewServer(handler)
defer mockServer.Close()
u := NewHelmUsecase()
charts, err := u.ListChartNames(ctx, mockServer.URL, "repo-secret", false)
Expect(err).Should(BeNil())
Expect(len(charts)).Should(BeEquivalentTo(1))
Expect(charts[0]).Should(BeEquivalentTo("mysql"))
versions, err := u.ListChartVersions(ctx, mockServer.URL, "mysql", "repo-secret", false)
Expect(err).Should(BeNil())
Expect(len(versions)).Should(BeEquivalentTo(1))
Expect(versions[0].Version).Should(BeEquivalentTo("8.8.23"))
values, err := u.GetChartValues(ctx, mockServer.URL, "mysql", "8.8.23", "repo-secret", false)
Expect(err).Should(BeNil())
Expect(values).ShouldNot(BeNil())
Expect(len(values)).ShouldNot(BeEquivalentTo(0))
})
It("coverage not secret notExist error", func() {
u := NewHelmUsecase()
_, err := u.ListChartNames(ctx, "http://127.0.0.1:8080", "repo-secret-notExist", false)
Expect(err).ShouldNot(BeNil())
_, err = u.ListChartVersions(ctx, "http://127.0.0.1:8080", "mysql", "repo-secret-notExist", false)
Expect(err).ShouldNot(BeNil())
_, err = u.GetChartValues(ctx, "http://127.0.0.1:8080", "mysql", "8.8.23", "repo-secret-notExist", false)
Expect(err).ShouldNot(BeNil())
})
})
var (
src = `{
"OAMSpecVer":"v0.2",
@@ -175,4 +330,43 @@ var (
"webhookService.port": 11443,
"webhookService.type": "ClusterIP"
}`
globalSecret = `
apiVersion: v1
stringData:
url: https://charts.bitnami.com/bitnami
kind: Secret
metadata:
labels:
config.oam.dev/type: config-helm-repository
name: global-helm-repo
namespace: vela-system
type: Opaque
`
projectSecret = `
apiVersion: v1
kind: Secret
metadata:
name: project-helm-repo
namespace: vela-system
labels:
config.oam.dev/type: config-helm-repository
config.oam.dev/project: my-project
stringData:
url: https://kedacore.github.io/charts
type: Opaque
`
repoSecret = `
apiVersion: v1
kind: Secret
metadata:
name: repo-secret
namespace: vela-system
labels:
config.oam.dev/type: config-helm-repository
config.oam.dev/project: my-project-2
stringData:
username: admin
password: admin
type: Opaque
`
)

View File

@@ -20,9 +20,16 @@ import (
"context"
"errors"
"fmt"
"strings"
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
@@ -46,6 +53,7 @@ type ProjectUsecase interface {
DeleteProjectUser(ctx context.Context, projectName string, userName string) error
UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error)
Init(ctx context.Context) error
GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error)
}
type projectUsecaseImpl struct {
@@ -290,7 +298,11 @@ func (p *projectUsecaseImpl) DeleteProject(ctx context.Context, name string) err
return err
}
}
return p.ds.Delete(ctx, &model.Project{Name: name})
if err := p.ds.Delete(ctx, &model.Project{Name: name}); err != nil {
return err
}
// delete config-sync application
return destroySyncConfigsApp(ctx, p.k8sClient, name)
}
// CreateProject create project
@@ -466,6 +478,118 @@ func (p *projectUsecaseImpl) UpdateProjectUser(ctx context.Context, projectName
return ConvertProjectUserModel2Base(&projectUser), nil
}
func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error) {
var (
configs []*apisv1.Config
legacyTerraformProviders []*apisv1.Config
apps = &v1beta1.ApplicationList{}
)
if err := p.k8sClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
model.LabelSourceOfTruth: model.FromInner,
types.LabelConfigCatalog: velaCoreConfig,
}); err != nil {
return nil, err
}
if configType == types.TerraformProvider || configType == "" {
// legacy providers
var providers = &terraformapi.ProviderList{}
if err := p.k8sClient.List(ctx, providers, client.InNamespace(types.DefaultAppNamespace)); err != nil {
return nil, err
}
for _, p := range providers.Items {
if p.Labels[types.LabelConfigCatalog] == velaCoreConfig {
continue
}
t := p.CreationTimestamp.Time
var status = configIsNotReady
if p.Status.State == terraformtypes.ProviderIsReady {
status = configIsReady
}
legacyTerraformProviders = append(legacyTerraformProviders, &apisv1.Config{
Name: p.Name,
CreatedTime: &t,
Status: status,
})
}
}
switch configType {
case types.TerraformProvider:
for _, a := range apps.Items {
appProject := a.Labels[types.LabelConfigProject]
if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) ||
!strings.Contains(a.Labels[types.LabelConfigType], "terraform-") {
continue
}
configs = append(configs, &apisv1.Config{
ConfigType: a.Labels[types.LabelConfigType],
Name: a.Name,
Project: appProject,
CreatedTime: &(a.CreationTimestamp.Time),
ApplicationStatus: a.Status.Phase,
})
}
configs = append(configs, legacyTerraformProviders...)
case "":
for _, a := range apps.Items {
appProject := a.Labels[types.LabelConfigProject]
if appProject != "" && appProject != projectName {
continue
}
configs = append(configs, &apisv1.Config{
ConfigType: a.Labels[types.LabelConfigType],
Name: a.Name,
Project: appProject,
CreatedTime: &(a.CreationTimestamp.Time),
ApplicationStatus: a.Status.Phase,
})
}
configs = append(configs, legacyTerraformProviders...)
case types.DexConnector, types.HelmRepository, types.ImageRegistry:
t := strings.ReplaceAll(configType, "config-", "")
for _, a := range apps.Items {
appProject := a.Labels[types.LabelConfigProject]
if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) {
continue
}
if a.Labels[types.LabelConfigType] == t {
configs = append(configs, &apisv1.Config{
ConfigType: a.Labels[types.LabelConfigType],
Name: a.Name,
Project: appProject,
CreatedTime: &(a.CreationTimestamp.Time),
ApplicationStatus: a.Status.Phase,
})
}
}
default:
return nil, errors.New("unsupported config type")
}
for i, c := range configs {
if c.ConfigType != "" {
d := &v1beta1.ComponentDefinition{}
err := p.k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: c.ConfigType}, d)
if err != nil {
klog.InfoS("failed to get component definition", "ComponentDefinition", configType, "err", err)
} else {
configs[i].ConfigTypeAlias = d.Annotations[definitionAlias]
}
}
if c.ApplicationStatus != "" {
if c.ApplicationStatus == common.ApplicationRunning {
configs[i].Status = configIsReady
} else {
configs[i].Status = configIsNotReady
}
}
}
return configs, nil
}
// ConvertProjectModel2Base convert project model to base struct
func ConvertProjectModel2Base(project *model.Project, owner *model.User) *apisv1.ProjectBase {
base := &apisv1.ProjectBase{

View File

@@ -18,14 +18,23 @@ package usecase
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"gotest.tools/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
@@ -155,6 +164,18 @@ var _ = Describe("Test project usecase functions", func() {
Name: "test-project",
Description: "this is a project description",
}
app1 := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "config-sync-test-project",
Namespace: "vela-system",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Type: "aaa",
}},
},
}
Expect(k8sClient.Create(context.TODO(), app1)).Should(BeNil())
_, err := projectUsecase.CreateProject(context.TODO(), req)
Expect(err).Should(BeNil())
@@ -223,6 +244,19 @@ var _ = Describe("Test project usecase functions", func() {
Name: "test-project",
Description: "this is a project description",
}
app1 := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "config-sync-test-project",
Namespace: "vela-system",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Type: "aaa",
}},
},
}
Expect(k8sClient.Create(context.TODO(), app1)).Should(BeNil())
_, err := projectUsecase.CreateProject(context.TODO(), req)
Expect(err).Should(BeNil())
@@ -244,3 +278,222 @@ var _ = Describe("Test project usecase functions", func() {
Expect(roles.Total).Should(BeEquivalentTo(0))
})
})
func TestProjectGetConfigs(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
terraformapi.AddToScheme(s)
createdTime, _ := time.Parse(time.UnixDate, "Wed Apr 7 11:06:39 PST 2022")
app1 := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "a1",
Namespace: velatypes.DefaultKubeVelaNS,
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromInner,
velatypes.LabelConfigCatalog: velaCoreConfig,
velatypes.LabelConfigType: "terraform-provider",
"config.oam.dev/project": "p1",
},
CreationTimestamp: metav1.NewTime(createdTime),
},
Status: common.AppStatus{Phase: common.ApplicationRunning},
}
app2 := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "a2",
Namespace: velatypes.DefaultKubeVelaNS,
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromInner,
velatypes.LabelConfigCatalog: velaCoreConfig,
velatypes.LabelConfigType: "terraform-provider",
},
CreationTimestamp: metav1.NewTime(createdTime),
},
Status: common.AppStatus{Phase: common.ApplicationRunning},
}
app3 := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "a3",
Namespace: velatypes.DefaultKubeVelaNS,
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromInner,
velatypes.LabelConfigCatalog: velaCoreConfig,
velatypes.LabelConfigType: "dex-connector",
"config.oam.dev/project": "p3",
},
CreationTimestamp: metav1.NewTime(createdTime),
},
Status: common.AppStatus{Phase: common.ApplicationRunning},
}
provider1 := &terraformapi.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "provider1",
Namespace: "default",
CreationTimestamp: metav1.NewTime(createdTime),
},
Status: terraformapi.ProviderStatus{
State: terraformtypes.ProviderIsReady,
},
}
provider2 := &terraformapi.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: "provider2",
Namespace: "default",
Labels: map[string]string{
velatypes.LabelConfigCatalog: velaCoreConfig,
},
},
Status: terraformapi.ProviderStatus{
State: terraformtypes.ProviderIsNotReady,
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(app1, app2, app3, provider1, provider2).Build()
h := &projectUsecaseImpl{k8sClient: k8sClient}
type args struct {
projectName string
configType string
h ProjectUsecase
}
type want struct {
configs []*apisv1.Config
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "project is matched",
args: args{
projectName: "p1",
configType: "terraform-provider",
h: h,
},
want: want{
configs: []*apisv1.Config{{
ConfigType: "terraform-provider",
Name: "a1",
Project: "p1",
CreatedTime: &createdTime,
ApplicationStatus: "running",
Status: "Ready",
}, {
ConfigType: "terraform-provider",
Name: "a2",
Project: "",
CreatedTime: &createdTime,
ApplicationStatus: "running",
Status: "Ready",
}, {
Name: "provider1",
CreatedTime: &createdTime,
Status: "Ready",
}},
},
},
{
name: "project is not matched",
args: args{
projectName: "p999",
configType: "terraform-provider",
h: h,
},
want: want{
configs: []*apisv1.Config{{
ConfigType: "terraform-provider",
Name: "a2",
Project: "",
CreatedTime: &createdTime,
ApplicationStatus: "running",
Status: "Ready",
}, {
Name: "provider1",
CreatedTime: &createdTime,
Status: "Ready",
}},
},
},
{
name: "config type is empty",
args: args{
projectName: "p3",
configType: "",
h: h,
},
want: want{
configs: []*apisv1.Config{{
ConfigType: "terraform-provider",
Name: "a2",
Project: "",
CreatedTime: &createdTime,
ApplicationStatus: "running",
Status: "Ready",
}, {
ConfigType: "dex-connector",
Name: "a3",
Project: "p3",
CreatedTime: &createdTime,
ApplicationStatus: "running",
Status: "Ready",
}, {
Name: "provider1",
CreatedTime: &createdTime,
Status: "Ready",
}},
},
},
{
name: "config type is dex",
args: args{
projectName: "p3",
configType: "config-dex-connector",
h: h,
},
want: want{
configs: []*apisv1.Config{{
ConfigType: "dex-connector",
Name: "a3",
Project: "p3",
CreatedTime: &createdTime,
ApplicationStatus: "running",
Status: "Ready",
}},
},
},
{
name: "config type is invalid",
args: args{
configType: "xxx",
h: h,
},
want: want{
errMsg: "unsupported config type",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.args.h.GetConfigs(ctx, tc.args.projectName, tc.args.configType)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
assert.DeepEqual(t, got, tc.want.configs)
})
}
}

View File

@@ -182,6 +182,7 @@ var ResourceMaps = map[string]resourceMetadata{
pathName: "userName",
},
"applicationTemplate": {},
"configs": {},
},
pathName: "projectName",
},
@@ -207,6 +208,14 @@ var ResourceMaps = map[string]resourceMetadata{
"permission": {},
"systemSetting": {},
"definition": {},
"configType": {
pathName: "configType",
subResources: map[string]resourceMetadata{
"config": {
pathName: "name",
},
},
},
}
var existResourcePaths = convert(ResourceMaps)

View File

@@ -18,31 +18,52 @@ package usecase
import (
"context"
"errors"
"fmt"
"reflect"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/rand"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
v1 "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/version"
)
// SystemInfoUsecase is usecase for systemInfoCollection
type SystemInfoUsecase interface {
Get(ctx context.Context) (*model.SystemInfo, error)
GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error)
UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error)
Init(ctx context.Context) error
}
type systemInfoUsecaseImpl struct {
ds datastore.DataStore
ds datastore.DataStore
kubeClient client.Client
}
// NewSystemInfoUsecase return a systemInfoCollectionUsecase
func NewSystemInfoUsecase(ds datastore.DataStore) SystemInfoUsecase {
return &systemInfoUsecaseImpl{ds: ds}
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("failed to get kube client: %s", err.Error())
}
return &systemInfoUsecaseImpl{ds: ds, kubeClient: kubecli}
}
func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) {
func (u systemInfoUsecaseImpl) Get(ctx context.Context) (*model.SystemInfo, error) {
// first get request will init systemInfoCollection{installId: {random}, enableCollection: true}
info := &model.SystemInfo{}
entities, err := u.ds.List(ctx, info, &datastore.ListOptions{})
@@ -54,7 +75,7 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
if info.LoginType == "" {
info.LoginType = model.LoginTypeLocal
}
return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
return info, nil
}
installID := rand.String(16)
info.InstallID = installID
@@ -64,11 +85,26 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
if err != nil {
return nil, err
}
return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
return info, nil
}
func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) {
// first get request will init systemInfoCollection{installId: {random}, enableCollection: true}
info, err := u.Get(ctx)
if err != nil {
return nil, err
}
return &v1.SystemInfoResponse{
SystemInfo: convertInfoToBase(info),
SystemVersion: v1.SystemVersion{
VelaVersion: version.VelaVersion,
GitVersion: version.GitRevision,
},
}, nil
}
func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error) {
info, err := u.GetSystemInfo(ctx)
info, err := u.Get(ctx)
if err != nil {
return nil, err
}
@@ -78,10 +114,131 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
LoginType: sysInfo.LoginType,
BaseModel: model.BaseModel{
CreateTime: info.CreateTime,
}}
},
}
if sysInfo.LoginType == model.LoginTypeDex {
admin := &model.User{Name: model.DefaultAdminUserName}
if err := u.ds.Get(ctx, admin); err != nil {
return nil, err
}
if admin.Email == "" {
return nil, bcode.ErrEmptyAdminEmail
}
if err := generateDexConfig(ctx, u.kubeClient, sysInfo.VelaAddress, &modifiedInfo); err != nil {
return nil, err
}
}
err = u.ds.Put(ctx, &modifiedInfo)
if err != nil {
return nil, err
}
return &v1.SystemInfoResponse{SystemInfo: modifiedInfo, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
return &v1.SystemInfoResponse{
SystemInfo: v1.SystemInfo{
InstallID: modifiedInfo.InstallID,
EnableCollection: modifiedInfo.EnableCollection,
LoginType: modifiedInfo.LoginType,
},
SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision},
}, nil
}
func (u systemInfoUsecaseImpl) Init(ctx context.Context) error {
info, err := u.Get(ctx)
if err != nil {
return err
}
signedKey = info.InstallID
_, err = initDexConfig(ctx, u.kubeClient, "http://velaux.com", &model.SystemInfo{})
return err
}
func convertInfoToBase(info *model.SystemInfo) v1.SystemInfo {
return v1.SystemInfo{
InstallID: info.InstallID,
EnableCollection: info.EnableCollection,
LoginType: info.LoginType,
}
}
func generateDexConfig(ctx context.Context, kubeClient client.Client, velaAddress string, info *model.SystemInfo) error {
secret, err := initDexConfig(ctx, kubeClient, velaAddress, info)
if err != nil {
return err
}
connectors, err := utils.GetDexConnectors(ctx, kubeClient)
if err != nil {
return err
}
if len(connectors) < 1 {
return bcode.ErrNoDexConnector
}
config, err := model.NewJSONStructByStruct(info.DexConfig)
if err != nil {
return err
}
(*config)["connectors"] = connectors
c, err := yaml.Marshal(config)
if err != nil {
return err
}
if !reflect.DeepEqual(secret.Data[secretDexConfigKey], c) {
secret.Data[secretDexConfigKey] = c
if err := kubeClient.Update(ctx, secret); err != nil {
return err
}
if err := restartDex(ctx, kubeClient); err != nil && !errors.Is(err, bcode.ErrDexNotFound) {
return err
}
}
return nil
}
func initDexConfig(ctx context.Context, kubeClient client.Client, velaAddress string, info *model.SystemInfo) (*corev1.Secret, error) {
dexConfig := model.DexConfig{
Issuer: fmt.Sprintf("%s/dex", velaAddress),
Web: model.DexWeb{
HTTP: "0.0.0.0:5556",
},
Storage: model.DexStorage{
Type: "memory",
},
StaticClients: []model.DexStaticClient{
{
ID: "velaux",
Name: "Vela UX",
Secret: "velaux-secret",
RedirectURIs: []string{fmt.Sprintf("%s/callback", velaAddress)},
},
},
EnablePasswordDB: true,
}
info.DexConfig = dexConfig
secret := &corev1.Secret{}
if err := kubeClient.Get(ctx, types.NamespacedName{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
}, secret); err != nil {
if !apierrors.IsNotFound(err) {
return nil, err
}
config, err := yaml.Marshal(info.DexConfig)
if err != nil {
return nil, err
}
if err := kubeClient.Create(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
secretDexConfigKey: config,
},
}); err != nil {
return nil, err
}
}
return secret, nil
}

View File

@@ -0,0 +1,26 @@
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: Dex config allow users to specify dex config in properties
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: dex-config
namespace: vela-system
spec:
schematic:
cue:
template: |
import "encoding/yaml"
output: {
apiVersion: "v1"
kind: "Secret"
metadata: name: context.name
namespace: context.namespace
type: "Opaque"
stringData: "config.yaml": yaml.Marshal(parameter)
}
parameter: {}
workload:
type: autodetects.core.oam.dev

View File

@@ -0,0 +1,36 @@
apiVersion: v1
entries:
mysql:
- annotations:
category: Database
apiVersion: v2
appVersion: 8.0.28
created: "2022-04-07T03:26:37.378966939Z"
dependencies:
- name: common
repository: https://charts.bitnami.com/bitnami
tags:
- bitnami-common
version: 1.x.x
description: Chart to create a Highly available MySQL cluster
digest: 96f79c6daba90fb40fc698979fab33f7a60987b1d23cd5080bc885129568a423
home: https://github.com/bitnami/charts/tree/master/bitnami/mysql
icon: https://bitnami.com/assets/stacks/mysql/img/mysql-stack-220x234.png
keywords:
- mysql
- database
- sql
- cluster
- high availability
maintainers:
- email: containers@bitnami.com
name: Bitnami
name: mysql
sources:
- https://github.com/bitnami/bitnami-docker-mysql
- https://mysql.com
urls:
- server-url/mysql-8.8.23.tgz
version: 8.8.23
generated: "2022-04-07T03:26:37Z"
serverInfo: {}

Binary file not shown.

View File

@@ -19,6 +19,8 @@ package usecase
import (
"context"
"errors"
"math/rand"
"strconv"
"golang.org/x/crypto/bcrypt"
"helm.sh/helm/v3/pkg/time"
@@ -82,7 +84,15 @@ func (u *userUsecaseImpl) Init(ctx context.Context) error {
Name: admin,
}); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
pwd := utils2.RandomString(8)
pwd := func() string {
p := utils2.RandomString(8)
p += strconv.Itoa(rand.Intn(9)) // #nosec
r := append([]rune(p), 'a'+rune(rand.Intn(26))) // #nosec
rand.Shuffle(len(r), func(i, j int) { r[i], r[j] = r[j], r[i] })
p = string(r)
return p
}()
encrypted, err := GeneratePasswordHash(pwd)
if err != nil {
return err
@@ -96,7 +106,7 @@ func (u *userUsecaseImpl) Init(ctx context.Context) error {
return err
}
// print default password of admin user in log
log.Logger.Infof("init admin user, password is %s", pwd)
log.Logger.Infof("initialized admin username and password: admin / %s\n", pwd)
secret := &corev1.Secret{}
if err := u.k8sClient.Get(ctx, k8stypes.NamespacedName{
Name: admin,
@@ -184,7 +194,7 @@ func (u *userUsecaseImpl) DeleteUser(ctx context.Context, username string) error
}
}
if err := u.ds.Delete(ctx, &model.User{Name: username}); err != nil {
log.Logger.Errorf("failed to delete user", username, err.Error())
log.Logger.Errorf("failed to delete user %s %v", utils2.Sanitize(username), err.Error())
return err
}
return nil
@@ -192,7 +202,7 @@ func (u *userUsecaseImpl) DeleteUser(ctx context.Context, username string) error
// CreateUser create user
func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserRequest) (*apisv1.UserBase, error) {
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := u.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}
@@ -220,7 +230,7 @@ func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserR
// UpdateUser update user
func (u *userUsecaseImpl) UpdateUser(ctx context.Context, user *model.User, req apisv1.UpdateUserRequest) (*apisv1.UserBase, error) {
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := u.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}

View File

@@ -25,11 +25,14 @@ import (
"github.com/emicklei/go-restful/v3"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
@@ -74,7 +77,12 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test HandleApplicationWebhook function", func() {
_, err := projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
var ns = corev1.Namespace{}
ns.Name = types.DefaultKubeVelaNS
err := k8sClient.Create(context.TODO(), &ns)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
_, err = projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
Expect(err).Should(BeNil())
_, err = targetUsecase.CreateTarget(context.TODO(), apisv1.CreateTargetRequest{Name: "dev-target-webhook", Project: "project-webhook"})

View File

@@ -35,4 +35,8 @@ var (
ErrInvalidLoginRequest = NewBcode(400, 12008, "the login request is invalid")
// ErrInvalidDexConfig is the error of invalid dex config
ErrInvalidDexConfig = NewBcode(400, 12009, "the dex config is invalid")
// ErrRefreshTokenExpired is the error of refresh token expired
ErrRefreshTokenExpired = NewBcode(400, 12010, "the refresh token is expired")
// ErrNoDexConnector is the error of no dex connector
ErrNoDexConnector = NewBcode(400, 12011, "there is no dex connector")
)

View File

@@ -30,3 +30,9 @@ var ErrChartNotExist = NewBcode(200, 13004, "this chart not exist in the reposit
// ErrSkipCacheParameter means the skip cache parameter miss config
var ErrSkipCacheParameter = NewBcode(400, 13005, "skip cache parameter miss config, the value only can be true or false")
// ErrRepoBasicAuth means extract repo auth info from secret error
var ErrRepoBasicAuth = NewBcode(400, 13006, "extract repo auth info from secret error")
// ErrRepoInvalidURL means user input url is invalid
var ErrRepoInvalidURL = NewBcode(400, 13007, "user input repository url is invalid")

View File

@@ -33,4 +33,8 @@ var (
ErrUserInconsistentPassword = NewBcode(401, 14007, "the password is inconsistent with the user")
// ErrUsernameNotExist is the error of username not exist
ErrUsernameNotExist = NewBcode(401, 14008, "the username is not exist")
// ErrDexNotFound is the error of dex not found
ErrDexNotFound = NewBcode(200, 14009, "the dex is not found")
// ErrEmptyAdminEmail is the error of empty admin email
ErrEmptyAdminEmail = NewBcode(400, 14010, "the admin email is empty, please set the admin email before using sso login")
)

View File

@@ -0,0 +1,52 @@
/*
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 utils
import (
"context"
"encoding/json"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
)
// GetDexConnectors returns the dex connectors for Dex connector controller
func GetDexConnectors(ctx context.Context, k8sClient client.Client) ([]map[string]interface{}, error) {
secrets := &v1.SecretList{}
if err := k8sClient.List(ctx, secrets, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{types.LabelConfigType: types.DexConnector}); err != nil {
return nil, err
}
connectors := make([]map[string]interface{}, len(secrets.Items))
for i, s := range secrets.Items {
var data map[string]interface{}
key := s.Labels[types.LabelConfigSubType]
err := json.Unmarshal(s.Data[key], &data)
if err != nil {
return nil, err
}
connectors[i] = map[string]interface{}{
"type": s.Labels[types.LabelConfigSubType],
"id": s.Name,
"name": s.Name,
"config": data,
}
}
return connectors, nil
}

View File

@@ -0,0 +1,106 @@
/*
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 utils
import (
"context"
"encoding/json"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func TestGetDexConnectors(t *testing.T) {
ctx := context.Background()
type args struct {
k8sClient client.Client
}
type want struct {
connectors []map[string]interface{}
err error
}
ldap := map[string]interface{}{
"clientID": "clientID",
"clientSecret": "clientSecret",
"callbackURL": "redirectURL",
"xxx": map[string]interface{}{"aaa": "bbb", "ccc": "ddd"},
}
data, err := json.Marshal(ldap)
assert.NoError(t, err)
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "a",
Namespace: "vela-system",
Labels: map[string]string{
"app.oam.dev/source-of-truth": "from-inner-system",
"config.oam.dev/catalog": "velacore-config",
"config.oam.dev/type": "config-dex-connector",
"config.oam.dev/sub-type": "ldap",
"project": "abc",
},
},
Data: map[string][]byte{
"ldap": data,
},
Type: v1.SecretTypeOpaque,
}
k8sClient := fake.NewClientBuilder().WithObjects(secret).Build()
testcaes := map[string]struct {
args args
want want
}{
"test": {args: args{
k8sClient: k8sClient,
},
want: want{
connectors: []map[string]interface{}{{
"id": "a",
"name": "a",
"type": "ldap",
"config": map[string]interface{}{
"clientID": "clientID",
"clientSecret": "clientSecret",
"callbackURL": "redirectURL",
"xxx": map[string]interface{}{"aaa": "bbb", "ccc": "ddd"},
},
}},
},
},
}
for name, tc := range testcaes {
t.Run(name, func(t *testing.T) {
got, err := GetDexConnectors(ctx, tc.args.k8sClient)
if err != tc.want.err {
t.Errorf("%s: GetDexConnectors() error = %v, wantErr %v", name, err, tc.want.err)
return
}
if !reflect.DeepEqual(got, tc.want.connectors) {
t.Errorf("%s: GetDexConnectors() = %v, want %v", name, got, tc.want.connectors)
}
})
}
}

View File

@@ -296,7 +296,7 @@ func (s *enabledAddonWebService) GetWebService() *restful.WebService {
Filter(s.rbacUsecase.CheckPerm("addon", "list")).
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
Returns(200, "OK", apis.ListAddonResponse{}).
Returns(200, "OK", apis.ListEnabledAddonResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListAddonResponse{}))

View File

@@ -0,0 +1,198 @@
/*
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 webservice
import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
// ConfigWebService returns config web service
func ConfigWebService(u usecase.ConfigHandler, rbacUseCase usecase.RBACUsecase) WebService {
return &configWebService{
handler: u,
rbacUseCase: rbacUseCase,
}
}
type configWebService struct {
handler usecase.ConfigHandler
rbacUseCase usecase.RBACUsecase
}
func (s *configWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/config_types").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for configuration management")
tags := []string{"config"}
ws.Route(ws.GET("/").To(s.listConfigTypes).
Doc("list all config types").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("configType", "list")).
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
Returns(200, "OK", []apis.ConfigType{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]apis.ConfigType{}))
ws.Route(ws.GET("/{configType}").To(s.getConfigType).
Doc("get a config type").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("configType", "get")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Returns(200, "OK", apis.ConfigType{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ConfigType{}))
ws.Route(ws.POST("/{configType}").To(s.createConfig).
Doc("create or update a config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("configType", "create")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Reads(apis.CreateConfigRequest{}).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Returns(404, "Not Found", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{configType}/configs").To(s.getConfigs).
Doc("get configs from a config type").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("config", "list")).
Param(ws.PathParameter("configType", "identifier of the config").DataType("string")).
Returns(200, "OK", []*apis.Config{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ConfigType{}))
ws.Route(ws.GET("/{configType}/configs/{name}").To(s.getConfig).
Doc("get a config from a config type").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("config", "get")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
Returns(200, "OK", []*apis.Config{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ConfigType{}))
ws.Route(ws.DELETE("/{configType}/configs/{name}").To(s.deleteConfig).
Doc("delete a config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("config", "delete")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Returns(404, "Not Found", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Filter(authCheckFilter)
return ws
}
func (s *configWebService) listConfigTypes(req *restful.Request, res *restful.Response) {
types, err := s.handler.ListConfigTypes(req.Request.Context(), req.QueryParameter("query"))
if len(types) == 0 && err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(types)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) getConfigType(req *restful.Request, res *restful.Response) {
t, err := s.handler.GetConfigType(req.Request.Context(), req.PathParameter("configType"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(t)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) createConfig(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateConfigRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
err := s.handler.CreateConfig(req.Request.Context(), createReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) getConfigs(req *restful.Request, res *restful.Response) {
configs, err := s.handler.GetConfigs(req.Request.Context(), req.PathParameter("configType"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(configs)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) getConfig(req *restful.Request, res *restful.Response) {
t, err := s.handler.GetConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(t)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) deleteConfig(req *restful.Request, res *restful.Response) {
err := s.handler.DeleteConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}

View File

@@ -46,11 +46,21 @@ func (h helmWebService) GetWebService() *restful.WebService {
tags := []string{"repository", "helm"}
// List charts
ws.Route(ws.GET("/chart_repos").To(h.listRepo).
Doc("list chart repo").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("project", "the config project").DataType("string")).
Returns(200, "OK", []string{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
// List charts
ws.Route(ws.GET("/charts").To(h.listCharts).
Doc("list charts").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
Returns(200, "OK", []string{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
@@ -60,6 +70,7 @@ func (h helmWebService) GetWebService() *restful.WebService {
Doc("list versions").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
Returns(200, "OK", v1.ChartVersionListResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
@@ -69,6 +80,7 @@ func (h helmWebService) GetWebService() *restful.WebService {
Doc("get chart value").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
Returns(200, "OK", map[string]interface{}{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
@@ -79,12 +91,13 @@ func (h helmWebService) GetWebService() *restful.WebService {
func (h helmWebService) listCharts(req *restful.Request, res *restful.Response) {
url := req.QueryParameter("repoUrl")
secName := req.QueryParameter("secretName")
skipCache, err := isSkipCache(req)
if err != nil {
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
return
}
charts, err := h.usecase.ListChartNames(context.Background(), url, skipCache)
charts, err := h.usecase.ListChartNames(context.Background(), url, secName, skipCache)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -99,13 +112,14 @@ func (h helmWebService) listCharts(req *restful.Request, res *restful.Response)
func (h helmWebService) listVersions(req *restful.Request, res *restful.Response) {
url := req.QueryParameter("repoUrl")
chartName := req.PathParameter("chart")
secName := req.QueryParameter("secretName")
skipCache, err := isSkipCache(req)
if err != nil {
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
return
}
versions, err := h.usecase.ListChartVersions(context.Background(), url, chartName, skipCache)
versions, err := h.usecase.ListChartVersions(context.Background(), url, chartName, secName, skipCache)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -119,6 +133,7 @@ func (h helmWebService) listVersions(req *restful.Request, res *restful.Response
func (h helmWebService) chartValues(req *restful.Request, res *restful.Response) {
url := req.QueryParameter("repoUrl")
secName := req.QueryParameter("secretName")
chartName := req.PathParameter("chart")
version := req.PathParameter("version")
skipCache, err := isSkipCache(req)
@@ -127,7 +142,7 @@ func (h helmWebService) chartValues(req *restful.Request, res *restful.Response)
return
}
versions, err := h.usecase.GetChartValues(context.Background(), url, chartName, version, skipCache)
versions, err := h.usecase.GetChartValues(context.Background(), url, chartName, version, secName, skipCache)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -139,6 +154,20 @@ func (h helmWebService) chartValues(req *restful.Request, res *restful.Response)
}
}
func (h helmWebService) listRepo(req *restful.Request, res *restful.Response) {
project := req.QueryParameter("project")
repos, err := h.usecase.ListChartRepo(context.Background(), project)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(repos)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func isSkipCache(req *restful.Request) (bool, error) {
skipStr := req.QueryParameter("skipCache")
skipCache := false

View File

@@ -176,6 +176,16 @@ func (n *projectWebService) GetWebService() *restful.WebService {
Returns(200, "OK", []apis.PermissionBase{}).
Writes([]apis.PermissionBase{}))
ws.Route(ws.GET("/{projectName}/configs").To(n.getConfigs).
Doc("get configs which are in a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(n.rbacUsecase.CheckPerm("project", "list")).
Param(ws.QueryParameter("configType", "config type").DataType("string")).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Returns(200, "OK", []*apis.Config{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]*apis.Config{}))
ws.Filter(authCheckFilter)
return ws
}
@@ -503,3 +513,23 @@ func (n *projectWebService) listProjectPermissions(req *restful.Request, res *re
return
}
}
func (n *projectWebService) getConfigs(req *restful.Request, res *restful.Response) {
configs, err := n.projectUsecase.GetConfigs(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("configType"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if configs == nil {
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
return
}
err = res.WriteEntity(configs)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}

View File

@@ -59,7 +59,7 @@ func returns500(b *restful.RouteBuilder) {
b.Returns(http.StatusInternalServerError, "Bummer, something went wrong", nil)
}
// Init init all webservice, pass in the required parameter object.
// Init inits all webservice, pass in the required parameter object.
// It can be implemented using the idea of dependency injection.
func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Duration, initDatabase bool) map[string]interface{} {
clusterUsecase := usecase.NewClusterUsecase(ds)
@@ -73,15 +73,16 @@ func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Durat
definitionUsecase := usecase.NewDefinitionUsecase()
addonUsecase := usecase.NewAddonUsecase(addonCacheTime)
envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase, definitionUsecase, envUsecase)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase)
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
systemInfoUsecase := usecase.NewSystemInfoUsecase(ds)
helmUsecase := usecase.NewHelmUsecase()
userUsecase := usecase.NewUserUsecase(ds, projectUsecase, systemInfoUsecase, rbacUsecase)
authenticationUsecase := usecase.NewAuthenticationUsecase(ds, systemInfoUsecase, userUsecase)
configUseCase := usecase.NewConfigUseCase(authenticationUsecase)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase)
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
// Modules that require default data initialization, Call it here in order
if initDatabase {
initData(ctx, userUsecase, rbacUsecase, projectUsecase, targetUsecase)
initData(ctx, userUsecase, rbacUsecase, projectUsecase, targetUsecase, systemInfoUsecase)
}
// Application
@@ -95,6 +96,9 @@ func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Durat
RegisterWebService(NewEnabledAddonWebService(addonUsecase, rbacUsecase))
RegisterWebService(NewAddonRegistryWebService(addonUsecase, rbacUsecase))
// Config management
RegisterWebService(ConfigWebService(configUseCase, rbacUsecase))
// Resources
RegisterWebService(NewClusterWebService(clusterUsecase, rbacUsecase))
RegisterWebService(NewOAMApplication(oamApplicationUsecase, rbacUsecase))

View File

@@ -66,6 +66,11 @@ func (c *CR2UX) initCache(ctx context.Context) error {
}
func (c *CR2UX) shouldSync(ctx context.Context, targetApp *v1beta1.Application, del bool) bool {
if targetApp != nil && targetApp.Labels != nil && targetApp.Labels[model.LabelSourceOfTruth] == model.FromInner {
return false
}
key := formatAppComposedName(targetApp.Name, targetApp.Namespace)
cachedData, ok := c.cache.Load(key)
if ok {
@@ -85,9 +90,11 @@ func (c *CR2UX) shouldSync(ctx context.Context, targetApp *v1beta1.Application,
// This is a double check to make sure the app not be converted and un-deployed
sot := c.CheckSoTFromAppMeta(ctx, targetApp.Name, targetApp.Namespace, CheckSoTFromCR(targetApp))
switch sot {
case model.FromUX, model.FromInner:
case model.FromUX:
// we don't sync if the application is not created from CR
return false
case model.FromInner:
// we don't sync if the application is not created from CR
return false
case model.FromCR:

View File

@@ -25,6 +25,7 @@ import (
corev1 "k8s.io/api/core/v1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
@@ -95,5 +96,28 @@ var _ = Describe("Test Cache", func() {
Expect(cr2ux.shouldSync(ctx, app1, false)).Should(BeEquivalentTo(true))
})
It("Test don't cache with from inner system label", func() {
dbNamespace := "cache-db-ns2-test"
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: dbNamespace})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
var ns = corev1.Namespace{}
ns.Name = dbNamespace
err = k8sClient.Create(context.TODO(), &ns)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
cr2ux := CR2UX{ds: ds, cli: k8sClient, cache: sync.Map{}}
ctx := context.Background()
app1 := &v1beta1.Application{}
app1.Name = "app1"
app1.Namespace = dbNamespace
app1.Generation = 1
app1.Spec.Components = []common.ApplicationComponent{}
app1.Labels = make(map[string]string)
app1.Labels[model.LabelSourceOfTruth] = model.FromInner
Expect(k8sClient.Create(ctx, app1)).Should(BeNil())
Expect(cr2ux.shouldSync(ctx, app1, false)).Should(BeEquivalentTo(false))
})
})

View File

@@ -20,6 +20,7 @@ import (
"context"
"strconv"
"strings"
"time"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
@@ -49,7 +50,8 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
model.LabelSourceOfTruth: model.FromCR,
},
}
appMeta.CreateTime = targetApp.CreationTimestamp.Time
appMeta.UpdateTime = time.Now()
// 1. convert app meta and env
dsApp := &model.DataStoreApp{
AppMeta: appMeta,

116
pkg/cmd/builder.go Normal file
View File

@@ -0,0 +1,116 @@
/*
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 cmd
import (
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/term"
)
// Builder build command with factory
type Builder struct {
cmd *cobra.Command
f Factory
}
// NamespaceFlagConfig config for namespace flag in cmd
type NamespaceFlagConfig struct {
completion bool
usage string
loadEnv bool
}
// NamespaceFlagOption the option for configuring namespace flag in cmd
type NamespaceFlagOption interface {
ApplyToNamespaceFlagOptions(*NamespaceFlagConfig)
}
func newNamespaceFlagOptions(options ...NamespaceFlagOption) NamespaceFlagConfig {
cfg := NamespaceFlagConfig{
completion: true,
usage: usageNamespace,
loadEnv: true,
}
for _, option := range options {
option.ApplyToNamespaceFlagOptions(&cfg)
}
return cfg
}
// NamespaceFlagNoCompletionOption disable auto-completion for namespace flag
type NamespaceFlagNoCompletionOption struct{}
// ApplyToNamespaceFlagOptions .
func (option NamespaceFlagNoCompletionOption) ApplyToNamespaceFlagOptions(cfg *NamespaceFlagConfig) {
cfg.completion = false
}
// NamespaceFlagUsageOption the usage description for namespace flag
type NamespaceFlagUsageOption string
// ApplyToNamespaceFlagOptions .
func (option NamespaceFlagUsageOption) ApplyToNamespaceFlagOptions(cfg *NamespaceFlagConfig) {
cfg.usage = string(option)
}
// NamespaceFlagDisableEnvOption disable loading namespace from env
type NamespaceFlagDisableEnvOption struct{}
// ApplyToNamespaceFlagOptions .
func (option NamespaceFlagDisableEnvOption) ApplyToNamespaceFlagOptions(cfg *NamespaceFlagConfig) {
cfg.loadEnv = false
}
// WithNamespaceFlag add namespace flag to the command, by default, it will also add env flag to the command
func (builder *Builder) WithNamespaceFlag(options ...NamespaceFlagOption) *Builder {
cfg := newNamespaceFlagOptions(options...)
builder.cmd.Flags().StringP(flagNamespace, "n", "", cfg.usage)
if cfg.completion {
cmdutil.CheckErr(builder.cmd.RegisterFlagCompletionFunc(
flagNamespace,
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return GetNamespacesForCompletion(cmd.Context(), builder.f, toComplete)
}))
}
if cfg.loadEnv {
return builder.WithEnvFlag()
}
return builder
}
// WithEnvFlag add env flag to the command
func (builder *Builder) WithEnvFlag() *Builder {
builder.cmd.PersistentFlags().StringP(flagEnv, "e", "", usageEnv)
return builder
}
// WithResponsiveWriter format the command outputs
func (builder *Builder) WithResponsiveWriter() *Builder {
builder.cmd.SetOut(term.NewResponsiveWriter(builder.cmd.OutOrStdout()))
return builder
}
// Build construct the command
func (builder *Builder) Build() *cobra.Command {
return builder.cmd
}
// NewCommandBuilder builder for command
func NewCommandBuilder(f Factory, cmd *cobra.Command) *Builder {
return &Builder{cmd: cmd, f: f}
}

72
pkg/cmd/completion.go Normal file
View File

@@ -0,0 +1,72 @@
/*
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 cmd
import (
"context"
"strings"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/oam"
)
func listObjectNamesForCompletion(ctx context.Context, f Factory, gvk schema.GroupVersionKind, listOptions []client.ListOption, toComplete string) ([]string, cobra.ShellCompDirective) {
uns := &unstructured.UnstructuredList{}
uns.SetGroupVersionKind(gvk)
if err := f.Client().List(ctx, uns, listOptions...); err != nil {
return nil, cobra.ShellCompDirectiveError
}
var candidates []string
for _, obj := range uns.Items {
if name := obj.GetName(); strings.HasPrefix(name, toComplete) {
candidates = append(candidates, name)
}
}
return candidates, cobra.ShellCompDirectiveNoFileComp
}
// GetNamespacesForCompletion auto-complete the namespace
func GetNamespacesForCompletion(ctx context.Context, f Factory, toComplete string) ([]string, cobra.ShellCompDirective) {
return listObjectNamesForCompletion(ctx, f, corev1.SchemeGroupVersion.WithKind("Namespace"), nil, toComplete)
}
// GetRevisionForCompletion auto-complete the revision according to the application
func GetRevisionForCompletion(ctx context.Context, f Factory, appName string, namespace string, toComplete string) ([]string, cobra.ShellCompDirective) {
var options []client.ListOption
if namespace != "" {
options = append(options, client.InNamespace(namespace))
}
if appName != "" {
options = append(options, client.MatchingLabels{oam.LabelAppName: appName})
}
return listObjectNamesForCompletion(ctx, f, v1beta1.SchemeGroupVersion.WithKind(v1beta1.ApplicationRevisionKind), options, toComplete)
}
// GetApplicationsForCompletion auto-complete application
func GetApplicationsForCompletion(ctx context.Context, f Factory, namespace string, toComplete string) ([]string, cobra.ShellCompDirective) {
var options []client.ListOption
if namespace != "" {
options = append(options, client.InNamespace(namespace))
}
return listObjectNamesForCompletion(ctx, f, v1beta1.SchemeGroupVersion.WithKind(v1beta1.ApplicationKind), options, toComplete)
}

46
pkg/cmd/factory.go Normal file
View File

@@ -0,0 +1,46 @@
/*
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 cmd
import (
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// Factory client factory for running command
type Factory interface {
Client() client.Client
}
// ClientGetter function for getting client
type ClientGetter func() (client.Client, error)
type defaultFactory struct {
ClientGetter
}
// Client return the client for command line use, interrupt if error encountered
func (f *defaultFactory) Client() client.Client {
cli, err := f.ClientGetter()
cmdutil.CheckErr(err)
return cli
}
// NewDefaultFactory create a factory based on client getter function
func NewDefaultFactory(clientGetter ClientGetter) Factory {
return &defaultFactory{ClientGetter: clientGetter}
}

27
pkg/cmd/types.go Normal file
View File

@@ -0,0 +1,27 @@
/*
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 cmd
const (
flagNamespace = "namespace"
flagEnv = "env"
)
const (
usageNamespace = "If present, the namespace scope for this CLI request"
usageEnv = "The environment name for the CLI request"
)

52
pkg/cmd/utils.go Normal file
View File

@@ -0,0 +1,52 @@
/*
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 cmd
import (
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/env"
)
// GetNamespace get namespace from command flags and env
func GetNamespace(f Factory, cmd *cobra.Command) string {
namespace, err := cmd.Flags().GetString(flagNamespace)
cmdutil.CheckErr(err)
if namespace != "" {
return namespace
}
// find namespace from env
envName, err := cmd.Flags().GetString(flagEnv)
if err != nil {
// ignore env if the command does not use the flag
return ""
}
cmdutil.CheckErr(common.SetGlobalClient(f.Client()))
var envMeta *types.EnvMeta
if envName != "" {
envMeta, err = env.GetEnvByName(envName)
} else {
envMeta, err = env.GetCurrentEnv()
}
if err != nil {
return ""
}
return envMeta.Namespace
}

View File

@@ -292,6 +292,35 @@ var _ = Describe("Test Application Controller", func() {
},
}
appWithMountToEnvs := &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app-with-mount-to-envs",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "myweb",
Type: "worker",
Properties: &runtime.RawExtension{Raw: []byte("{\"cmd\":[\"sleep\",\"1000\"],\"image\":\"busybox\"}")},
},
},
},
}
appWithMountToEnvs.Spec.Components[0].Traits = []common.ApplicationTrait{
{
Type: "storage",
Properties: &runtime.RawExtension{Raw: []byte("{\"secret\": [{\"name\": \"myweb-secret\",\"mountToEnv\": {\"envName\": \"firstEnv\",\"secretKey\": \"firstKey\"},\"mountToEnvs\": [{\"envName\": \"secondEnv\",\"secretKey\": \"secondKey\"}],\"data\": {\"firstKey\": \"dmFsdWUwMQo=\",\"secondKey\": \"dmFsdWUwMgo=\"}}]}")},
},
{
Type: "storage",
Properties: &runtime.RawExtension{Raw: []byte("{\"configMap\": [{\"name\": \"myweb-cm\",\"mountToEnvs\": [{\"envName\":\"thirdEnv\",\"configMapKey\":\"thirdKey\"},{\"envName\":\"fourthEnv\",\"configMapKey\":\"fourthKey\"}],\"data\": {\"thirdKey\": \"Value03\",\"fourthKey\": \"Value04\"}}]}")},
},
}
cd := &v1beta1.ComponentDefinition{}
cDDefJson, _ := yaml.YAMLToJSON([]byte(componentDefYaml))
k8sObjectsCDJson, _ := yaml.YAMLToJSON([]byte(k8sObjectsComponentDefinitionYaml))
@@ -2568,6 +2597,66 @@ var _ = Describe("Test Application Controller", func() {
Expect(k8sClient.Delete(ctx, secret)).Should(BeNil())
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
})
It("test application with multi-mountToEnv will create application", func() {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "app-with-mount-to-envs",
},
}
Expect(k8sClient.Create(ctx, ns)).Should(BeNil())
appWithMountToEnvs.SetNamespace(ns.Name)
app := appWithMountToEnvs.DeepCopy()
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
appKey := client.ObjectKey{
Name: app.Name,
Namespace: app.Namespace,
}
testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey})
By("Check App running successfully")
curApp := &v1beta1.Application{}
Expect(k8sClient.Get(ctx, appKey, curApp)).Should(BeNil())
Expect(curApp.Status.Phase).Should(Equal(common.ApplicationRunning))
appRevision := &v1beta1.ApplicationRevision{}
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: app.Namespace,
Name: curApp.Status.LatestRevision.Name,
}, appRevision)).Should(BeNil())
By("Check affiliated resource tracker is created")
expectRTName := fmt.Sprintf("%s-%s", appRevision.GetName(), appRevision.GetNamespace())
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: expectRTName}, &v1beta1.ResourceTracker{})
}, 10*time.Second, 500*time.Millisecond).Should(Succeed())
By("Check AppRevision Created with the expected workload spec")
appRev := &v1beta1.ApplicationRevision{}
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: app.Name + "-v1", Namespace: app.GetNamespace()}, appRev)
}, 10*time.Second, 500*time.Millisecond).Should(Succeed())
By("Check secret Created with the expected trait-storage spec")
secret := &corev1.Secret{}
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: ns.Name,
Name: app.Spec.Components[0].Name + "-secret",
}, secret)).Should(BeNil())
By("Check configMap Created with the expected trait-storage spec")
cm := &corev1.ConfigMap{}
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: ns.Name,
Name: app.Spec.Components[0].Name + "-cm",
}, cm)).Should(BeNil())
Expect(k8sClient.Delete(ctx, cm)).Should(BeNil())
Expect(k8sClient.Delete(ctx, secret)).Should(BeNil())
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
})
})
const (
@@ -3604,6 +3693,17 @@ spec:
}
},
] | []
configMapMountToEnvsList: *[
for v in parameter.configMap if v.mountToEnvs != _|_ for k in v.mountToEnvs {
{
name: k.envName
valueFrom: configMapKeyRef: {
name: v.name
key: k.configMapKey
}
}
},
] | []
secretVolumeMountsList: *[
for v in parameter.secret if v.mountPath != _|_ {
{
@@ -3623,6 +3723,17 @@ spec:
}
},
] | []
secretMountToEnvsList: *[
for v in parameter.secret if v.mountToEnvs != _|_ for k in v.mountToEnvs {
{
name: k.envName
valueFrom: secretKeyRef: {
name: v.name
key: k.secretKey
}
}
},
] | []
emptyDirVolumeMountsList: *[
for v in parameter.emptyDir {
{
@@ -3645,7 +3756,7 @@ spec:
containers: [{
// +patchKey=name
env: configMapEnvMountsList + secretEnvMountsList
env: configMapEnvMountsList + secretEnvMountsList + configMapMountToEnvsList + secretMountToEnvsList
// +patchKey=name
volumeDevices: volumeDevicesList
// +patchKey=name
@@ -3765,6 +3876,10 @@ spec:
envName: string
configMapKey: string
}
mountToEnvs?: [...{
envName: string
configMapKey: string
}]
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool
@@ -3784,6 +3899,10 @@ spec:
envName: string
secretKey: string
}
mountToEnvs?: [...{
envName: string
secretKey: string
}]
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool

View File

@@ -17,7 +17,7 @@ limitations under the License.
package controller
import (
"flag"
flag "github.com/spf13/pflag"
ctrlClient "github.com/oam-dev/kubevela/pkg/client"
"github.com/oam-dev/kubevela/pkg/component"

Some files were not shown because too many files have changed in this diff Show More