Compare commits

...

63 Commits

Author SHA1 Message Date
Jianbo Sun
d3fbd5fd45 Fix: addon API panic and override workflow step for addon installation (#3533)
* Fix: addon API panic

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: use policy to deploy addon

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: use create or update app instead of patch apply on addon update

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: unit test

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-29 02:37:57 +08:00
barnettZQG
b0202ebf14 Fix: change the default permission name (#3531)
* Fix: change default permission name

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

* Fix: the addon api bug

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

* Fix: set local cluster name

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-28 23:37:25 +08:00
Tianxin Dong
295164229d Fix: optimize err for auth in apiserver (#3526)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-28 22:35:56 +08:00
Jianbo Sun
ba09cf5c2e Fix: fix migrate data duplicate in list API and add cluster info in addon status (#3528)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-28 22:30:35 +08:00
Somefive
099e25a552 Fix: enhance ref object to support health check for deployment (#3529)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-28 22:13:51 +08:00
Min Kim
b2f5b69380 chore: bump cluster-register (#3514)
Signed-off-by: yue9944882 <291271447@qq.com>
2022-03-28 21:57:44 +08:00
Zheng Xi Zhou
5d61cee3f6 Fix: support multiple-tenant cloud resources (#3305)
* Fix: support multiple-tenant cloud resources

Split terraform-xxx to two parts, the original addon will only install
definitions, and the provider object will be created by `vela provider`

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

* continue

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

* continue 0302

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

* support `vela provider list`

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

* generate a configmap in an addon terraform-xxx

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

* fix listing providers

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

* support add a provider

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

* support delete a provider

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

* support legacy providers

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-28 21:55:33 +08:00
Somefive
2af198bda7 Docs: add feature docs for multi-cluster and application revision (#3530)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-28 21:47:57 +08:00
wyike
161d2646cb Feat: helm repo as addon registry to support addon's multi-version (#3523)
* versioned registry impl
add more test

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

* fix ci

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

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

fix ui

fix

fix

fix

modify addon registry
2022-03-28 21:25:38 +08:00
Avery
4f8e7506f9 Fix: fix bugs in vela def doc-gen from local file (#3490)
* fix bugs in using local filepath as markdown filename; fix bugs in parseLocalFile

Signed-off-by: Nicola115 <2225992901@qq.com>

* ignore control character mismatch between lc.TerraformConfiguration and raw text

Signed-off-by: Nicola115 <2225992901@qq.com>

* goimports

Signed-off-by: Nicola115 <2225992901@qq.com>

* store remote and local in MarkDownReference struct

Signed-off-by: Nicola115 <2225992901@qq.com>

* gofmt

Signed-off-by: Nicola115 <2225992901@qq.com>

* fix too many argument bugs in generate.go:45

Signed-off-by: Nicola115 <2225992901@qq.com>

* fix invalid comment

Signed-off-by: Nicola115 <2225992901@qq.com>
2022-03-28 20:57:24 +08:00
Somefive
160ef64855 Feat: support livediff for referred object (#3525)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-28 17:52:33 +08:00
Guangzhe Huang
905c49eb81 Fix: fix undefined field error in email workflowstep (#3522)
Signed-off-by: huanggze <gzhuang2014@gmail.com>
2022-03-28 16:14:13 +08:00
Somefive
c4a8fcf29c Feat: rework referred objects for version (#3446)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-28 16:09:12 +08:00
Somefive
e72690bade Fix: ignore kubebuilder generated file in code coverage (#3524)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-28 16:08:59 +08:00
barnettZQG
044c4bf73c Feat: add RBAC support (#3493)
* Feat: add the rbac data model

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

* Feat: add some api about the project

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

* Feat: add CRUD about the project and the project user

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

* Feat: add CRUD about the role and perm check filter function

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

* Feat: update swagger config

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

* Feat: add default roles and perm policies

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

* Feat: add perm check filter for all webservice

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

* Feat: change the method that find project name

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

* Feat: query applications and envs by user perm

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

* Feat: support get login user info

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

* Fix: change default permissions

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

* Feat: change PermPolicy to Permission

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

* Feat: add some unit test and fix the e2e test error

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

* Fix: change some comment word

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

* Fix: e2e api path error

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-28 16:03:11 +08:00
Somefive
795231ceb5 Feat: add revision command (#3506)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-28 14:47:47 +08:00
Min Kim
3f621e57b2 bugfix: helm install cluster-gateway be compatible w/ existing APIService w/o annoation (#3512)
Signed-off-by: yue9944882 <291271447@qq.com>
2022-03-28 11:52:47 +08:00
Somefive
9fc992ea3e Fix: concurrent write to controller revision (#3520)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-27 19:56:25 +08:00
Tianxin Dong
c2f5175fd1 Fix: fix parse token error validate (#3507)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-25 22:34:32 +08:00
Kunshuai Zhu
a7d3cd5d1a Chore: Nominate JooKS-me as a Reviewer member (#3508)
Signed-off-by: zhukunshuai <jookunshuai@gmail.com>
2022-03-25 18:04:11 +08:00
yangs
ccfab6ce7f Feat: add health check for componentdefinition task (#3509)
Signed-off-by: yangsoon <songyang.song@alibaba-inc.com>

Co-authored-by: yangsoon <songyang.song@alibaba-inc.com>
2022-03-25 16:21:57 +08:00
Tianxin Dong
03cd0d144a Feat: add login handle for apiserver (#3471)
* Feat: add login handle for apiserver

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

* fix go lint

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

* Fix: clean up code and add user in ctx

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

* Fix: fix swagger conflict

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

* Feat: add auth in apiserver e2e test

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

* Fix: nit fix for apiserver e2e test

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-24 17:59:09 +08:00
Somefive
741544c00c Feat: rework apprev (#3498)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-24 14:40:19 +08:00
Jianbo Sun
723a5d83ca Fix: vela status --endpoint format (#3502)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-24 09:47:15 +08:00
StevenLeiZhang
8af5afa5dc Fix: api-server fail to start, when multi-cluster is disabled (#3501)
Signed-off-by: StevenLeiZhang <zhangleiic@163.com>
2022-03-23 20:46:29 +08:00
Tianxin Dong
aaa1db8760 Fix: fix workflow cli must have workflow in spec (#3469)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-23 17:16:39 +08:00
Jianbo Sun
c5baa1cae9 Feat: support specify cluster in addon installation (#3497)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-23 14:33:15 +08:00
Wei (段少)
eda7e6c5a0 Chore: Nomiante Daniel as a Maintainer (#3491)
Signed-off-by: BinaryHB0916 <davidduan0916@gmail.com>
2022-03-23 09:49:38 +08:00
Jianbo Sun
e5fd150cd5 Fix: add client validation and severside dry run for vela dry-run (#3485)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-22 16:39:36 +08:00
barnettZQG
d041d8c35d Feat: support delete the readonly application (#3492)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-22 15:49:55 +08:00
wyike
345e4c8144 fix disable addon check several bugs (#3488)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-22 10:06:16 +08:00
Jianbo Sun
9a8ec5d797 Fix: delete app won't be synced in UI (#3487)
* Fix: delete app won't be synced in UI

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: cache should be ignored in app meta not exist

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-22 10:04:10 +08:00
Jianbo Sun
d9a676a688 Feat: add page index for env list API (#3482)
* Feat: add page index for env list API

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: add check for target create without namespace or name

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Fix: adress commnets

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-21 19:28:28 +08:00
Jianbo Sun
9a3ad7ef84 Feat: add readOnly for velaux application which is synced from CR (#3479)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-21 16:33:29 +08:00
朱晓兵
9671e3b232 Fix(context): add support for context.appLables and context.appAnnotations (#3463) (#3468)
* Fix(context): add support for context.appLables and context.appAnnotations (#3463)

Signed-off-by: zxbyoyoyo <596908030@qq.com>
Signed-off-by: 朱晓兵 <596908030@qq.com>

* Fix: unit test

Signed-off-by: 朱晓兵 <596908030@qq.com>

* Fix: recover deleted field

Signed-off-by: 朱晓兵 <596908030@qq.com>
2022-03-21 12:02:30 +08:00
Jianbo Sun
0c97f8311c Chore: Nominate fourierr as a Reviewer member (#3475)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-21 12:01:39 +08:00
wyike
a6460d67b6 Feat: check if an addon is been used while disabling. (#3457)
* first commit

add more tests

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

fix ci

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

fix ci

fix test

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

* fetch only definitions from registry for lagacy addon app

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

fix ci

* fix error test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-21 10:38:56 +08:00
Tianxin Dong
3ea2ac6d0f Feat: add user management apis (#3458)
* Feat: add user management apis

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

* Feat: add e2e test and some nit fix

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

* Feat: add password validate

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

* Feat: add email modification in update user

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

* Fix: fix user detail to user base

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

* Fix: fix ut

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

* Fix: fix test

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

* Fix: fix rebase

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

* Fix: add password check in create user

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

* Fix: fix bcode confilt

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-19 15:51:32 +08:00
wyike
13c420dada Fix: optimize helm repository logic put cache lower layer and return bcode when error (#3466)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-18 18:36:19 +08:00
Jianbo Sun
6354912bba Feat: sync application from CR to data store (#3428)
* Feat: sync application from CR to data store

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Feature: address comments

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* Feat: add migrate database feature to avoid max 63 charactor in kubeapi storage

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

* update the sync data

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-18 09:55:15 +08:00
barnettZQG
5209be6da9 Feat: add the conditions for ui schema spec (#3461)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-18 09:52:58 +08:00
maxiangbo
8f9908e723 Feat: support the mountPath of trait-storage-secret is optional (#3462)
* Feat: support the mountPath parameter of trait-storage-secret is optional Signed-off-by: maxiangbo maxiangboo@cmbchina.com

Signed-off-by: fourierr <maxiangboo@qq.com>

* Feat: support the mountPath parameter of trait-storage is optional Signed-off-by: maxiangbo maxiangboo@cmbchina.com

Signed-off-by: fourierr <maxiangboo@qq.com>
2022-03-17 20:04:57 +08:00
Evan Li
296c82344b Fix(workflow): correct terraform component health check (#3456)
Closes: #3355
Signed-off-by: Evan Li <evan.li97@outlook.com>
2022-03-16 20:30:50 +08:00
Somefive
2d19454a35 Feat: enhance labels & annotations trait (#3454)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-16 17:50:12 +08:00
maxiangbo
f67d2db0d1 Fix: trait env and storage conflict Signed-off-by: maxiangbo maxiangboo@cmbchina.com (#3452)
Signed-off-by: fourierr <maxiangboo@qq.com>
2022-03-16 15:14:18 +08:00
Avery
3634ad6f18 Fix: enable provider elastic (#3453)
* enable provider ec

Signed-off-by: Nicola115 <2225992901@qq.com>

* rename provider ec as elastic

Signed-off-by: Nicola115 <2225992901@qq.com>
2022-03-16 15:07:25 +08:00
Tianxin Dong
6c5a40d768 Feat: add dex login in apiserver (#3417)
* Feat: add dex login in apiserver

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

* resolve comments

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

* Fix: get dex config from secret

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

* Fix: delete cache for dex config

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

* fix ci

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-16 14:37:41 +08:00
wyike
c91a7ac273 Feat(apiserver): helm apiserver webservice (list chart, list version, extract helm values) (#3398)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-16 11:42:08 +08:00
barnettZQG
b437cf4310 Feat: support query the policy definitions (#3445)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-16 10:56:53 +08:00
Sunghoon Kang
040d5e1776 Chore: fix issue-commands workflow (#3451)
It seems that the `issue-commands` workflow keeps failing.

This commit fixes the `issue-commands` workflow by bumping up the
action version and replacing `npm install` with `npm ci` for
reproducibility.

Refs: https://github.com/oam-dev/kubevela-github-actions/pull/11

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>
2022-03-16 10:12:58 +08:00
barnettZQG
7bec3506f5 Feat: add auto clear data function for the memory cache (#3439)
* Feat: add auto clear data function for the memory cache

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

* Feat: add memory cache store

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

* Feat: add some test case for the utils package

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-15 19:14:20 +08:00
Jianbo Sun
efc1597317 Nominate devholic as as a Review member (#3444)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-15 16:45:26 +08:00
Kunshuai Zhu
38665e319d Feat: poll multi-cluster metrics and export to prometheus (#3429)
* Feat: poll multi-cluster metrics and export to prometheus

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

* pass context to polling loop

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

* move metrics definition to montitor/metrics/multicluster.go

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

* remove pod usage metric and make reviewable

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

* revert the change of GetClusterMetricsFromMetricsAPI

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

* revert the change of GetClusterMetricsFromMetricsAPI

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

* Separate the polling logic into a function

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

* add start menber function

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

* make refreshPeriod a menber var

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

* fix typo

Signed-off-by: zhukunshuai <jookunshuai@gmail.com>
2022-03-15 13:03:10 +08:00
barnettZQG
1e0f329304 Feat: add component definiton in the detail component response body (#3437)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-15 11:57:16 +08:00
Sunghoon Kang
1300a980f0 Feat: reconcile app with scoped permissions (#3434)
* Refactor: refactor multi cluster round trippers

Before adding more RoundTrippers, it would be better to expose common
logic in the utility package.

This commit exports `tryCancelRequest` at `utils` package, and make
`secretMultiClusterRoundTripper` implement `RoundTripperWrapper`
interface to allow chaining multiple round trippers.

Refs #3432

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>

* Feat: reconcile app with scoped permissions

Currently, all Application resources are reconciled by the Roles bound
to the controller service account. This behavior gives us the power to
manage resources across multiple namespaces. However, this behavior can
be problematic in the soft-multitenancy environment.

This commit adds `serviceAccountName` to ApplicationSepc to reconcile
Application with the given service account for reconciling Application
with scoped permissions.

Refs #3432

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>

* Refactor: extract context setter as method

https://github.com/oam-dev/kubevela/pull/3434#discussion_r825561603

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>

* Feat: use annotation instead of spec

https://github.com/oam-dev/kubevela/issues/3432#issuecomment-1066460269

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>

* Refactor: unify service account setter caller

https://github.com/oam-dev/kubevela/pull/3434#discussion_r825853612

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>

* Refactor: rename GetServiceAccountName

https://github.com/oam-dev/kubevela/pull/3434#discussion_r826514565

Signed-off-by: Sunghoon Kang <hoon@linecorp.com>
2022-03-15 11:55:50 +08:00
Zheng Xi Zhou
b6b81c336e Fix: add generate JDBC connection workflowDefintion (#3360)
* Fix: add generate JDBC connection workflowDefintion

Added a WorkflowDefinition

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

* format .cue definition

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-03-15 10:53:22 +08:00
Somefive
e2275efe56 Feat: local virtual cluster + namespace in topology (#3436)
* Feat: virtual cluster local + namespace

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

* Chore: refactor some names

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-15 10:06:36 +08:00
Somefive
c054ee32b1 Fix: use clusterLabelSelector as default (#3438)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-14 19:30:52 +08:00
Somefive
19424cfaa4 Fix: add state keep for healthy suspend (#3426)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-14 14:38:43 +08:00
Rimond
a5fb09814e Fix(docs): rollout example error (#3427)
* modify docs errors

Signed-off-by: liming.jlm <liming.jlm@alibaba-inc.com>

* modify the incorrect file name

Signed-off-by: liming.jlm <liming.jlm@alibaba-inc.com>

Co-authored-by: liming.jlm <liming.jlm@alibaba-inc.com>
2022-03-14 13:13:05 +08:00
Hongchao Deng
c1b116b360 Chore: update CODEOWNERS to promote barnettZQG and leejanee (#3435)
Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>
2022-03-14 10:22:57 +08:00
Shukun Zhang
3af893950e add vela status --endpoint into the tips for vela up (#3433)
Co-authored-by: zhangshukun <zhangshukun.7@bytedance.com>
2022-03-14 09:39:11 +08:00
barnettZQG
20583e089a Feat: support query applied resources by velaQL (#3423)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-03-11 21:22:58 +08:00
342 changed files with 21333 additions and 3149 deletions

4
.github/CODEOWNERS vendored
View File

@@ -1,7 +1,7 @@
# This file is a github code protect rule follow the codeowners https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners#example-of-a-codeowners-file
* @hongchaodeng @wonderflow @leejanee
design/ @hongchaodeng @resouer @wonderflow
* @barnettZQG @wonderflow @leejanee
design/ @barnettZQG @leejanee @wonderflow
# Owner of CUE
pkg/cue @leejanee @FogDong

View File

@@ -92,10 +92,10 @@ jobs:
kubectl wait --for=condition=Ready pod -l app=source-controller -n flux-system --timeout=600s
kubectl wait --for=condition=Ready pod -l app=helm-controller -n flux-system --timeout=600s
- name: Run apiserver unit test
- name: Run api server unit test
run: make unit-test-apiserver
- name: Run apiserver e2e test
- name: Run api server e2e test
run: |
export ALIYUN_ACCESS_KEY_ID=${{ secrets.ALIYUN_ACCESS_KEY_ID }}
export ALIYUN_ACCESS_KEY_SECRET=${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}

View File

@@ -14,9 +14,9 @@ jobs:
with:
repository: "oam-dev/kubevela-github-actions"
path: ./actions
ref: v0.4.1
ref: v0.4.2
- name: Install Actions
run: npm install --production --prefix ./actions
run: npm ci --production --prefix ./actions
- name: Run Commands
uses: ./actions/commands
with:
@@ -66,4 +66,4 @@ jobs:
uses: zeebe-io/backport-action@v0.0.6
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_workspace: ${{ github.workspace }}
github_workspace: ${{ github.workspace }}

View File

@@ -19,7 +19,7 @@ unit-test-core:
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... ./apis/... | grep -v apiserver)
go test $(shell go list ./references/... | grep -v apiserver)
unit-test-apiserver:
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... | grep -E 'apiserver|velaql')
go test -gcflags=all=-l -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... | grep -E 'apiserver|velaql')
# Build vela cli binary
build: fmt vet lint staticcheck vela-cli kubectl-vela
@@ -132,5 +132,4 @@ def-install:
helm-doc-gen: helmdoc
readme-generator -v charts/vela-core/values.yaml -r charts/vela-core/README.md
cat charts/vela-core/README.md
readme-generator -v charts/vela-minimal/values.yaml -r charts/vela-minimal/README.md

View File

@@ -7,6 +7,9 @@ Reviewers:
- reetasingh
- wangwang
- evanli18
- devholic
- fourierr
- JooKS-me
Approvers:
- Somefive (Multi-Cluster)
@@ -26,6 +29,7 @@ Maintainers:
- leejanee
- zzxwill
- BinaryHB0916
- dhiguero
Emeritus Members:
- ryanzhang-oss

View File

@@ -322,6 +322,23 @@ type PolicyStatus struct {
Status *runtime.RawExtension `json:"status,omitempty"`
}
// WorkflowStep defines how to execute a workflow step.
type WorkflowStep struct {
// Name is the unique name of the workflow step.
Name string `json:"name"`
Type string `json:"type"`
// +kubebuilder:pruning:PreserveUnknownFields
Properties *runtime.RawExtension `json:"properties,omitempty"`
DependsOn []string `json:"dependsOn,omitempty"`
Inputs StepInputs `json:"inputs,omitempty"`
Outputs StepOutputs `json:"outputs,omitempty"`
}
// WorkflowStatus record the status of workflow
type WorkflowStatus struct {
AppRevision string `json:"appRevision,omitempty"`
@@ -605,3 +622,17 @@ func ParseApplicationConditionType(s string) (ApplicationConditionType, error) {
}
return -1, errors.New("unknown condition type")
}
// ReferredObject the referred Kubernetes object
type ReferredObject struct {
// +kubebuilder:validation:EmbeddedResource
// +kubebuilder:pruning:PreserveUnknownFields
runtime.RawExtension `json:",inline"`
}
// ReferredObjectList a list of referred Kubernetes objects
type ReferredObjectList struct {
// Objects a list of Kubernetes objects.
// +optional
Objects []ReferredObject `json:"objects,omitempty"`
}

View File

@@ -469,6 +469,44 @@ func (in *RawExtensionPointer) DeepCopy() *RawExtensionPointer {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ReferredObject) DeepCopyInto(out *ReferredObject) {
*out = *in
in.RawExtension.DeepCopyInto(&out.RawExtension)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReferredObject.
func (in *ReferredObject) DeepCopy() *ReferredObject {
if in == nil {
return nil
}
out := new(ReferredObject)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ReferredObjectList) DeepCopyInto(out *ReferredObjectList) {
*out = *in
if in.Objects != nil {
in, out := &in.Objects, &out.Objects
*out = make([]ReferredObject, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReferredObjectList.
func (in *ReferredObjectList) DeepCopy() *ReferredObjectList {
if in == nil {
return nil
}
out := new(ReferredObjectList)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Revision) DeepCopyInto(out *Revision) {
*out = *in
@@ -636,6 +674,41 @@ func (in *WorkflowStatus) DeepCopy() *WorkflowStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkflowStep) DeepCopyInto(out *WorkflowStep) {
*out = *in
if in.Properties != nil {
in, out := &in.Properties, &out.Properties
*out = new(runtime.RawExtension)
(*in).DeepCopyInto(*out)
}
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Inputs != nil {
in, out := &in.Inputs, &out.Inputs
*out = make(StepInputs, len(*in))
copy(*out, *in)
}
if in.Outputs != nil {
in, out := &in.Outputs, &out.Outputs
*out = make(StepOutputs, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowStep.
func (in *WorkflowStep) DeepCopy() *WorkflowStep {
if in == nil {
return nil
}
out := new(WorkflowStep)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkflowStepStatus) DeepCopyInto(out *WorkflowStepStatus) {
*out = *in

View File

@@ -0,0 +1,74 @@
/*
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 v1alpha1
const (
// RefObjectsComponentType refers to the type of ref-objects
RefObjectsComponentType = "ref-objects"
)
// RefObjectsComponentSpec defines the spec of ref-objects component
type RefObjectsComponentSpec struct {
// Objects the referrers to the Kubernetes objects
Objects []ObjectReferrer `json:"objects,omitempty"`
}
// ObjectReferrer selects Kubernetes objects
type ObjectReferrer struct {
// ObjectTypeIdentifier identifies the type of referred objects
ObjectTypeIdentifier `json:",inline"`
// ObjectSelector select object by name or labelSelector
ObjectSelector `json:",inline"`
}
// ObjectTypeIdentifier identifies the scheme of Kubernetes object
type ObjectTypeIdentifier struct {
// Resource is the resource name of the Kubernetes object.
Resource string `json:"resource"`
// Group is the API Group of the Kubernetes object.
Group string `json:"group"`
// LegacyObjectTypeIdentifier is the legacy identifier
// Deprecated: use resource/group instead
LegacyObjectTypeIdentifier `json:",inline"`
}
// LegacyObjectTypeIdentifier legacy object type identifier
type LegacyObjectTypeIdentifier struct {
// APIVersion is the APIVersion of the Kubernetes object.
APIVersion string `json:"apiVersion"`
// APIVersion is the Kind of the Kubernetes object.
Kind string `json:"kind"`
}
// ObjectSelector selector for Kubernetes object
type ObjectSelector struct {
// Name is the name of the Kubernetes object.
// If empty, it will inherit the application component's name.
Name string `json:"name,omitempty"`
// Namespace is the namespace for selecting Kubernetes objects.
// If empty, it will inherit the application's namespace.
Namespace string `json:"namespace,omitempty"`
// Cluster is the cluster for selecting Kubernetes objects.
// If empty, it will use the local cluster
Cluster string `json:"cluster,omitempty"`
// LabelSelector selects Kubernetes objects by labels
// Exclusive to "name"
LabelSelector map[string]string `json:"labelSelector,omitempty"`
// DeprecatedLabelSelector a deprecated alias to LabelSelector
// Deprecated: use labelSelector instead.
DeprecatedLabelSelector map[string]string `json:"selector,omitempty"`
}

View File

@@ -20,7 +20,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
)
// +kubebuilder:object:root=true
@@ -61,7 +61,7 @@ type Workflow struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Steps []v1beta1.WorkflowStep `json:"steps,omitempty"`
Steps []common.WorkflowStep `json:"steps,omitempty"`
}
// +kubebuilder:object:root=true

View File

@@ -25,8 +25,25 @@ const (
// TopologyPolicySpec defines the spec of topology policy
type TopologyPolicySpec struct {
Clusters []string `json:"clusters,omitempty"`
ClusterSelector map[string]string `json:"clusterSelector,omitempty"`
// Placement embeds the selectors for choosing cluster
Placement `json:",inline"`
// Namespace is the target namespace to deploy in the selected clusters.
// +optional
Namespace string `json:"namespace,omitempty"`
}
// Placement describes which clusters to be selected in this topology
type Placement struct {
// Clusters is the names of the clusters to select.
Clusters []string `json:"clusters,omitempty"`
// ClusterLabelSelector is the label selector for clusters.
// Exclusive to "clusters"
ClusterLabelSelector map[string]string `json:"clusterLabelSelector,omitempty"`
// DeprecatedClusterSelector is a depreciated alias for ClusterLabelSelector.
// Deprecated: Use clusterLabelSelector instead.
DeprecatedClusterSelector map[string]string `json:"clusterSelector,omitempty"`
}
// OverridePolicySpec defines the spec of override policy

View File

@@ -38,6 +38,18 @@ var (
AddToScheme = SchemeBuilder.AddToScheme
)
// Policy meta
var (
PolicyKind = "Policy"
PolicyGroupVersionKind = SchemeGroupVersion.WithKind(PolicyKind)
)
// Workflow meta
var (
WorkflowKind = "Workflow"
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(PolicyKind)
)
func init() {
SchemeBuilder.Register(&Policy{}, &PolicyList{})
SchemeBuilder.Register(&Workflow{}, &WorkflowList{})

View File

@@ -25,7 +25,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -326,6 +325,21 @@ func (in *GarbageCollectPolicySpec) DeepCopy() *GarbageCollectPolicySpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LegacyObjectTypeIdentifier) DeepCopyInto(out *LegacyObjectTypeIdentifier) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LegacyObjectTypeIdentifier.
func (in *LegacyObjectTypeIdentifier) DeepCopy() *LegacyObjectTypeIdentifier {
if in == nil {
return nil
}
out := new(LegacyObjectTypeIdentifier)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) {
*out = *in
@@ -348,6 +362,68 @@ func (in *NamespaceSelector) DeepCopy() *NamespaceSelector {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectReferrer) DeepCopyInto(out *ObjectReferrer) {
*out = *in
out.ObjectTypeIdentifier = in.ObjectTypeIdentifier
in.ObjectSelector.DeepCopyInto(&out.ObjectSelector)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferrer.
func (in *ObjectReferrer) DeepCopy() *ObjectReferrer {
if in == nil {
return nil
}
out := new(ObjectReferrer)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectSelector) DeepCopyInto(out *ObjectSelector) {
*out = *in
if in.LabelSelector != nil {
in, out := &in.LabelSelector, &out.LabelSelector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.DeprecatedLabelSelector != nil {
in, out := &in.DeprecatedLabelSelector, &out.DeprecatedLabelSelector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectSelector.
func (in *ObjectSelector) DeepCopy() *ObjectSelector {
if in == nil {
return nil
}
out := new(ObjectSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectTypeIdentifier) DeepCopyInto(out *ObjectTypeIdentifier) {
*out = *in
out.LegacyObjectTypeIdentifier = in.LegacyObjectTypeIdentifier
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectTypeIdentifier.
func (in *ObjectTypeIdentifier) DeepCopy() *ObjectTypeIdentifier {
if in == nil {
return nil
}
out := new(ObjectTypeIdentifier)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OverridePolicySpec) DeepCopyInto(out *OverridePolicySpec) {
*out = *in
@@ -375,6 +451,40 @@ func (in *OverridePolicySpec) DeepCopy() *OverridePolicySpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Placement) DeepCopyInto(out *Placement) {
*out = *in
if in.Clusters != nil {
in, out := &in.Clusters, &out.Clusters
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ClusterLabelSelector != nil {
in, out := &in.ClusterLabelSelector, &out.ClusterLabelSelector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.DeprecatedClusterSelector != nil {
in, out := &in.DeprecatedClusterSelector, &out.DeprecatedClusterSelector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Placement.
func (in *Placement) DeepCopy() *Placement {
if in == nil {
return nil
}
out := new(Placement)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PlacementDecision) DeepCopyInto(out *PlacementDecision) {
*out = *in
@@ -453,22 +563,33 @@ func (in *PolicyList) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TopologyPolicySpec) DeepCopyInto(out *TopologyPolicySpec) {
func (in *RefObjectsComponentSpec) DeepCopyInto(out *RefObjectsComponentSpec) {
*out = *in
if in.Clusters != nil {
in, out := &in.Clusters, &out.Clusters
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ClusterSelector != nil {
in, out := &in.ClusterSelector, &out.ClusterSelector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
if in.Objects != nil {
in, out := &in.Objects, &out.Objects
*out = make([]ObjectReferrer, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RefObjectsComponentSpec.
func (in *RefObjectsComponentSpec) DeepCopy() *RefObjectsComponentSpec {
if in == nil {
return nil
}
out := new(RefObjectsComponentSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TopologyPolicySpec) DeepCopyInto(out *TopologyPolicySpec) {
*out = *in
in.Placement.DeepCopyInto(&out.Placement)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TopologyPolicySpec.
func (in *TopologyPolicySpec) DeepCopy() *TopologyPolicySpec {
if in == nil {
@@ -486,7 +607,7 @@ func (in *Workflow) DeepCopyInto(out *Workflow) {
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.Steps != nil {
in, out := &in.Steps, &out.Steps
*out = make([]v1beta1.WorkflowStep, len(*in))
*out = make([]common.WorkflowStep, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}

View File

@@ -50,21 +50,7 @@ type AppPolicy struct {
}
// WorkflowStep defines how to execute a workflow step.
type WorkflowStep struct {
// Name is the unique name of the workflow step.
Name string `json:"name"`
Type string `json:"type"`
// +kubebuilder:pruning:PreserveUnknownFields
Properties *runtime.RawExtension `json:"properties,omitempty"`
DependsOn []string `json:"dependsOn,omitempty"`
Inputs common.StepInputs `json:"inputs,omitempty"`
Outputs common.StepOutputs `json:"outputs,omitempty"`
}
type WorkflowStep common.WorkflowStep
// Workflow defines workflow steps and other attributes
type Workflow struct {

View File

@@ -17,11 +17,10 @@
package v1beta1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
)
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
@@ -52,19 +51,23 @@ type ApplicationRevisionSpec struct {
// ScopeGVK records the apiVersion to GVK mapping
ScopeGVK map[string]metav1.GroupVersionKind `json:"scopeGVK,omitempty"`
// Components records the rendered components from Application, it will contains the whole K8s CR of workload in it.
// +deprecated
Components []common.RawComponent `json:"components,omitempty"`
// Policies records the external policies
Policies map[string]v1alpha1.Policy `json:"policies,omitempty"`
// ApplicationConfiguration records the rendered applicationConfiguration from Application,
// it will contains the whole K8s CR of trait and the reference component in it.
// +kubebuilder:validation:EmbeddedResource
// Workflow records the external workflow
Workflow *v1alpha1.Workflow `json:"workflow,omitempty"`
// ReferredObjects records the referred objects used in the ref-object typed components
// +kubebuilder:pruning:PreserveUnknownFields
// +deprecated
ApplicationConfiguration runtime.RawExtension `json:"applicationConfiguration,omitempty"`
ReferredObjects []common.ReferredObject `json:"referredObjects,omitempty"`
}
// ResourcesConfigMap references the ConfigMap that's generated to contain all final rendered resources.
ResourcesConfigMap corev1.LocalObjectReference `json:"resourcesConfigMap,omitempty"`
// ApplicationRevisionStatus is the status of ApplicationRevision
type ApplicationRevisionStatus struct {
// Succeeded records if the workflow finished running with success
Succeeded bool `json:"succeeded"`
// Workflow the running status of the workflow
Workflow *common.WorkflowStatus `json:"workflow,omitempty"`
}
// +kubebuilder:object:root=true
@@ -72,14 +75,18 @@ type ApplicationRevisionSpec struct {
// ApplicationRevision is the Schema for the ApplicationRevision API
// +kubebuilder:storageversion
// +kubebuilder:resource:categories={oam},shortName=apprev
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="AGE",type=date,JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="PUBLISH_VERSION",type=string,JSONPath=`.metadata.annotations['app\.oam\.dev\/publishVersion']`
// +kubebuilder:printcolumn:name="SUCCEEDED",type=string,JSONPath=`.status.succeeded`
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ApplicationRevision struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ApplicationRevisionSpec `json:"spec,omitempty"`
Spec ApplicationRevisionSpec `json:"spec,omitempty"`
Status ApplicationRevisionStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true

View File

@@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -113,6 +114,7 @@ func (in *ApplicationRevision) DeepCopyInto(out *ApplicationRevision) {
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationRevision.
@@ -218,15 +220,25 @@ func (in *ApplicationRevisionSpec) DeepCopyInto(out *ApplicationRevisionSpec) {
(*out)[key] = val
}
}
if in.Components != nil {
in, out := &in.Components, &out.Components
*out = make([]common.RawComponent, len(*in))
if in.Policies != nil {
in, out := &in.Policies, &out.Policies
*out = make(map[string]v1alpha1.Policy, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
if in.Workflow != nil {
in, out := &in.Workflow, &out.Workflow
*out = new(v1alpha1.Workflow)
(*in).DeepCopyInto(*out)
}
if in.ReferredObjects != nil {
in, out := &in.ReferredObjects, &out.ReferredObjects
*out = make([]common.ReferredObject, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.ApplicationConfiguration.DeepCopyInto(&out.ApplicationConfiguration)
out.ResourcesConfigMap = in.ResourcesConfigMap
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationRevisionSpec.
@@ -239,6 +251,26 @@ func (in *ApplicationRevisionSpec) DeepCopy() *ApplicationRevisionSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationRevisionStatus) DeepCopyInto(out *ApplicationRevisionStatus) {
*out = *in
if in.Workflow != nil {
in, out := &in.Workflow, &out.Workflow
*out = new(common.WorkflowStatus)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationRevisionStatus.
func (in *ApplicationRevisionStatus) DeepCopy() *ApplicationRevisionStatus {
if in == nil {
return nil
}
out := new(ApplicationRevisionStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
*out = *in

View File

@@ -0,0 +1,31 @@
/*
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 types
import "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
const (
// CredentialTypeInternal identifies the virtual cluster from internal kubevela system
CredentialTypeInternal v1alpha1.CredentialType = "Internal"
// CredentialTypeOCMManagedCluster identifies the virtual cluster from ocm
CredentialTypeOCMManagedCluster v1alpha1.CredentialType = "ManagedCluster"
// ClusterBlankEndpoint identifies the endpoint of a cluster as blank (not available)
ClusterBlankEndpoint = "-"
// ClustersArg indicates the argument for specific clusters to install addon
ClustersArg = "clusters"
)

View File

@@ -41,6 +41,8 @@ var DefaultKubeVelaNS = "vela-system"
const (
// AnnoDefinitionDescription is the annotation which describe what is the capability used for in a WorkloadDefinition/TraitDefinition Object
AnnoDefinitionDescription = "definition.oam.dev/description"
// AnnoDefinitionIcon is the annotation which describe the icon url
AnnoDefinitionIcon = "definition.oam.dev/icon"
// AnnoDefinitionAppliedWorkloads is the annotation which describe what is the workloads used for in a TraitDefinition Object
AnnoDefinitionAppliedWorkloads = "definition.oam.dev/appliedWorkloads"
// LabelDefinition is the label for definition

View File

@@ -2025,6 +2025,12 @@ spec:
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
- jsonPath: .metadata.annotations['app\.oam\.dev\/publishVersion']
name: PUBLISH_VERSION
type: string
- jsonPath: .status.succeeded
name: SUCCEEDED
type: string
name: v1beta1
schema:
openAPIV3Schema:
@@ -2747,13 +2753,6 @@ spec:
type: object
type: object
type: object
applicationConfiguration:
description: ApplicationConfiguration records the rendered applicationConfiguration
from Application, it will contains the whole K8s CR of trait and
the reference component in it.
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
componentDefinitions:
additionalProperties:
description: ComponentDefinition is the Schema for the componentdefinitions
@@ -3087,20 +3086,51 @@ spec:
description: ComponentDefinitions records the snapshot of the componentDefinitions
related with the created/modified Application
type: object
components:
description: Components records the rendered components from Application,
it will contains the whole K8s CR of workload in it.
items:
description: RawComponent record raw component
policies:
additionalProperties:
description: Policy is the Schema for the policy API
properties:
raw:
apiVersion:
description: 'APIVersion defines the versioned schema of this
representation of an object. Servers should convert recognized
schemas to the latest internal value, and may reject unrecognized
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource
this object represents. Servers may infer this from the endpoint
the client submits requests to. Cannot be updated. In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
properties:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type:
type: string
required:
- raw
- type
type: object
type: array
description: Policies records the external policies
type: object
policyDefinitions:
additionalProperties:
description: PolicyDefinition is the Schema for the policydefinitions
@@ -3377,15 +3407,16 @@ spec:
description: PolicyDefinitions records the snapshot of the PolicyDefinitions
related with the created/modified Application
type: object
resourcesConfigMap:
description: ResourcesConfigMap references the ConfigMap that's generated
to contain all final rendered resources.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
referredObjects:
description: ReferredObjects records the referred objects used in
the ref-object typed components
items:
description: ReferredObject the referred Kubernetes object
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type: array
x-kubernetes-preserve-unknown-fields: true
scopeDefinitions:
additionalProperties:
description: A ScopeDefinition registers a kind of Kubernetes custom
@@ -3819,6 +3850,89 @@ spec:
description: TraitDefinitions records the snapshot of the traitDefinitions
related with the created/modified Application
type: object
workflow:
description: Workflow records the external workflow
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this
representation of an object. Servers should convert recognized
schemas to the latest internal value, and may reject unrecognized
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource
this object represents. Servers may infer this from the endpoint
the client submits requests to. Cannot be updated. In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow
step.
properties:
dependsOn:
items:
type: string
type: array
inputs:
description: StepInputs defines variable input of WorkflowStep
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: StepOutputs defines output variable of WorkflowStep
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
type: object
x-kubernetes-preserve-unknown-fields: true
type:
type: string
required:
- name
- type
type: object
type: array
type: object
workflowStepDefinitions:
additionalProperties:
description: WorkflowStepDefinition is the Schema for the workflowstepdefinitions
@@ -4408,10 +4522,182 @@ spec:
required:
- application
type: object
status:
description: ApplicationRevisionStatus is the status of ApplicationRevision
properties:
succeeded:
description: Succeeded records if the workflow finished running with
success
type: boolean
workflow:
description: Workflow the running status of the workflow
properties:
appRevision:
type: string
contextBackend:
description: 'ObjectReference contains enough information to let
you inspect or modify the referred object. --- New uses of this
type are discouraged because of difficulty describing its usage
when embedded in APIs. 1. Ignored fields. It includes many
fields which are not generally honored. For instance, ResourceVersion
and FieldPath are both very rarely valid in actual usage. 2.
Invalid usage help. It is impossible to add specific help for
individual usage. In most embedded usages, there are particular restrictions
like, "must refer only to types A and B" or "UID not honored"
or "name must be restricted". Those cannot be well described
when embedded. 3. Inconsistent validation. Because the usages
are different, the validation rules are different by usage,
which makes it hard for users to predict what will happen. 4.
The fields are both imprecise and overly precise. Kind is not
a precise mapping to a URL. This can produce ambiguity during
interpretation and require a REST mapping. In most cases, the
dependency is on the group,resource tuple and the version
of the actual struct is irrelevant. 5. We cannot easily change
it. Because this type is embedded in many locations, updates
to this type will affect numerous schemas. Don''t make
new APIs embed an underspecified API type they do not control.
Instead of using this type, create a locally provided and used
type that is well-focused on your reference. For example, ServiceReferences
for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
.'
properties:
apiVersion:
description: API version of the referent.
type: string
fieldPath:
description: 'If referring to a piece of an object instead
of an entire object, this string should contain a valid
JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within
a pod, this would take on a value like: "spec.containers{name}"
(where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]"
(container with index 2 in this pod). This syntax is chosen
only to have some well-defined way of referencing a part
of an object. TODO: this design is not final and this field
is subject to change in the future.'
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
resourceVersion:
description: 'Specific resourceVersion to which this reference
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
type: string
uid:
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
type: string
type: object
finished:
type: boolean
message:
type: string
mode:
description: WorkflowMode describes the mode of workflow
type: string
startTime:
format: date-time
type: string
steps:
items:
description: WorkflowStepStatus record the status of a workflow
step
properties:
firstExecuteTime:
description: FirstExecuteTime is the first time this step
execution.
format: date-time
type: string
id:
type: string
lastExecuteTime:
description: LastExecuteTime is the last time this step
execution.
format: date-time
type: string
message:
description: A human readable message indicating details
about why the workflowStep is in this state.
type: string
name:
type: string
phase:
description: WorkflowStepPhase describes the phase of a
workflow step.
type: string
reason:
description: A brief CamelCase message indicating details
about why the workflowStep is in this state.
type: string
subSteps:
description: SubStepsStatus record the status of workflow
steps.
properties:
mode:
description: WorkflowMode describes the mode of workflow
type: string
stepIndex:
type: integer
steps:
items:
description: WorkflowSubStepStatus record the status
of a workflow step
properties:
id:
type: string
message:
description: A human readable message indicating
details about why the workflowStep is in this
state.
type: string
name:
type: string
phase:
description: WorkflowStepPhase describes the phase
of a workflow step.
type: string
reason:
description: A brief CamelCase message indicating
details about why the workflowStep is in this
state.
type: string
type:
type: string
required:
- id
type: object
type: array
type: object
type:
type: string
required:
- id
type: object
type: array
suspend:
type: boolean
terminated:
type: boolean
required:
- finished
- mode
- suspend
- terminated
type: object
required:
- succeeded
type: object
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -7,10 +7,8 @@ data:
registries: '{
"KubeVela":{
"name": "KubeVela",
"oss": {
"end_point": "https://addons.kubevela.net",
"bucket": "",
"path": ""
"helm": {
"url": "https://addons.kubevela.net",
}
}
}'

View File

@@ -89,9 +89,19 @@ spec:
{{ end }}
---
{{ if .Values.multicluster.enabled }}
# 1. Check whether APIService ""v1alpha1.cluster.core.oam.dev" is already present in the cluster
# 2.a If the APIService doesn't exist, create it.
# 2.b If the APIService exists without helm-chart related annotation, skip creating it to the
# cluster because the APIService can be managed by an external controller.
# 2.c If the APIService exists with valid helm-chart annotations, which means that the APIService
# is previously managed by helm commands, hence update the APIService consistently.
{{ $apiSvc := (lookup "apiregistration.k8s.io/v1" "APIService" "" "v1alpha1.cluster.core.oam.dev") }}
{{ $shouldAdopt := (not $apiSvc) }}
{{ if not $shouldAdopt }}{{ $shouldAdopt = (index ($apiSvc).metadata.annotations "meta.helm.sh/release-name") }}{{ end }}
{{ if not $shouldAdopt }}
{{ if $apiSvc.metadata.annotations }}
{{ $shouldAdopt = (index ($apiSvc).metadata.annotations "meta.helm.sh/release-name") }}
{{ end }}
{{ end }}
{{ if $shouldAdopt }}
apiVersion: apiregistration.k8s.io/v1
kind: APIService

View File

@@ -16,17 +16,20 @@ spec:
schematic:
cue:
template: |
// +patchStrategy=jsonMergePatch
patch: {
metadata: annotations: {
for k, v in parameter {
"\(k)": v
}
}
spec: template: metadata: annotations: {
for k, v in parameter {
"\(k)": v
if context.output.spec != _|_ && context.output.spec.template != _|_ {
spec: template: metadata: annotations: {
for k, v in parameter {
"\(k)": v
}
}
}
}
parameter: [string]: string
parameter: [string]: string | null

View File

@@ -8,6 +8,8 @@ metadata:
name: gateway
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
appliesToWorkloads:
- '*'
podDisruptive: false
schematic:
cue:

View File

@@ -0,0 +1,49 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/generate-jdbc-connection.cue
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Generate a JDBC connection based on Component of alibaba-rds
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: generate-jdbc-connection
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
import (
"vela/op"
"encoding/base64"
)
output: op.#Read & {
value: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: parameter.name
if parameter.namespace != _|_ {
namespace: parameter.namespace
}
}
}
}
dbHost: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_HOST"])}
dbPort: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PORT"])}
dbName: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_NAME"])}
username: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_USER"])}
password: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PASSWORD"])}
env: [
{name: "url", value: "jdbc://" + dbHost.str + ":" + dbPort.str + "/" + dbName.str + "?characterEncoding=utf8&useSSL=false"},
{name: "username", value: username.str},
{name: "password", value: password.str},
]
parameter: {
// +usage=Specify the name of the secret generated by database component
name: string
// +usage=Specify the namespace of the secret generated by database component
namespace?: string
}

View File

@@ -16,17 +16,20 @@ spec:
schematic:
cue:
template: |
// +patchStrategy=jsonMergePatch
patch: {
metadata: labels: {
for k, v in parameter {
"\(k)": v
}
}
spec: template: metadata: labels: {
for k, v in parameter {
"\(k)": v
if context.output.spec != _|_ && context.output.spec.template != _|_ {
spec: template: metadata: labels: {
for k, v in parameter {
"\(k)": v
}
}
}
}
parameter: [string]: string
parameter: [string]: string | null

View File

@@ -291,8 +291,10 @@ spec:
if parameter.email.from.password.value != _|_ {
email1: op.#SendEmail & {
from: {
address: parameter.email.from.value
alias: parameter.email.from.alias
address: parameter.email.from.address
if parameter.email.from.alias != _|_ {
alias: parameter.email.from.alias
}
password: parameter.email.from.password.value
host: parameter.email.from.host
port: parameter.email.from.port
@@ -318,8 +320,10 @@ spec:
stringValue: op.#ConvertString & {bt: decoded}
email2: op.#SendEmail & {
from: {
address: parameter.email.from.value
alias: parameter.email.from.alias
address: parameter.email.from.address
if parameter.email.from.alias != _|_ {
alias: parameter.email.from.alias
}
password: stringValue.str
host: parameter.email.from.host
port: parameter.email.from.port

View File

@@ -29,6 +29,47 @@ spec:
}
}
parameter: objects: [...#K8sObject]
status:
customStatus: |-
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
ready: {
readyReplicas: *0 | int
} & {
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
}
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
}
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
message: ""
}
healthPolicy: |-
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
ready: {
updatedReplicas: *0 | int
readyReplicas: *0 | int
replicas: *0 | int
observedGeneration: *0 | int
} & {
if context.output.status.updatedReplicas != _|_ {
updatedReplicas: context.output.status.updatedReplicas
}
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if context.output.status.replicas != _|_ {
replicas: context.output.status.replicas
}
if context.output.status.observedGeneration != _|_ {
observedGeneration: context.output.status.observedGeneration
}
}
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
}
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
isHealth: true
}
workload:
type: autodetects.core.oam.dev

View File

@@ -23,7 +23,7 @@ spec:
},
] | []
configMapVolumesList: *[
for v in parameter.configMap {
for v in parameter.configMap if v.mountPath != _|_ {
{
name: "configmap-" + v.name
configMap: {
@@ -37,7 +37,7 @@ spec:
},
] | []
secretVolumesList: *[
for v in parameter.secret {
for v in parameter.secret if v.mountPath != _|_ {
{
name: "secret-" + v.name
secret: {
@@ -69,7 +69,7 @@ spec:
},
] | []
configMapVolumeMountsList: *[
for v in parameter.configMap {
for v in parameter.configMap if v.mountPath != _|_ {
{
name: "configmap-" + v.name
mountPath: v.mountPath
@@ -88,7 +88,7 @@ spec:
},
] | []
secretVolumeMountsList: *[
for v in parameter.secret {
for v in parameter.secret if v.mountPath != _|_ {
{
name: "secret-" + v.name
mountPath: v.mountPath
@@ -126,14 +126,14 @@ spec:
// +patchKey=name
volumes: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
containers: [...{
containers: [{
// +patchKey=name
env: configMapEnvMountsList + secretEnvMountsList
// +patchKey=name
volumeDevices: volumeDevicesList
// +patchKey=name
volumeMounts: pvcVolumeMountsList + configMapVolumeMountsList + secretVolumeMountsList + emptyDirVolumeMountsList
}]
}, ...]
}
outputs: {
@@ -248,7 +248,7 @@ spec:
envName: string
configMapKey: string
}
mountPath: string
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool
data?: {...}
@@ -267,7 +267,7 @@ spec:
envName: string
secretKey: string
}
mountPath: string
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool
stringData?: {...}

View File

@@ -244,6 +244,30 @@ spec:
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}
status:
customStatus: |-
status: {
active: *0 | int
failed: *0 | int
succeeded: *0 | int
} & {
if context.output.status.active != _|_ {
active: context.output.status.active
}
if context.output.status.failed != _|_ {
failed: context.output.status.failed
}
if context.output.status.succeeded != _|_ {
succeeded: context.output.status.succeeded
}
}
message: "Active/Failed/Succeeded:\(status.active)/\(status.failed)/\(status.succeeded)"
healthPolicy: |-
succeeded: *0 | int
if context.output.status.succeeded != _|_ {
succeeded: context.output.status.succeeded
}
isHealth: succeeded == context.output.spec.parallelism
workload:
definition:
apiVersion: batch/v1

View File

@@ -503,52 +503,35 @@ spec:
}
status:
customStatus: |-
import "strconv"
ready: {
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
readyReplicas: *0 | int
} & {
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
}
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
healthPolicy: |-
ready: {
if context.output.status.updatedReplicas == _|_ {
updatedReplicas : 0
updatedReplicas: *0 | int
readyReplicas: *0 | int
replicas: *0 | int
observedGeneration: *0 | int
} & {
if context.output.status.updatedReplicas != _|_ {
updatedReplicas: context.output.status.updatedReplicas
}
if context.output.status.updatedReplicas != _|_ {
updatedReplicas : context.output.status.updatedReplicas
}
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if context.output.status.replicas == _|_ {
replicas: 0
}
if context.output.status.replicas != _|_ {
replicas: context.output.status.replicas
}
if context.output.status.observedGeneration != _|_ {
observedGeneration: context.output.status.observedGeneration
}
if context.output.status.observedGeneration == _|_ {
observedGeneration: 0
}
}
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
workload:
definition:
apiVersion: apps/v1

View File

@@ -396,52 +396,35 @@ spec:
}
status:
customStatus: |-
import "strconv"
ready: {
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
readyReplicas: *0 | int
} & {
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
}
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
healthPolicy: |-
ready: {
if context.output.status.updatedReplicas == _|_ {
updatedReplicas : 0
updatedReplicas: *0 | int
readyReplicas: *0 | int
replicas: *0 | int
observedGeneration: *0 | int
} & {
if context.output.status.updatedReplicas != _|_ {
updatedReplicas: context.output.status.updatedReplicas
}
if context.output.status.updatedReplicas != _|_ {
updatedReplicas : context.output.status.updatedReplicas
}
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if context.output.status.replicas == _|_ {
replicas: 0
}
if context.output.status.replicas != _|_ {
replicas: context.output.status.replicas
}
if context.output.status.observedGeneration != _|_ {
observedGeneration: context.output.status.observedGeneration
}
if context.output.status.observedGeneration == _|_ {
observedGeneration: 0
}
}
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
workload:
definition:
apiVersion: apps/v1

View File

@@ -0,0 +1,44 @@
apiVersion: "v1"
kind: "ConfigMap"
metadata:
name: "service-applied-resources-view"
namespace: {{ include "systemDefinitionNamespace" . }}
data:
template: |
import (
"vela/ql"
)
parameter: {
appName: string
appNs: string
name?: string
cluster?: string
clusterNs?: string
}
response: ql.#ListAppliedResources & {
app: {
name: parameter.appName
namespace: parameter.appNs
filter: {
if parameter.cluster != _|_ {
cluster: parameter.cluster
}
if parameter.clusterNs != _|_ {
clusterNamespace: parameter.clusterNs
}
if parameter.name != _|_ {
components: [parameter.name]
}
}
}
}
if response.err == _|_ {
status: {
resources: response.list
}
}
if response.err != _|_ {
status: {
error: response.err
}
}

View File

@@ -11,6 +11,7 @@ data:
parameter: {
appName: string
appNs: string
name?: string
cluster?: string
clusterNs?: string
}
@@ -25,6 +26,9 @@ data:
if parameter.clusterNs != _|_ {
clusterNamespace: parameter.clusterNs
}
if parameter.name != _|_ {
components: [parameter.name]
}
}
}
}

View File

@@ -2025,6 +2025,12 @@ spec:
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
- jsonPath: .metadata.annotations['app\.oam\.dev\/publishVersion']
name: PUBLISH_VERSION
type: string
- jsonPath: .status.succeeded
name: SUCCEEDED
type: string
name: v1beta1
schema:
openAPIV3Schema:
@@ -2747,13 +2753,6 @@ spec:
type: object
type: object
type: object
applicationConfiguration:
description: ApplicationConfiguration records the rendered applicationConfiguration
from Application, it will contains the whole K8s CR of trait and
the reference component in it.
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
componentDefinitions:
additionalProperties:
description: ComponentDefinition is the Schema for the componentdefinitions
@@ -3087,20 +3086,51 @@ spec:
description: ComponentDefinitions records the snapshot of the componentDefinitions
related with the created/modified Application
type: object
components:
description: Components records the rendered components from Application,
it will contains the whole K8s CR of workload in it.
items:
description: RawComponent record raw component
policies:
additionalProperties:
description: Policy is the Schema for the policy API
properties:
raw:
apiVersion:
description: 'APIVersion defines the versioned schema of this
representation of an object. Servers should convert recognized
schemas to the latest internal value, and may reject unrecognized
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource
this object represents. Servers may infer this from the endpoint
the client submits requests to. Cannot be updated. In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
properties:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type:
type: string
required:
- raw
- type
type: object
type: array
description: Policies records the external policies
type: object
policyDefinitions:
additionalProperties:
description: PolicyDefinition is the Schema for the policydefinitions
@@ -3377,15 +3407,16 @@ spec:
description: PolicyDefinitions records the snapshot of the PolicyDefinitions
related with the created/modified Application
type: object
resourcesConfigMap:
description: ResourcesConfigMap references the ConfigMap that's generated
to contain all final rendered resources.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
referredObjects:
description: ReferredObjects records the referred objects used in
the ref-object typed components
items:
description: ReferredObject the referred Kubernetes object
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type: array
x-kubernetes-preserve-unknown-fields: true
scopeDefinitions:
additionalProperties:
description: A ScopeDefinition registers a kind of Kubernetes custom
@@ -3819,6 +3850,89 @@ spec:
description: TraitDefinitions records the snapshot of the traitDefinitions
related with the created/modified Application
type: object
workflow:
description: Workflow records the external workflow
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this
representation of an object. Servers should convert recognized
schemas to the latest internal value, and may reject unrecognized
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource
this object represents. Servers may infer this from the endpoint
the client submits requests to. Cannot be updated. In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow
step.
properties:
dependsOn:
items:
type: string
type: array
inputs:
description: StepInputs defines variable input of WorkflowStep
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: StepOutputs defines output variable of WorkflowStep
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
type: object
x-kubernetes-preserve-unknown-fields: true
type:
type: string
required:
- name
- type
type: object
type: array
type: object
workflowStepDefinitions:
additionalProperties:
description: WorkflowStepDefinition is the Schema for the workflowstepdefinitions
@@ -4408,10 +4522,182 @@ spec:
required:
- application
type: object
status:
description: ApplicationRevisionStatus is the status of ApplicationRevision
properties:
succeeded:
description: Succeeded records if the workflow finished running with
success
type: boolean
workflow:
description: Workflow the running status of the workflow
properties:
appRevision:
type: string
contextBackend:
description: 'ObjectReference contains enough information to let
you inspect or modify the referred object. --- New uses of this
type are discouraged because of difficulty describing its usage
when embedded in APIs. 1. Ignored fields. It includes many
fields which are not generally honored. For instance, ResourceVersion
and FieldPath are both very rarely valid in actual usage. 2.
Invalid usage help. It is impossible to add specific help for
individual usage. In most embedded usages, there are particular restrictions
like, "must refer only to types A and B" or "UID not honored"
or "name must be restricted". Those cannot be well described
when embedded. 3. Inconsistent validation. Because the usages
are different, the validation rules are different by usage,
which makes it hard for users to predict what will happen. 4.
The fields are both imprecise and overly precise. Kind is not
a precise mapping to a URL. This can produce ambiguity during
interpretation and require a REST mapping. In most cases, the
dependency is on the group,resource tuple and the version
of the actual struct is irrelevant. 5. We cannot easily change
it. Because this type is embedded in many locations, updates
to this type will affect numerous schemas. Don''t make
new APIs embed an underspecified API type they do not control.
Instead of using this type, create a locally provided and used
type that is well-focused on your reference. For example, ServiceReferences
for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
.'
properties:
apiVersion:
description: API version of the referent.
type: string
fieldPath:
description: 'If referring to a piece of an object instead
of an entire object, this string should contain a valid
JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within
a pod, this would take on a value like: "spec.containers{name}"
(where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]"
(container with index 2 in this pod). This syntax is chosen
only to have some well-defined way of referencing a part
of an object. TODO: this design is not final and this field
is subject to change in the future.'
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
resourceVersion:
description: 'Specific resourceVersion to which this reference
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
type: string
uid:
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
type: string
type: object
finished:
type: boolean
message:
type: string
mode:
description: WorkflowMode describes the mode of workflow
type: string
startTime:
format: date-time
type: string
steps:
items:
description: WorkflowStepStatus record the status of a workflow
step
properties:
firstExecuteTime:
description: FirstExecuteTime is the first time this step
execution.
format: date-time
type: string
id:
type: string
lastExecuteTime:
description: LastExecuteTime is the last time this step
execution.
format: date-time
type: string
message:
description: A human readable message indicating details
about why the workflowStep is in this state.
type: string
name:
type: string
phase:
description: WorkflowStepPhase describes the phase of a
workflow step.
type: string
reason:
description: A brief CamelCase message indicating details
about why the workflowStep is in this state.
type: string
subSteps:
description: SubStepsStatus record the status of workflow
steps.
properties:
mode:
description: WorkflowMode describes the mode of workflow
type: string
stepIndex:
type: integer
steps:
items:
description: WorkflowSubStepStatus record the status
of a workflow step
properties:
id:
type: string
message:
description: A human readable message indicating
details about why the workflowStep is in this
state.
type: string
name:
type: string
phase:
description: WorkflowStepPhase describes the phase
of a workflow step.
type: string
reason:
description: A brief CamelCase message indicating
details about why the workflowStep is in this
state.
type: string
type:
type: string
required:
- id
type: object
type: array
type: object
type:
type: string
required:
- id
type: object
type: array
suspend:
type: boolean
terminated:
type: boolean
required:
- finished
- mode
- suspend
- terminated
type: object
required:
- succeeded
type: object
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -16,17 +16,20 @@ spec:
schematic:
cue:
template: |
// +patchStrategy=jsonMergePatch
patch: {
metadata: annotations: {
for k, v in parameter {
"\(k)": v
}
}
spec: template: metadata: annotations: {
for k, v in parameter {
"\(k)": v
if context.output.spec != _|_ && context.output.spec.template != _|_ {
spec: template: metadata: annotations: {
for k, v in parameter {
"\(k)": v
}
}
}
}
parameter: [string]: string
parameter: [string]: string | null

View File

@@ -8,6 +8,8 @@ metadata:
name: gateway
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
appliesToWorkloads:
- '*'
podDisruptive: false
schematic:
cue:

View File

@@ -0,0 +1,49 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/generate-jdbc-connection.cue
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Generate a JDBC connection based on Component of alibaba-rds
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: generate-jdbc-connection
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
import (
"vela/op"
"encoding/base64"
)
output: op.#Read & {
value: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: parameter.name
if parameter.namespace != _|_ {
namespace: parameter.namespace
}
}
}
}
dbHost: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_HOST"])}
dbPort: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PORT"])}
dbName: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_NAME"])}
username: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_USER"])}
password: op.#ConvertString & {bt: base64.Decode(null, output.value.data["DB_PASSWORD"])}
env: [
{name: "url", value: "jdbc://" + dbHost.str + ":" + dbPort.str + "/" + dbName.str + "?characterEncoding=utf8&useSSL=false"},
{name: "username", value: username.str},
{name: "password", value: password.str},
]
parameter: {
// +usage=Specify the name of the secret generated by database component
name: string
// +usage=Specify the namespace of the secret generated by database component
namespace?: string
}

View File

@@ -16,17 +16,20 @@ spec:
schematic:
cue:
template: |
// +patchStrategy=jsonMergePatch
patch: {
metadata: labels: {
for k, v in parameter {
"\(k)": v
}
}
spec: template: metadata: labels: {
for k, v in parameter {
"\(k)": v
if context.output.spec != _|_ && context.output.spec.template != _|_ {
spec: template: metadata: labels: {
for k, v in parameter {
"\(k)": v
}
}
}
}
parameter: [string]: string
parameter: [string]: string | null

View File

@@ -291,8 +291,10 @@ spec:
if parameter.email.from.password.value != _|_ {
email1: op.#SendEmail & {
from: {
address: parameter.email.from.value
alias: parameter.email.from.alias
address: parameter.email.from.address
if parameter.email.from.alias != _|_ {
alias: parameter.email.from.alias
}
password: parameter.email.from.password.value
host: parameter.email.from.host
port: parameter.email.from.port
@@ -318,8 +320,10 @@ spec:
stringValue: op.#ConvertString & {bt: decoded}
email2: op.#SendEmail & {
from: {
address: parameter.email.from.value
alias: parameter.email.from.alias
address: parameter.email.from.address
if parameter.email.from.alias != _|_ {
alias: parameter.email.from.alias
}
password: stringValue.str
host: parameter.email.from.host
port: parameter.email.from.port

View File

@@ -29,6 +29,47 @@ spec:
}
}
parameter: objects: [...#K8sObject]
status:
customStatus: |-
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
ready: {
readyReplicas: *0 | int
} & {
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
}
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
}
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
message: ""
}
healthPolicy: |-
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
ready: {
updatedReplicas: *0 | int
readyReplicas: *0 | int
replicas: *0 | int
observedGeneration: *0 | int
} & {
if context.output.status.updatedReplicas != _|_ {
updatedReplicas: context.output.status.updatedReplicas
}
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if context.output.status.replicas != _|_ {
replicas: context.output.status.replicas
}
if context.output.status.observedGeneration != _|_ {
observedGeneration: context.output.status.observedGeneration
}
}
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
}
if context.output.apiVersion != "apps/v1" || context.output.kind != "Deployment" {
isHealth: true
}
workload:
type: autodetects.core.oam.dev

View File

@@ -23,7 +23,7 @@ spec:
},
] | []
configMapVolumesList: *[
for v in parameter.configMap {
for v in parameter.configMap if v.mountPath != _|_ {
{
name: "configmap-" + v.name
configMap: {
@@ -37,7 +37,7 @@ spec:
},
] | []
secretVolumesList: *[
for v in parameter.secret {
for v in parameter.secret if v.mountPath != _|_ {
{
name: "secret-" + v.name
secret: {
@@ -69,7 +69,7 @@ spec:
},
] | []
configMapVolumeMountsList: *[
for v in parameter.configMap {
for v in parameter.configMap if v.mountPath != _|_ {
{
name: "configmap-" + v.name
mountPath: v.mountPath
@@ -88,7 +88,7 @@ spec:
},
] | []
secretVolumeMountsList: *[
for v in parameter.secret {
for v in parameter.secret if v.mountPath != _|_ {
{
name: "secret-" + v.name
mountPath: v.mountPath
@@ -126,14 +126,14 @@ spec:
// +patchKey=name
volumes: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
containers: [...{
containers: [{
// +patchKey=name
env: configMapEnvMountsList + secretEnvMountsList
// +patchKey=name
volumeDevices: volumeDevicesList
// +patchKey=name
volumeMounts: pvcVolumeMountsList + configMapVolumeMountsList + secretVolumeMountsList + emptyDirVolumeMountsList
}]
}, ...]
}
outputs: {
@@ -248,7 +248,7 @@ spec:
envName: string
configMapKey: string
}
mountPath: string
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool
data?: {...}
@@ -267,7 +267,7 @@ spec:
envName: string
secretKey: string
}
mountPath: string
mountPath?: string
defaultMode: *420 | int
readOnly: *false | bool
stringData?: {...}

View File

@@ -244,6 +244,30 @@ spec:
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}
status:
customStatus: |-
status: {
active: *0 | int
failed: *0 | int
succeeded: *0 | int
} & {
if context.output.status.active != _|_ {
active: context.output.status.active
}
if context.output.status.failed != _|_ {
failed: context.output.status.failed
}
if context.output.status.succeeded != _|_ {
succeeded: context.output.status.succeeded
}
}
message: "Active/Failed/Succeeded:\(status.active)/\(status.failed)/\(status.succeeded)"
healthPolicy: |-
succeeded: *0 | int
if context.output.status.succeeded != _|_ {
succeeded: context.output.status.succeeded
}
isHealth: succeeded == context.output.spec.parallelism
workload:
definition:
apiVersion: batch/v1

View File

@@ -503,52 +503,35 @@ spec:
}
status:
customStatus: |-
import "strconv"
ready: {
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
readyReplicas: *0 | int
} & {
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
}
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
healthPolicy: |-
ready: {
if context.output.status.updatedReplicas == _|_ {
updatedReplicas : 0
updatedReplicas: *0 | int
readyReplicas: *0 | int
replicas: *0 | int
observedGeneration: *0 | int
} & {
if context.output.status.updatedReplicas != _|_ {
updatedReplicas: context.output.status.updatedReplicas
}
if context.output.status.updatedReplicas != _|_ {
updatedReplicas : context.output.status.updatedReplicas
}
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if context.output.status.replicas == _|_ {
replicas: 0
}
if context.output.status.replicas != _|_ {
replicas: context.output.status.replicas
}
if context.output.status.observedGeneration != _|_ {
observedGeneration: context.output.status.observedGeneration
}
if context.output.status.observedGeneration == _|_ {
observedGeneration: 0
}
}
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
workload:
definition:
apiVersion: apps/v1

View File

@@ -396,52 +396,35 @@ spec:
}
status:
customStatus: |-
import "strconv"
ready: {
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
readyReplicas: *0 | int
} & {
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
}
message: "Ready:" + strconv.FormatInt(ready.readyReplicas, 10) + "/" + strconv.FormatInt(context.output.spec.replicas, 10)
message: "Ready:\(ready.readyReplicas)/\(context.output.spec.replicas)"
healthPolicy: |-
ready: {
if context.output.status.updatedReplicas == _|_ {
updatedReplicas : 0
updatedReplicas: *0 | int
readyReplicas: *0 | int
replicas: *0 | int
observedGeneration: *0 | int
} & {
if context.output.status.updatedReplicas != _|_ {
updatedReplicas: context.output.status.updatedReplicas
}
if context.output.status.updatedReplicas != _|_ {
updatedReplicas : context.output.status.updatedReplicas
}
if context.output.status.readyReplicas == _|_ {
readyReplicas: 0
}
if context.output.status.readyReplicas != _|_ {
readyReplicas: context.output.status.readyReplicas
}
if context.output.status.replicas == _|_ {
replicas: 0
}
if context.output.status.replicas != _|_ {
replicas: context.output.status.replicas
}
if context.output.status.observedGeneration != _|_ {
observedGeneration: context.output.status.observedGeneration
}
if context.output.status.observedGeneration == _|_ {
observedGeneration: 0
}
}
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
isHealth: (context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)
workload:
definition:
apiVersion: apps/v1

View File

@@ -120,5 +120,5 @@ func (s *Server) buildSwagger() (*spec.Swagger, error) {
if err != nil {
return nil, fmt.Errorf("create apiserver failed : %w ", err)
}
return restfulspec.BuildSwagger(server.RegisterServices()), nil
return restfulspec.BuildSwagger(server.RegisterServices(context.Background(), false)), nil
}

View File

@@ -35,6 +35,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"github.com/oam-dev/kubevela/pkg/auth"
ctrlClient "github.com/oam-dev/kubevela/pkg/client"
standardcontroller "github.com/oam-dev/kubevela/pkg/controller"
commonconfig "github.com/oam-dev/kubevela/pkg/controller/common"
@@ -42,6 +43,7 @@ import (
oamv1alpha2 "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/controller/utils"
"github.com/oam-dev/kubevela/pkg/cue/packages"
_ "github.com/oam-dev/kubevela/pkg/features"
_ "github.com/oam-dev/kubevela/pkg/monitor/metrics"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
@@ -86,6 +88,8 @@ func main() {
var renewDeadline time.Duration
var retryPeriod time.Duration
var enableClusterGateway bool
var enableClusterMetrics bool
var clusterMetricsInterval time.Duration
flag.BoolVar(&useWebhook, "use-webhook", false, "Enable Admission Webhook")
flag.StringVar(&certDir, "webhook-cert-dir", "/k8s-webhook-server/serving-certs", "Admission webhook cert/key dir.")
@@ -133,6 +137,8 @@ func main() {
flag.DurationVar(&retryPeriod, "leader-election-retry-period", 2*time.Second,
"The duration the LeaderElector clients should wait between tries of actions")
flag.BoolVar(&enableClusterGateway, "enable-cluster-gateway", false, "Enable cluster-gateway to use multicluster, disabled by default.")
flag.BoolVar(&enableClusterMetrics, "enable-cluster-metrics", false, "Enable cluster-metrics-management to collect metrics from clusters with cluster-gateway, disabled by default. When this param is enabled, enable-cluster-gateway should be enabled")
flag.DurationVar(&clusterMetricsInterval, "cluster-metrics-interval", 15*time.Second, "The interval that ClusterMetricsMgr will collect metrics from clusters, default value is 15 seconds.")
flag.BoolVar(&controllerArgs.EnableCompatibility, "enable-asi-compatibility", false, "enable compatibility for asi")
flag.BoolVar(&controllerArgs.IgnoreAppWithoutControllerRequirement, "ignore-app-without-controller-version", false, "If true, application controller will not process the app without 'app.oam.dev/controller-version-require' annotation")
standardcontroller.AddOptimizeFlags()
@@ -197,13 +203,23 @@ func main() {
restConfig.UserAgent = kubevelaName + "/" + version.GitRevision
restConfig.QPS = float32(qps)
restConfig.Burst = burst
restConfig.Wrap(auth.NewImpersonatingRoundTripper)
// wrapper the round tripper by multi cluster rewriter
if enableClusterGateway {
if _, err := multicluster.Initialize(restConfig, true); err != nil {
client, err := multicluster.Initialize(restConfig, true)
if err != nil {
klog.ErrorS(err, "failed to enable multi-cluster capability")
os.Exit(1)
}
if enableClusterMetrics {
_, err := multicluster.NewClusterMetricsMgr(context.Background(), client, clusterMetricsInterval)
if err != nil {
klog.ErrorS(err, "failed to enable multi-cluster-metrics capability")
os.Exit(1)
}
}
}
ctrl.SetLogger(klogr.New())

View File

@@ -6,3 +6,5 @@ coverage:
patch:
default:
target: 70%
ignore:
- "**/zz_generated.deepcopy.go"

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

View File

@@ -0,0 +1,139 @@
# Managing KubeVela Application Versions
![overall-arch](./application-revision-arch.jpg)
In KubeVela, ApplicationRevision keeps the snapshot of application and all its runtime dependencies such as ComponentRevision, external Policy or referred objects.
This revision can be used review the application changes and rollback to past configurations.
In KubeVela v1.3, for application which uses the `PublishVersion` feature, we support viewing the history revisions, checking the differences across revisions, and rolling back to the latest succeeded revision.
For application with the `app.oam.dev/publishVersion` annotation, the workflow runs are strictly controlled.
The annotation, which is noted as *publishVersion* in the following paragraphs, is used to identify a static version of the application and its dependencies.
When the annotation is updated to a new value, the application will generate a new revision no matter if the application spec or the dependencies are changed.
It will then trigger a fresh new run of workflow after terminating the previous run.
During the running of workflow, all related data are retrieved from the ApplicationRevision, which means the changes to the application spec or the dependencies will not take effects until a newer `publishVerison` is annotated.
Fo example, let's start with an application with has referred objects, external workflow and policies.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: nginx-publish-version
namespace: examples
annotations:
app.oam.dev/publishVersion: alpha1
spec:
components:
- name: nginx-publish-version
type: ref-objects
properties:
objects:
- resource: deployment
workflow:
ref: make-release-in-hangzhou
---
apiVersion: core.oam.dev/v1alpha1
kind: Policy
metadata:
name: topology-hangzhou-clusters
namespace: examples
type: topology
properties:
clusterLabelSelector:
region: hangzhou
---
apiVersion: core.oam.dev/v1alpha1
kind: Workflow
metadata:
name: make-release-in-hangzhou
namespace: examples
steps:
- name: deploy-hangzhou
type: deploy
properties:
policies: ["topology-hangzhou-clusters"]
```
This application should be successful after a while.
Now if we edit the referred deployment and set its image to an invalid value, such as `nginx:1.200`.
The application will not re-run the workflow to make this change take effect automatically.
But since the dependencies of this application changes, it means the next workflow run will update the deployment image.
Now let's run `vela live-diff nginx-publish-version -n examples` to check this diff
```bash
$ vela live-diff nginx-publish-version -n examples
* Application (nginx-publish-version) has no change
* External Policy (topology-hangzhou-clusters) has no change
* External Workflow (make-release-in-hangzhou) has no change
* Referred Object (apps/v1 Deployment examples/nginx-publish-version) has been modified(*)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
- deployment.kubernetes.io/revision: "1"
+ deployment.kubernetes.io/revision: "2"
labels:
app: nginx-publish-version
name: nginx-publish-version
namespace: examples
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx-publish-version
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx-publish-version
spec:
containers:
- - image: nginx
+ - image: nginx:1.200
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
```
We can see all the changes of the application spec and the dependencies.
Now let's make this change take effects.
Update the `publishVersion` annotation in the application to `alpha2` to trigger the re-run of workflow.
We will find the application stuck at `runningWorkflow` as the deployment cannot finish the update progress due to the invalid image.
Now we can run `vela revision list nginx-publish-version -n examples` to list all the available revisions.
```bash
$ vela revision list nginx-publish-version -n examples
NAME PUBLISH_VERSION SUCCEEDED HASH BEGIN_TIME STATUS SIZE
nginx-publish-version-v1 alpha1 true d428eff1f0a7918 2022-03-28 20:54:25 Succeeded 8.1 KiB
nginx-publish-version-v2 alpha2 false 4f04da8827d87922 2022-03-28 21:01:25 Executing 8.1 KiB
```
Before rolling back, we need to suspend the workflow of the application first. Run `vela workflow suspend nginx-publish-version -n examples`.
After the application workflow is suspended, run `vela workflow rollback nginx-publish-version -n examples`, the workflow will be rolled back and the application resources will restore to the succeeded state.
```bash
$ vela workflow suspend nginx-publish-version -n examples
Successfully suspend workflow: nginx-publish-version
$ vela workflow rollback nginx-publish-version -n examples
Find succeeded application revision nginx-publish-version-v1 (PublishVersion: alpha1) to rollback.
Application spec rollback successfully.
Application status rollback successfully.
Application rollback completed.
Application outdated revision cleaned up.
```

View File

@@ -0,0 +1,103 @@
# How to use
1. define a stateful component with StatefulSet as output
```shell
$ vela def apply stateful.cue
ComponentDefinition test-stateful created in namespace vela-system.
```
2. define a custom trait with patch volume
```shell
$ vela def apply volume-trait.cue
TraitDefinition storageclass created in namespace vela-system.
```
3. You can validate it by:
```
$ vela def vet volume-trait.cue
Validation succeed.
```
4. try dry run your app:
```
vela dry-run -f app.yaml
```
```yaml
# Application(website) -- Component(custom-component)
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
annotations: {}
labels:
app.oam.dev/appRevision: ""
app.oam.dev/component: custom-component
app.oam.dev/name: website
app.oam.dev/namespace: default
app.oam.dev/resourceType: WORKLOAD
workload.oam.dev/type: test-stateful
name: custom-component
namespace: default
spec:
minReadySeconds: 10
replicas: 1
selector:
matchLabels:
app: custom-component
serviceName: custom-component
template:
metadata:
labels:
app: custom-component
spec:
containers:
- image: nginx:latest
name: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- mountPath: /usr/share/nginx/html
name: test
terminationGracePeriodSeconds: 10
volumeClaimTemplates:
- metadata:
name: test
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: cbs
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
app: custom-component
app.oam.dev/appRevision: ""
app.oam.dev/component: custom-component
app.oam.dev/name: website
app.oam.dev/namespace: default
app.oam.dev/resourceType: TRAIT
trait.oam.dev/resource: web
trait.oam.dev/type: AuxiliaryWorkload
name: custom-component
namespace: default
spec:
clusterIP: None
ports:
- name: web
port: 80
selector:
app: custom-component
```

View File

@@ -0,0 +1,20 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: website
namespace: default
spec:
components:
- name: custom-component
type: test-stateful
properties:
image: nginx:latest
replicas: 1
traits:
- type: storageclass
properties:
volumeClaimTemplates:
- name: test
requests: 10Gi
storageClassName: cbs
mountPath: /usr/share/nginx/html

View File

@@ -0,0 +1,58 @@
"test-stateful": {
annotations: {}
attributes: workload: definition: {
apiVersion: "apps/v1"
kind: "StatefulSet"
}
description: "StatefulSet component."
labels: {}
type: "component"
}
template: {
output: {
apiVersion: "apps/v1"
kind: "StatefulSet"
metadata: name: context.name
spec: {
selector: matchLabels: app: context.name
minReadySeconds: 10
replicas: parameter.replicas
serviceName: context.name
template: {
metadata: labels: app: context.name
spec: {
containers: [{
name: "nginx"
ports: [{
name: "web"
containerPort: 80
}]
image: parameter.image
}]
terminationGracePeriodSeconds: 10
}
}
}
}
outputs: web: {
apiVersion: "v1"
kind: "Service"
metadata: {
name: context.name
labels: app: context.name
}
spec: {
clusterIP: "None"
ports: [{
name: "web"
port: 80
}]
selector: app: context.name
}
}
parameter: {
image: string
replicas: int
}
}

View File

@@ -0,0 +1,56 @@
storageclass: {
type: "trait"
annotations: {}
labels: {}
description: "Add storageclass on K8s pod for your workload which follows the pod spec in path 'spec.template'."
attributes: {
appliesToWorkloads: ["*"]
}
}
template: {
volumeClaimTemplatesList: *[
for v in parameter.volumeClaimTemplates {
{
metadata: name: v.name
spec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: v.requests
storageClassName: v.storageClassName
}
}
},
] | []
volumeClaimTemplateVolumeMountsList: *[
for v in parameter.volumeClaimTemplates {
{
name: v.name
mountPath: v.mountPath
}
},
] | []
patch: {
// +patchKey=name
spec: {
template: spec: {
containers: [...{
// +patchKey=name
volumeMounts: volumeClaimTemplateVolumeMountsList
}]
}
// +patchKey=name
volumeClaimTemplates: volumeClaimTemplatesList
}
}
parameter: {
volumeClaimTemplates?: [...{
name: string
requests: string
storageClassName: string
mountPath: string
}]
}
}

View File

@@ -0,0 +1,433 @@
# Advanced examples for multi-cluster deployment
The below features are introduced in KubeVela v1.3.
![overall-arch](./ref-arch.jpg)
## Topology Policy
Topology policy is a policy used to describe the location where application component should be deployed and managed.
The most straight forward way is directly specifying the names of clusters to be deployed.
In the following example, the nginx webservice will be deployed to the `examples` namespace in both `hangzhou-1` and `hangzhou-2` clusters concurrently.
After nginx in both clusters are ready, the application will finish running workflow and becomes healthy.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: basic-topology
namespace: examples
spec:
components:
- name: nginx-basic
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusters: ["hangzhou-1", "hangzhou-2"]
```
The clusters in the topology can also be selected by labels instead of names.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: label-selector-topology
namespace: examples
spec:
components:
- name: nginx-label-selector
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
```
If you want to deploy application components into the control plane cluster, you can use the `local` cluster.
Besides, you can also deploy your application components in another namespace other than the application's namespace.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: local-ns-topology
namespace: examples
spec:
components:
- name: nginx-local-ns
type: webservice
properties:
image: nginx
policies:
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
```
## Deploy WorkflowStep
By default, if you declare multiple topology policies in the application, the application components will be deployed in all destinations following the order of the policies.
If you want to manipulate the process of deploying them, for example, changing the order or adding manual-approval, you can use the `deploy` workflow step explicitly in the workflow to achieve that.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: deploy-workflowstep
namespace: examples
spec:
components:
- name: nginx-deploy-workflowstep
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
workflow:
steps:
- type: deploy
name: deploy-local
properties:
policies: ["topology-local"]
- type: deploy
name: deploy-hangzhou
properties:
# require manual approval before running this step
auto: false
policies: ["topology-hangzhou-clusters"]
```
You can also deploy application components with different topology policies concurrently, by filling these topology policies in on `deploy` step.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: deploy-concurrently
namespace: examples
spec:
components:
- name: nginx-deploy-concurrently
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
workflow:
steps:
- type: deploy
name: deploy-all
properties:
policies: ["topology-local", "topology-hangzhou-clusters"]
```
## Override Policy
Override policy helps you to customize the application components in different clusters. For example, using a different container image or changing the default number of replicas. The override policy should be used together with the topology policy in the `deploy` workflow step.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: deploy-with-override
namespace: examples
spec:
components:
- name: nginx-with-override
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
- name: override-nginx-legacy-image
type: override
properties:
components:
- name: nginx-with-override
properties:
image: nginx:1.20
- name: override-high-availability
type: override
properties:
components:
- type: webservice
traits:
- type: scaler
properties:
replicas: 3
workflow:
steps:
- type: deploy
name: deploy-local
properties:
policies: ["topology-local"]
- type: deploy
name: deploy-hangzhou
properties:
policies: ["topology-hangzhou-clusters", "override-nginx-legacy-image", "override-high-availability"]
```
The override policy has many advanced capabilities, such as adding new component or selecting components to use.
The following example will deploy `nginx:1.20` to local cluster. `nginx` and `nginx:stable` will be deployed to hangzhou clusters.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: advance-override
namespace: examples
spec:
components:
- name: nginx-advance-override-legacy
type: webservice
properties:
image: nginx:1.20
- name: nginx-advance-override-latest
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
- name: override-nginx-legacy
type: override
properties:
selector: ["nginx-advance-override-legacy"]
- name: override-nginx-latest
type: override
properties:
selector: ["nginx-advance-override-latest", "nginx-advance-override-stable"]
components:
- name: nginx-advance-override-stable
type: webservice
properties:
image: nginx:stable
workflow:
steps:
- type: deploy
name: deploy-local
properties:
policies: ["topology-local", "override-nginx-legacy"]
- type: deploy
name: deploy-hangzhou
properties:
policies: ["topology-hangzhou-clusters", "override-nginx-latest"]
```
## Ref-object Component
Sometimes, you may want to copy resources from one place to other places, such as copying secrets from the control plane cluster into managed clusters.
You can use the `ref-object` typed component to achieve that.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: ref-objects-example
namespace: examples
spec:
components:
- name: image-pull-secrets
type: ref-objects
properties:
objects:
- resource: secret
name: image-credential-to-copy
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
```
You can also select resources by labels and duplicate them from one cluster into another cluster.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: ref-objects-duplicate-deployments
namespace: examples
spec:
components:
- name: duplicate-deployment
type: ref-objects
properties:
objects:
- resource: deployment
cluster: hangzhou-1
# select all deployment in the `examples` namespace in cluster `hangzhou-1` that matches the labelSelector
labelSelector:
need-duplicate: "true"
policies:
- name: topology-hangzhou-2
type: topology
properties:
clusters: ["hangzhou-2"]
```
You can also form a component by multiple referenced resources and even attach traits to the main workload.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: ref-objects-multiple-resources
namespace: examples
spec:
components:
- name: nginx-ref-multiple-resources
type: ref-objects
properties:
objects:
- resource: deployment
- resource: service
traits:
- type: scaler
properties:
replicas: 3
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
```
## External Policies and Workflow
Sometimes, you may want to use the same policy across multiple applications or reuse previous workflow to deploy different resources.
To reduce the repeated code, you can leverage the external policies and workflow and refer to them in your applications.
> NOTE: you can only refer to Policy and Workflow within your application's namespace.
```yaml
apiVersion: core.oam.dev/v1alpha1
kind: Policy
metadata:
name: topology-hangzhou-clusters
namespace: examples
type: topology
properties:
clusterLabelSelector:
region: hangzhou
---
apiVersion: core.oam.dev/v1alpha1
kind: Policy
metadata:
name: override-high-availability-webservice
namespace: examples
type: override
properties:
components:
- type: webservice
traits:
- type: scaler
properties:
replicas: 3
---
apiVersion: core.oam.dev/v1alpha1
kind: Workflow
metadata:
name: make-release-in-hangzhou
namespace: examples
steps:
- type: deploy
name: deploy-hangzhou
properties:
auto: false
policies: ["override-high-availability-webservice", "topology-hangzhou-clusters"]
```
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: external-policies-and-workflow
namespace: examples
spec:
components:
- name: nginx-external-policies-and-workflow
type: webservice
properties:
image: nginx
workflow:
ref: make-release-in-hangzhou
```
> NOTE: The internal policies will be loaded first. External policies will only be used when there is no corresponding policy inside the application. In the following example, we can reuse `tology-hangzhou-clusters` policy and `make-release-in-hangzhou` workflow but modify the `override-high-availability-webservice` by injecting the same-named policy inside the new application.
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: nginx-stable-ultra
namespace: examples
spec:
components:
- name: nginx-stable-ultra
type: webservice
properties:
image: nginx:stable
policies:
- name: override-high-availability-webservice
type: override
properties:
components:
- type: webservice
traits:
- type: scaler
properties:
replicas: 5
workflow:
ref: make-release-in-hangzhou
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

107
docs/examples/rbac/rbac.md Normal file
View File

@@ -0,0 +1,107 @@
# RBAC
User:
```yaml
name: user
userRoles: ["app-developer"]
...
```
ProjectUser:
```yaml
username: user
project: demo
userRoles: ["app-developer"]
```
Role:
```yaml
name: app-developer
project: demo
permissions: ["app-manage"]
```
```yaml
name: admin
permissions: ["all"]
```
Permission:
```yaml
name: app-manage
project: demo
resource: ["project:demo/application:*"]
actions: ["*"]
effect: Allow
principal: {}
condition: {}
```
```yaml
name: app1-manage
project: demo
resource: ["project:demo/application:app1/*"]
actions: ["*"]
effect: Allow
principal: {}
condition: {}
name: app2-manage
project: demo
resource: ["project:demo/application:app2/*"]
actions: ["*"]
effect: Allow
principal: {}
condition: {}
```
```yaml
name: cluster-manage
resource: ["cluster:*"]
actions: ["*"]
effect: Allow
principal: {}
condition: {}
```
```yaml
name: cluster-beijing-manage
resource: ["cluster:beijing"]
actions: ["*"]
effect: Allow
principal: {}
condition: {}
```
```yaml
name: all
resource: ["*"]
actions: ["*"]
effect: Allow
principal: {}
condition: {}
```
PermissionTemplate:
```yaml
name: app-manage
resource: ["project:${projectName}/application:*"]
actions: ["*"]
level: project
effect: Allow
principal: {}
condition: {}
```
```yaml
name: deny-delete-cluster
resource: ["cluster:*"]
actions: ["delete"]
level: platform
effect: Deny
```

View File

@@ -29,5 +29,5 @@ kubectl apply -f ./docs/examples/rollout-trait/app-v3.yaml
6. modify targetSize as 7 to scale
```shell
kubectl apply -f ./docs/examples/rollout-trait/app-sacle.yaml
kubectl apply -f ./docs/examples/rollout-trait/app-scale.yaml
```

View File

@@ -0,0 +1,40 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: jdbc
spec:
components:
- name: db
type: alibaba-rds
properties:
instance_name: favorite-links
database_name: db1
account_name: oamtest
password: U34rfwefwefffaked
security_ips: [ "0.0.0.0/0" ]
privilege: ReadWrite
writeConnectionSecretToRef:
name: db-conn
- name: express-server
type: webservice
properties:
image: crccheck/hello-world
port: 8000
workflow:
steps:
- name: jdbc
type: generate-jdbc-connection
outputs:
- name: jdbc
valueFrom: jdbc
properties:
name: db-conn
namespace: default
- name: apply
type: apply-component
inputs:
- from: jdbc
parameterKey: env
properties:
component: express-server

View File

@@ -0,0 +1,25 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: busybox
spec:
components:
- name: busybox
type: webservice
properties:
image: busybox
cmd: ["sleep", "86400"]
annotations:
annotation-key: annotation-value
to-delete-annotation-key: to-delete-annotation-value
traits:
# the `annotations` trait will add/delete annotation key/value pair to the
# labels of the workload and the template inside the spec of the workload (if exists)
# 1. if original annotations contains the key, value will be overridden
# 2. if original annotations do not contain the key, value will be added
# 3. if original annotations contains the key and the value is null, the key will be removed
- type: annotations
properties:
added-annotation-key: added-annotation-value
annotation-key: modified-annotation-value
to-delete-annotation-key: null

View File

@@ -23,11 +23,6 @@ spec:
- type: json-patch
properties:
operations:
- op: add
path: "/metadata"
value:
labels:
deploy-label-key: deploy-label-added-value
- op: add
path: "/spec/replicas"
value: 3

View File

@@ -0,0 +1,25 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: busybox
spec:
components:
- name: busybox
type: webservice
properties:
image: busybox
cmd: ["sleep", "86400"]
labels:
label-key: label-value
to-delete-label-key: to-delete-label-value
traits:
# the `labels` trait will add/delete label key/value pair to the
# labels of the workload and the template inside the spec of the workload (if exists)
# 1. if original labels contains the key, value will be overridden
# 2. if original labels do not contain the key, value will be added
# 3. if original labels contains the key and the value is null, the key will be removed
- type: labels
properties:
added-label-key: added-label-value
label-key: modified-label-value
to-delete-label-key: null

View File

@@ -156,7 +156,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
tdName := "annotations"
output, err := e2e.Exec(fmt.Sprintf("kubectl-vela show %s -n default", tdName))
Expect(err).NotTo(HaveOccurred())
Expect(output).Should(ContainSubstring("map[string]string"))
Expect(output).Should(ContainSubstring("map[string](null|string)"))
})
It("Test show webservice def with cue ignore annotation ", func() {
tdName := "webservice"
@@ -728,9 +728,7 @@ spec:
---
`
var livediffResult = `---
# Application (test-vela-app) has been modified(*)
---
var livediffResult = `Application (test-vela-app) has been modified(*)
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
@@ -759,15 +757,11 @@ var livediffResult = `---
type: test-webservice
status: {}
---
## Component (express-server) has been removed(-)
---
* Component (express-server) has been removed(-)
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- annotations: {}
- labels:
- app.oam.dev/appRevision: ""
- app.oam.dev/component: express-server
- app.oam.dev/name: test-vela-app
- app.oam.dev/namespace: default
@@ -790,15 +784,11 @@ var livediffResult = `---
- ports:
- - containerPort: 80
---
### Component (express-server) / Trait (test-ingress/service) has been removed(-)
---
* Component (express-server) / Trait (test-ingress/service) has been removed(-)
- apiVersion: v1
- kind: Service
- metadata:
- annotations: {}
- labels:
- app.oam.dev/appRevision: ""
- app.oam.dev/component: express-server
- app.oam.dev/name: test-vela-app
- app.oam.dev/namespace: default
@@ -814,15 +804,11 @@ var livediffResult = `---
- selector:
- app.oam.dev/component: express-server
---
### Component (express-server) / Trait (test-ingress/ingress) has been removed(-)
---
* Component (express-server) / Trait (test-ingress/ingress) has been removed(-)
- apiVersion: networking.k8s.io/v1beta1
- kind: Ingress
- metadata:
- annotations: {}
- labels:
- app.oam.dev/appRevision: ""
- app.oam.dev/component: express-server
- app.oam.dev/name: test-vela-app
- app.oam.dev/namespace: default
@@ -841,15 +827,11 @@ var livediffResult = `---
- servicePort: 80
- path: /
---
## Component (new-express-server) has been added(+)
---
* Component (new-express-server) has been added(+)
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ annotations: {}
+ labels:
+ app.oam.dev/appRevision: ""
+ app.oam.dev/component: new-express-server
+ app.oam.dev/name: test-vela-app
+ app.oam.dev/namespace: default
@@ -877,15 +859,11 @@ var livediffResult = `---
+ requests:
+ cpu: "0.5"
---
### Component (new-express-server) / Trait (test-ingress/service) has been added(+)
---
* Component (new-express-server) / Trait (test-ingress/service) has been added(+)
+ apiVersion: v1
+ kind: Service
+ metadata:
+ annotations: {}
+ labels:
+ app.oam.dev/appRevision: ""
+ app.oam.dev/component: new-express-server
+ app.oam.dev/name: test-vela-app
+ app.oam.dev/namespace: default
@@ -901,15 +879,11 @@ var livediffResult = `---
+ selector:
+ app.oam.dev/component: new-express-server
---
### Component (new-express-server) / Trait (test-ingress/ingress) has been added(+)
---
* Component (new-express-server) / Trait (test-ingress/ingress) has been added(+)
+ apiVersion: networking.k8s.io/v1beta1
+ kind: Ingress
+ metadata:
+ annotations: {}
+ labels:
+ app.oam.dev/appRevision: ""
+ app.oam.dev/component: new-express-server
+ app.oam.dev/name: test-vela-app
+ app.oam.dev/namespace: default

13
go.mod
View File

@@ -14,6 +14,7 @@ require (
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/briandowns/spinner v1.11.1
github.com/containerd/containerd v1.4.13
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/coreos/prometheus-operator v0.41.1
github.com/crossplane/crossplane-runtime v0.14.1-0.20210722005935-0b469fcc77cd
github.com/davecgh/go-spew v1.1.1
@@ -22,6 +23,7 @@ require (
github.com/emicklei/go-restful/v3 v3.0.0-rc2
github.com/evanphx/json-patch v4.11.0+incompatible
github.com/fatih/color v1.12.0
github.com/form3tech-oss/jwt-go v3.2.3+incompatible
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gertd/go-pluralize v0.1.7
github.com/getkin/kin-openapi v0.34.0
@@ -41,7 +43,7 @@ require (
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/mitchellh/hashstructure/v2 v2.0.1
github.com/oam-dev/cluster-gateway v1.1.6
github.com/oam-dev/cluster-register v1.0.3
github.com/oam-dev/cluster-register v1.0.4-0.20220325092210-cee4a3d3fb7d
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28
github.com/oam-dev/terraform-controller v0.4.2
github.com/olekukonko/tablewriter v0.0.5
@@ -61,6 +63,7 @@ require (
github.com/wonderflow/cert-manager-api v1.0.3
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/tools v0.1.6 // indirect
@@ -74,8 +77,10 @@ require (
k8s.io/api v0.22.1
k8s.io/apiextensions-apiserver v0.22.1
k8s.io/apimachinery v0.22.1
k8s.io/apiserver v0.22.1
k8s.io/cli-runtime v0.21.0
k8s.io/client-go v0.22.1
k8s.io/component-base v0.22.1
k8s.io/klog v1.0.0
k8s.io/klog/v2 v2.9.0
k8s.io/kube-aggregator v0.22.1
@@ -146,7 +151,6 @@ require (
github.com/evanphx/json-patch/v5 v5.1.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/zapr v0.4.0 // indirect
@@ -212,6 +216,7 @@ require (
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
@@ -242,7 +247,6 @@ require (
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // 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
@@ -258,14 +262,13 @@ require (
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
istio.io/api v0.0.0-20210128181506-0c4b8e54850f // indirect
istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a // indirect
k8s.io/apiserver v0.22.1 // indirect
k8s.io/component-base v0.22.1 // indirect
sigs.k8s.io/apiserver-network-proxy v0.0.24 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.24 // indirect
sigs.k8s.io/apiserver-runtime v1.0.3-0.20210913073608-0663f60bfee2 // indirect

7
go.sum
View File

@@ -353,6 +353,7 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
@@ -1250,8 +1251,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oam-dev/cluster-gateway v1.1.6 h1:CY6m2Qcs6XJ/l/NY48CdHD7GAel9zZ/erUOz2zYzxkI=
github.com/oam-dev/cluster-gateway v1.1.6/go.mod h1:SF7S4Ss+VUs2OVxmvSrrFGcaNFoXy6JWxHAnUxC1QcY=
github.com/oam-dev/cluster-register v1.0.3 h1:n6OTkNYxXYtkl2KjFv8444hF0B3SV9uyP8FL8G1QPUk=
github.com/oam-dev/cluster-register v1.0.3/go.mod h1:AoqoF9HgmluxtRBYyvKDbLNdlPY6Xvm+/6uo6LjLaBw=
github.com/oam-dev/cluster-register v1.0.4-0.20220325092210-cee4a3d3fb7d h1:ZZsBkksYDzwJEjqx9/XBD+VwlhHz8flkZvMJYzO4ASA=
github.com/oam-dev/cluster-register v1.0.4-0.20220325092210-cee4a3d3fb7d/go.mod h1:nKEUMfuEB8pHKsaSah9IA+UQzezrPYebBdRozyNtlZc=
github.com/oam-dev/stern v1.13.2 h1:jlGgtJbKmIVhzkH44ft5plkgs8XEfvxbFrQdX60CQR4=
github.com/oam-dev/stern v1.13.2/go.mod h1:0pLjZt0amXE/ErF16Rdrgd98H2owN8Hmn3/7CX5+AeA=
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28 h1:tD8HiFKnt0jnwdTWjeqUnfnUYLD/+Nsmj8ZGIxqDWiU=
@@ -1359,6 +1360,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v0.0.0-20210510181950-ab96adb96fea/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus-community/prom-label-proxy v0.1.1-0.20200616110844-0fbfa11fa8f3/go.mod h1:XdjyZg7LCbCC5FADHtpgNp6kQ0W9beXVGfmcvndMj5Y=
github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE=
@@ -2385,6 +2387,7 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=

View File

@@ -42,7 +42,8 @@ func main() {
os.Exit(1)
}
if err := ref.GenerateReferenceDocs(ctx, c, path, types.DefaultKubeVelaNS); err != nil {
ref.Remote = &plugins.Remote{Namespace: types.DefaultKubeVelaNS}
if err := ref.GenerateReferenceDocs(ctx, c, path); err != nil {
fmt.Println(err)
os.Exit(1)
}

View File

@@ -2025,6 +2025,12 @@ spec:
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
- jsonPath: .metadata.annotations['app\.oam\.dev\/publishVersion']
name: PUBLISH_VERSION
type: string
- jsonPath: .status.succeeded
name: SUCCEEDED
type: string
name: v1beta1
schema:
openAPIV3Schema:
@@ -2747,13 +2753,6 @@ spec:
type: object
type: object
type: object
applicationConfiguration:
description: ApplicationConfiguration records the rendered applicationConfiguration
from Application, it will contains the whole K8s CR of trait and
the reference component in it.
type: object
componentDefinitions:
additionalProperties:
description: ComponentDefinition is the Schema for the componentdefinitions
@@ -3087,20 +3086,51 @@ spec:
description: ComponentDefinitions records the snapshot of the componentDefinitions
related with the created/modified Application
type: object
components:
description: Components records the rendered components from Application,
it will contains the whole K8s CR of workload in it.
items:
description: RawComponent record raw component
policies:
additionalProperties:
description: Policy is the Schema for the policy API
properties:
raw:
apiVersion:
description: 'APIVersion defines the versioned schema of this
representation of an object. Servers should convert recognized
schemas to the latest internal value, and may reject unrecognized
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource
this object represents. Servers may infer this from the endpoint
the client submits requests to. Cannot be updated. In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
properties:
type: object
type:
type: string
required:
- raw
- type
type: object
type: array
description: Policies records the external policies
type: object
policyDefinitions:
additionalProperties:
description: PolicyDefinition is the Schema for the policydefinitions
@@ -3377,15 +3407,16 @@ spec:
description: PolicyDefinitions records the snapshot of the PolicyDefinitions
related with the created/modified Application
type: object
resourcesConfigMap:
description: ResourcesConfigMap references the ConfigMap that's generated
to contain all final rendered resources.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
referredObjects:
description: ReferredObjects records the referred objects used in
the ref-object typed components
items:
description: ReferredObject the referred Kubernetes object
type: object
type: array
scopeDefinitions:
additionalProperties:
description: A ScopeDefinition registers a kind of Kubernetes custom
@@ -3819,6 +3850,89 @@ spec:
description: TraitDefinitions records the snapshot of the traitDefinitions
related with the created/modified Application
type: object
workflow:
description: Workflow records the external workflow
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this
representation of an object. Servers should convert recognized
schemas to the latest internal value, and may reject unrecognized
values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource
this object represents. Servers may infer this from the endpoint
the client submits requests to. Cannot be updated. In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow
step.
properties:
dependsOn:
items:
type: string
type: array
inputs:
description: StepInputs defines variable input of WorkflowStep
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: StepOutputs defines output variable of WorkflowStep
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
type: object
type:
type: string
required:
- name
- type
type: object
type: array
type: object
workflowStepDefinitions:
additionalProperties:
description: WorkflowStepDefinition is the Schema for the workflowstepdefinitions
@@ -4408,10 +4522,182 @@ spec:
required:
- application
type: object
status:
description: ApplicationRevisionStatus is the status of ApplicationRevision
properties:
succeeded:
description: Succeeded records if the workflow finished running with
success
type: boolean
workflow:
description: Workflow the running status of the workflow
properties:
appRevision:
type: string
contextBackend:
description: 'ObjectReference contains enough information to let
you inspect or modify the referred object. --- New uses of this
type are discouraged because of difficulty describing its usage
when embedded in APIs. 1. Ignored fields. It includes many
fields which are not generally honored. For instance, ResourceVersion
and FieldPath are both very rarely valid in actual usage. 2.
Invalid usage help. It is impossible to add specific help for
individual usage. In most embedded usages, there are particular restrictions
like, "must refer only to types A and B" or "UID not honored"
or "name must be restricted". Those cannot be well described
when embedded. 3. Inconsistent validation. Because the usages
are different, the validation rules are different by usage,
which makes it hard for users to predict what will happen. 4.
The fields are both imprecise and overly precise. Kind is not
a precise mapping to a URL. This can produce ambiguity during
interpretation and require a REST mapping. In most cases, the
dependency is on the group,resource tuple and the version
of the actual struct is irrelevant. 5. We cannot easily change
it. Because this type is embedded in many locations, updates
to this type will affect numerous schemas. Don''t make
new APIs embed an underspecified API type they do not control.
Instead of using this type, create a locally provided and used
type that is well-focused on your reference. For example, ServiceReferences
for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
.'
properties:
apiVersion:
description: API version of the referent.
type: string
fieldPath:
description: 'If referring to a piece of an object instead
of an entire object, this string should contain a valid
JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within
a pod, this would take on a value like: "spec.containers{name}"
(where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]"
(container with index 2 in this pod). This syntax is chosen
only to have some well-defined way of referencing a part
of an object. TODO: this design is not final and this field
is subject to change in the future.'
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
resourceVersion:
description: 'Specific resourceVersion to which this reference
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
type: string
uid:
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
type: string
type: object
finished:
type: boolean
message:
type: string
mode:
description: WorkflowMode describes the mode of workflow
type: string
startTime:
format: date-time
type: string
steps:
items:
description: WorkflowStepStatus record the status of a workflow
step
properties:
firstExecuteTime:
description: FirstExecuteTime is the first time this step
execution.
format: date-time
type: string
id:
type: string
lastExecuteTime:
description: LastExecuteTime is the last time this step
execution.
format: date-time
type: string
message:
description: A human readable message indicating details
about why the workflowStep is in this state.
type: string
name:
type: string
phase:
description: WorkflowStepPhase describes the phase of a
workflow step.
type: string
reason:
description: A brief CamelCase message indicating details
about why the workflowStep is in this state.
type: string
subSteps:
description: SubStepsStatus record the status of workflow
steps.
properties:
mode:
description: WorkflowMode describes the mode of workflow
type: string
stepIndex:
type: integer
steps:
items:
description: WorkflowSubStepStatus record the status
of a workflow step
properties:
id:
type: string
message:
description: A human readable message indicating
details about why the workflowStep is in this
state.
type: string
name:
type: string
phase:
description: WorkflowStepPhase describes the phase
of a workflow step.
type: string
reason:
description: A brief CamelCase message indicating
details about why the workflowStep is in this
state.
type: string
type:
type: string
required:
- id
type: object
type: array
type: object
type:
type: string
required:
- id
type: object
type: array
suspend:
type: boolean
terminated:
type: boolean
required:
- finished
- mode
- suspend
- terminated
type: object
required:
- succeeded
type: object
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -53,6 +53,7 @@ import (
"sigs.k8s.io/yaml"
common2 "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"
"github.com/oam-dev/kubevela/apis/types"
utils2 "github.com/oam-dev/kubevela/pkg/controller/utils"
@@ -113,6 +114,9 @@ var (
// CLIMetaOptions get Addon metadata for CLI display
CLIMetaOptions = ListOptions{}
// UnInstallOptions used for addon uninstalling
UnInstallOptions = ListOptions{GetDefinition: true}
)
const (
@@ -282,6 +286,7 @@ func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UID
return nil, fmt.Errorf("fail to generate openAPIschema for addon %s : %w", meta.Name, err)
}
}
addon.AvailableVersions = []string{addon.Version}
return addon, nil
}
@@ -520,32 +525,22 @@ func genAddonAPISchema(addonRes *UIData) error {
return nil
}
// RenderApp render a K8s application
func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config, k8sClient client.Client, args map[string]interface{}) (*v1beta1.Application, error) {
if args == nil {
args = map[string]interface{}{}
func getClusters(args map[string]interface{}) []string {
ccr, ok := args[types.ClustersArg]
if !ok {
return nil
}
app := addon.AppTemplate
if app == nil {
app = &v1beta1.Application{
TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
ObjectMeta: metav1.ObjectMeta{
Name: Convert2AppName(addon.Name),
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
oam.LabelAddonName: addon.Name,
},
},
Spec: v1beta1.ApplicationSpec{
Components: []common2.ApplicationComponent{},
},
}
cc, ok := ccr.([]string)
if !ok {
return nil
}
app.Labels = util.MergeMapOverrideWithDst(app.Labels, map[string]string{oam.LabelAddonName: addon.Name})
// force override the namespace defined vela with DefaultVelaNS,this value can be modified by Env
app.SetNamespace(types.DefaultKubeVelaNS)
return cc
}
// renderNeededNamespaceAsComps will convert namespace as app components to create namespace for managed clusters
func renderNeededNamespaceAsComps(addon *InstallPackage) []common2.ApplicationComponent {
var nscomps []common2.ApplicationComponent
// create namespace for managed clusters
for _, namespace := range addon.NeedNamespace {
// vela-system must exist before rendering vela addon
if namespace == types.DefaultKubeVelaNS {
@@ -556,42 +551,154 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
Name: fmt.Sprintf("%s-namespace", namespace),
Properties: util.Object2RawExtension(renderNamespace(namespace)),
}
app.Spec.Components = append(app.Spec.Components, comp)
nscomps = append(nscomps, comp)
}
return nscomps
}
func renderResources(addon *InstallPackage, args map[string]interface{}) ([]common2.ApplicationComponent, error) {
var resources []common2.ApplicationComponent
if len(addon.YAMLTemplates) != 0 {
comp, err := renderK8sObjectsComponent(addon.YAMLTemplates, addon.Name)
if err != nil {
return nil, err
}
app.Spec.Components = append(app.Spec.Components, *comp)
resources = append(resources, *comp)
}
for _, tmpl := range addon.CUETemplates {
comp, err := renderCUETemplate(tmpl, addon.Parameters, args)
if err != nil {
return nil, ErrRenderCueTmpl
return nil, NewAddonError(fmt.Sprintf("fail to render cue template %s", err.Error()))
}
if addon.Name == ObservabilityAddon && strings.HasSuffix(comp.Name, ".cue") {
comp.Name = strings.Split(comp.Name, ".cue")[0]
}
app.Spec.Components = append(app.Spec.Components, *comp)
resources = append(resources, *comp)
}
return resources, nil
}
func formatAppFramework(addon *InstallPackage) *v1beta1.Application {
app := addon.AppTemplate
if app == nil {
app = &v1beta1.Application{
TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "Application"},
Spec: v1beta1.ApplicationSpec{
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)
if app.Labels == nil {
app.Labels = make(map[string]string)
}
app.Labels[oam.LabelAddonName] = addon.Name
app.Labels[oam.LabelAddonVersion] = addon.Version
return app
}
func checkDeployClusters(ctx context.Context, k8sClient client.Client, args map[string]interface{}) ([]string, error) {
deployClusters := getClusters(args)
if len(deployClusters) == 0 || k8sClient == nil {
return nil, nil
}
vcs, err := multicluster.ListVirtualClusters(ctx, k8sClient)
if err != nil {
return nil, err
}
var res []string
for _, c := range deployClusters {
c = strings.TrimSpace(c)
if c == "" {
continue
}
var found bool
for _, vc := range vcs {
if c == vc.Name {
found = true
break
}
}
if !found {
return nil, errors.Errorf("cluster %s not exist", c)
}
res = append(res, c)
}
return res, nil
}
// RenderApp render a K8s application
func RenderApp(ctx context.Context, addon *InstallPackage, k8sClient client.Client, args map[string]interface{}) (*v1beta1.Application, error) {
if args == nil {
args = map[string]interface{}{}
}
app := formatAppFramework(addon)
app.Spec.Components = append(app.Spec.Components, renderNeededNamespaceAsComps(addon)...)
resources, err := renderResources(addon, args)
if err != nil {
return nil, err
}
app.Spec.Components = append(app.Spec.Components, resources...)
deployClusters, err := checkDeployClusters(ctx, k8sClient, args)
if err != nil {
return nil, err
}
switch {
case isDeployToRuntimeOnly(addon):
if app.Spec.Workflow == nil {
app.Spec.Workflow = &v1beta1.Workflow{Steps: make([]v1beta1.WorkflowStep, 0)}
}
app.Spec.Workflow.Steps = append(app.Spec.Workflow.Steps,
v1beta1.WorkflowStep{
Name: "deploy-control-plane",
Type: "apply-application",
},
v1beta1.WorkflowStep{
Name: "deploy-runtime",
Type: "deploy2runtime",
if len(deployClusters) == 0 {
// deploy to all clusters
app.Spec.Workflow = &v1beta1.Workflow{Steps: []v1beta1.WorkflowStep{
{
Name: "deploy-control-plane",
Type: "apply-application",
},
{
Name: "deploy-runtime",
Type: "deploy2runtime",
},
}}
// TODO(wonderflow): this can be merged into len(deployClusters) > 0 case
/*
allclusters, err := multicluster.ListVirtualClusters(ctx, k8sClient)
if err != nil {
return nil, err
}
for _, c := range allclusters {
deployClusters = append(deployClusters, c.Name)
}
*/
} else {
var found bool
for _, c := range deployClusters {
if c == multicluster.ClusterLocalName {
found = true
break
}
}
if !found {
deployClusters = append(deployClusters, multicluster.ClusterLocalName)
}
// deploy to specified clusters
if app.Spec.Policies == nil {
app.Spec.Policies = []v1beta1.AppPolicy{}
}
body, _ := json.Marshal(map[string][]string{types.ClustersArg: deployClusters})
app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
Name: "specified-addon-clusters",
Type: v1alpha1.TopologyPolicyType,
Properties: &runtime.RawExtension{Raw: body},
})
// addon should not contain workflow, this also update legacy addon with deploy2runtime steps
app.Spec.Workflow = nil
}
case addon.Name == ObservabilityAddon:
clusters, err := allocateDomainForAddon(ctx, k8sClient)
if err != nil {
@@ -627,36 +734,7 @@ func RenderApp(ctx context.Context, addon *InstallPackage, config *rest.Config,
app.Spec.Workflow.Steps = append(app.Spec.Workflow.Steps, workflowSteps...)
default:
for _, cueDef := range addon.CUEDefinitions {
def := definition.Definition{Unstructured: unstructured.Unstructured{}}
err := def.FromCUEString(cueDef.Data, config)
if err != nil {
return nil, errors.Wrapf(err, "fail to render definition: %s in cue's format", cueDef.Name)
}
if def.Unstructured.GetNamespace() == "" {
def.Unstructured.SetNamespace(types.DefaultKubeVelaNS)
}
app.Spec.Components = append(app.Spec.Components, common2.ApplicationComponent{
Name: cueDef.Name,
Type: "raw",
Properties: util.Object2RawExtension(&def.Unstructured),
})
}
for _, teml := range addon.DefSchemas {
u, err := renderSchemaConfigmap(teml)
if err != nil {
return nil, err
}
app.Spec.Components = append(app.Spec.Components, common2.ApplicationComponent{
Name: teml.Name,
Type: "raw",
Properties: util.Object2RawExtension(u),
})
}
// set to nil so workflow mode will be set to "DAG" automatically
if app.Spec.Workflow != nil && len(app.Spec.Workflow.Steps) == 0 {
app.Spec.Workflow = nil
}
}
return app, nil
@@ -695,14 +773,13 @@ func RenderDefinitions(addon *InstallPackage, config *rest.Config) ([]*unstructu
func RenderDefinitionSchema(addon *InstallPackage) ([]*unstructured.Unstructured, error) {
schemaConfigmaps := make([]*unstructured.Unstructured, 0)
if isDeployToRuntimeOnly(addon) {
for _, teml := range addon.DefSchemas {
u, err := renderSchemaConfigmap(teml)
if err != nil {
return nil, err
}
schemaConfigmaps = append(schemaConfigmaps, u)
// No matter runtime mode or control mode , definition schemas only needs to control plane k8s.
for _, teml := range addon.DefSchemas {
u, err := renderSchemaConfigmap(teml)
if err != nil {
return nil, err
}
schemaConfigmaps = append(schemaConfigmaps, u)
}
return schemaConfigmaps, nil
}
@@ -996,26 +1073,37 @@ func (h *Installer) enableAddon(addon *InstallPackage) error {
return nil
}
func (h *Installer) loadInstallPackage(name string) (*InstallPackage, error) {
metas, err := h.getAddonMeta()
if err != nil {
return nil, errors.Wrap(err, "fail to get addon meta")
func (h *Installer) loadInstallPackage(name, version string) (*InstallPackage, error) {
var installPackage *InstallPackage
var err error
if !IsVersionRegistry(*h.r) {
metas, err := h.getAddonMeta()
if err != nil {
return nil, errors.Wrap(err, "fail to get addon meta")
}
meta, ok := metas[name]
if !ok {
return nil, ErrNotExist
}
var uiData *UIData
uiData, err = h.cache.GetUIData(*h.r, name, version)
if err != nil {
return nil, err
}
// enable this addon if it's invisible
installPackage, err = h.r.GetInstallPackage(&meta, uiData)
if err != nil {
return nil, errors.Wrap(err, "fail to find dependent addon in source repository")
}
} else {
versionedRegistry := BuildVersionedRegistry(h.r.Name, h.r.Helm.URL)
installPackage, err = versionedRegistry.GetAddonInstallPackage(context.Background(), name, version)
if err != nil {
return nil, err
}
}
meta, ok := metas[name]
if !ok {
return nil, ErrNotExist
}
var uiData *UIData
uiData, err = h.cache.GetUIData(*h.r, name)
if err != nil {
return nil, err
}
// enable this addon if it's invisible
installPackage, err := h.r.GetInstallPackage(&meta, uiData)
if err != nil {
return nil, errors.Wrap(err, "fail to find dependent addon in source repository")
}
return installPackage, nil
}
@@ -1043,7 +1131,8 @@ func (h *Installer) installDependency(addon *InstallPackage) error {
if !apierrors.IsNotFound(err) {
return err
}
depAddon, err := h.loadInstallPackage(dep.Name)
// always install addon's latest version
depAddon, err := h.loadInstallPackage(dep.Name, "")
if err != nil {
return err
}
@@ -1075,9 +1164,27 @@ func (h *Installer) checkDependency(addon *InstallPackage) ([]string, error) {
}
return needEnable, nil
}
func (h *Installer) createOrUpdate(app *v1beta1.Application) error {
var getapp v1beta1.Application
err := h.cli.Get(h.ctx, client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, &getapp)
if apierrors.IsNotFound(err) {
return h.cli.Create(h.ctx, app)
}
if err != nil {
return err
}
getapp.Spec = app.Spec
err = h.cli.Update(h.ctx, &getapp)
if err != nil {
klog.Errorf("fail to create application: %v", err)
return errors.Wrap(err, "fail to create application")
}
getapp.DeepCopyInto(app)
return nil
}
func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
app, err := RenderApp(h.ctx, addon, h.config, h.cli, h.args)
app, err := RenderApp(h.ctx, addon, h.cli, h.args)
if err != nil {
return errors.Wrap(err, "render addon application fail")
}
@@ -1100,10 +1207,12 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error {
return errors.Wrap(err, "render addon definitions' schema fail")
}
err = h.apply.Apply(h.ctx, app, apply.DisableUpdateAnnotation())
if err != nil {
klog.Errorf("fail to create application: %v", err)
return errors.Wrap(err, "fail to create application")
if err := passDefInAppAnnotation(defs, app); err != nil {
return errors.Wrapf(err, "cannot pass definition to addon app's annotation")
}
if err = h.createOrUpdate(app); err != nil {
return err
}
for _, def := range defs {

View File

@@ -21,7 +21,9 @@ import (
"fmt"
"time"
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -33,6 +35,7 @@ import (
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
v1alpha12 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam/util"
@@ -270,6 +273,56 @@ var _ = Describe("Test addon util func", func() {
})
var _ = Describe("Test render addon with specified clusters", func() {
BeforeEach(func() {
Expect(k8sClient.Create(ctx, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "add-c1",
Namespace: "vela-system",
Labels: map[string]string{
v1alpha1.LabelKeyClusterCredentialType: string(v1alpha1.CredentialTypeX509Certificate),
v1alpha1.LabelKeyClusterEndpointType: v1alpha1.ClusterEndpointTypeConst,
"key": "value",
},
},
})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "add-c2",
Namespace: "vela-system",
Labels: map[string]string{
v1alpha1.LabelKeyClusterCredentialType: string(v1alpha1.CredentialTypeX509Certificate),
v1alpha1.LabelKeyClusterEndpointType: v1alpha1.ClusterEndpointTypeConst,
"key": "value",
},
},
})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
It("test render not exits cluster", func() {
i := &baseAddon
i.Name = "test-cluster-addon"
args := map[string]interface{}{
"clusters": []string{"add-c1", "ne"},
}
_, err := RenderApp(ctx, i, k8sClient, args)
Expect(err.Error()).Should(BeEquivalentTo("cluster ne not exist"))
})
It("test render normal addon with specified clusters", func() {
i := &baseAddon
i.DeployTo = &DeployTo{RuntimeCluster: true}
i.Name = "test-cluster-addon-normal"
args := map[string]interface{}{
"clusters": []string{"add-c1", "add-c2"},
}
ap, err := RenderApp(ctx, i, k8sClient, args)
Expect(err).Should(BeNil())
Expect(ap.Spec.Policies).Should(BeEquivalentTo([]v1beta1.AppPolicy{{Name: "specified-addon-clusters",
Type: v1alpha12.TopologyPolicyType,
Properties: &runtime.RawExtension{Raw: []byte(`{"clusters":["add-c1","add-c2","local"]}`)}}}))
})
})
const (
appYaml = `apiVersion: core.oam.dev/v1beta1
kind: Application

View File

@@ -61,6 +61,10 @@ var paths = []string{
"test-error-addon/metadata.yaml",
"test-error-addon/resources/parameter.cue",
"test-disable-addon/metadata.yaml",
"test-disable-addon/definitions/compDef.yaml",
"test-disable-addon/definitions/traitDef.cue",
}
var ossHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request) {
@@ -128,7 +132,7 @@ func testReaderFunc(t *testing.T, reader AsyncReader) {
rName := "KubeVela"
uiDataList, err := ListAddonUIDataFromReader(reader, registryMeta, rName, UIMetaOptions)
assert.True(t, strings.Contains(err.Error(), "#parameter.example: preference mark not allowed at this position"))
assert.Equal(t, len(uiDataList), 3)
assert.Equal(t, 4, len(uiDataList))
assert.Equal(t, uiDataList[0].RegistryName, rName)
// test get install package
@@ -224,15 +228,16 @@ func TestRender(t *testing.T) {
func TestRenderApp(t *testing.T) {
addon := baseAddon
app, err := RenderApp(ctx, &addon, nil, nil, map[string]interface{}{})
app, err := RenderApp(ctx, &addon, nil, map[string]interface{}{})
assert.NoError(t, err, "render app fail")
assert.Equal(t, len(app.Spec.Components), 2)
// definition should not be rendered
assert.Equal(t, len(app.Spec.Components), 1)
}
func TestRenderAppWithNeedNamespace(t *testing.T) {
addon := baseAddon
addon.NeedNamespace = append(addon.NeedNamespace, types.DefaultKubeVelaNS)
app, err := RenderApp(ctx, &addon, nil, nil, map[string]interface{}{})
addon.NeedNamespace = append(addon.NeedNamespace, types.DefaultKubeVelaNS, "test-ns2")
app, err := RenderApp(ctx, &addon, nil, map[string]interface{}{})
assert.NoError(t, err, "render app fail")
assert.Equal(t, len(app.Spec.Components), 2)
for _, c := range app.Spec.Components {
@@ -253,7 +258,7 @@ func TestRenderDeploy2RuntimeAddon(t *testing.T) {
assert.Equal(t, def.GetAPIVersion(), "core.oam.dev/v1beta1")
assert.Equal(t, def.GetKind(), "TraitDefinition")
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, nil, map[string]interface{}{})
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{})
assert.NoError(t, err)
steps := app.Spec.Workflow.Steps
assert.True(t, len(steps) >= 2)
@@ -274,7 +279,7 @@ func TestRenderDefinitions(t *testing.T) {
assert.Equal(t, def.GetAPIVersion(), "core.oam.dev/v1beta1")
assert.Equal(t, def.GetKind(), "TraitDefinition")
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, nil, map[string]interface{}{})
app, err := RenderApp(ctx, &addonDeployToRuntime, nil, map[string]interface{}{})
assert.NoError(t, err)
// addon which app work on no-runtime-cluster mode workflow is nil
assert.Nil(t, app.Spec.Workflow)
@@ -287,7 +292,7 @@ func TestRenderK8sObjects(t *testing.T) {
RuntimeCluster: false,
}
app, err := RenderApp(ctx, &addonMultiYaml, nil, nil, map[string]interface{}{})
app, err := RenderApp(ctx, &addonMultiYaml, nil, map[string]interface{}{})
assert.NoError(t, err)
assert.Equal(t, len(app.Spec.Components), 1)
comp := app.Spec.Components[0]
@@ -547,12 +552,12 @@ func TestRenderApp4Observability(t *testing.T) {
},
},
args: map[string]interface{}{},
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":null}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application"}]}},"status":{}}`,
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability","addons.oam.dev/version":""}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":null}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application"}]}},"status":{}}`,
},
}
for _, tc := range testcases {
t.Run("", func(t *testing.T) {
app, err := RenderApp(ctx, &tc.addon, nil, k8sClient, tc.args)
app, err := RenderApp(ctx, &tc.addon, k8sClient, tc.args)
assert.Equal(t, tc.err, err)
if app != nil {
data, err := json.Marshal(app)
@@ -594,12 +599,12 @@ func TestRenderApp4ObservabilityWithK8sData(t *testing.T) {
},
},
args: map[string]interface{}{},
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability"}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
application: `{"kind":"Application","apiVersion":"core.oam.dev/v1beta1","metadata":{"name":"addon-observability","namespace":"vela-system","creationTimestamp":null,"labels":{"addons.oam.dev/name":"observability","addons.oam.dev/version":""}},"spec":{"components":[],"policies":[{"name":"domain","type":"env-binding","properties":{"envs":[{"name":"test-secret","placement":{"clusterSelector":{"name":"test-secret"}}}]}}],"workflow":{"steps":[{"name":"deploy-control-plane","type":"apply-application-in-parallel"},{"name":"test-secret","type":"deploy2env","properties":{"env":"test-secret","parallel":true,"policy":"domain"}}]}},"status":{}}`,
},
}
for _, tc := range testcases {
t.Run("", func(t *testing.T) {
app, err := RenderApp(ctx, &tc.addon, nil, k8sClient, tc.args)
app, err := RenderApp(ctx, &tc.addon, k8sClient, tc.args)
assert.Equal(t, tc.err, err)
if app != nil {
data, err := json.Marshal(app)

View File

@@ -43,6 +43,8 @@ type Cache struct {
registry map[string]Registry
versionedUIData map[string]map[string]*UIData
mutex *sync.RWMutex
ds RegistryDataStore
@@ -51,11 +53,12 @@ type Cache struct {
// NewCache will build a new cache instance
func NewCache(ds RegistryDataStore) *Cache {
return &Cache{
uiData: make(map[string][]*UIData),
registryMeta: make(map[string]map[string]SourceMeta),
registry: make(map[string]Registry),
mutex: new(sync.RWMutex),
ds: ds,
uiData: make(map[string][]*UIData),
registryMeta: make(map[string]map[string]SourceMeta),
registry: make(map[string]Registry),
versionedUIData: make(map[string]map[string]*UIData),
mutex: new(sync.RWMutex),
ds: ds,
}
}
@@ -80,21 +83,35 @@ func (u *Cache) ListAddonMeta(r Registry) (map[string]SourceMeta, error) {
}
// GetUIData get addon data for UI display from cache, if cache not found, it will find from source
func (u *Cache) GetUIData(r Registry, addonName string) (*UIData, error) {
addon := u.getCachedUIData(r.Name, addonName)
func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error) {
addon := u.getCachedUIData(r, addonName, version)
if addon != nil {
return addon, nil
}
var err error
registryMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
if !IsVersionRegistry(r) {
registryMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
}
meta, ok := registryMeta[addonName]
if !ok {
return nil, ErrNotExist
}
addon, err = r.GetUIData(&meta, UIMetaOptions)
if err != nil {
return nil, err
}
} else {
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)
return nil, err
}
}
meta, ok := registryMeta[addonName]
if !ok {
return nil, ErrNotExist
}
return r.GetUIData(&meta, UIMetaOptions)
return addon, nil
}
// ListUIData will always list UIData from cache first, if not exist, read from source.
@@ -104,24 +121,40 @@ func (u *Cache) ListUIData(r Registry) ([]*UIData, error) {
if listAddons != nil {
return listAddons, nil
}
addonMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
}
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
if err != nil {
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
if !IsVersionRegistry(r) {
addonMeta, err := u.ListAddonMeta(r)
if err != nil {
return nil, err
}
listAddons, err = r.ListUIData(addonMeta, UIMetaOptions)
if err != nil {
return nil, fmt.Errorf("fail to get addons from registry %s, %w", r.Name, err)
}
} else {
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
listAddons, err = versionedRegistry.ListAddon()
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
return nil, err
}
}
u.putAddonUIData2Cache(r.Name, listAddons)
return listAddons, nil
}
func (u *Cache) getCachedUIData(registry, addonName string) *UIData {
addons := u.listCachedUIData(registry)
for _, a := range addons {
if a.Name == addonName {
return a
func (u *Cache) getCachedUIData(registry Registry, addonName, version string) *UIData {
if !IsVersionRegistry(registry) {
addons := u.listCachedUIData(registry.Name)
for _, a := range addons {
if a.Name == addonName {
return a
}
}
} else {
if len(version) == 0 {
version = "latest"
}
return u.versionedUIData[registry.Name][fmt.Sprintf("%s-%s", addonName, version)]
}
return nil
}
@@ -201,6 +234,20 @@ func (u *Cache) putRegistry2Cache(registry []Registry) {
}
}
func (u *Cache) putVersionedUIData2Cache(registryName, addonName, version string, uiData *UIData) {
if u == nil {
return
}
u.mutex.Lock()
defer u.mutex.Unlock()
if u.versionedUIData[registryName] == nil {
u.versionedUIData[registryName] = make(map[string]*UIData)
}
u.versionedUIData[registryName][fmt.Sprintf("%s-%s", addonName, version)] = uiData
}
func (u *Cache) discoverAndRefreshRegistry() {
registries, err := u.ds.ListRegistries(context.Background())
if err != nil {
@@ -210,17 +257,36 @@ func (u *Cache) discoverAndRefreshRegistry() {
u.putRegistry2Cache(registries)
for _, r := range registries {
registryMeta, err := r.ListAddonMeta()
if err != nil {
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
continue
if !IsVersionRegistry(r) {
registryMeta, err := r.ListAddonMeta()
if err != nil {
log.Logger.Errorf("fail to list registry %s metadata, %v", r.Name, err)
continue
}
u.putAddonMeta2Cache(r.Name, registryMeta)
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
continue
}
u.putAddonUIData2Cache(r.Name, uiData)
} else {
versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL)
uiDatas, err := versionedRegistry.ListAddon()
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
continue
}
for _, addon := range uiDatas {
uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
if err != nil {
log.Logger.Errorf("fail to get addon from registry %s, addon %s version %s for cache updating, %v", addon.Name, r.Name, err)
continue
}
u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
// we also no version key, if use get addonUIData without version will return this vale as latest data.
u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
}
}
u.putAddonMeta2Cache(r.Name, registryMeta)
uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
if err != nil {
log.Logger.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
continue
}
u.putAddonUIData2Cache(r.Name, uiData)
}
}

33
pkg/addon/cache_test.go Normal file
View File

@@ -0,0 +1,33 @@
/*
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 (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPutVersionedUIData2cache(t *testing.T) {
uiData := UIData{Meta: Meta{Name: "fluxcd", Icon: "test.com/fluxcd.png", Version: "1.0.0"}}
u := NewCache(nil)
u.putVersionedUIData2Cache("helm-repo", "fluxcd", "1.0.0", &uiData)
assert.NotEmpty(t, u.versionedUIData)
assert.NotEmpty(t, u.versionedUIData["helm-repo"])
assert.NotEmpty(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"])
assert.Equal(t, u.versionedUIData["helm-repo"]["fluxcd-1.0.0"].Name, "fluxcd")
}

View File

@@ -22,7 +22,6 @@ import (
"fmt"
"k8s.io/client-go/discovery"
"k8s.io/klog/v2"
v1 "k8s.io/api/core/v1"
@@ -32,8 +31,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
commontypes "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/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
@@ -51,9 +52,9 @@ const (
)
// EnableAddon will enable addon with dependency check, source is where addon from.
func EnableAddon(ctx context.Context, name string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error {
func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error {
h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache)
pkg, err := h.loadInstallPackage(name)
pkg, err := h.loadInstallPackage(name, version)
if err != nil {
return err
}
@@ -65,12 +66,24 @@ func EnableAddon(ctx context.Context, name string, cli client.Client, discoveryC
}
// DisableAddon will disable addon from cluster.
func DisableAddon(ctx context.Context, cli client.Client, name string) error {
func DisableAddon(ctx context.Context, cli client.Client, name string, config *rest.Config, force bool) error {
app, err := FetchAddonRelatedApp(ctx, cli, name)
// if app not exist, report error
if err != nil {
return err
}
if !force {
var usingAddonApp []v1beta1.Application
usingAddonApp, err = checkAddonHasBeenUsed(ctx, cli, name, *app, config)
if err != nil {
return err
}
if len(usingAddonApp) != 0 {
return fmt.Errorf(fmt.Sprintf("%s please delete them first", usingAppsInfo(usingAddonApp)))
}
}
if err := cli.Delete(ctx, app); err != nil {
return err
}
@@ -118,21 +131,30 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
}
return Status{}, err
}
var clusters = make(map[string]map[string]interface{})
for _, r := range app.Status.AppliedResources {
if r.Cluster == "" {
r.Cluster = multicluster.ClusterLocalName
}
// TODO(wonderflow): we should collect all the necessary information as observability, currently we only collect cluster name
clusters[r.Cluster] = make(map[string]interface{})
}
if app.Status.Workflow != nil && app.Status.Workflow.Suspend {
return Status{AddonPhase: suspend, AppStatus: &app.Status}, nil
return Status{AddonPhase: suspend, AppStatus: &app.Status, Clusters: clusters, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion]}, nil
}
switch app.Status.Phase {
case commontypes.ApplicationRunning:
if name == ObservabilityAddon {
// TODO(wonderflow): this is a hack Implementation and need be fixed in a unified way
var (
clusters = make(map[string]map[string]interface{})
sec v1.Secret
domain string
sec v1.Secret
domain string
)
if err = cli.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: Convert2SecName(name)}, &sec); err != nil {
klog.ErrorS(err, "failed to get observability secret")
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
return Status{AddonPhase: enabling, AppStatus: &app.Status, Clusters: clusters}, nil
}
if v, ok := sec.Data[ObservabilityAddonDomainArg]; ok {
@@ -141,7 +163,7 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
observability, err := GetObservabilityAccessibilityInfo(ctx, cli, domain)
if err != nil {
klog.ErrorS(err, "failed to get observability accessibility info")
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
return Status{AddonPhase: enabling, AppStatus: &app.Status, Clusters: clusters}, nil
}
for _, o := range observability {
@@ -158,11 +180,11 @@ func GetAddonStatus(ctx context.Context, cli client.Client, name string) (Status
}
return Status{AddonPhase: enabled, AppStatus: &app.Status, Clusters: clusters}, nil
}
return Status{AddonPhase: enabled, AppStatus: &app.Status}, nil
return Status{AddonPhase: enabled, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion], Clusters: clusters}, nil
case commontypes.ApplicationDeleting:
return Status{AddonPhase: disabling, AppStatus: &app.Status}, nil
return Status{AddonPhase: disabling, AppStatus: &app.Status, Clusters: clusters}, nil
default:
return Status{AddonPhase: enabling, AppStatus: &app.Status}, nil
return Status{AddonPhase: enabling, AppStatus: &app.Status, InstalledVersion: app.GetLabels()[oam.LabelAddonVersion], Clusters: clusters}, nil
}
}
@@ -221,5 +243,6 @@ type Status struct {
AddonPhase string
AppStatus *commontypes.AppStatus
// the status of multiple clusters
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
Clusters map[string]map[string]interface{} `json:"clusters,omitempty"`
InstalledVersion string
}

View File

@@ -0,0 +1,71 @@
/*
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 (
"testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v3/pkg/chart/loader"
)
var files = []*loader.BufferedFile{
{
Name: "metadata.yaml",
Data: []byte(`name: test-helm-addon
version: 1.0.0
description: This is a addon for test when install addon from helm repo
icon: https://www.terraform.io/assets/images/logo-text-8c3ba8a6.svg
url: https://terraform.io/
tags: []
deployTo:
control_plane: true
runtime_cluster: false
dependencies: []
invisible: false`),
},
{
Name: "/resources/parameter.cue",
Data: []byte(`parameter: {
// test wrong parameter
example: *"default"
}`),
},
}
func TestMemoryReader(t *testing.T) {
m := MemoryReader{
Name: "fluxcd",
Files: files,
}
meta, err := m.ListAddonMeta()
assert.NoError(t, err)
assert.Equal(t, len(meta["fluxcd"].Items), 2)
metaFile, err := m.ReadFile("metadata.yaml")
assert.NoError(t, err)
assert.NotEmpty(t, metaFile)
paramterData, err := m.ReadFile("/resources/parameter.cue")
assert.NoError(t, err)
assert.NotEmpty(t, paramterData)
}

View File

@@ -96,5 +96,4 @@ func TestGiteeReader(t *testing.T) {
_, err := r.ReadFile("example/metadata.yaml")
assert.NoError(t, err)
testReaderFunc(t, r)
}

View File

@@ -108,7 +108,6 @@ func TestGitHubReader(t *testing.T) {
_, err := r.ReadFile("example/metadata.yaml")
assert.NoError(t, err)
testReaderFunc(t, r)
}
// Int is a helper routine that allocates a new int value

View File

@@ -0,0 +1,60 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package addon
import (
"path/filepath"
"strings"
"helm.sh/helm/v3/pkg/chart/loader"
)
// MemoryReader is async reader for memory data
type MemoryReader struct {
Name string
Files []*loader.BufferedFile
fileData map[string]string
}
// ListAddonMeta list all metadata of helm repo registry
func (l *MemoryReader) ListAddonMeta() (map[string]SourceMeta, error) {
metas := SourceMeta{Name: l.Name}
for _, f := range l.Files {
metas.Items = append(metas.Items, OSSItem{tp: "file", name: f.Name})
if l.fileData == nil {
l.fileData = make(map[string]string)
}
l.fileData[f.Name] = string(f.Data)
}
return map[string]SourceMeta{l.Name: metas}, nil
}
// ReadFile ready file from memory
func (l *MemoryReader) ReadFile(path string) (string, error) {
if file, ok := l.fileData[path]; ok {
return file, nil
}
return l.fileData[strings.TrimPrefix(path, l.Name+"/")], nil
}
// RelativePath calculate the relative path of one file
func (l *MemoryReader) RelativePath(item Item) string {
if strings.HasPrefix(item.GetName(), l.Name) {
return item.GetName()
}
return filepath.Join(l.Name, item.GetName())
}

View File

@@ -37,6 +37,7 @@ 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"`

View File

@@ -63,6 +63,11 @@ type GiteeAddonSource struct {
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"`
}
// Item is a partial interface for github.RepositoryContent
type Item interface {
// GetType return "dir" or "file"

View File

@@ -22,6 +22,9 @@ import (
"time"
"k8s.io/client-go/discovery"
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
ocmclusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1"
ocmworkv1 "open-cluster-management.io/api/work/v1"
v12 "k8s.io/api/core/v1"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -79,6 +82,9 @@ var _ = BeforeSuite(func(done Done) {
Expect(coreoam.AddToScheme(scheme)).NotTo(HaveOccurred())
Expect(clientgoscheme.AddToScheme(scheme)).NotTo(HaveOccurred())
Expect(crdv1.AddToScheme(scheme)).NotTo(HaveOccurred())
_ = ocmclusterv1alpha1.Install(scheme)
_ = ocmclusterv1.Install(scheme)
_ = ocmworkv1.Install(scheme)
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())

Binary file not shown.

15
pkg/addon/testdata/helm-repo/index.yaml vendored Normal file
View File

@@ -0,0 +1,15 @@
apiVersion: v1
entries:
fluxcd:
- created: "2022-03-25T21:04:51.244331+08:00"
description: Extended workload to do continuous and progressive delivery
home: https://fluxcd.io
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
keywords:
- extended_workload
- gitops
name: fluxcd
urls:
- http://127.0.0.1:18083/fluxcd-1.0.0.tgz
version: 1.0.0
generated: "0001-01-01T00:00:00Z"

View File

@@ -0,0 +1,9 @@
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: my-comp
namespace: vela-system
spec:
schematic:
cue:
template:

View File

@@ -0,0 +1,19 @@
"my-trait": {
type: "trait"
annotations: {}
description: "Rollout the component."
attributes: {
manageWorkload: true
status: {
customStatus: #"""
message: context.outputs.rollout.status.rollingState
"""#
healthPolicy: #"""
isHealth: context.outputs.rollout.status.batchRollingState == "batchReady"
"""#
}
}
}
template: {
outputs: rollout: {}
}

View File

@@ -0,0 +1,13 @@
name: test-disable-addon
version: 1.0.0
tags:
- only_test
deployTo:
control_plane: true
runtime_cluster: false
dependencies: []
invisible: false

View File

@@ -37,19 +37,37 @@ type UIData struct {
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
Parameters string `json:"parameters"`
RegistryName string `json:"registryName"`
AvailableVersions []string `json:"availableVersions"`
}
// InstallPackage contains all necessary files that can be installed for an addon
type InstallPackage struct {
Meta
Definitions []ElementFile `json:"definitions"`
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
Parameters string `json:"parameters"`
CUETemplates []ElementFile `json:"CUETemplates"`
YAMLTemplates []ElementFile `json:"YAMLTemplates,omitempty"`
DefSchemas []ElementFile `json:"def_schemas,omitempty"`
AppTemplate *v1beta1.Application `json:"appTemplate"`
// Definitions and CUEDefinitions are converted as OAM X-Definitions, they will only in control plane cluster
Definitions []ElementFile `json:"definitions"`
CUEDefinitions []ElementFile `json:"CUEDefinitions"`
// DefSchemas are UI schemas read by VelaUX, it will only be installed in control plane clusters
DefSchemas []ElementFile `json:"defSchemas,omitempty"`
Parameters string `json:"parameters"`
// CUETemplates and YAMLTemplates are resources needed to be installed in managed clusters
CUETemplates []ElementFile `json:"CUETemplates"`
YAMLTemplates []ElementFile `json:"YAMLTemplates,omitempty"`
AppTemplate *v1beta1.Application `json:"appTemplate"`
}
// WholeAddonPackage contains all infos of an addon
type WholeAddonPackage struct {
InstallPackage
APISchema *openapi3.Schema `json:"schema"`
// Detail is README.md in an addon
Detail string `json:"detail,omitempty"`
AvailableVersions []string `json:"availableVersions"`
}
// Meta defines the format for a single addon

226
pkg/addon/utils.go Normal file
View File

@@ -0,0 +1,226 @@
/*
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 (
"context"
"fmt"
"strings"
errors "github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
rest "k8s.io/client-go/rest"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/definition"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
const (
compDefAnnotation = "addon.oam.dev/componentDefinitions"
traitDefAnnotation = "addon.oam.dev/traitDefinitions"
workflowStepDefAnnotation = "addon.oam.dev/workflowStepDefinitions"
defKeytemplate = "addon-%s-%s"
)
// parse addon's created x-defs in addon-app's annotation, this will be used to check whether app still using it while disabling.
func passDefInAppAnnotation(defs []*unstructured.Unstructured, app *v1beta1.Application) error {
var comps, traits, workflowSteps []string
for _, def := range defs {
switch def.GetObjectKind().GroupVersionKind().Kind {
case v1beta1.ComponentDefinitionKind:
comps = append(comps, def.GetName())
case v1beta1.TraitDefinitionKind:
traits = append(traits, def.GetName())
case v1beta1.WorkflowStepDefinitionKind:
workflowSteps = append(workflowSteps, def.GetName())
default:
return fmt.Errorf("cannot handle definition types %s, name %s", def.GetObjectKind().GroupVersionKind().Kind, def.GetName())
}
}
if len(comps) != 0 {
app.SetAnnotations(util.MergeMapOverrideWithDst(app.GetAnnotations(), map[string]string{compDefAnnotation: strings.Join(comps, ",")}))
}
if len(traits) != 0 {
app.SetAnnotations(util.MergeMapOverrideWithDst(app.GetAnnotations(), map[string]string{traitDefAnnotation: strings.Join(traits, ",")}))
}
if len(workflowSteps) != 0 {
app.SetAnnotations(util.MergeMapOverrideWithDst(app.GetAnnotations(), map[string]string{workflowStepDefAnnotation: strings.Join(workflowSteps, ",")}))
}
return nil
}
// check whether this addon has been used by some applications
func checkAddonHasBeenUsed(ctx context.Context, k8sClient client.Client, name string, addonApp v1beta1.Application, config *rest.Config) ([]v1beta1.Application, error) {
apps := v1beta1.ApplicationList{}
if err := k8sClient.List(ctx, &apps, client.InNamespace("")); err != nil {
return nil, err
}
if len(apps.Items) == 0 {
return nil, nil
}
createdDefs := make(map[string]bool)
for key, defNames := range addonApp.GetAnnotations() {
switch key {
case compDefAnnotation, traitDefAnnotation, workflowStepDefAnnotation:
merge2DefMap(key, defNames, createdDefs)
}
}
if len(createdDefs) == 0 {
if err := findLegacyAddonDefs(ctx, k8sClient, name, addonApp.GetLabels()[oam.LabelAddonRegistry], config, createdDefs); err != nil {
return nil, err
}
}
var res []v1beta1.Application
CHECKNEXT:
for _, app := range apps.Items {
for _, component := range app.Spec.Components {
if createdDefs[fmt.Sprintf(defKeytemplate, "comp", component.Type)] {
res = append(res, app)
// this app has used this addon, there is no need check other components
continue CHECKNEXT
}
for _, trait := range component.Traits {
if createdDefs[fmt.Sprintf(defKeytemplate, "trait", trait.Type)] {
res = append(res, app)
continue CHECKNEXT
}
}
}
if app.Spec.Workflow == nil || len(app.Spec.Workflow.Steps) == 0 {
return res, nil
}
for _, s := range app.Spec.Workflow.Steps {
if createdDefs[fmt.Sprintf(defKeytemplate, "wfStep", s.Type)] {
res = append(res, app)
continue CHECKNEXT
}
}
}
return res, nil
}
// merge2DefMap will parse annotation in addon's app to 'created x-definition'. Then stroe them in defMap
func merge2DefMap(defType string, defNames string, defMap map[string]bool) {
list := strings.Split(defNames, ",")
template := "addon-%s-%s"
for _, defName := range list {
switch defType {
case compDefAnnotation:
defMap[fmt.Sprintf(template, "comp", defName)] = true
case traitDefAnnotation:
defMap[fmt.Sprintf(template, "trait", defName)] = true
case workflowStepDefAnnotation:
defMap[fmt.Sprintf(template, "wfStep", defName)] = true
}
}
}
// for old addon's app no 'created x-definitions' annotation, fetch the definitions from alive addon registry. Put them in defMap
func findLegacyAddonDefs(ctx context.Context, k8sClient client.Client, addonName string, registryName string, config *rest.Config, defs map[string]bool) error {
// if the addon enable by local we cannot fetch the source definitions yet, so skip the check
if registryName == "local" {
return nil
}
registryDS := NewRegistryDataStore(k8sClient)
registries, err := registryDS.ListRegistries(ctx)
if err != nil {
return err
}
var defObjects []*unstructured.Unstructured
for i, registry := range registries {
if registry.Name == registryName {
var uiData *UIData
if !IsVersionRegistry(registry) {
installer := NewAddonInstaller(ctx, k8sClient, nil, nil, config, &registries[i], nil, nil)
metas, err := installer.getAddonMeta()
if err != nil {
return err
}
meta := metas[addonName]
// only fetch definition files from registry.
uiData, err = registry.GetUIData(&meta, UnInstallOptions)
if err != nil {
return errors.Wrapf(err, "cannot fetch addon difinition files from registry")
}
} else {
versionedRegistry := BuildVersionedRegistry(registry.Name, registry.Helm.URL)
uiData, err = versionedRegistry.GetAddonUIData(ctx, addonName, "")
if err != nil {
return errors.Wrapf(err, "cannot fetch addon difinition files from registry")
}
}
for _, defYaml := range uiData.Definitions {
def, err := renderObject(defYaml)
if err != nil {
// don't let one error defined definition block whole disable process
continue
}
defObjects = append(defObjects, def)
}
for _, cueDef := range uiData.CUEDefinitions {
def := definition.Definition{Unstructured: unstructured.Unstructured{}}
err := def.FromCUEString(cueDef.Data, config)
if err != nil {
// don't let one error defined cue definition block whole disable process
continue
}
defObjects = append(defObjects, &def.Unstructured)
}
}
}
for _, defObject := range defObjects {
switch defObject.GetObjectKind().GroupVersionKind().Kind {
case v1beta1.ComponentDefinitionKind:
defs[fmt.Sprintf(defKeytemplate, "comp", defObject.GetName())] = true
case v1beta1.TraitDefinitionKind:
defs[fmt.Sprintf(defKeytemplate, "trait", defObject.GetName())] = true
case v1beta1.WorkflowStepDefinitionKind:
defs[fmt.Sprintf(defKeytemplate, "wfStep", defObject.GetName())] = true
}
}
return nil
}
func usingAppsInfo(apps []v1beta1.Application) string {
res := "addon is being used :"
appsNamespaceNameList := map[string][]string{}
for _, app := range apps {
appsNamespaceNameList[app.GetNamespace()] = append(appsNamespaceNameList[app.GetNamespace()], app.GetName())
}
for namespace, appNames := range appsNamespaceNameList {
nameStr := strings.Join(appNames, ",")
res += fmt.Sprintf("{%s} in namespace:%s,", nameStr, namespace)
}
res = strings.TrimSuffix(res, ",") + ".Please delete them before disabling the addon."
return res
}
// IsVersionRegistry check the repo source if support multi-version addon
func IsVersionRegistry(r Registry) bool {
return r.Helm != nil
}

258
pkg/addon/utils_test.go Normal file
View File

@@ -0,0 +1,258 @@
/*
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 (
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"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/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var _ = Describe("Test definition check", func() {
var compDef v1beta1.ComponentDefinition
var traitDef v1beta1.TraitDefinition
var wfStepDef v1beta1.WorkflowStepDefinition
BeforeEach(func() {
compDef = v1beta1.ComponentDefinition{}
traitDef = v1beta1.TraitDefinition{}
wfStepDef = v1beta1.WorkflowStepDefinition{}
Expect(yaml.Unmarshal([]byte(compDefYaml), &compDef)).Should(BeNil())
Expect(k8sClient.Create(ctx, &compDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(yaml.Unmarshal([]byte(traitDefYaml), &traitDef)).Should(BeNil())
Expect(k8sClient.Create(ctx, &traitDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(yaml.Unmarshal([]byte(wfStepDefYaml), &wfStepDef)).Should(BeNil())
Expect(k8sClient.Create(ctx, &wfStepDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
})
It("Test pass def to app annotation", func() {
c := v1beta1.ComponentDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "ComponentDefinition"}}
c.SetName("my-comp")
t := v1beta1.TraitDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "TraitDefinition"}}
t.SetName("my-trait")
w := v1beta1.WorkflowStepDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "WorkflowStepDefinition"}}
w.SetName("my-wfstep")
var defs []*unstructured.Unstructured
cDef, err := util.Object2Unstructured(c)
Expect(err).Should(BeNil())
defs = append(defs, cDef)
tDef, err := util.Object2Unstructured(t)
defs = append(defs, tDef)
Expect(err).Should(BeNil())
wDef, err := util.Object2Unstructured(w)
Expect(err).Should(BeNil())
defs = append(defs, wDef)
addonApp := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "addon-app", Namespace: velatypes.DefaultKubeVelaNS}}
err = passDefInAppAnnotation(defs, &addonApp)
Expect(err).Should(BeNil())
anno := addonApp.GetAnnotations()
Expect(len(anno)).Should(BeEquivalentTo(3))
Expect(anno[compDefAnnotation]).Should(BeEquivalentTo("my-comp"))
Expect(anno[traitDefAnnotation]).Should(BeEquivalentTo("my-trait"))
Expect(anno[workflowStepDefAnnotation]).Should(BeEquivalentTo("my-wfstep"))
})
It("Test checkAddonHasBeenUsed func", func() {
addonApp := v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(addonAppYaml), &addonApp)).Should(BeNil())
app1 := v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(testApp1Yaml), &app1)).Should(BeNil())
Expect(k8sClient.Create(ctx, &app1)).Should(BeNil())
app2 := v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(testApp2Yaml), &app2)).Should(BeNil())
Expect(k8sClient.Create(ctx, &app2)).Should(BeNil())
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-ns"}}))
app3 := v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(testApp3Yaml), &app3)).Should(BeNil())
Expect(k8sClient.Create(ctx, &app3)).Should(BeNil())
usedApps, err := checkAddonHasBeenUsed(ctx, k8sClient, "my-addon", addonApp, cfg)
Expect(err).Should(BeNil())
Expect(len(usedApps)).Should(BeEquivalentTo(3))
})
It("check fetch lagacy addon definitions", func() {
res := make(map[string]bool)
server := httptest.NewServer(ossHandler)
defer server.Close()
url := server.URL
cmYaml := strings.ReplaceAll(registryCmYaml, "TEST_SERVER_URL", url)
cm := v1.ConfigMap{}
Expect(yaml.Unmarshal([]byte(cmYaml), &cm)).Should(BeNil())
Expect(k8sClient.Create(ctx, &cm)).Should(BeNil())
disableTestAddonApp := v1beta1.Application{}
Expect(yaml.Unmarshal([]byte(addonDisableTestAppYaml), &disableTestAddonApp)).Should(BeNil())
Expect(findLegacyAddonDefs(ctx, k8sClient, "test-disable-addon", disableTestAddonApp.GetLabels()[oam.LabelAddonRegistry], cfg, res)).Should(BeNil())
Expect(len(res)).Should(BeEquivalentTo(2))
})
})
func TestMerge2Map(t *testing.T) {
res := make(map[string]bool)
merge2DefMap(compDefAnnotation, "my-comp1,my-comp2", res)
merge2DefMap(traitDefAnnotation, "my-trait1,my-trait2", res)
merge2DefMap(workflowStepDefAnnotation, "my-wfStep1,my-wfStep2", res)
assert.Equal(t, 6, len(res))
}
func TestUsingAddonInfo(t *testing.T) {
apps := []v1beta1.Application{
v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-1"}},
v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-2", Name: "app-2"}},
v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-3"}},
}
res := usingAppsInfo(apps)
assert.Equal(t, true, strings.Contains(res, "Please delete them before disabling the addon"))
}
const (
compDefYaml = `
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: my-comp
namespace: vela-system
`
traitDefYaml = `
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
name: my-trait
namespace: vela-system
`
wfStepDefYaml = `
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
name: my-wfstep
namespace: vela-system
`
)
const (
addonAppYaml = `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
labels:
addons.oam.dev/name: myaddon
addons.oam.dev/registry: KubeVela
annotations:
addon.oam.dev/componentDefinitions: "my-comp"
addon.oam.dev/traitDefinitions: "my-trait"
addon.oam.dev/workflowStepDefinitions: "my-wfstep"
name: addon-myaddon
namespace: vela-system
spec:
`
testApp1Yaml = `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
labels:
name: app-1
namespace: default
spec:
components:
- name: comp1
type: my-comp
traits:
- type: my-trait
`
testApp2Yaml = `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
labels:
name: app-2
namespace: default
spec:
components:
- name: comp2
type: webservice
traits:
- type: my-trait
`
testApp3Yaml = `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-3
namespace: test-ns
spec:
components:
- name: podinfo
type: webservice
workflow:
steps:
- type: my-wfstep
name: deploy
`
registryCmYaml = `
apiVersion: v1
data:
registries: '{ "KubeVela":{ "name": "KubeVela", "oss": { "end_point": "TEST_SERVER_URL",
"bucket": "", "path": "" } } }'
kind: ConfigMap
metadata:
name: vela-addon-registry
namespace: vela-system
`
addonDisableTestAppYaml = `
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: addon-test-disable-addon
namespace: vela-system
labels:
addons.oam.dev/name: test-disable-addon
addons.oam.dev/registry: KubeVela
spec:
components:
- name: podinfo
type: webservice
`
)

View File

@@ -0,0 +1,172 @@
/*
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 (
"bytes"
"context"
"fmt"
"sort"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/repo"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/helm"
)
// VersionedRegistry is the interface of support version registry
type VersionedRegistry interface {
ListAddon() ([]*UIData, error)
GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error)
GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error)
}
// BuildVersionedRegistry is build versioned addon registry
func BuildVersionedRegistry(name, repoURL string) VersionedRegistry {
return &versionedRegistry{
name: name,
url: repoURL,
h: helm.NewHelperWithCache(),
}
}
type versionedRegistry struct {
url string
name string
h *helm.Helper
}
func (i *versionedRegistry) ListAddon() ([]*UIData, error) {
chartIndex, err := i.h.GetIndexInfo(i.url, false)
if err != nil {
return nil, err
}
return i.resolveAddonListFromIndex(i.name, chartIndex), nil
}
func (i *versionedRegistry) GetAddonUIData(ctx context.Context, addonName, version string) (*UIData, error) {
wholePackage, err := i.loadAddon(ctx, addonName, version)
if err != nil {
return nil, err
}
return &UIData{
Meta: wholePackage.Meta,
APISchema: wholePackage.APISchema,
Parameters: wholePackage.Parameters,
Detail: wholePackage.Detail,
Definitions: wholePackage.Definitions,
AvailableVersions: wholePackage.AvailableVersions,
}, nil
}
func (i *versionedRegistry) GetAddonInstallPackage(ctx context.Context, addonName, version string) (*InstallPackage, error) {
wholePackage, err := i.loadAddon(ctx, addonName, version)
if err != nil {
return nil, err
}
return &wholePackage.InstallPackage, nil
}
func (i *versionedRegistry) resolveAddonListFromIndex(repoName string, index *repo.IndexFile) []*UIData {
var res []*UIData
for addonName, versions := range index.Entries {
if len(versions) == 0 {
continue
}
sort.Sort(sort.Reverse(versions))
latestVersion := versions[0]
var availableVersions []string
for _, version := range versions {
availableVersions = append(availableVersions, version.Version)
}
o := UIData{Meta: Meta{
Name: addonName,
Icon: latestVersion.Icon,
Tags: latestVersion.Keywords,
Description: latestVersion.Description,
Version: latestVersion.Version,
}, RegistryName: repoName, AvailableVersions: availableVersions}
res = append(res, &o)
}
return res
}
func (i versionedRegistry) loadAddon(ctx context.Context, name, version string) (*WholeAddonPackage, error) {
versions, err := i.h.ListVersions(i.url, name, false)
if err != nil {
return nil, err
}
if len(versions) == 0 {
return nil, ErrNotExist
}
var addonVersion *repo.ChartVersion
sort.Sort(sort.Reverse(versions))
if len(version) == 0 {
// if not specify version will always use the latest version
addonVersion = versions[0]
}
var availableVersions []string
for i, v := range versions {
availableVersions = append(availableVersions, v.Version)
if v.Version == version {
addonVersion = versions[i]
}
}
if addonVersion == nil {
return nil, fmt.Errorf("specified version %s not exist", version)
}
for _, chartURL := range addonVersion.URLs {
archive, err := common.HTTPGet(ctx, chartURL)
if err != nil {
continue
}
bufferedFile, err := loader.LoadArchiveFiles(bytes.NewReader(archive))
if err != nil {
continue
}
addonPkg, err := loadAddonPackage(name, bufferedFile)
if err != nil {
return nil, err
}
addonPkg.AvailableVersions = availableVersions
return addonPkg, nil
}
return nil, fmt.Errorf("cannot fetch addon package")
}
func loadAddonPackage(addonName string, files []*loader.BufferedFile) (*WholeAddonPackage, error) {
mr := MemoryReader{Name: addonName, Files: files}
metas, err := mr.ListAddonMeta()
if err != nil {
return nil, err
}
meta := metas[addonName]
addonUIData, err := GetUIDataFromReader(&mr, &meta, UIMetaOptions)
if err != nil {
return nil, err
}
installPackage, err := GetInstallPackageFromReader(&mr, &meta, addonUIData)
if err != nil {
return nil, err
}
return &WholeAddonPackage{
InstallPackage: *installPackage,
Detail: addonUIData.Detail,
APISchema: addonUIData.APISchema,
}, nil
}

View File

@@ -0,0 +1,76 @@
/*
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 (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestVersionRegistry(t *testing.T) {
go func() {
http.HandleFunc("/", versionedHandler)
err := http.ListenAndServe(fmt.Sprintf(":%d", 18083), nil)
if err != nil {
log.Fatal("Setup server error:", err)
}
}()
// wait server setup
time.Sleep(3 * time.Second)
r := BuildVersionedRegistry("helm-repo", "http://127.0.0.1:18083")
addons, err := r.ListAddon()
assert.NoError(t, err)
assert.Equal(t, len(addons), 1)
assert.Equal(t, addons[0].Name, "fluxcd")
assert.Equal(t, len(addons[0].AvailableVersions), 1)
addonUIData, err := r.GetAddonUIData(context.Background(), "fluxcd", "1.0.0")
assert.NoError(t, err)
assert.NotEmpty(t, addonUIData.Definitions)
assert.NotEmpty(t, addonUIData.Icon)
addonsInstallPackage, err := r.GetAddonInstallPackage(context.Background(), "fluxcd", "1.0.0")
assert.NoError(t, err)
assert.NotEmpty(t, addonsInstallPackage)
assert.NotEmpty(t, addonsInstallPackage.YAMLTemplates)
assert.NotEmpty(t, addonsInstallPackage.DefSchemas)
}
var versionedHandler http.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) {
switch {
case strings.Contains(request.URL.Path, "index.yaml"):
files, err := ioutil.ReadFile("./testdata/helm-repo/index.yaml")
if err != nil {
_, _ = writer.Write([]byte(err.Error()))
}
writer.Write(files)
case strings.Contains(request.URL.Path, "fluxcd-1.0.0.tgz"):
files, err := ioutil.ReadFile("./testdata/helm-repo/fluxcd-1.0.0.tgz")
if err != nil {
_, _ = writer.Write([]byte(err.Error()))
}
writer.Write(files)
}
}

View File

@@ -17,6 +17,8 @@ limitations under the License.
package clients
import (
"errors"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -25,6 +27,7 @@ import (
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
var kubeClient client.Client
@@ -42,6 +45,18 @@ func GetKubeClient() (client.Client, error) {
}
var err error
kubeClient, kubeConfig, err = multicluster.GetMulticlusterKubernetesClient()
if err == nil {
return kubeClient, nil
}
if !errors.Is(err, multicluster.ErrDetectClusterGateway) {
return nil, err
}
// create single cluster client
conf, err := config.GetConfig()
if err != nil {
return nil, err
}
kubeClient, err = client.New(conf, client.Options{Scheme: common.Scheme})
if err != nil {
return nil, err
}

View File

@@ -73,6 +73,7 @@ type Entity interface {
SetUpdateTime(time time.Time)
PrimaryKey() string
TableName() string
ShortTableName() string
Index() map[string]string
}
@@ -111,9 +112,22 @@ type FuzzyQueryOption struct {
Query string
}
// InQueryOption defines the include search filter option
type InQueryOption struct {
Key string
Values []string
}
// IsNotExistQueryOption means the value is empty
type IsNotExistQueryOption struct {
Key string
}
// FilterOptions filter query returned items
type FilterOptions struct {
Queries []FuzzyQueryOption
Queries []FuzzyQueryOption
In []InQueryOption
IsNotExist []IsNotExistQueryOption
}
// ListOptions list api options
@@ -141,7 +155,7 @@ type DataStore interface {
// Get entity from database, Name() and TableName() can't return zero value.
Get(ctx context.Context, entity Entity) error
// List entities from database, TableName() can't return zero value.
// List entities from database, TableName() can't return zero value, if no matches, it will return a zero list without error.
List(ctx context.Context, query Entity, options *ListOptions) ([]Entity, error)
// Count entities from database, TableName() can't return zero value.

View File

@@ -64,6 +64,7 @@ func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error)
return nil, fmt.Errorf("create namespace failure %w", err)
}
}
migrate(cfg.Database)
return &kubeapi{
kubeclient: kubeClient,
namespace: cfg.Database,
@@ -71,7 +72,9 @@ func New(ctx context.Context, cfg datastore.Config) (datastore.DataStore, error)
}
func generateName(entity datastore.Entity) string {
name := fmt.Sprintf("veladatabase-%s-%s", entity.TableName(), entity.PrimaryKey())
// record the old ways here, it'll be migrated
// name := fmt.Sprintf("veladatabase-%s-%s", entity.TableName(), entity.PrimaryKey())
name := fmt.Sprintf("%s-%s", entity.ShortTableName(), entity.PrimaryKey())
return strings.ReplaceAll(name, "_", "-")
}
@@ -117,23 +120,23 @@ func (m *kubeapi) Add(ctx context.Context, entity datastore.Entity) error {
}
// BatchAdd batch add entity, this operation has some atomicity.
func (m *kubeapi) BatchAdd(ctx context.Context, entitys []datastore.Entity) error {
donotRollback := make(map[string]int)
for i, saveEntity := range entitys {
func (m *kubeapi) BatchAdd(ctx context.Context, entities []datastore.Entity) error {
notRollback := make(map[string]int)
for i, saveEntity := range entities {
if err := m.Add(ctx, saveEntity); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
donotRollback[saveEntity.PrimaryKey()] = 1
notRollback[saveEntity.PrimaryKey()] = 1
}
for _, deleteEntity := range entitys[:i] {
if _, exit := donotRollback[deleteEntity.PrimaryKey()]; !exit {
for _, deleteEntity := range entities[:i] {
if _, exit := notRollback[deleteEntity.PrimaryKey()]; !exit {
if err := m.Delete(ctx, deleteEntity); err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
log.Logger.Errorf("rollback delete component failure %w", err)
log.Logger.Errorf("rollback delete entity failure %w", err)
}
}
}
}
return datastore.NewDBError(fmt.Errorf("save components occur error, %w", err))
return datastore.NewDBError(fmt.Errorf("save entities occur error, %w", err))
}
}
return nil
@@ -327,7 +330,7 @@ func _filterConfigMapByFuzzyQueryOptions(items []corev1.ConfigMap, queries []dat
return _items
}
// TableName() can't return zero value.
// List will list all database records by select labels according to table name
func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datastore.ListOptions) ([]datastore.Entity, error) {
if entity.TableName() == "" {
return nil, datastore.ErrTableNameEmpty
@@ -337,6 +340,10 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
if err != nil {
return nil, datastore.NewDBError(err)
}
rq, _ := labels.NewRequirement(MigrateKey, selection.DoesNotExist, []string{"ok"})
selector = selector.Add(*rq)
for k, v := range entity.Index() {
rq, err := labels.NewRequirement(k, selection.Equals, []string{v})
if err != nil {
@@ -344,6 +351,24 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
}
selector = selector.Add(*rq)
}
if op != nil {
for _, inFilter := range op.In {
rq, err := labels.NewRequirement(inFilter.Key, selection.In, inFilter.Values)
if err != nil {
log.Logger.Errorf("new list requirement failure %s", err.Error())
return nil, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
for _, notFilter := range op.IsNotExist {
rq, err := labels.NewRequirement(notFilter.Key, selection.DoesNotExist, []string{})
if err != nil {
log.Logger.Errorf("new list requirement failure %s", err.Error())
return nil, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
}
options := &client.ListOptions{
LabelSelector: selector,
Namespace: m.namespace,
@@ -382,7 +407,6 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
items = items[:limit]
}
var list []datastore.Entity
log.Logger.Debugf("query %s result count %d", selector, len(items))
for _, item := range items {
ent, err := datastore.NewEntity(entity)
if err != nil {
@@ -413,6 +437,24 @@ func (m *kubeapi) Count(ctx context.Context, entity datastore.Entity, filterOpti
}
selector = selector.Add(*rq)
}
if filterOptions != nil {
for _, inFilter := range filterOptions.In {
rq, err := labels.NewRequirement(inFilter.Key, selection.In, inFilter.Values)
if err != nil {
return 0, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
for _, notFilter := range filterOptions.IsNotExist {
rq, err := labels.NewRequirement(notFilter.Key, selection.DoesNotExist, []string{})
if err != nil {
log.Logger.Errorf("new list requirement failure %s", err.Error())
return 0, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
}
options := &client.ListOptions{
LabelSelector: selector,
Namespace: m.namespace,

View File

@@ -96,7 +96,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
&model.Application{Name: "kubevela-app-4", Project: "testProject", Description: "this is demo 4"},
}
err := kubeStore.BatchAdd(context.TODO(), datas)
Expect(err).ToNot(HaveOccurred())
@@ -106,7 +106,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
}
err = kubeStore.BatchAdd(context.TODO(), datas2)
equal := cmp.Diff(strings.Contains(err.Error(), "save components occur error"), true)
equal := cmp.Diff(strings.Contains(err.Error(), "save entities occur error"), true)
Expect(equal).To(BeEmpty())
})
@@ -157,6 +157,26 @@ var _ = Describe("Test kubeapi datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 4)
Expect(diff).Should(BeEmpty())
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 2)
Expect(diff).Should(BeEmpty())
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 3)
Expect(diff).Should(BeEmpty())
})
It("Test list clusters with sort and fuzzy query", func() {
@@ -221,6 +241,23 @@ var _ = Describe("Test kubeapi datastore driver", func() {
})
Expect(err).Should(Succeed())
Expect(count).Should(Equal(int64(2)))
count, err = kubeStore.Count(context.TODO(), &app, &datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(2)))
count, err = kubeStore.Count(context.TODO(), &app, &datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(3)))
})
It("Test isExist function", func() {

View File

@@ -0,0 +1,88 @@
/*
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 kubeapi
import (
"context"
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
)
// MigrateKey marks the label key of the migrated data
const MigrateKey = "db.oam.dev/migrated"
// migrate will migrate the configmap to new short table name, it won't delete the configmaps:
// users can delete by the following commands:
// kubectl -n kubevela delete cm -l db.oam.dev/migrated=ok
func migrate(dbns string) {
kubeClient, err := clients.GetKubeClient()
if err != nil {
panic(err)
}
models := model.GetRegisterModels()
for _, k := range models {
var configMaps corev1.ConfigMapList
table := k.TableName()
selector, _ := labels.Parse(fmt.Sprintf("table=%s", table))
if err = kubeClient.List(context.Background(), &configMaps, &client.ListOptions{Namespace: dbns, LabelSelector: selector}); err != nil {
err = client.IgnoreNotFound(err)
if err != nil {
klog.Errorf("migrate db for kubeapi storage err: %v", err)
continue
}
}
var migrated bool
for _, cm := range configMaps.Items {
if strings.HasPrefix(cm.Name, strings.ReplaceAll(k.ShortTableName()+"-", "_", "-")) {
migrated = true
break
}
}
if migrated || len(configMaps.Items) == 0 {
continue
}
klog.Infof("migrating data for table %v", k.TableName())
for _, cm := range configMaps.Items {
cm := cm
checkprefix := strings.ReplaceAll(fmt.Sprintf("veladatabase-%s", k.TableName()), "_", "-")
if !strings.HasPrefix(cm.Name, checkprefix) {
continue
}
cm.Labels[MigrateKey] = "ok"
err = kubeClient.Update(context.Background(), &cm)
if err != nil {
klog.Errorf("update migrated record %s for kubeapi storage err: %v", cm.Name, err)
}
cm.Name = strings.ReplaceAll(k.ShortTableName()+strings.TrimPrefix(cm.Name, checkprefix), "_", "-")
cm.ResourceVersion = ""
delete(cm.Labels, MigrateKey)
err = kubeClient.Create(context.Background(), &cm)
if err != nil {
klog.Errorf("migrate record %s for kubeapi storage err: %v", cm.Name, err)
}
}
}
}

View File

@@ -0,0 +1,60 @@
/*
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 kubeapi
import (
"context"
"fmt"
"strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
)
var _ = Describe("Test Migrate", func() {
It("Test migrate labels", func() {
clients.SetKubeClient(k8sClient)
nsName := "test-migrate"
ds := &kubeapi{kubeclient: k8sClient, namespace: nsName}
ns := &v1.Namespace{}
ns.Name = nsName
Expect(k8sClient.Create(context.Background(), ns)).Should(BeNil())
entity := &model.Application{Name: "my-app"}
cm := ds.generateConfigMap(entity)
name := fmt.Sprintf("veladatabase-%s-%s", entity.TableName(), entity.PrimaryKey())
cm.Name = strings.ReplaceAll(name, "_", "-")
cm.Namespace = nsName
Expect(ds.kubeclient.Create(context.Background(), cm)).Should(BeNil())
migrate(nsName)
cmList := v1.ConfigMapList{}
Expect(k8sClient.List(context.Background(), &cmList, client.InNamespace(nsName))).Should(BeNil())
Expect(len(cmList.Items)).Should(BeEquivalentTo(2))
es, err := ds.List(context.Background(), &model.Application{}, nil)
Expect(err).Should(BeNil())
Expect(len(es)).Should(BeEquivalentTo(1))
})
})

View File

@@ -84,23 +84,23 @@ func (m *mongodb) Add(ctx context.Context, entity datastore.Entity) error {
}
// BatchAdd batch add entity, this operation has some atomicity.
func (m *mongodb) BatchAdd(ctx context.Context, entitys []datastore.Entity) error {
donotRollback := make(map[string]int)
for i, saveEntity := range entitys {
func (m *mongodb) BatchAdd(ctx context.Context, entities []datastore.Entity) error {
notRollback := make(map[string]int)
for i, saveEntity := range entities {
if err := m.Add(ctx, saveEntity); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
donotRollback[saveEntity.PrimaryKey()] = 1
notRollback[saveEntity.PrimaryKey()] = 1
}
for _, deleteEntity := range entitys[:i] {
if _, exit := donotRollback[deleteEntity.PrimaryKey()]; !exit {
for _, deleteEntity := range entities[:i] {
if _, exit := notRollback[deleteEntity.PrimaryKey()]; !exit {
if err := m.Delete(ctx, deleteEntity); err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
log.Logger.Errorf("rollback delete component failure %w", err)
log.Logger.Errorf("rollback delete entity failure %w", err)
}
}
}
}
return datastore.NewDBError(fmt.Errorf("save components occur error, %w", err))
return datastore.NewDBError(fmt.Errorf("save entities occur error, %w", err))
}
}
return nil
@@ -192,10 +192,14 @@ func (m *mongodb) Delete(ctx context.Context, entity datastore.Entity) error {
}
func _applyFilterOptions(filter bson.D, filterOptions datastore.FilterOptions) bson.D {
if len(filterOptions.Queries) > 0 {
for _, queryOp := range filterOptions.Queries {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bsonx.Regex(".*"+queryOp.Query+".*", "s")})
}
for _, queryOp := range filterOptions.Queries {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bsonx.Regex(".*"+queryOp.Query+".*", "s")})
}
for _, queryOp := range filterOptions.In {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bson.D{bson.E{Key: "$in", Value: queryOp.Values}}})
}
for _, queryOp := range filterOptions.IsNotExist {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bson.D{bson.E{Key: "$eq", Value: ""}}})
}
return filter
}
@@ -216,7 +220,7 @@ func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datasto
})
}
}
if op != nil && len(op.Queries) > 0 {
if op != nil {
filter = _applyFilterOptions(filter, op.FilterOptions)
}
var findOptions options.FindOptions
@@ -276,7 +280,7 @@ func (m *mongodb) Count(ctx context.Context, entity datastore.Entity, filterOpti
})
}
}
if filterOptions != nil && len(filterOptions.Queries) > 0 {
if filterOptions != nil {
filter = _applyFilterOptions(filter, *filterOptions)
}
count, err := collection.CountDocuments(ctx, filter)

View File

@@ -70,7 +70,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
&model.Application{Name: "kubevela-app-4", Project: "test-project", Description: "this is demo 4"},
&model.Workflow{Name: "kubevela-app-workflow", AppPrimaryKey: "kubevela-app-2", Description: "this is workflow"},
&model.ApplicationTrigger{Name: "kubevela-app-trigger", AppPrimaryKey: "kubevela-app-2", Token: "token-test", Description: "this is demo 4"},
}
@@ -82,7 +82,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
}
err = mongodbDriver.BatchAdd(context.TODO(), datas2)
equal := cmp.Diff(strings.Contains(err.Error(), "save components occur error"), true)
equal := cmp.Diff(strings.Contains(err.Error(), "save entities occur error"), true)
Expect(equal).To(BeEmpty())
})
@@ -133,6 +133,25 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 1)
Expect(diff).Should(BeEmpty())
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 2)
Expect(diff).Should(BeEmpty())
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 3)
Expect(diff).Should(BeEmpty())
})
It("Test list clusters with sort and fuzzy query", func() {
@@ -196,6 +215,23 @@ var _ = Describe("Test mongodb datastore driver", func() {
})
Expect(err).Should(Succeed())
Expect(count).Should(Equal(int64(2)))
count, err = mongodbDriver.Count(context.TODO(), &app, &datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(2)))
count, err = mongodbDriver.Count(context.TODO(), &app, &datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(3)))
})
It("Test isExist function", func() {

View File

@@ -18,13 +18,14 @@ package model
import (
"fmt"
"strings"
"time"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
)
func init() {
RegistModel(&ApplicationComponent{}, &ApplicationPolicy{}, &Application{}, &ApplicationRevision{}, &ApplicationTrigger{})
RegisterModel(&ApplicationComponent{}, &ApplicationPolicy{}, &Application{}, &ApplicationRevision{}, &ApplicationTrigger{})
}
// Application application delivery model
@@ -43,7 +44,15 @@ func (a *Application) TableName() string {
return tableNamePrefix + "application"
}
// ShortTableName is the compressed version of table name for kubeapi storage and others
func (a *Application) ShortTableName() string {
return "app"
}
// PrimaryKey return custom primary key
// the app primary key is the app name, so the app name is globally unique in every namespace
// when the app is synced from CR, the first synced one be same with app name,
// if there's any conflicts, the name will be composed by <appname>-<namespace>
func (a *Application) PrimaryKey() string {
return a.Name
}
@@ -60,6 +69,38 @@ func (a *Application) Index() map[string]string {
return index
}
// GetAppNameForSynced will trim namespace suffix for synced CR
func (a *Application) GetAppNameForSynced() string {
if a.Labels == nil {
return a.Name
}
namespace := a.Labels[LabelSyncNamespace]
if namespace == "" {
return a.Name
}
return strings.TrimSuffix(a.Name, "-"+namespace)
}
// GetAppNamespaceForSynced will return the namespace of synced CR
func (a *Application) GetAppNamespaceForSynced() string {
if a.Labels == nil {
return ""
}
return a.Labels[LabelSyncNamespace]
}
// IsSynced answer if the app is synced one
func (a *Application) IsSynced() bool {
if a.Labels == nil {
return false
}
sot := a.Labels[LabelSourceOfTruth]
if sot == FromCR || sot == FromInner {
return true
}
return false
}
// ClusterSelector cluster selector
type ClusterSelector struct {
Name string `json:"name"`
@@ -102,6 +143,11 @@ func (a *ApplicationComponent) TableName() string {
return tableNamePrefix + "application_component"
}
// ShortTableName is the compressed version of table name for kubeapi storage and others
func (a *ApplicationComponent) ShortTableName() string {
return "app_cmp"
}
// PrimaryKey return custom primary key
func (a *ApplicationComponent) PrimaryKey() string {
return fmt.Sprintf("%s-%s", a.AppPrimaryKey, a.Name)
@@ -138,6 +184,11 @@ func (a *ApplicationPolicy) TableName() string {
return tableNamePrefix + "application_policy"
}
// ShortTableName is the compressed version of table name for kubeapi storage and others
func (a *ApplicationPolicy) ShortTableName() string {
return "app_plc"
}
// PrimaryKey return custom primary key
func (a *ApplicationPolicy) PrimaryKey() string {
return fmt.Sprintf("%s-%s", a.AppPrimaryKey, a.Name)
@@ -270,6 +321,11 @@ func (a *ApplicationRevision) TableName() string {
return tableNamePrefix + "application_revision"
}
// ShortTableName is the compressed version of table name for kubeapi storage and others
func (a *ApplicationRevision) ShortTableName() string {
return "app_rev"
}
// PrimaryKey return custom primary key
func (a *ApplicationRevision) PrimaryKey() string {
return fmt.Sprintf("%s-%s", a.AppPrimaryKey, a.Version)
@@ -349,6 +405,11 @@ func (w *ApplicationTrigger) TableName() string {
return tableNamePrefix + "trigger"
}
// ShortTableName is the compressed version of table name for kubeapi storage and others
func (w *ApplicationTrigger) ShortTableName() string {
return "app_tg"
}
// PrimaryKey return custom primary key
func (w *ApplicationTrigger) PrimaryKey() string {
return w.Token

View File

@@ -23,7 +23,7 @@ import (
)
func init() {
RegistModel(&Cluster{})
RegisterModel(&Cluster{})
}
// ProviderInfo describes the information from provider API
@@ -82,6 +82,11 @@ func (c *Cluster) TableName() string {
return tableNamePrefix + "cluster"
}
// ShortTableName is the compressed version of table name for kubeapi storage and others
func (c *Cluster) ShortTableName() string {
return "cls"
}
// PrimaryKey primary key for datastore
func (c *Cluster) PrimaryKey() string {
return c.Name

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