Compare commits

..

154 Commits

Author SHA1 Message Date
Jianbo Sun
204a578aaa Merge pull request #401 from captainroy-hy/check-oam-pod
wait until runtime ready during vela install
2020-10-21 17:42:54 +08:00
roy wang
de28c06af5 wait until runtime ready during vela install
check runtime ready before vela dashboard

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-10-21 17:43:24 +09:00
Hongchao Deng
20a5457d5f refactor pkg/application to use Appfile (#402)
* refactor pkg/application to use Appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* fix build

* fix workload

* refactor pkg/application to use Appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* rebase

* fix

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* e2e

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* update design

* add test coverage for appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* comment

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>
2020-10-21 13:01:46 +08:00
Hongchao Deng
fa575a0103 Merge pull request #414 from wonderflow/logs
support vela logs
2020-10-20 08:45:29 -07:00
Hongchao Deng
841ce47ecd Merge pull request #415 from wonderflow/fixroutedef
fix route trait definition
2020-10-20 08:44:10 -07:00
Jianbo Sun
df495d9c04 Update pkg/commands/logs.go
Co-authored-by: Ryan Zhang <yangzhangrice@hotmail.com>
2020-10-20 23:24:47 +08:00
天元
5f755d4ec9 fix route trait definition 2020-10-20 20:58:36 +08:00
天元
a195d25546 support vela logs 2020-10-20 20:45:28 +08:00
Jianbo Sun
177e89a399 Merge pull request #410 from wonderflow/docgen
refactor command and add vela generate-doc for cli
2020-10-20 15:13:48 +08:00
天元
04fcad21d4 refactor command and add vela generate-doc for cli 2020-10-20 14:54:02 +08:00
Zheng Xi Zhou
13dea58e35 Refine help and error message (#345)
* Refine help and error message

refined error message for `vela app run`
and correct help message for `vela comp ls`

To fix #320

* fix make issue
2020-10-20 14:34:19 +08:00
Hongchao Deng
38dc6fe73e fix bugs on pkg/appfile (#409)
* fix bugs on pkg/appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* fix
2020-10-20 13:55:57 +08:00
Jianbo Sun
33c9a1dade Merge pull request #408 from ryanzhang-oss/fix-local
fix local dev environment
2020-10-20 11:46:53 +08:00
Ryan Zhang
b13677363f install CRD manually 2020-10-20 11:30:34 +08:00
Ryan Zhang
50e5c6a25c Merge pull request #403 from wonderflow/fixrace
fix race and update oam-runtime dependency
2020-10-19 13:41:13 +08:00
天元
3812ed2488 fix race and update oam-runtime dependency 2020-10-19 13:19:08 +08:00
Jianbo Sun
1cc8a3980c Merge pull request #387 from wonderflow/route1
give route trait new discovery port way with podspecable design
2020-10-18 11:24:38 +08:00
Hongchao Deng
b08c6b9441 Appfile: Extensible, User-friendly Application Config Format (#390)
* design doc

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* Support deployment via appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* design update

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* comments

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* update

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* refactor

* add multi services example in design doc
2020-10-18 11:22:17 +08:00
Ryan Zhang
8de3ee27f4 Merge pull request #392 from oam-dev/rollout-design
rollout trait high level design
2020-10-17 19:52:33 +08:00
Hongchao Deng
82714d163d Merge pull request #398 from hongchaodeng/codecov
move codecov upload to github workflow
2020-10-16 16:58:15 -07:00
Hongchao Deng
4fa3fb6486 move codecov upload to github workflow 2020-10-16 16:44:35 -07:00
Hongchao Deng
62de2e4d50 test codecov 2020-10-16 16:19:40 -07:00
Hongchao Deng
146dcb5e41 Merge pull request #397 from hongchaodeng/codecov
Setup codecov
2020-10-16 16:18:52 -07:00
Hongchao Deng
4889e458d4 add token 2020-10-16 16:07:47 -07:00
Hongchao Deng
8f4d9b37d2 fix 2020-10-16 15:24:59 -07:00
Hongchao Deng
0e83ff303c add codecov 2020-10-16 15:16:58 -07:00
天元
3d2bd9a898 add a design doc for route trait, along with fix and tests 2020-10-16 20:50:08 +08:00
Ryan Zhang
96f6a38869 rough rollout trait design 2020-10-15 21:06:50 +08:00
Jianbo Sun
4ef766c05d Merge pull request #395 from wonderflow/definition
move definition files to a separate dir
2020-10-15 17:30:05 +08:00
天元
2c721e5177 move definition files to a separate dir 2020-10-15 16:40:00 +08:00
Jianbo Sun
f58617f733 Merge pull request #394 from wonderflow/fixcert
covert certmanager to v1 and use wonderflow/cert-manager-api to reduce dependency collision
2020-10-15 12:13:50 +08:00
天元
1166c5b526 covert certmanager to v1 and use wonderflow/cert-manager-api to reduce dependency collision 2020-10-15 11:52:40 +08:00
Jianbo Sun
a5cacbe383 Merge pull request #393 from wonderflow/fix
add args for vela install and fix E2E with fresh image build
2020-10-14 23:41:33 +08:00
天元
d9302ff982 add args for vela install and fix E2E with fresh image build 2020-10-14 23:22:15 +08:00
Jianbo Sun
3751b98e7a Merge pull request #389 from wonderflow/initappdeploy
bootstrap application deployment for app level rollout/traffic management
2020-10-14 12:49:16 +08:00
天元
a66938eee8 add more to contributing guide 2020-10-14 12:01:52 +08:00
Ryan Zhang
529d150737 add an example on how to use flagger and fix some minor bugs (#381) 2020-10-14 11:43:57 +08:00
天元
395e2d55f7 bootstrap application deployment for app level rollout/traffic management 2020-10-14 11:41:31 +08:00
Jianbo Sun
4cb677418f Merge pull request #388 from captainroy-hy/fix-e2e-setup-2
revert commented e2e cases
2020-10-14 10:05:56 +08:00
roy wang
ad819b354a fix e2e-setup
revert commented e2e cases

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-10-13 21:56:44 +09:00
Jianbo Sun
48404bde98 Merge pull request #386 from wonderflow/fixconfig
fix make core-run panic
2020-10-13 19:57:22 +08:00
Jianbo Sun
a36e6fc14b Merge pull request #379 from captainroy-hy/add-probes
add liveness & readiness probes
2020-10-13 19:46:44 +08:00
天元
f3eed5f283 fix make core-run panic 2020-10-13 19:35:01 +08:00
roy wang
53fc9e5b71 add ready/health probes
add waiting for webhook secret ready

add unit tests

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-10-13 18:57:58 +09:00
Jianbo Sun
1479109324 move chart build to source file, allow chart CRD switching to V1 of cert-manager (#382)
* add chart_source

* generate chart to fake file

* switching to V1 of cert-manager

make exec time longer
2020-10-13 17:20:59 +08:00
Hongchao Deng
f106752b48 Merge pull request #385 from wonderflow/route
align to oam-k8s-runtime v0.3.0-rc1 and its CRD file
2020-10-12 21:20:59 -07:00
Hongchao Deng
467d3ab59c Merge pull request #384 from hongchaodeng/data
cue: rename 'data' to 'output'
2020-10-12 21:12:26 -07:00
天元
6076516c24 align to oam-k8s-runtime v0.3.0-rc1 and its CRD file 2020-10-13 12:11:55 +08:00
Hongchao Deng
af91fdf21e cue: rename 'data' to 'output' 2020-10-12 21:02:33 -07:00
Jianbo Sun
4bb8492f43 Merge pull request #383 from captainroy-hy/fix-block-e2e
comment out e2e blocking cases
2020-10-13 11:41:55 +08:00
roy wang
4428a6e738 remove e2e blocking cases
Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-10-13 12:12:17 +09:00
silenceper
1f3548eee4 fix check resource exists (#373)
* fix check resource exists

* fix go lint

* add tests, fix DoesCRDExist

* remove init func

* fix should return err
2020-10-13 07:54:19 +08:00
Jianbo Sun
64d77656cf Merge pull request #380 from zzxwill/env-args
Update env init API doc
2020-10-12 15:46:43 +08:00
zzxwill
21f191b889 Update env init API doc 2020-10-12 15:32:07 +08:00
Jianbo Sun
60402414f1 Merge pull request #372 from zzxwill/fix-link
Fix code of conduct link
2020-10-10 18:28:35 +08:00
zzxwill
6fb9d02e8d Fix code of conduct link
renamed:    CODE_OF_CONDUCT.md  -> CODE_OF_CONDUCT.md
2020-10-10 17:35:16 +08:00
Jianbo Sun
eaac01b58c Merge pull request #366 from wonderflow/descirption
add description for workload and trait
2020-10-10 14:47:45 +08:00
Zheng Xi Zhou
af724ad58a Update appliesToWorkloads field in traitdefinition (#368)
* Update appliesToWorkloads field in traitdefintion

updated appliesToWorkloads fileds in traitdefinition
for three workloads, webservice, task and backend.
fix issue #346

* remove duplication
2020-10-10 14:27:34 +08:00
天元
590918af32 add description for workload and trait 2020-10-10 10:15:30 +08:00
Jianbo Sun
0b59db8fb0 Merge pull request #367 from resouer/fix-typo
Fix typos in design doc [part 2]
2020-10-10 09:59:38 +08:00
Harry Zhang
04deadd684 Fix typos in design doc [part 2] 2020-10-09 10:45:19 -07:00
Jianbo Sun
dd08aa4d45 Merge pull request #329 from captainroy-hy/track-comp-status
track status changing in vela init
2020-10-09 19:12:35 +08:00
roy wang
8b3bda82f7 add e2e test for vela init
modify e2e setup to wait oam-runtime pod running

add k8sclient in e2e test

refine tracking status of vela init

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-10-09 19:36:33 +09:00
roy wang
6aafb90acd track status changing in vela init
fix e2e-test

update oam-k8s-runtime dependency to latest tag

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-10-09 18:41:53 +09:00
Jianbo Sun
a0e12b84c7 Merge pull request #364 from resouer/update-project
Add design doc to kubevela and update readme
2020-10-09 14:48:55 +08:00
Harry Zhang
e56eb2fa67 Complete detailed design (part 1) 2020-10-08 22:38:08 -07:00
Jianbo Sun
28e75551ed Merge pull request #363 from hanxie-crypto/feature07
bug fix
2020-10-09 10:10:30 +08:00
hanxie
045a4aae2d bug fix 2020-10-09 09:58:48 +08:00
Jianbo Sun
53a9e9284d Merge pull request #359 from wonderflow/fixroute
Some fixes
2020-10-09 08:31:37 +08:00
天元
189175c4db using Environment intead of ENV fixes #350 2020-10-09 08:21:31 +08:00
Jianbo Sun
aa9c96d0b5 Merge pull request #360 from captainroy-hy/fix-hack-generate
fix minor bug in generate.go
2020-10-08 19:32:32 +08:00
roy wang
ceaf6db82c fix minor bugs in generate.go
Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-10-08 19:09:22 +09:00
天元
e4570e22ae fix podspec workload not create service anymore 2020-10-08 16:09:04 +08:00
天元
c9a28309b2 fix trait detach not work 2020-10-08 16:08:42 +08:00
天元
6d50eed4f9 fix temparory chart dir created when develop locally 2020-10-08 15:58:43 +08:00
天元
e922666d5f rename 'vela comp run' to 'vela comp deploy' 2020-10-08 15:46:30 +08:00
天元
43223f0759 fix helm chart release name to kubevela 2020-10-08 15:31:05 +08:00
Harry Zhang
2641630b97 Init commit on design doc 2020-10-07 10:31:55 -07:00
Ryan Zhang
f4d5a13934 Merge pull request #356 from oam-dev/rename
rename containerized to podspecworkload
2020-10-06 10:13:54 -07:00
Ryan Zhang
61544a0be6 rename containerized to podspecworkload 2020-10-06 00:25:16 -07:00
Ryan Zhang
e422d0ba86 Merge pull request #352 from oam-dev/install-helm
Hack the Helm install client and improve the helm chart
2020-10-02 23:14:12 -07:00
Ryan Zhang
d0d8d77337 fix the test 2020-10-02 20:26:50 -07:00
Ryan Zhang
3f468e4752 improve helm install 2020-10-02 19:29:31 -07:00
Jianbo Sun
3b6ced2a58 Merge pull request #351 from resouer/readme
Add details for the project
2020-10-02 11:47:05 +08:00
Harry Zhang
f29bb26880 Add details for the project 2020-10-01 19:22:15 -07:00
Jianbo Sun
6ede8601b4 Merge pull request #343 from zzxwill/api
Refine and fix API
2020-09-30 14:44:38 +08:00
zzxwill
8a15d19348 Refine and fix API
- fixed trait attach issue
- allow leaving out `/` to make API more friendly
- merge similar struct
2020-09-29 19:30:52 +08:00
Zheng Xi Zhou
e0b4e5f76f Merge pull request #342 from hanxie-crypto/feature06
update applist
2020-09-29 19:27:30 +08:00
hanxie
6c04af02cb update applist 2020-09-29 19:08:04 +08:00
Zheng Xi Zhou
351049b74f Merge pull request #337 from hanxie-crypto/feature05
Component module related
2020-09-29 16:58:07 +08:00
hanxie
30127f8a3e Component module related 2020-09-28 19:37:33 +08:00
Ryan Zhang
6863ac02a1 add back the test after the cue templates are updated (#328)
* add back the test

* enhance e2e test
2020-09-28 17:51:06 +08:00
silenceper
24c30e65fb Determine whether it is necessary to create a service (#333)
* Determine whether it is necessary to create a service

* add checkContainerPortsSpecified func

* add check workload is nil
2020-09-28 10:08:51 +08:00
silenceper
c4dd02761a support delete multi env (#331)
* support delete multi env

* fix golint
2020-09-27 15:44:46 +08:00
Hongchao Deng
72b7a89e9a Merge pull request #332 from silenceper/fix-break
use return instead of break
2020-09-26 23:41:37 -07:00
silenceper
453976e28f use return instead of break 2020-09-27 14:31:03 +08:00
Jianbo Sun
914fa61819 Merge pull request #325 from zzxwill/component-api
Implemente Component api
2020-09-27 12:19:52 +08:00
zzxwill
e1a2edb604 fix naming issue 2020-09-27 11:35:46 +08:00
zzxwill
d245f3f939 implemented component delete API 2020-09-26 21:43:32 +08:00
zzxwill
433296d718 Update trait attach/detach with the introduction of component 2020-09-26 21:43:32 +08:00
zzxwill
28c3c683a0 Implement component related API
Update app list/show, implmented
component show
2020-09-26 21:41:15 +08:00
Jianbo Sun
50ba36eb1e Merge pull request #319 from oam-dev/cue-improve
Tweak the cue template format
2020-09-26 13:12:08 +08:00
Ryan Zhang
c69c809264 improve CUE format 2020-09-25 21:58:30 -07:00
Sun Jianbo
fac05a3bd1 Merge pull request #326 from zzxwill/naming
Fix naming in `json` of struct fields
2020-09-25 15:39:43 +08:00
zzxwill
60e34d5b03 Fix naming in json of struct fields
almost all of names in `json` of struct fileds
should be in `lowerUpper` format
2020-09-25 14:55:24 +08:00
Hongchao Deng
8b480df72d Merge pull request #318 from hongchaodeng/gomod
fixed dep should be put under 'replace'
2020-09-23 21:00:44 -07:00
Sun Jianbo
5deef1c098 Merge pull request #314 from captainroy-hy/fix-comp-status
fix bug: handle exception cases in `vela comp status`
2020-09-24 10:15:59 +08:00
roy wang
11d4fcd56f handle exception cases of vela comp status
Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-09-24 10:59:53 +09:00
Hongchao Deng
d78648b73c fixed dep should be put under 'replace' 2020-09-23 09:12:09 -07:00
Sun Jianbo
525c228bd7 Merge pull request #311 from wonderflow/fix
fix env exist but issuer not exist
2020-09-23 11:22:11 +08:00
天元
ba6a53c6f5 fix env exist but issuer not exist 2020-09-23 10:38:59 +08:00
Sun Jianbo
0d1dd62b8a Merge pull request #308 from wonderflow/init
vela init to create and run application in one command
2020-09-22 20:23:41 +08:00
天元
5ac7265474 vela init to create and run application in one command 2020-09-22 20:07:53 +08:00
Zheng Xi Zhou
08701568a1 Fix OpenAPIV3Schema Validation Issue (#300)
* Fix OpenAPIV3Schema Validation Issue

Temporarily corrects spec.validation.openAPIV3Schema issue, and it would be removed
after this issue was fixed https://github.com/oam-dev/kubevela/issues/284.

* fix vela install issue

* return error during fixing APIV3SchemaValidation issue
2020-09-22 15:51:38 +08:00
roy wang
faedce906a fix #128 | sync deleted workload locally
Signed-off-by: roy wang <seiwy2010@gmail.com>

fix & add unit tests

Signed-off-by: roy wang <seiwy2010@gmail.com>

fix info output & unit test

Signed-off-by: roy wang <seiwy2010@gmail.com>

fix e2e-test

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-09-21 19:22:34 +08:00
Sun Jianbo
f5908741d3 Update pkg/commands/system.go 2020-09-21 19:22:34 +08:00
mosesyou
5414cc1f86 fix duplicate vela system info failure message 2020-09-21 19:22:34 +08:00
天元
371a989f9b add move readme 2020-09-21 19:22:34 +08:00
天元
8b3af3be93 temporarily add containerized to chart 2020-09-21 19:22:34 +08:00
hanxie
d6e519f1c4 Components static page,bugfix,capability delete 2020-09-21 19:22:34 +08:00
Sun Jianbo
90fd0a5055 Merge pull request #295 from wonderflow/production-issuer
add production certificate issuer and fix route trait
2020-09-18 21:45:14 +08:00
天元
3af41f6515 add production certificate issuer and fix route trait 2020-09-18 19:06:51 +08:00
Yue Wang
ee39054537 get cue temp from remote through URI (#287)
* get cue template of capabilities from remote by URI

Signed-off-by: roy wang <seiwy2010@gmail.com>

* add e2e tests

Signed-off-by: roy wang <seiwy2010@gmail.com>

* fix type conversion

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-09-18 15:59:36 +08:00
Ryan Zhang
b5218d371a Merge pull request #293 from wonderflow/iamge
use image from docker hub instead of quay.io
2020-09-17 19:23:41 -07:00
天元
7f5298a802 use image from docker hub instead of quay.io 2020-09-17 17:30:33 +08:00
Sun Jianbo
2ffd56a993 Merge pull request #291 from wonderflow/zip
use compressed file for release
2020-09-17 15:07:00 +08:00
天元
e0d7eed9d3 update readme 2020-09-17 14:52:33 +08:00
Sun Jianbo
b0ec26bca0 Merge pull request #290 from wonderflow/image
update image tag to latest and change image pull policy
2020-09-17 14:44:19 +08:00
天元
2116c9ad0e use compressed file for release 2020-09-17 14:42:00 +08:00
天元
d02d6675ab update image tag to latest and change image pull policy 2020-09-17 14:08:33 +08:00
Sun Jianbo
1a43c0e540 Merge pull request #276 from wonderflow/routetrait
add route trait as vela api gateway
2020-09-17 14:06:00 +08:00
Sun Jianbo
e6b46c6c1b Merge pull request #288 from mosesyou/bugfix-comp-run
fix `vela comp run` panic when args empty
2020-09-17 11:57:27 +08:00
mosesyou
387afaa2c2 fix vela comp run panic when args empty 2020-09-17 11:08:55 +08:00
Ryan Zhang
f9fea8b53a Merge pull request #285 from wonderflow/dockerpackage
Add Image upload for Docker Hub and Github Docker package
2020-09-16 19:32:07 -07:00
Hongchao Deng
21c58c0aa2 Merge pull request #286 from oam-dev/warning
Add warning for not play around it for now
2020-09-16 16:43:43 -07:00
Lei Zhang (Harry)
d5a8b54503 Add warning for not play around it for now 2020-09-16 16:39:06 -07:00
天元
c390928368 split release and daily build 2020-09-16 22:57:56 +08:00
天元
c4897008dc add docker image build 2020-09-16 21:50:56 +08:00
天元
fe09a85e53 add docker build 2020-09-16 21:34:20 +08:00
天元
1b9ee5c882 add route trait as vela apigate 2020-09-16 19:42:01 +08:00
Sun Jianbo
cc5e3dc6a2 Merge pull request #281 from oam-dev/install-dependency
vela core install dependencies
2020-09-16 17:29:02 +08:00
Ryan Zhang
8a9470b9b3 fix build error 2020-09-16 01:12:50 -07:00
Ryan Zhang
e0a21b2bd4 Merge pull request #268 from wonderflow/template
update chart and refactor default workload type
2020-09-15 22:52:32 -07:00
Ryan Zhang
dfeff25a31 install dependencies 2020-09-15 22:04:52 -07:00
天元
9510db0ace update chart and refactor default workload 2020-09-16 12:09:29 +08:00
Sun Jianbo
1576d1ff2b Merge pull request #280 from hanxie-crypto/feature03
Update code and page optimization ,fix bug
2020-09-16 11:37:03 +08:00
hanxie
764e1bbb79 Update code and page optimization ,fix bug 2020-09-16 11:03:16 +08:00
Sun Jianbo
83e718c4cd Merge pull request #261 from hanxie-crypto/feature01
update dashboard ux
2020-09-16 10:50:13 +08:00
Ryan Zhang
16fd51a213 adjust the helm setting and README (#269)
* add dependancy installer and fix e2e

* fix build

* add kubebuilder
2020-09-15 15:08:17 +08:00
Sun Jianbo
1744b4752c Merge pull request #270 from zzxwill/website
Remove website related files
2020-09-13 11:44:31 +08:00
zzxwill
7ad7848856 Remove website related files
Removed website related files and planning moving
the website to oam-dev/kubevela.io
2020-09-13 11:29:44 +08:00
Sun Jianbo
6df4e192f9 Merge pull request #267 from zzxwill/env
Rename `vela env switch` to `vela env set`
2020-09-11 21:40:36 +08:00
zzxwill
bc3169a1b5 Rename vela env switch to vela env set
Renaming and update cli documentation
Fix #235
2020-09-11 19:46:45 +08:00
hanxie
b026cf20f4 update dashboard ux 2020-09-11 14:43:08 +08:00
400 changed files with 38825 additions and 27678 deletions

25
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Publish to Registry
on:
push:
branches:
- master
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Publish to Github Docker Package Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oam-dev/kubevela/vela-core
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: docker.pkg.github.com
tags: "latest"
- name: Publish to Docker Hub Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oamdev/vela-core
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tags: "latest"

View File

@@ -34,9 +34,18 @@ jobs:
with:
version: "v0.7.0"
- name: Load Image to kind cluster
run: make kind-load
- name: install Kubebuilder
uses: RyanSiu1995/kubebuilder-action@v1
- name: Run Make
run: make
- name: Run Make Manager
run: make manager
- name: Run e2e tests
run: |
make e2e-setup

View File

@@ -30,5 +30,16 @@ jobs:
with:
version: "v0.7.0"
- name: install Kubebuilder
uses: RyanSiu1995/kubebuilder-action@v1
- name: Run Make test
run: make test
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.txt
flags: unittests
name: codecov-umbrella

View File

@@ -32,6 +32,8 @@ jobs:
run: make generate-source
- name: Run cross-build
run: make cross-build
- name: Run compress binary
run: make compress
- name: Create Release
id: create_release
uses: actions/create-release@v1
@@ -41,24 +43,67 @@ jobs:
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Upload Linux
- name: Upload Linux tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-linux-amd64
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64
asset_path: ./_bin/vela-linux-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload MacOS
- name: Upload Linux zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-darwin-amd64
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64
asset_path: ./_bin/vela-linux-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Windows
- name: Upload MacOS tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-windows-amd64.exe
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.exe
asset_content_type: binary/octet-stream
asset_path: ./_bin/vela-darwin-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload MacOS zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-darwin-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Windows tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-windows-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload Windows zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-windows-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Checksums
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/sha256sums.txt
asset_name: sha256sums.txt
asset_content_type: text/plain
- name: Publish to Github Docker Package Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oam-dev/kubevela/vela-core
username: $GITHUB_ACTOR
password: ${{ secrets.GITHUB_TOKEN }}
registry: docker.pkg.github.com
tags: "${{ steps.get_version.outputs.VERSION }}"
- name: Publish to Docker Hub Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oamdev/vela-core
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tags: "${{ steps.get_version.outputs.VERSION }}"

8
.gitignore vendored
View File

@@ -5,6 +5,7 @@
*.so
*.dylib
bin
_bin
e2e/vela
# Test binary, build with `go test -c`
@@ -12,6 +13,7 @@ e2e/vela
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
coverage.txt
# Kubernetes Generated files - skip generated files, except for vendored files
@@ -35,9 +37,13 @@ config/crd/bases
tmp/
cmd/vela/fake/source.go
cmd/vela/fake/chart_source.go
charts/vela-core/crds/_.yaml
.vela/
# Dashboard
dashboard/node_modules/
node_modules/
.eslintcache
dashboard/dist/
dashboard/package-lock.json

3
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,3 @@
# Code of Conduct
KubeVela follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).

View File

@@ -5,110 +5,130 @@ contributing to `kubevela` or build a PoC (Proof of Concept).
## Prerequisites
1. Golang version 1.12+
2. Kubernetes version v1.15+ with `~/.kube/config` configured.
3. OAM Kubernetes Runtime installed.
4. Kustomize version 3.8+
5. ginkgo 1.14.0+ (just for [E2E test](https://github.com/oam-dev/kubevela/blob/master/DEVELOPMENT.md#e2e-test))
6. golangci-lint 1.31.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
1. Golang version 1.13+
2. Kubernetes version v1.16+ with `~/.kube/config` configured.
3. ginkgo 1.14.0+ (just for [E2E test](https://github.com/oam-dev/kubevela/blob/master/DEVELOPMENT.md#e2e-test))
4. golangci-lint 1.31.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
## Build
* Clone this project
```shell script
git clone git@github.com:oam-dev/kubevela.git
```
* Install Template CRD into your cluster
* Build Vela CLI
```shell script
make install
make
```
* Install template object
* Configure vela to PATH
after build, make will create `vela` binary to `bin/`, Set this path to PATH.
```shell script
kubectl apply -f config/samples/
export PATH=$PATH:/your/path/to/project/kubevela/bin
```
## Develop & Debug
If you change Template CRD, remember to rerun `make install`.
Then you can use `vela` command directly.
Use the following command to develop and debug.
* Build Vela Core
```shell script
$ cd cmd/vela
$ go run main.go COMMAND [FLAG]
make manager
```
* Run Vela Core
Firstly make sure your cluster has CRDs.
```shell script
make core-install
```
Run locally:
```shell script
make core-run
```
This command will run controller locally, it will use your local KubeConfig which means you need to have a k8s cluster
locally. If you don't have a one, we suggest that you could setup up a cluster with [kind](https://kind.sigs.k8s.io/).
## Use
* Create environment
```shell script
vela env init myenv --namespace myenv --email my@email.com --domain kubevela.io
```
* Create Component
For example, use the following command to create and run an application.
```shell script
$ go run main.go run containerized app2057 nginx:1.9.4
Creating AppConfig app2057
$ vela comp deploy mycomp -t webservice --image crccheck/hello-world --port 8000
Creating AppConfig appcomp
SUCCEED
$ kubectl get oam
NAME WORKLOAD-KIND
component.core.oam.dev/app2057 ContainerizedWorkload
NAME AGE
containerizedworkload.core.oam.dev/poc 53m
NAME AGE
applicationconfiguration.core.oam.dev/app2057 69s
NAME DEFINITION-NAME
traitdefinition.core.oam.dev/simplerollouttraits.extend.oam.dev simplerollouttraits.extend.oam.dev
NAME DEFINITION-NAME
workloaddefinition.core.oam.dev/containerizedworkloads.core.oam.dev containerizedworkloads.core.oam.dev
workloaddefinition.core.oam.dev/deployments.apps deployments.apps
workloaddefinition.core.oam.dev/statefulsets.apps statefulsets.apps
```
## E2E test
```
$ make e2e-test
Running Suite: Trait Suite
==========================
Random Seed: 1596559178
Will run 5 of 5 specs
* Add Trait
Trait env init
should print env initiation successful message
kubevela/e2e/commonContext.go:14
Create env succeed, current env is default
------------------------------
Trait env switch
should show env switch message
kubevela/e2e/commonContext.go:40
Switch env succeed, current env is default
------------------------------
Trait run
should print successful creation information
kubevela/e2e/commonContext.go:76
Creating AppConfig app-trait-basic
SUCCEED
------------------------------
Trait kubevela attach trait
should print successful attached information
kubevela/e2e/trait/trait_test.go:24
Applying trait for app
```shell script
$ vela route mycomp
Adding route for app abc
Succeeded!
------------------------------
Trait delete
should print successful deletion information
kubevela/e2e/commonContext.go:85
Deleting AppConfig "app-trait-basic"
DELETE SUCCEED
Ran 5 of 5 Specs in 9.717 seconds
SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS
```
* Check Status
```
$ vela comp status abc
Showing status of Component abc deployed in Environment t2
Component Status:
Name: abc PodSpecWorkload(type) UNKNOWN APIVersion standard.oam.dev/v1alpha1 Kind PodSpecWorkload workload is unknown for HealthScope
Traits
└─Trait/route
Last Deployment:
Created at: 2020-09-18 18:47:09 +0800 CST
Updated at: 2020-09-18T18:47:16+08:00
```
* Delete App
```shell script
$ vela app ls
abc
$ vela app delete abc
Deleting Application "abc"
delete apps succeed abc from t2
```
## Tests
### Unit test
```shell script
make test
```
### E2E test
** Before e2e test start, make sure you have vela-core running.**
```shell script
make core-run
```
Start to test.
```
make e2e-test
```
## Make a pull request

192
DESIGN.md Normal file
View File

@@ -0,0 +1,192 @@
# KubeVela Design
This document is the detailed design and architecture of the KubeVela being built in this repository.
> All the diagram in this documentation could be found in [this slides](https://docs.google.com/presentation/d/1Y3gnKrd7fUZGgee7Ia9vBsRIYhcZLQwMUCDkk1RJvQc/edit?usp=sharing).
## Overview
KubeVela is a simple, complete, but highly extensible cloud native application platform based on Kubernetes and Open Application Model (OAM). KubeVela intends to bring application-centric experience to its end users and democratize building cloud native application platforms for platform engineers.
## User Stories
As a end user (e.g. application developers, operators etc) of the platform, I only want to focus on coding my business logic and ship them to various environments at ease. Let's say:
- Here's my source code.
- Here's my application configuration (described in end user PoV).
- Deploy it in test environment.
- Deploy it in production environment.
- Monitoring, debugging, rollout/rollback the application.
- Dockerfile is fine, but please keep it simple.
As a platform engineer, I want to build a easy-to-use platform for my end users. In detail, the platform should be:
- Heroku-like (_in terms of both user experience and functionality_).
- and I prefer to build my own version with OSS tools, particularly, with Kubernetes (for obvious reason).
- Easy to build.
- I am too busy to reinvent any wheel, I want to reuse every capability in Kubernetes community as possible, with minimal effort. Writing some simple CRD and controllers is fine, but please, just the simple ones like copy-paste.
- Powerful and highly extensible.
- I don't want to lock my users with restricted abstractions and capabilities like traditional PaaS or FaaS/Serverless. I love Kubernetes and what it has enabled. So in terms of capability, I hope my platform is fully open and has unlimited possibilities like Kubernetes itself, rather than another opinionated close system like traditional PaaS.
## Core Principles
In nutshell, the principles for KubeVela project are:
- For end users, it out-of-the-box ships a fully featured PaaS-like experience, nothing special.
- For platform builders, it works like a special Kubernetes "distro" or extensible PaaS core that could be used to build something more complex. It allows platform builders to ship any existing capabilities in cloud native ecosystem to end users with minimal effort, or develop a new capability at ease in a standardized and Kubernetes-native approach.
## Design Details
### 1. Application Centric
The design of KubeVela intends to make users think in terms of application, not containers or infrastructure.
Lacking application context impacts the user experience and significantly raised the bar to adopt cloud native stack. We believe "application" is the natural mindset of developers and it's the core concept an application platform should expose.
![alt](resources/app-centric.png)
KubeVela intends to let developers push code, define application in developer facing primitives, and make daily operations as configurations of the application.
Thus, KubeVela choose to:
1. Introduce "application" as first class citizen and main API.
2. Build the whole system around "application", i.e. model capabilities of Kubernetes as application configuration, with clarity and manageability.
#### Solution
Instead of creating a in-house "application CRD", KubeVela adopts [Open Application Model (OAM)](https://github.com/oam-dev/spec) as its application definition, since OAM:
1. Defines micro-services application by default.
2. Can model operations as part of the application (i.e. `Trait`).
2. Highly extensible: every workload and trait in OAM is a independent definition, no abstraction or capability lock-in.
### 2. Capability Oriented Architecture
To enable platform builders use KubeVela as the extensible "PaaS core", KubeVela intends to make its every capability a standalone "plug-in".
![alt](resources/coa.png)
For example, there are several "built-in" workload types in KubeVela such as `Web Service` or `Task`. It is by design that they are all independent CRD controllers that abstract Kubernetes built-in workloads and create Service automatically if needed. KubeVela itself is **NOT** aware of the specification or implementation of these workload types.
This means platform builders are free to bring their own workload types by simply install a CRD controller, or even just reference a k8s built-in resource like StatefulSet as new workload type.
Similarly, all the "built-in" operations such as `scaling` or `rollout` (i.e. "traits" in KubeVela) are also independent CRD controllers which are **NOT** bound with specific workload types. Platform builders are free to bring their own traits implementations by simply providing a CRD controller, reference a k8s built-in resource like `HPA` or `NetworkPolicy` as trait is also possible.
This loosely coupled design of KubeVela adopts the idea of Capability Oriented Architecture (COA), i.e. instead of creating a close system like traditional PaaS, KubeVela intends to become an application-centric framework to connect end users with underlying infrastructure capabilities.
#### Solution
We decide to build KubeVela core with [OAM Kubernetes runtime](https://github.com/crossplane/oam-kubernetes-runtime) which already implemented the building block features such as standalone workload type and trait controllers. Whether a given trait could work with certain workload types is also clarified as `appliesTo` field of trait definition. For platform builders, OAM Kubernetes Runtime also provided a [lightweight library](https://github.com/crossplane/oam-kubernetes-runtime/blob/2be3900a3817aed570de9ec353e6ab0b50e100f0/pkg/controller/v1alpha2/core/traits/manualscalertrait/manualscalertrait_controller.go#L42) to implement trait controller so it doesn't need to care about workload kinds.
##### Capability Register and Discovery
KubeVela leverages [OAM definition objects](https://github.com/oam-dev/spec/blob/master/4.workload_definitions.md) to register and discover workloads and traits:
```console
$ kubectl apply -f workload-definition.yaml # register a new workload type
$ kubectl apply -f trait-definition.yaml # register a new trait
```
Note that OAM definition objects only care about API resource, not including the controllers. Thus KubeVela intends to include a **CRD registry** so whenever a new API resource is installed as workload or trait, KubeVela could install its controller automatically from the registry. That of course means we envision the CRD registry could register a CRD and Helm chart (which contains the manifest of the controller). In practice, we are currently evaluating RedHat's Operator Lifecycle Manager (OLM) but no the final conclusion yet.
##### Cloud Services Integration
For capabilities like cloud services, KubeVela intends to leverage Kubernetes as the universal control plane so [Crossplane](https://github.com/crossplane/crossplane) core will be used to register cloud services as workload types.
### 3. Extensible User Interface
KubeVela is built with Kubernetes and OAM also adopts Kubernetes API resource model. So in nutshell, **ALL** functionalities of KubeVela core are able to be consumed by simple `kubectl`, for example:
```yaml
$ kubectl apply -f frontend-component.yaml # create frontend component
$ kubectl apply -f backend-component.yaml # create backend component
$ kubectl apply -f application-config.yaml # assign operational traits to components
```
However, Kubernetes API should not be the end game. Declarative YAML objects are great to build platforms like KubeVela with but when directly exposed to end users, it creates heavy mental burden and high learning curve. For example, even with highest level abstraction, the default [containerized workload schema](https://github.com/oam-dev/spec/blob/master/core/workloads/schema/containerized_workload.md) of OAM still has dozens fields to learn with many rough edges to be aware.
Thus KubeVela intends to introduce a lightweight user facing layer on top of its Kubernetes API objects with following goals in mind:
- Shorten the learning curve of new developers. Most capabilities in KubeVela are developed by big
companies that run very complex workloads. However, for the bigger developer community, the new user facing layer will provide a much simpler path to on-board KubeVela.
- Developers can describe their applications and behavior of their components without making assumptions on availability of specific Kubernetes API. For instance, a developer will be able to model auto-scaling needs without referring to the CRD of auto-scaling trait.
- Provides a single source of truth of the application description. The user facing layer allows developers to work with a single artifact to capture the application definition. This artifact is the definitive truth of how the application is supposed to look like. It simplifies administrative tasks such as change management. It also serves as an anchor for application truth to avoid configuration drifts during operation.
- Highly extensible. For example, when a new workload type or trait is installed, the end users could access this new capability directly from user interface layer, no re-compile or re-deploy of KubeVela is required.
#### Solution
We concluded above requirements of the user interface layer as introducing a modeling language on top of the existing model objects. With this context, we decide to adopt [CUElang](https://github.com/cuelang/cue) as the modeling language of KubeVela. In detail, every workload or trait definition will inline a cue template as the contract between user and Kubernetes API object.
However, we don't assume users need to learn or write CUElang directly. Instead, the reason we choose CUElang is it's just perfect to help us create developer facing tools based on it. Thus, KubeVela introduced three most common tools based to CUElang for developers to use:
1. A command line tool.
2. A GUI dashboard.
3. A Docker Compose style `appfile`.
An example for `vela cli`:
```console
$ vela comp deploy frontend -t webservice --image oamdev/testapp:v1 --port 80 --app helloworld
```
The `-t webservice --image oamdev/testapp:v1 --port 80` arguments are not hard coded, they are schema defined by in-line CUE template of `WebService` workload definition.
The `appfile` is essentially a YAML version of command line tool so we can support more complex and serious scenarios by simply running `$ vela up hello-world.yaml`:
```yaml
version: "1.0-alpha.1"
name: helloworld
services:
express-server:
type: webservice # workload type
build:
docker:
file: Dockerfile
context: .
image: oamdev/testapp:v1
cmd: ["node", "server.js"]
ports:
- 8080:80
env:
- FOO=bar
scale: # scaling trait
replica: 2
auto:
range: "1-10"
cpu: 80
qps: 1000
canary: # canary trait
step: 5
headers:
- "foo:bar.*"
redis:
image: oamdev/redis
secrets:
my-secret: /local-path/my-secret # load local file into k8s secret
```
The schema of above `appfile` is not hard coded, they are defined by in-line CUE templates of `WebService` workload definition, `Scaling` trait definition and `Canary` trait definition.
We will skip the example of dashboard, but similarly, the schema of GUI forms are defined by in-lined CUE template of definition objects.
## Architecture
![alt](resources/arch.png)
From highest level, KubeVela is composed by only two components:
### 1. User interface layer
Including: `cli`, `dashboard`, `appfile`, they are all client side tools to provide developer facing abstractions by leveraging CUElang based parametering and templating.
### 2. KubeVela core
Including:
- [OAM Kubernetes runtime](https://github.com/crossplane/oam-kubernetes-runtime) to provide application level building blocks such as `Component` and `Application` etc.
- [Built-in workload and trait controllers](https://github.com/oam-dev/kubevela/tree/master/pkg/controller/v1alpha1) to implement core capabilities such as `webservice`, `route` and `rollout` etc.
- Capability Management: manage features of KubeVela following Capability Oriented Architecture.
- Every feature of KubeVela is a "addon", and it is registered by Kubernetes API resource (including CRD) leveraging OAM definition objects.
- CRD Registry: register controllers of Kubernetes add-ons and discover them by CRD. This will enable automatically install controllers/operators when CRD is missing in the cluster.

View File

@@ -4,10 +4,11 @@ VELA_VERSION ?= 0.1.0
GIT_COMMIT ?= git-$(shell git rev-parse --short HEAD)
VELA_VERSION_VAR := github.com/oam-dev/kubevela/version.VelaVersion
VELA_GITVERSION_VAR := github.com/oam-dev/kubevela/version.GitRevision
LDFLAGS ?= "-X $(VELA_VERSION_VAR)=$(VELA_VERSION) -X $(VELA_GITVERSION_VAR)=$(GIT_COMMIT) -X main.chartTGZSource=$$(cat -) -s -w"
LDFLAGS ?= "-X $(VELA_VERSION_VAR)=$(VELA_VERSION) -X $(VELA_GITVERSION_VAR)=$(GIT_COMMIT)"
GOX = go run github.com/mitchellh/gox
TARGETS := darwin/amd64 linux/amd64 windows/amd64
DIST_DIRS := find * -type d -exec
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@@ -20,11 +21,13 @@ all: build
# Run tests
test: fmt vet lint
go test ./pkg/... -coverprofile cover.out
./hack/unit_test.sh
# Build manager binary
build: fmt vet lint
go run hack/chart/generate.go | go build -o bin/vela -ldflags ${LDFLAGS} cmd/vela/main.go
go run hack/chart/generate.go
go build -o bin/vela -ldflags ${LDFLAGS} cmd/vela/main.go
git checkout cmd/vela/fake/chart_source.go
npm-build:
cd dashboard && npm run build && cd ./..
@@ -32,11 +35,26 @@ npm-build:
npm-install:
cd dashboard && npm install && cd ./..
generate-doc:
rm -r documentation/cli/*
go run hack/docgen/gen.go
generate-source:
go run hack/frontend/source.go
cross-build:
go run hack/chart/generate.go | GO111MODULE=on CGO_ENABLED=0 $(GOX) -ldflags $(LDFLAGS) -parallel=3 -output="bin/vela-{{.OS}}-{{.Arch}}" -osarch='$(TARGETS)' ./cmd/vela/
go run hack/chart/generate.go
GO111MODULE=on CGO_ENABLED=0 $(GOX) -ldflags $(LDFLAGS) -parallel=3 -output="_bin/{{.OS}}-{{.Arch}}/vela" -osarch='$(TARGETS)' ./cmd/vela/
compress:
( \
cd _bin && \
$(DIST_DIRS) cp ../LICENSE {} \; && \
$(DIST_DIRS) cp ../README.md {} \; && \
$(DIST_DIRS) tar -zcf vela-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r vela-{}.zip {} \; && \
sha256sum vela-* > sha256sums.txt \
)
# Run against the configured Kubernetes cluster in ~/.kube/config
run: fmt vet
@@ -51,7 +69,7 @@ vet:
go vet ./...
lint: golangci
$(GOLANGCILINT) run -E golint,goimports ./...
$(GOLANGCILINT) run --timeout 10m -E golint,goimports ./...
# Build the docker image
docker-build: test
@@ -62,8 +80,10 @@ docker-push:
docker push ${IMG}
e2e-setup:
bin/vela install --image-pull-policy IfNotPresent --image-repo vela-core-test --image-tag $(GIT_COMMIT)
ginkgo version
ginkgo -v -r e2e/setup
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-core,app.kubernetes.io/instance=kubevela -n vela-system --timeout=600s
bin/vela dashboard &
e2e-test:
@@ -77,6 +97,10 @@ e2e-api-test:
e2e-cleanup:
# Clean up
# load docker image to the kind cluster
kind-load:
docker build -t vela-core-test:$(GIT_COMMIT) .
kind load docker-image vela-core-test:$(GIT_COMMIT) || { echo >&2 "kind not installed or error loading image: $(IMAGE)"; exit 1; }
# Image URL to use all building/pushing image targets
IMG ?= vela-core:latest
@@ -88,29 +112,29 @@ core-test: generate fmt vet manifests
go test ./pkg/... -coverprofile cover.out
# Build manager binary
manager: generate fmt vet
manager: generate fmt vet lint manifests
go build -o bin/manager ./cmd/core/main.go
# Run against the configured Kubernetes cluster in ~/.kube/config
core-run: generate fmt vet manifests
go run ./cmd/core/main.go
# Install CRDs into a cluster
# Install CRDs and Definitions of Vela Core into a cluster, this is for develop convenient.
core-install: manifests
kustomize build config/crd | kubectl apply -f -
kubectl apply -f charts/vela-core/crds/
kubectl apply -f charts/vela-core/templates/definitions/
kubectl apply -f charts/third_party/prometheus
# Uninstall CRDs from a cluster
# Uninstall CRDs and Definitions of Vela Core from a cluster, this is for develop convenient.
core-uninstall: manifests
kustomize build config/crd | kubectl delete -f -
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
core-deploy: manifests
cd config/manager && kustomize edit set image controller=${IMG}
kustomize build config/default | kubectl apply -f -
kubectl delete -f charts/vela-core/crds/
kubectl delete -f charts/vela-core/templates/definitions/
kubectl delete -f charts/third_party/prometheus
# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=charts/vela/crds
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=charts/vela-core/crds
rm charts/vela-core/crds/_.yaml
# Generate code
generate: controller-gen

View File

@@ -1,3 +1,7 @@
domain: oam.dev
repo: github.com/oam-dev/kubevela
resources:
- group: standard
kind: Route
version: v1alpha1
version: "2"

338
README.md
View File

@@ -1,6 +1,27 @@
# KubeVela
The Open Application Platform based on Kubernetes and OAM.
The Open Application Platform based on Kubernetes and Open Application Model (OAM).
## Project Status
:rotating_light: **Warning: this project is still a work in progress with lots of rough edges, please don't look inside unless you know what you are doing.**
KubeVela project is initialized and maintained by the cloud native community since day 0 with [bootstrapping contributors from 8+ different organizations](https://github.com/oam-dev/kubevela/graphs/contributors). We intend for KubeVela to have a open governance since the very beginning and donate the project to neutral foundation as soon as it's released.
## Purpose and Goal
- For developers and operators
- KubeVela, as an out-of-box Cloud Native Application Management Platform, provides numerous workloads and operation tooling for application defining, deployment, scaling, traffic, rollout, routing, monitoring, logging, alerting, CI/CD and so on.
- For platform builders
- KubeVela, as a highly extensible PaaS/Serverless Core, provides pluggable capabilities, an elegant way to integrate any workloads and operational capabilities (i.e. traits).
## Design and Architecture
Read more about [KubeVela's high level design and architecture](DESIGN.md).
## Demo Instructions
See the demo instructions below get a sense of what we've accomplished and are working on.
## Install
@@ -11,87 +32,267 @@ The Open Application Platform based on Kubernetes and OAM.
### Get the Vela CLI
Download the `vela` binary from the [Releases page](https://github.com/oam-dev/kubevela/releases). Change file mod and add it to `$PATH` to get started.
Download the `vela` binary from the [releases page](https://github.com/oam-dev/kubevela/releases). Unpack the `vela` binary and add it to `$PATH` to get started.
For exmaple:
```shell
chmod a+x vela-v0.0.2-darwin-amd64
sudo mv ./vela-v0.0.2-darwin-amd64 /usr/local/bin/vela
sudo mv ./vela /usr/local/bin/vela
```
### Install Vela Core
```shell script
```console
$ vela install
- Installing Vela Core:
- Installing builtin capabilities:
Successful applied 4 kinds of Workloads and Traits: deployments.apps,containerizedworkloads.core.oam.dev,manualscalertraits.core.oam.dev,simplerollouttraits.extend.oam.dev.
syncing workload definitions from cluster...
[WARN]handle template task: #Template.metadata.name: reference "task" not found
get 5 workload definitions from cluster, syncing...5 workload definitions successfully synced
syncing trait definitions from cluster...
[WARN]handle template metricstraits.standard.oam.dev: #Template.metadata.name: reference "metricstraits" not found
get 2 trait definitions from cluster, syncing...2 trait definitions successfully synced
- Finished.
```
This command will install vela core controller into your K8s cluster, along with built-in workloads and traits.
## Using KubeVela
After `vela install` you will see available workloads and traits.
```console
$ vela workloads
NAME DEFINITION
backend podspecworkloads.standard.oam.dev
task jobs.batch.k8s.io
webservice podspecworkloads.standard.oam.dev
```
## Demos
#### Create ENV
```
$ vela env init test --namespace test
Create env succeed, current env is test
$ vela env ls
NAME CURRENT NAMESPACE
default default
test * test
$ vela env switch default
Switch env succeed, current env is default
$ vela env delete test
test deleted
$ vela env delete default
Error: you can't delete current using default
```console
$ vela traits
NAME DEFINITION APPLIES TO
route routes.standard.oam.dev webservice,backend
scale manualscalertraits.core.oam.dev webservice,backend
```
#### workload run
```shell script
$ vela comp run -t deployment app123 -p 80 --image nginx:1.9.4
Creating AppConfig app123
### Create environment
Before working with your application, you should prepare an deploy environment for it (e.g. test, staging, prod etc).
```console
$ vela env init demo --namespace demo --email my@email.com --domain kubevela.io
ENVIROMENT demo CREATED, Namespace: demo, Email: my@email.com.
```
Vela will create a Kubernetes namespace called `demo` , with namespace level issuer for certificate generation using the email you provided.
You could check the environment metadata in your local:
```console
$ cat ~/.vela/envs/demo/config.json
{"name":"demo","namespace":"demo","email":"my@email.com","domain":"kubevela.io","issuer":"oam-env-demo"}
```
### Create simple component
Then let's create application, we will use the `demo` environment.
```console
$ vela comp deploy mycomp -t webservice --image crccheck/hello-world --port 8000 --app myapp
Creating AppConfig appcomp
SUCCEED
$ vela comp status app123
```
#### app
### Create micro-services application
Vela supports micro-services application by default thanks to Open Application Model.
```console
$ vela comp deploy db -t backend --image crccheck/hello-world --app myapp
Creating App myapp
SUCCEED
```
$ vela app ls
app123
poc08032042
poc1039
```console
$ vela comp ls
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
ccc ccc deployment Deployed 2020-08-27 10:56:41 +0800 CST
com1 com1 Deployed 2020-08-26 16:45:50 +0800 CST
com2 com1 Deployed 2020-08-26 16:45:50 +0800 CST
myapp myapp route,scale Deployed 2020-08-19 15:11:17 +0800 CST
$ vela app delete app123
Deleting AppConfig "app123"
DELETE SUCCEED
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
db myapp backend Deployed 2020-09-18 22:42:04 +0800 CST
mycomp myapp webservice Deployed 2020-09-18 22:42:04 +0800 CST
```
#### Auto-Completion
#### Under the hood
In Kubernetes, vela creates an OAM application configuration named `myapp` to manage all related components.
```console
$ kubectl get appconfig -n demo
NAME AGE
myapp 24s
```
```console
$ kubectl get components -n demo
NAME AGE
mycomp 24s
db 10s
```
Vela Core is responsible for managing the underlying Kubernetes resources linked with the components and application configuration above.
```console
$ kubectl get deployment -n demo
NAME READY UP-TO-DATE AVAILABLE AGE
mycomp 1/1 1 1 38s
db 1/1 1 1 20s
```
```console
$ kubectl get svc -n demo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mycomp ClusterIP 172.21.4.228 <none> 8080/TCP 49s
```
### Manage operational configurations of the application
Vela leverages OAM trait system to manage operational configurations such as `scale`, `route`, `canary`, `autocale`etc in application centric approach.
Let's take `route` as example.
### `route`
If you want to use `route`, please make sure you have [nginx-ingress controller[https://kubernetes.github.io/ingress-nginx/deploy/] in your cluster.
```console
$ vela route mycomp --app myapp
Adding route for app mycomp
Succeeded!
```
For now you have to check the public address manually (this will be fixed soon so `vela route` will return visiting URL as result):
```console
$ kubectl get ingress -n demo
NAME HOSTS ADDRESS PORTS AGE
mycomp-trait-5b576c4fc mycomp.kubevela.io 123.57.10.233 80, 443 73s
```
And after you configure the `kubevela.io` domain pointing to the public address above.
Your application will be reached by `https://mycomp.kubevela.io` with `mTLS` automatically enabled.
### Under the hood
Vela will manage the underlying Kubernetes resource which implements the `route` trait.
```console
$ kubectl get routes.standard.oam.dev -n demo
NAME AGE
mycomp-trait-5b576c4fc 18s
```
`routes.standard.oam.dev` is a CRD controller which will manage ingress, domain, certificate etc for your application.
### Check status
Check the application:
```console
$ vela app show myapp
About:
Name: myapp
Created at: 2020-09-18 22:42:04.191171 +0800 CST
Updated at: 2020-09-18 22:51:11.128997 +0800 CST
Environment:
Namespace: demo
Components:
Name Type Traits
db backend
mycomp webservice route
```
Check specific component:
```console
$ vela comp show mycomp
About:
Name: mycomp
WorkloadType: webservice
Application: myapp
Environment:
Namespace: demo
Arguments:
image: crccheck/hello-world
name: mycomp
port: 8000
Traits:
route:
domain: mycomp.kubevela.io
issuer: oam-env-demo
name: route
```
```
$ vela comp status mycomp
Showing status of Component mycomp deployed in Environment demo
Component Status:
Name: mycomp PodSpecWorkload(type) UNKNOWN APIVersion standard.oam.dev/v1alpha1 Kind PodSpecWorkload workload is unknown for HealthScope
Traits
└─Trait/route
Last Deployment:
Created at: 2020-09-18 22:42:04 +0800 CST
Updated at: 2020-09-18T22:51:11+08:00
```
### Delete application or component
```console
$ vela app ls
myapp
```
```console
$ vela comp ls
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
db myapp backend Deployed 2020-09-18 22:42:04 +0800 CST
mycomp myapp webservice route Deployed 2020-09-18 22:42:04 +0800 CST
```
```console
$ vela comp delete db
Deleting Component 'db' from Application 'db'
```
```console
$ vela comp ls
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
mycomp myapp webservice route Deployed 2020-09-18 22:42:04 +0800 CST
```
```console
$ vela app delete myapp
Deleting Application "myapp"
delete apps succeed myapp from demo
```
## Dashboard
Vela has a simple client side dashboard for you to interact with (note it's still under development). The functionality is equivalent to the vela cli.
```console
$ vela dashboard
```
#### Auto-completion
##### bash
```shell script
```console
To load completions in your current shell session:
$ source <(vela completion bash)
@@ -104,7 +305,7 @@ MacOS:
##### zsh
```shell script
```console
To load completions in your current shell session:
$ source <(vela completion zsh)
@@ -114,21 +315,24 @@ $ vela completion zsh > "${fpath[1]}/_vela"
### Clean your environment
```shell script
$ helm uninstall vela-core -n oam-system
release "vela-core" uninstalled
```console
$ helm uninstall kubevela -n vela-system
release "kubevela" uninstalled
```
```shell script
$ kubectl delete crd workloaddefinitions.core.oam.dev traitdefinitions.core.oam.dev
```console
$ kubectl delete crd workloaddefinitions.core.oam.dev traitdefinitions.core.oam.dev scopedefinitions.core.oam.dev
customresourcedefinition.apiextensions.k8s.io "workloaddefinitions.core.oam.dev" deleted
customresourcedefinition.apiextensions.k8s.io "traitdefinitions.core.oam.dev" deleted
```
```shell script
```console
$ rm -r ~/.vela
```
## CONTRIBUTING
## Contributing
Check out [CONTRIBUTING.md](./CONTRIBUTING.md) to see how to develop with KubeVela.
## Code of Conduct
This project has adopted the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for further details.

View File

@@ -0,0 +1,59 @@
/*
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 v1alpha2
import (
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// ApplicationDeploymentSpec defines the desired state of ApplicationDeployment
type ApplicationDeploymentSpec struct {
//TODO add spec here
}
// ApplicationDeploymentStatus defines the observed state of ApplicationDeployment
type ApplicationDeploymentStatus struct {
//TODO add status field here
runtimev1alpha1.ConditionedStatus `json:",inline"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:categories={oam}
// +kubebuilder:subresource:status
// ApplicationDeployment is the Schema for the ApplicationDeployment API
type ApplicationDeployment struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ApplicationDeploymentSpec `json:"spec,omitempty"`
Status ApplicationDeploymentStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ApplicationDeploymentList contains a list of ApplicationDeployment
type ApplicationDeploymentList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ApplicationDeployment `json:"items"`
}
func init() {
SchemeBuilder.Register(&ApplicationDeployment{}, &ApplicationDeploymentList{})
}

View File

@@ -0,0 +1,35 @@
/*
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 v1alpha2 contains API Schema definitions for the core.oam.dev v1alpha2 API group
// +kubebuilder:object:generate=true
// +groupName=core.oam.dev
package v1alpha2
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "core.oam.dev", Version: "v1alpha2"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)

View File

@@ -0,0 +1,115 @@
// +build !ignore_autogenerated
/*
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDeployment) DeepCopyInto(out *ApplicationDeployment) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeployment.
func (in *ApplicationDeployment) DeepCopy() *ApplicationDeployment {
if in == nil {
return nil
}
out := new(ApplicationDeployment)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ApplicationDeployment) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDeploymentList) DeepCopyInto(out *ApplicationDeploymentList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ApplicationDeployment, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeploymentList.
func (in *ApplicationDeploymentList) DeepCopy() *ApplicationDeploymentList {
if in == nil {
return nil
}
out := new(ApplicationDeploymentList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ApplicationDeploymentList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDeploymentSpec) DeepCopyInto(out *ApplicationDeploymentSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeploymentSpec.
func (in *ApplicationDeploymentSpec) DeepCopy() *ApplicationDeploymentSpec {
if in == nil {
return nil
}
out := new(ApplicationDeploymentSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDeploymentStatus) DeepCopyInto(out *ApplicationDeploymentStatus) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDeploymentStatus.
func (in *ApplicationDeploymentStatus) DeepCopy() *ApplicationDeploymentStatus {
if in == nil {
return nil
}
out := new(ApplicationDeploymentStatus)
in.DeepCopyInto(out)
return out
}

View File

@@ -40,11 +40,13 @@ type Capability struct {
Name string `json:"name"`
Type CapType `json:"type"`
CueTemplate string `json:"template,omitempty"`
CueTemplateURI string `json:"templateURI,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
DefinitionPath string `json:"definition"`
CrdName string `json:"crdName,omitempty"`
Center string `json:"center,omitempty"`
Status string `json:"status,omitempty"`
Description string `json:"description,omitempty"`
//trait only
AppliesTo []string `json:"appliesTo,omitempty"`
@@ -56,10 +58,11 @@ type Capability struct {
}
type Chart struct {
Repo string `json:"repo"`
URL string `json:"url"`
Name string `json:"name"`
Version string `json:"version"`
Repo string `json:"repo"`
URL string `json:"url"`
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
Version string `json:"version"`
}
type Installation struct {

View File

@@ -1,8 +1,8 @@
package types
const (
DefaultOAMNS = "oam-system"
DefaultOAMReleaseName = "vela-core"
DefaultOAMNS = "vela-system"
DefaultOAMReleaseName = "kubevela"
DefaultOAMRuntimeChartName = "vela-core"
DefaultOAMVersion = ">0.0.0-0"
@@ -11,13 +11,11 @@ const (
)
const (
AnnAPIVersion = "definition.oam.dev/apiVersion"
AnnKind = "definition.oam.dev/kind"
AnnAPIVersion = "definition.oam.dev/apiVersion"
AnnKind = "definition.oam.dev/kind"
AnnDescription = "definition.oam.dev/description"
// Indicate which workloadDefinition generate from
AnnWorkloadDef = "workload.oam.dev/name"
// Indicate which traitDefinition generate from
AnnTraitDef = "trait.oam.dev/name"
LabelPodSpecable = "workload.oam.dev/podspecable"
)
const (
@@ -27,8 +25,13 @@ const (
type EnvMeta struct {
Name string `json:"name"`
Current string `json:"current,omitempty"`
Namespace string `json:"namespace"`
Email string `json:"email,omitempty"`
Domain string `json:"domain,omitempty"`
// Below are not arguments, should be auto-generated
Issuer string `json:"issuer"`
Current string `json:"current,omitempty"`
}
const (

View File

@@ -23,20 +23,20 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ContainerizedSpec defines the desired state of Containerized
type ContainerizedSpec struct {
// PodSpecWorkloadSpec defines the desired state of PodSpecWorkload
type PodSpecWorkloadSpec struct {
// Replicas is the desired number of replicas of the given podSpec.
// These are replicas in the sense that they are instantiations of the same podSpec.
// If unspecified, defaults to 1.
Replicas *int32 `json:"replicas"`
// PodSpec describes the pods that will be created,
// we omit the meta part as it will be exactly the same as the containerized
// we omit the meta part as it will be exactly the same as the PodSpecWorkload
PodSpec v1.PodSpec `json:"podSpec"`
}
// ContainerizedStatus defines the observed state of Containerized
type ContainerizedStatus struct {
// PodSpecWorkloadStatus defines the observed state of PodSpecWorkload
type PodSpecWorkloadStatus struct {
cpv1alpha1.ConditionedStatus `json:",inline"`
// Resources managed by this workload.
@@ -45,36 +45,36 @@ type ContainerizedStatus struct {
// +kubebuilder:object:root=true
// Containerized is the Schema for the containerizeds API
// PodSpecWorkload is the Schema for the PodSpec API
// +kubebuilder:resource:categories={oam}
// +kubebuilder:subresource:status
type Containerized struct {
type PodSpecWorkload struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ContainerizedSpec `json:"spec,omitempty"`
Status ContainerizedStatus `json:"status,omitempty"`
Spec PodSpecWorkloadSpec `json:"spec,omitempty"`
Status PodSpecWorkloadStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ContainerizedList contains a list of Containerized
type ContainerizedList struct {
// PodSpecWorkloadList contains a list of PodSpecWorkload
type PodSpecWorkloadList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Containerized `json:"items"`
Items []PodSpecWorkload `json:"items"`
}
func init() {
SchemeBuilder.Register(&Containerized{}, &ContainerizedList{})
SchemeBuilder.Register(&PodSpecWorkload{}, &PodSpecWorkloadList{})
}
var _ oam.Workload = &Containerized{}
var _ oam.Workload = &PodSpecWorkload{}
func (in *Containerized) SetConditions(c ...cpv1alpha1.Condition) {
func (in *PodSpecWorkload) SetConditions(c ...cpv1alpha1.Condition) {
in.Status.SetConditions(c...)
}
func (in *Containerized) GetCondition(c cpv1alpha1.ConditionType) cpv1alpha1.Condition {
func (in *PodSpecWorkload) GetCondition(c cpv1alpha1.ConditionType) cpv1alpha1.Condition {
return in.Status.GetCondition(c)
}

153
api/v1alpha1/route_types.go Normal file
View File

@@ -0,0 +1,153 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// RouteSpec defines the desired state of Route
type RouteSpec struct {
// WorkloadReference to the workload whose metrics needs to be exposed
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
// Host is the host of the route
Host string `json:"host"`
// TLS indicate route trait will create SSL secret using cert-manager with specified issuer
// If this is nil, route trait will use a selfsigned issuer
TLS *TLS `json:"tls,omitempty"`
// Rules contain multiple rules of route
Rules []Rule `json:"rules,omitempty"`
// Provider indicate which ingress controller implementation the route trait will use, by default it's nginx-ingress
Provider string `json:"provider,omitempty"`
}
// Rule defines to route rule
type Rule struct {
// Name will become the suffix of underlying ingress created by this rule, if not, will use index as suffix.
Name string `json:"name,omitempty"`
// Path is location Path, default for "/"
Path string `json:"path,omitempty"`
// RewriteTarget will rewrite request from Path to RewriteTarget path.
RewriteTarget string `json:"rewriteTarget,omitempty"`
// CustomHeaders pass a custom list of headers to the backend service.
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
// DefaultBackend will become the ingress default backend if the backend is not available
DefaultBackend *runtimev1alpha1.TypedReference `json:"defaultBackend,omitempty"`
// Backend indicate how to connect backend service
// If it's nil, will auto discovery
Backend *Backend `json:"backend,omitempty"`
}
type TLS struct {
IssuerName string `json:"issuerName,omitempty"`
// Type indicate the issuer is ClusterIssuer or Issuer(namespace issuer), by default, it's Issuer
// +kubebuilder:default:=Issuer
Type IssuerType `json:"type,omitempty"`
}
type IssuerType string
const (
ClusterIssuer IssuerType = "ClusterIssuer"
NamespaceIssuer IssuerType = "Issuer"
)
// Route will automatically discover podSpec and label for BackendService.
// If BackendService is already set, discovery won't work.
// If BackendService is not set, the discovery mechanism will work.
type Backend struct {
// ReadTimeout used for setting read timeout duration for backend service, the unit is second.
ReadTimeout int `json:"readTimeout,omitempty"`
// SendTimeout used for setting send timeout duration for backend service, the unit is second.
SendTimeout int `json:"sendTimeout,omitempty"`
// BackendService specifies the backend K8s service and port, it's optional
BackendService *BackendServiceRef `json:"backendService,omitempty"`
}
// BackendServiceRef specifies the backend K8s service and port, if specified, the two fields are all required
type BackendServiceRef struct {
// Port allow you direct specify backend service port.
Port intstr.IntOrString `json:"port"`
// ServiceName allow you direct specify K8s service for backend service.
ServiceName string `json:"serviceName"`
}
// RouteStatus defines the observed state of Route
type RouteStatus struct {
Ingresses []runtimev1alpha1.TypedReference `json:"ingresses,omitempty"`
Service *runtimev1alpha1.TypedReference `json:"service,omitempty"`
runtimev1alpha1.ConditionedStatus `json:",inline"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:categories={oam}
// +kubebuilder:subresource:status
// Route is the Schema for the routes API
type Route struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RouteSpec `json:"spec,omitempty"`
Status RouteStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// RouteList contains a list of Route
type RouteList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Route `json:"items"`
}
func init() {
SchemeBuilder.Register(&Route{}, &RouteList{})
}
var _ oam.Trait = &Route{}
func (r *Route) SetConditions(c ...runtimev1alpha1.Condition) {
r.Status.SetConditions(c...)
}
func (r *Route) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
return r.Status.GetCondition(c)
}
// GetWorkloadReference of this ManualScalerTrait.
func (r *Route) GetWorkloadReference() runtimev1alpha1.TypedReference {
return r.Spec.WorkloadReference
}
// SetWorkloadReference of this ManualScalerTrait.
func (r *Route) SetWorkloadReference(rt runtimev1alpha1.TypedReference) {
r.Spec.WorkloadReference = rt
}

View File

@@ -26,102 +26,37 @@ import (
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Containerized) DeepCopyInto(out *Containerized) {
func (in *Backend) DeepCopyInto(out *Backend) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Containerized.
func (in *Containerized) DeepCopy() *Containerized {
if in == nil {
return nil
}
out := new(Containerized)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Containerized) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContainerizedList) DeepCopyInto(out *ContainerizedList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Containerized, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerizedList.
func (in *ContainerizedList) DeepCopy() *ContainerizedList {
if in == nil {
return nil
}
out := new(ContainerizedList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ContainerizedList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContainerizedSpec) DeepCopyInto(out *ContainerizedSpec) {
*out = *in
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = new(int32)
if in.BackendService != nil {
in, out := &in.BackendService, &out.BackendService
*out = new(BackendServiceRef)
**out = **in
}
in.PodSpec.DeepCopyInto(&out.PodSpec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerizedSpec.
func (in *ContainerizedSpec) DeepCopy() *ContainerizedSpec {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend.
func (in *Backend) DeepCopy() *Backend {
if in == nil {
return nil
}
out := new(ContainerizedSpec)
out := new(Backend)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContainerizedStatus) DeepCopyInto(out *ContainerizedStatus) {
func (in *BackendServiceRef) DeepCopyInto(out *BackendServiceRef) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
*out = make([]corev1alpha1.TypedReference, len(*in))
copy(*out, *in)
}
out.Port = in.Port
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerizedStatus.
func (in *ContainerizedStatus) DeepCopy() *ContainerizedStatus {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendServiceRef.
func (in *BackendServiceRef) DeepCopy() *BackendServiceRef {
if in == nil {
return nil
}
out := new(ContainerizedStatus)
out := new(BackendServiceRef)
in.DeepCopyInto(out)
return out
}
@@ -223,6 +158,252 @@ func (in *MetricsTraitStatus) DeepCopy() *MetricsTraitStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSpecWorkload) DeepCopyInto(out *PodSpecWorkload) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpecWorkload.
func (in *PodSpecWorkload) DeepCopy() *PodSpecWorkload {
if in == nil {
return nil
}
out := new(PodSpecWorkload)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PodSpecWorkload) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSpecWorkloadList) DeepCopyInto(out *PodSpecWorkloadList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]PodSpecWorkload, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpecWorkloadList.
func (in *PodSpecWorkloadList) DeepCopy() *PodSpecWorkloadList {
if in == nil {
return nil
}
out := new(PodSpecWorkloadList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PodSpecWorkloadList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSpecWorkloadSpec) DeepCopyInto(out *PodSpecWorkloadSpec) {
*out = *in
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = new(int32)
**out = **in
}
in.PodSpec.DeepCopyInto(&out.PodSpec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpecWorkloadSpec.
func (in *PodSpecWorkloadSpec) DeepCopy() *PodSpecWorkloadSpec {
if in == nil {
return nil
}
out := new(PodSpecWorkloadSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSpecWorkloadStatus) DeepCopyInto(out *PodSpecWorkloadStatus) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
*out = make([]corev1alpha1.TypedReference, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpecWorkloadStatus.
func (in *PodSpecWorkloadStatus) DeepCopy() *PodSpecWorkloadStatus {
if in == nil {
return nil
}
out := new(PodSpecWorkloadStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Route) DeepCopyInto(out *Route) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route.
func (in *Route) DeepCopy() *Route {
if in == nil {
return nil
}
out := new(Route)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Route) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteList) DeepCopyInto(out *RouteList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Route, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteList.
func (in *RouteList) DeepCopy() *RouteList {
if in == nil {
return nil
}
out := new(RouteList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RouteList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteSpec) DeepCopyInto(out *RouteSpec) {
*out = *in
out.WorkloadReference = in.WorkloadReference
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLS)
**out = **in
}
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]Rule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteSpec.
func (in *RouteSpec) DeepCopy() *RouteSpec {
if in == nil {
return nil
}
out := new(RouteSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteStatus) DeepCopyInto(out *RouteStatus) {
*out = *in
if in.Ingresses != nil {
in, out := &in.Ingresses, &out.Ingresses
*out = make([]corev1alpha1.TypedReference, len(*in))
copy(*out, *in)
}
if in.Service != nil {
in, out := &in.Service, &out.Service
*out = new(corev1alpha1.TypedReference)
**out = **in
}
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteStatus.
func (in *RouteStatus) DeepCopy() *RouteStatus {
if in == nil {
return nil
}
out := new(RouteStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Rule) DeepCopyInto(out *Rule) {
*out = *in
if in.CustomHeaders != nil {
in, out := &in.CustomHeaders, &out.CustomHeaders
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.DefaultBackend != nil {
in, out := &in.DefaultBackend, &out.DefaultBackend
*out = new(corev1alpha1.TypedReference)
**out = **in
}
if in.Backend != nil {
in, out := &in.Backend, &out.Backend
*out = new(Backend)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
func (in *Rule) DeepCopy() *Rule {
if in == nil {
return nil
}
out := new(Rule)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScapeServiceEndPoint) DeepCopyInto(out *ScapeServiceEndPoint) {
*out = *in
@@ -250,3 +431,18 @@ func (in *ScapeServiceEndPoint) DeepCopy() *ScapeServiceEndPoint {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLS) DeepCopyInto(out *TLS) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.
func (in *TLS) DeepCopy() *TLS {
if in == nil {
return nil
}
out := new(TLS)
in.DeepCopyInto(out)
return out
}

View File

@@ -1,4 +0,0 @@
#!/bin/bash
# Download and unpack operator Lifecycle Manager (coreos)
curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/0.15.1/install.sh | bash -s 0.15.1

View File

@@ -1,24 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: my-grafana-operator
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: operatorgroup
namespace: my-grafana-operator
spec:
targetNamespaces:
- my-grafana-operator
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: my-grafana-operator
namespace: my-grafana-operator
spec:
channel: alpha
name: grafana-operator
source: operatorhubio-catalog
sourceNamespace: olm

View File

@@ -1,6 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
mornitoring: oam
name: monitoring

View File

@@ -1,265 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: podmonitors.monitoring.coreos.com
spec:
group: monitoring.coreos.com
names:
kind: PodMonitor
listKind: PodMonitorList
plural: podmonitors
singular: podmonitor
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: PodMonitor defines monitoring for a set of pods.
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
spec:
description: Specification of desired Pod selection for target discovery
by Prometheus.
properties:
jobLabel:
description: The label to use to retrieve the job name from.
type: string
namespaceSelector:
description: Selector to select which namespaces the Endpoints objects
are discovered from.
properties:
any:
description: Boolean describing whether all namespaces are selected
in contrast to a list restricting them.
type: boolean
matchNames:
description: List of namespace names.
items:
type: string
type: array
type: object
podMetricsEndpoints:
description: A list of endpoints allowed as part of this PodMonitor.
items:
description: PodMetricsEndpoint defines a scrapeable endpoint of
a Kubernetes Pod serving Prometheus metrics.
properties:
honorLabels:
description: HonorLabels chooses the metric's labels on collisions
with target labels.
type: boolean
honorTimestamps:
description: HonorTimestamps controls whether Prometheus respects
the timestamps present in scraped data.
type: boolean
interval:
description: Interval at which metrics should be scraped
type: string
metricRelabelings:
description: MetricRelabelConfigs to apply to samples before
ingestion.
items:
description: 'RelabelConfig allows dynamic rewriting of the
label set, being applied to samples before ingestion. It
defines `<metric_relabel_configs>`-section of Prometheus
configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs'
properties:
action:
description: Action to perform based on regex matching.
Default is 'replace'
type: string
modulus:
description: Modulus to take of the hash of the source
label values.
format: int64
type: integer
regex:
description: Regular expression against which the extracted
value is matched. Default is '(.*)'
type: string
replacement:
description: Replacement value against which a regex replace
is performed if the regular expression matches. Regex
capture groups are available. Default is '$1'
type: string
separator:
description: Separator placed between concatenated source
label values. default is ';'.
type: string
sourceLabels:
description: The source labels select values from existing
labels. Their content is concatenated using the configured
separator and matched against the configured regular
expression for the replace, keep, and drop actions.
items:
type: string
type: array
targetLabel:
description: Label to which the resulting value is written
in a replace action. It is mandatory for replace actions.
Regex capture groups are available.
type: string
type: object
type: array
params:
additionalProperties:
items:
type: string
type: array
description: Optional HTTP URL parameters
type: object
path:
description: HTTP path to scrape for metrics.
type: string
port:
description: Name of the pod port this endpoint refers to. Mutually
exclusive with targetPort.
type: string
proxyUrl:
description: ProxyURL eg http://proxyserver:2195 Directs scrapes
to proxy through this endpoint.
type: string
relabelings:
description: 'RelabelConfigs to apply to samples before ingestion.
More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config'
items:
description: 'RelabelConfig allows dynamic rewriting of the
label set, being applied to samples before ingestion. It
defines `<metric_relabel_configs>`-section of Prometheus
configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs'
properties:
action:
description: Action to perform based on regex matching.
Default is 'replace'
type: string
modulus:
description: Modulus to take of the hash of the source
label values.
format: int64
type: integer
regex:
description: Regular expression against which the extracted
value is matched. Default is '(.*)'
type: string
replacement:
description: Replacement value against which a regex replace
is performed if the regular expression matches. Regex
capture groups are available. Default is '$1'
type: string
separator:
description: Separator placed between concatenated source
label values. default is ';'.
type: string
sourceLabels:
description: The source labels select values from existing
labels. Their content is concatenated using the configured
separator and matched against the configured regular
expression for the replace, keep, and drop actions.
items:
type: string
type: array
targetLabel:
description: Label to which the resulting value is written
in a replace action. It is mandatory for replace actions.
Regex capture groups are available.
type: string
type: object
type: array
scheme:
description: HTTP scheme to use for scraping.
type: string
scrapeTimeout:
description: Timeout after which the scrape is ended
type: string
targetPort:
anyOf:
- type: integer
- type: string
description: 'Deprecated: Use ''port'' instead.'
x-kubernetes-int-or-string: true
type: object
type: array
podTargetLabels:
description: PodTargetLabels transfers labels on the Kubernetes Pod
onto the target.
items:
type: string
type: array
sampleLimit:
description: SampleLimit defines per-scrape limit on number of scraped
samples that will be accepted.
format: int64
type: integer
selector:
description: Selector to select Pod objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: A label selector requirement is a selector that
contains values, a key, and an operator that relates the key
and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship to
a set of values. Valid operators are In, NotIn, Exists
and DoesNotExist.
type: string
values:
description: values is an array of string values. If the
operator is In or NotIn, the values array must be non-empty.
If the operator is Exists or DoesNotExist, the values
array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator
is "In", and the values array contains only "value". The requirements
are ANDed.
type: object
type: object
required:
- podMetricsEndpoints
- selector
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -1,212 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: probes.monitoring.coreos.com
spec:
group: monitoring.coreos.com
names:
kind: Probe
listKind: ProbeList
plural: probes
singular: probe
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: Probe defines monitoring for a set of static targets or ingresses.
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
spec:
description: Specification of desired Ingress selection for target discovery
by Prometheus.
properties:
interval:
description: Interval at which targets are probed using the configured
prober. If not specified Prometheus' global scrape interval is used.
type: string
jobName:
description: The job name assigned to scraped metrics by default.
type: string
module:
description: 'The module to use for probing specifying how to probe
the target. Example module configuring in the blackbox exporter:
https://github.com/prometheus/blackbox_exporter/blob/master/example.yml'
type: string
prober:
description: Specification for the prober to use for probing targets.
The prober.URL parameter is required. Targets cannot be probed if
left empty.
properties:
path:
description: Path to collect metrics from. Defaults to `/probe`.
type: string
scheme:
description: HTTP scheme to use for scraping. Defaults to `http`.
type: string
url:
description: Mandatory URL of the prober.
type: string
required:
- url
type: object
scrapeTimeout:
description: Timeout for scraping metrics from the Prometheus exporter.
type: string
targets:
description: Targets defines a set of static and/or dynamically discovered
targets to be probed using the prober.
properties:
ingress:
description: Ingress defines the set of dynamically discovered
ingress objects which hosts are considered for probing.
properties:
namespaceSelector:
description: Select Ingress objects by namespace.
properties:
any:
description: Boolean describing whether all namespaces
are selected in contrast to a list restricting them.
type: boolean
matchNames:
description: List of namespace names.
items:
type: string
type: array
type: object
relabelingConfigs:
description: 'RelabelConfigs to apply to samples before ingestion.
More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config'
items:
description: 'RelabelConfig allows dynamic rewriting of
the label set, being applied to samples before ingestion.
It defines `<metric_relabel_configs>`-section of Prometheus
configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs'
properties:
action:
description: Action to perform based on regex matching.
Default is 'replace'
type: string
modulus:
description: Modulus to take of the hash of the source
label values.
format: int64
type: integer
regex:
description: Regular expression against which the extracted
value is matched. Default is '(.*)'
type: string
replacement:
description: Replacement value against which a regex
replace is performed if the regular expression matches.
Regex capture groups are available. Default is '$1'
type: string
separator:
description: Separator placed between concatenated source
label values. default is ';'.
type: string
sourceLabels:
description: The source labels select values from existing
labels. Their content is concatenated using the configured
separator and matched against the configured regular
expression for the replace, keep, and drop actions.
items:
type: string
type: array
targetLabel:
description: Label to which the resulting value is written
in a replace action. It is mandatory for replace actions.
Regex capture groups are available.
type: string
type: object
type: array
selector:
description: Select Ingress objects by labels.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In, NotIn,
Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values array
must be non-empty. If the operator is Exists or
DoesNotExist, the values array must be empty.
This array is replaced during a strategic merge
patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field is
"key", the operator is "In", and the values array contains
only "value". The requirements are ANDed.
type: object
type: object
type: object
staticConfig:
description: 'StaticConfig defines static targets which are considers
for probing. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#static_config.'
properties:
labels:
additionalProperties:
type: string
description: Labels assigned to all metrics scraped from the
targets.
type: object
static:
description: Targets is a list of URLs to probe using the
configured prober.
items:
type: string
type: array
type: object
type: object
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -1,94 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: prometheusrules.monitoring.coreos.com
spec:
group: monitoring.coreos.com
names:
kind: PrometheusRule
listKind: PrometheusRuleList
plural: prometheusrules
singular: prometheusrule
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: PrometheusRule defines alerting rules for a Prometheus instance
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
spec:
description: Specification of desired alerting rule definitions for Prometheus.
properties:
groups:
description: Content of Prometheus rule file
items:
description: 'RuleGroup is a list of sequentially evaluated recording
and alerting rules. Note: PartialResponseStrategy is only used
by ThanosRuler and will be ignored by Prometheus instances. Valid
values for this field are ''warn'' or ''abort''. More info: https://github.com/thanos-io/thanos/blob/master/docs/components/rule.md#partial-response'
properties:
interval:
type: string
name:
type: string
partial_response_strategy:
type: string
rules:
items:
description: Rule describes an alerting or recording rule.
properties:
alert:
type: string
annotations:
additionalProperties:
type: string
type: object
expr:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
for:
type: string
labels:
additionalProperties:
type: string
type: object
record:
type: string
required:
- expr
type: object
type: array
required:
- name
- rules
type: object
type: array
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -1,82 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
app.kubernetes.io/version: v0.41.1
name: prometheus-operator
rules:
- apiGroups:
- monitoring.coreos.com
resources:
- alertmanagers
- alertmanagers/finalizers
- prometheuses
- prometheuses/finalizers
- thanosrulers
- thanosrulers/finalizers
- servicemonitors
- podmonitors
- probes
- prometheusrules
verbs:
- '*'
- apiGroups:
- apps
resources:
- statefulsets
verbs:
- '*'
- apiGroups:
- ""
resources:
- configmaps
- secrets
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- delete
- apiGroups:
- ""
resources:
- services
- services/finalizers
- endpoints
verbs:
- get
- create
- update
- delete
- apiGroups:
- ""
resources:
- nodes
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create

View File

@@ -1,34 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
app.kubernetes.io/version: v0.41.1
name: prometheus-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus-operator
subjects:
- kind: ServiceAccount
name: prometheus-operator
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
app.kubernetes.io/version: v0.41.1
name: prometheus-operator-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: "cluster-admin"
subjects:
- kind: ServiceAccount
name: prometheus-operator
namespace: monitoring

View File

@@ -1,60 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
app.kubernetes.io/version: v0.41.1
name: prometheus-operator
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
template:
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
app.kubernetes.io/version: v0.41.1
spec:
containers:
- args:
- --kubelet-service=kube-system/kubelet
- --logtostderr=true
- --config-reloader-image=jimmidyson/configmap-reload:v0.4.0
- --prometheus-config-reloader=quay.io/coreos/prometheus-config-reloader:v0.41.1
image: quay.io/coreos/prometheus-operator:v0.41.1
name: prometheus-operator
ports:
- containerPort: 8080
name: http
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
securityContext:
allowPrivilegeEscalation: false
- args:
- --logtostderr
- --secure-listen-address=:8443
- --tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- --upstream=http://127.0.0.1:8080/
image: quay.io/coreos/kube-rbac-proxy:v0.4.1
name: kube-rbac-proxy
ports:
- containerPort: 8443
name: https
securityContext:
runAsUser: 65534
nodeSelector:
beta.kubernetes.io/os: linux
securityContext:
runAsNonRoot: true
runAsUser: 65534
serviceAccountName: prometheus-operator

View File

@@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
app.kubernetes.io/version: v0.41.1
name: prometheus-operator
namespace: monitoring
spec:
clusterIP: None
ports:
- name: https
port: 8443
targetPort: https
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator

View File

@@ -1,9 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: prometheus-operator
app.kubernetes.io/version: v0.41.1
name: prometheus-operator
namespace: monitoring

View File

@@ -1,6 +1,6 @@
apiVersion: v2
name: vela-core
description: A Helm chart for Kubernetes
description: A Helm chart for Kube Vela core
# A chart can be either an 'application' or a 'library' chart.
#

File diff suppressed because it is too large Load Diff

View File

@@ -101,16 +101,29 @@ spec:
to match a value.
properties:
fieldPath:
description: FieldPath specifies got value from
workload/trait object
type: string
op:
description: ConditionOperator specifies the operator
to match a value.
type: string
value:
description: Value specifies an expected value This
is mutually exclusive with ValueFrom
type: string
valueFrom:
description: ValueFrom specifies expected value
from AppConfig This is mutually exclusive with
Value
properties:
fieldPath:
type: string
required:
- fieldPath
type: object
required:
- op
- value
type: object
type: array
fieldPath:
@@ -233,16 +246,29 @@ spec:
requirement to match a value.
properties:
fieldPath:
description: FieldPath specifies got value
from workload/trait object
type: string
op:
description: ConditionOperator specifies the
operator to match a value.
type: string
value:
description: Value specifies an expected value
This is mutually exclusive with ValueFrom
type: string
valueFrom:
description: ValueFrom specifies expected
value from AppConfig This is mutually exclusive
with Value
properties:
fieldPath:
type: string
required:
- fieldPath
type: object
required:
- op
- value
type: object
type: array
fieldPath:
@@ -338,6 +364,8 @@ spec:
- kind
- name
type: object
reason:
type: string
to:
description: DependencyToObject represents the object that
dependency data goes to.
@@ -365,10 +393,47 @@ spec:
type: object
required:
- from
- reason
- to
type: object
type: array
type: object
historyWorkloads:
description: HistoryWorkloads will record history but still working
revision workloads.
items:
description: HistoryWorkload contain the old component revision
that are still running
properties:
revision:
description: component revision of this workload
type: string
workloadRef:
description: Reference to running workload.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: object
type: array
observedGeneration:
description: The generation observed by the appConfig controller.
format: int64
type: integer
status:
description: Status is a place holder for a customized controller
to fill if it needs a single place to summarize the status of the
@@ -482,8 +547,6 @@ spec:
type: object
type: object
type: array
required:
- dependency
type: object
type: object
served: true

View File

@@ -0,0 +1,90 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: applicationdeployments.core.oam.dev
spec:
group: core.oam.dev
names:
categories:
- oam
kind: ApplicationDeployment
listKind: ApplicationDeploymentList
plural: applicationdeployments
singular: applicationdeployment
scope: Namespaced
versions:
- name: v1alpha2
schema:
openAPIV3Schema:
description: ApplicationDeployment is the Schema for the ApplicationDeployment
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
spec:
description: ApplicationDeploymentSpec defines the desired state of ApplicationDeployment
type: object
status:
description: ApplicationDeploymentStatus defines the observed state of
ApplicationDeployment
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -72,6 +72,7 @@ spec:
will specify parameter values using this name.
type: string
required:
default: false
description: Required specifies whether or not a value for this
parameter must be supplied when authoring an ApplicationConfiguration.
type: boolean
@@ -82,7 +83,9 @@ spec:
type: array
workload:
description: A Workload that will be created for each ApplicationConfiguration
that includes this Component. Workloads must be defined by a WorkloadDefinition.
that includes this Component. Workload is an instance of a workloadDefinition.
We either use the GVK info or a special "type" field in the workload
to associate the content of the workload with its workloadDefinition
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
@@ -137,6 +140,10 @@ spec:
- name
- revision
type: object
observedGeneration:
description: The generation observed by the component controller.
format: int64
type: integer
type: object
type: object
served: true

View File

@@ -119,10 +119,76 @@ spec:
- type
type: object
type: array
health:
type: string
healthConditions:
description: WorkloadHealthConditions represents health condition
of workloads in the scope
items:
description: WorkloadHealthCondition represents informative health
condition.
properties:
componentName:
description: ComponentName represents the component name if
target is a workload
type: string
diagnosis:
type: string
healthStatus:
description: HealthStatus represents health status strings.
type: string
targetWorkload:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
workloadStatus:
description: WorkloadStatus represents status of workloads whose
HealthStatus is UNKNOWN.
type: string
required:
- healthStatus
type: object
type: array
scopeHealthCondition:
description: ScopeHealthCondition represents health condition summary
of the scope
properties:
healthStatus:
description: HealthStatus represents health status strings.
type: string
healthyWorkloads:
format: int64
type: integer
total:
format: int64
type: integer
unhealthyWorkloads:
format: int64
type: integer
unknownWorkloads:
format: int64
type: integer
required:
- healthStatus
type: object
required:
- health
- scopeHealthCondition
type: object
type: object
served: true

View File

@@ -85,6 +85,16 @@ spec:
builders
type: object
x-kubernetes-preserve-unknown-fields: true
podSpecPath:
description: PodSpecPath indicates where/if this workload has K8s
podSpec field if one workload has podSpec, trait can do lot's of
assumption such as port, env, volume fields.
type: string
revisionLabel:
description: RevisionLabel indicates which label for underlying resources(e.g.
pods) of this workload can be used by trait to create resource selectors(e.g.
label selector for pods).
type: string
required:
- definitionRef
type: object

View File

@@ -6,22 +6,22 @@ metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: containerizeds.standard.oam.dev
name: podspecworkloads.standard.oam.dev
spec:
group: standard.oam.dev
names:
categories:
- oam
kind: Containerized
listKind: ContainerizedList
plural: containerizeds
singular: containerized
kind: PodSpecWorkload
listKind: PodSpecWorkloadList
plural: podspecworkloads
singular: podspecworkload
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Containerized is the Schema for the containerizeds API
description: PodSpecWorkload is the Schema for the PodSpec API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@@ -36,11 +36,11 @@ spec:
metadata:
type: object
spec:
description: ContainerizedSpec defines the desired state of Containerized
description: PodSpecWorkloadSpec defines the desired state of PodSpecWorkload
properties:
podSpec:
description: PodSpec describes the pods that will be created, we omit
the meta part as it will be exactly the same as the containerized
the meta part as it will be exactly the same as the PodSpecWorkload
properties:
activeDeadlineSeconds:
description: Optional duration in seconds the pod may be active
@@ -5694,7 +5694,7 @@ spec:
- replicas
type: object
status:
description: ContainerizedStatus defines the observed state of Containerized
description: PodSpecWorkloadStatus defines the observed state of PodSpecWorkload
properties:
conditions:
description: Conditions of the resource.

View File

@@ -0,0 +1,255 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: routes.standard.oam.dev
spec:
group: standard.oam.dev
names:
categories:
- oam
kind: Route
listKind: RouteList
plural: routes
singular: route
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Route is the Schema for the routes 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
spec:
description: RouteSpec defines the desired state of Route
properties:
host:
description: Host is the host of the route
type: string
provider:
description: Provider indicate which ingress controller implementation
the route trait will use, by default it's nginx-ingress
type: string
rules:
description: Rules contain multiple rules of route
items:
description: Rule defines to route rule
properties:
backend:
description: Backend indicate how to connect backend service
If it's nil, will auto discovery
properties:
backendService:
description: BackendService specifies the backend K8s service
and port, it's optional
properties:
port:
anyOf:
- type: integer
- type: string
description: Port allow you direct specify backend service
port.
x-kubernetes-int-or-string: true
serviceName:
description: ServiceName allow you direct specify K8s
service for backend service.
type: string
required:
- port
- serviceName
type: object
readTimeout:
description: ReadTimeout used for setting read timeout duration
for backend service, the unit is second.
type: integer
sendTimeout:
description: SendTimeout used for setting send timeout duration
for backend service, the unit is second.
type: integer
type: object
customHeaders:
additionalProperties:
type: string
description: CustomHeaders pass a custom list of headers to
the backend service.
type: object
defaultBackend:
description: DefaultBackend will become the ingress default
backend if the backend is not available
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
name:
description: Name will become the suffix of underlying ingress
created by this rule, if not, will use index as suffix.
type: string
path:
description: Path is location Path, default for "/"
type: string
rewriteTarget:
description: RewriteTarget will rewrite request from Path to
RewriteTarget path.
type: string
type: object
type: array
tls:
description: TLS indicate route trait will create SSL secret using
cert-manager with specified issuer If this is nil, route trait will
use a selfsigned issuer
properties:
issuerName:
type: string
type:
default: Issuer
description: Type indicate the issuer is ClusterIssuer or Issuer(namespace
issuer), by default, it's Issuer
type: string
type: object
workloadRef:
description: WorkloadReference to the workload whose metrics needs
to be exposed
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- host
type: object
status:
description: RouteStatus defines the observed state of Route
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
ingresses:
items:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: array
service:
description: A TypedReference refers to an object by Name, Kind, and
APIVersion. It is commonly used to reference cluster-scoped objects
or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -1,42 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: backend-service
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ContainerizedWorkload"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
metadata: name: containerized.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
}
}
containerized: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: containerizeds.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ContainerizedWorkload"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
metadata:
name: containerized.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
}
}
containerized: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -0,0 +1,39 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: backend
annotations:
definition.oam.dev/apiVersion: "standard.oam.dev/v1alpha1"
definition.oam.dev/kind: "PodSpecWorkload"
definition.oam.dev/description: "Backend worker without ports exposed"
spec:
definitionRef:
name: podspecworkload.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
extension:
template: |
output: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "PodSpecWorkload"
metadata:
name: parameter.name
spec: {
replicas: 1
podSpec: {
containers: [{
image: parameter.image
name: parameter.name
}]
}
}
}
#backend: {
name: string
// +usage=specify app image
// +short=i
image: string
}
parameter: #backend

View File

@@ -2,6 +2,10 @@ apiVersion: core.oam.dev/v1alpha2
kind: ScopeDefinition
metadata:
name: healthscopes.core.oam.dev
annotations:
definition.oam.dev/apiVersion: core.oam.dev/v1alpha2
definition.oam.dev/kind: HealthScope
namespace: default
spec:
workloadRefsPath: spec.workloadRefs
allowComponentOverlap: true

View File

@@ -1,25 +1,31 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: manualscalertraits.core.oam.dev
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ManualScalerTrait"
definition.oam.dev/apiVersion: core.oam.dev/v1alpha2
definition.oam.dev/kind: ManualScalerTrait
definition.oam.dev/description: "Scale replica for workload"
name: scaler
namespace: default
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- apps/v1.Deployment
- webservice
definitionRef:
name: manualscalertraits.core.oam.dev
workloadRefPath: spec.workloadRef
extension:
template: |
#Template: {
template: |-
output: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ManualScalerTrait"
spec: {
replicaCount: scale.replica
replicaCount: parameter.replica
}
}
scale: {
#scale: {
//+short=r
replica: *2 | int
}
parameter: #scale

View File

@@ -0,0 +1,53 @@
---
apiVersion: v1
kind: Namespace
metadata:
labels:
mornitoring: oam
name: monitoring
---
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: metric
namespace: default
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: MetricsTrait
definition.oam.dev/description: "Add metric monitoring for workload"
spec:
appliesToWorkloads:
- webservice
- backend
- task
- containerizedworkloads.core.oam.dev
- clonesetworkloads.apps.kruise.io
- deployments.apps
- statefulsets.apps
definitionRef:
name: metricstraits.standard.oam.dev
workloadRefPath: spec.workloadRef
extension:
template: |-
#metrics: {
// +usage=format of the metrics, default as prometheus
// +short=f
format: *"prometheus" | string
path: *"/metrics" | string
scheme: *"http" | string
enabled: *true | bool
port: *8080 | >=1024 & uint16
// +usage= the label selector for the pods, default is the workload labels
selector?: [string]: string
}
spec: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "MetricsTrait"
spec: {
scrapeService: parameter
}
}
parameter: #metrics
output: {}

View File

@@ -0,0 +1,48 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: podspecworkloads.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: "standard.oam.dev/v1alpha1"
definition.oam.dev/kind: "PodSpecWorkload"
spec:
definitionRef:
name: podspecworkloads.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
output: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "PodSpecWorkload"
metadata:
name: parameter.name
spec: {
replicas: 1
podSpec: {
containers: [{
image: parameter.image
name: parameter.name
ports: [{
containerPort: parameter.port
protocol: "TCP"
name: "default"
}]
}]
}
}
}
#webservice: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}
parameter: #webservice

View File

@@ -0,0 +1,35 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: route
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Route
definition.oam.dev/description: "Add a route for workload"
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- standard.oam.dev/v1alpha1.PodSpecWorkload
- deployments.apps
- webservice
workloadRefPath: spec.workloadRef
definitionRef:
name: routes.standard.oam.dev
extension:
template: |
output: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Route"
spec: {
host: parameter.domain
tls: {
issuerName: parameter.issuer
}
}
}
#route: {
domain: *"" | string
issuer: *"" | string
}
parameter: #route

View File

@@ -0,0 +1,49 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: task
annotations:
definition.oam.dev/apiVersion: "v1"
definition.oam.dev/kind: "Job"
definition.oam.dev/description: "One-time off task or job"
spec:
definitionRef:
name: jobs
extension:
defaultTraits:
- monitor
- logging
template: |
output: {
apiVersion: "v1"
kind: "Job"
metadata: name: parameter.name
spec: {
parallelism: parameter.count
completions: parameter.count
template:
spec:
containers: [{
image: parameter.image
name: parameter.name
ports: [{
containerPort: parameter.port
protocol: "TCP"
name: "default"
}]
}]
}
}
#task: {
// +usage=specify number of tasks to run in parallel
// +short=c
count: *1 | int
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}
parameter: #task

View File

@@ -0,0 +1,48 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: webservice
annotations:
definition.oam.dev/apiVersion: "standard.oam.dev/v1alpha1"
definition.oam.dev/kind: "PodSpecWorkload"
definition.oam.dev/description: "Long running service with ports exposed"
spec:
definitionRef:
name: podspecworkloads.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
output: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "PodSpecWorkload"
metadata:
name: parameter.name
spec: {
replicas: 1
podSpec: {
containers: [{
image: parameter.image
name: parameter.name
ports: [{
containerPort: parameter.port
protocol: "TCP"
name: "default"
}]
}]
}
}
}
#webservice: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}
parameter: #webservice

View File

@@ -18,7 +18,7 @@ metadata:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: manager-rolebinding
name: {{ include "kubevela.fullname" . }}:manager-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
@@ -33,7 +33,7 @@ subjects:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: leader-election-role
name: {{ include "kubevela.fullname" . }}:leader-election-role
rules:
- apiGroups:
- ""
@@ -66,11 +66,11 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: leader-election-rolebinding
name: {{ include "kubevela.fullname" . }}:leader-election-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: leader-election-role
name: {{ include "kubevela.fullname" . }}:leader-election-role
subjects:
- kind: ServiceAccount
name: {{ include "kubevela.serviceAccountName" . }}
@@ -80,6 +80,7 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "kubevela.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "kubevela.labels" . | nindent 4 }}
spec:
@@ -110,6 +111,7 @@ spec:
- "--use-webhook=true"
- "--webhook-port={{ .Values.webhookService.port }}"
- "--webhook-cert-dir={{ .Values.certificate.mountPath }}"
- "--health-addr=:{{ .Values.healthCheck.port }}"
{{ end }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ quote .Values.image.pullPolicy }}
@@ -120,6 +122,21 @@ spec:
- containerPort: {{ .Values.webhookService.port }}
name: webhook-server
protocol: TCP
- containerPort: {{ .Values.healthCheck.port }}
name: healthz
protocol: TCP
readinessProbe:
httpGet:
path: /readyz
port: healthz
initialDelaySeconds: 90
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: healthz
initialDelaySeconds: 90
periodSeconds: 5
volumeMounts:
- mountPath: {{ .Values.certificate.mountPath }}
name: tls-cert-vol
@@ -141,4 +158,4 @@ spec:
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}

View File

@@ -1,41 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: metricstraits.standard.oam.dev
spec:
appliesToWorkloads:
- containerizedworkloads.core.oam.dev
- clonesetworkloads.apps.kruise.io
- deployments.apps
- statefulsets.apps
definitionRef:
name: metricstraits.standard.oam.dev
workloadRefPath: spec.workloadRef
extension:
template: |
#Template: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "MetricsTrait"
metadata:
name: metricstraits.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
}
}
containerized: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -1,47 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: task
annotations:
definition.oam.dev/apiVersion: "v1"
definition.oam.dev/kind: "Job"
spec:
definitionRef:
name: jobs
extension:
defaultTraits:
- monitor
- logging
template: |
#Template: {
apiVersion: "v1"
kind: "Job"
metadata: name: task
spec: {
parallelism: taskSpec.count
completions: taskSpec.count
template:
spec:
containers: [{
image: taskSpec.image
name: taskSpec.name
ports: [{
containerPort: taskSpec.port
protocol: "TCP"
name: "default"
}]
}]
}
}
taskSpec: {
// +usage=specify number of tasks to run in parallel
// +short=c
count: *1 | int
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -0,0 +1,22 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: vela-config
namespace: {{ .Release.Namespace }}
data:
certificates.cert-manager.io: |
{
"repo": "jetstack",
"urL": "https://charts.jetstack.io",
"name": "cert-manager",
"namespace": "cert-manager",
"version": "1.0.3"
}
servicemonitors.monitoring.coreos.com: |
{
"repo": "prometheus-community",
"urL": "https://prometheus-community.github.io/helm-charts",
"name": "kube-prometheus-stack",
"namespace": "monitoring",
"version": "9.4.4"
}

View File

@@ -3,8 +3,8 @@
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
creationTimestamp: null
name: mutating-webhook-configuration
namespace: {{ .Release.Namespace }}
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ .Values.certificate.certificateName }}
webhooks:
@@ -31,7 +31,7 @@ webhooks:
service:
name: {{ template "kubevela.name" . }}-webhook
namespace: {{ .Release.Namespace }}
path: /mutate-standard-oam-dev-v1alpha1-containerized
path: /mutate-standard-oam-dev-v1alpha1-podspecworkload
failurePolicy: Fail
name: mcontainerized.kb.io
rules:
@@ -43,14 +43,14 @@ webhooks:
- CREATE
- UPDATE
resources:
- Containerized
- podspecworkloads
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
creationTimestamp: null
name: validating-webhook-configuration
namespace: {{ .Release.Namespace }}
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ .Values.certificate.certificateName }}
webhooks:
@@ -78,7 +78,7 @@ webhooks:
service:
name: {{ template "kubevela.name" . }}-webhook
namespace: {{ .Release.Namespace }}
path: /validate-standard-oam-dev-v1alpha1-containerized
path: /validate-standard-oam-dev-v1alpha1-podspecworkload
failurePolicy: Fail
name: vcontainerized.kb.io
rules:
@@ -89,15 +89,15 @@ webhooks:
operations:
- CREATE
- UPDATE
- DELETE
resources:
- Containerized
- podspecworkloads
---
apiVersion: v1
kind: Service
metadata:
name: {{ template "kubevela.name" . }}-webhook
namespace: {{ .Release.Namespace }}
labels:
{{- include "kubevela.labels" . | nindent 4 }}
spec:
@@ -113,7 +113,7 @@ spec:
---
# The following manifests contain a self-signed issuer CR and a certificate CR.
# More document can be found at https://docs.cert-manager.io
apiVersion: cert-manager.io/v1alpha2
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: {{ .Values.certificate.issuerName | quote }}
@@ -121,7 +121,7 @@ spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1alpha2
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ .Values.certificate.certificateName }}

View File

@@ -1,43 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: web-service
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ContainerizedWorkload"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
metadata:
name: containerized.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
}
}
containerized: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -6,8 +6,8 @@ replicaCount: 1
useWebhook: true
image:
repository: oamdev/vela-core
tag: 0.1
pullPolicy: IfNotPresent
tag: latest
pullPolicy: Always
imagePullSecrets: []
nameOverride: ""
@@ -63,6 +63,9 @@ webhookService:
type: ClusterIP
port: 9443
healthCheck:
port: 9440
nodeSelector: {}
tolerations: []

View File

@@ -1,10 +1,14 @@
package main
import (
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"time"
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/crossplane/crossplane-runtime/pkg/logging"
@@ -16,26 +20,45 @@ import (
injectorcontroller "github.com/oam-dev/trait-injector/controllers"
"github.com/oam-dev/trait-injector/pkg/injector"
"github.com/oam-dev/trait-injector/pkg/plugin"
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
velacoreoamdev "github.com/oam-dev/kubevela/api/core.oam.dev/v1alpha2"
velacore "github.com/oam-dev/kubevela/api/v1alpha1"
velacontroller "github.com/oam-dev/kubevela/pkg/controller"
"github.com/oam-dev/kubevela/pkg/controller/dependency"
velawebhook "github.com/oam-dev/kubevela/pkg/webhook"
)
var scheme = runtime.NewScheme()
const (
kubevelaName = "kubevela"
)
var (
setupLog = ctrl.Log.WithName(kubevelaName)
scheme = runtime.NewScheme()
waitSecretTimeout = 90 * time.Second
waitSecretInterval = 2 * time.Second
)
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = crdv1.AddToScheme(scheme)
_ = oamcore.AddToScheme(scheme)
_ = monitoring.AddToScheme(scheme)
_ = velacore.AddToScheme(scheme)
_ = velacoreoamdev.AddToScheme(scheme)
_ = injectorv1alpha1.AddToScheme(scheme)
_ = certmanager.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
@@ -47,6 +70,7 @@ func main() {
var webhookPort int
var useWebhook, useTraitInjector bool
var controllerArgs oamcontroller.Args
var healthAddr string
flag.BoolVar(&useWebhook, "use-webhook", false, "Enable Admission Webhook")
flag.BoolVar(&useTraitInjector, "use-trait-injector", false, "Enable TraitInjector")
@@ -60,6 +84,7 @@ func main() {
flag.BoolVar(&logCompress, "log-compress", true, "Enable compression on the rotated logs.")
flag.IntVar(&controllerArgs.RevisionLimit, "revision-limit", 50,
"RevisionLimit is the maximum number of revisions that will be maintained. The default value is 50.")
flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.")
flag.Parse()
// setup logging
@@ -79,24 +104,44 @@ func main() {
o.DestWritter = w
}))
setupLog := ctrl.Log.WithName("vela-runtime")
// install dependency charts first
k8sClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "unable to create a kubernetes client")
os.Exit(1)
}
if err = dependency.Install(k8sClient); err != nil {
setupLog.Error(err, "unable to install the dependency")
os.Exit(1)
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "vela-runtime",
Port: webhookPort,
CertDir: certDir,
Scheme: scheme,
MetricsBindAddress: metricsAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: kubevelaName,
Port: webhookPort,
CertDir: certDir,
HealthProbeBindAddress: healthAddr,
})
if err != nil {
setupLog.Error(err, "unable to create a controller manager")
os.Exit(1)
}
if err := registerHealthChecks(mgr); err != nil {
setupLog.Error(err, "unable to register ready/health checks")
os.Exit(1)
}
if useWebhook {
setupLog.Info("vela webhook enabled, will serving at :" + strconv.Itoa(webhookPort))
oamwebhook.Add(mgr)
velawebhook.Register(mgr)
if err := waitWebhookSecretVolume(certDir, waitSecretTimeout, waitSecretInterval); err != nil {
setupLog.Error(err, "unable to get webhook secret")
os.Exit(1)
}
}
if err = oamv1alpha2.Setup(mgr, controllerArgs, logging.NewLogrLogger(setupLog)); err != nil {
@@ -133,3 +178,50 @@ func main() {
os.Exit(1)
}
}
// registerHealthChecks is used to create readiness&liveness probes
func registerHealthChecks(mgr ctrl.Manager) error {
setupLog.Info("creating readiness/health check")
if err := mgr.AddReadyzCheck("ping", healthz.Ping); err != nil {
return err
}
if err := mgr.AddHealthzCheck("ping", healthz.Ping); err != nil {
return err
}
return nil
}
// waitWebhookSecretVolume waits for webhook secret ready to avoid mgr running crash
func waitWebhookSecretVolume(certDir string, timeout, interval time.Duration) error {
start := time.Now()
for {
time.Sleep(interval)
if time.Since(start) > timeout {
return fmt.Errorf("getting webhook secret timeout after %s", timeout.String())
}
setupLog.Info(fmt.Sprintf("waiting webhook secret, time consumed: %d/%d seconds ...",
int64(time.Since(start).Seconds()), int64(timeout.Seconds())))
if _, err := os.Stat(certDir); !os.IsNotExist(err) {
f, _ := os.Open(certDir)
defer f.Close()
// check if dir is empty
if _, err := f.Readdir(1); err == io.EOF {
continue
}
// check if secret files are empty
err := filepath.Walk(certDir, func(path string, info os.FileInfo, err error) error {
// even Cert dir is created, cert files are still empty for a while
if info.Size() == 0 {
return errors.New("secret is not ready")
}
return nil
})
if err == nil {
setupLog.Info(fmt.Sprintf("webhook secret is ready (time consumed: %d seconds)",
int64(time.Since(start).Seconds())))
return nil
}
}
}
}

66
cmd/core/main_test.go Normal file
View File

@@ -0,0 +1,66 @@
package main
import (
"io/ioutil"
"os"
"testing"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var (
testdir = "testdir"
testTimeout = 2 * time.Second
testInterval = 1 * time.Second
)
func TestGinkgo(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "test main")
}
var _ = Describe("test waitSecretVolume", func() {
BeforeEach(func() {
err := os.MkdirAll(testdir, 0755)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
os.RemoveAll(testdir)
})
When("dir not exist or empty", func() {
It("return timeout error", func() {
err := waitWebhookSecretVolume(testdir, testTimeout, testInterval)
Expect(err).To(HaveOccurred())
By("remove dir")
os.RemoveAll(testdir)
err = waitWebhookSecretVolume(testdir, testTimeout, testInterval)
Expect(err).To(HaveOccurred())
})
})
When("dir contains empty file", func() {
It("return timeout error", func() {
By("add empty file")
_, err := os.Create(testdir + "/emptyFile")
Expect(err).NotTo(HaveOccurred())
err = waitWebhookSecretVolume(testdir, testTimeout, testInterval)
Expect(err).To(HaveOccurred())
})
})
When("files in dir are not empty", func() {
It("return nil", func() {
By("add non-empty file")
_, err := os.Create(testdir + "/file")
Expect(err).NotTo(HaveOccurred())
err = ioutil.WriteFile(testdir+"/file", []byte("test"), os.ModeAppend)
Expect(err).NotTo(HaveOccurred())
err = waitWebhookSecretVolume(testdir, testTimeout, testInterval)
Expect(err).NotTo(HaveOccurred())
})
})
})

View File

@@ -0,0 +1,5 @@
package fake
// ChartSource is a base64-encoded, gzipped tarball of the default Helm chart(charts/vela-core).
// Its value is initialized at build time. This whole file will be rewrite at that time, please don't change this file.
var ChartSource string

View File

@@ -1,174 +1,19 @@
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"time"
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/cmd/vela/fake"
"github.com/oam-dev/kubevela/pkg/commands"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/utils/system"
"github.com/oam-dev/kubevela/version"
"github.com/crossplane/oam-kubernetes-runtime/apis/core"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
var (
scheme = k8sruntime.NewScheme()
)
// chartTGZSource is a base64-encoded, gzipped tarball of the default Helm chart.
// Its value is initialized at build time.
var chartTGZSource string
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = core.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
func main() {
rand.Seed(time.Now().UnixNano())
command := newCommand()
command := commands.NewCommand()
if err := command.Execute(); err != nil {
os.Exit(1)
}
}
func newCommand() *cobra.Command {
ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
cmds := &cobra.Command{
Use: "vela",
DisableFlagParsing: true,
Run: func(cmd *cobra.Command, args []string) {
allCommands := cmd.Commands()
cmd.Printf("✈️ A Micro App Platform for Kubernetes.\n\nUsage:\n vela [flags]\n vela [command]\n\nAvailable Commands:\n\n")
PrintHelpByTag(cmd, allCommands, types.TypeStart)
PrintHelpByTag(cmd, allCommands, types.TypeApp)
PrintHelpByTag(cmd, allCommands, types.TypeTraits)
PrintHelpByTag(cmd, allCommands, types.TypeOthers)
PrintHelpByTag(cmd, allCommands, types.TypeSystem)
cmd.Println("Flags:")
cmd.Println(" -h, --help help for vela")
cmd.Println()
cmd.Println(`Use "vela [command] --help" for more information about a command.`)
},
SilenceUsage: true,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
},
}
cmds.PersistentFlags().StringP("env", "e", "", "specify env name for application")
restConf, err := config.GetConfig()
if err != nil {
fmt.Println("get kubeconfig err", err)
os.Exit(1)
}
commandArgs := types.Args{
Config: restConf,
Schema: scheme,
}
if err := system.InitDirs(); err != nil {
fmt.Println("InitDir err", err)
os.Exit(1)
}
cmds.AddCommand(
// Getting Start
commands.NewInstallCommand(commandArgs, chartTGZSource, ioStream),
commands.NewEnvCommand(commandArgs, ioStream),
// Getting Start
NewVersionCommand(),
// Apps
commands.NewAppsCommand(commandArgs, ioStream),
// Workloads
commands.AddCompCommands(commandArgs, ioStream),
// Capability Systems
commands.CapabilityCommandGroup(commandArgs, ioStream),
// System
commands.SystemCommandGroup(commandArgs, ioStream),
commands.NewCompletionCommand(),
commands.NewTraitsCommand(ioStream),
commands.NewWorkloadsCommand(ioStream),
commands.NewDashboardCommand(commandArgs, ioStream, fake.FrontendSource),
commands.NewLogsCommand(commandArgs, ioStream),
)
// Traits
if err = commands.AddTraitCommands(cmds, commandArgs, ioStream); err != nil {
fmt.Println("Add trait commands from traitDefinition err", err)
os.Exit(1)
}
// this is for mute klog
fset := flag.NewFlagSet("logs", flag.ContinueOnError)
klog.InitFlags(fset)
_ = fset.Set("v", "-1")
return cmds
}
func PrintHelpByTag(cmd *cobra.Command, all []*cobra.Command, tag string) {
cmd.Printf(" %s:\n\n", tag)
table := uitable.New()
for _, c := range all {
if val, ok := c.Annotations[types.TagCommandType]; ok && val == tag {
table.AddRow(" "+c.Use, c.Long)
for _, subcmd := range c.Commands() {
table.AddRow(" "+subcmd.Use, " "+subcmd.Long)
}
}
}
cmd.Println(table.String())
if tag == types.TypeTraits {
if len(table.Rows) > 0 {
cmd.Println()
}
cmd.Println(" Want more? < install more capabilities by `vela cap` >")
}
cmd.Println()
}
func NewVersionCommand() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Prints out build version information",
Long: "Prints out build version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf(`Version: %v
GitRevision: %v
GolangVersion: %v
`,
version.VelaVersion,
version.GitRevision,
runtime.Version())
},
Annotations: map[string]string{
types.TagCommandType: types.TypeStart,
},
}
}

View File

@@ -1,42 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: ingresses.networking.k8s.io
annotations:
"definition.oam.dev/apiVersion": "networking.k8s.io/v1beta1"
"definition.oam.dev/kind": "Ingress"
spec:
revisionEnabled: true
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- deployments.apps
definitionRef:
name: ingresses.networking.k8s.io
extension:
install:
helm:
repo: stable
name: nginx-ingress
url: https://kubernetes-charts.storage.googleapis.com/
version: 1.41.2
template: |
#Template: {
apiVersion: "networking.k8s.io/v1beta1"
kind: "Ingress"
spec: {
rules: [{
host: route.domain
http: paths: [{
backend: {
serviceName: route.service
servicePort: route.port
}}]
}]
}
}
route: {
domain: string
port: *80 | int
service: string
}

View File

@@ -1,30 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: simplerollouttraits.extend.oam.dev
annotations:
"definition.oam.dev/apiVersion": "extend.oam.dev/v1alpha2"
"definition.oam.dev/kind": "SimpleRolloutTrait"
spec:
revisionEnabled: true
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- deployments.apps
definitionRef:
name: simplerollouttraits.extend.oam.dev
extension:
template: |
#Template: {
apiVersion: "extend.oam.dev/v1alpha2"
kind: "SimpleRolloutTrait"
spec: {
replica: rollout.replica
maxUnavailable: rollout.maxUnavailable
batch: rollout.batch
}
}
rollout: {
replica: *3 | int
maxUnavailable: *1 | int
batch: *2 | int
}

View File

@@ -1,43 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: containerizedworkloads.core.oam.dev
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ContainerizedWorkload"
spec:
definitionRef:
name: containerizedworkloads.core.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
metadata: name: containerized.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
}
}
containerized: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -1,52 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: deployments.apps
annotations:
definition.oam.dev/apiVersion: "apps/v1"
definition.oam.dev/kind: "Deployment"
spec:
definitionRef:
name: deployments.apps
extension:
template: |
#Template: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: name: deployment.name
spec: {
selector:
matchLabels:
app: deployment.name
template: {
metadata:
labels:
app: deployment.name
spec: containers: [{
image: deployment.image
name: deployment.name
env: deployment.env
ports: [{
containerPort: deployment.port
protocol: "TCP"
name: "default"
}]
}]
}
}
}
deployment: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *8080 | int
env: [...{
name: string
value: string
}]
}

View File

@@ -29,9 +29,9 @@ webhooks:
service:
name: webhook-service
namespace: system
path: /mutate-standard-oam-dev-v1alpha1-containerized
path: /mutate-standard-oam-dev-v1alpha1-podspecworkload
failurePolicy: Fail
name: mcontainerized.kb.io
name: mpodspecworkload.kb.io
rules:
- apiGroups:
- standard.oam.dev
@@ -41,7 +41,7 @@ webhooks:
- CREATE
- UPDATE
resources:
- Containerized
- PodSpecWorkload
---
apiVersion: admissionregistration.k8s.io/v1beta1
@@ -74,9 +74,9 @@ webhooks:
service:
name: webhook-service
namespace: system
path: /validate-standard-oam-dev-v1alpha1-containerized
path: /validate-standard-oam-dev-v1alpha1-podspecworkload
failurePolicy: Fail
name: vcontainerized.kb.io
name: vpodspecworkload.kb.io
rules:
- apiGroups:
- standard.oam.dev
@@ -87,4 +87,4 @@ webhooks:
- UPDATE
- DELETE
resources:
- Containerized
- PodSpecWorkload

View File

@@ -12,9 +12,7 @@ export default defineConfig({
hmr: true,
},
locale: {
// default zh-CN
default: 'en-US',
// default true, when it is true, will use `navigator.language` overwrite default
antd: false,
baseNavigator: false,
},
@@ -36,22 +34,14 @@ export default defineConfig({
routes: [
{
path: '/',
// redirect: `/${envname}/ApplicationList`,
redirect: `/ApplicationList`,
},
{
name: 'ApplicationList',
icon: 'table',
// path: `/${envname}/ApplicationList`,
path: `/ApplicationList`,
component: './ApplicationList',
},
{
name: 'ApplicationList.ApplicationListDetail',
hideInMenu: true,
path: '/ApplicationList/ApplicationListDetail',
component: './ApplicationList/ApplicationListDetail',
},
{
name: 'ApplicationList.WorkloadDetail',
icon: 'smile',
@@ -67,35 +57,22 @@ export default defineConfig({
hideInMenu: true,
},
{
name: 'ApplicationList.CreateApplication',
name: 'ApplicationList.Components',
hideInMenu: true,
path: '/ApplicationList/CreateApplication',
component: './ApplicationList/CreateApplication',
path: '/ApplicationList/:appName/Components',
component: './ApplicationList/Components',
},
{
name: 'ApplicationList.Components.createComponent',
hideInMenu: true,
path: '/ApplicationList/:appName/createComponent',
component: './ApplicationList/CreateComponent',
},
{
name: 'Workload',
icon: 'table',
path: '/Workload',
routes: [
// {
// name: 'Deployment',
// icon: 'table',
// path: '/Workload/Deployment',
// component: './Workload/Deployment',
// },
// {
// name: 'Containerized',
// icon: 'smile',
// path: '/Workload/Containerized',
// component: './Workload/Containerized',
// },
// {
// name: 'Detail',
// icon: 'smile',
// path: '/Workload/Detail',
// component: './Workload/Detail',
// hideInMenu: true,
// },
{
name: 'WorkloadItem',
icon: 'smile',
@@ -109,25 +86,6 @@ export default defineConfig({
name: 'Traits',
icon: 'table',
routes: [
// {
// name: 'Scale',
// icon: 'table',
// path: '/Traits/Scale',
// component: './Traits/Scale',
// },
// {
// name: 'Rollout',
// icon: 'smile',
// path: '/Traits/Rollout',
// component: './Traits/Rollout',
// },
// {
// name: 'Detail',
// icon: 'smile',
// path: '/Traits/Detail',
// component: './Traits/Detail',
// hideInMenu: true,
// },
{
name: 'TraitItem',
icon: 'smile',
@@ -136,12 +94,6 @@ export default defineConfig({
},
],
},
// {
// name: 'Release',
// icon: 'table',
// path: '/Release',
// component: './Release',
// },
{
name: 'Capability',
icon: 'table',
@@ -183,8 +135,13 @@ export default defineConfig({
],
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
theme: {
// ...darkTheme,
// 主题配置
'primary-color': defaultSettings.primaryColor,
'link-color': defaultSettings.linkColor,
'link-hover-color': defaultSettings.linkHoverColor,
'disabled-bg': defaultSettings.disabledBg,
'disabled-color': defaultSettings.disabledColor,
'btn-disable-color': defaultSettings.btnDisableColor,
},
// @ts-ignore
title: false,

View File

@@ -1,14 +1,19 @@
const proSettings = {
navTheme: 'dark',
// 拂晓蓝
primaryColor: '#1890ff',
// 主题颜色配置
primaryColor: '#1B58F4', // 全局主色
linkColor: '#1B58F4', // 链接色
linkHoverColor: '#1B58F4',
disabledBg: '#EBEBEB', // 失效背景色,
disabledColor: '#BEBEBE', // 失效文本色,
btnDisableColor: '#A4A4A4', // 禁用btn文字颜色
layout: 'side',
contentWidth: 'Fluid',
fixedHeader: false,
fixSiderbar: true,
colorWeak: false,
menu: {
locale: true,
locale: false,
},
title: 'Micro App Engine',
pwa: false,

View File

@@ -8,11 +8,8 @@
export default {
dev: {
'/api': {
target: 'http://123.56.222.218:8081/',
target: 'http://127.0.0.1:38081/',
changeOrigin: true,
// pathRewrite: {
// "^/api": "",
// },
},
},
test: {

View File

@@ -35,34 +35,44 @@ export default class CreateTraitItem extends React.PureComponent {
return this.formRefStep2.current.getFieldsValue();
};
resetFields = () => {
return this.formRefStep2.current.resetFields();
};
validateFields = () => {
return this.formRefStep2.current.validateFields();
};
setDefaultValue = (traitType) => {
this.formRefStep2.current.setFieldsValue({ name: traitType });
this.traitSelectChange(traitType);
};
traitSelectChange = async (value, isType = 1) => {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else if (isType) {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
if (value) {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else if (isType) {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
}
}
}
};
@@ -77,8 +87,12 @@ export default class CreateTraitItem extends React.PureComponent {
name="control-ref"
className="traitItem"
>
<Form.Item name="name" label="Trait">
<Select placeholder="Select a Trait" allowClear onChange={this.traitSelectChange}>
<Form.Item
name="name"
label="Trait"
rules={[{ required: true, message: 'Please Select a Trait!' }]}
>
<Select placeholder="Select a Trait" onChange={this.traitSelectChange}>
{availableTraitList.map((item) => {
return (
<Option value={item.name} key={item.name}>
@@ -92,8 +106,34 @@ export default class CreateTraitItem extends React.PureComponent {
<div className="relativeBox">
{this.state.parameters ? (
this.state.parameters.map((item) => {
return (
<Form.Item name={item.name} label={item.name} key={item.name}>
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);

View File

@@ -23,9 +23,8 @@ export default class WorkSpaceDropDown extends React.Component {
return env.current === '*';
});
this.setState({
envs: envs,
workSpaceName: envName,
namespace: namespace,
namespace,
});
this.props.dispatch({
type: 'globalData/currentEnv',

View File

@@ -1,23 +1,36 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Row, Col, Modal, Select, message } from 'antd';
import { Button, Row, Col, Modal, Select, message, Breadcrumb, Form, Input } from 'antd';
import './index.less';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
const { Option } = Select;
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
@connect(({ loading, applist, globalData }) => ({
loadingAll: loading.models.applist,
currentEnv: globalData.currentEnv,
returnObj: applist.returnObj,
}))
class Trait extends React.Component {
formRefStep2 = React.createRef();
constructor(props) {
super(props);
this.state = {
visible: false,
selectValue: null,
compList: [],
};
}
@@ -25,6 +38,19 @@ class Trait extends React.Component {
this.getInitialData();
}
shouldComponentUpdate(nextProps) {
if (nextProps.currentEnv === this.props.currentEnv) {
return true;
}
this.props.dispatch({
type: 'applist/getList',
payload: {
url: `/api/envs/${nextProps.currentEnv}/apps/`,
},
});
return true;
}
getInitialData = async () => {
if (this.props.currentEnv) {
await this.props.dispatch({
@@ -37,28 +63,64 @@ class Trait extends React.Component {
};
showModal = () => {
this.setState({
visible: true,
});
this.setState(
{
visible: true,
},
() => {
if (this.formRefStep2.current) {
this.formRefStep2.current.resetFields();
}
},
);
};
handleOk = () => {
const { selectValue } = this.state;
if (selectValue) {
this.setState({
visible: false,
handleOk = async () => {
await this.formRefStep2.current.validateFields();
const { title } = this.props.propsObj;
if (title) {
const submitObj = {
name: title,
flags: [],
};
const submitData = this.formRefStep2.current.getFieldValue();
Object.keys(submitData).forEach((currentKey) => {
if (
currentKey !== 'name' &&
currentKey !== 'appName' &&
currentKey !== 'compName' &&
submitData[currentKey]
) {
submitObj.flags.push({
name: currentKey,
value: submitData[currentKey].toString(),
});
}
});
const { history } = this.props.propsObj;
history.push({
pathname: '/ApplicationList/ApplicationListDetail',
state: {
appName: selectValue,
envName: this.props.currentEnv,
traitType: this.props.propsObj.title,
},
});
} else {
message.warn('please select a application');
const { currentEnv: envName } = this.props;
const { appName, compName } = submitData;
if (envName && appName && compName) {
const res = await this.props.dispatch({
type: 'trait/attachOneTraits',
payload: {
envName,
appName,
compName,
params: submitObj,
},
});
if (res) {
this.setState({
visible: false,
});
message.success(res);
const { history } = this.props.propsObj;
history.push({
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
});
}
}
}
};
@@ -68,107 +130,231 @@ class Trait extends React.Component {
});
};
onChange = (value) => {
onChange = async (value) => {
this.setState({
selectValue: value,
compList: [],
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
envName: this.props.currentEnv,
appName: value,
},
});
if (res) {
const compData = _.get(res, 'components', []);
const compList = [];
compData.forEach((item) => {
compList.push({
compName: item.name,
});
});
this.setState({
compList,
});
}
};
onSearch = () => {};
render() {
const { btnValue, title, settings, btnIsShow, crdInfo, appliesTo } = this.props.propsObj;
const appList = _.get(this.props, 'returnObj', []);
const { btnValue, title, settings = [], btnIsShow, crdInfo, appliesTo } = this.props.propsObj;
const initialObj = {};
if (settings.length) {
settings.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
}
let appList = _.get(this.props, 'returnObj', []);
if (!appList) {
appList = [];
}
const { compList = [] } = this.state;
return (
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<Row>
<Col span="22">
<p className="title">Applies To</p>
<p>{Array.isArray(appliesTo) ? appliesTo.join(', ') : appliesTo}</p>
</Col>
</Row>
<p className="title">Configurable Properties:</p>
{settings.map((item, index) => {
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
<Button
type="primary"
className="create-button"
onClick={this.showModal}
style={{ display: btnIsShow ? 'block' : 'none' }}
>
{btnValue}
</Button>
<Modal
title="Select a Application"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Next
</Button>,
]}
>
<Select
showSearch
allowClear
value={this.state.selectValue}
style={{ width: '100%' }}
placeholder="Select a Application"
optionFilterProp="children"
onChange={this.onChange}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Traits</Breadcrumb.Item>
<Breadcrumb.Item>{title}</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<Row>
<Col span="22">
<p className="title">Applies To:</p>
<p>{Array.isArray(appliesTo) ? appliesTo.join(', ') : appliesTo}</p>
</Col>
</Row>
<p className="title">Configurable Properties:</p>
{settings.map((item, index) => {
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
<Button
type="primary"
className="create-button"
onClick={this.showModal}
style={{ display: btnIsShow ? 'block' : 'none' }}
>
{appList.length ? (
appList.map((item) => {
return (
<Option key={item.name} value={item.name}>
{item.name}
</Option>
);
})
) : (
<Fragment />
)}
</Select>
</Modal>
</Col>
</Row>
</PageContainer>
{btnValue}
</Button>
<Modal
title="Attach Trait"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Submit
</Button>,
]}
>
<Form
labelAlign="left"
{...layout}
ref={this.formRefStep2}
name="control-ref"
className="traitItem"
initialValues={initialObj}
>
<Form.Item
label="App"
name="appName"
rules={[{ required: true, message: 'Please Select a Application!' }]}
>
<Select
showSearch
value={this.state.selectValue}
style={{ width: '100%' }}
placeholder="Select a Application"
optionFilterProp="children"
onChange={this.onChange}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{appList.length ? (
appList.map((item, index) => {
return (
<Option key={index.toString()} value={item.name}>
{item.name}
</Option>
);
})
) : (
<Fragment />
)}
</Select>
</Form.Item>
<Form.Item
label="Component"
name="compName"
rules={[{ required: true, message: 'Please Select a Component!' }]}
>
<Select
allowClear
// value={this.state.selectValue}
style={{ width: '100%' }}
placeholder="Select a Component"
>
{compList.length ? (
compList.map((item) => {
return (
<Option key={item.compName} value={item.compName}>
{item.compName}
</Option>
);
})
) : (
<Fragment />
)}
</Select>
</Form.Item>
<div className="relativeBox">
<Form.Item label="Properties" />
{settings ? (
settings.map((item) => {
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{
pattern: /^[0-9]*$/,
message: `${item.name} only use digits(0-9).`,
},
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);
})
) : (
<></>
)}
</div>
</Form>
</Modal>
</Col>
</Row>
</PageContainer>
</div>
);
}
}

View File

@@ -1,69 +1,226 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Row, Col } from 'antd';
import { Button, Row, Col, Modal, Select, Breadcrumb, Form } from 'antd';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
import './index.less';
export default class Workload extends React.PureComponent {
const { Option } = Select;
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
@connect(({ loading, applist, globalData }) => ({
loadingAll: loading.models.applist,
currentEnv: globalData.currentEnv,
returnObj: applist.returnObj,
}))
export default class Workload extends React.Component {
formRefStep2 = React.createRef();
constructor(props) {
super(props);
this.state = {
visible: false,
};
}
componentDidMount() {
this.getInitialData();
}
shouldComponentUpdate(nextProps) {
if (nextProps.currentEnv === this.props.currentEnv) {
return true;
}
this.props.dispatch({
type: 'applist/getList',
payload: {
url: `/api/envs/${nextProps.currentEnv}/apps/`,
},
});
return true;
}
getInitialData = async () => {
if (this.props.currentEnv) {
await this.props.dispatch({
type: 'applist/getList',
payload: {
url: `/api/envs/${this.props.currentEnv}/apps/`,
},
});
}
};
showModal = () => {
this.setState(
{
visible: true,
},
() => {
if (this.formRefStep2.current) {
this.formRefStep2.current.resetFields();
}
},
);
};
handleOk = async () => {
const submitData = await this.formRefStep2.current.validateFields();
const { history } = this.props.propsObj;
history.push({
pathname: `/ApplicationList/${submitData.appName}/createComponent`,
state: {
...submitData,
isCreate: false,
envName: this.props.currentEnv,
WorkloadType: this.props.propsObj.title,
},
});
};
handleCancel = () => {
this.setState({
visible: false,
});
};
onChange = () => {};
onSearch = () => {};
render() {
const {
btnValue,
pathname,
title,
crdInfo,
state,
settings,
hrefAddress,
btnIsShow,
} = this.props.propsObj;
const { btnValue, title, crdInfo, settings, btnIsShow } = this.props.propsObj;
let appList = _.get(this.props, 'returnObj', []);
if (!appList) {
appList = [];
}
return (
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<a href={hrefAddress}>?</a>
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<p className="title">Configurable Settings:</p>
{settings.map((item, index) => {
if (item.name === 'name') {
return <Fragment key={index.toString()} />;
}
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
{
// eslint-disable-next-line consistent-return
}
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
<Link to={{ pathname, state }} style={{ display: btnIsShow ? 'block' : 'none' }}>
<Button type="primary" className="create-button">
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Workloads</Breadcrumb.Item>
<Breadcrumb.Item>{title}</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<p className="title">Configurable Settings:</p>
{settings.map((item, index) => {
if (item.name === 'name') {
return <Fragment key={index.toString()} />;
}
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
{
// eslint-disable-next-line consistent-return
}
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
{/* <Link to={{ pathname, state }} style={{ display: btnIsShow ? 'block' : 'none' }}>
<Button type="primary" className="create-button">
{btnValue}
</Button>
</Link> */}
<Button
type="primary"
className="create-button"
onClick={() => this.showModal()}
style={{ display: btnIsShow ? 'block' : 'none' }}
>
{btnValue}
</Button>
</Link>
</Col>
</Row>
</PageContainer>
</Col>
</Row>
<Modal
title="Add Component"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Next
</Button>,
]}
>
<Form
labelAlign="left"
{...layout}
ref={this.formRefStep2}
name="control-ref"
className="traitItem"
>
<Form.Item
label="App"
name="appName"
rules={[{ required: true, message: 'Please Select a Application!' }]}
>
<Select
showSearch
style={{ width: '100%' }}
placeholder="Select a Application"
optionFilterProp="children"
onChange={this.onChange}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{appList.length ? (
appList.map((item, index) => {
return (
<Option key={index.toString()} value={item.name}>
{item.name}
</Option>
);
})
) : (
<Fragment />
)}
</Select>
</Form.Item>
</Form>
</Modal>
</PageContainer>
</div>
);
}
}

View File

@@ -29,13 +29,6 @@ beforeEach(async () => {
describe('Ant Design Pro E2E test', () => {
const testPage = (path) => async () => {
await page.goto(`${BASE_URL}${path}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
};
const routers = formatter(RouterConfig);
@@ -46,12 +39,5 @@ describe('Ant Design Pro E2E test', () => {
it('topmenu should have footer', async () => {
const params = '?navTheme=light&layout=topmenu';
await page.goto(`${BASE_URL}${params}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
});
});

View File

@@ -69,3 +69,35 @@ ol {
.ant-page-header-heading {
display: none !important;
}
.ant-pro-page-container-warp {
display: none;
}
.ant-pro-basicLayout-content {
margin: 0 !important;
}
.ant-pro-basicLayout-content .ant-pro-page-container {
margin: 0 !important;
}
.breadCrumb {
padding: 12px 24px;
background: #fff;
}
.ant-breadcrumb a:hover {
color: #1b58f4 !important;
}
// 对齐
.ant-form-item-label > label::before {
display: inline-block;
width: 7.09px;
height: 14px;
margin-right: 4px;
color: rgb(255, 77, 79);
font-size: 14px;
font-family: SimSun, sans-serif;
line-height: 1;
content: '';
}
.ant-spin-nested-loading {
height: calc(100% - 46px) !important;
}

View File

@@ -4,7 +4,7 @@
* https://github.com/ant-design/ant-design-pro-layout
*/
import ProLayout from '@ant-design/pro-layout';
import React, { useEffect } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { Link, useIntl, connect, history } from 'umi';
import RightContent from '@/components/GlobalHeader/RightContent';
import {
@@ -16,15 +16,6 @@ import {
} from '@ant-design/icons';
import _ from 'lodash';
// const menuDataRender = (menuList) => {
// return menuList.map((item) => {
// const localItem = {
// ...item,
// children: item.children ? menuDataRender(item.children) : undefined,
// };
// return localItem;
// });
// };
const AddIcon = (menuData) => {
return menuData.map((item) => {
const name = _.get(item, 'name', '');
@@ -52,18 +43,43 @@ const AddIcon = (menuData) => {
const BasicLayout = (props) => {
const { settings, dispatch, menus } = props;
const [currentSelectKeys, setCurrentSelectedKeys] = useState('');
const timerRef = useRef();
const getCurrentSelectKeys = () => {
const pathnameCur = props.history.location.pathname;
if (pathnameCur) {
if (pathnameCur.includes('Application')) {
setCurrentSelectedKeys(['applist']);
} else if (pathnameCur.includes('Capability')) {
setCurrentSelectedKeys(['Capability']);
} else if (pathnameCur.includes('System/Env')) {
setCurrentSelectedKeys(['Env']);
} else if (pathnameCur.includes('Workload')) {
const arr = pathnameCur.split('/');
const key = arr[arr.length - 1];
setCurrentSelectedKeys([key]);
} else if (pathnameCur.includes('Traits')) {
const arr = pathnameCur.split('/');
const key = arr[arr.length - 1];
setCurrentSelectedKeys([key]);
}
}
};
useEffect(() => {
if (dispatch) {
// dispatch({
// type: 'user/fetchCurrent',
// });
// dispatch({
// type: 'settings/getSetting',
// });
dispatch({
type: 'menus/getMenuData',
});
}
timerRef.current = props.history.listen((route) => {
getCurrentSelectKeys(route.pathname);
});
return () => {
if (timerRef.current) {
timerRef.current = null;
}
};
// setCurrentSelectedKeys('applist')
}, []);
const { formatMessage } = useIntl();
@@ -75,9 +91,19 @@ const BasicLayout = (props) => {
if (menuItemProps.isUrl || !menuItemProps.path) {
return defaultDom;
}
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
// return <Link to={menuItemProps.path}>{defaultDom}</Link>;
return (
<div
onClick={() => {
setCurrentSelectedKeys([menuItemProps.key]);
history.push(menuItemProps.path);
}}
>
{defaultDom}
</div>
);
}}
selectedKeys={currentSelectKeys}
breadcrumbRender={(routers = []) => [
{
path: '/',

View File

@@ -2,17 +2,15 @@ import { getapplist, createApp, getAppDetail, deleteApp } from '@/services/appli
const TestModel = {
namespace: 'applist',
state: {
// initailState: '8880'
},
state: {},
effects: {
*getList({ payload }, { call, put }) {
const res = yield call(getapplist, payload);
// getlist是引入services层那个js文件的getlist方法payload是后台要求传递的参数res就是后台返过来的数据
yield put({
type: 'addList', // 这就是reducer的addNum方法put用来触发reducer中的方法payload是传过去的参数。同时也能触发同等级effects中的方法
type: 'addList',
payload: {
returnObj: res, // 把后台返回的数据赋值给num,假如哪个reducer中的方法是由这里effects去触发的哪个num名必须是这里的名字num如果reducer中的方法不是这触发那名字可以随意取
returnObj: res,
},
});
},

View File

@@ -10,9 +10,7 @@ import {
const TestModel = {
namespace: 'capability',
state: {
// initailState: '8880'
},
state: {},
effects: {
*getCapabilityCenterlist({ payload }, { call }) {
const res = yield call(getCapabilityCenterlist, payload);

View File

@@ -0,0 +1,22 @@
import { getComponentList, getComponentDetail, deleteComponent } from '@/services/components.js';
const TestModel = {
namespace: 'components',
state: {},
effects: {
*getComponentList({ payload }, { call }) {
const res = yield call(getComponentList, payload);
return res;
},
*getComponentDetail({ payload }, { call }) {
const res = yield call(getComponentDetail, payload);
return res;
},
*deleteComponent({ payload }, { call }) {
const res = yield call(deleteComponent, payload);
return res;
},
},
reducers: {},
};
export default TestModel;

View File

@@ -6,7 +6,7 @@ const globalModel = {
effects: {
*currentEnv({ payload }, { put }) {
yield put({
type: 'setCurrentEnv', // 这就是reducer的addNum方法put用来触发reducer中的方法payload是传过去的参数。同时也能触发同等级effects中的方法
type: 'setCurrentEnv',
payload,
});
},

View File

@@ -1,36 +1,31 @@
import { capabilityList } from '@/services/capability.js';
import { getTraits } from '@/services/trait.js';
import { getWorkload } from '@/services/workload.js';
function getMenuList(response) {
function getMenuList(workload, trait) {
let workloadList = [];
let traitList = [];
// eslint-disable-next-line no-param-reassign
response = response.filter((item) => {
return item.status === 'installed';
});
response.forEach((item) => {
if (item.type === 'workload') {
workloadList.push(item.name);
} else if (item.type === 'trait') {
traitList.push(item.name);
}
});
// 在此之前要对workloadList和traitList进行一次去重操作
workloadList = workloadList.map((item) => {
// eslint-disable-next-line no-param-reassign
item = item.charAt(0).toUpperCase() + item.slice(1);
return {
name: item,
path: `/Workload/${item}`,
};
});
traitList = traitList.map((item) => {
// eslint-disable-next-line no-param-reassign
item = item.charAt(0).toUpperCase() + item.slice(1);
return {
name: item,
path: `/Traits/${item}`,
};
});
if (workload) {
workloadList = workload.map((item) => {
let name1 = item.name;
name1 = name1.charAt(0).toUpperCase() + name1.slice(1);
return {
name: name1,
path: `/Workload/${name1}`,
key: name1,
};
});
}
if (trait) {
traitList = trait.map((item) => {
let name1 = item.name;
name1 = name1.charAt(0).toUpperCase() + name1.slice(1);
return {
name: name1,
path: `/Traits/${name1}`,
key: name1,
};
});
}
// 只是动态生成侧边栏(name,path,icon)路由还是config.js里面配置的路由
const menuList = [
{
@@ -41,6 +36,7 @@ function getMenuList(response) {
name: 'ApplicationList',
icon: 'Table',
path: `/ApplicationList`,
key: 'applist',
},
{
name: 'ApplicationList.ApplicationListDetail',
@@ -76,13 +72,10 @@ function getMenuList(response) {
},
],
},
// {
// name: 'Release',
// path: '/Release',
// },
{
name: 'Capability',
path: '/Capability',
key: 'Capability',
},
{
path: '/System',
@@ -91,6 +84,7 @@ function getMenuList(response) {
{
name: 'Env',
path: '/System/Env',
key: 'Env',
},
],
},
@@ -110,8 +104,9 @@ const TestModel = {
},
effects: {
*getMenuData({ payload }, { call, put }) {
let response = yield call(capabilityList, payload);
response = getMenuList(response);
const workloadList = yield call(getWorkload, payload);
const traitList = yield call(getTraits, payload);
const response = getMenuList(workloadList, traitList);
yield put({
type: 'saveMenuData',
payload: response,

View File

@@ -2,9 +2,7 @@ import { getTraitByName, getTraits, attachOneTraits, deleteOneTrait } from '@/se
const TestModel = {
namespace: 'trait',
state: {
// initailState: '8880'
},
state: {},
effects: {
*getTraitByName({ payload }, { call }) {
const res = yield call(getTraitByName, payload);

View File

@@ -2,9 +2,7 @@ import { createWorkload, getWorkload, getWorkloadByName } from '@/services/workl
const TestModel = {
namespace: 'workload',
state: {
// initailState: '8880'
},
state: {},
effects: {
*createWorkload({ payload }, { call }) {
const res = yield call(createWorkload, payload);

View File

@@ -43,8 +43,6 @@ const Topology = () => {
],
};
// const width = document.getElementById('container').scrollWidth;
// const height = document.getElementById('container').scrollHeight || 500;
const width = 1000;
const height = 400;

View File

@@ -1,11 +1,9 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Button, Row, Col, Tabs, Popconfirm, message, Tooltip, Modal, Spin } from 'antd';
import { connect } from 'dva';
import _ from 'lodash';
import CreateTraitItem from '../../../components/AttachOneTrait/index.jsx';
import Topology from './Topology.jsx';
const { TabPane } = Tabs;
@@ -17,112 +15,160 @@ class TableList extends React.Component {
constructor(props) {
super(props);
this.state = {
appDetailData: {},
compDetailData: {},
visible: false,
traitList: [],
availableTraitList: [],
envName: '',
appName: '',
compName: '',
compList: [],
};
}
componentDidMount() {
this.getInitialData(1);
this.getInitialData();
}
getInitialData = async (times) => {
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
const traitType = _.get(this.props, 'location.state.traitType', '');
if (appName && envName) {
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.compName !== nextProps.compName) {
this.getInitialData(nextProps.compName);
}
}
getInitialData = async (nextCompName) => {
const appName = _.get(this.props, 'appName', '');
const envName = _.get(this.props, 'envName', '');
const compList = _.get(this.props, 'compList', []);
let compName = _.get(this.props, 'compName', '');
compName = nextCompName || compName;
this.setState({
compList,
});
if (appName && envName && compName) {
this.setState({
envName,
appName,
compName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
type: 'components/getComponentDetail',
payload: {
envName,
appName,
compName,
},
});
if (res) {
this.setState({
appDetailData: res,
compDetailData: res,
});
}
const traits = await this.props.dispatch({
type: 'trait/getTraits',
});
this.setState({
traitList: traits,
});
const workloadType = _.get(res, 'Workload.workload.kind', '');
if (workloadType && workloadType === 'ContainerizedWorkload') {
this.getAcceptTrait('containerized');
} else if (workloadType && workloadType === 'Deployment') {
this.getAcceptTrait('deployment');
}
// traitTypetraittrait
if (traitType && times === 1) {
// this.createTrait(traitType)
await this.setState({
visible: true,
const traits = await this.props.dispatch({
type: 'trait/getTraits',
});
this.child.setDefaultValue(traitType);
if (traits) {
const checkedTrait = [];
if (res.traits) {
res.traits.forEach((item) => {
const traitNameObj = _.get(item, 'trait.metadata.annotations', '');
const traitName =
traitNameObj['vela.oam.dev/traitDef'] || traitNameObj['trait.oam.dev/name'];
checkedTrait.push(traitName);
});
}
const newTraits = traits.filter((item) => {
if (checkedTrait.includes(item.name)) {
return false;
}
return true;
});
this.setState({
traitList: newTraits,
});
}
const workloadType = _.get(res, 'workload.kind', '');
if (workloadType) {
this.getAcceptTrait(workloadType.toLowerCase());
}
// if (workloadType && workloadType === '') {
// this.getAcceptTrait('containerized');
// } else if (workloadType && workloadType === 'Deployment') {
// this.getAcceptTrait('deployment');
// }
}
}
};
getAcceptTrait = (workloadType) => {
const res = this.state.traitList.filter((item) => {
if (item.appliesTo.indexOf(workloadType) !== -1) {
return true;
}
return false;
});
// getAcceptTrait = (workloadType) => {
// const res = this.state.traitList.filter((item) => {
// if (item.appliesTo) {
// if(item.appliesTo==='*'){
// return true;
// }
// if (item.appliesTo.indexOf(workloadType) !== -1) {
// return true;
// }
// return false;
// }
// return false;
// });
// this.setState(() => ({
// availableTraitList: res,
// }));
// };
getAcceptTrait = () => {
const res = this.state.traitList;
this.setState(() => ({
availableTraitList: res,
}));
};
deleteApp = async (e) => {
deleteComp = async (e) => {
e.stopPropagation();
const { currentEnv: envName } = this.props;
const { appDetailData } = this.state;
const appName = _.get(appDetailData, 'Workload.workload.metadata.name', '');
if (appName && envName) {
const { envName, appName, compName, compList } = this.state;
if (appName && envName && compName) {
const res = await this.props.dispatch({
type: 'applist/deleteApp',
type: 'components/deleteComponent',
payload: {
appName,
envName,
compName,
},
});
if (res) {
message.success(res);
this.props.history.push({ pathname: '/ApplicationList' });
if (compList.length === 1) {
//
this.props.history.push({
pathname: `/ApplicationList`,
});
} else {
// component
this.props.getInitCompList(compList.length);
}
}
}
};
deleteTrait = async (e, item) => {
e.stopPropagation();
const { appName, envName } = this.state;
const { appName, envName, compName } = this.state;
const traitNameObj = _.get(item, 'trait.metadata.annotations', '');
const traitName = traitNameObj['vela.oam.dev/traitDef'] || traitNameObj['trait.oam.dev/name'];
if (traitName && appName && envName) {
if (traitName && appName && envName && compName) {
const res = await this.props.dispatch({
type: 'trait/deleteOneTrait',
payload: {
envName,
appName,
traitName,
compName,
},
});
if (res) {
message.success(res);
this.getInitialData(2);
this.getInitialData(compName);
}
}
};
@@ -132,12 +178,20 @@ class TableList extends React.Component {
};
createTrait = async () => {
await this.setState({
visible: true,
});
await this.setState(
{
visible: true,
},
() => {
if (this.child) {
this.child.resetFields();
}
},
);
};
handleOk = async () => {
await this.child.validateFields();
const submitData = this.child.getSelectValue();
if (submitData.name) {
const submitObj = {
@@ -152,13 +206,14 @@ class TableList extends React.Component {
});
}
});
const { envName, appName } = this.state;
if (envName && appName) {
const { envName, appName, compName } = this.state;
if (envName && appName && compName) {
const res = await this.props.dispatch({
type: 'trait/attachOneTraits',
payload: {
envName,
appName,
compName,
params: submitObj,
},
});
@@ -167,7 +222,7 @@ class TableList extends React.Component {
visible: false,
});
message.success(res);
this.getInitialData(2);
this.getInitialData(compName);
}
}
} else {
@@ -187,43 +242,32 @@ class TableList extends React.Component {
gotoWorkloadDetail = (e) => {
e.stopPropagation();
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
if (appName && envName) {
this.props.history.push({
pathname: '/ApplicationList/WorkloadDetail',
state: { appName, envName },
});
}
};
gotoTraitDetail = (e, traitItem) => {
gotoTraitDetail = (e) => {
e.stopPropagation();
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
if (appName && envName) {
this.props.history.push({
pathname: '/ApplicationList/TraitDetail',
state: { traitItem, appName, envName },
});
}
};
render() {
const status = _.get(this.state.appDetailData, 'Status', '');
const Workload = _.get(this.state.appDetailData, 'Workload.workload', {});
const Traits = _.get(this.state.appDetailData, 'Traits', []);
const { compDetailData } = this.state;
const status = _.get(compDetailData, 'status', '');
const Workload = _.get(compDetailData, 'workload', {});
const Traits = _.get(compDetailData, 'traits', []);
let containers = {};
// if (Workload.kind === 'ContainerizedWorkload') {
// containers = _.get(Workload, 'spec.containers[0]', {});
// } else if (Workload.kind === 'Deployment') {
// containers = _.get(Workload, 'spec.template.spec.containers[0]', {});
// }
containers = _.get(Workload, 'spec.containers[0]', {});
if (_.get(Workload, 'kind', '') === 'Job') {
containers = _.get(Workload, 'spec.template.spec.containers[0]', {});
} else {
containers = _.get(Workload, 'spec.podSpec.containers[0]', {});
}
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
const colorObj = {
Deployed: '#4CAF51',
Staging: '#F44337',
UNKNOWN: '#1890ff',
};
return (
<PageContainer>
<div style={{ margin: '8px' }}>
<Spin spinning={loadingAll}>
<div className="card-container app-detial">
<h2>{_.get(Workload, 'metadata.name')}</h2>
@@ -234,15 +278,17 @@ class TableList extends React.Component {
<TabPane tab="Summary" key="1">
<Row>
<Col span="11">
<div className="summaryBox1" onClick={(e) => this.gotoWorkloadDetail(e)}>
{/* <div className="summaryBox1"> */}
<div
className="summaryBox1"
onClick={(e) => this.gotoWorkloadDetail(e)}
style={{ background: colorObj[status] || '#1890ff' }}
>
<Row>
<Col span="22">
<p className="title">{Workload.kind}</p>
<p>{Workload.apiVersion}</p>
</Col>
<Col span="2">
{/* <a href="JavaScript:;">?</a> */}
<p className="title hasCursor" onClick={this.hrefClick}>
?
</p>
@@ -253,6 +299,21 @@ class TableList extends React.Component {
</p>
<p className="title">Settings:</p>
<Row>
{_.get(Workload, 'kind', '') === 'Job' ? (
<Fragment>
<Col span="8">
<p>count</p>
</Col>
<Col span="16">
<p>
{_.get(Workload, 'spec.completions', '') ||
_.get(Workload, 'spec.parallelism', '')}
</p>
</Col>
</Fragment>
) : (
<Fragment />
)}
{Object.keys(containers).map((currentKey) => {
if (currentKey === 'ports') {
return (
@@ -268,6 +329,18 @@ class TableList extends React.Component {
// eslint-disable-next-line no-else-return
} else if (currentKey === 'name') {
return <Fragment key={currentKey} />;
// eslint-disable-next-line no-else-return
} else if (currentKey === 'env') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>env</p>
</Col>
<Col span="16">
<p>{_.get(containers[currentKey], '[0].value', '')}</p>
</Col>
</Fragment>
);
}
return (
<Fragment key={currentKey}>
@@ -282,23 +355,18 @@ class TableList extends React.Component {
})}
</Row>
</div>
<div className="summaryBox2">
<p className="title">Status:</p>
<p>{status}</p>
{/* <Row>
<Col span="8">
<p>Available Replicas</p>
<p>Ready Replicas</p>
</Col>
<Col span="16">
<p>1</p>
<p>1</p>
</Col>
</Row> */}
</div>
<Popconfirm
title="Are you sure delete this app?"
onConfirm={(e) => this.deleteApp(e)}
title={
this.state.compList.length === 1 ? (
<div>
<p>There is only one component in the current application,</p>
<p>Do you want to delete the entire application?</p>
</div>
) : (
'Are you sure delete this component?'
)
}
onConfirm={(e) => this.deleteComp(e)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
@@ -312,11 +380,7 @@ class TableList extends React.Component {
Traits.map((item, index) => {
const traitItem = _.get(item, 'trait', {});
const annotations = _.get(traitItem, 'metadata.annotations', {});
let traitType = 1;
const spec = _.get(traitItem, 'spec', {});
if (traitItem.kind === 'Ingress') {
traitType = 2;
}
return (
<div
className="summaryBox"
@@ -355,54 +419,22 @@ class TableList extends React.Component {
</Row>
<p className="title">Properties:</p>
<Row>
{traitType === 2 ? (
<Fragment>
<Col span="8">
<p>domain</p>
</Col>
<Col span="16">
<p>{_.get(spec, 'rules[0].host', '')}</p>
</Col>
<Col span="8">
<p>service</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.serviceName',
'',
)}
</p>
</Col>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.servicePort',
'',
)}
</p>
</Col>
</Fragment>
) : (
Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
</Col>
</Fragment>
);
})
)}
{/* {Object.keys(spec).map((currentKey) => {
{Object.keys(spec).map((currentKey) => {
if (spec[currentKey].constructor === Object) {
const backend = _.get(spec, `${currentKey}`, {});
return Object.keys(backend).map((currentKey1) => {
return (
<Fragment key={currentKey1}>
<Col span="8">
<p>{currentKey1}</p>
</Col>
<Col span="16">
<p>{backend[currentKey1]}</p>
</Col>
</Fragment>
);
});
}
return (
<Fragment key={currentKey}>
<Col span="8">
@@ -413,7 +445,7 @@ class TableList extends React.Component {
</Col>
</Fragment>
);
})} */}
})}
</Row>
<div style={{ clear: 'both', height: '32px' }}>
<Popconfirm
@@ -456,13 +488,12 @@ class TableList extends React.Component {
</Row>
</TabPane>
<TabPane tab="Topology" key="2">
{/* <p>Topology</p> */}
<Topology />
<p>Topology</p>
</TabPane>
</Tabs>
</div>
<Modal
title="attach a trait"
title="Attach a Trait"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
@@ -484,7 +515,7 @@ class TableList extends React.Component {
/>
</Modal>
</Spin>
</PageContainer>
</div>
);
}
}

View File

@@ -1,9 +1,7 @@
.app-detial {
.ant-tabs {
height: 800px;
min-height: 800px;
min-height: 500px;
padding: 10px 20px;
overflow: auto;
background-color: #fff;
.ant-tabs-content-holder {
background: #fff;
@@ -19,7 +17,6 @@
font-weight: 500;
font-size: 16px;
line-height: 36px;
// color: #fff;
}
p {
margin: 0;

View File

@@ -0,0 +1,190 @@
import React from 'react';
import { Link } from 'umi';
import { Breadcrumb, Button, Menu, Spin, Popconfirm, message } from 'antd';
import { connect } from 'dva';
import _ from 'lodash';
import './index.less';
import ComponentDetail from '../ComponentDetail/index.jsx';
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.applist,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
constructor(props) {
super(props);
this.state = {
envName: '',
componentName: '',
defaultSelectedKeys: '',
appName: '',
compList: [],
};
}
componentDidMount() {
this.getInitData();
}
// shouldComponentUpdate(nextProps) {
// if (nextProps.currentEnv === this.props.currentEnv) {
// return true;
// }
// this.getInitData(nextProps.currentEnv);
// return true;
// }
getInitData = async (changeEnvName) => {
let appName = '';
let description = '';
let envName = this.props.currentEnv;
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
description = _.get(this.props, 'location.state.description', '');
envName = _.get(this.props, 'location.state.envName', this.props.currentEnv);
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('description', description);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
description = sessionStorage.getItem('description');
envName = sessionStorage.getItem('envName');
}
this.setState({
appName,
envName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
envName: changeEnvName || envName,
appName,
},
});
if (res) {
const compData = _.get(res, 'components', []);
const compList = [];
compData.forEach((item) => {
compList.push({
compName: item.name,
});
});
this.setState({
compList,
});
if (compList.length) {
this.changeComponent({
key: compList[0].compName,
});
}
}
};
changeComponent = ({ key }) => {
this.setState({
componentName: key,
defaultSelectedKeys: key,
});
};
deleteApp = async (e) => {
e.stopPropagation();
const { envName } = this.state;
const { appName } = this.state;
if (appName && envName) {
const res = await this.props.dispatch({
type: 'applist/deleteApp',
payload: {
appName,
envName,
},
});
if (res) {
message.success(res);
this.props.history.push({ pathname: '/ApplicationList' });
}
}
};
cancel = (e) => {
e.stopPropagation();
};
render() {
const { envName, componentName, defaultSelectedKeys, appName, compList } = this.state;
const { loadingAll } = this.props;
return (
<div style={{ height: '100%' }}>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>{appName}</Breadcrumb.Item>
<Breadcrumb.Item>Components</Breadcrumb.Item>
<Breadcrumb.Item>{componentName}</Breadcrumb.Item>
</Breadcrumb>
</div>
<Spin spinning={loadingAll}>
<div className="appComponent">
<div className="left">
<Menu
mode="inline"
onClick={this.changeComponent}
defaultSelectedKeys={[defaultSelectedKeys]}
selectedKeys={defaultSelectedKeys}
>
<Menu.ItemGroup key="g1" title="Components">
{compList.map((item) => {
return <Menu.Item key={item.compName}>{item.compName}</Menu.Item>;
})}
</Menu.ItemGroup>
</Menu>
<div className="addComp">
<Link
to={{
pathname: `/ApplicationList/${appName}/createComponent`,
state: { appName, envName, isCreate: false },
}}
>
add a new comp
</Link>
</div>
</div>
{defaultSelectedKeys ? (
<div className="right">
<div className="btn">
<Popconfirm
title="Are you sure delete this app?"
placement="bottom"
onConfirm={(e) => this.deleteApp(e)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button type="primary">Delete App</Button>
</Popconfirm>
</div>
<ComponentDetail
appName={appName}
compName={componentName}
envName={envName}
getInitCompList={this.getInitData}
compList={compList}
history={this.props.history}
/>
</div>
) : (
<div style={{ width: '100%', height: '100%', background: '#FFFFFF' }} />
)}
</div>
</Spin>
</div>
);
}
}
export default TableList;

View File

@@ -0,0 +1,48 @@
.ant-spin-nested-loading {
height: 100%;
}
.ant-spin-container {
height: 100%;
}
.appComponent {
display: flex;
height: 100%;
.left {
width: 200px;
background: #fff;
.ant-menu-item-group-title {
color: #000;
font-weight: 500;
font-size: 18px;
border-top: 1px solid #efefef;
border-bottom: 1px solid #efefef;
}
.ant-menu-item-group-list .ant-menu-item,
.ant-menu-item-group-list .ant-menu-submenu-title {
padding-left: 16px !important;
color: #000;
font-size: 16px;
}
.addComp {
padding: 8px 16px;
color: blue;
font-size: 16px;
text-decoration: underline;
background-color: #fff;
border-top: 1px solid #efefef;
border-right: 1px solid #efefef;
border-bottom: 1px solid #efefef;
cursor: pointer;
}
}
.right {
position: relative;
flex: 1;
.btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 8;
}
}
}

View File

@@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Button, Row, Col, Form, Input, Select, Steps, message } from 'antd';
import { Button, Row, Col, Form, Input, Select, Steps, message, Breadcrumb } from 'antd';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
@@ -46,24 +46,40 @@ class TableList extends React.Component {
workloadSettings: [],
step1SubmitObj: {},
step1InitialValues: {
workload_type: '',
workloadType: '',
},
step1Settings: [],
appName: '',
envName: '',
isCreate: '',
};
}
UNSAFE_componentWillMount() {
const activeStep = _.get(this.props, 'location.state.activeStep', 0);
this.setState(() => ({
current: activeStep,
}));
}
componentDidMount() {
this.getInitalData();
}
getInitalData = async () => {
let appName = '';
let envName = '';
let isCreate = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
isCreate = _.get(this.props, 'location.state.isCreate', false);
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
sessionStorage.setItem('isCreate', isCreate);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
isCreate = sessionStorage.getItem('isCreate');
}
this.setState({
appName,
envName,
isCreate,
});
const res = await this.props.dispatch({
type: 'workload/getWorkload',
});
@@ -73,24 +89,6 @@ class TableList extends React.Component {
this.setState({
traitList: traits,
});
//
const traitType = _.get(this.props, 'location.state.TraitType', '');
if (traitType) {
// let availableTraitList = traits.filter((item)=>{
// return item.name === traitType
// })
this.setState({
availableTraitList: traits,
traitNum: [
{
refname: null,
initialData: { name: traitType },
uniq: new Date().valueOf(),
},
],
});
}
if (Array.isArray(res) && res.length) {
this.setState(
() => ({
@@ -98,11 +96,17 @@ class TableList extends React.Component {
}),
() => {
if (this.state.current === 0) {
const WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
let WorkloadType = '';
if (this.props.location.state) {
WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
sessionStorage.setItem('WorkloadType', WorkloadType);
} else {
WorkloadType = sessionStorage.getItem('WorkloadType');
}
this.formRefStep1.current.setFieldsValue({
workload_type: WorkloadType || this.state.workloadList[0].name,
workloadType: WorkloadType || this.state.workloadList[0].name,
});
this.workloadTypeChange(this.state.workloadList[0].name);
this.workloadTypeChange(WorkloadType || this.state.workloadList[0].name);
}
},
);
@@ -117,7 +121,12 @@ class TableList extends React.Component {
});
};
onFinishStep2 = () => {
onFinishStep2 = async () => {
const asyncValidateArray = [];
this.state.traitNum.forEach((item) => {
asyncValidateArray.push(item.refname.validateFields());
});
await Promise.all(asyncValidateArray);
const newTraitNum = this.state.traitNum.map((item) => {
// eslint-disable-next-line no-param-reassign
item.initialData = item.refname.getSelectValue();
@@ -144,8 +153,9 @@ class TableList extends React.Component {
};
changeShowMore = () => {
const isMore = this.state.isShowMore;
this.setState({
isShowMore: true,
isShowMore: !isMore,
});
};
@@ -163,23 +173,31 @@ class TableList extends React.Component {
};
createApp = async () => {
const { step1SubmitObj, traitNum } = this.state;
const { traitNum, isCreate } = this.state;
const { step1SubmitObj } = this.state;
if (isCreate === true || isCreate === 'true') {
step1SubmitObj.envName = this.props.currentEnv;
} else {
step1SubmitObj.envName = this.state.envName;
}
step1SubmitObj.appName = this.state.appName;
const submitObj = _.cloneDeep(step1SubmitObj);
const { workload_name: workloadName } = step1SubmitObj;
const { workloadName, appName } = step1SubmitObj;
submitObj.flags.push({
name: 'name',
value: workloadName.toString(),
});
//
if (traitNum.length) {
const { env_name: envName } = step1SubmitObj;
const { envName } = step1SubmitObj;
const step2SubmitObj = [];
traitNum.forEach(({ initialData }) => {
if (initialData.name) {
const initialObj = {
name: initialData.name,
env_name: envName,
workload_name: workloadName,
envName,
workloadName,
appName,
flags: [],
};
Object.keys(initialData).forEach((key) => {
@@ -204,7 +222,8 @@ class TableList extends React.Component {
if (res) {
message.success(res);
this.props.history.push({
pathname: '/ApplicationList',
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName: step1SubmitObj.envName },
});
}
};
@@ -213,13 +232,13 @@ class TableList extends React.Component {
await this.formRefStep1.current.validateFields();
const currentData = this.formRefStep1.current.getFieldsValue();
const submitObj = {
env_name: this.props.currentEnv,
workload_type: currentData.workload_type,
workload_name: currentData.workload_name,
envName: this.props.currentEnv,
workloadType: currentData.workloadType,
workloadName: currentData.workloadName,
flags: [],
};
Object.keys(currentData).forEach((key) => {
if (key !== 'workload_name' && key !== 'workload_type' && currentData[key]) {
if (key !== 'workloadName' && key !== 'workloadType' && currentData[key]) {
submitObj.flags.push({
name: key,
value: currentData[key].toString(),
@@ -232,15 +251,15 @@ class TableList extends React.Component {
step1Settings: submitObj.flags,
step1SubmitObj: submitObj,
});
this.getAcceptTrait(currentData.workload_type);
this.getAcceptTrait(currentData.workloadType);
};
workloadTypeChange = (value) => {
const content = this.formRefStep1.current.getFieldsValue();
this.formRefStep1.current.resetFields();
const initialObj = {
workload_type: content.workload_type,
workload_name: content.workload_name,
workloadType: content.workloadType,
workloadName: content.workloadName,
};
this.formRefStep1.current.setFieldsValue(initialObj);
const currentWorkloadSetting = this.state.workloadList.filter((item) => {
@@ -274,8 +293,14 @@ class TableList extends React.Component {
getAcceptTrait = (workloadType) => {
const res = this.state.traitList.filter((item) => {
if (item.appliesTo.indexOf(workloadType) !== -1) {
return true;
if (item.appliesTo) {
if (item.appliesTo === '*') {
return true;
}
if (item.appliesTo.indexOf(workloadType) !== -1) {
return true;
}
return false;
}
return false;
});
@@ -289,16 +314,14 @@ class TableList extends React.Component {
this.state.traitNum = this.state.traitNum.filter((item) => {
return item.uniq !== uniq;
});
// this.setState(()=>({
// traitNum: this.state.traitNum
// }));
this.setState((prev) => ({
traitNum: prev.traitNum,
}));
};
render() {
const { current, step1InitialValues, traitNum, workloadSettings } = this.state;
const { appName, envName } = this.state;
const { current, step1InitialValues, traitNum, workloadSettings, isCreate } = this.state;
let { workloadList } = this.state;
workloadList = Array.isArray(workloadList) ? workloadList : [];
let currentDetail;
@@ -317,9 +340,14 @@ class TableList extends React.Component {
>
<div style={{ padding: '16px 48px 0px 16px' }}>
<Form.Item
name="workload_name"
name="workloadName"
label="Name"
rules={[
{
pattern: /^[a-z0-9-_]+$/,
message:
'Names can only use digits(0-9),lowercase letters(a-z),and dashes(-),Underline.',
},
{
required: true,
message: 'Please input name!',
@@ -329,7 +357,7 @@ class TableList extends React.Component {
<Input />
</Form.Item>
<Form.Item
name="workload_type"
name="workloadType"
label="Workload Type"
rules={[
{
@@ -356,8 +384,15 @@ class TableList extends React.Component {
)}
</Select>
</Form.Item>
<Form.Item label="Settings" />
</div>
<Form.Item
label="Settings"
style={{
background: 'rgba(0, 0, 0, 0.04)',
paddingLeft: '16px',
marginLeft: '-10px',
}}
/>
<div className="relativeBox">
<p className="hasMore">?</p>
{Array.isArray(workloadSettings) && workloadSettings.length ? (
@@ -365,7 +400,7 @@ class TableList extends React.Component {
if (item.name === 'name') {
return <Fragment key={item.name} />;
}
return (
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
@@ -375,6 +410,22 @@ class TableList extends React.Component {
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
@@ -389,9 +440,24 @@ class TableList extends React.Component {
<Button type="primary" className="floatRightGap" onClick={this.createWorkload}>
Next
</Button>
<Link to="/ApplicationList">
<Button className="floatRightGap">Cancle</Button>
</Link>
{isCreate === true || isCreate === 'true' ? (
<Link
to={{
pathname: `/ApplicationList`,
}}
>
<Button className="floatRightGap">Cancle</Button>
</Link>
) : (
<Link
to={{
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
}}
>
<Button className="floatRightGap">Cancle</Button>
</Link>
)}
</div>
</Form>
</div>
@@ -403,22 +469,22 @@ class TableList extends React.Component {
<div className="minBox" style={{ width: '60%' }}>
<div style={{ padding: '0px 48px 0px 16px', width: '60%' }}>
<p style={{ fontSize: '18px', lineHeight: '32px' }}>
Name:<span>{step1InitialValues.workload_name}</span>
Name:<span>{step1InitialValues.workloadName}</span>
</p>
</div>
<div style={{ border: '1px solid #eee', padding: '16px 48px 16px 16px' }}>
<p className="title">{step1InitialValues.workload_type}</p>
<p className="title">{step1InitialValues.workloadType}</p>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>apps/v1</span>
<span
style={{
color: '#1890ff',
cursor: 'pointer',
display: this.state.isShowMore ? 'none' : 'black',
// display: this.state.isShowMore ? 'none' : 'black',
}}
onClick={this.changeShowMore}
>
more...
{this.state.isShowMore ? 'close...' : 'more...'}
</span>
</div>
{this.state.isShowMore ? (
@@ -481,14 +547,14 @@ class TableList extends React.Component {
<div>
<div className="minBox">
<p>
Name:<span>{step1InitialValues.workload_name}</span>
Name:<span>{step1InitialValues.workloadName}</span>
</p>
<Row>
<Col span="11">
<div className="summaryBox1">
<Row>
<Col span="22">
<p className="title">{step1InitialValues.workload_type}</p>
<p className="title">{step1InitialValues.workloadType}</p>
<p>apps/v1</p>
</Col>
</Row>
@@ -548,11 +614,6 @@ class TableList extends React.Component {
</Row>
</div>
<div className="buttonBox">
{/* <Link to="/ApplicationList">
<Button type="primary" className="floatRight">
Confirm
</Button>
</Link> */}
<Button
type="primary"
className="floatRight"
@@ -570,16 +631,43 @@ class TableList extends React.Component {
);
}
return (
<PageContainer>
<div className="create-container create-app">
<Steps current={current}>
<Step title="Step 1" description="Choose Workload" />
<Step title="Step 2" description="Attach Trait" />
<Step title="Step 3" description="Review and confirm" />
</Steps>
{currentDetail}
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
{isCreate === true || isCreate === 'true' ? (
<span>{appName}</span>
) : (
<Link
to={{
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
}}
>
{appName}
</Link>
)}
</Breadcrumb.Item>
<Breadcrumb.Item>createComponent</Breadcrumb.Item>
</Breadcrumb>
</div>
</PageContainer>
<PageContainer>
<div className="create-container create-app">
<Steps current={current}>
<Step title="Step 1" description="Choose Workload" />
<Step title="Step 2" description="Attach Trait" />
<Step title="Step 3" description="Review and confirm" />
</Steps>
{currentDetail}
</div>
</PageContainer>
</div>
);
}
}

View File

@@ -27,7 +27,6 @@
}
.relativeBox {
position: relative;
// border: 1px solid #eee;
padding: 0 48px 0 16px;
.hasMore {
position: absolute;
@@ -53,7 +52,7 @@
}
.summaryBox1 {
color: #fff;
background: #0097a7;
background: rgb(24, 144, 255);
}
.summaryBox2 {
background: yellow;

View File

@@ -35,29 +35,35 @@ export default class CreateTraitItem extends React.PureComponent {
return this.formRefStep2.current.getFieldsValue();
};
validateFields = () => {
return this.formRefStep2.current.validateFields();
};
traitSelectChange = async (value, isType = 1) => {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
if (value) {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
}
}
}
};
@@ -78,8 +84,12 @@ export default class CreateTraitItem extends React.PureComponent {
>
<div style={{ border: '1px solid #eee', margin: '16px 0px 8px' }}>
<div style={{ padding: '16px 48px 0px 16px' }}>
<Form.Item name="name" label="Trait">
<Select placeholder="Select a Trait" allowClear onChange={this.traitSelectChange}>
<Form.Item
name="name"
label="Trait"
rules={[{ required: true, message: 'Please Select a Trait!' }]}
>
<Select placeholder="Select a Trait" onChange={this.traitSelectChange}>
{availableTraitList.map((item) => {
return (
<Option value={item.name} key={item.name}>
@@ -89,14 +99,39 @@ export default class CreateTraitItem extends React.PureComponent {
})}
</Select>
</Form.Item>
<Form.Item label="Properties" />
<Form.Item label="Properties" style={{ marginLeft: '-10px' }} />
</div>
<div className="relativeBox">
{/* <p className="hasMore">?</p> */}
{this.state.parameters ? (
this.state.parameters.map((item) => {
return (
<Form.Item name={item.name} label={item.name} key={item.name}>
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);

View File

@@ -1,38 +1,86 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { SearchOutlined, BranchesOutlined, ApartmentOutlined } from '@ant-design/icons';
import { Button, Card, Row, Col, Form, Select, DatePicker, Spin, Empty } from 'antd';
import { Button, Form, Spin, Breadcrumb, Modal, Input, Table, Space, message } from 'antd';
import { connect } from 'dva';
import moment from 'moment';
import './index.less';
import { Link } from 'umi';
const { Option } = Select;
const layout = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 18,
},
};
@connect(({ loading, applist, globalData }) => ({
loadingAll: loading.models.applist,
// 当applist这个models有数据请求行为的时候loading为true没有请求的时候为false
// loadingList: loading.effects['applist/getList'],
// 当applist的effects中的getList有异步请求行为时为true没有请求行为时为false
returnObj: applist.returnObj,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
formRef = React.createRef();
columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (text, record) => {
return (
<Link
to={{
pathname: `/ApplicationList/${record.name}/Components`,
state: { appName: record.name, envName: this.props.currentEnv },
}}
>
{text}
</Link>
);
},
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
render: (text) => {
return text;
},
},
{
title: 'Created Time',
dataIndex: 'createdTime',
key: 'createdTime',
render: (text) => {
return this.getFormatDate(text);
},
},
{
title: 'Actions',
dataIndex: 'Actions',
key: 'Actions',
render: (text, record) => {
return (
<Space>
<Button onClick={() => this.goToDetail(record)}>Details</Button>
<Button onClick={() => this.deleteApp(record)}>Delete</Button>
</Space>
);
},
},
];
constructor(props) {
super(props);
this.state = {};
this.state = {
visible: false,
};
}
componentDidMount() {
const { currentEnv } = this.props;
if (currentEnv) {
this.props.dispatch({
type: 'applist/getList', // applist对应models层的命名空间namespace
payload: {
url: `/api/envs/${currentEnv}/apps/`,
},
});
}
this.getInitData();
}
shouldComponentUpdate(nextProps) {
@@ -40,29 +88,81 @@ class TableList extends React.Component {
return true;
}
this.props.dispatch({
type: 'applist/getList', // applist对应models层的命名空间namespace
type: 'applist/getList',
payload: {
url: `/api/envs/${nextProps.currentEnv}/apps/`,
},
});
return true;
// return true;
}
onFinish = () => {
// const data = moment(values.createTime).format('YYYY-MM-DD')
getInitData = () => {
const { currentEnv } = this.props;
if (currentEnv) {
this.props.dispatch({
type: 'applist/getList',
payload: {
url: `/api/envs/${currentEnv}/apps/`,
},
});
}
};
goToDetail = (record) => {
this.props.history.push({
pathname: `/ApplicationList/${record.name}/Components`,
state: { appName: record.name, envName: this.props.currentEnv },
});
};
deleteApp = async (record) => {
const appName = record.name;
const envName = this.props.currentEnv;
if (appName && envName) {
const res = await this.props.dispatch({
type: 'applist/deleteApp',
payload: {
appName,
envName,
},
});
if (res) {
message.success(res);
this.getInitData();
}
}
};
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = async () => {
const submitData = await this.formRef.current.validateFields();
this.props.history.push({
pathname: `/ApplicationList/${submitData.appName}/createComponent`,
state: { ...submitData, isCreate: true },
});
};
handleCancel = () => {
this.setState({
visible: false,
});
};
onFinish = () => {};
handleChange = () => {};
handleAdd = () => {};
onSelect = () => {
// console.log("selected", selectedKeys, info);
};
onSelect = () => {};
getHeight = (num) => {
return `${num * 55}px`;
return `${num * 43}px`;
};
getFormatDate = (time) => {
@@ -71,107 +171,71 @@ class TableList extends React.Component {
render() {
let { loadingAll, returnObj } = this.props;
const { currentEnv } = this.props;
loadingAll = loadingAll || false;
returnObj = returnObj || [];
const colorObj = {
Deployed: 'first1',
Staging: 'first2',
UNKNOWN: 'first3',
};
loadingAll = loadingAll || false;
return (
<PageContainer>
<Spin spinning={loadingAll}>
<div className="applist">
<Form name="horizontal_login" layout="inline" onFinish={this.onFinish}>
<Form.Item name="createTime">
<DatePicker placeholder="createTime" />
</Form.Item>
<Form.Item name="status">
<Select
placeholder="status"
style={{ width: 120 }}
onChange={this.handleChange}
allowClear
>
<Option value="True">True</Option>
<Option value="False">False</Option>
<Option value="UNKNOWN">UNKNOWN</Option>
</Select>
</Form.Item>
<Form.Item>
<Button icon={<SearchOutlined />} htmlType="submit">
Search
</Button>
</Form.Item>
<Form.Item>
<Link to="/ApplicationList/CreateApplication">
<Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Applications</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingAll}>
<div className="applist">
<Form name="horizontal_login" layout="inline" onFinish={this.onFinish}>
<Form.Item>
<Button onClick={this.showModal} type="primary" style={{ marginBottom: 16 }}>
create
</Button>
</Link>
</Form.Item>
</Form>
</div>
<Table
rowKey={(record) => record.name + Math.random(1, 100)}
columns={this.columns}
dataSource={returnObj}
/>
</Spin>
<Modal
title="Create Application"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="submit" type="primary" onClick={this.handleOk}>
Create
</Button>,
]}
>
<Form {...layout} ref={this.formRef} name="control-ref" labelAlign="left">
<Form.Item
name="appName"
label="Name"
rules={[
{
required: true,
message: 'Please input application name!',
},
{
pattern: /^[a-z0-9-_]+$/,
message:
'Name can only use digits(0-9),lowercase letters(a-z),and dashes(-),Underline.',
},
]}
>
<Input />
</Form.Item>
<Form.Item name="description" label="Description">
<Input.TextArea />
</Form.Item>
</Form>
</div>
<Row gutter={16}>
{Array.isArray(returnObj) && returnObj.length ? (
returnObj.map((item, index) => {
const { traits = [] } = item;
return (
<Col span={6} onClick={this.gotoDetail} key={index.toString()}>
<Link
to={{
pathname: '/ApplicationList/ApplicationListDetail',
state: { appName: item.name, envName: currentEnv },
}}
>
<Card
title={item.name}
bordered={false}
extra={this.getFormatDate(item.created)}
>
<div className="cardContent">
<div className="box2" style={{ height: this.getHeight(traits.length) }} />
<div className="box1">
{traits.length ? (
<div className="box3" style={{ width: '40px' }} />
) : (
''
)}
<div
className={['hasPadding', colorObj[item.status] || 'first3'].join(
' ',
)}
>
<ApartmentOutlined style={{ marginRight: '10px' }} />
{item.workload}
</div>
</div>
{traits.map((item1, index1) => {
return (
<div className="box1" key={index1.toString()}>
<div className="box3" style={{ width: '80px' }} />
<div className="other hasPadding">
<BranchesOutlined style={{ marginRight: '10px' }} />
{item1}
</div>
</div>
);
})}
</div>
</Card>
</Link>
</Col>
);
})
) : (
<div style={{ width: '100%', height: '80%' }}>
<Empty />
</div>
)}
</Row>
</Spin>
</PageContainer>
</Modal>
</PageContainer>
</div>
);
}
}

View File

@@ -15,38 +15,38 @@
}
.box2 {
position: absolute;
top: 21px;
top: 15px;
width: 0;
padding: 0;
border-left: 1px solid black;
}
.box3 {
position: absolute;
top: 21px;
top: 15px;
height: 0;
border-bottom: 1px solid black;
}
.hasPadding {
padding: 10px;
padding: 4px;
color: #fff;
}
div {
margin-bottom: 10px;
}
.first1 {
margin-left: 40px;
background: #1890ff;
margin-left: 30px;
background: #4caf51;
}
.first2 {
margin-left: 40px;
background: red;
margin-left: 30px;
background: #f44337;
}
.first3 {
margin-left: 40px;
background: #92c47c;
margin-left: 30px;
background: #1890ff;
}
.other {
margin-left: 80px;
background: #ee9611;
margin-left: 50px;
background: #ffc105;
}
}

View File

@@ -1,13 +1,13 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Space, Button, Row, Col, message, Spin } from 'antd';
// import { Space, Modal, Button, Row, Col, message, Spin } from 'antd';
// import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Space, Button, Row, Col, message, Spin, Breadcrumb, Modal } from 'antd';
import { Link } from 'umi';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
// const { confirm } = Modal;
const { confirm } = Modal;
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.capability,
@@ -19,6 +19,7 @@ class TableList extends React.PureComponent {
this.state = {
workloadList: [],
traitList: [],
capabilityCenterName: '',
};
}
@@ -34,7 +35,16 @@ class TableList extends React.PureComponent {
const workloadList = [];
const traitList = [];
if (Array.isArray(res)) {
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
let capabilityCenterName = '';
if (this.props.location.state) {
capabilityCenterName = _.get(this.props, 'location.state.name', '');
sessionStorage.setItem('capabilityCenterName', capabilityCenterName);
} else {
capabilityCenterName = sessionStorage.getItem('capabilityCenterName');
}
this.setState({
capabilityCenterName,
});
res.forEach((item) => {
if (item.center === capabilityCenterName) {
if (item.type === 'workload') {
@@ -53,13 +63,12 @@ class TableList extends React.PureComponent {
};
gotoOtherPage = () => {
// window.location.href = 'https://github.com/oam-dev/catalog/blob/master/workloads/cloneset/README.md';
window.open('https://github.com/oam-dev/catalog/blob/master/workloads/cloneset/README.md');
// window.open('https://github.com/oam-dev/catalog/blob/master/workloads/cloneset/README.md');
};
installSignle = async (e, name) => {
e.stopPropagation();
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
const { capabilityCenterName } = this.state;
const res = await this.props.dispatch({
type: 'capability/syncOneCapability',
payload: {
@@ -69,32 +78,34 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
// this.getInitialData();
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
};
uninstallSignle = async (e, name) => {
e.stopPropagation();
// const capabilityCenterName = _.get(this.props, 'location.state.name', '');
if (name) {
const res = await this.props.dispatch({
type: 'capability/deleteOneCapability',
payload: {
// capabilityCenterName,
capabilityName: name,
},
});
if (res) {
message.success(res);
// this.getInitialData();
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
}
};
syncAllSignle = async () => {
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
const { capabilityCenterName } = this.state;
if (capabilityCenterName) {
const res = await this.props.dispatch({
type: 'capability/syncCapability',
@@ -104,56 +115,51 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
// this.getInitialData();
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
}
};
showDeleteConfirm = () => {
message.info('正在开发中...');
// // eslint-disable-next-line
// const _this = this;
// const capabilityCenterName = _.get(this.props, 'location.state.name', '');
// if (capabilityCenterName) {
// confirm({
// title: `Are you sure delete ${capabilityCenterName}?`,
// icon: <ExclamationCircleOutlined />,
// width: 500,
// content: (
// <div>
// <p style={{ margin: '0px' }}>您本次移除 {capabilityCenterName},将会删除的应用列表:</p>
// <Space>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// </Space>
// <p style={{ margin: '0px' }}>
// 确认后,移除 {capabilityCenterName},并且删除相应的应用?
// </p>
// </div>
// ),
// okText: 'Yes',
// okType: 'danger',
// cancelText: 'No',
// async onOk() {
// const res = await _this.props.dispatch({
// type: 'capability/deleteCapability',
// payload: {
// capabilityName: capabilityCenterName,
// },
// });
// if (res) {
// message.success(res);
// _this.props.history.push({ pathname: '/Capability' });
// }
// },
// onCancel() {
// // console.log('Cancel');
// },
// });
// }
// eslint-disable-next-line
const _this = this;
const { capabilityCenterName } = this.state;
if (capabilityCenterName) {
confirm({
title: `Are you sure delete ${capabilityCenterName}?`,
icon: <ExclamationCircleOutlined />,
width: 500,
content: (
<div>
<p style={{ margin: '0px' }}>您本次移除 {capabilityCenterName}将会删除的应用列表</p>
<p style={{ margin: '0px' }}>
确认后移除 {capabilityCenterName}并且删除相应的应用
</p>
</div>
),
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
const res = await _this.props.dispatch({
type: 'capability/deleteCapability',
payload: {
capabilityCenterName,
},
});
if (res) {
message.success(res);
_this.props.history.push({ pathname: '/Capability' });
}
},
onCancel() {
// console.log('Cancel');
},
});
}
};
render() {
@@ -161,82 +167,101 @@ class TableList extends React.PureComponent {
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
return (
<PageContainer>
<Spin spinning={loadingAll}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.syncAllSignle}>
Install all
</Button>
<Button type="default" onClick={this.showDeleteConfirm}>
Remove
</Button>
</Space>
</div>
<div>
<h3>Workloads</h3>
<Row>
{workloadList.length ? (
workloadList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<img
src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1109866916,1852667152&fm=26&gp=0.jpg"
alt="workload"
/>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button onClick={(e) => this.installSignle(e, item.name)}>install</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的workload</div>
</Fragment>
)}
</Row>
</div>
<div>
<h3>Traits</h3>
<Row>
{traitList.length ? (
traitList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<img
src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1109866916,1852667152&fm=26&gp=0.jpg"
alt="workload"
/>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button onClick={(e) => this.installSignle(e, item.name)}>install</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的trait</div>
</Fragment>
)}
</Row>
</div>
</Spin>
</PageContainer>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/Capability">Capability</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Detail</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingAll}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.syncAllSignle}>
Install all
</Button>
<Button type="default" onClick={this.showDeleteConfirm}>
Remove
</Button>
</Space>
</div>
<div>
<h3>Workloads</h3>
<Row>
{workloadList.length ? (
workloadList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<div className="title">{item.name.substr(0, 3).toUpperCase()}</div>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button
onClick={(e) => this.installSignle(e, item.name)}
type="primary"
ghost
>
install
</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的workload</div>
</Fragment>
)}
</Row>
</div>
<div>
<h3>Traits</h3>
<Row>
{traitList.length ? (
traitList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<div className="title">{item.name.substr(0, 3).toUpperCase()}</div>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button
onClick={(e) => this.installSignle(e, item.name)}
type="primary"
ghost
>
install
</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的trait</div>
</Fragment>
)}
</Row>
</div>
</Spin>
</PageContainer>
</div>
);
}
}

View File

@@ -3,7 +3,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
margin: 10px;
margin: 10px 16px 10px 0;
padding: 10px 0;
background-color: #fff;
cursor: pointer;
@@ -11,6 +11,12 @@
width: 60%;
margin: 10px auto;
}
.title {
width: 60%;
margin: 10px auto;
font-size: 30px;
text-align: center;
}
p {
margin: 0;
margin-bottom: 10px;

View File

@@ -1,14 +1,14 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Table, Space, Modal, Form, Input, message, Spin } from 'antd';
// import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Table, Space, Modal, Form, Input, message, Spin, Breadcrumb } from 'antd';
import { CopyOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
// const { confirm } = Modal;
const { Column } = Table;
const { confirm } = Modal;
const layout = {
labelCol: {
@@ -32,6 +32,7 @@ class TableList extends React.PureComponent {
this.state = {
visible: false,
capabilityList: [],
isCreateLoading: false,
};
}
@@ -56,14 +57,19 @@ class TableList extends React.PureComponent {
}
};
showModal = () => {
this.setState({
showModal = async () => {
await this.setState({
visible: true,
isCreateLoading: false,
});
this.formRef.current.resetFields();
};
handleOk = async () => {
const submitData = await this.formRef.current.validateFields();
this.setState({
isCreateLoading: true,
});
const res = await this.props.dispatch({
type: 'capability/createCapabilityCenter',
payload: {
@@ -85,13 +91,6 @@ class TableList extends React.PureComponent {
}
};
// handleTest = async () => {
// await this.formRef.current.validateFields();
// this.setState({
// visible: false,
// });
// };
handleCancel = () => {
this.setState({
visible: false,
@@ -113,58 +112,63 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
// this.getInitialData();
}
const newList1 = _.cloneDeep(this.state.capabilityList);
newList1[index].btnSyncLoading = false;
this.setState(() => ({
capabilityList: newList1,
}));
window.location.reload();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
};
showDeleteConfirm = () => {
message.info('正在开发中...');
// if (record) {
// // eslint-disable-next-line
// const _this = this;
// confirm({
// title: `Are you sure delete ${record}?`,
// icon: <ExclamationCircleOutlined />,
// width: 500,
// content: (
// <div>
// <p>您本次移除 {record},将会删除的应用列表:</p>
// <Space>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// </Space>
// <p>确认后,移除{record},并且删除相应的应用?</p>
// </div>
// ),
// okText: 'Yes',
// okType: 'danger',
// cancelText: 'No',
// async onOk() {
// const res = await _this.props.dispatch({
// type: 'capability/deleteCapability',
// payload: {
// capabilityName: record,
// },
// });
// if (res) {
// message.success(res);
// _this.getInitialData();
// }
// },
// onCancel() {
// // console.log('Cancel');
// },
// });
// }
copyURL = (text) => {
const oInput = document.createElement('input');
oInput.value = text;
document.body.appendChild(oInput);
oInput.select();
document.execCommand('Copy');
oInput.className = 'oInput';
oInput.style.display = 'none';
message.success('copy success');
};
showDeleteConfirm = (record) => {
if (record) {
// eslint-disable-next-line
const _this = this;
confirm({
title: `Are you sure delete ${record}?`,
icon: <ExclamationCircleOutlined />,
width: 500,
content: (
<div>
<p>您本次移除 {record}将会删除的应用列表</p>
<p>确认后移除{record}并且删除相应的应用</p>
</div>
),
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
const res = await _this.props.dispatch({
type: 'capability/deleteCapability',
payload: {
capabilityCenterName: record,
},
});
if (res) {
message.success(res);
_this.getInitialData();
}
},
onCancel() {
// console.log('Cancel');
},
});
}
};
render() {
@@ -172,107 +176,130 @@ class TableList extends React.PureComponent {
let { loadingList } = this.props;
loadingList = loadingList || false;
capabilityList = Array.isArray(capabilityList) ? capabilityList : [];
const { isCreateLoading } = this.state;
return (
<PageContainer>
<Spin spinning={loadingList}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.showModal}>
Create
</Button>
{/* <Button type="default">Sync All</Button> */}
</Space>
</div>
<Modal
title="Create Capability Center"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
// <Button key="test" onClick={this.handleTest}>
// Test
// </Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Create
</Button>,
]}
>
<Form {...layout} ref={this.formRef} name="control-ref" labelAlign="left">
<Form.Item
name="Name"
label="Name"
rules={[
{
required: true,
message: 'Please input name!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="Address"
label="URL"
rules={[
// { pattern: '/^((https|http|ftp|rtsp|mms){0,1}(:\/\/){0,1})\.(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~\/])+$/',
// message: 'please input correct URL'
// },
{
required: true,
message: 'Please input URL!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table dataSource={capabilityList} pagination={false} rowKey={(record) => record.name}>
<Column
title="Name"
dataIndex="name"
key="name"
render={(text, record) => {
return (
<Link to={{ pathname: '/Capability/Detail', state: { name: record.name } }}>
{text}
</Link>
);
}}
/>
<Column
title="URL"
dataIndex="url"
key="url"
render={(text) => {
return (
<a href={text} target="_blank" rel="noreferrer">
{text}
</a>
);
}}
/>
<Column
title="Operations"
dataIndex="name"
key="name"
render={(text, record, index) => {
return (
<Space>
<Button
loading={record.btnSyncLoading}
onClick={() => this.syncSignle(text, index)}
>
sync
</Button>
<Button onClick={() => this.showDeleteConfirm(text)}>remove</Button>
</Space>
);
}}
/>
</Table>
</Spin>
</PageContainer>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Capability</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingList}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.showModal}>
Create
</Button>
</Space>
</div>
<Modal
title="Create Capability Center"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button
loading={isCreateLoading}
key="submit"
type="primary"
onClick={this.handleOk}
>
Create
</Button>,
]}
>
<Form {...layout} ref={this.formRef} name="control-ref" labelAlign="left">
<Form.Item
name="Name"
label="Name"
rules={[
{
required: true,
message: 'Please input name!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,60}$', 'g'),
message:
'The maximum length is 60,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="Address"
label="URL"
rules={[
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
{
required: true,
message: 'Please input URL!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table dataSource={capabilityList} pagination={false} rowKey={(record) => record.name}>
<Column
title="Name"
dataIndex="name"
key="name"
render={(text, record) => {
return (
<Link to={{ pathname: '/Capability/Detail', state: { name: record.name } }}>
{text}
</Link>
);
}}
/>
<Column
title="URL"
dataIndex="url"
key="url"
width="60%"
render={(text) => {
return (
<div className="hoverItem">
<a href={text} target="_blank" rel="noreferrer">
{text}
</a>
<div className="copyIcon" onClick={() => this.copyURL(text)}>
<CopyOutlined />
</div>
</div>
);
}}
/>
<Column
title="Operations"
dataIndex="name"
key="name"
render={(text, record, index) => {
return (
<Space>
<Button
loading={record.btnSyncLoading}
onClick={() => this.syncSignle(text, index)}
type="primary"
ghost
>
sync
</Button>
<Button onClick={() => this.showDeleteConfirm(text)}>remove</Button>
</Space>
);
}}
/>
</Table>
</Spin>
</PageContainer>
</div>
);
}
}

View File

@@ -1,3 +1,14 @@
p {
margin: 0;
}
.hoverItem {
.copyIcon {
display: none;
margin-left: 10px;
font-size: 16px;
cursor: pointer;
}
}
.hoverItem:hover .copyIcon {
display: inline-block;
}

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