Compare commits

...

24 Commits

Author SHA1 Message Date
github-actions[bot]
4b4e4f8530 [Backport release-1.6] Fix: enhance the default permissions (#4977)
* Fix: enhance the default permissions

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit c72b95c81e)

* Fix: unit test error

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 28683d0813)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-11-01 20:51:33 +08:00
github-actions[bot]
0121e8b6ef Feat: allowing restart a compeleted workflow (#4976)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 74eea5bed2)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-11-01 20:35:36 +08:00
github-actions[bot]
382510aa67 [Backport release-1.6] Fix: mongodb can not decode runtime.Object (#4974)
* Fix: mongodb can not decode runtime.Object

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit bc43762f86)

* reuse struct

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit fa8a6335dc)

* fix no output when filter

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 4b9e016b01)

Co-authored-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
2022-11-01 17:25:55 +08:00
github-actions[bot]
7ae7d2a5ef [Backport release-1.6] Fix: grant the permission to read the ConfigMap in the vela-system namesapce (#4970)
* Fix: grant the permission to read the ConfigMap in the vela-system namespace

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 643120d74c)

* Fix: change the test

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 13f3afd2f4)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-11-01 10:00:04 +08:00
github-actions[bot]
0736e85e07 [Backport release-1.6] Feat: implement pipeline APIs (#4969)
* add context when run pipeline

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 72f3ad792e)

* Feat: implement pipeline API

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit f560c346cc)

* Extract get log logic and implement getPipelineRunLog API

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 060c6ab9e9)

* Init and delete pipeline contexts

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 5e96bd3106)

* fix panic

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 51072f7947)

* Allow not specifying context

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 810ddcf0bd)

* change pipeline to path parameter

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 3d51c0cb2d)

* Add permission check filter

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 6883767430)

* project -> projects in route

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 1f09f3996b)

* fix route conflict

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 7eea696830)

* Add project alias

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit b07dd72338)

* Feat: change the list pipeline API

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit bd804734b0)

* Feat: filter the project

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 82eee2cc11)

* Fix: the error of the run APi

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit ac87bd3f1a)

* fix log pipeline run API

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit acde8e981e)

* Fix lint, fix the error of log api

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit b8373e6cde)

* fix error returning

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 2e9b4792b0)

* Fix: change the lable to annotation

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit bf08275fde)

* remove log config not found error

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit cdd77dfd8f)

* fix pipeline list api return no context info

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit cdcfa165d1)

* Fix: create the namespace

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit b6888dd87d)

* get pipeline lastrun info

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit a943423d22)

* allow query single step output

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit e2310bbf34)

* organize code in api layer

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 6fd53ed078)

* fix project filter, add context value when get pp run, extend lastRun

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 861f69d555)

* fix get output and implement get input api

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 61495ee70d)

* Fix: change the last run

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit aeb842a45e)

* if query sub-step outout, return it directly

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 467ba25751)

* Fix: change the run stats

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 7a90e7e310)

* Fix: change the output

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 595a871b0d)

* flatten the input/output api

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit efc9692354)

* more info for i/o vars

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 7fe0e1109c)

* fix nested i/o struct

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 201d1228bd)

* add fromStep in input api

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 2400018962)

* add e2e test skeleton

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit f20f9a1ac6)

* add more e2e test

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 98b27f886b)

* use db to store pipeline

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 08962c4f2f)

* keep the last 5k lines of log

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 22b352da14)

* use stern param to keep last lines of logs

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 3eadbf91c8)

* filter, nil labels, spec check

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit bad90b3f7a)

* empty res, index, detail param

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit b12d889e97)

* Add e2e test

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit ae617a928e)

* fix e2e test and unit test

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 0f4e030b84)

* add context e2e test

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 88879e6e43)

* goimports

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 58429978b3)

* add more test

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 75760521a4)

* review

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit cc4c706466)

* remove optional tag in returned value, unify the imports name

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 658b184aef)

* fix e2e test

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit a9e9c96856)

* add stop test

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 58aa2e5125)

* more coverage

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 01ecb51323)

* single case selct

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit d9e8fd0342)

* optimize log color

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit be9840c3cb)

* add default permission and role

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit cf074444ac)

* fix permission ut

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 1bdcad63a2)

* change the log api implementation

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 587f745430)

* add color, add container order

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 6e7f187605)

* lint

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 64ba029031)

* fix filter nil will cut all log

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 799dfe377a)

* longer timeout and lint

Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
(cherry picked from commit 89873f1f66)

Co-authored-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-11-01 00:13:37 +08:00
github-actions[bot]
f01e6d9723 Chore: stable the version of cue and workflow (#4964)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 0defa2d53d)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-10-31 15:12:35 +08:00
github-actions[bot]
2d7d4ef99d [Backport release-1.6] Fix: do not change the workflow after the env changed (#4962)
* Fix: do not change the workflow after the env changed

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 3425c2043f)

* Fix: change the test case

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit c593fcd75b)

* Fix: check app when deleting the target from a env

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit d2d25a8f9d)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-10-31 11:19:49 +08:00
github-actions[bot]
6bbce07a21 Feat: add mode in workflow template (#4960)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 19a30371dd)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-10-31 11:09:54 +08:00
github-actions[bot]
12ba4631c1 [Backport release-1.6] Fix: bocde error override origin error message (#4955)
* fix bcode error

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

* small fix

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

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-10-29 17:05:25 +08:00
github-actions[bot]
d5b4f9ae5d [Backport release-1.6] Fix: update namespace only if it doesn't have the env label (#4953)
* Fix: update namespace only if it doesn't have the env label

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
(cherry picked from commit 9cf18e94d8)

* Fix: update namespace only if it doesn't have the env label

Signed-off-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
(cherry picked from commit 4031aa25ae)

Co-authored-by: wuzhongjian <wuzhongjian_yewu@cmss.chinamobile.com>
2022-10-29 10:36:02 +08:00
github-actions[bot]
d62185315a It is unnecessary to judge that the index function is not null (#4952)
Signed-off-by: liutiangang <liutiangang@cmbchina.com>
(cherry picked from commit 2570df7e83)

Co-authored-by: liutiangang <liutiangang@cmbchina.com>
2022-10-29 09:13:48 +08:00
github-actions[bot]
12f0cebc6c more error info for apiservice and add tests (#4949)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

change the mock addon version

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

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-10-29 08:32:26 +08:00
github-actions[bot]
284a7d08b2 Feat: sync the model of api workflow with core (#4950)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 0bfa9f9143)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-10-29 08:31:13 +08:00
github-actions[bot]
c91850ce0d Fix: apiserver & vela status use multi-cluster client (#4946)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
(cherry picked from commit a1a3996062)

Co-authored-by: Somefive <yd219913@alibaba-inc.com>
2022-10-28 14:15:58 +08:00
github-actions[bot]
e13b31d00e [Backport release-1.6] Fix: can't obtain pods generated by Crontask component (patch of PR 4925) (#4945)
* Fix: get label selector from cronJob

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit 03be6b3f8f)

* Fix: small fix

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit b8c97411d5)

* Fix: use scheme create obj

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit fa00132e2d)

* Fix: update method of generating selector

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit d30010ec7a)

* Fix: update cron-task

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit 864c04eee6)

* Fix: add test case

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit c1cdf8f290)

* Fix: add test case

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit f977805810)

Co-authored-by: HanMengnan <1448189829@qq.com>
2022-10-28 11:34:28 +08:00
github-actions[bot]
71d0d7344f Fix: fix gen doc for container image (#4937)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
(cherry picked from commit 33f7c2539b)

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-10-27 22:19:10 +08:00
github-actions[bot]
247845db0a fix gitlab addon registry (#4939)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit f3ee964734)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-10-27 22:17:58 +08:00
github-actions[bot]
427809cea7 Fix: update export2config and export2secret example (#4940)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
(cherry picked from commit b2de4020a4)

Co-authored-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-10-27 19:07:39 +08:00
github-actions[bot]
6c29b7b088 Fix: get Application status using jsonpath filter expression (#4933)
Signed-off-by: yanghaojia <yanghaojia@coding.net>
(cherry picked from commit 16272cffaa)

Co-authored-by: yanghaojia <yanghaojia@coding.net>
2022-10-27 14:45:14 +08:00
github-actions[bot]
77e85472fa [Backport release-1.6] Fix: can't get pods of CronTask component (#4930)
* Fix: get label selector from cronJob

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit a9b2c3ac88)

* Fix: update cron-task yaml

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit 188b544db0)

* Fix: small fix

Signed-off-by: HanMengnan <1448189829@qq.com>
(cherry picked from commit 9d393df2fa)

Co-authored-by: HanMengnan <1448189829@qq.com>
2022-10-27 11:22:51 +08:00
github-actions[bot]
c60df945c3 [Backport release-1.6] Feat: support operations for workflow run (#4924)
* Feat: support operations for workflow run

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

* fix test

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

* fix lint

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

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-10-27 10:26:13 +08:00
github-actions[bot]
28488a4e9b [Backport release-1.6] Feat: enhance the apply-once capability (#4919)
* Feat: enhance the apply-once capability

Signed-off-by: 朱晓兵 <596908030@qq.com>
(cherry picked from commit 43eef883d0)

* Fix: add unit-test

Signed-off-by: 朱晓兵 <596908030@qq.com>
(cherry picked from commit 514c2bc8bd)

* Fix: adjustment variable name

Signed-off-by: 朱晓兵 <596908030@qq.com>
(cherry picked from commit 70b3621ac6)

* Fix: add doc

Signed-off-by: 朱晓兵 <596908030@qq.com>
(cherry picked from commit 5506fe9cda)

* Fix: adjustment variable name

Signed-off-by: 朱晓兵 <596908030@qq.com>
(cherry picked from commit 4e5f8d9443)

Co-authored-by: 朱晓兵 <596908030@qq.com>
2022-10-26 16:36:08 +08:00
github-actions[bot]
1ae7ba1e1e [Backport release-1.6] Fix: add sub step in vela workflow logs (#4918)
* Fix: add sub step in vela workflow logs

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

* fix lint

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

Co-authored-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-10-25 16:58:02 +08:00
Somefive
2076c2f937 Feat: add feature docs and controller parameters for MultiStageComponentApply (#4873) (#4917)
Signed-off-by: ZhongsJie <zhongsjie@gmail.com>

Signed-off-by: ZhongsJie <zhongsjie@gmail.com>

Signed-off-by: ZhongsJie <zhongsjie@gmail.com>
Co-authored-by: ZhongsJie <62382570+ZhongsJie96@users.noreply.github.com>
2022-10-25 15:00:23 +08:00
97 changed files with 4969 additions and 2642 deletions

View File

@@ -176,10 +176,12 @@ jobs:
make e2e-cleanup
make e2e-setup-core
bin/vela addon enable fluxcd
bin/vela addon enable vela-workflow
timeout 600s bash -c -- 'while true; do kubectl get ns flux-system; if [ $? -eq 0 ] ; then break; else sleep 5; fi;done'
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-core,app.kubernetes.io/instance=kubevela -n vela-system --timeout=600s
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
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-workflow -n vela-system --timeout=600s
- name: Run api server e2e test
run: |

View File

@@ -23,8 +23,17 @@ import (
const (
// ApplyOncePolicyType refers to the type of configuration drift policy
ApplyOncePolicyType = "apply-once"
// ApplyOnceStrategyOnAppUpdate policy takes effect on application updating
ApplyOnceStrategyOnAppUpdate ApplyOnceAffectStrategy = "onUpdate"
// ApplyOnceStrategyOnAppStateKeep policy takes effect on application state keep
ApplyOnceStrategyOnAppStateKeep ApplyOnceAffectStrategy = "onStateKeep"
// ApplyOnceStrategyAlways policy takes effect always
ApplyOnceStrategyAlways ApplyOnceAffectStrategy = "always"
)
// ApplyOnceAffectStrategy is a string that mark the policy effective stage
type ApplyOnceAffectStrategy string
// ApplyOncePolicySpec defines the spec of preventing configuration drift
type ApplyOncePolicySpec struct {
Enable bool `json:"enable"`
@@ -45,6 +54,9 @@ type ApplyOnceStrategy struct {
// Path the specified path that allow configuration drift
// like 'spec.template.spec.containers[0].resources' and '*' means the whole target allow configuration drift
Path []string `json:"path"`
// ApplyOnceAffectStrategy Decide when the strategy will take effect
// like affect:onUpdate/onStateKeep/always
ApplyOnceAffectStrategy ApplyOnceAffectStrategy `json:"affect"`
}
// FindStrategy find apply-once strategy for target resource

View File

@@ -97,6 +97,7 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
| `featureGates.gzipResourceTracker` | if enabled, resourceTracker will be compressed using gzip before being stored | `false` |
| `featureGates.zstdResourceTracker` | if enabled, resourceTracker will be compressed using zstd before being stored. It is much faster and more efficient than gzip. If both gzip and zstd are enabled, zstd will be used. | `false` |
| `featureGates.applyOnce` | if enabled, the apply-once feature will be applied to all applications, no state-keep and no resource data storage in ResourceTracker | `false` |
| `featureGates.multiStageComponentApply` | if enabled, the multiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply. | `false` |
### MultiCluster parameters

View File

@@ -2209,10 +2209,11 @@ spec:
execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
description: SubSteps is the mode of workflow sub
steps execution
type: string
type: object
ref:
@@ -4008,6 +4009,17 @@ spec:
namespace:
type: string
type: object
mode:
description: WorkflowExecuteMode defines the mode of workflow
execution
properties:
steps:
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow

View File

@@ -1020,10 +1020,10 @@ spec:
execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
ref:

View File

@@ -1,10 +1,10 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.6.2
controller-gen.kubebuilder.io/version: v0.9.0
creationTimestamp: null
name: workflows.core.oam.dev
spec:
group: core.oam.dev
@@ -34,6 +34,16 @@ spec:
type: string
metadata:
type: object
mode:
description: WorkflowExecuteMode defines the mode of workflow execution
properties:
steps:
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow step.
@@ -161,153 +171,3 @@ spec:
type: object
served: true
storage: true
- name: v1beta1
schema:
openAPIV3Schema:
description: Workflow defines workflow steps and other attributes
properties:
mode:
description: WorkflowExecuteMode defines the mode of workflow execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
type: string
type: object
ref:
type: string
steps:
items:
description: WorkflowStep defines how to execute a workflow step.
properties:
dependsOn:
description: DependsOn is the dependency of the step
items:
type: string
type: array
if:
description: If is the if condition of the step
type: string
inputs:
description: Inputs is the inputs of the step
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
meta:
description: Meta is the meta data of the workflow step.
properties:
alias:
type: string
type: object
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: Outputs is the outputs of the step
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
description: Properties is the properties of the step
type: object
x-kubernetes-preserve-unknown-fields: true
subSteps:
items:
description: WorkflowStepBase defines the workflow step base
properties:
dependsOn:
description: DependsOn is the dependency of the step
items:
type: string
type: array
if:
description: If is the if condition of the step
type: string
inputs:
description: Inputs is the inputs of the step
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
meta:
description: Meta is the meta data of the workflow step.
properties:
alias:
type: string
type: object
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: Outputs is the outputs of the step
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
description: Properties is the properties of the step
type: object
x-kubernetes-preserve-unknown-fields: true
timeout:
description: Timeout is the timeout of the step
type: string
type:
description: Type is the type of the workflow step.
type: string
required:
- name
- type
type: object
type: array
timeout:
description: Timeout is the timeout of the step
type: string
type:
description: Type is the type of the workflow step.
type: string
required:
- name
- type
type: object
type: array
type: object
served: true
storage: false
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -12,6 +12,8 @@ spec:
cue:
template: |
#ApplyOnceStrategy: {
// +usage=When the strategy takes effect,e.g. onUpdate、onStateKeep
affect?: string
// +usage=Specify the path of the resource that allow configuration drift
path: [...string]
}

View File

@@ -116,6 +116,39 @@ subjects:
name: {{ include "kubevela.serviceAccountName" . }}
---
# permissions to read the view of VelaQL, schemas, and templates.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "kubevela.fullname" . }}:template-reader-role
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- configmaps/status
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "kubevela.fullname" . }}:template-reader-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "kubevela.fullname" . }}:template-reader-role
subjects:
- kind: Group
name: template-reader
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -221,6 +254,7 @@ spec:
- "--feature-gates=GzipResourceTracker={{- .Values.featureGates.gzipResourceTracker | toString -}}"
- "--feature-gates=ZstdResourceTracker={{- .Values.featureGates.zstdResourceTracker | toString -}}"
- "--feature-gates=ApplyOnce={{- .Values.featureGates.applyOnce | toString -}}"
- "--feature-gates=MultiStageComponentApply= {{- .Values.featureGates.multiStageComponentApply | toString -}}"
{{ if .Values.authentication.enabled }}
{{ if .Values.authentication.withUser }}
- "--authentication-with-user"

View File

@@ -113,11 +113,13 @@ optimize:
##@param featureGates.gzipResourceTracker if enabled, resourceTracker will be compressed using gzip before being stored
##@param featureGates.zstdResourceTracker if enabled, resourceTracker will be compressed using zstd before being stored. It is much faster and more efficient than gzip. If both gzip and zstd are enabled, zstd will be used.
##@param featureGates.applyOnce if enabled, the apply-once feature will be applied to all applications, no state-keep and no resource data storage in ResourceTracker
##@param featureGates.multiStageComponentApply if enabled, the multiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply.
featureGates:
enableLegacyComponentRevision: false
gzipResourceTracker: false
zstdResourceTracker: false
applyOnce: false
multiStageComponentApply: false
## @section MultiCluster parameters

View File

@@ -2209,10 +2209,11 @@ spec:
execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
description: SubSteps is the mode of workflow sub
steps execution
type: string
type: object
ref:
@@ -4008,6 +4009,17 @@ spec:
namespace:
type: string
type: object
mode:
description: WorkflowExecuteMode defines the mode of workflow
execution
properties:
steps:
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow

View File

@@ -1020,10 +1020,10 @@ spec:
execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
ref:

View File

@@ -12,6 +12,8 @@ spec:
cue:
template: |
#ApplyOnceStrategy: {
// +usage=When the strategy takes effect,e.g. onUpdate、onStateKeep
affect?: string
// +usage=Specify the path of the resource that allow configuration drift
path: [...string]
}

File diff suppressed because it is too large Load Diff

View File

@@ -120,4 +120,61 @@ EOF
In the `apply-once-app-3` case, any changes of `hello-cosmos` deployment will not be brought back and any changes
of `hello-cosmos` service will be brought back in the next reconcile loop. In the same time, any changes
of `hello-world` component will be brought back in the next reconcile loop.
of `hello-world` component will be brought back in the next reconcile loop.
```shell
$ cat <<EOF | kubectl apply -f -
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: apply-once-app-4
spec:
components:
- name: hello-world
type: webservice
properties:
image: crccheck/hello-world
port: 8080
traits:
- type: scaler
properties:
replicas: 1
- name: hello-cosmos
type: webservice
properties:
image: crccheck/hello-world
port: 8080
traits:
- type: scaler
properties:
replicas: 1
policies:
- name: apply-once
type: apply-once
properties:
enable: true
rules:
- selector:
componentNames: [ "hello-cosmos" ]
resourceTypes: [ "Deployment" ]
strategy:
affect: onStateKeep
path: [ "spec.replicas"]
EOF
```
By default, KubeVela executes the apply-once policy in two phases: application update and cycle state maintenance,
allowing configuration drift depending on the policy configuration.
If you have special requirements, you can set the affect to determine the phase of policy execution .
affect supported configurations: onUpdate/onStateKeep/always (default)
When affect=always, or not set, the policy is executed in two phase.
When affect=onStateKeep, the policy is executed only during the stateKeep phase. In the case of `apply-once-app-4`, any
changes to the deployed copy of `hello-cosmos` will not be brought back to the next state keeping loop, but will be
brought back to the next application update.
When affect=onUpdate, the policy is only executed when the application is updated. In the case of `
apply-once-app-4`, if affect=onUpdate is set, any changes to the deployed copy of `hello-cosmos` will not be brought
back in the next application update, but will be brought back in the next state keeping loop.

View File

@@ -0,0 +1,43 @@
# MultiStageComponentApply
This example shows how to enable MultiStageComponentApply, the MultiStageComponentApply feature will be combined with the stage field in TraitDefinition to complete the multi-stage apply. Currently, the stage field in TraitDefinition is an optional parameter, which provides `PreDispatch` and `PostDispatch`.
## How to use multi-stage
> The future-gate is still in alpha stage, and it is recommended to use it only in short-term test clusters.
The `MultiStageComponentApply` is not enabled by default, you need some extra works to use it.
1. Add an args `--feature-gates=MultiStageComponentApply=ture` in KubeVela controller's deployment like:
```yaml
spec:
containers:
- args:
- --feature-gates=MultiStageComponentApply=true
...
```
2. Sometime, you have multi-stage apply requirements inside the component, and it is the `outputs` resource defined in the trait. In this case, you can use the `stage` with the value `PreDispatch` or `PostDispatch` like:
```yaml
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: Add storages on K8s pod for your workload which follows the pod spec in path 'spec.template'.
name: storage
namespace: vela-system
spec:
appliesToWorkloads:
- deployments.apps
- statefulsets.apps
- daemonsets.apps
- jobs.batch
podDisruptive: true
stage: PreDispatch
schematic:
cue:
template: |
...
```

View File

@@ -0,0 +1,19 @@
name: not-match-addon
version: 1.0.0
description: Extended workload to do continuous and progressive delivery
icon: https://raw.githubusercontent.com/fluxcd/flux/master/docs/_files/weave-flux.png
url: https://fluxcd.io
tags:
- mock
dependencies: []
#- name: addon_name
# set invisible means this won't be list and will be enabled when depended on
# for example, terraform-alibaba depends on terraform which is invisible,
# when terraform-alibaba is enabled, terraform will be enabled automatically
# default: false
invisible: false
system:
kubernetes: "<=v1.3.0"

View File

@@ -23,4 +23,15 @@ entries:
annotations:
system.vela: ">=1.5.0"
system.kubernetes: ">=1.30.0"
vela-workflow:
- annotations:
system.vela: '>=v1.6.0-beta.1'
created: "2022-10-29T09:11:16.865230605Z"
description: vela-workflow provides the capability to run a standalone workflow
home: https://github.com/kubevela/workflow
icon: https://static.kubevela.net/images/logos/KubeVela%20-03.png
name: vela-workflow
urls:
- http://127.0.0.1:9098/helm/vela-workflow-v0.3.1.tgz
version: v0.3.1
generated: "2022-06-15T13:17:04.733573+08:00"

View File

@@ -22,9 +22,9 @@ import (
"fmt"
"html/template"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
@@ -108,24 +108,31 @@ var ossHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request
var helmHandler http.HandlerFunc = func(rw http.ResponseWriter, req *http.Request) {
switch {
case strings.Contains(req.URL.Path, "index.yaml"):
file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/index.yaml")
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/index.yaml")
if err != nil {
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
case strings.Contains(req.URL.Path, "fluxcd-test-version-1.0.0.tgz"):
file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz")
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-1.0.0.tgz")
if err != nil {
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
case strings.Contains(req.URL.Path, "fluxcd-test-version-2.0.0.tgz"):
file, err := ioutil.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz")
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/fluxcd-test-version-2.0.0.tgz")
if err != nil {
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
case strings.Contains(req.URL.Path, "vela-workflow-v0.3.1.tgz"):
file, err := os.ReadFile("./e2e/addon/mock/testrepo/helm-repo/vela-workflow-v0.3.1.tgz")
if err != nil {
_, _ = rw.Write([]byte(err.Error()))
}
rw.Write(file)
}
}
func init() {

18
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/oam-dev/kubevela
go 1.19
require (
cuelang.org/go v0.4.4-0.20220915174651-ad253ed099e9
cuelang.org/go v0.5.0-alpha.1
github.com/AlecAivazis/survey/v2 v2.1.1
github.com/FogDong/uitable v0.0.5
github.com/Masterminds/semver/v3 v3.1.1
@@ -53,13 +53,14 @@ require (
github.com/hashicorp/hcl/v2 v2.9.1
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
github.com/imdario/mergo v0.3.12
github.com/klauspost/compress v1.15.9
github.com/klauspost/compress v1.15.11
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f
github.com/kubevela/workflow v0.0.0-20221019093241-b5b7a0d79051
github.com/kubevela/workflow v0.3.1
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/mitchellh/hashstructure/v2 v2.0.1
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/oam-dev/cluster-gateway v1.4.0
github.com/oam-dev/cluster-register v1.0.4-0.20220928064144-5f76a9d7ca8c
github.com/oam-dev/terraform-config-inspect v0.0.0-20210418082552-fc72d929aa28
@@ -77,7 +78,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.7.1
github.com/tidwall/gjson v1.9.3
github.com/wercker/stern v0.0.0-20190705090245-4fa46dd6987f
github.com/wonderflow/cert-manager-api v1.0.4-0.20210304051430-e08aa76f6c5f
@@ -85,7 +86,7 @@ require (
github.com/xlab/treeprint v1.1.0
go.mongodb.org/mongo-driver v1.5.1
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.8
@@ -239,7 +240,6 @@ require (
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
@@ -292,12 +292,12 @@ require (
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9 // indirect
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/grpc v1.48.0 // indirect

29
go.sum
View File

@@ -73,8 +73,8 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
cuelang.org/go v0.4.4-0.20220915174651-ad253ed099e9 h1:4mfDNgtdb398g0bekqiW8J8tw+JN3/U/3wh+Jw/I4Yk=
cuelang.org/go v0.4.4-0.20220915174651-ad253ed099e9/go.mod h1:nxWFAPWKYvZJ+eYayxArWqKKjdBTeU1N52vJpML/c6w=
cuelang.org/go v0.5.0-alpha.1 h1:uftOYkiScCHPCQMF2dIwoyCIJsTAEONkFSA2GCm5xIc=
cuelang.org/go v0.5.0-alpha.1/go.mod h1:nxWFAPWKYvZJ+eYayxArWqKKjdBTeU1N52vJpML/c6w=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI=
github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
@@ -1304,8 +1304,8 @@ github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@@ -1334,8 +1334,8 @@ github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2 h1:C3cAfrxst1+dIWgLLh
github.com/kubevela/pkg v0.0.0-20221024115939-a103acee6db2/go.mod h1:TgIGEB/r0NOy63Jzem7WsL3AIr34l+ClH9dmPqcZ4d4=
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f h1:1lUtU1alPThdcsn4MI6XjPb7eJLuZPpmlEdgjtnUMKw=
github.com/kubevela/prism v1.5.1-0.20220915071949-6bf3ad33f84f/go.mod h1:m724/7ANnB/iukyHW20+DicpeJMEC/JA0ZhgsHY10MA=
github.com/kubevela/workflow v0.0.0-20221019093241-b5b7a0d79051 h1:ET01t1GCjbERb+uwgGZnLHoLo4ceE1+gHnmgM/9or5g=
github.com/kubevela/workflow v0.0.0-20221019093241-b5b7a0d79051/go.mod h1:1XyGmfIkD6gPAegUkeDBXXModeiu8NVUWIgersTqwr8=
github.com/kubevela/workflow v0.3.1 h1:R2h6bZbcBSF1OswF0LtLIGn+X+fS0xPOoYgWgOPn1Ig=
github.com/kubevela/workflow v0.3.1/go.mod h1:5jfZ8T1m/En44wDGRf2YqCSlODfEnAV+9PnzoLoDlFs=
github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
@@ -1923,8 +1923,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@@ -2137,8 +2138,9 @@ go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -2198,8 +2200,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -2517,8 +2519,8 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -2550,8 +2552,9 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -2209,10 +2209,11 @@ spec:
execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
description: SubSteps is the mode of workflow sub
steps execution
type: string
type: object
ref:
@@ -4008,6 +4009,17 @@ spec:
namespace:
type: string
type: object
mode:
description: WorkflowExecuteMode defines the mode of workflow
execution
properties:
steps:
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow

View File

@@ -1021,10 +1021,10 @@ spec:
execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
ref:

View File

@@ -1,10 +1,9 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.6.2
controller-gen.kubebuilder.io/version: v0.9.0
name: workflows.core.oam.dev
spec:
group: core.oam.dev
@@ -34,6 +33,16 @@ spec:
type: string
metadata:
type: object
mode:
description: WorkflowExecuteMode defines the mode of workflow execution
properties:
steps:
description: Steps is the mode of workflow steps execution
type: string
subSteps:
description: SubSteps is the mode of workflow sub steps execution
type: string
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow step.
@@ -161,297 +170,3 @@ spec:
type: object
served: true
storage: true
- name: v1alpha1
schema:
openAPIV3Schema:
description: Workflow is the Schema for the workflow API
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:
type: object
steps:
items:
description: WorkflowStep defines how to execute a workflow step.
properties:
dependsOn:
description: DependsOn is the dependency of the step
items:
type: string
type: array
if:
description: If is the if condition of the step
type: string
inputs:
description: Inputs is the inputs of the step
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
meta:
description: Meta is the meta data of the workflow step.
properties:
alias:
type: string
type: object
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: Outputs is the outputs of the step
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
description: Properties is the properties of the step
type: object
subSteps:
items:
description: WorkflowStepBase defines the workflow step base
properties:
dependsOn:
description: DependsOn is the dependency of the step
items:
type: string
type: array
if:
description: If is the if condition of the step
type: string
inputs:
description: Inputs is the inputs of the step
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
meta:
description: Meta is the meta data of the workflow step.
properties:
alias:
type: string
type: object
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: Outputs is the outputs of the step
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
description: Properties is the properties of the step
type: object
timeout:
description: Timeout is the timeout of the step
type: string
type:
description: Type is the type of the workflow step.
type: string
required:
- name
- type
type: object
type: array
timeout:
description: Timeout is the timeout of the step
type: string
type:
description: Type is the type of the workflow step.
type: string
required:
- name
- type
type: object
type: array
type: object
served: true
storage: false
- name: v1beta1
schema:
openAPIV3Schema:
description: Workflow defines workflow steps and other attributes
properties:
mode:
description: WorkflowExecuteMode defines the mode of workflow execution
properties:
steps:
description: WorkflowMode describes the mode of workflow
type: string
subSteps:
description: WorkflowMode describes the mode of workflow
type: string
type: object
ref:
type: string
steps:
items:
description: WorkflowStep defines how to execute a workflow step.
properties:
dependsOn:
description: DependsOn is the dependency of the step
items:
type: string
type: array
if:
description: If is the if condition of the step
type: string
inputs:
description: Inputs is the inputs of the step
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
meta:
description: Meta is the meta data of the workflow step.
properties:
alias:
type: string
type: object
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: Outputs is the outputs of the step
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
description: Properties is the properties of the step
type: object
subSteps:
items:
description: WorkflowStepBase defines the workflow step base
properties:
dependsOn:
description: DependsOn is the dependency of the step
items:
type: string
type: array
if:
description: If is the if condition of the step
type: string
inputs:
description: Inputs is the inputs of the step
items:
properties:
from:
type: string
parameterKey:
type: string
required:
- from
- parameterKey
type: object
type: array
meta:
description: Meta is the meta data of the workflow step.
properties:
alias:
type: string
type: object
name:
description: Name is the unique name of the workflow step.
type: string
outputs:
description: Outputs is the outputs of the step
items:
properties:
name:
type: string
valueFrom:
type: string
required:
- name
- valueFrom
type: object
type: array
properties:
description: Properties is the properties of the step
type: object
timeout:
description: Timeout is the timeout of the step
type: string
type:
description: Type is the type of the workflow step.
type: string
required:
- name
- type
type: object
type: array
timeout:
description: Timeout is the timeout of the step
type: string
type:
description: Type is the type of the workflow step.
type: string
required:
- name
- type
type: object
type: array
type: object
served: true
storage: false
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -612,7 +612,7 @@ func unmarshalToContent(content []byte) (fileContent *github.RepositoryContent,
}
func genAddonAPISchema(addonRes *UIData) error {
cueScript := script.CUE([]byte(addonRes.Parameters))
cueScript := script.CUE(addonRes.Parameters)
schema, err := cueScript.ParsePropertiesToSchema()
if err != nil {
return err

View File

@@ -16,10 +16,60 @@ limitations under the License.
package model
import "fmt"
import (
"fmt"
"github.com/kubevela/workflow/api/v1alpha1"
)
func init() {
RegisterModel(&PipelineContext{})
RegisterModel(&Pipeline{})
}
// Structs copied from workflow/api/v1alpha1/types.go
// WorkflowSpec defines workflow steps and other attributes
type WorkflowSpec struct {
Mode *v1alpha1.WorkflowExecuteMode `json:"mode,omitempty"`
Steps []WorkflowStep `json:"steps,omitempty"`
}
// Pipeline is the model of pipeline
type Pipeline struct {
BaseModel
Spec WorkflowSpec
Name string `json:"name"`
Project string `json:"project"`
Alias string `json:"alias"`
Description string `json:"description"`
}
// PrimaryKey return custom primary key
func (p Pipeline) PrimaryKey() string {
return fmt.Sprintf("%s-%s", p.Project, p.Name)
}
// TableName return custom table name
func (p Pipeline) TableName() string {
return tableNamePrefix + "pipeline"
}
// ShortTableName is the compressed version of table name for kubeapi storage and others
func (p Pipeline) ShortTableName() string {
return "pipeline"
}
// Index return custom index
func (p Pipeline) Index() map[string]string {
var index = make(map[string]string)
if p.Project != "" {
index["project"] = p.Project
}
if p.Name != "" {
index["name"] = p.Name
}
return index
}
// Value is a k-v pair

View File

@@ -16,8 +16,6 @@ limitations under the License.
package model
import "fmt"
func init() {
RegisterModel(&Project{})
}
@@ -29,11 +27,15 @@ type Project struct {
Alias string `json:"alias"`
Owner string `json:"owner"`
Description string `json:"description,omitempty"`
Namespace string `json:"namespace"`
}
// GetNamespace get the namespace name of this project.
func (p *Project) GetNamespace() string {
return fmt.Sprintf("project-%s", p.Name)
if p.Namespace != "" {
return p.Namespace
}
return p.Name
}
// TableName return custom table name

View File

@@ -50,16 +50,25 @@ type Workflow struct {
// WorkflowStep defines how to execute a workflow step.
type WorkflowStep struct {
WorkflowStepBase `json:",inline"`
SubSteps []WorkflowStepBase `json:"subSteps,omitempty"`
}
// WorkflowStepBase is the step base of workflow
type WorkflowStepBase struct {
// Name is the unique name of the workflow step.
Name string `json:"name"`
Alias string `json:"alias"`
Type string `json:"type"`
Description string `json:"description"`
OrderIndex int `json:"orderIndex"`
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty"`
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty"`
DependsOn []string `json:"dependsOn"`
Properties *JSONStruct `json:"properties,omitempty"`
Name string `json:"name"`
Alias string `json:"alias"`
Type string `json:"type"`
Description string `json:"description"`
OrderIndex int `json:"orderIndex"`
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty"`
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty"`
DependsOn []string `json:"dependsOn"`
Properties *JSONStruct `json:"properties,omitempty"`
Meta *workflowv1alpha1.WorkflowStepMeta `json:"meta,omitempty"`
If string `json:"if,omitempty"`
Timeout string `json:"timeout,omitempty"`
}
// TableName return custom table name
@@ -114,6 +123,12 @@ type WorkflowRecord struct {
// WorkflowStepStatus is the workflow step status database model
type WorkflowStepStatus struct {
StepStatus `json:",inline"`
SubStepsStatus []StepStatus `json:"subSteps,omitempty"`
}
// StepStatus is the workflow step status database model
type StepStatus struct {
ID string `json:"id"`
Name string `json:"name"`
Alias string `json:"alias"`

View File

@@ -32,6 +32,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
@@ -599,25 +600,20 @@ func GenEnvWorkflowStepsAndPolicies(ctx context.Context, kubeClient client.Clien
}
var steps []model.WorkflowStep
for _, step := range workflowSteps {
base, err := convert.FromCRWorkflowStepBase(step.WorkflowStepBase)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", pkgUtils.Sanitize(app.Name), pkgUtils.Sanitize(step.Name), err.Error())
continue
}
targetName := strings.Replace(step.Name, "-cloud-resource", "", 1)
s := model.WorkflowStep{
Name: step.Name,
Type: step.Type,
Alias: fmt.Sprintf("Deploy To %s", targetName),
Description: fmt.Sprintf("deploy app to delivery target %s", targetName),
DependsOn: step.DependsOn,
Inputs: step.Inputs,
Outputs: step.Outputs,
base.Alias = fmt.Sprintf("Deploy To %s", targetName)
base.Description = fmt.Sprintf("deploy app to delivery target %s", targetName)
ws := model.WorkflowStep{
WorkflowStepBase: *base,
SubSteps: make([]model.WorkflowStepBase, 0),
}
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", pkgUtils.Sanitize(app.Name), pkgUtils.Sanitize(step.Name), err.Error())
continue
}
s.Properties = properties
}
steps = append(steps, s)
// no sub steps handle here
steps = append(steps, ws)
}
return steps, policies
}

View File

@@ -40,54 +40,70 @@ import (
func TestCompareWorkflowSteps(t *testing.T) {
existSteps := []model.WorkflowStep{
{
Name: "step1",
Type: "deploy2env",
Properties: &model.JSONStruct{
"policy": "env-policy",
"env": "target1",
WorkflowStepBase: model.WorkflowStepBase{
Name: "step1",
Type: "deploy2env",
Properties: &model.JSONStruct{
"policy": "env-policy",
"env": "target1",
},
},
},
{
Name: "suspend",
Type: "suspend",
},
{
Name: "step2",
Type: "deploy2env",
Properties: &model.JSONStruct{
"policy": "env-policy",
"env": "target2",
WorkflowStepBase: model.WorkflowStepBase{
Name: "suspend",
Type: "suspend",
},
},
{
Name: "step3",
Type: "deploy2env",
Properties: &model.JSONStruct{
"policy": "env-policy",
"env": "target3",
WorkflowStepBase: model.WorkflowStepBase{
Name: "step2",
Type: "deploy2env",
Properties: &model.JSONStruct{
"policy": "env-policy",
"env": "target2",
},
},
},
{
Name: "notify",
Type: "notify",
Properties: &model.JSONStruct{"message": "dddd"},
WorkflowStepBase: model.WorkflowStepBase{
Name: "step3",
Type: "deploy2env",
Properties: &model.JSONStruct{
"policy": "env-policy",
"env": "target3",
},
},
},
{
WorkflowStepBase: model.WorkflowStepBase{
Name: "notify",
Type: "notify",
Properties: &model.JSONStruct{"message": "dddd"},
},
},
}
newSteps := []model.WorkflowStep{
{
Name: "step1",
Type: "deploy",
Properties: &model.JSONStruct{"policies": []string{"target1"}},
WorkflowStepBase: model.WorkflowStepBase{
Name: "step1",
Type: "deploy",
Properties: &model.JSONStruct{"policies": []string{"target1"}},
},
},
{
Name: "step2",
Type: "deploy",
Properties: &model.JSONStruct{"policies": []string{"target2"}},
WorkflowStepBase: model.WorkflowStepBase{
Name: "step2",
Type: "deploy",
Properties: &model.JSONStruct{"policies": []string{"target2"}},
},
},
{
Name: "step4",
Type: "deploy",
Properties: &model.JSONStruct{"policies": []string{"target4"}},
WorkflowStepBase: model.WorkflowStepBase{
Name: "step4",
Type: "deploy",
Properties: &model.JSONStruct{"policies": []string{"target4"}},
},
},
}
exist := createWorkflowSteps(existSteps, []datastore.Entity{
@@ -368,11 +384,15 @@ var _ = Describe("Test workflow model", func() {
workflow.Steps = []model.WorkflowStep{
workflow.Steps[0], {
Type: "suspend",
Name: "suspend",
WorkflowStepBase: model.WorkflowStepBase{
Type: "suspend",
Name: "suspend",
},
}, workflow.Steps[1], {
Type: "notification",
Name: "notification",
WorkflowStepBase: model.WorkflowStepBase{
Type: "notification",
Name: "notification",
},
},
}

View File

@@ -412,14 +412,12 @@ func (u *addonServiceImpl) EnableAddon(ctx context.Context, name string, args ap
continue
}
if strings.Contains(err.Error(), "specified version") {
berr := bcode.ErrAddonInvalidVersion
berr.Message = err.Error()
return berr
return bcode.ErrAddonInvalidVersion.SetMessage(err.Error())
}
// wrap this error with special bcode
if errors.As(err, &pkgaddon.VersionUnMatchError{}) {
return bcode.ErrAddonSystemVersionMismatch
return bcode.ErrAddonSystemVersionMismatch.SetMessage(err.Error())
}
// except `addon not found`, other errors should return directly
return err

View File

@@ -533,7 +533,7 @@ var _ = Describe("Test application service function", func() {
Expect(cmp.Diff(compareResponse.TargetAppYAML, "")).Should(BeEmpty())
Expect(cmp.Diff(compareResponse.BaseAppYAML, "")).ShouldNot(BeEmpty())
By("compare when app's env add target, should return true")
By("compare when app's env add target, should return false")
_, err = targetService.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: "dev-target1", Project: appModel.Project, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: "dev-target1"}})
Expect(err).Should(BeNil())
_, err = envService.UpdateEnv(context.TODO(), "app-dev",
@@ -548,7 +548,8 @@ var _ = Describe("Test application service function", func() {
},
})
Expect(err).Should(BeNil())
check(compareResponse, true)
// Existing applications are not affected after update env.
check(compareResponse, false)
By("compare when update app's trait, should return true")
// reset app config
@@ -846,19 +847,25 @@ var _ = Describe("Test apiserver policy rest api", func() {
EnvName: "default",
Steps: []v1.WorkflowStep{
{
Name: "default",
Type: "deploy",
Properties: `{"policies":["local"]}`,
WorkflowStepBase: v1.WorkflowStepBase{
Name: "default",
Type: "deploy",
Properties: `{"policies":["local"]}`,
},
},
{
Name: "suspend",
Type: "suspend",
Properties: `{"duration": "10m"}`,
WorkflowStepBase: v1.WorkflowStepBase{
Name: "suspend",
Type: "suspend",
Properties: `{"duration": "10m"}`,
},
},
{
Name: "second",
Type: "deploy",
Properties: `{"policies":["cluster1"]}`,
WorkflowStepBase: v1.WorkflowStepBase{
Name: "second",
Type: "deploy",
Properties: `{"policies":["cluster1"]}`,
},
},
},
}
@@ -870,9 +877,11 @@ var _ = Describe("Test apiserver policy rest api", func() {
EnvName: "default",
Steps: []v1.WorkflowStep{
{
Name: "second",
Type: "deploy",
Properties: `{"policies":["cluster3"]}`,
WorkflowStepBase: v1.WorkflowStepBase{
Name: "second",
Type: "deploy",
Properties: `{"policies":["cluster3"]}`,
},
},
},
}

View File

@@ -231,6 +231,7 @@ func (c *cloudShellServiceImpl) prepareKubeConfig(ctx context.Context) error {
var groups []string
for _, p := range projects {
permissions, err := c.RBACService.GetUserPermissions(ctx, user, p.Name, false)
// The kubernetes permission set is generated based on simple rules, but this is not completely strict.
var readOnly bool
if err != nil {
log.Logger.Errorf("failed to get the user permissions %s", err.Error())
@@ -239,7 +240,7 @@ func (c *cloudShellServiceImpl) prepareKubeConfig(ctx context.Context) error {
readOnly = checkReadOnly(p.Name, permissions)
}
if readOnly {
groupName, err := c.managePrivilegesForProjectRead(ctx, p.Name, true)
groupName, err := c.managePrivilegesForProject(ctx, p, true)
if err != nil {
log.Logger.Errorf("failed to privileges the user %s", err.Error())
}
@@ -247,9 +248,16 @@ func (c *cloudShellServiceImpl) prepareKubeConfig(ctx context.Context) error {
groups = append(groups, groupName)
}
} else {
groups = append(groups, utils.KubeVelaProjectGroupPrefix+p.Name)
groupName, err := c.managePrivilegesForProject(ctx, p, false)
if err != nil {
log.Logger.Errorf("failed to privileges the user %s", err.Error())
}
if groupName != "" {
groups = append(groups, groupName)
}
}
}
groups = append(groups, utils.TemplateReaderGroup)
if utils.StringsContain(user.UserRoles, "admin") {
groups = append(groups, utils.KubeVelaAdminGroupPrefix+"admin")
@@ -375,8 +383,9 @@ func checkReadOnly(projectName string, permissions []*model.Permission) bool {
return !ra.Match(permissions)
}
// managePrivilegesForProjectRead grant the read privileges for a project
func (c *cloudShellServiceImpl) managePrivilegesForProjectRead(ctx context.Context, projectName string, readOnly bool) (string, error) {
// managePrivilegesForProject grant the privileges for a project
func (c *cloudShellServiceImpl) managePrivilegesForProject(ctx context.Context, project *apisv1.ProjectBase, readOnly bool) (string, error) {
projectName := project.Name
targets, err := c.TargetService.ListTargets(ctx, 0, 0, projectName)
if err != nil {
log.Logger.Infof("failed to list the targets by the project name %s :%s", projectName, err.Error())
@@ -392,7 +401,14 @@ func (c *cloudShellServiceImpl) managePrivilegesForProjectRead(ctx context.Conte
for _, e := range envs.Envs {
authPDs = append(authPDs, &auth.ApplicationPrivilege{Cluster: kubevelatypes.ClusterLocalName, Namespace: e.Namespace, ReadOnly: readOnly})
}
// The namespace of the environment: Application and WorkflowRun
authPDs = append(authPDs, &auth.ApplicationPrivilege{Cluster: kubevelatypes.ClusterLocalName, Namespace: project.Namespace, ReadOnly: readOnly})
groupName := utils.KubeVelaProjectReadGroupPrefix + projectName
if !readOnly {
groupName = utils.KubeVelaProjectGroupPrefix + projectName
}
identity := &auth.Identity{Groups: []string{groupName}}
writer := &bytes.Buffer{}
if err := auth.GrantPrivileges(ctx, c.KubeClient, authPDs, identity, writer, auth.WithReplace); err != nil {

View File

@@ -48,6 +48,7 @@ var _ = Describe("Test cloudshell service function", func() {
cloudShellService *cloudShellServiceImpl
userService *userServiceImpl
projectService *projectServiceImpl
envService *envServiceImpl
err error
database string
)
@@ -56,7 +57,7 @@ var _ = Describe("Test cloudshell service function", func() {
database = "cloudshell-test-kubevela"
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: database})
Expect(err).Should(Succeed())
envService := &envServiceImpl{
envService = &envServiceImpl{
Store: ds,
KubeClient: k8sClient,
}
@@ -66,7 +67,8 @@ var _ = Describe("Test cloudshell service function", func() {
ProjectService: projectService,
}
projectService = &projectServiceImpl{
Store: ds,
Store: ds,
K8sClient: k8sClient,
RbacService: &rbacServiceImpl{
Store: ds,
},
@@ -99,14 +101,13 @@ var _ = Describe("Test cloudshell service function", func() {
}
})
It("test prepareKubeConfig", func() {
It("Test prepareKubeConfig", func() {
err = userService.Init(context.TODO())
Expect(err).Should(BeNil())
err = projectService.Init(context.TODO())
Expect(err).Should(BeNil())
By("test the developer users")
_, err = userService.CreateUser(context.TODO(), apisv1.CreateUserRequest{Name: "test-dev", Password: "test"})
Expect(err).Should(BeNil())
@@ -170,17 +171,37 @@ var _ = Describe("Test cloudshell service function", func() {
err = cloudShellService.prepareKubeConfig(ctx)
Expect(err).Should(BeNil())
var cm corev1.ConfigMap
err = k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: kubevelatypes.DefaultKubeVelaNS, Name: makeUserConfigName("admin-test")}, &cm)
checkConfig := func() {
var cm corev1.ConfigMap
err = k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: kubevelatypes.DefaultKubeVelaNS, Name: makeUserConfigName("admin-test")}, &cm)
Expect(err).Should(BeNil())
Expect(len(cm.Data["identity"]) > 0).Should(BeTrue())
var identity auth.Identity
err = yaml.Unmarshal([]byte(cm.Data["identity"]), &identity)
Expect(err).Should(BeNil())
Expect(utils.StringsContain(identity.Groups, utils.KubeVelaAdminGroupPrefix+"admin")).Should(BeTrue())
Expect(utils.StringsContain(identity.Groups, utils.TemplateReaderGroup)).Should(BeTrue())
}
checkConfig()
By("Test other projects")
_, err = projectService.CreateProject(ctx, apisv1.CreateProjectRequest{Name: "cloudshell"})
Expect(err).Should(BeNil())
Expect(len(cm.Data["identity"]) > 0).Should(BeTrue())
var identity auth.Identity
err = yaml.Unmarshal([]byte(cm.Data["identity"]), &identity)
_, err = envService.CreateEnv(ctx, apisv1.CreateEnvRequest{Name: "cloudshell-env", Project: "cloudshell"})
Expect(err).Should(BeNil())
Expect(utils.StringsContain(identity.Groups, utils.KubeVelaAdminGroupPrefix+"admin")).Should(BeTrue())
err = cloudShellService.prepareKubeConfig(ctx)
Expect(err).Should(BeNil())
err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "kubevela:writer:application:binding", Namespace: "cloudshell-env"}, &rb)
Expect(err).Should(BeNil())
Expect(rb.Subjects[0].Name).Should(Equal(utils.KubeVelaProjectGroupPrefix + "cloudshell"))
checkConfig()
})
It("test prepare", func() {
It("Test prepare", func() {
By("Test with not CRD")
_, err = userService.CreateUser(context.TODO(), apisv1.CreateUserRequest{Name: "test", Password: "test"})
Expect(err).Should(BeNil())

View File

@@ -26,6 +26,7 @@ import (
apierror "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/repository"
@@ -184,23 +185,6 @@ func checkEqual(old, new []string) bool {
return reflect.DeepEqual(old, new)
}
func (p *envServiceImpl) updateAppWithNewEnv(ctx context.Context, envName string, env *model.Env) error {
// List all apps inside the env
apps, err := listApp(ctx, p.Store, apisv1.ListApplicationOptions{Env: envName})
if err != nil {
return err
}
for _, app := range apps {
err = repository.UpdateEnvWorkflow(ctx, p.KubeClient, p.Store, app, env)
if err != nil {
return err
}
}
return nil
}
// UpdateEnv update an env for request
func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.UpdateEnvRequest) (*apisv1.Env, error) {
env := &model.Env{}
@@ -221,25 +205,33 @@ func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
if err != nil || !pass {
return nil, bcode.ErrEnvTargetConflict
}
var targetChanged bool
if len(req.Targets) > 0 && !checkEqual(env.Targets, req.Targets) {
targetChanged = true
env.Targets = req.Targets
}
targets, err := repository.ListTarget(ctx, p.Store, "", nil)
if err != nil {
return nil, err
}
var targetMap = make(map[string]*model.Target, len(targets))
for i, existTarget := range targets {
targetMap[existTarget.Name] = targets[i]
}
for _, target := range req.Targets {
if _, exist := targetMap[target]; !exist {
var targets []*model.Target
if len(req.Targets) > 0 {
_, _, deleted := util.ThreeWaySliceCompare(req.Targets, env.Targets)
if len(deleted) > 0 {
count, err := p.GetAppCountInEnv(ctx, env)
if err != nil {
return nil, err
}
if count > 0 {
return nil, bcode.ErrEnvTargetNotAllowDelete
}
}
targets, err = repository.ListTarget(ctx, p.Store, "", &datastore.ListOptions{
FilterOptions: datastore.FilterOptions{
In: []datastore.InQueryOption{{
Key: "name",
Values: req.Targets,
}},
},
})
if err != nil {
return nil, err
}
if len(targets) != len(req.Targets) {
return nil, bcode.ErrTargetNotExist
}
env.Targets = req.Targets
}
// create namespace at first
@@ -247,13 +239,6 @@ func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
return nil, err
}
if targetChanged {
if err = p.updateAppWithNewEnv(ctx, name, env); err != nil {
log.Logger.Errorf("update envbinding failure %s", err.Error())
return nil, err
}
}
if err := managePrivilegesForEnvironment(ctx, p.KubeClient, env, false); err != nil {
return nil, err
}
@@ -262,6 +247,14 @@ func (p *envServiceImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
return resp, nil
}
func (p *envServiceImpl) GetAppCountInEnv(ctx context.Context, env *model.Env) (int, error) {
var appList v1beta1.ApplicationList
if err := p.KubeClient.List(ctx, &appList, client.InNamespace(env.Namespace), client.MatchingLabels{model.LabelSourceOfTruth: model.FromUX}); err != nil {
return 0, err
}
return len(appList.Items), nil
}
// CreateEnv create an env for request
func (p *envServiceImpl) CreateEnv(ctx context.Context, req apisv1.CreateEnvRequest) (*apisv1.Env, error) {
newEnv := &model.Env{
@@ -344,14 +337,23 @@ func convertEnvModel2Base(env *model.Env, targets []*model.Target) *apisv1.Env {
UpdateTime: env.UpdateTime,
}
for _, dt := range env.Targets {
var t *model.Target
for _, tg := range targets {
if dt == tg.Name {
data.Targets = append(data.Targets, apisv1.NameAlias{
Name: dt,
Alias: tg.Alias,
})
t = tg
break
}
}
if t != nil {
data.Targets = append(data.Targets, apisv1.NameAlias{
Name: dt,
Alias: t.Alias,
})
} else {
data.Targets = append(data.Targets, apisv1.NameAlias{
Name: dt,
})
}
}
return &data
}

View File

@@ -25,8 +25,11 @@ import (
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
@@ -53,6 +56,8 @@ var _ = Describe("Test env service functions", func() {
// create target
err := ds.Add(context.TODO(), &model.Target{Name: "env-test"})
Expect(err).Should(BeNil())
err = ds.Add(context.TODO(), &model.Target{Name: "env-test-2"})
Expect(err).Should(BeNil())
req := apisv1.CreateEnvRequest{
Name: "test-env",
@@ -113,6 +118,35 @@ var _ = Describe("Test env service functions", func() {
Expect(err).Should(BeNil())
Expect(cmp.Diff(env.Description, req5.Description)).Should(BeEmpty())
By("Test update the targets of the env")
req6 := apisv1.UpdateEnvRequest{
Description: "this is a env description update",
Targets: []string{"env-test", "env-test-2"},
}
env, err = envService.UpdateEnv(context.TODO(), "test-env-2", req6)
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(env.Targets), len(req6.Targets))).Should(BeEmpty())
Expect(k8sClient.Create(context.TODO(), &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "env-app",
Namespace: env.Namespace,
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromUX,
},
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{},
},
})).Should(BeNil())
req7 := apisv1.UpdateEnvRequest{
Description: "this is a env description update",
Targets: []string{"env-test"},
}
_, err = envService.UpdateEnv(context.TODO(), "test-env-2", req7)
Expect(err).Should(Equal(bcode.ErrEnvTargetNotAllowDelete))
// clean up the env
err = envService.DeleteEnv(context.TODO(), "test-env")
Expect(err).Should(BeNil())

View File

@@ -83,7 +83,7 @@ var _ = Describe("Test helm repo list", func() {
pSec = v1.Secret{}
gSec = v1.Secret{}
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "project-my-project"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "my-project"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(yaml.Unmarshal([]byte(projectSecret), &pSec)).Should(BeNil())
Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil())
Expect(k8sClient.Create(ctx, &pSec)).Should(BeNil())
@@ -390,7 +390,7 @@ apiVersion: v1
kind: Secret
metadata:
name: project-helm-repo
namespace: project-my-project
namespace: my-project
labels:
config.oam.dev/type: helm-repository
config.oam.dev/catalog: velacore-config

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
/*
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 service
import (
"context"
"github.com/kubevela/workflow/api/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var (
// defaultNamespace = "project-default-ns1-test"
pipelineService *pipelineServiceImpl
pipelineRunService *pipelineRunServiceImpl
userService *userServiceImpl
contextService *contextServiceImpl
projectService *projectServiceImpl
ctx context.Context
pipelineName = "test-pipeline"
projectName = "test-project"
)
var _ = Describe("Test pipeline service functions", func() {
It("Init services and project", func() {
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "pipeline-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
pipelineService = NewTestPipelineService(ds, k8sClient, cfg).(*pipelineServiceImpl)
pipelineRunService = pipelineService.PipelineRunService.(*pipelineRunServiceImpl)
contextService = pipelineService.ContextService.(*contextServiceImpl)
projectService = pipelineService.ProjectService.(*projectServiceImpl)
userService = &userServiceImpl{Store: ds, K8sClient: k8sClient}
ctx = context.WithValue(context.TODO(), &apisv1.CtxKeyUser, "admin")
err = userService.Init(context.TODO())
Expect(err).Should(BeNil())
_, err = projectService.CreateProject(ctx, apisv1.CreateProjectRequest{
Name: projectName,
Owner: "admin",
})
Expect(err).Should(BeNil())
projModel, err := projectService.GetProject(context.TODO(), projectName)
Expect(err).Should(BeNil())
ctx = context.WithValue(ctx, &apisv1.CtxKeyProject, projModel)
})
It("Test create pipeline", func() {
props := model.JSONStruct{
"url": "https://api.github.com/repos/kubevela/kubevela",
}
testPipelineSteps := []model.WorkflowStep{
{
SubSteps: []model.WorkflowStepBase{
{
Name: "request",
Type: "request",
Outputs: v1alpha1.StepOutputs{
{
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
Name: "stars",
},
},
Properties: &props,
},
},
WorkflowStepBase: model.WorkflowStepBase{
Name: "step-group",
Type: "step-group",
},
},
}
By("create pipeline with sub-steps")
pipeline, err := pipelineService.CreatePipeline(ctx, apisv1.CreatePipelineRequest{
Name: pipelineName,
Spec: model.WorkflowSpec{
Steps: testPipelineSteps,
},
})
Expect(err).Should(BeNil())
Expect(pipeline.Name).Should(Equal(pipelineName))
Expect(pipeline.Spec.Steps[0].Name).Should(Equal("step-group"))
})
It("list pipeline", func() {
pipelines, err := pipelineService.ListPipelines(ctx, apisv1.ListPipelineRequest{
Detailed: true,
})
Expect(err).Should(BeNil())
Expect(pipelines).ShouldNot(BeNil())
Expect(pipelines.Total).Should(Equal(1))
Expect(len(pipelines.Pipelines)).Should(Equal(1))
Expect(pipelines.Pipelines[0].Info).ShouldNot(BeNil())
})
It("get pipeline contexts", func() {
By("no context")
contexts, err := contextService.ListContexts(ctx, projectName, pipelineName)
Expect(err).Should(BeNil())
Expect(contexts.Total).Should(Equal(0))
Expect(len(contexts.Contexts)).Should(Equal(0))
By("create context")
contextName := "test-context"
contextKey := "test-key"
contextVal := "test-val"
ppCtx := apisv1.Context{
Name: contextName,
Values: []model.Value{
{
Key: contextKey,
Value: contextVal,
},
},
}
context, err := contextService.CreateContext(ctx, projectName, pipelineName, ppCtx)
Expect(err).Should(BeNil())
Expect(len(context.Contexts)).Should(Equal(1))
})
})

View File

@@ -22,6 +22,7 @@ import (
"fmt"
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
@@ -31,6 +32,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/utils"
)
// ProjectService project manage service.
@@ -73,29 +75,19 @@ func (p *projectServiceImpl) Init(ctx context.Context) error {
// the default env and default target both using the `default` namespace in control plane cluster
func (p *projectServiceImpl) InitDefaultProjectEnvTarget(ctx context.Context, defaultNamespace string) error {
var project = model.Project{}
entities, err := p.Store.List(ctx, &project, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{
IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "owner",
},
},
}})
entities, err := p.Store.List(ctx, &project, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{}})
if err != nil {
return fmt.Errorf("initialize project failed %w", err)
}
if len(entities) > 0 {
for _, project := range entities {
pro := project.(*model.Project)
var init = pro.Owner == ""
pro.Owner = model.DefaultAdminUserName
if err := p.Store.Put(ctx, pro); err != nil {
return err
}
// owner is empty, it is old data
if init {
if err := p.RbacService.InitDefaultRoleAndUsersForProject(ctx, pro); err != nil {
return fmt.Errorf("init default role and users for project %s failure %w", pro.Name, err)
}
if err := p.RbacService.SyncDefaultRoleAndUsersForProject(ctx, pro); err != nil {
return fmt.Errorf("fail to sync the default role and users for the project %s %w", pro.Name, err)
}
}
return nil
@@ -159,6 +151,13 @@ func (p *projectServiceImpl) GetProject(ctx context.Context, projectName string)
}
return nil, err
}
if _, err := utils.GetNamespace(ctx, p.K8sClient, project.GetNamespace()); err != nil {
if apierrors.IsNotFound(err) {
if err := utils.CreateNamespace(ctx, p.K8sClient, projectName); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, bcode.ErrProjectNamespaceFail
}
}
}
return project, nil
}
@@ -296,7 +295,6 @@ func (p *projectServiceImpl) DeleteProject(ctx context.Context, name string) err
// CreateProject create project
func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.CreateProjectRequest) (*apisv1.ProjectBase, error) {
exist, err := p.Store.IsExist(ctx, &model.Project{Name: req.Name})
if err != nil {
log.Logger.Errorf("check project name is exist failure %s", err.Error())
@@ -319,19 +317,24 @@ func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.Creat
}
}
if err := utils.CreateNamespace(ctx, p.K8sClient, req.Name); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, bcode.ErrProjectNamespaceFail
}
newProject := &model.Project{
Name: req.Name,
Description: req.Description,
Alias: req.Alias,
Owner: owner,
Namespace: req.Name,
}
if err := p.Store.Add(ctx, newProject); err != nil {
return nil, err
}
if err := p.RbacService.InitDefaultRoleAndUsersForProject(ctx, newProject); err != nil {
log.Logger.Errorf("init default role and users for project failure %s", err.Error())
if err := p.RbacService.SyncDefaultRoleAndUsersForProject(ctx, newProject); err != nil {
log.Logger.Errorf("fail to sync the default role and users for the project: %s", err.Error())
}
return ConvertProjectModel2Base(newProject, user), nil
@@ -526,6 +529,7 @@ func ConvertProjectModel2Base(project *model.Project, owner *model.User) *apisv1
CreateTime: project.CreateTime,
UpdateTime: project.UpdateTime,
Owner: apisv1.NameAlias{Name: project.Owner},
Namespace: project.GetNamespace(),
}
if owner != nil && owner.Name == project.Owner {
base.Owner = apisv1.NameAlias{Name: owner.Name, Alias: owner.Alias}

View File

@@ -59,6 +59,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
"project:{projectName}/permission:*",
"project:{projectName}/environment:*",
"project:{projectName}/application:*/*",
"project:{projectName}/pipeline:*/*",
},
Actions: []string{"detail", "list"},
Effect: "Allow",
@@ -89,13 +90,23 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
Scope: "project",
},
{
Name: "configuration-read",
Alias: "Environment Management",
Name: "config-management",
Alias: "Config Management",
Resources: []string{"project:{projectName}/config:*", "project:{projectName}/provider:*"},
Actions: []string{"list", "detail"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "project",
},
{
Name: "pipeline-management",
Alias: "Pipeline Management",
Resources: []string{
"project:{projectName}/pipeline:*",
},
Actions: []string{"*"},
Effect: "Allow",
Scope: "project",
},
}
var defaultPlatformPermission = []*model.PermissionTemplate{
@@ -234,6 +245,17 @@ var ResourceMaps = map[string]resourceMetadata{
pathName: "configName",
},
"provider": {},
"pipeline": {
pathName: "pipelineName",
subResources: map[string]resourceMetadata{
"context": {
pathName: "contextName",
},
"pipelineRun": {
pathName: "pipelineRunName",
},
},
},
},
pathName: "projectName",
},
@@ -276,7 +298,7 @@ var ResourceMaps = map[string]resourceMetadata{
"configTemplate": {},
}
var existResourcePaths = convert(ResourceMaps)
var existResourcePaths = convertSources(ResourceMaps)
type resourceMetadata struct {
subResources map[string]resourceMetadata
@@ -320,11 +342,11 @@ func checkResourcePath(resource string) (string, error) {
return path, fmt.Errorf("there is no resource %s", resource)
}
func convert(sources map[string]resourceMetadata) map[string]string {
func convertSources(sources map[string]resourceMetadata) map[string]string {
list := make(map[string]string)
for k, v := range sources {
if len(v.subResources) > 0 {
for sub, subWithPathName := range convert(v.subResources) {
for sub, subWithPathName := range convertSources(v.subResources) {
if subWithPathName != "" {
withPathname := fmt.Sprintf("/%s:*%s", k, subWithPathName)
if v.pathName != "" {
@@ -383,7 +405,7 @@ type RBACService interface {
ListPermissions(ctx context.Context, projectName string) ([]apisv1.PermissionBase, error)
CreatePermission(ctx context.Context, projectName string, req apisv1.CreatePermissionRequest) (*apisv1.PermissionBase, error)
DeletePermission(ctx context.Context, projectName, permName string) error
InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error
SyncDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error
Init(ctx context.Context) error
}
@@ -835,7 +857,17 @@ func (p *rbacServiceImpl) CreatePermission(ctx context.Context, projectName stri
return assembler.ConvertPermission2DTO(&permission), nil
}
func (p *rbacServiceImpl) InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error {
func (p *rbacServiceImpl) SyncDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error {
permissions, err := p.ListPermissions(ctx, project.Name)
if err != nil {
return err
}
var permissionMap = map[string]apisv1.PermissionBase{}
for i, per := range permissions {
permissionMap[per.Name] = permissions[i]
}
var batchData []datastore.Entity
for _, permissionTemp := range defaultProjectPermissionTemplate {
var rra = RequestResourceAction{}
@@ -849,39 +881,52 @@ func (p *rbacServiceImpl) InitDefaultRoleAndUsersForProject(ctx context.Context,
})
formattedResource = append(formattedResource, rra.GetResource().String())
}
batchData = append(batchData, &model.Permission{
permission := &model.Permission{
Name: permissionTemp.Name,
Alias: permissionTemp.Alias,
Project: project.Name,
Resources: formattedResource,
Actions: permissionTemp.Actions,
Effect: permissionTemp.Effect,
})
}
batchData = append(batchData, &model.Role{
Name: "app-developer",
Alias: "App Developer",
Permissions: []string{"project-view", "app-management", "env-management", "configuration-read"},
Project: project.Name,
}, &model.Role{
Name: "project-admin",
Alias: "Project Admin",
Permissions: []string{"project-view", "app-management", "env-management", "role-management", "configuration-read"},
Project: project.Name,
}, &model.Role{
Name: "project-viewer",
Alias: "Project Viewer",
Permissions: []string{"project-view"},
Project: project.Name,
})
if project.Owner != "" {
var projectUser = &model.ProjectUser{
ProjectName: project.Name,
UserRoles: []string{"project-admin"},
Username: project.Owner,
}
batchData = append(batchData, projectUser)
if perm, exist := permissionMap[permissionTemp.Name]; exist {
if !utils.EqualSlice(perm.Resources, permissionTemp.Resources) || utils.EqualSlice(perm.Actions, permissionTemp.Actions) {
if err := p.Store.Put(ctx, permission); err != nil {
return err
}
}
continue
}
batchData = append(batchData, permission)
}
if len(permissions) == 0 {
batchData = append(batchData, &model.Role{
Name: "app-developer",
Alias: "App Developer",
Permissions: []string{"project-view", "app-management", "env-management", "config-management", "pipeline-management"},
Project: project.Name,
}, &model.Role{
Name: "project-admin",
Alias: "Project Admin",
Permissions: []string{"project-view", "app-management", "env-management", "pipeline-management", "config-management", "role-management"},
Project: project.Name,
}, &model.Role{
Name: "project-viewer",
Alias: "Project Viewer",
Permissions: []string{"project-view"},
Project: project.Name,
})
if project.Owner != "" {
var projectUser = &model.ProjectUser{
ProjectName: project.Name,
UserRoles: []string{"project-admin"},
Username: project.Owner,
}
batchData = append(batchData, projectUser)
}
}
return p.Store.BatchAdd(ctx, batchData)
}

View File

@@ -189,7 +189,7 @@ var _ = Describe("Test rbac service", func() {
err = ds.Add(context.TODO(), &model.Project{Name: "init-test", Owner: "test-user"})
Expect(err).Should(BeNil())
err = rbacService.InitDefaultRoleAndUsersForProject(context.TODO(), &model.Project{Name: "init-test"})
err = rbacService.SyncDefaultRoleAndUsersForProject(context.TODO(), &model.Project{Name: "init-test"})
Expect(err).Should(BeNil())
roles, err := rbacService.ListRole(context.TODO(), "init-test", 0, 0)
@@ -198,7 +198,7 @@ var _ = Describe("Test rbac service", func() {
policies, err := rbacService.ListPermissions(context.TODO(), "init-test")
Expect(err).Should(BeNil())
Expect(len(policies)).Should(BeEquivalentTo(int64(5)))
Expect(len(policies)).Should(BeEquivalentTo(int64(6)))
})
It("Test UpdatePermission", func() {

View File

@@ -25,6 +25,7 @@ import (
"testing"
"time"
"github.com/kubevela/workflow/api/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
@@ -74,7 +75,10 @@ var _ = BeforeSuite(func(done Done) {
By("new kube client")
cfg.Timeout = time.Minute * 2
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
scheme := common.Scheme
err = v1alpha1.AddToScheme(scheme)
Expect(err).ShouldNot(HaveOccurred())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
Expect(err).Should(BeNil())
Expect(k8sClient).ToNot(BeNil())
By("new kube client success")

View File

@@ -36,6 +36,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/repository"
"github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
assembler "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/assembler/v1"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
@@ -149,26 +150,12 @@ func (w *workflowServiceImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
if err != nil && errors.Is(err, datastore.ErrRecordNotExist) {
return nil, err
}
var steps []model.WorkflowStep
for _, step := range req.Steps {
properties, err := model.NewJSONStructByString(step.Properties)
if err != nil {
log.Logger.Errorf("parse trait properties failire %w", err)
return nil, bcode.ErrInvalidProperties
}
steps = append(steps, model.WorkflowStep{
Name: step.Name,
Type: step.Type,
Alias: step.Alias,
Inputs: step.Inputs,
Outputs: step.Outputs,
Description: step.Description,
DependsOn: step.DependsOn,
Properties: properties,
})
modelSteps, err := assembler.CreateWorkflowStepModel(req.Steps)
if err != nil {
return nil, err
}
if workflow != nil {
workflow.Steps = steps
workflow.Steps = modelSteps
workflow.Alias = req.Alias
workflow.Description = req.Description
workflow.Default = req.Default
@@ -178,7 +165,7 @@ func (w *workflowServiceImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
} else {
// It is allowed to set multiple workflows as default, and only one takes effect.
workflow = &model.Workflow{
Steps: steps,
Steps: modelSteps,
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
@@ -490,6 +477,8 @@ func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimary
status := app.Status.Workflow
summaryStatus := model.RevisionStatusRunning
switch {
case status.Phase == workflowv1alpha1.WorkflowStateFailed:
summaryStatus = model.RevisionStatusFail
case status.Finished:
summaryStatus = model.RevisionStatusComplete
case status.Terminated:
@@ -497,17 +486,26 @@ func (w *workflowServiceImpl) syncWorkflowStatus(ctx context.Context, appPrimary
}
record.Status = summaryStatus
stepStatus := make(map[string]*workflowv1alpha1.WorkflowStepStatus, len(status.Steps))
for i, step := range status.Steps {
stepStatus[step.Name] = &status.Steps[i]
stepStatus := make(map[string]*model.WorkflowStepStatus, len(status.Steps))
stepAlias := make(map[string]string)
for _, step := range record.Steps {
stepAlias[step.Name] = step.Alias
for _, sub := range step.SubStepsStatus {
stepAlias[sub.Name] = sub.Alias
}
}
for _, step := range status.Steps {
stepStatus[step.Name] = &model.WorkflowStepStatus{
StepStatus: convert.FromCRWorkflowStepStatus(step.StepStatus, stepAlias[step.Name]),
SubStepsStatus: make([]model.StepStatus, 0),
}
for _, sub := range step.SubStepsStatus {
stepStatus[step.Name].SubStepsStatus = append(stepStatus[step.Name].SubStepsStatus, convert.FromCRWorkflowStepStatus(sub, stepAlias[sub.Name]))
}
}
for i, step := range record.Steps {
if stepStatus[step.Name] != nil {
record.Steps[i].Phase = stepStatus[step.Name].Phase
record.Steps[i].Message = stepStatus[step.Name].Message
record.Steps[i].Reason = stepStatus[step.Name].Reason
record.Steps[i].FirstExecuteTime = stepStatus[step.Name].FirstExecuteTime.Time
record.Steps[i].LastExecuteTime = stepStatus[step.Name].LastExecuteTime.Time
record.Steps[i] = *stepStatus[step.Name]
}
}
record.Finished = strconv.FormatBool(status.Finished)
@@ -542,9 +540,19 @@ func (w *workflowServiceImpl) CreateWorkflowRecord(ctx context.Context, appModel
steps := make([]model.WorkflowStepStatus, len(workflow.Steps))
for i, step := range workflow.Steps {
steps[i] = model.WorkflowStepStatus{
Name: step.Name,
Alias: step.Alias,
Type: step.Type,
StepStatus: model.StepStatus{
Name: step.Name,
Alias: step.Alias,
Type: step.Type,
},
SubStepsStatus: make([]model.StepStatus, 0),
}
for _, sub := range step.SubSteps {
steps[i].SubStepsStatus = append(steps[i].SubStepsStatus, model.StepStatus{
Name: sub.Name,
Alias: sub.Alias,
Type: sub.Type,
})
}
}
@@ -709,7 +717,7 @@ func TerminateWorkflow(ctx context.Context, kubecli client.Client, app *v1beta1.
switch sub.Phase {
case workflowv1alpha1.WorkflowStepPhaseFailed:
if sub.Reason != wfTypes.StatusReasonFailedAfterRetries && sub.Reason != wfTypes.StatusReasonTimeout {
steps[i].SubStepsStatus[j].Phase = wfTypes.StatusReasonTerminate
steps[i].SubStepsStatus[j].Reason = wfTypes.StatusReasonTerminate
}
case workflowv1alpha1.WorkflowStepPhaseRunning:
steps[i].SubStepsStatus[j].Phase = workflowv1alpha1.WorkflowStepPhaseFailed

View File

@@ -58,7 +58,7 @@ var _ = Describe("Test workflow service functions", func() {
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
rbacService := &rbacServiceImpl{Store: ds}
projectService = &projectServiceImpl{Store: ds, RbacService: rbacService}
projectService = &projectServiceImpl{Store: ds, RbacService: rbacService, K8sClient: k8sClient}
envService = &envServiceImpl{Store: ds, KubeClient: k8sClient, ProjectService: projectService}
envBinding = &envBindingServiceImpl{
Store: ds,
@@ -123,12 +123,28 @@ var _ = Describe("Test workflow service functions", func() {
EnvName: "dev",
Steps: []apisv1.WorkflowStep{
{
Name: "apply-pvc",
Alias: "step-alias-1",
WorkflowStepBase: apisv1.WorkflowStepBase{
Name: "apply-server",
Alias: "step-alias-1",
},
},
{
Name: "apply-server",
Alias: "step-alias-2",
WorkflowStepBase: apisv1.WorkflowStepBase{
Name: "apply-server2",
Alias: "step-alias-2",
},
},
{
WorkflowStepBase: apisv1.WorkflowStepBase{
Name: "group",
Alias: "group-alias",
},
SubSteps: []apisv1.WorkflowStepBase{
{
Name: "suspend",
Alias: "my-suspend",
},
},
},
},
Default: &defaultW,
@@ -255,16 +271,20 @@ var _ = Describe("Test workflow service functions", func() {
By("check the record")
record, err := workflowService.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-233")
Expect(err).Should(BeNil())
Expect(record.Status).Should(Equal(model.RevisionStatusComplete))
Expect(record.Status).Should(Equal(model.RevisionStatusFail))
Expect(record.Steps[0].Alias).Should(Equal("step-alias-1"))
Expect(record.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseSucceeded))
Expect(record.Steps[1].Alias).Should(Equal("step-alias-2"))
Expect(record.Steps[1].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseSucceeded))
Expect(record.Steps[2].Alias).Should(Equal("group-alias"))
Expect(record.Steps[2].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseFailed))
Expect(record.Steps[2].SubStepsStatus[0].Alias).Should(Equal("my-suspend"))
Expect(record.Steps[2].SubStepsStatus[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseFailed))
By("check the application revision")
err = workflowService.Store.Get(ctx, revision)
Expect(err).Should(BeNil())
Expect(revision.Status).Should(Equal(model.RevisionStatusComplete))
Expect(revision.Status).Should(Equal(model.RevisionStatusFail))
By("create another workflow record to test sync status from controller revision")
app.Status.Workflow.Finished = false
@@ -314,12 +334,12 @@ var _ = Describe("Test workflow service functions", func() {
By("check the record")
anotherRecord, err := workflowService.DetailWorkflowRecord(context.TODO(), workflow, "test-workflow-2-111")
Expect(err).Should(BeNil())
Expect(anotherRecord.Status).Should(Equal(model.RevisionStatusComplete))
Expect(anotherRecord.Status).Should(Equal(model.RevisionStatusFail))
By("check the application revision")
err = workflowService.Store.Get(ctx, anotherRevision)
Expect(err).Should(BeNil())
Expect(anotherRevision.Status).Should(Equal(model.RevisionStatusComplete))
Expect(anotherRevision.Status).Should(Equal(model.RevisionStatusFail))
})
It("Test CreateRecord function", func() {
@@ -541,10 +561,14 @@ var _ = Describe("Test workflow service functions", func() {
Finished: "false",
Steps: []model.WorkflowStepStatus{
{
Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
StepStatus: model.StepStatus{
Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
},
},
{
Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
StepStatus: model.StepStatus{
Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
},
},
},
})
@@ -619,10 +643,10 @@ var yamlStr = `apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
annotations:
app.oam.dev/workflowName: test-workflow-2
app.oam.dev/appName: app-workflow
app.oam.dev/deployVersion: "1234"
app.oam.dev/publishVersion: "test-workflow-name-111"
app.oam.dev/appName: "app-workflow"
app.oam.dev/publishVersion: test-workflow-name-111
app.oam.dev/workflowName: test-workflow-2
name: app-workflow
namespace: default
spec:
@@ -632,31 +656,71 @@ spec:
image: crccheck/hello-world
port: 8000
type: webservice
- name: express-server2
properties:
image: crccheck/hello-world
port: 8000
type: webservice
workflow:
steps:
- name: apply-server
properties:
component: express-server
type: apply-component
- name: apply-server2
properties:
component: express-server
type: apply-component
- name: group
subSteps:
- name: suspend
timeout: 1s
type: suspend
type: step-group
status:
status: workflowFailed
workflow:
appRevision: test-workflow-name-111
contextBackend:
name: workflow-app-workflow-context
namespace: default
uid: ef9bcf49-66a7-4c69-b349-150810aa2bac
endTime: "2022-10-28T06:45:46Z"
finished: true
message: The workflow terminates because of the failed steps
mode: StepByStep-DAG
startTime: "2022-10-28T06:45:37Z"
status: failed
steps:
- firstExecuteTime: "2021-10-26T11:19:33Z"
id: t8bpvi88d1
lastExecuteTime: "2021-10-26T11:19:33Z"
name: apply-pvc
phase: succeeded
type: apply-object
- firstExecuteTime: "2021-10-26T11:19:33Z"
id: 9fou7rbq9r
lastExecuteTime: "2021-10-26T11:19:33Z"
- firstExecuteTime: "2022-10-28T06:45:37Z"
id: fg5uiwroe6
lastExecuteTime: "2022-10-28T06:45:45Z"
name: apply-server
phase: succeeded
type: apply-component
- firstExecuteTime: "2022-10-28T06:45:45Z"
id: prouwp48y7
lastExecuteTime: "2022-10-28T06:45:45Z"
name: apply-server2
phase: succeeded
type: apply-component
- firstExecuteTime: "2022-10-28T06:45:45Z"
id: s6o27xnkzq
lastExecuteTime: "2022-10-28T06:45:46Z"
name: group
phase: failed
reason: Timeout
subSteps:
- firstExecuteTime: "2022-10-28T06:45:45Z"
id: ctu63esz2m
lastExecuteTime: "2022-10-28T06:45:46Z"
name: suspend
phase: failed
reason: Timeout
type: suspend
type: step-group
suspend: false
terminated: false
finished: true
appRevision: "test-workflow-name-111"`
terminated: true`
func (w *workflowServiceImpl) createTestApplicationRevision(ctx context.Context, revision *model.ApplicationRevision) error {
if err := w.Store.Add(ctx, revision); err != nil {

View File

@@ -112,25 +112,48 @@ func FromCRWorkflow(ctx context.Context, cli client.Client, appPrimaryKey string
steps = app.Spec.Workflow.Steps
}
for _, s := range steps {
ws := model.WorkflowStep{
Name: s.Name,
Type: s.Type,
Inputs: s.Inputs,
Outputs: s.Outputs,
DependsOn: s.DependsOn,
base, err := FromCRWorkflowStepBase(s.WorkflowStepBase)
if err != nil {
return dataWf, nil, err
}
if s.Properties != nil {
properties, err := model.NewJSONStruct(s.Properties)
ws := model.WorkflowStep{
WorkflowStepBase: *base,
SubSteps: make([]model.WorkflowStepBase, 0),
}
for _, sub := range s.SubSteps {
subBase, err := FromCRWorkflowStepBase(sub)
if err != nil {
return dataWf, nil, err
}
ws.Properties = properties
ws.SubSteps = append(ws.SubSteps, *subBase)
}
dataWf.Steps = append(dataWf.Steps, ws)
}
return dataWf, steps, nil
}
// FromCRWorkflowStepBase convert cr to model
func FromCRWorkflowStepBase(step workflowv1alpha1.WorkflowStepBase) (*model.WorkflowStepBase, error) {
base := &model.WorkflowStepBase{
Name: step.Name,
Type: step.Type,
Inputs: step.Inputs,
Outputs: step.Outputs,
DependsOn: step.DependsOn,
Meta: step.Meta,
If: step.If,
Timeout: step.Timeout,
}
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
return nil, err
}
base.Properties = properties
}
return base, nil
}
// FromCRTargets converts deployed Cluster/Namespace from Application CR Status into velaux data store
func FromCRTargets(ctx context.Context, cli client.Client, targetApp *v1beta1.Application, existTargets []datastore.Entity, project string) ([]*model.Target, map[string]string) {
existTarget := make(map[string]*model.Target)
@@ -187,9 +210,11 @@ func FromCRWorkflowRecord(app *v1beta1.Application, workflow model.Workflow, rev
steps := make([]model.WorkflowStepStatus, len(workflow.Steps))
for i, step := range workflow.Steps {
steps[i] = model.WorkflowStepStatus{
Name: step.Name,
Alias: step.Alias,
Type: step.Type,
StepStatus: model.StepStatus{
Name: step.Name,
Alias: step.Alias,
Type: step.Type,
},
}
}
return &model.WorkflowRecord{
@@ -205,6 +230,21 @@ func FromCRWorkflowRecord(app *v1beta1.Application, workflow model.Workflow, rev
}
}
// FromCRWorkflowStepStatus convert the workflow step status to workflow step status
func FromCRWorkflowStepStatus(stepStatus workflowv1alpha1.StepStatus, alias string) model.StepStatus {
return model.StepStatus{
Name: stepStatus.Name,
Alias: alias,
ID: stepStatus.ID,
Type: stepStatus.Type,
Message: stepStatus.Message,
Reason: stepStatus.Reason,
Phase: stepStatus.Phase,
FirstExecuteTime: stepStatus.FirstExecuteTime.Time,
LastExecuteTime: stepStatus.LastExecuteTime.Time,
}
}
// FromCRApplicationRevision convert the application revision to the revision in the data store
func FromCRApplicationRevision(ctx context.Context, cli client.Client, app *v1beta1.Application, workflow model.Workflow, envName string) *model.ApplicationRevision {
if app.Status.Workflow == nil || app.Status.Workflow.AppRevision == "" {

View File

@@ -17,19 +17,18 @@ limitations under the License.
package clients
import (
"errors"
"fmt"
pkgmulticluster "github.com/kubevela/pkg/multicluster"
"github.com/kubevela/workflow/api/v1alpha1"
"github.com/kubevela/workflow/pkg/cue/packages"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"github.com/kubevela/workflow/pkg/cue/packages"
apiConfig "github.com/oam-dev/kubevela/pkg/apiserver/config"
"github.com/oam-dev/kubevela/pkg/auth"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
@@ -74,20 +73,13 @@ func GetKubeClient() (client.Client, error) {
if kubeConfig == nil {
return nil, fmt.Errorf("please call SetKubeConfig first")
}
var err error
kubeClient, err = multicluster.Initialize(kubeConfig, false)
if err == nil {
return kubeClient, nil
}
if !errors.Is(err, multicluster.ErrDetectClusterGateway) {
return nil, err
}
// create single cluster client
kubeClient, err = client.New(kubeConfig, client.Options{Scheme: common.Scheme})
err := v1alpha1.AddToScheme(common.Scheme)
if err != nil {
return nil, err
}
return kubeClient, nil
return pkgmulticluster.NewClient(kubeConfig, pkgmulticluster.ClientOptions{
Options: client.Options{Scheme: common.Scheme},
})
}
// GetKubeConfig create/get kube runtime config

View File

@@ -212,13 +212,11 @@ func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datasto
collection := m.client.Database(m.database).Collection(entity.TableName())
// bson.D{{}} specifies 'all documents'
filter := bson.D{}
if entity.Index() != nil {
for k, v := range entity.Index() {
filter = append(filter, bson.E{
Key: strings.ToLower(k),
Value: v,
})
}
for k, v := range entity.Index() {
filter = append(filter, bson.E{
Key: strings.ToLower(k),
Value: v,
})
}
if op != nil {
filter = _applyFilterOptions(filter, op.FilterOptions)
@@ -272,13 +270,11 @@ func (m *mongodb) Count(ctx context.Context, entity datastore.Entity, filterOpti
}
collection := m.client.Database(m.database).Collection(entity.TableName())
filter := bson.D{}
if entity.Index() != nil {
for k, v := range entity.Index() {
filter = append(filter, bson.E{
Key: strings.ToLower(k),
Value: v,
})
}
for k, v := range entity.Index() {
filter = append(filter, bson.E{
Key: strings.ToLower(k),
Value: v,
})
}
if filterOptions != nil {
filter = _applyFilterOptions(filter, *filterOptions)

View File

@@ -152,19 +152,36 @@ func ConvertFromRecordModel(record *model.WorkflowRecord) *apisv1.WorkflowRecord
// ConvertFromWorkflowStepModel assemble the WorkflowStep model to DTO
func ConvertFromWorkflowStepModel(step model.WorkflowStep) apisv1.WorkflowStep {
apiStep := apisv1.WorkflowStep{
WorkflowStepBase: ConvertFromWorkflowStepBaseModel(step.WorkflowStepBase),
SubSteps: make([]apisv1.WorkflowStepBase, 0),
}
if step.Properties != nil {
apiStep.Properties = step.Properties.JSON()
}
for _, sub := range step.SubSteps {
apiStep.SubSteps = append(apiStep.SubSteps, ConvertFromWorkflowStepBaseModel(sub))
}
return apiStep
}
// ConvertFromWorkflowStepBaseModel assemble the WorkflowStep model to DTO
func ConvertFromWorkflowStepBaseModel(step model.WorkflowStepBase) apisv1.WorkflowStepBase {
apiStepBase := apisv1.WorkflowStepBase{
Name: step.Name,
Type: step.Type,
Alias: step.Alias,
Description: step.Description,
Inputs: step.Inputs,
Outputs: step.Outputs,
Properties: step.Properties.JSON(),
DependsOn: step.DependsOn,
Meta: step.Meta,
If: step.If,
Timeout: step.Timeout,
}
if step.Properties != nil {
apiStep.Properties = step.Properties.JSON()
apiStepBase.Properties = step.Properties.JSON()
}
return apiStep
return apiStepBase
}
// ConvertWorkflowBase assemble the Workflow model to DTO

View File

@@ -47,21 +47,44 @@ func ConvertToEnvBindingModel(app *model.Application, envBind apisv1.EnvBinding)
func CreateWorkflowStepModel(apiSteps []apisv1.WorkflowStep) ([]model.WorkflowStep, error) {
var steps []model.WorkflowStep
for _, step := range apiSteps {
properties, err := model.NewJSONStructByString(step.Properties)
base, err := CreateWorkflowStepBaseModel(step.WorkflowStepBase)
if err != nil {
log.Logger.Errorf("parse trait properties failure %w", err)
return nil, bcode.ErrInvalidProperties
return nil, err
}
steps = append(steps, model.WorkflowStep{
Name: step.Name,
Alias: step.Alias,
Description: step.Description,
DependsOn: step.DependsOn,
Type: step.Type,
Inputs: step.Inputs,
Outputs: step.Outputs,
Properties: properties,
})
stepModel := model.WorkflowStep{
WorkflowStepBase: *base,
SubSteps: make([]model.WorkflowStepBase, 0),
}
for _, sub := range step.SubSteps {
base, err := CreateWorkflowStepBaseModel(sub)
if err != nil {
return nil, err
}
stepModel.SubSteps = append(stepModel.SubSteps, *base)
}
steps = append(steps, stepModel)
}
return steps, nil
}
// CreateWorkflowStepBaseModel convert api to model
func CreateWorkflowStepBaseModel(step apisv1.WorkflowStepBase) (*model.WorkflowStepBase, error) {
properties, err := model.NewJSONStructByString(step.Properties)
if err != nil {
log.Logger.Errorf("parse trait workflow step failure %w", err)
return nil, bcode.ErrInvalidProperties
}
return &model.WorkflowStepBase{
Name: step.Name,
Type: step.Type,
Alias: step.Alias,
Description: step.Description,
Properties: properties,
Inputs: step.Inputs,
Outputs: step.Outputs,
DependsOn: step.DependsOn,
Meta: step.Meta,
If: step.If,
Timeout: step.Timeout,
}, nil
}

View File

@@ -50,12 +50,14 @@ var (
CtxKeyApplicationComponent = "component"
// CtxKeyUser request context key of user
CtxKeyUser = "user"
// CtxKeyProject request context key of project
CtxKeyProject = "project"
// CtxKeyToken request context key of request token
CtxKeyToken = "token"
// CtxKeyPipeline request context key of pipeline
CtxKeyPipeline = "pipeline"
// CtxKeyPipelineContex request context key of pipeline context
CtxKeyPipelineContex = "pipeline-context"
// CtxKeyPipelineContext request context key of pipeline context
CtxKeyPipelineContext = "pipeline-context"
// CtxKeyPipelineRun request context key of pipeline run
CtxKeyPipelineRun = "pipeline-run"
)
@@ -791,6 +793,7 @@ type ProjectBase struct {
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
Owner NameAlias `json:"owner,omitempty"`
Namespace string `json:"namespace"`
}
// CreateProjectRequest create project request body
@@ -1003,15 +1006,24 @@ type UpdateWorkflowRequest struct {
// WorkflowStep workflow step config
type WorkflowStep struct {
WorkflowStepBase `json:",inline"`
SubSteps []WorkflowStepBase `json:"subSteps,omitempty"`
}
// WorkflowStepBase is the step base of workflow
type WorkflowStepBase struct {
// Name is the unique name of the workflow step.
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Type string `json:"type" validate:"checkname"`
Description string `json:"description" optional:"true"`
DependsOn []string `json:"dependsOn" optional:"true"`
Properties string `json:"properties,omitempty"`
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty" optional:"true"`
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty" optional:"true"`
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Type string `json:"type" validate:"checkname"`
Description string `json:"description" optional:"true"`
DependsOn []string `json:"dependsOn" optional:"true"`
Properties string `json:"properties,omitempty"`
Meta *workflowv1alpha1.WorkflowStepMeta `json:"meta,omitempty" optional:"true"`
If string `json:"if,omitempty" optional:"true"`
Timeout string `json:"timeout,omitempty" optional:"true"`
Inputs workflowv1alpha1.StepInputs `json:"inputs,omitempty" optional:"true"`
Outputs workflowv1alpha1.StepOutputs `json:"outputs,omitempty" optional:"true"`
}
// DetailWorkflowResponse detail workflow response
@@ -1542,16 +1554,17 @@ type ListConfigDistributionResponse struct {
// PipelineMeta is metadata of pipeline
type PipelineMeta struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Project string `json:"project"`
Description string `json:"description" optional:"true"`
Name string `json:"name"`
Alias string `json:"alias"`
Project NameAlias `json:"project"`
Description string `json:"description"`
CreateTime time.Time `json:"createTime"`
}
// PipelineBase is the base info of pipeline
type PipelineBase struct {
PipelineMeta `json:",inline"`
Spec workflowv1alpha1.WorkflowSpec `json:"spec"`
Spec model.WorkflowSpec `json:"spec"`
}
// RunStatInfo is the pipeline run statistics info
@@ -1570,11 +1583,10 @@ type RunStat struct {
// CreatePipelineRequest is the request body of creating pipeline
type CreatePipelineRequest struct {
Name string `json:"name" validate:"checkname"`
Project string `json:"project"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Spec workflowv1alpha1.WorkflowSpec `json:"spec"`
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Spec model.WorkflowSpec `json:"spec"`
}
// PipelineMetaResponse is the response body contains PipelineMeta
@@ -1584,8 +1596,9 @@ type PipelineMetaResponse struct {
// ListPipelineRequest is the request body of listing pipeline
type ListPipelineRequest struct {
Projects []string `json:"projects"`
Query string `json:"query"`
Projects []string `json:"projects" optional:"true"`
Query string `json:"query" optional:"true"`
Detailed bool `json:"detailed" optional:"true"`
}
// ListPipelineResponse is the response body of listing pipeline
@@ -1602,14 +1615,9 @@ type PipelineListItem struct {
// UpdatePipelineRequest is the request body of updating pipeline
type UpdatePipelineRequest struct {
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Spec workflowv1alpha1.WorkflowSpec `json:"spec" optional:"true"`
}
// GetPipelineRequest is the request body of getting pipeline
type GetPipelineRequest struct {
Detailed bool `json:"detailed"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Spec model.WorkflowSpec `json:"spec" optional:"true"`
}
// GetPipelineResponse is the response body of getting pipeline
@@ -1620,9 +1628,8 @@ type GetPipelineResponse struct {
// PipelineInfo is the info of pipeline
type PipelineInfo struct {
RelatedApps []ApplicationBase `json:"relatedApps"`
LastRunStatus workflowv1alpha1.WorkflowRunStatus `json:"lastRunStatus"`
RunStat RunStat `json:"runStat"`
LastRun *PipelineRun `json:"lastRun"`
RunStat RunStat `json:"runStat"`
}
/***********************/
@@ -1643,9 +1650,9 @@ type PipelineRunBriefing struct {
// PipelineRunMeta is the metadata of pipeline run
type PipelineRunMeta struct {
PipelineName string `json:"pipelineName"`
Project string `json:"project"`
PipelineRunName string `json:"pipelineRunName"`
PipelineName string `json:"pipelineName"`
Project NameAlias `json:"project"`
PipelineRunName string `json:"pipelineRunName"`
}
// PipelineRun is the info of pipeline run
@@ -1658,14 +1665,16 @@ type PipelineRun struct {
type PipelineRunBase struct {
PipelineRunMeta `json:",inline"`
// Record marks the run of the pipeline
Record int64 `json:"record"`
ContextName string `json:"contextName"`
Spec workflowv1alpha1.WorkflowRunSpec `json:"spec"`
Record int64 `json:"record"`
ContextName string `json:"contextName"`
ContextValues []model.Value `json:"contextValues"`
Spec workflowv1alpha1.WorkflowRunSpec `json:"spec"`
}
// RunPipelineRequest is the request body of running pipeline
type RunPipelineRequest struct {
// Mode is the mode of the pipeline run. Available values are: "StepByStep", "DAG" for both `step` and `subStep`
// default: "StepByStep" for `step`, "DAG" for `subStep`
Mode workflowv1alpha1.WorkflowExecuteMode `json:"mode" optional:"true"`
ContextName string `json:"contextName"`
}
@@ -1678,12 +1687,18 @@ type ListPipelineRunResponse struct {
// GetPipelineRunLogResponse is the response body of getting pipeline run log
type GetPipelineRunLogResponse struct {
Log []Log `json:"log"`
StepBase `json:",inline"`
Log string `json:"log"`
}
// GetPipelineRunOutputResponse is the response body of getting pipeline run output
type GetPipelineRunOutputResponse struct {
Output []Output `json:"output"`
StepOutputs []StepOutputBase `json:"outputs"`
}
// GetPipelineRunInputResponse is the response body of getting pipeline run input
type GetPipelineRunInputResponse struct {
StepInputs []StepInputBase `json:"inputs"`
}
// StepBase is the base info of step
@@ -1694,16 +1709,31 @@ type StepBase struct {
Phase string `json:"phase"`
}
// Log is the log of step
type Log struct {
// StepOutputBase is the output of step
type StepOutputBase struct {
StepBase `json:",inline"`
Log string `json:"log"`
Values []OutputVar `json:"values"`
}
// Output is the output of step
type Output struct {
// StepInputBase is the input of step
type StepInputBase struct {
StepBase `json:",inline"`
Vars map[string]string `json:"vars"`
Values []InputVar `json:"values"`
}
// OutputVar is one output var
type OutputVar struct {
Name string `json:"name"`
Value string `json:"value"`
ValueFrom string `json:"valueFrom"`
}
// InputVar is one input var
type InputVar struct {
From string `json:"from"`
FromStep string `json:"fromStep"`
ParameterKey string `json:"parameterKey"`
Value string `json:"value"`
}
/*******************/

View File

@@ -62,6 +62,7 @@ func InitAPIBean() []interface{} {
RegisterAPIInterface(NewApplicationAPIInterface())
RegisterAPIInterface(NewProjectAPIInterface())
RegisterAPIInterface(NewEnvAPIInterface())
RegisterAPIInterface(NewPipelineAPIInterface())
// Extension
RegisterAPIInterface(NewDefinitionAPIInterface())
@@ -82,7 +83,6 @@ func InitAPIBean() []interface{} {
RegisterAPIInterface(NewWebhookAPIInterface())
RegisterAPIInterface(NewRepositoryAPIInterface())
RegisterAPIInterface(NewCloudShellAPIInterface())
RegisterAPIInterface(NewPipelineAPIInterface())
// Authentication
RegisterAPIInterface(NewAuthenticationAPIInterface())

View File

@@ -18,199 +18,242 @@ package api
import (
"context"
"strconv"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
"github.com/kubevela/workflow/api/v1alpha1"
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
)
type pipelineAPIInterface struct {
PipelineService service.PipelineService `inject:""`
PipelineRunService service.PipelineRunService `inject:""`
ContextService service.ContextService `inject:""`
}
type pipelinePathParamKey string
const (
// Project is the project name key of query param
Project pipelinePathParamKey = "projectName"
Project string = "projectName"
// Pipeline is the pipeline name of query param
Pipeline pipelinePathParamKey = "pipelineName"
Pipeline string = "pipelineName"
// PipelineRun is the pipeline run name of query param
PipelineRun pipelinePathParamKey = "runName"
PipelineRun string = "runName"
// ContextName is the context name of query param
ContextName pipelinePathParamKey = "contextName"
ContextName string = "contextName"
)
// GetWebServiceRoute is the implementation of pipeline Interface
func (p *pipelineAPIInterface) GetWebServiceRoute() *restful.WebService {
ws := new(restful.WebService)
func initPipelineRoutes(ws *restful.WebService, n *projectAPIInterface) {
tags := []string{"pipeline"}
projParam := func(builder *restful.RouteBuilder) {
builder.Param(ws.QueryParameter(string(Project), "project name").Required(true))
builder.Param(ws.PathParameter(Project, "project name").Required(true))
builder.Filter(n.projectCheckFilter)
}
pipelineParam := func(builder *restful.RouteBuilder) {
builder.Param(ws.PathParameter(string(Pipeline), "pipeline name").Required(true))
builder.Filter(p.pipelineCheckFilter)
builder.Param(ws.PathParameter(Pipeline, "pipeline name").Required(true))
builder.Filter(n.pipelineCheckFilter)
}
ctxParam := func(builder *restful.RouteBuilder) {
builder.Param(ws.PathParameter(string(ContextName), "pipeline context name").Required(true))
builder.Filter(p.pipelineContextCheckFilter)
builder.Param(ws.PathParameter(ContextName, "pipeline context name").Required(true))
builder.Filter(n.pipelineContextCheckFilter)
}
runParam := func(builder *restful.RouteBuilder) {
builder.Param(ws.PathParameter(string(PipelineRun), "pipeline run name").Required(true))
builder.Filter(p.pipelineRunCheckFilter)
builder.Param(ws.PathParameter(PipelineRun, "pipeline run name").Required(true))
builder.Filter(n.pipelineRunCheckFilter)
}
meta := func(builder *restful.RouteBuilder) {
builder.Metadata(restfulspec.KeyOpenAPITags, tags)
}
ws.Path(versionPrefix+"/pipelines").
Consumes(restful.MIME_JSON, restful.MIME_XML).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for pipeline manage")
ws.Route(ws.POST("").To(p.createPipeline).
ws.Route(ws.POST("/{projectName}/pipelines").To(n.createPipeline).
Doc("create pipeline").
Reads(apis.CreatePipelineRequest{}).
Returns(200, "OK", apis.PipelineBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.PipelineBase{}).Do(meta))
Filter(n.RBACService.CheckPerm("project/pipeline", "create")).
Writes(apis.PipelineBase{}).Do(meta, projParam))
ws.Route(ws.GET("").To(p.listPipelines).
Doc("list pipelines").
Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")).
Returns(200, "OK", apis.ListPipelineResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListPipelineResponse{}).Do(meta, projParam))
ws.Route(ws.GET("/{pipelineName}").To(p.getPipeline).
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}").To(n.getPipeline).
Doc("get pipeline").
Reads(apis.GetPipelineRequest{}).
Returns(200, "OK", apis.GetPipelineResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.GetPipelineResponse{}).Do(meta, projParam, pipelineParam))
// use Param instead of pipelineParam to get pipeline information
Param(ws.PathParameter(Pipeline, "pipeline name").Required(true)).
Filter(n.RBACService.CheckPerm("project/pipeline", "detail")).
Writes(apis.GetPipelineResponse{}).Do(meta, projParam))
ws.Route(ws.PUT("/{pipelineName}").To(p.updatePipeline).
ws.Route(ws.PUT("/{projectName}/pipelines/{pipelineName}").To(n.updatePipeline).
Doc("update pipeline").
Reads(apis.UpdatePipelineRequest{}).
Returns(200, "OK", apis.PipelineBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline", "update")).
Writes(apis.PipelineBase{}).Do(meta, projParam, pipelineParam))
ws.Route(ws.DELETE("/{pipelineName}").To(p.deletePipeline).
ws.Route(ws.DELETE("/{projectName}/pipelines/{pipelineName}").To(n.deletePipeline).
Doc("delete pipeline").
Returns(200, "OK", apis.PipelineMetaResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline", "delete")).
Writes(apis.PipelineMetaResponse{}).Do(meta, projParam, pipelineParam))
ws.Route(ws.POST("/{pipelineName}/contexts").To(p.createContextValue).
ws.Route(ws.POST("/{projectName}/pipelines/{pipelineName}/contexts").To(n.createContextValue).
Doc("create pipeline context values").
Reads(apis.CreateContextValuesRequest{}).
Returns(200, "OK", apis.Context{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/context", "create")).
Writes(apis.Context{}).Do(meta, projParam, pipelineParam))
ws.Route(ws.GET("/{pipelineName}/contexts").To(p.listContextValues).
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/contexts").To(n.listContextValues).
Doc("list pipeline context values").
Returns(200, "OK", apis.ListContextValueResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/context", "list")).
Writes(apis.ListContextValueResponse{}).Do(meta, projParam, pipelineParam))
ws.Route(ws.PUT("/{pipelineName}/contexts/{contextName}").To(p.updateContextValue).
ws.Route(ws.PUT("/{projectName}/pipelines/{pipelineName}/contexts/{contextName}").To(n.updateContextValue).
Doc("update pipeline context value").
Reads(apis.UpdateContextValuesRequest{}).
Returns(200, "OK", apis.Context{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/context", "update")).
Writes(apis.Context{}).Do(meta, projParam, pipelineParam, ctxParam))
ws.Route(ws.DELETE("/{pipelineName}/contexts/{contextName}").To(p.deleteContextValue).
ws.Route(ws.DELETE("/{projectName}/pipelines/{pipelineName}/contexts/{contextName}").To(n.deleteContextValue).
Doc("delete pipeline context value").
Returns(200, "OK", apis.ContextNameResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/context", "delete")).
Writes(apis.ContextNameResponse{}).Do(meta, projParam, pipelineParam, ctxParam))
ws.Route(ws.POST("/{pipelineName}/run").To(p.runPipeline).
ws.Route(ws.POST("/{projectName}/pipelines/{pipelineName}/run").To(n.runPipeline).
Doc("run pipeline").
Reads(apis.RunPipelineRequest{}).
Returns(200, "OK", apis.PipelineRunMeta{}).
Returns(200, "OK", apis.PipelineRun{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline", "run")).
Writes(apis.PipelineRunMeta{}).Do(meta, projParam, pipelineParam))
ws.Route(ws.GET("/{pipelineName}/runs").To(p.listPipelineRuns).
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs").To(n.listPipelineRuns).
Doc("list pipeline runs").
Param(ws.QueryParameter("status", "query identifier of the status").DataType("string")).
Returns(200, "OK", apis.ListPipelineRunResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "list")).
Writes(apis.ListPipelineRunResponse{}).Do(meta, projParam, pipelineParam))
ws.Route(ws.POST("/{pipelineName}/runs/{runName}/stop").To(p.stopPipeline).
ws.Route(ws.POST("/{projectName}/pipelines/{pipelineName}/runs/{runName}/stop").To(n.stopPipeline).
Doc("stop pipeline run").
Returns(200, "OK", apis.PipelineRunMeta{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "stop")).
Writes(apis.PipelineRunMeta{}).Do(meta, projParam, pipelineParam, runParam))
ws.Route(ws.GET("/{pipelineName}/runs/{runName}").To(p.getPipelineRun).
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}").To(n.getPipelineRun).
Doc("get pipeline run").
Returns(200, "OK", apis.PipelineRunBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "get")).
Writes(apis.PipelineRunBase{}).Do(meta, projParam, pipelineParam, runParam))
ws.Route(ws.DELETE("/{pipelineName}/runs/{runName}").To(p.deletePipelineRun).
ws.Route(ws.DELETE("/{projectName}/pipelines/{pipelineName}/runs/{runName}").To(n.deletePipelineRun).
Doc("delete pipeline run").
Returns(200, "OK", apis.PipelineRunMeta{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "delete")).
Writes(apis.PipelineRunMeta{}).Do(meta, projParam, pipelineParam, runParam))
// get pipeline run status
ws.Route(ws.GET("/{pipelineName}/runs/{runName}/status").To(p.getPipelineRunStatus).
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/status").To(n.getPipelineRunStatus).
Doc("get pipeline run status").
Returns(200, "OK", workflowv1alpha1.WorkflowRunStatus{}).
Returns(200, "OK", v1alpha1.WorkflowRunStatus{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(workflowv1alpha1.WorkflowRunStatus{}).Do(meta, projParam, pipelineParam, runParam))
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
Writes(v1alpha1.WorkflowRunStatus{}).Do(meta, projParam, pipelineParam, runParam))
// get pipeline run log
ws.Route(ws.GET("/{pipelineName}/runs/{runName}/log").To(p.getPipelineRunLog).
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/log").To(n.getPipelineRunLog).
Doc("get pipeline run log").
Param(ws.QueryParameter("step", "query by specific id").DataType("string")).
Param(ws.QueryParameter("step", "query by specific step name").DataType("string")).
Returns(200, "OK", apis.GetPipelineRunLogResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
Writes(apis.GetPipelineRunLogResponse{}).Do(meta, projParam, pipelineParam, runParam))
// get pipeline run output
ws.Route(ws.GET("/{pipelineName}/runs/{runName}/output").To(p.getPipelineRunOutput).
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/output").To(n.getPipelineRunOutput).
Doc("get pipeline run output").
Param(ws.QueryParameter("step", "query by specific id").DataType("string")).
Param(ws.QueryParameter("step", "query by specific step name").DataType("string").Required(true)).
Returns(200, "OK", apis.GetPipelineRunOutputResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
Writes(apis.GetPipelineRunOutputResponse{}).Do(meta, projParam, pipelineParam, runParam))
// get pipeline run input
ws.Route(ws.GET("/{projectName}/pipelines/{pipelineName}/runs/{runName}/input").To(n.getPipelineRunInput).
Doc("get pipeline run input").
Param(ws.QueryParameter("step", "query by specific step name").DataType("string").Required(true)).
Returns(200, "OK", apis.GetPipelineRunInputResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Filter(n.RBACService.CheckPerm("project/pipeline/pipelineRun", "detail")).
Writes(apis.GetPipelineRunInputResponse{}).Do(meta, projParam, pipelineParam, runParam))
ws.Filter(authCheckFilter)
}
// GetWebServiceRoute is the implementation of pipeline Interface
func (n *pipelineAPIInterface) GetWebServiceRoute() *restful.WebService {
tags := []string{"pipeline"}
meta := func(builder *restful.RouteBuilder) {
builder.Metadata(restfulspec.KeyOpenAPITags, tags)
}
ws := new(restful.WebService)
ws.Path(versionPrefix+"/pipelines").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for project manage")
ws.Route(ws.GET("").To(n.listPipelines).
Doc("list pipelines").
Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")).
Param(ws.QueryParameter("projectName", "query pipelines within a project").DataType("string")).
Param(ws.QueryParameter("detailed", "query pipelines with detail").DataType("bool").DefaultValue("true")).
Returns(200, "OK", apis.ListPipelineResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListPipelineResponse{}).Do(meta))
ws.Filter(authCheckFilter)
return ws
}
type pipelineAPIInterface struct {
PipelineService service.PipelineService `inject:""`
}
// NewPipelineAPIInterface new pipeline manage APIInterface
func NewPipelineAPIInterface() Interface {
return &pipelineAPIInterface{}
}
func (p *pipelineAPIInterface) listPipelines(req *restful.Request, res *restful.Response) {
var projetNames []string
if req.QueryParameter("project") != "" {
projetNames = append(projetNames, req.QueryParameter("project"))
func (n *pipelineAPIInterface) listPipelines(req *restful.Request, res *restful.Response) {
var projectNames []string
if req.QueryParameter(Project) != "" {
projectNames = append(projectNames, req.QueryParameter(Project))
}
pipelines, err := p.PipelineService.ListPipelines(req.Request.Context(), apis.ListPipelineRequest{
Projects: projetNames,
_detailed := req.QueryParameter("detailed")
if _detailed == "" {
_detailed = "true"
}
detailed, err := strconv.ParseBool(_detailed)
if err != nil {
bcode.ReturnError(req, res, errors.Wrap(err, "invalid detailed param"))
}
pipelines, err := n.PipelineService.ListPipelines(req.Request.Context(), apis.ListPipelineRequest{
Projects: projectNames,
Query: req.QueryParameter("query"),
Detailed: detailed,
})
if err != nil {
log.Logger.Errorf("list pipeline failure %s", err.Error())
@@ -223,15 +266,18 @@ func (p *pipelineAPIInterface) listPipelines(req *restful.Request, res *restful.
}
}
func (p *pipelineAPIInterface) getPipeline(req *restful.Request, res *restful.Response) {
pipeline := req.Request.Context().Value(apis.CtxKeyPipeline).(apis.PipelineBase)
func (n *projectAPIInterface) getPipeline(req *restful.Request, res *restful.Response) {
pipeline, err := n.PipelineService.GetPipeline(req.Request.Context(), req.PathParameter(Pipeline), true)
if err != nil {
return
}
if err := res.WriteEntity(pipeline); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) createPipeline(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) createPipeline(req *restful.Request, res *restful.Response) {
var createReq apis.CreatePipelineRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
@@ -241,19 +287,25 @@ func (p *pipelineAPIInterface) createPipeline(req *restful.Request, res *restful
bcode.ReturnError(req, res, err)
return
}
pipelineBase, err := p.PipelineService.CreatePipeline(req.Request.Context(), createReq)
pipelineBase, err := n.PipelineService.CreatePipeline(req.Request.Context(), createReq)
if err != nil {
log.Logger.Errorf("create pipeline failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
_, err = n.ContextService.InitContext(req.Request.Context(), pipelineBase.Project.Name, pipelineBase.Name)
if err != nil {
log.Logger.Errorf("init pipeline context failure: %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(pipelineBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) updatePipeline(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) updatePipeline(req *restful.Request, res *restful.Response) {
var updateReq apis.UpdatePipelineRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
@@ -263,8 +315,8 @@ func (p *pipelineAPIInterface) updatePipeline(req *restful.Request, res *restful
bcode.ReturnError(req, res, err)
return
}
base := req.Request.Context().Value(apis.CtxKeyPipeline).(apis.PipelineBase)
pipelineBase, err := p.PipelineService.UpdatePipeline(req.Request.Context(), base.Name, base.Project, updateReq)
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
pipelineBase, err := n.PipelineService.UpdatePipeline(req.Request.Context(), pipeline.Name, updateReq)
if err != nil {
log.Logger.Errorf("update pipeline failure %s", err.Error())
bcode.ReturnError(req, res, err)
@@ -276,43 +328,43 @@ func (p *pipelineAPIInterface) updatePipeline(req *restful.Request, res *restful
}
}
func (p *pipelineAPIInterface) deletePipeline(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) deletePipeline(req *restful.Request, res *restful.Response) {
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
err := p.PipelineService.DeletePipeline(req.Request.Context(), pipeline)
err := n.PipelineService.DeletePipeline(req.Request.Context(), pipeline)
if err != nil {
log.Logger.Errorf("delete pipeline failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(pipeline.PipelineMeta); err != nil {
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) runPipeline(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) runPipeline(req *restful.Request, res *restful.Response) {
var runReq apis.RunPipelineRequest
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
if err := req.ReadEntity(runReq); err != nil {
if err := req.ReadEntity(&runReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
err := p.PipelineService.RunPipeline(req.Request.Context(), pipeline, runReq)
run, err := n.PipelineService.RunPipeline(req.Request.Context(), pipeline, runReq)
if err != nil {
log.Logger.Errorf("run pipeline failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(pipeline.PipelineMeta); err != nil {
if err := res.WriteEntity(run); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) stopPipeline(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
err := p.PipelineRunService.StopPipelineRun(req.Request.Context(), pipelineRun.PipelineRunBase)
func (n *projectAPIInterface) stopPipeline(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
err := n.PipelineRunService.StopPipelineRun(req.Request.Context(), pipelineRun.PipelineRunBase)
if err != nil {
log.Logger.Errorf("stop pipeline failure %s", err.Error())
bcode.ReturnError(req, res, err)
@@ -324,9 +376,9 @@ func (p *pipelineAPIInterface) stopPipeline(req *restful.Request, res *restful.R
}
}
func (p *pipelineAPIInterface) listPipelineRuns(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) listPipelineRuns(req *restful.Request, res *restful.Response) {
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
pipelineRuns, err := p.PipelineRunService.ListPipelineRuns(req.Request.Context(), pipeline)
pipelineRuns, err := n.PipelineRunService.ListPipelineRuns(req.Request.Context(), pipeline)
if err != nil {
log.Logger.Errorf("list pipeline runs failure %s", err.Error())
bcode.ReturnError(req, res, err)
@@ -338,33 +390,65 @@ func (p *pipelineAPIInterface) listPipelineRuns(req *restful.Request, res *restf
}
}
func (p *pipelineAPIInterface) getPipelineRun(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
func (n *projectAPIInterface) getPipelineRun(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
if err := res.WriteEntity(pipelineRun.PipelineRunBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) getPipelineRunStatus(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
func (n *projectAPIInterface) getPipelineRunStatus(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
if err := res.WriteEntity(pipelineRun.Status); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) getPipelineRunLog(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) getPipelineRunLog(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
logs, err := n.PipelineRunService.GetPipelineRunLog(req.Request.Context(), *pipelineRun, req.QueryParameter("step"))
if err != nil {
log.Logger.Errorf("get pipeline run log failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(logs); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) getPipelineRunOutput(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) getPipelineRunOutput(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
output, err := n.PipelineRunService.GetPipelineRunOutput(req.Request.Context(), *pipelineRun, req.QueryParameter("step"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(output); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (p *pipelineAPIInterface) deletePipelineRun(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(apis.PipelineRun)
err := p.PipelineRunService.DeletePipelineRun(req.Request.Context(), pipelineRun.PipelineRunMeta)
func (n *projectAPIInterface) getPipelineRunInput(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
input, err := n.PipelineRunService.GetPipelineRunInput(req.Request.Context(), *pipelineRun, req.QueryParameter("step"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(input); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectAPIInterface) deletePipelineRun(req *restful.Request, res *restful.Response) {
pipelineRun := req.Request.Context().Value(&apis.CtxKeyPipelineRun).(*apis.PipelineRun)
err := n.PipelineRunService.DeletePipelineRun(req.Request.Context(), pipelineRun.PipelineRunMeta)
if err != nil {
log.Logger.Errorf("delete pipeline run failure %s", err.Error())
bcode.ReturnError(req, res, err)
@@ -376,11 +460,11 @@ func (p *pipelineAPIInterface) deletePipelineRun(req *restful.Request, res *rest
}
}
func (p *pipelineAPIInterface) listContextValues(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) listContextValues(req *restful.Request, res *restful.Response) {
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
contextValues, err := p.ContextService.ListContexts(req.Request.Context(), pipeline.Project, pipeline.Name)
contextValues, err := n.ContextService.ListContexts(req.Request.Context(), pipeline.Project.Name, pipeline.Name)
if err != nil {
log.Logger.Errorf("list context values failure %s", err.Error())
log.Logger.Errorf("list context values failure: %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
@@ -390,7 +474,7 @@ func (p *pipelineAPIInterface) listContextValues(req *restful.Request, res *rest
}
}
func (p *pipelineAPIInterface) createContextValue(req *restful.Request, res *restful.Response) {
func (n *projectAPIInterface) createContextValue(req *restful.Request, res *restful.Response) {
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
var createReq apis.CreateContextValuesRequest
if err := req.ReadEntity(&createReq); err != nil {
@@ -403,7 +487,7 @@ func (p *pipelineAPIInterface) createContextValue(req *restful.Request, res *res
}
pipelineCtx := apis.Context(createReq)
_, err := p.ContextService.CreateContext(req.Request.Context(), pipeline.Project, pipeline.Name, pipelineCtx)
_, err := n.ContextService.CreateContext(req.Request.Context(), pipeline.Project.Name, pipeline.Name, pipelineCtx)
if err != nil {
log.Logger.Errorf("create context failure %s", err.Error())
bcode.ReturnError(req, res, err)
@@ -415,8 +499,8 @@ func (p *pipelineAPIInterface) createContextValue(req *restful.Request, res *res
}
}
func (p *pipelineAPIInterface) updateContextValue(req *restful.Request, res *restful.Response) {
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContex).(apis.Context)
func (n *projectAPIInterface) updateContextValue(req *restful.Request, res *restful.Response) {
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContext).(apis.Context)
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
var updateReq apis.UpdateContextValuesRequest
if err := req.ReadEntity(&updateReq); err != nil {
@@ -428,7 +512,7 @@ func (p *pipelineAPIInterface) updateContextValue(req *restful.Request, res *res
return
}
pipelineCtx := apis.Context{Name: plCtx.Name, Values: updateReq.Values}
_, err := p.ContextService.UpdateContext(req.Request.Context(), pipeline.Project, pipeline.Name, pipelineCtx)
_, err := n.ContextService.UpdateContext(req.Request.Context(), pipeline.Project.Name, pipeline.Name, pipelineCtx)
if err != nil {
log.Logger.Errorf("update context failure %s", err.Error())
bcode.ReturnError(req, res, err)
@@ -440,10 +524,10 @@ func (p *pipelineAPIInterface) updateContextValue(req *restful.Request, res *res
}
}
func (p *pipelineAPIInterface) deleteContextValue(req *restful.Request, res *restful.Response) {
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContex).(apis.Context)
func (n *projectAPIInterface) deleteContextValue(req *restful.Request, res *restful.Response) {
plCtx := req.Request.Context().Value(&apis.CtxKeyPipelineContext).(apis.Context)
pipeline := req.Request.Context().Value(&apis.CtxKeyPipeline).(apis.PipelineBase)
err := p.ContextService.DeleteContext(req.Request.Context(), pipeline.Project, pipeline.Name, plCtx.Name)
err := n.ContextService.DeleteContext(req.Request.Context(), pipeline.Project.Name, pipeline.Name, plCtx.Name)
if err != nil {
log.Logger.Errorf("delete context failure %s", err.Error())
bcode.ReturnError(req, res, err)
@@ -455,8 +539,18 @@ func (p *pipelineAPIInterface) deleteContextValue(req *restful.Request, res *res
}
}
func (p *pipelineAPIInterface) pipelineCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
pipeline, err := p.PipelineService.GetPipeline(req.Request.Context(), req.PathParameter("pipelineName"), req.QueryParameter("projectName"))
func (n *projectAPIInterface) projectCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
project, err := n.ProjectService.GetProject(req.Request.Context(), req.PathParameter(Project))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyProject, project))
chain.ProcessFilter(req, res)
}
func (n *projectAPIInterface) pipelineCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
pipeline, err := n.PipelineService.GetPipeline(req.Request.Context(), req.PathParameter(Pipeline), false)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -465,8 +559,8 @@ func (p *pipelineAPIInterface) pipelineCheckFilter(req *restful.Request, res *re
chain.ProcessFilter(req, res)
}
func (p *pipelineAPIInterface) pipelineContextCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
contexts, err := p.ContextService.ListContexts(req.Request.Context(), req.PathParameter("pipelineName"), req.QueryParameter("projectName"))
func (n *projectAPIInterface) pipelineContextCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
contexts, err := n.ContextService.ListContexts(req.Request.Context(), req.PathParameter(Project), req.PathParameter(Pipeline))
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -477,20 +571,22 @@ func (p *pipelineAPIInterface) pipelineContextCheckFilter(req *restful.Request,
bcode.ReturnError(req, res, bcode.ErrContextNotFound)
return
}
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyPipelineContex, apis.Context{
req.Request = req.Request.WithContext(context.WithValue(req.Request.Context(), &apis.CtxKeyPipelineContext, apis.Context{
Name: contextName,
Values: contextValue,
}))
chain.ProcessFilter(req, res)
}
func (p *pipelineAPIInterface) pipelineRunCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
func (n *projectAPIInterface) pipelineRunCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
meta := apis.PipelineRunMeta{
PipelineName: req.PathParameter(string(Pipeline)),
Project: req.QueryParameter(string(Project)),
PipelineRunName: req.PathParameter(string(PipelineRun)),
PipelineName: req.PathParameter(Pipeline),
Project: apis.NameAlias{
Name: req.PathParameter(Project),
},
PipelineRunName: req.PathParameter(PipelineRun),
}
run, err := p.PipelineRunService.GetPipelineRun(req.Request.Context(), meta)
run, err := n.PipelineRunService.GetPipelineRun(req.Request.Context(), meta)
if err != nil {
bcode.ReturnError(req, res, err)
return

View File

@@ -29,10 +29,14 @@ import (
)
type projectAPIInterface struct {
RbacService service.RBACService `inject:""`
ProjectService service.ProjectService `inject:""`
TargetService service.TargetService `inject:""`
ConfigService service.ConfigService `inject:""`
RbacService service.RBACService `inject:""`
ProjectService service.ProjectService `inject:""`
TargetService service.TargetService `inject:""`
ConfigService service.ConfigService `inject:""`
PipelineService service.PipelineService `inject:""`
PipelineRunService service.PipelineRunService `inject:""`
ContextService service.ContextService `inject:""`
RBACService service.RBACService `inject:""`
}
// NewProjectAPIInterface new project APIInterface
@@ -305,6 +309,7 @@ func (n *projectAPIInterface) GetWebServiceRoute() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListTerraformProviderResponse{}))
initPipelineRoutes(ws, n)
ws.Filter(authCheckFilter)
return ws
}

View File

@@ -38,6 +38,9 @@ const KubeVelaProjectReadGroupPrefix = "kubevela:project-ro:"
// KubeVelaAdminGroupPrefix the prefix kubevela admin
const KubeVelaAdminGroupPrefix = "kubevela:admin:"
// TemplateReaderGroup This group includes the permission that read the ConfigMap in the vela-system namespace.
const TemplateReaderGroup = "template-reader"
// ContextWithUserInfo extract user from context (parse username and project) for impersonation
func ContextWithUserInfo(ctx context.Context) context.Context {
if !features.APIServerFeatureGate.Enabled(features.APIServerEnableImpersonation) {

View File

@@ -33,3 +33,6 @@ var ErrDeleteEnvButAppExist = NewBcode(400, 11005, "env can't be deleted as app
// ErrEnvTargetConflict in one project, one target can only belong to one env
var ErrEnvTargetConflict = NewBcode(400, 11006, "in one project, one target can only belong to one env.")
// ErrEnvTargetNotAllowDelete means can not remove existing targets from this environment, because there are applications deployed.
var ErrEnvTargetNotAllowDelete = NewBcode(400, 11007, "target can not be deleted, because there are applications deployed.")

View File

@@ -21,4 +21,22 @@ var (
ErrContextNotFound = NewBcode(400, 17001, "pipeline context is not found")
// ErrContextAlreadyExist means the certain context already exists
ErrContextAlreadyExist = NewBcode(400, 17002, "pipeline context of pipeline already exist")
// ErrGetPipelineInfo means failed to get pipeline info
ErrGetPipelineInfo = NewBcode(400, 17003, "get pipeline info failed")
// ErrPipelineNotExist means specific pipeline not found
ErrPipelineNotExist = NewBcode(404, 17004, "failed to find log pods")
// ErrGetPodsLogs means failed to get pods logs
ErrGetPodsLogs = NewBcode(500, 17006, "failed to get pods logs")
// ErrReadSourceLog means failed to read source log
ErrReadSourceLog = NewBcode(500, 17007, "failed to read log from URL source")
// ErrGetContextBackendData means failed to get context backend data
ErrGetContextBackendData = NewBcode(500, 17008, "failed to get context backend data")
// ErrNoSteps means pipeline doesn't have a step
ErrNoSteps = NewBcode(400, 17009, "pipeline step number is zero")
// ErrPipelineExist means the pipeline is exist
ErrPipelineExist = NewBcode(400, 17010, "the pipeline is exist")
// ErrPipelineRunFinished means pipeline run is finished
ErrPipelineRunFinished = NewBcode(400, 17011, "pipeline run is finished")
// ErrWrongMode means the pipeline run mode is wrong
ErrWrongMode = NewBcode(400, 17012, "wrong pipeline run mode, only \"DAG\" and \"StepByStep\" are supported")
)

View File

@@ -452,15 +452,24 @@ func (p *Parser) loadWorkflowToAppfile(ctx context.Context, af *Appfile) error {
Steps: workflowv1alpha1.WorkflowModeDAG,
SubSteps: workflowv1alpha1.WorkflowModeDAG,
}
if wfSpec := af.app.Spec.Workflow; wfSpec != nil && len(wfSpec.Steps) > 0 {
if wfSpec := af.app.Spec.Workflow; wfSpec != nil {
app := af.app
mode := wfSpec.Mode
if wfSpec.Ref != "" && mode == nil {
wf := &workflowv1alpha1.Workflow{}
if err := af.WorkflowClient(p.client).Get(ctx, ktypes.NamespacedName{Namespace: af.app.Namespace, Name: app.Spec.Workflow.Ref}, wf); err != nil {
return err
}
mode = wf.Mode
}
af.WorkflowSteps = wfSpec.Steps
af.WorkflowMode.Steps = workflowv1alpha1.WorkflowModeStep
if wfSpec.Mode != nil {
if wfSpec.Mode.Steps != "" {
af.WorkflowMode.Steps = wfSpec.Mode.Steps
if mode != nil {
if mode.Steps != "" {
af.WorkflowMode.Steps = mode.Steps
}
if wfSpec.Mode.SubSteps != "" {
af.WorkflowMode.SubSteps = wfSpec.Mode.SubSteps
if mode.SubSteps != "" {
af.WorkflowMode.SubSteps = mode.SubSteps
}
}
}

View File

@@ -338,7 +338,7 @@ func (a *ApplicationPrivilege) GetRoles() []client.Object {
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{"core.oam.dev"},
Resources: []string{"applications", "policies", "workflows"},
Resources: []string{"applications", "policies", "workflows", "workflowruns"},
Verbs: verbs,
},
},

View File

@@ -222,7 +222,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
isUpdate := app.Status.Workflow.Message != "" && workflowInstance.Status.Message == ""
workflowInstance.Status.Phase = workflowState
app.Status.Workflow = workflow.ConvertWorkflowStatus(workflowInstance.Status, app.Status.Workflow.AppRevision)
logCtx.Info("Workflow return state=%s", workflowState)
logCtx.Info(fmt.Sprintf("Workflow return state=%s", workflowState))
switch workflowState {
case workflowv1alpha1.WorkflowStateSuspending:
if duration := executor.GetSuspendBackoffWaitTime(); duration > 0 {

View File

@@ -60,7 +60,6 @@ import (
"github.com/oam-dev/kubevela/pkg/oam/testutil"
"github.com/oam-dev/kubevela/pkg/oam/util"
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/workflow"
)
// TODO: Refactor the tests to not copy and paste duplicated code 10 times
@@ -1207,7 +1206,7 @@ var _ = Describe("Test Application Controller", func() {
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationWorkflowSuspending))
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(workflow.MessageSuspendFailedAfterRetries))
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(wfTypes.MessageSuspendFailedAfterRetries))
Expect(checkApp.Status.Workflow.Steps[1].Phase).Should(BeEquivalentTo(workflowv1alpha1.WorkflowStepPhaseFailed))
Expect(checkApp.Status.Workflow.Steps[1].Reason).Should(BeEquivalentTo(wfTypes.StatusReasonFailedAfterRetries))
@@ -1327,7 +1326,7 @@ var _ = Describe("Test Application Controller", func() {
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationWorkflowSuspending))
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(workflow.MessageSuspendFailedAfterRetries))
Expect(checkApp.Status.Workflow.Message).Should(BeEquivalentTo(wfTypes.MessageSuspendFailedAfterRetries))
Expect(checkApp.Status.Workflow.Steps[1].Phase).Should(BeEquivalentTo(workflowv1alpha1.WorkflowStepPhaseFailed))
Expect(checkApp.Status.Workflow.Steps[1].Reason).Should(BeEquivalentTo(wfTypes.StatusReasonFailedAfterRetries))

View File

@@ -0,0 +1,281 @@
/*
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 application
import (
"context"
"encoding/json"
"fmt"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/oam/testutil"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var _ = Describe("Test Application with apply-once policy", func() {
ctx := context.Background()
initReplicas := int32(2)
targetReplicas := int32(5)
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "apply-once-policy-test",
},
}
baseApp := &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "baseApp",
Namespace: ns.Name,
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "baseComp",
Type: "worker",
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
Traits: []common.ApplicationTrait{{
Type: "scale",
Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"replicas": %d}`, initReplicas))},
}},
},
},
Policies: []v1beta1.AppPolicy{{
Name: "basePolicy",
Type: "apply-once",
Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"enable": true,"rules": [{"selector": { "resourceTypes": ["Deployment"] }, "strategy": {"affect":"%s", "path": ["spec.replicas"] }}]}`, ""))},
}},
},
}
worker := &v1beta1.ComponentDefinition{}
workerCdDefJson, _ := yaml.YAMLToJSON([]byte(componentDefYaml))
scaleTrait := &v1beta1.TraitDefinition{}
scaleTdDefJson, _ := yaml.YAMLToJSON([]byte(scaleTraitDefYaml))
BeforeEach(func() {
Expect(k8sClient.Create(ctx, ns.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(json.Unmarshal(workerCdDefJson, worker)).Should(BeNil())
Expect(k8sClient.Create(ctx, worker.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(json.Unmarshal(scaleTdDefJson, scaleTrait)).Should(BeNil())
Expect(k8sClient.Create(ctx, scaleTrait.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
Context("Test Application with apply-once policy in different affect stage", func() {
It(" Affect not set or affect is empty , test effective globally", func() {
app := baseApp.DeepCopy()
app.SetName("apply-once-app-1")
app.Spec.Components[0].Name = "apply-once-comp-1"
By("step 1. Create app , replicas: 2")
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
By("step 2. Update deployment to replicas: 5 ")
Eventually(updateDeployReplicas(ctx, app, targetReplicas), time.Second*3, time.Microsecond*300).Should(BeNil())
By("step 3. Check OnUpdate, e.g. update app's component with new properties, replicas should be 5 ")
for i := 0; i <= 3; i++ {
properties := &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"cmd":["sleep","%d"],"image":"busybox"}`, i*1000))}
Eventually(updateApp(ctx, app, properties), time.Second*3, time.Microsecond*300).Should(BeNil())
testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
deploy := new(v1.Deployment)
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
Expect(*deploy.Spec.Replicas).Should(Equal(targetReplicas))
}
By("step 4. Check OnStateKeep, replicas also should be 5 ")
rk, err := resourcekeeper.NewResourceKeeper(context.Background(), k8sClient, app)
Expect(err).Should(BeNil())
for i := 0; i <= 3; i++ {
// state keep :5
Expect(rk.StateKeep(context.Background())).Should(BeNil())
deploy := new(v1.Deployment)
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
Expect(*deploy.Spec.Replicas).Should(Equal(targetReplicas))
}
})
It("Affect: onStateKeep, test only effective when state keep", func() {
By("step 1. Create app , replicas: 2")
app := baseApp.DeepCopy()
app.SetName("apply-once-app-2")
app.Spec.Components[0].Name = "apply-once-comp-2"
app.Spec.Policies[0].Properties = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"enable": true,"rules": [{"selector": { "resourceTypes": ["Deployment"] }, "strategy": {"affect":"%s", "path": ["spec.replicas"] }}]}`, v1alpha1.ApplyOnceStrategyOnAppStateKeep))}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
By("step 2. Update deployment, replicas: 5 ")
Eventually(updateDeployReplicas(ctx, app, targetReplicas), time.Second*3, time.Microsecond*300).Should(BeNil())
By("step 3. Check OnStateKeep, replicas should be 5 ")
rk, err := resourcekeeper.NewResourceKeeper(context.Background(), k8sClient, app)
Expect(err).Should(BeNil())
for i := 0; i <= 3; i++ {
// state keep : use newest replicas
Expect(rk.StateKeep(context.Background())).Should(BeNil())
deploy := new(v1.Deployment)
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
Expect(*deploy.Spec.Replicas).Should(Equal(targetReplicas))
}
By("step 4. Check OnUpdate, e.g. update app's component with new properties, replicas should be 2 ")
for i := 0; i <= 3; i++ {
// onupdate: not use newest replicas
properties := &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"cmd":["sleep","%d"],"image":"busybox"}`, i*1000))}
Eventually(updateApp(ctx, app, properties), time.Second*3, time.Microsecond*300).Should(BeNil())
testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
deploy := new(v1.Deployment)
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
Expect(*deploy.Spec.Replicas).Should(Equal(initReplicas))
}
})
It("Affect: onUpdate , test only effective when updating the app", func() {
By("step 1. Create app , replicas: 2")
app := baseApp.DeepCopy()
app.SetName("apply-once-app-3")
app.Spec.Components[0].Name = "apply-once-comp-3"
app.Spec.Policies[0].Properties = &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"enable": true,"rules": [{"selector": { "resourceTypes": ["Deployment"] }, "strategy": {"affect":"%s", "path": ["spec.replicas"] }}]}`, v1alpha1.ApplyOnceStrategyOnAppUpdate))}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
By("step 2. Update deployment, replicas: 5 ")
Eventually(updateDeployReplicas(ctx, app, targetReplicas), time.Second*3, time.Microsecond*300).Should(BeNil())
By("step 3. Check OnUpdate, e.g. update app's component with new properties, replicas should be 5 ")
for i := 0; i <= 3; i++ {
// onUpdate : use newest replicas
properties := &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"cmd":["sleep","%d"],"image":"busybox"}`, i*1000))}
Eventually(updateApp(ctx, app, properties), time.Second*3, time.Microsecond*300).Should(BeNil())
testutil.ReconcileRetry(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
Eventually(waitAppRunning(ctx, app), 3*time.Second, 300*time.Second).Should(BeNil())
deploy := new(v1.Deployment)
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
Expect(*deploy.Spec.Replicas).Should(Equal(initReplicas))
}
By("step 4. Check OnStateKeep, replicas should be 2 ")
rk, err := resourcekeeper.NewResourceKeeper(context.Background(), k8sClient, app)
Expect(err).Should(BeNil())
for i := 0; i <= 3; i++ {
// state keep : not use newest replicas
Expect(rk.StateKeep(context.Background())).Should(BeNil())
deploy := new(v1.Deployment)
deployObjKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
Expect(k8sClient.Get(ctx, deployObjKey, deploy)).Should(BeNil())
Expect(*deploy.Spec.Replicas).Should(Equal(initReplicas))
}
})
})
})
func updateDeployReplicas(ctx context.Context, app *v1beta1.Application, targetReplicas int32) func() error {
return func() error {
deploy := new(v1.Deployment)
deployKey := client.ObjectKey{Name: app.Spec.Components[0].Name, Namespace: app.Namespace}
Expect(k8sClient.Get(ctx, deployKey, deploy)).Should(BeNil())
deploy.Spec.Replicas = &targetReplicas
return k8sClient.Update(ctx, deploy)
}
}
func waitAppRunning(ctx context.Context, app *v1beta1.Application) func() error {
return func() error {
appV1 := new(v1beta1.Application)
_, err := testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(app)})
if err != nil {
return err
}
if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(app), appV1); err != nil {
return err
}
if appV1.Status.Phase != common.ApplicationRunning {
return errors.New("app is not in running status")
}
return nil
}
}
func updateApp(ctx context.Context, app *v1beta1.Application, properties *runtime.RawExtension) func() error {
return func() error {
oldApp := new(v1beta1.Application)
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(app), oldApp)).Should(BeNil())
newApp := oldApp.DeepCopy()
newApp.Spec.Components[0].Properties = properties
return k8sClient.Update(ctx, newApp)
}
}
const (
scaleTraitDefYaml = `
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: Manually scale K8s pod for your workload which follows the pod spec in path 'spec.template'.
name: scale
namespace: vela-system
spec:
appliesToWorkloads:
- deployments.apps
- statefulsets.apps
podDisruptive: false
schematic:
cue:
template: |
parameter: {
// +usage=Specify the number of workload
replicas: *1 | int
}
// +patchStrategy=retainKeys
patch: spec: replicas: parameter.replicas
`
)

View File

@@ -23,6 +23,10 @@ import (
"fmt"
"time"
pkgmulticluster "github.com/kubevela/pkg/multicluster"
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
errors2 "github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
@@ -32,12 +36,6 @@ import (
"k8s.io/klog/v2"
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
pkgmulticluster "github.com/kubevela/pkg/multicluster"
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
"github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/common"
@@ -51,7 +49,7 @@ const (
var (
// ClusterGatewaySecretNamespace the namespace where cluster-gateway secret locates
ClusterGatewaySecretNamespace string
ClusterGatewaySecretNamespace = "vela-system"
)
// ClusterNameInContext extract cluster name from context
@@ -174,16 +172,6 @@ func UpgradeExistingClusterSecret(ctx context.Context, c client.Client) error {
return nil
}
// GetMulticlusterKubernetesClient get client with multicluster function enabled
func GetMulticlusterKubernetesClient() (client.Client, *rest.Config, error) {
k8sConfig, err := config.GetConfig()
if err != nil {
return nil, nil, err
}
k8sClient, err := Initialize(k8sConfig, false)
return k8sClient, k8sConfig, err
}
// ListExistingClusterSecrets list existing cluster secrets
func ListExistingClusterSecrets(ctx context.Context, c client.Client) ([]v1.Secret, error) {
secrets := &v1.SecretList{}

View File

@@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/auth"
"github.com/oam-dev/kubevela/pkg/features"
"github.com/oam-dev/kubevela/pkg/multicluster"
@@ -141,6 +142,10 @@ func (h *resourceKeeper) dispatch(ctx context.Context, manifests []*unstructured
if h.isShared(manifest) {
ao = append([]apply.ApplyOption{apply.SharedByApp(h.app)}, ao...)
}
manifest, err := ApplyStrategies(applyCtx, h, manifest, v1alpha1.ApplyOnceStrategyOnAppUpdate)
if err != nil {
return errors.Wrapf(err, "failed to apply once policy for application %s,%s", h.app.Name, err.Error())
}
return h.applicator.Apply(applyCtx, manifest, ao...)
}, manifests, MaxDispatchConcurrent)
return velaerrors.AggregateErrors(errs.([]error))

View File

@@ -24,6 +24,9 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"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/pkg/auth"
"github.com/oam-dev/kubevela/pkg/multicluster"
@@ -60,7 +63,7 @@ func (h *resourceKeeper) StateKeep(ctx context.Context) error {
return errors.Wrapf(err, "failed to decode resource %s from resourcetracker", mr.ResourceKey())
}
applyCtx := multicluster.ContextWithClusterName(ctx, mr.Cluster)
manifest, err = ApplyStrategies(applyCtx, h, manifest)
manifest, err = ApplyStrategies(applyCtx, h, manifest, v1alpha1.ApplyOnceStrategyOnAppStateKeep)
if err != nil {
return errors.Wrapf(err, "failed to apply once resource %s from resourcetracker %s", mr.ResourceKey(), rt.Name)
}
@@ -79,34 +82,49 @@ func (h *resourceKeeper) StateKeep(ctx context.Context) error {
}
// ApplyStrategies will generate manifest with applyOnceStrategy
func ApplyStrategies(ctx context.Context, h *resourceKeeper, manifest *unstructured.Unstructured) (*unstructured.Unstructured, error) {
func ApplyStrategies(ctx context.Context, h *resourceKeeper, manifest *unstructured.Unstructured, matchedAffectStage v1alpha1.ApplyOnceAffectStrategy) (*unstructured.Unstructured, error) {
if h.applyOncePolicy == nil {
return manifest, nil
}
applyOncePath := h.applyOncePolicy.FindStrategy(manifest)
if applyOncePath != nil {
un := new(unstructured.Unstructured)
un.SetAPIVersion(manifest.GetAPIVersion())
un.SetKind(manifest.GetKind())
err := h.Get(ctx, types.NamespacedName{Name: manifest.GetName(), Namespace: manifest.GetNamespace()}, un)
if err != nil {
return nil, err
}
for _, path := range applyOncePath.Path {
if path == "*" {
manifest = un.DeepCopy()
break
}
value, err := fieldpath.Pave(un.UnstructuredContent()).GetValue(path)
if err != nil {
return nil, err
}
err = fieldpath.Pave(manifest.UnstructuredContent()).SetValue(path, value)
strategy := h.applyOncePolicy.FindStrategy(manifest)
if strategy != nil {
affectStage := strategy.ApplyOnceAffectStrategy
if shouldMerge(affectStage, matchedAffectStage) {
un := new(unstructured.Unstructured)
un.SetAPIVersion(manifest.GetAPIVersion())
un.SetKind(manifest.GetKind())
err := h.Get(ctx, types.NamespacedName{Name: manifest.GetName(), Namespace: manifest.GetNamespace()}, un)
if err != nil {
if kerrors.IsNotFound(err) {
return manifest, nil
}
return nil, err
}
return mergeValue(strategy.Path, manifest, un)
}
}
return manifest, nil
}
func shouldMerge(affectStage v1alpha1.ApplyOnceAffectStrategy, matchedAffectType v1alpha1.ApplyOnceAffectStrategy) bool {
return affectStage == "" || affectStage == v1alpha1.ApplyOnceStrategyAlways || affectStage == matchedAffectType
}
func mergeValue(paths []string, manifest *unstructured.Unstructured, un *unstructured.Unstructured) (*unstructured.Unstructured, error) {
for _, path := range paths {
if path == "*" {
manifest = un.DeepCopy()
break
}
value, err := fieldpath.Pave(un.UnstructuredContent()).GetValue(path)
if err != nil {
return nil, err
}
err = fieldpath.Pave(manifest.UnstructuredContent()).SetValue(path, value)
if err != nil {
return nil, err
}
return manifest, nil
}
return manifest, nil
}

View File

@@ -1,11 +1,18 @@
#ApplyComponent: {
#provider: "oam"
#do: "component-apply"
cluster: *"" | string
env: *"" | string
namespace: *"" | string
#provider: "oam"
#do: "component-apply"
// +usage=The cluster to use
cluster: *"" | string
// +usage=The env to use
env: *"" | string
// +usage=The namespace to apply
namespace: *"" | string
// +usage=Whether to wait healthy of the applied component
waitHealthy: *true | bool
// +usage=The value of the component resource
value: {...}
// +usage=The patcher that will be applied to the resource, you can define the strategy of list merge through comments. Reference doc here: https://kubevela.io/docs/platform-engineers/traits/patch-trait#patch-in-workflow-step
patch?: {...}
...
}
@@ -26,6 +33,11 @@
#LoadComponets: {
#provider: "oam"
#do: "load"
// +usage=If specify `app`, use specified application to load its component resources otherwise use current application
app?: string
// +usage=The value of the components will be filled in this field after the action is executed, you can use value[componentName] to refer a specified component
value?: {...}
...
}

View File

@@ -19,6 +19,7 @@ package common
import (
"fmt"
pkgmulticluster "github.com/kubevela/pkg/multicluster"
"k8s.io/client-go/discovery"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -86,7 +87,9 @@ func (a *Args) GetClient() (client.Client, error) {
return nil, err
}
}
newClient, err := client.New(a.config, client.Options{Scheme: a.Schema})
newClient, err := pkgmulticluster.NewClient(a.config,
pkgmulticluster.ClientOptions{
Options: client.Options{Scheme: a.Schema}})
if err != nil {
return nil, err
}

View File

@@ -21,7 +21,19 @@ import (
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
"text/template"
"time"
"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/wercker/stern/stern"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/client-go/kubernetes"
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
authv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
@@ -78,10 +90,11 @@ func MergeNoConflictLabels(labels map[string]string) MutateOption {
// It will report an error if the labels conflict while it will override the annotations
func CreateOrUpdateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error {
err := CreateNamespace(ctx, kubeClient, name, options...)
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
// only if namespace don't have the env label that we need to update it
if apierrors.IsAlreadyExists(err) {
return UpdateNamespace(ctx, kubeClient, name, options...)
}
return UpdateNamespace(ctx, kubeClient, name, options...)
return err
}
// CreateNamespace will create a namespace with mutate option
@@ -198,3 +211,125 @@ func IsClusterScope(gvk schema.GroupVersionKind, mapper meta.RESTMapper) (bool,
isClusterScope := len(mappings) > 0 && mappings[0].Scope.Name() == meta.RESTScopeNameRoot
return isClusterScope, err
}
// GetPodsLogs get logs from pods
func GetPodsLogs(ctx context.Context, config *rest.Config, containerName string, selectPods []*querytypes.PodBase, tmpl string, logC chan<- string, tailLines *int64) error {
if err := verifyPods(selectPods); err != nil {
return err
}
podRegex := getPodRegex(selectPods)
pods, err := regexp.Compile(podRegex)
if err != nil {
return fmt.Errorf("fail to compile '%s' for logs query", podRegex)
}
container := regexp.MustCompile(".*")
if containerName != "" {
container = regexp.MustCompile(containerName + ".*")
}
// These pods are from the same namespace, so we can use the first one to get the namespace
namespace := selectPods[0].Metadata.Namespace
selector := labels.NewSelector()
// Only use the labels to select pod if query one pod's log. It is only used when query vela-core log
if len(selectPods) == 1 {
for k, v := range selectPods[0].Metadata.Labels {
req, _ := labels.NewRequirement(k, selection.Equals, []string{v})
if req != nil {
selector = selector.Add(*req)
}
}
}
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
added, removed, err := stern.Watch(ctx,
clientSet.CoreV1().Pods(namespace),
pods,
container,
nil,
[]stern.ContainerState{stern.RUNNING, stern.TERMINATED},
selector,
)
if err != nil {
return err
}
tails := make(map[string]*stern.Tail)
funs := map[string]interface{}{
"json": func(in interface{}) (string, error) {
b, err := json.Marshal(in)
if err != nil {
return "", err
}
return string(b), nil
},
"color": func(color color.Color, text string) string {
return color.SprintFunc()(text)
},
}
template, err := template.New("log").Funcs(funs).Parse(tmpl)
if err != nil {
return errors.Wrap(err, "unable to parse template")
}
go func() {
for p := range added {
id := p.GetID()
if tails[id] != nil {
continue
}
// 48h
dur, _ := time.ParseDuration("48h")
tail := stern.NewTail(p.Namespace, p.Pod, p.Container, template, &stern.TailOptions{
Timestamps: true,
SinceSeconds: int64(dur.Seconds()),
Exclude: nil,
Include: nil,
Namespace: false,
TailLines: tailLines, // default for all logs
})
tails[id] = tail
tail.Start(ctx, clientSet.CoreV1().Pods(p.Namespace), logC)
}
}()
go func() {
for p := range removed {
id := p.GetID()
if tails[id] == nil {
continue
}
tails[id].Close()
delete(tails, id)
}
}()
<-ctx.Done()
close(logC)
return nil
}
func getPodRegex(pods []*querytypes.PodBase) string {
var podNames []string
for _, pod := range pods {
podNames = append(podNames, fmt.Sprintf("(%s.*)", pod.Metadata.Name))
}
return strings.Join(podNames, "|")
}
func verifyPods(pods []*querytypes.PodBase) error {
if len(pods) == 0 {
return errors.New("no pods selected")
}
if len(pods) == 1 {
return nil
}
namespace := pods[0].Metadata.Namespace
for _, pod := range pods {
if pod.Metadata.Namespace != namespace {
return errors.New("cannot select pods from different namespaces")
}
}
return nil
}

View File

@@ -23,12 +23,12 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/client-go/rest"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"k8s.io/utils/pointer"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
@@ -60,7 +60,11 @@ var _ = BeforeSuite(func(done Done) {
By("new kube client")
cfg.Timeout = time.Minute * 2
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
scheme := common.Scheme
batchv1.AddToScheme(scheme)
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
Expect(err).Should(BeNil())
Expect(k8sClient).ToNot(BeNil())

View File

@@ -206,6 +206,15 @@ func init() {
DefaultGenListOptionFunc: kustomization2AnyListOption,
DisableFilterByOwnerReference: true,
},
ChildrenResourcesRule{
SubResources: buildSubResources([]*SubResourceSelector{
{
ResourceType: ResourceType{APIVersion: "v1", Kind: "Pod"},
listOptions: cronJobLabelListOption,
},
}),
GroupResourceType: GroupResourceType{Group: "batch/v1", Kind: "CronJob"},
},
)
}
@@ -292,9 +301,9 @@ type WorkloadUnstructured struct {
unstructured.Unstructured
}
// GetSelector get the selector from the field path: spec.selector
func (w *WorkloadUnstructured) GetSelector() (labels.Selector, error) {
value, exist, err := unstructured.NestedFieldNoCopy(w.Object, "spec", "selector")
// GetSelector get the selector from the field path
func (w *WorkloadUnstructured) GetSelector(fields ...string) (labels.Selector, error) {
value, exist, err := unstructured.NestedFieldNoCopy(w.Object, fields...)
if err != nil {
return nil, err
}
@@ -311,9 +320,27 @@ func (w *WorkloadUnstructured) GetSelector() (labels.Selector, error) {
return labels.Everything(), nil
}
func (w *WorkloadUnstructured) convertLabel2Selector(fields ...string) (labels.Selector, error) {
value, exist, err := unstructured.NestedFieldNoCopy(w.Object, fields...)
if err != nil {
return nil, err
}
if !exist {
return labels.Everything(), nil
}
if v, ok := value.(map[string]interface{}); ok {
var selector v1.LabelSelector
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(v, &selector.MatchLabels); err != nil {
return nil, err
}
return v1.LabelSelectorAsSelector(&selector)
}
return labels.Everything(), nil
}
var defaultWorkloadLabelListOption genListOptionFunc = func(obj unstructured.Unstructured) (client.ListOptions, error) {
workload := WorkloadUnstructured{obj}
deploySelector, err := workload.GetSelector()
deploySelector, err := workload.GetSelector("spec", "selector")
if err != nil {
return client.ListOptions{}, err
}
@@ -333,6 +360,15 @@ var service2EndpointListOption = func(obj unstructured.Unstructured) (client.Lis
return client.ListOptions{Namespace: svc.Namespace, LabelSelector: stsSelector}, nil
}
var cronJobLabelListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
workload := WorkloadUnstructured{obj}
cronJobSelector, err := workload.convertLabel2Selector("spec", "jobTemplate", "metadata", "labels")
if err != nil {
return client.ListOptions{}, err
}
return client.ListOptions{Namespace: obj.GetNamespace(), LabelSelector: cronJobSelector}, nil
}
var helmRelease2AnyListOption = func(obj unstructured.Unstructured) (client.ListOptions, error) {
hrSelector, err := v1.LabelSelectorAsSelector(&v1.LabelSelector{MatchLabels: map[string]string{
"helm.toolkit.fluxcd.io/name": obj.GetName(),

View File

@@ -28,10 +28,13 @@ import (
"github.com/fluxcd/source-controller/api/v1beta2"
"github.com/stretchr/testify/assert"
v12 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
types2 "k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -201,6 +204,91 @@ func TestService2EndpointOption(t *testing.T) {
assert.Equal(t, "service-name=test,uid=test-uid", l.LabelSelector.String())
}
func TestConvertLabel2Selector(t *testing.T) {
cronJob1 := `
apiVersion: batch/v1
kind: CronJob
metadata:
name: cronjob1
labels:
app: cronjob1
spec:
schedule: "* * * * *"
jobTemplate:
metadata:
labels:
app: cronJob1
spec:
template:
spec:
containers:
- name: cronjob
image: busybox
command: ["/bin/sh","-c","date"]
restartPolicy: Never
`
obj := unstructured.Unstructured{}
dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
_, _, err := dec.Decode([]byte(cronJob1), nil, &obj)
assert.NoError(t, err)
workload1 := WorkloadUnstructured{obj}
selector1, err := workload1.convertLabel2Selector("not", "exist")
assert.Equal(t, selector1, labels.Everything())
assert.NoError(t, err)
selector2, err := workload1.convertLabel2Selector()
assert.Equal(t, selector2, nil)
assert.Error(t, err)
_, _, err = dec.Decode([]byte(cronJob1), nil, &obj)
assert.NoError(t, err)
workload2 := WorkloadUnstructured{obj}
selector3, err := workload2.convertLabel2Selector("apiVersion")
assert.Equal(t, selector3, labels.Everything())
assert.NoError(t, err)
_, _, err = dec.Decode([]byte(cronJob1), nil, &obj)
assert.NoError(t, err)
workload3 := WorkloadUnstructured{obj}
selector4, err := workload3.convertLabel2Selector("spec", "jobTemplate", "metadata", "labels")
assert.Equal(t, selector4.String(), "app=cronJob1")
assert.NoError(t, err)
}
func TestCronJobLabelListOption(t *testing.T) {
cronJob := `
apiVersion: batch/v1
kind: CronJob
metadata:
name: cronjob1
labels:
app: cronjob1
spec:
schedule: "* * * * *"
jobTemplate:
metadata:
labels:
app: cronJob1
spec:
template:
spec:
containers:
- name: cronjob
image: busybox
command: ["/bin/sh","-c","date"]
restartPolicy: Never
`
// convert yaml to unstructured
obj := unstructured.Unstructured{}
dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
_, _, err := dec.Decode([]byte(cronJob), nil, &obj)
assert.NoError(t, err)
l, err := cronJobLabelListOption(obj)
assert.NoError(t, err)
assert.Equal(t, "app=cronJob1", l.LabelSelector.String())
}
func TestServiceStatus(t *testing.T) {
lbHealthSvc := v1.Service{Spec: v1.ServiceSpec{Type: v1.ServiceTypeLoadBalancer}, Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
@@ -1212,9 +1300,66 @@ var _ = Describe("unit-test to e2e test", func() {
},
},
}
pod5 := v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
ObjectMeta: metav1.ObjectMeta{
Name: "pod5",
Namespace: "test-namespace",
Labels: map[string]string{
"app": "cronJob1",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Image: "nginx",
Name: "nginx",
},
},
},
}
cronJob1 := batchv1.CronJob{
TypeMeta: metav1.TypeMeta{
APIVersion: "batch/v1",
Kind: "CronJob",
},
ObjectMeta: metav1.ObjectMeta{
Name: "cronjob1",
Namespace: "test-namespace",
Labels: map[string]string{
"app": "cronJob1",
},
},
Spec: batchv1.CronJobSpec{
Schedule: "* * * * *",
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "cronJob1",
},
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
RestartPolicy: "OnFailure",
Containers: []v1.Container{
{
Image: "nginx",
Name: "nginx",
},
},
},
},
},
},
},
}
var objectList []client.Object
objectList = append(objectList, &deploy1, &deploy1, &rs1, &rs2, &rs3, &rs4, &pod1, &pod2, &pod3, &rs4, &pod4)
objectList = append(objectList, &deploy1, &deploy1, &rs1, &rs2, &rs3, &rs4, &pod1, &pod2, &pod3, &rs4, &pod4, &pod5, &cronJob1)
BeforeEach(func() {
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, deploy1.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
@@ -1225,6 +1370,7 @@ var _ = Describe("unit-test to e2e test", func() {
Expect(k8sClient.Create(ctx, pod1.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, pod2.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, pod3.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, pod5.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
cRs4 := rs4.DeepCopy()
Expect(k8sClient.Create(ctx, cRs4)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
@@ -1238,6 +1384,7 @@ var _ = Describe("unit-test to e2e test", func() {
},
})
Expect(k8sClient.Create(ctx, cPod4)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, cronJob1.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
})
AfterEach(func() {
@@ -1285,6 +1432,18 @@ var _ = Describe("unit-test to e2e test", func() {
nil, nil, true)
Expect(err).Should(BeNil())
Expect(len(items3)).Should(BeEquivalentTo(1))
u4 := unstructured.Unstructured{}
u4.SetNamespace(cronJob1.Namespace)
u4.SetName(cronJob1.Name)
u4.SetAPIVersion("batch/v1")
u4.SetKind("CronJob")
Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: u4.GetNamespace(), Name: u4.GetName()}, &u4))
Expect(err).Should(BeNil())
item4, err := listItemByRule(ctx, k8sClient, ResourceType{APIVersion: "v1", Kind: "Pod"}, u4,
cronJobLabelListOption, nil, true)
Expect(err).Should(BeNil())
Expect(len(item4)).Should(BeEquivalentTo(1))
})
It("iterate resource", func() {

View File

@@ -22,9 +22,12 @@ import (
"io"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
wfUtils "github.com/kubevela/workflow/pkg/utils"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/service"
@@ -36,27 +39,24 @@ import (
errors3 "github.com/oam-dev/kubevela/pkg/utils/errors"
)
// WorkflowOperator is opratior handler for workflow's resume/rollback/restart
type WorkflowOperator interface {
Suspend(ctx context.Context, app *v1beta1.Application) error
Resume(ctx context.Context, app *v1beta1.Application) error
Rollback(ctx context.Context, app *v1beta1.Application) error
Restart(ctx context.Context, app *v1beta1.Application) error
Terminate(ctx context.Context, app *v1beta1.Application) error
// NewApplicationWorkflowOperator get an workflow operator with k8sClient, ioWriter(optional, useful for cli) and application
func NewApplicationWorkflowOperator(cli client.Client, w io.Writer, app *v1beta1.Application) wfUtils.WorkflowOperator {
return appWorkflowOperator{
cli: cli,
outputWriter: w,
application: app,
}
}
// NewWorkflowOperator get an workflow operator with k8sClient and ioWriter(optional, useful for cli)
func NewWorkflowOperator(cli client.Client, w io.Writer) WorkflowOperator {
return wfOperator{cli: cli, outputWriter: w}
}
type wfOperator struct {
type appWorkflowOperator struct {
cli client.Client
outputWriter io.Writer
application *v1beta1.Application
}
// Suspend a running workflow
func (wo wfOperator) Suspend(ctx context.Context, app *v1beta1.Application) error {
func (wo appWorkflowOperator) Suspend(ctx context.Context) error {
app := wo.application
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
}
@@ -80,7 +80,8 @@ func (wo wfOperator) Suspend(ctx context.Context, app *v1beta1.Application) erro
}
// Resume a suspending workflow
func (wo wfOperator) Resume(ctx context.Context, app *v1beta1.Application) error {
func (wo appWorkflowOperator) Resume(ctx context.Context) error {
app := wo.application
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
}
@@ -108,9 +109,28 @@ func (wo wfOperator) Resume(ctx context.Context, app *v1beta1.Application) error
// Rollback a running in middle state workflow.
// nolint
func (wo wfOperator) Rollback(ctx context.Context, app *v1beta1.Application) error {
func (wo appWorkflowOperator) Rollback(ctx context.Context) error {
app := wo.application
if app.Status.Workflow != nil && !app.Status.Workflow.Terminated && !app.Status.Workflow.Suspend && !app.Status.Workflow.Finished {
return fmt.Errorf("can not rollback a running workflow")
}
if oam.GetPublishVersion(app) == "" {
return fmt.Errorf("app without public version cannot rollback")
if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
return fmt.Errorf("the latest revision is not set: %s", app.Name)
}
// get the last revision
revision := &v1beta1.ApplicationRevision{}
if err := wo.cli.Get(ctx, types.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
return fmt.Errorf("failed to get the latest revision: %w", err)
}
app.Spec = revision.Spec.Application.Spec
if err := wo.cli.Status().Update(ctx, app); err != nil {
return err
}
fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
return nil
}
appRevs, err := application.GetSortedAppRevisions(ctx, wo.cli, app.Name, app.Namespace)
@@ -249,7 +269,8 @@ func (wo wfOperator) Rollback(ctx context.Context, app *v1beta1.Application) err
}
// Restart a terminated or finished workflow.
func (wo wfOperator) Restart(ctx context.Context, app *v1beta1.Application) error {
func (wo appWorkflowOperator) Restart(ctx context.Context) error {
app := wo.application
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
}
@@ -263,7 +284,8 @@ func (wo wfOperator) Restart(ctx context.Context, app *v1beta1.Application) erro
return wo.writeOutputF("Successfully restart workflow: %s\n", app.Name)
}
func (wo wfOperator) Terminate(ctx context.Context, app *v1beta1.Application) error {
func (wo appWorkflowOperator) Terminate(ctx context.Context) error {
app := wo.application
if err := service.TerminateWorkflow(context.TODO(), wo.cli, app); err != nil {
return err
}
@@ -271,7 +293,7 @@ func (wo wfOperator) Terminate(ctx context.Context, app *v1beta1.Application) er
return wo.writeOutputF("Successfully terminate workflow: %s\n", app.Name)
}
func (wo wfOperator) writeOutput(str string) error {
func (wo appWorkflowOperator) writeOutput(str string) error {
if wo.outputWriter == nil {
return nil
}
@@ -279,7 +301,7 @@ func (wo wfOperator) writeOutput(str string) error {
return err
}
func (wo wfOperator) writeOutputF(format string, a ...interface{}) error {
func (wo appWorkflowOperator) writeOutputF(format string, a ...interface{}) error {
if wo.outputWriter == nil {
return nil
}

View File

@@ -48,10 +48,10 @@ var _ = Describe("Kruise rollout test", func() {
StartTime: metav1.Now(),
}
Expect(k8sClient.Status().Update(ctx, &checkApp)).Should(BeNil())
operator := NewWorkflowOperator(k8sClient, nil)
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
checkApp = v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
Expect(operator.Suspend(ctx, checkApp.DeepCopy())).Should(BeNil())
Expect(operator.Suspend(ctx)).Should(BeNil())
checkApp = v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
Expect(checkApp.Status.Workflow.Suspend).Should(BeEquivalentTo(true))
@@ -60,8 +60,8 @@ var _ = Describe("Kruise rollout test", func() {
It("Resume workflow", func() {
checkApp := v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
operator := NewWorkflowOperator(k8sClient, nil)
Expect(operator.Resume(ctx, &checkApp)).Should(BeNil())
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
Expect(operator.Resume(ctx)).Should(BeNil())
checkApp = v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
Expect(checkApp.Status.Workflow.Suspend).Should(BeEquivalentTo(false))
@@ -70,8 +70,8 @@ var _ = Describe("Kruise rollout test", func() {
It("Terminate workflow", func() {
checkApp := v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
operator := NewWorkflowOperator(k8sClient, nil)
Expect(operator.Terminate(ctx, &checkApp)).Should(BeNil())
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
Expect(operator.Terminate(ctx)).Should(BeNil())
checkApp = v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
Expect(checkApp.Status.Workflow.Terminated).Should(BeEquivalentTo(true))
@@ -80,8 +80,8 @@ var _ = Describe("Kruise rollout test", func() {
It("Restart workflow", func() {
checkApp := v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
operator := NewWorkflowOperator(k8sClient, nil)
Expect(operator.Restart(ctx, &checkApp)).Should(BeNil())
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
Expect(operator.Restart(ctx)).Should(BeNil())
checkApp = v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
Expect(checkApp.Status.Workflow).Should(BeNil())
@@ -99,8 +99,8 @@ var _ = Describe("Kruise rollout test", func() {
checkApp.Annotations = map[string]string{
oam.AnnotationPublishVersion: "v2",
}
operator := NewWorkflowOperator(k8sClient, nil)
Expect(operator.Rollback(ctx, checkApp.DeepCopy())).Should(BeNil())
operator := NewApplicationWorkflowOperator(k8sClient, nil, checkApp.DeepCopy())
Expect(operator.Rollback(ctx)).Should(BeNil())
checkApp = v1beta1.Application{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())

View File

@@ -20,6 +20,7 @@ import (
"fmt"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
wfTypes "github.com/kubevela/workflow/pkg/types"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
@@ -30,16 +31,9 @@ var (
DisableRecorder = false
)
const (
// MessageTerminatedFailedAfterRetries is the message of failed after retries
MessageTerminatedFailedAfterRetries = "The workflow terminates automatically because the failed times of steps have reached the limit"
// MessageSuspendFailedAfterRetries is the message of failed after retries
MessageSuspendFailedAfterRetries = "The workflow suspends automatically because the failed times of steps have reached the limit"
)
// IsFailedAfterRetry check if application is hang due to FailedAfterRetry
func IsFailedAfterRetry(app *oamcore.Application) bool {
return app.Status.Workflow != nil && (app.Status.Workflow.Message == MessageTerminatedFailedAfterRetries || app.Status.Workflow.Message == MessageSuspendFailedAfterRetries)
return app.Status.Workflow != nil && app.Status.Workflow.Message == wfTypes.MessageSuspendFailedAfterRetries
}
// ConvertWorkflowStatus convert workflow run status to workflow status

View File

@@ -31,19 +31,20 @@ import (
)
const (
addonRegistryType = "type"
addonEndpoint = "endpoint"
addonOssBucket = "bucket"
addonPath = "path"
addonGitToken = "gitToken"
addonOssType = "OSS"
addonGitType = "git"
addonGiteeType = "gitee"
addonGitlabType = "gitlab"
addonHelmType = "helm"
addonUsername = "username"
addonPassword = "password"
addonRepoName = "repoName"
addonRegistryType = "type"
addonEndpoint = "endpoint"
addonOssBucket = "bucket"
addonPath = "path"
addonGitToken = "gitToken"
addonOssType = "OSS"
addonGitType = "git"
addonGiteeType = "gitee"
addonGitlabType = "gitlab"
addonHelmType = "helm"
addonUsername = "username"
addonPassword = "password"
// only gitlab registry need set this flag
addonRepoName = "gitlabRepoName"
addonHelmInsecureSkipTLS = "insecureSkipTLS"
)
@@ -67,10 +68,12 @@ func NewAddonRegistryCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.
// NewAddAddonRegistryCommand return an addon registry create command
func NewAddAddonRegistryCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Short: "Add an addon registry.",
Long: "Add an addon registry.",
Example: `"vela addon registry add <my-registry-name> --type OSS --endpoint=<URL> --bucket=<bukect-name> or vela addon registry add my-repo --type git --endpoint=<URL> --path=<OSS-ptah> --gitToken=<git token>"`,
Use: "add",
Short: "Add an addon registry.",
Long: "Add an addon registry.",
Example: `add a helm repo registry: vela addon registry add --type=helm my-repo --endpoint=<URL>
add a github registry: vela addon registry add my-repo --type git --endpoint=<URL> --path=<ptah> --token=<git token>"
add a gitlab registry: vela addon registry add my-repo --type gitlab --endpoint=<URL> --gitlabRepoName=<repoName> --path=<path> --token=<git token>`,
RunE: func(cmd *cobra.Command, args []string) error {
registry, err := getRegistryFromArgs(cmd, args)
if err != nil {
@@ -298,6 +301,7 @@ func parseArgsFromFlag(cmd *cobra.Command) {
cmd.Flags().StringP(addonGitToken, "", "", "specify the github repo token")
cmd.Flags().StringP(addonUsername, "", "", "specify the Helm addon registry username")
cmd.Flags().StringP(addonPassword, "", "", "specify the Helm addon registry password")
cmd.Flags().StringP(addonRepoName, "", "", "specify the gitlab addon registry repoName")
cmd.Flags().BoolP(addonHelmInsecureSkipTLS, "", false,
"specify the Helm addon registry skip tls verify")
}

View File

@@ -29,7 +29,6 @@ import (
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
pkgmulticluster "github.com/kubevela/pkg/multicluster"
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
"github.com/oam-dev/cluster-gateway/pkg/config"
@@ -66,11 +65,6 @@ func ClusterCommandGroup(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comm
},
// check if cluster-gateway is ready
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
config, err := c.GetConfig()
if err != nil {
return err
}
config.Wrap(pkgmulticluster.NewTransportWrapper())
k8sClient, err := c.GetClient()
if err != nil {
return errors.Wrapf(err, "failed to get k8s client")

View File

@@ -261,6 +261,7 @@ func wrapStepName(step workflowv1alpha1.StepStatus) string {
}
func unwrapStepName(step string) string {
step = strings.TrimPrefix(step, " ")
switch {
case strings.HasPrefix(step, emojiSucceed):
return strings.TrimPrefix(step, emojiSucceed)

View File

@@ -18,24 +18,16 @@ package cli
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"text/template"
"time"
"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/wercker/stern/stern"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/client-go/kubernetes"
"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/utils"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/util"
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
@@ -99,45 +91,25 @@ type Args struct {
}
func (l *Args) printPodLogs(ctx context.Context, ioStreams util.IOStreams, selectPod *querytypes.PodBase, filters []string) error {
pod, err := regexp.Compile(selectPod.Metadata.Name + ".*")
if err != nil {
return fmt.Errorf("fail to compile '%s' for logs query", selectPod.Metadata.Name+".*")
}
container := regexp.MustCompile(".*")
if l.ContainerName != "" {
container = regexp.MustCompile(l.ContainerName + ".*")
}
namespace := selectPod.Metadata.Namespace
selector := labels.NewSelector()
for k, v := range selectPod.Metadata.Labels {
req, _ := labels.NewRequirement(k, selection.Equals, []string{v})
if req != nil {
selector = selector.Add(*req)
}
}
config, err := l.Args.GetConfig()
if err != nil {
return err
}
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
added, removed, err := stern.Watch(ctx,
clientSet.CoreV1().Pods(namespace),
pod,
container,
nil,
[]stern.ContainerState{stern.RUNNING, stern.TERMINATED},
selector,
)
if err != nil {
return err
}
tails := make(map[string]*stern.Tail)
logC := make(chan string, 1024)
var t string
switch l.Output {
case "default":
if color.NoColor {
t = "{{.ContainerName}} {{.Message}}"
} else {
t = "{{color .ContainerColor .ContainerName}} {{.Message}}"
}
case "raw":
t = "{{.Message}}"
case "json":
t = "{{json .}}\n"
}
go func() {
for {
select {
@@ -158,71 +130,11 @@ func (l *Args) printPodLogs(ctx context.Context, ioStreams util.IOStreams, selec
}
}()
var t string
switch l.Output {
case "default":
if color.NoColor {
t = "{{.ContainerName}} {{.Message}}"
} else {
t = "{{color .ContainerColor .ContainerName}} {{.Message}}"
}
case "raw":
t = "{{.Message}}"
case "json":
t = "{{json .}}\n"
}
funs := map[string]interface{}{
"json": func(in interface{}) (string, error) {
b, err := json.Marshal(in)
if err != nil {
return "", err
}
return string(b), nil
},
"color": func(color color.Color, text string) string {
return color.SprintFunc()(text)
},
}
template, err := template.New("log").Funcs(funs).Parse(t)
err = utils.GetPodsLogs(ctx, config, l.ContainerName, []*querytypes.PodBase{selectPod}, t, logC, nil)
if err != nil {
return errors.Wrap(err, "unable to parse template")
return err
}
go func() {
for p := range added {
id := p.GetID()
if tails[id] != nil {
continue
}
// 48h
dur, _ := time.ParseDuration("48h")
tail := stern.NewTail(p.Namespace, p.Pod, p.Container, template, &stern.TailOptions{
Timestamps: true,
SinceSeconds: int64(dur.Seconds()),
Exclude: nil,
Include: nil,
Namespace: false,
TailLines: nil, // default for all logs
})
tails[id] = tail
tail.Start(ctx, clientSet.CoreV1().Pods(p.Namespace), logC)
}
}()
go func() {
for p := range removed {
id := p.GetID()
if tails[id] == nil {
continue
}
tails[id].Close()
delete(tails, id)
}
}()
<-ctx.Done()
return nil
}

View File

@@ -224,15 +224,6 @@ func formatEndpoints(endpoints []types2.ServiceEndpoint) [][]string {
}
func printAppEndpoints(ctx context.Context, appName string, namespace string, f Filter, velaC common.Args, skipEmptyTable bool) error {
config, err := velaC.GetConfig()
if err != nil {
return err
}
client, err := multicluster.Initialize(config, false)
if err != nil {
return err
}
velaC.SetClient(client)
endpoints, err := GetServiceEndpoints(ctx, appName, namespace, velaC, f)
if err != nil {
return err

View File

@@ -86,59 +86,10 @@ func (ui *UserInput) read() (string, error) {
//
// format = "yaml" / "json" / "jsonpath={.field}"
func formatApplicationString(format string, app *v1beta1.Application) (string, error) {
var ret string
if format == "" {
return "", fmt.Errorf("no format provided")
}
// No, we don't want managedFields, get rid of it.
app.ManagedFields = nil
switch format {
case "yaml":
b, err := yaml.Marshal(app)
if err != nil {
return "", err
}
ret = string(b)
case "json":
b, err := json.MarshalIndent(app, "", " ")
if err != nil {
return "", err
}
ret = string(b)
default:
// format is not any of json/yaml/jsonpath, not supported
if !strings.HasPrefix(format, "jsonpath") {
return "", fmt.Errorf("output %s is not supported", format)
}
// format = jsonpath
s := strings.Split(format, "=")
if len(s) < 2 {
return "", fmt.Errorf("jsonpath template format specified but no template given")
}
path, err := get.RelaxedJSONPathExpression(s[1])
if err != nil {
return "", err
}
jp := jsonpath.New("").AllowMissingKeys(true)
err = jp.Parse(path)
if err != nil {
return "", err
}
buf := &bytes.Buffer{}
err = jp.Execute(buf, app)
if err != nil {
return "", err
}
ret = buf.String()
}
return ret, nil
return printObj(format, app)
}
// AskToChooseOnePod will ask user to select one pod
@@ -231,24 +182,28 @@ func AskToChooseOneService(services []types.ResourceItem, selectPort bool) (*typ
}
func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevision) (string, error) {
// No, we don't want managedFields, get rid of it.
apprev.ManagedFields = nil
return printObj(format, apprev)
}
func printObj(format string, obj interface{}) (string, error) {
var ret string
if format == "" {
return "", fmt.Errorf("no format provided")
}
// No, we don't want managedFields, get rid of it.
apprev.ManagedFields = nil
switch format {
case "yaml":
b, err := yaml.Marshal(apprev)
b, err := yaml.Marshal(obj)
if err != nil {
return "", err
}
ret = string(b)
case "json":
b, err := json.MarshalIndent(apprev, "", " ")
b, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return "", err
}
@@ -260,7 +215,7 @@ func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevi
}
// format = jsonpath
s := strings.Split(format, "=")
s := strings.SplitN(format, "=", 2)
if len(s) < 2 {
return "", fmt.Errorf("jsonpath template format specified but no template given")
}
@@ -276,7 +231,7 @@ func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevi
}
buf := &bytes.Buffer{}
err = jp.Execute(buf, apprev)
err = jp.Execute(buf, obj)
if err != nil {
return "", err
}

View File

@@ -25,6 +25,7 @@ import (
"gotest.tools/assert"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
)
@@ -89,6 +90,23 @@ status: {}
str, err = formatApplicationString("jsonpath={.apiVersion}", app)
assert.NilError(t, err)
assert.Equal(t, str, "core.oam.dev/v1beta1")
str, err = formatApplicationString("jsonpath={.spec.components[?(@.name==\"test-server\")].type}", &v1beta1.Application{
ObjectMeta: v1.ObjectMeta{
Name: "test-app",
Namespace: "dev",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "test-server",
Type: "webservice",
},
},
},
})
assert.NilError(t, err)
assert.Equal(t, str, "webservice")
}
func TestConvertApplicationRevisionTo(t *testing.T) {
@@ -200,6 +218,32 @@ status:
},
},
}, exp: Exp{out: "core.oam.dev/v1beta1", err: ""}},
"jsonpath filter expression": {format: "jsonpath={.spec.application.spec.components[?(@.name==\"test-server\")].type}", apprev: &v1beta1.ApplicationRevision{
TypeMeta: v1.TypeMeta{
Kind: "ApplicationRevision",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: v1.ObjectMeta{
Name: "test-apprev",
Namespace: "dev",
},
Spec: v1beta1.ApplicationRevisionSpec{
Application: v1beta1.Application{
ObjectMeta: v1.ObjectMeta{
Name: "test-app",
Namespace: "dev",
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "test-server",
Type: "webservice",
},
},
},
},
},
}, exp: Exp{out: "webservice", err: ""}},
"jsonpath with error": {format: "jsonpath", apprev: &v1beta1.ApplicationRevision{
TypeMeta: v1.TypeMeta{
Kind: "ApplicationRevision",

View File

@@ -26,7 +26,6 @@ import (
"github.com/spf13/cobra"
pkgmulticluster "github.com/kubevela/pkg/multicluster"
"github.com/kubevela/workflow/pkg/cue/model/value"
"github.com/oam-dev/kubevela/apis/types"
@@ -372,7 +371,6 @@ func QueryValue(ctx context.Context, velaC common.Args, queryView *velaql.QueryV
if err != nil {
return nil, err
}
config.Wrap(pkgmulticluster.NewTransportWrapper())
client, err := velaC.GetClient()
if err != nil {
return nil, err

View File

@@ -25,7 +25,6 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/pkg/errors"
"github.com/spf13/cobra"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
pkgmulticluster "github.com/kubevela/pkg/multicluster"
@@ -33,15 +32,12 @@ import (
wfTypes "github.com/kubevela/workflow/pkg/types"
wfUtils "github.com/kubevela/workflow/pkg/utils"
common2 "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/oam"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
"github.com/oam-dev/kubevela/pkg/workflow/operation"
"github.com/oam-dev/kubevela/references/appfile"
)
// NewWorkflowCommand create `workflow` command
@@ -49,252 +45,143 @@ func NewWorkflowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
cmd := &cobra.Command{
Use: "workflow",
Short: "Operate application delivery workflow.",
Long: "Operate the Workflow during Application Delivery. Note that workflow command is both valid for Application Workflow and WorkflowRun. The command will try to find the Application first, if not found, it will try to find WorkflowRun. You can also specify the resource type by using --type flag.",
Long: "Operate the Workflow during Application Delivery. Note that workflow command is both valid for Application Workflow and WorkflowRun(expect for [restart, rollout] command, they're only valid for Application Workflow). The command will try to find the Application first, if not found, it will try to find WorkflowRun. You can also specify the resource type by using --type flag.",
Annotations: map[string]string{
types.TagCommandType: types.TypeCD,
},
}
wargs := &WorkflowArgs{
Args: c,
Writer: ioStreams.Out,
}
cmd.AddCommand(
NewWorkflowSuspendCommand(c, ioStreams),
NewWorkflowResumeCommand(c, ioStreams),
NewWorkflowTerminateCommand(c, ioStreams),
NewWorkflowRestartCommand(c, ioStreams),
NewWorkflowRollbackCommand(c, ioStreams),
NewWorkflowLogsCommand(c, ioStreams),
NewWorkflowSuspendCommand(c, ioStreams, wargs),
NewWorkflowResumeCommand(c, ioStreams, wargs),
NewWorkflowTerminateCommand(c, ioStreams, wargs),
NewWorkflowRestartCommand(c, ioStreams, wargs),
NewWorkflowRollbackCommand(c, ioStreams, wargs),
NewWorkflowLogsCommand(c, ioStreams, wargs),
)
return cmd
}
// NewWorkflowSuspendCommand create workflow suspend command
func NewWorkflowSuspendCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
func NewWorkflowSuspendCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "suspend",
Short: "Suspend an application workflow.",
Long: "Suspend an application workflow in cluster.",
Example: "vela workflow suspend <application-name>",
PreRun: checkApplicationNotRunning(c),
PreRun: wargs.checkWorkflowNotComplete(),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify application name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
ctx := context.Background()
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return err
}
config, err := c.GetConfig()
if err != nil {
return err
}
config.Wrap(pkgmulticluster.NewTransportWrapper())
cli, err := c.GetClient()
if err != nil {
return err
}
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Suspend(context.Background(), app)
return wargs.Operator.Suspend(ctx)
},
}
addNamespaceAndEnvArg(cmd)
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
return cmd
}
// NewWorkflowResumeCommand create workflow resume command
func NewWorkflowResumeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
func NewWorkflowResumeCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "resume",
Short: "Resume a suspend application workflow.",
Long: "Resume a suspend application workflow in cluster.",
Example: "vela workflow resume <application-name>",
PreRun: checkApplicationNotRunning(c),
PreRun: wargs.checkWorkflowNotComplete(),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify application name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
ctx := context.Background()
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return err
}
config, err := c.GetConfig()
if err != nil {
return err
}
config.Wrap(pkgmulticluster.NewTransportWrapper())
cli, err := c.GetClient()
if err != nil {
return err
}
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Resume(context.Background(), app)
return wargs.Operator.Resume(ctx)
},
}
addNamespaceAndEnvArg(cmd)
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
return cmd
}
// NewWorkflowTerminateCommand create workflow terminate command
func NewWorkflowTerminateCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
func NewWorkflowTerminateCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "terminate",
Short: "Terminate an application workflow.",
Long: "Terminate an application workflow in cluster.",
Example: "vela workflow terminate <application-name>",
PreRun: checkApplicationNotRunning(c),
Short: "Terminate an workflow.",
Long: "Terminate an workflow in cluster.",
Example: "vela workflow terminate <workflow-name>",
PreRun: wargs.checkWorkflowNotComplete(),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify application name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
ctx := context.Background()
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return err
}
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
}
cli, err := c.GetClient()
if err != nil {
return err
}
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Terminate(context.Background(), app)
return wargs.Operator.Terminate(ctx)
},
}
addNamespaceAndEnvArg(cmd)
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
return cmd
}
// NewWorkflowRestartCommand create workflow restart command
func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "restart",
Short: "Restart an application workflow.",
Long: "Restart an application workflow in cluster.",
Example: "vela workflow restart <application-name>",
PreRun: checkApplicationNotRunning(c),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify application name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
ctx := context.Background()
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return err
}
config, err := c.GetConfig()
if err != nil {
return err
}
config.Wrap(pkgmulticluster.NewTransportWrapper())
cli, err := c.GetClient()
if err != nil {
return err
}
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Restart(context.Background(), app)
return wargs.Operator.Restart(ctx)
},
}
addNamespaceAndEnvArg(cmd)
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
return cmd
}
// NewWorkflowRollbackCommand create workflow rollback command
func NewWorkflowRollbackCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
func NewWorkflowRollbackCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "rollback",
Short: "Rollback an application workflow to the latest revision.",
Long: "Rollback an application workflow to the latest revision.",
Example: "vela workflow rollback <application-name>",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify application name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
ctx := context.Background()
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return err
}
config, err := c.GetConfig()
if err != nil {
return err
}
config.Wrap(pkgmulticluster.NewTransportWrapper())
cli, err := c.GetClient()
if err != nil {
return err
}
if app.Status.Workflow != nil && !app.Status.Workflow.Terminated && !app.Status.Workflow.Suspend && !app.Status.Workflow.Finished {
return fmt.Errorf("can not rollback a running workflow")
}
if oam.GetPublishVersion(app) == "" {
if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
return fmt.Errorf("the latest revision is not set: %s", app.Name)
}
// get the last revision
revision := &v1beta1.ApplicationRevision{}
if err := cli.Get(context.TODO(), k8stypes.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
return fmt.Errorf("failed to get the latest revision: %w", err)
}
app.Spec = revision.Spec.Application.Spec
if err := cli.Status().Update(context.TODO(), app); err != nil {
return err
}
fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
return nil
}
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Rollback(context.Background(), app)
return wargs.Operator.Rollback(ctx)
},
}
addNamespaceAndEnvArg(cmd)
cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
return cmd
}
// NewWorkflowLogsCommand create workflow logs command
func NewWorkflowLogsCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
wargs := &WorkflowArgs{Args: c}
func NewWorkflowLogsCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
cmd := &cobra.Command{
Use: "logs",
Short: "Tail logs for workflow steps",
Long: "Tail logs for workflow steps, note that you need to use op.#Logs in step definition to set the log config of the step.",
Example: "vela workflow logs <application-name>",
Example: "vela workflow logs <workflow-name>",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify Application or WorkflowRun name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
return err
}
cli, err := c.GetClient()
if err != nil {
return err
}
ctx := context.Background()
if err := wargs.getWorkflowInstance(ctx, cli, namespace, args[0]); err != nil {
if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
return err
}
return wargs.printStepLogs(ctx, cli, ioStream)
@@ -312,6 +199,8 @@ type WorkflowArgs struct {
Type string
Output string
ControllerLabels map[string]string
Operator wfUtils.WorkflowOperator
Writer io.Writer
Args common.Args
StepName string
App *v1beta1.Application
@@ -324,7 +213,24 @@ const (
instanceTypeWorkflowRun string = "workflow"
)
func (w *WorkflowArgs) getWorkflowInstance(ctx context.Context, cli client.Client, namespace, name string) error {
func (w *WorkflowArgs) getWorkflowInstance(ctx context.Context, cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("please specify the name of application/workflow")
}
name := args[0]
namespace, err := GetFlagNamespaceOrEnv(cmd, w.Args)
if err != nil {
return err
}
cli, err := w.Args.GetClient()
if err != nil {
return err
}
config, err := w.Args.GetConfig()
if err != nil {
return err
}
config.Wrap(pkgmulticluster.NewTransportWrapper())
switch w.Type {
case "":
app := &v1beta1.Application{}
@@ -384,6 +290,7 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
EndTime: status.EndTime,
},
}
w.Operator = operation.NewApplicationWorkflowOperator(cli, w.Writer, w.App)
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-core"}
case instanceTypeWorkflowRun:
var steps []workflowv1alpha1.WorkflowStep
@@ -404,6 +311,7 @@ func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.
Steps: steps,
Status: w.WorkflowRun.Status,
}
w.Operator = wfUtils.NewWorkflowRunOperator(cli, w.Writer, w.WorkflowRun)
w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-workflow"}
default:
return fmt.Errorf("unknown workflow instance type %s", w.Type)
@@ -422,7 +330,7 @@ func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioS
}
logConfig, err := wfUtils.GetLogConfigFromStep(ctx, cli, w.WorkflowInstance.Status.ContextBackend.Name, w.WorkflowInstance.Name, w.WorkflowInstance.Namespace, w.StepName)
if err != nil {
return err
return errors.WithMessage(err, fmt.Sprintf("step [%s]", w.StepName))
}
if err := selectStepLogSource(logConfig); err != nil {
return err
@@ -453,11 +361,17 @@ func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioS
}
func (w *WorkflowArgs) selectWorkflowStep() error {
steps := make(map[string]workflowv1alpha1.WorkflowStepStatus)
stepsKey := make([]string, 0)
for _, step := range w.WorkflowInstance.Status.Steps {
stepsKey = append(stepsKey, wrapStepName(step.StepStatus))
for _, sub := range step.SubStepsStatus {
stepsKey = append(stepsKey, fmt.Sprintf(" %s", wrapStepName(sub)))
}
}
if len(stepsKey) == 0 {
return fmt.Errorf("workflow is not start")
}
prompt := &survey.Select{
Message: "Select a step to show logs:",
Options: stepsKey,
@@ -465,21 +379,7 @@ func (w *WorkflowArgs) selectWorkflowStep() error {
var stepName string
err := survey.AskOne(prompt, &stepName, survey.WithValidator(survey.Required))
if err != nil {
return fmt.Errorf("failed to select %s: %w", w.StepName, err)
}
if step := steps[w.StepName]; step.Type == wfTypes.WorkflowStepTypeStepGroup {
stepsKey := make([]string, 0)
for _, sub := range step.SubStepsStatus {
stepsKey = append(stepsKey, wrapStepName(sub))
}
prompt := &survey.Select{
Message: "Select a sub step to show logs:",
Options: stepsKey,
}
err := survey.AskOne(prompt, &stepName, survey.WithValidator(survey.Required))
if err != nil {
return fmt.Errorf("failed to select %s: %w", w.StepName, err)
}
return fmt.Errorf("failed to select step %s: %w", unwrapStepName(w.StepName), err)
}
w.StepName = unwrapStepName(stepName)
return nil
@@ -534,21 +434,12 @@ func (w *WorkflowArgs) printResourceLogs(ctx context.Context, cli client.Client,
return l.printPodLogs(ctx, ioStreams, selectPod, filters)
}
func checkApplicationNotRunning(c common.Args) func(cmd *cobra.Command, args []string) {
func (w *WorkflowArgs) checkWorkflowNotComplete() func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
// Any error will be returned to let the normal execution report the error
if len(args) < 1 {
if err := w.getWorkflowInstance(context.Background(), cmd, args); err != nil {
return
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
return
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return
}
if app.Status.Phase == common2.ApplicationRunning {
if w.WorkflowInstance.Status.Phase == workflowv1alpha1.WorkflowStateSucceeded {
cmd.Printf("%s workflow not allowed because application %s is running\n", cmd.Use, args[0])
os.Exit(1)
}

View File

@@ -63,7 +63,7 @@ func TestWorkflowSuspend(t *testing.T) {
expectedErr error
}{
"no app name specified": {
expectedErr: fmt.Errorf("must specify application name"),
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
},
"workflow not running": {
app: &v1beta1.Application{
@@ -74,7 +74,7 @@ func TestWorkflowSuspend(t *testing.T) {
Spec: workflowSpec,
Status: common.AppStatus{},
},
expectedErr: fmt.Errorf("the workflow in application is not running"),
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
},
"suspend successfully": {
app: &v1beta1.Application{
@@ -95,7 +95,7 @@ func TestWorkflowSuspend(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowSuspendCommand(c, ioStream)
cmd := NewWorkflowSuspendCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
@@ -145,7 +145,7 @@ func TestWorkflowResume(t *testing.T) {
expectedErr error
}{
"no app name specified": {
expectedErr: fmt.Errorf("must specify application name"),
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
},
"workflow not suspended": {
app: &v1beta1.Application{
@@ -170,7 +170,7 @@ func TestWorkflowResume(t *testing.T) {
Spec: workflowSpec,
Status: common.AppStatus{},
},
expectedErr: fmt.Errorf("the workflow in application is not running"),
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
},
"workflow terminated": {
app: &v1beta1.Application{
@@ -225,7 +225,7 @@ func TestWorkflowResume(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowResumeCommand(c, ioStream)
cmd := NewWorkflowResumeCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
@@ -285,7 +285,7 @@ func TestWorkflowTerminate(t *testing.T) {
expectedErr error
}{
"no app name specified": {
expectedErr: fmt.Errorf("must specify application name"),
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
},
"workflow not running": {
app: &v1beta1.Application{
@@ -296,7 +296,7 @@ func TestWorkflowTerminate(t *testing.T) {
Spec: workflowSpec,
Status: common.AppStatus{},
},
expectedErr: fmt.Errorf("the workflow in application is not running"),
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
},
"terminate successfully": {
app: &v1beta1.Application{
@@ -350,7 +350,7 @@ func TestWorkflowTerminate(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowTerminateCommand(c, ioStream)
cmd := NewWorkflowTerminateCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
@@ -412,7 +412,7 @@ func TestWorkflowRestart(t *testing.T) {
expectedErr error
}{
"no app name specified": {
expectedErr: fmt.Errorf("must specify application name"),
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
},
"workflow not running": {
app: &v1beta1.Application{
@@ -423,7 +423,7 @@ func TestWorkflowRestart(t *testing.T) {
Spec: workflowSpec,
Status: common.AppStatus{},
},
expectedErr: fmt.Errorf("the workflow in application is not running"),
expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
},
"restart successfully": {
app: &v1beta1.Application{
@@ -444,7 +444,7 @@ func TestWorkflowRestart(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowRestartCommand(c, ioStream)
cmd := NewWorkflowRestartCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
@@ -496,12 +496,12 @@ func TestWorkflowRollback(t *testing.T) {
expectedErr error
}{
"no app name specified": {
expectedErr: fmt.Errorf("must specify application name"),
expectedErr: fmt.Errorf("please specify the name of application/workflow"),
},
"workflow running": {
app: &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "workflow-not-running",
Name: "workflow-running",
Namespace: "default",
},
Spec: workflowSpec,
@@ -569,7 +569,7 @@ func TestWorkflowRollback(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowRollbackCommand(c, ioStream)
cmd := NewWorkflowRollbackCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})

View File

@@ -44,6 +44,7 @@ import (
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/helm"
util2 "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/docgen/fix"
)
// DescriptionUndefined indicates the description is not defined
@@ -215,10 +216,17 @@ func GetTraitsFromClusterWithValidateOption(ctx context.Context, namespace strin
var templateErrors []error
for _, td := range traitDefs.Items {
tmp, err := GetCapabilityByTraitDefinitionObject(td)
if err != nil {
templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name))
continue
var tmp *types.Capability
var err error
// FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed
if td.Name == "container-image" {
tmp = fix.CapContainerImage
} else {
tmp, err = GetCapabilityByTraitDefinitionObject(td)
if err != nil {
templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name))
continue
}
}
tmp.Namespace = namespace
if validateFlag {

View File

@@ -2,31 +2,33 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: export-config
name: export2config
namespace: default
spec:
components:
- name: express-server
type: webservice
properties:
image: oamdev/hello-world
port: 8000
- name: export2config-demo-server
type: webservice
properties:
image: oamdev/hello-world
port: 8000
workflow:
steps:
- name: apply-server
type: apply-component
outputs:
outputs:
- name: status
valueFrom: output.status.conditions[0].message
properties:
component: express-server
component: export2config-demo-server
- name: export-config
type: export-config
type: export2config
inputs:
- from: status
parameterKey: data.serverstatus
properties:
configName: my-configmap
data:
testkey: testvalue
testkey: |
testvalue
value-line-2
```

View File

@@ -6,7 +6,7 @@ metadata:
namespace: default
spec:
components:
- name: express-server
- name: express-server-sec
type: webservice
properties:
image: oamdev/hello-world
@@ -19,14 +19,16 @@ spec:
- name: status
valueFrom: output.status.conditions[0].message
properties:
component: express-server
component: express-server-sec
- name: export-secret
type: export-secret
type: export2secret
inputs:
- from: status
parameterKey: data.serverstatus
properties:
secretName: my-secret
data:
testkey: testvalue
testkey: |
testvalue
value-line-2
```

View File

@@ -0,0 +1,34 @@
/*
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 fix
import (
"encoding/json"
"github.com/oam-dev/kubevela/apis/types"
)
var (
// CapContainerImage is the cap for container image
CapContainerImage *types.Capability
)
// FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed
func init() {
legacyJSON := `{"name":"container-image","type":"trait","template":"#PatchParams: {\n\t// +usage=Specify the name of the target container, if not set, use the component name\n\tcontainerName: *\"\" | string\n\t// +usage=Specify the image of the container\n\timage: string\n\t// +usage=Specify the image pull policy of the container\n\timagePullPolicy: *\"\" | \"IfNotPresent\" | \"Always\" | \"Never\"\n}\nPatchContainer: {\n\t_params: #PatchParams\n\tname: _params.containerName\n\t_baseContainers: context.output.spec.template.spec.containers\n\t_matchContainers_: [ for _container_ in _baseContainers if _container_.name == name {_container_}]\n\t_baseContainer: *_|_ | {...}\n\tif len(_matchContainers_) == 0 {\n\t\terr: \"container \\(name) not found\"\n\t}\n\tif len(_matchContainers_) \u003e 0 {\n\t\t// +patchStrategy=retainKeys\n\t\timage: _params.image\n\n\t\tif _params.imagePullPolicy != \"\" {\n\t\t\t// +patchStrategy=retainKeys\n\t\t\timagePullPolicy: _params.imagePullPolicy\n\t\t}\n\t}\n}\npatch: spec: template: spec: {\n\tif parameter.containers == _|_ {\n\t\t// +patchKey=name\n\t\tcontainers: [{\n\t\t\tPatchContainer \u0026 {_params: {\n\t\t\t\tif parameter.containerName == \"\" {\n\t\t\t\t\tcontainerName: context.name\n\t\t\t\t}\n\t\t\t\tif parameter.containerName != \"\" {\n\t\t\t\t\tcontainerName: parameter.containerName\n\t\t\t\t}\n\t\t\t\timage: parameter.image\n\t\t\t\timagePullPolicy: parameter.imagePullPolicy\n\t\t\t}}\n\t\t}]\n\t}\n\tif parameter.containers != _|_ {\n\t\t// +patchKey=name\n\t\tcontainers: [ for c in parameter.containers {\n\t\t\tif c.containerName == \"\" {\n\t\t\t\terr: \"containerName must be set for containers\"\n\t\t\t}\n\t\t\tif c.containerName != \"\" {\n\t\t\t\tPatchContainer \u0026 {_params: c}\n\t\t\t}\n\t\t}]\n\t}\n}\nparameter: *#PatchParams | close({\n\t// +usage=Specify the container image for multiple containers\n\tcontainers: [...#PatchParams]\n})\nerrs: [ for c in patch.spec.template.spec.containers if c.err != _|_ {c.err}]\n","parameters":[{"name":"containerName","default":"","usage":"Specify the name of the target container, if not set, use the component name","type":16},{"name":"image","required":true,"default":"","usage":"Specify the image of the container","type":16},{"name":"imagePullPolicy","default":"","usage":"Specify the image pull policy of the container","type":16}],"description":"Set the image of the container.","category":"cue","appliesTo":["deployments.apps","statefulsets.apps","daemonsets.apps","jobs.batch"],"kubetemplate":null}`
_ = json.Unmarshal([]byte(legacyJSON), &CapContainerImage)
}

View File

@@ -53,6 +53,7 @@ import (
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/terraform"
"github.com/oam-dev/kubevela/references/docgen/fix"
)
// ParseReference is used to include the common function `parseParameter`
@@ -74,7 +75,7 @@ func (ref *ParseReference) getCapabilities(ctx context.Context, c common.Args) (
case ref.Local != nil:
lcaps, err := ParseLocalFiles(ref.Local.Path, c)
if err != nil {
return nil, fmt.Errorf("failed to get capability from local file %s: %w", ref.DefinitionName, err)
return nil, fmt.Errorf("failed to get capability from local file %s: %w", ref.Local.Path, err)
}
for _, lcap := range lcaps {
caps = append(caps, *lcap)
@@ -519,6 +520,11 @@ func ParseLocalFiles(localFilePath string, c common.Args) ([]*types.Capability,
if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".cue") {
return nil
}
// FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed
if strings.Contains(path, "container-image") {
lcaps = append(lcaps, fix.CapContainerImage)
return nil
}
lcap, err := ParseLocalFile(path, c)
if err != nil {
return err
@@ -588,7 +594,7 @@ func ParseLocalFile(localFilePath string, c common.Args) (*types.Capability, err
}
lcap, err := ParseCapabilityFromUnstructured(mapper, pd, def.Unstructured)
if err != nil {
return nil, errors.Wrapf(err, "fail to parse definition to capability")
return nil, errors.Wrapf(err, "fail to parse definition to capability %s", def.GetName())
}
return &lcap, nil

View File

@@ -17,7 +17,10 @@ limitations under the License.
package e2e_apiserver_test
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"time"
. "github.com/onsi/ginkgo"
@@ -188,5 +191,26 @@ var _ = Describe("Test addon rest api", func() {
Expect(decodeResponseBody(res, &addonStatus)).Should(Succeed())
Expect(addonStatus.Name).Should(BeEquivalentTo("mock-addon"))
})
It("enable addon with not match system version requirement", func() {
req := apisv1.EnableAddonRequest{
Args: map[string]interface{}{
"testkey": "testvalue",
},
}
res := post("/addons/not-match-addon/enable", req)
defer res.Body.Close()
type errResp struct {
BusinessCode int `json:"BusinessCode"`
Message string `json:"Message"`
}
var errResponse errResp
body, err := ioutil.ReadAll(res.Body)
Expect(err).Should(BeNil())
err = json.Unmarshal(body, &errResponse)
Expect(err).Should(BeNil())
Expect(errResponse.BusinessCode).Should(BeEquivalentTo(50018))
Expect(strings.Contains(errResponse.Message, "fail to install"))
})
})
})

View File

@@ -0,0 +1,357 @@
/*
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 e2e_apiserver_test
import (
"context"
"net/http"
"strconv"
"time"
"github.com/google/go-cmp/cmp"
"github.com/kubevela/workflow/api/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
var (
testPipelineSteps []model.WorkflowStep
props = model.JSONStruct{"url": "https://api.github.com/repos/kubevela/kubevela"}
)
func init() {
testPipelineSteps = []model.WorkflowStep{
{
WorkflowStepBase: model.WorkflowStepBase{
Name: "request",
Type: "request",
Outputs: v1alpha1.StepOutputs{
{
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
Name: "stars",
},
},
Properties: &props,
},
},
}
}
var _ = Describe("Test the rest api about the pipeline", func() {
var (
projectName1 = testNSprefix + strconv.FormatInt(time.Now().UnixNano(), 10)
pipelineName = "test-pipeline"
description = "amazing pipeline"
contextName = "test-context"
contextKey = "test-key"
contextVal = "test-val"
pipelineRunName string
)
defer GinkgoRecover()
It("create project and apply definitions", func() {
defer GinkgoRecover()
var req = apisv1.CreateProjectRequest{
Name: projectName1,
Description: "KubeVela Project",
}
res := post("/projects", req)
var projectBase apisv1.ProjectBase
Expect(decodeResponseBody(res, &projectBase)).Should(Succeed())
Expect(cmp.Diff(projectBase.Name, req.Name)).Should(BeEmpty())
Expect(cmp.Diff(projectBase.Description, req.Description)).Should(BeEmpty())
def1 := new(v1beta1.WorkflowStepDefinition)
def2 := new(v1beta1.WorkflowStepDefinition)
Expect(common.ReadYamlToObject("./testdata/request.yaml", def1)).Should(BeNil())
Expect(k8sClient.Create(context.Background(), def1)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(common.ReadYamlToObject("./testdata/log.yaml", def2)).Should(BeNil())
Expect(k8sClient.Create(context.Background(), def2)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
It("create pipeline", func() {
var req = apisv1.CreatePipelineRequest{
Name: pipelineName,
Description: description,
Spec: model.WorkflowSpec{
Steps: testPipelineSteps,
},
}
res := post("/projects/"+projectName1+"/pipelines", req)
var pipeline apisv1.PipelineBase
Expect(decodeResponseBody(res, &pipeline)).Should(Succeed())
Expect(cmp.Diff(pipeline.Name, req.Name)).Should(BeEmpty())
Expect(len(pipeline.Spec.Steps)).Should(Equal(len(req.Spec.Steps)))
})
It("create context", func() {
var req = apisv1.CreateContextValuesRequest{
Name: contextName,
Values: []model.Value{
{
Key: contextKey,
Value: contextVal,
},
},
}
res := post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/contexts", req)
var context apisv1.Context
Expect(decodeResponseBody(res, &context)).Should(Succeed())
Expect(cmp.Diff(context.Name, req.Name)).Should(BeEmpty())
Expect(cmp.Diff(context.Values, req.Values)).Should(BeEmpty())
})
It("get contexts", func() {
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/contexts")
var contexs apisv1.ListContextValueResponse
Expect(decodeResponseBody(res, &contexs)).Should(Succeed())
Expect(len(contexs.Contexts)).Should(Equal(1))
ctx, ok := contexs.Contexts[contextName]
Expect(ok).Should(BeTrue())
Expect(len(ctx)).Should(Equal(1))
})
It("update context", func() {
var req = apisv1.UpdateContextValuesRequest{
Values: []model.Value{
{
Key: contextKey,
Value: "new-val",
},
},
}
res := put("/projects/"+projectName1+"/pipelines/"+pipelineName+"/contexts/"+contextName, req)
var context apisv1.Context
Expect(res.StatusCode).Should(Equal(http.StatusOK))
Expect(decodeResponseBody(res, &context)).Should(Succeed())
By("check the context value")
Expect(cmp.Diff(context.Values[0].Value, "new-val")).Should(BeEmpty())
})
It("update pipeline", func() {
newSteps := make([]model.WorkflowStep, 0)
newSteps = append(newSteps, model.WorkflowStep{
SubSteps: []model.WorkflowStepBase{
{
Name: "request1",
Type: "request",
Outputs: v1alpha1.StepOutputs{
{
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
Name: "stars",
},
},
Properties: &props,
},
{
Name: "request2",
Type: "request",
Outputs: v1alpha1.StepOutputs{
{
ValueFrom: "import \"strconv\"\n\"Current star count: \" + strconv.FormatInt(response[\"stargazers_count\"], 10)\n",
Name: "stars-copy",
},
},
Properties: &props,
},
},
WorkflowStepBase: model.WorkflowStepBase{
Name: "request-group",
Type: "step-group",
},
})
newSteps = append(newSteps, model.WorkflowStep{
WorkflowStepBase: model.WorkflowStepBase{
Name: "log",
Type: "log",
Inputs: v1alpha1.StepInputs{
{
ParameterKey: "data",
From: "stars",
},
},
},
})
var req = apisv1.UpdatePipelineRequest{
Description: description,
Spec: model.WorkflowSpec{
Steps: newSteps,
},
}
res := put("/projects/"+projectName1+"/pipelines/"+pipelineName, req)
var pipeline apisv1.PipelineBase
Expect(decodeResponseBody(res, &pipeline)).Should(Succeed())
Expect(len(pipeline.Spec.Steps)).Should(Equal(len(req.Spec.Steps)))
})
It("run pipeline", func() {
var req = apisv1.RunPipelineRequest{
Mode: v1alpha1.WorkflowExecuteMode{
Steps: "StepByStep",
SubSteps: "DAG",
},
ContextName: contextName,
}
res := post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/run", req)
var run apisv1.PipelineRun
Expect(decodeResponseBody(res, &run)).Should(Succeed())
Expect(run.PipelineRunName).ShouldNot(BeEmpty())
pipelineRunName = run.PipelineRunName
})
It("list pipeline", func() {
res := get("/pipelines?query=amazing")
var pipelines apisv1.ListPipelineResponse
Expect(decodeResponseBody(res, &pipelines)).Should(Succeed())
Expect(pipelines.Total).Should(BeNumerically("==", 1))
Expect(pipelines.Pipelines[0].Name).Should(Equal(pipelineName))
})
It("get pipeline", func() {
Eventually(func(g Gomega) {
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName)
var pipeline apisv1.GetPipelineResponse
g.Expect(decodeResponseBody(res, &pipeline)).Should(Succeed())
g.Expect(pipeline.Name).Should(Equal(pipelineName))
g.Expect(pipeline.Description).Should(Equal(description))
g.Expect(pipeline.PipelineInfo.LastRun).ShouldNot(BeNil())
g.Expect(pipeline.PipelineInfo.RunStat.Total).Should(Equal(apisv1.RunStatInfo{Total: 1, Success: 1}))
g.Expect(len(pipeline.PipelineInfo.RunStat.Week)).Should(Equal(7))
}, 10*time.Second, 1*time.Second).Should(Succeed())
})
It("list pipeline runs", func() {
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs")
var runs apisv1.ListPipelineRunResponse
Expect(decodeResponseBody(res, &runs)).Should(Succeed())
Expect(runs.Total).Should(BeNumerically("==", 1))
})
It("get pipeline run", func() {
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName)
var run apisv1.PipelineRunBase
Expect(decodeResponseBody(res, &run)).Should(Succeed())
Expect(run.PipelineRunName).Should(Equal(pipelineRunName))
})
It("get pipeline run status", func() {
Eventually(func(g Gomega) {
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/status")
var status v1alpha1.WorkflowRunStatus
g.Expect(decodeResponseBody(res, &status)).Should(Succeed())
g.Expect(status.Finished).Should(Equal(true))
g.Expect(status.Phase).Should(Equal(v1alpha1.WorkflowStateSucceeded))
g.Expect(status.Message).Should(BeEmpty())
}, 100*time.Second, 1*time.Second).Should(Succeed())
})
It("get pipeline run output", func() {
outputStep := "request1"
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/output?step=" + outputStep)
var output apisv1.GetPipelineRunOutputResponse
Expect(decodeResponseBody(res, &output)).Should(Succeed())
Expect(output.StepOutputs).Should(HaveLen(1))
Expect(output.StepOutputs[0].Name).Should(Equal(outputStep))
Expect(output.StepOutputs[0].Values).Should(HaveLen(1))
Expect(output.StepOutputs[0].Values[0].Value).ShouldNot(BeEmpty())
})
It("get pipeline run input", func() {
inputStep := "log"
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/input?step=" + inputStep)
var input apisv1.GetPipelineRunInputResponse
Expect(decodeResponseBody(res, &input)).Should(Succeed())
Expect(input.StepInputs).Should(HaveLen(1))
Expect(input.StepInputs[0].Name).Should(Equal(inputStep))
Expect(input.StepInputs[0].Values).Should(HaveLen(1))
Expect(input.StepInputs[0].Values[0].Value).ShouldNot(BeEmpty())
})
It("get pipeline run logs", func() {
logStep := "log"
res := get("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName + "/log?step=" + logStep)
var logs apisv1.GetPipelineRunLogResponse
Expect(decodeResponseBody(res, &logs)).Should(Succeed())
Expect(logs.Name).Should(Equal(logStep))
Expect(logs.Log).ShouldNot(BeEmpty())
})
It("delete pipeline run", func() {
res := delete("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName)
Expect(res.StatusCode).Should(Equal(http.StatusOK))
})
It("stop pipeline", func() {
By("update pipeline so that it will run for a while")
var req = apisv1.UpdatePipelineRequest{
Spec: model.WorkflowSpec{
Steps: []model.WorkflowStep{
{
WorkflowStepBase: model.WorkflowStepBase{
Name: "request",
Type: "request",
Timeout: "20s",
DependsOn: []string{"not-exist-step"},
},
},
},
},
}
res := put("/projects/"+projectName1+"/pipelines/"+pipelineName, req)
Expect(res.StatusCode).Should(Equal(http.StatusOK))
By("run the pipeline")
var run apisv1.PipelineRun
res = post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/run", apisv1.RunPipelineRequest{})
Expect(res.StatusCode).Should(Equal(http.StatusOK))
Expect(decodeResponseBody(res, &run)).Should(Succeed())
pipelineRunName = run.PipelineRunName
By("stop the pipeline")
var meta apisv1.PipelineRunMeta
res = post("/projects/"+projectName1+"/pipelines/"+pipelineName+"/runs/"+pipelineRunName+"/stop", nil)
Expect(res.StatusCode).Should(Equal(http.StatusOK))
Expect(decodeResponseBody(res, &meta)).Should(Succeed())
Expect(meta.PipelineRunName).Should(Equal(pipelineRunName))
By("delete pipeline run")
res = delete("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/runs/" + pipelineRunName)
Expect(res.StatusCode).Should(Equal(http.StatusOK))
})
It("delete context", func() {
res := delete("/projects/" + projectName1 + "/pipelines/" + pipelineName + "/contexts/" + contextName)
Expect(res.StatusCode).Should(Equal(http.StatusOK))
})
It("delete pipeline", func() {
res := delete("/projects/" + projectName1 + "/pipelines/" + pipelineName)
Expect(res.StatusCode).Should(Equal(http.StatusOK))
})
It("delete project", func() {
res := delete("/projects/" + projectName1)
Expect(res.StatusCode).Should(Equal(http.StatusOK))
})
})

View File

@@ -59,6 +59,7 @@ func TestE2eApiserverTest(t *testing.T) {
// Suite test in e2e-apiserver-test relies on the pre-setup kubernetes environment
var _ = BeforeSuite(func() {
defer GinkgoRecover()
ctx := context.Background()
@@ -115,7 +116,7 @@ var _ = BeforeSuite(func() {
err = json.NewDecoder(resp.Body).Decode(code)
Expect(err).Should(BeNil())
return fmt.Errorf("rest service not ready code:%d message:%s", resp.StatusCode, code.Message)
}, time.Second*10, time.Millisecond*200).Should(BeNil())
}, time.Second*20, time.Millisecond*200).Should(BeNil())
var err error
k8sClient, err = clients.GetKubeClient()
Expect(err).ShouldNot(HaveOccurred())

View File

@@ -0,0 +1,35 @@
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/alias: ""
definition.oam.dev/description: Apply raw kubernetes objects for your workflow steps
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: log
namespace: vela-system
spec:
schematic:
cue:
template: |
import (
"vela/op"
)
apply: op.#Log & {
parameter
}
parameter: {
data?: string
level: *3 | int
source?: close({
url: string
}) | close({
resources?: [...{
name?: string
cluster?: string
namespace?: string
labelSelector?: {...}
}]
})
}

View File

@@ -0,0 +1,43 @@
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/alias: ""
definition.oam.dev/description: Send request to the url
name: request
namespace: vela-system
spec:
schematic:
cue:
template: |
import (
"vela/op"
"encoding/json"
)
http: op.http.#Do & {
method: parameter.method
url: parameter.url
request: {
if parameter.body != _|_ {
body: json.Marshal(parameter.body)
}
if parameter.header != _|_ {
header: parameter.header
}
}
}
fail: op.#Steps & {
if http.response.statusCode > 400 {
requestFail: op.#Fail & {
message: "request of \(parameter.url) is fail: \(http.response.statusCode)"
}
}
}
response: json.Unmarshal(http.response.body)
parameter: {
url: string
method: *"GET" | "POST" | "PUT" | "DELETE"
body?: {...}
header?: [string]: string
}

View File

@@ -97,8 +97,10 @@ var _ = Describe("Test multicluster standalone scenario", func() {
Eventually(func(g Gomega) {
app := &v1beta1.Application{}
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: namespace, Name: "podinfo"}, app)).Should(Succeed())
Expect(k8sClient.Delete(context.Background(), app)).Should(Succeed())
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Namespace: namespace, Name: "podinfo"}, app)).Should(Succeed())
g.Expect(app.Status.Workflow).ShouldNot(BeNil())
g.Expect(app.Status.Workflow.Mode).Should(Equal("DAG-DAG"))
g.Expect(k8sClient.Delete(context.Background(), app)).Should(Succeed())
}, 15*time.Second).Should(Succeed())
Eventually(func(g Gomega) {

View File

@@ -2,6 +2,8 @@ apiVersion: core.oam.dev/v1alpha1
kind: Workflow
metadata:
name: deploy-podinfo
mode:
steps: DAG
steps:
- type: deploy
name: deploy-worker

View File

@@ -8,6 +8,8 @@
template: {
#ApplyOnceStrategy: {
// +usage=When the strategy takes effect,e.g. onUpdate、onStateKeep
affect?: string
// +usage=Specify the path of the resource that allow configuration drift
path: [...string]
}