Compare commits
510 Commits
0.2.0-rc1
...
maxgio92/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
857c338c53 | ||
|
|
5a9c25b125 | ||
|
|
3cd7bfe6d4 | ||
|
|
ff53cc2f38 | ||
|
|
852ab16323 | ||
|
|
9c18471879 | ||
|
|
3435f5464b | ||
|
|
f216d0bd8d | ||
|
|
f9e7256746 | ||
|
|
5b46e8eb81 | ||
|
|
dd5ed4575e | ||
|
|
f9554d4cae | ||
|
|
a36c7545db | ||
|
|
f612ecea0c | ||
|
|
098a74b565 | ||
|
|
5a8a8ae77a | ||
|
|
a8430f2e72 | ||
|
|
3afc470534 | ||
|
|
d84f0be76b | ||
|
|
3a174bf755 | ||
|
|
90a2e9c742 | ||
|
|
a091331070 | ||
|
|
cb3439bd3d | ||
|
|
1fd390b91e | ||
|
|
80c83689f5 | ||
|
|
da3d42801b | ||
|
|
9643885574 | ||
|
|
ac3f2bbdd7 | ||
|
|
adb214f7f9 | ||
|
|
ef26d0e6db | ||
|
|
3d6f29fa43 | ||
|
|
261876b59b | ||
|
|
ab750141c6 | ||
|
|
e237249815 | ||
|
|
e15191c2a0 | ||
|
|
741db523e5 | ||
|
|
7b3f850035 | ||
|
|
72733415f0 | ||
|
|
cac2920827 | ||
|
|
e0b339d68a | ||
|
|
4f55dd8db8 | ||
|
|
fd738341ed | ||
|
|
fce1658827 | ||
|
|
93547c128f | ||
|
|
f1dc028649 | ||
|
|
37381184d2 | ||
|
|
82b58d7d53 | ||
|
|
60e826dc83 | ||
|
|
6e8ddd102f | ||
|
|
b64aaebc89 | ||
|
|
9a85631bb8 | ||
|
|
51ed42981f | ||
|
|
cf313d415b | ||
|
|
526a6053a5 | ||
|
|
0dd13a96fc | ||
|
|
1c8a5d8f5a | ||
|
|
b9fc50861b | ||
|
|
29d29ccd4b | ||
|
|
f207546af0 | ||
|
|
deb0858fae | ||
|
|
1af56b736b | ||
|
|
3c9228d1aa | ||
|
|
bf6760fbd0 | ||
|
|
23564f8e40 | ||
|
|
a8b84c8cb3 | ||
|
|
8c0c8c653d | ||
|
|
ec89f5dd26 | ||
|
|
68956a075a | ||
|
|
c036feeefc | ||
|
|
9f6883d309 | ||
|
|
e7227d24e9 | ||
|
|
f168137407 | ||
|
|
49e76f7f93 | ||
|
|
9d69770888 | ||
|
|
f4ac85dfed | ||
|
|
cb4289d45b | ||
|
|
01197892a4 | ||
|
|
345836630c | ||
|
|
69a6394e59 | ||
|
|
a3495cf614 | ||
|
|
7662c3dc6a | ||
|
|
137b0f083b | ||
|
|
9fd18db5a5 | ||
|
|
364adf7d9e | ||
|
|
cb3ce372b9 | ||
|
|
59d81c2002 | ||
|
|
85861ee5dc | ||
|
|
ed88606031 | ||
|
|
afae361627 | ||
|
|
535ef7412c | ||
|
|
f373debf54 | ||
|
|
569d803e95 | ||
|
|
7b3b0d6504 | ||
|
|
0bfca6b60e | ||
|
|
fdc1b3fe39 | ||
|
|
f7bc2e24cc | ||
|
|
d3021633cd | ||
|
|
7fefe4f6de | ||
|
|
302bb19707 | ||
|
|
27a7792c31 | ||
|
|
1a60e83772 | ||
|
|
632268dd68 | ||
|
|
4e07de37c4 | ||
|
|
1d10bcab1e | ||
|
|
d4a5f3beca | ||
|
|
cd56eab119 | ||
|
|
6cee5b73af | ||
|
|
8e7325aecb | ||
|
|
be26783424 | ||
|
|
0b199f4136 | ||
|
|
1bbaebbc90 | ||
|
|
4b8d8b2a7c | ||
|
|
3fb4c41daf | ||
|
|
055791966a | ||
|
|
c9af9c18e4 | ||
|
|
fef381d2b4 | ||
|
|
19aff8c882 | ||
|
|
8da7e22cb2 | ||
|
|
47c37a3d5d | ||
|
|
677175b3ed | ||
|
|
c95e3a2068 | ||
|
|
0be3be4480 | ||
|
|
6ad434fcfb | ||
|
|
e53911942d | ||
|
|
a179645f26 | ||
|
|
778fb4bcc2 | ||
|
|
bc23324fe7 | ||
|
|
4a6fd49554 | ||
|
|
d7baf18bf9 | ||
|
|
5c7804e1bf | ||
|
|
c4481f26f7 | ||
|
|
ec715d2e8f | ||
|
|
0aeaf89cb7 | ||
|
|
3d31ddb4e3 | ||
|
|
e83f344cdc | ||
|
|
da83a8711a | ||
|
|
43a944ace0 | ||
|
|
0acc2d2ef1 | ||
|
|
14f9686bbb | ||
|
|
6ba9826c51 | ||
|
|
bd58084ded | ||
|
|
3a5e50886d | ||
|
|
e2768dad83 | ||
|
|
b97c23176d | ||
|
|
fa8e805842 | ||
|
|
8df66fc232 | ||
|
|
c2218912eb | ||
|
|
e361e2d424 | ||
|
|
260b60d263 | ||
|
|
e0d5e6feb2 | ||
|
|
0784dc7177 | ||
|
|
b17c6c4636 | ||
|
|
52cf597041 | ||
|
|
b8dcded882 | ||
|
|
6a175e9017 | ||
|
|
3c609f84db | ||
|
|
7c3a59c4e4 | ||
|
|
d3e3b8a881 | ||
|
|
7a8148bd58 | ||
|
|
405d3ac52d | ||
|
|
f92acf9a9d | ||
|
|
bbb7b850d6 | ||
|
|
0f7284d190 | ||
|
|
7db263b2b6 | ||
|
|
0a8f50f761 | ||
|
|
7a66e8ea93 | ||
|
|
b5eb03ea76 | ||
|
|
681b514516 | ||
|
|
b28b98a7bc | ||
|
|
f6bf0ca446 | ||
|
|
1081bad7cb | ||
|
|
79372c7332 | ||
|
|
4e8faaf845 | ||
|
|
d1b008972c | ||
|
|
a14c7609df | ||
|
|
03456c0b54 | ||
|
|
ddfe2219a0 | ||
|
|
6b68363a46 | ||
|
|
357834c5b9 | ||
|
|
085d9f6503 | ||
|
|
196e3c910d | ||
|
|
0039c91c23 | ||
|
|
26965a5ea2 | ||
|
|
422b6598ba | ||
|
|
61e6ab4088 | ||
|
|
94c6a64fcb | ||
|
|
75ebb571e4 | ||
|
|
8f3b3eac29 | ||
|
|
7979c256d9 | ||
|
|
bdafbcf90a | ||
|
|
d0530bbbe3 | ||
|
|
1035afc7fe | ||
|
|
67046c5b54 | ||
|
|
564c4db81a | ||
|
|
30c3ab078d | ||
|
|
e9b803b9cd | ||
|
|
cb8e504832 | ||
|
|
713867d916 | ||
|
|
23e55c685c | ||
|
|
6393541818 | ||
|
|
c140ab076e | ||
|
|
6b629777b7 | ||
|
|
5554ed5f32 | ||
|
|
00ef9a2f67 | ||
|
|
46c2f0e997 | ||
|
|
0c0a90a934 | ||
|
|
9d65013a22 | ||
|
|
60ab33337d | ||
|
|
225d671301 | ||
|
|
7538926bae | ||
|
|
0de0eca72a | ||
|
|
d5a702ceae | ||
|
|
a2fda44110 | ||
|
|
06330cf992 | ||
|
|
1ec9936158 | ||
|
|
694b519af8 | ||
|
|
0b34f04291 | ||
|
|
a702ef2af2 | ||
|
|
04d91af9f5 | ||
|
|
8949be7497 | ||
|
|
df08c9e63e | ||
|
|
07daffd669 | ||
|
|
3a42b90221 | ||
|
|
09277e9f3d | ||
|
|
47794c0cf8 | ||
|
|
e24394f329 | ||
|
|
01053d5deb | ||
|
|
b749e34547 | ||
|
|
82480f3afd | ||
|
|
88a9c242a4 | ||
|
|
651c62ff4a | ||
|
|
dcb8b784d5 | ||
|
|
7a698633d7 | ||
|
|
894ea5016b | ||
|
|
e4e3283b90 | ||
|
|
007f0083c2 | ||
|
|
bc6fc920d3 | ||
|
|
01b511b509 | ||
|
|
6223b1c297 | ||
|
|
d5158f06be | ||
|
|
047f4a0ff7 | ||
|
|
71cdb45925 | ||
|
|
9182895811 | ||
|
|
2eceb0935a | ||
|
|
8ead555743 | ||
|
|
57bf3d1c1b | ||
|
|
bb58e90f5d | ||
|
|
f8fa87a998 | ||
|
|
b3658b7bfc | ||
|
|
54d0201161 | ||
|
|
44ffe0ddf5 | ||
|
|
491ab71842 | ||
|
|
4e9dbf8690 | ||
|
|
34614015a0 | ||
|
|
737fb26e39 | ||
|
|
b56015922f | ||
|
|
ddb9ffd79e | ||
|
|
cae65c9f84 | ||
|
|
befcf65bdd | ||
|
|
e1d98334a2 | ||
|
|
848c6d99c2 | ||
|
|
bd12068397 | ||
|
|
4604e44c37 | ||
|
|
31863b53af | ||
|
|
7a055fcb9f | ||
|
|
29ab5ca64a | ||
|
|
c52f7844db | ||
|
|
9244122d42 | ||
|
|
f883e7b662 | ||
|
|
2f5f31b678 | ||
|
|
e7ef9642ad | ||
|
|
34f73af5c4 | ||
|
|
18912a002b | ||
|
|
d43ad2f9f8 | ||
|
|
9a595877ce | ||
|
|
c0d4aab582 | ||
|
|
6761fb93dc | ||
|
|
bf9e0f6b10 | ||
|
|
f937942c49 | ||
|
|
89d7f301c6 | ||
|
|
2a6ff09340 | ||
|
|
35f48107fc | ||
|
|
7aa62b6f1d | ||
|
|
58645f39bb | ||
|
|
0e55823a0c | ||
|
|
ba690480a7 | ||
|
|
faa2306a30 | ||
|
|
c1448c82e9 | ||
|
|
776a56b5bc | ||
|
|
e4883bb737 | ||
|
|
e70afb5e77 | ||
|
|
ee7af18f98 | ||
|
|
ac7de3bf88 | ||
|
|
8883b15aa9 | ||
|
|
e23132c820 | ||
|
|
bec59a585e | ||
|
|
9c649ac7eb | ||
|
|
3455aed503 | ||
|
|
ad1edf57ac | ||
|
|
d64dcb5a44 | ||
|
|
76d7697703 | ||
|
|
96f4f31c17 | ||
|
|
c3f9dfe652 | ||
|
|
502e9a556f | ||
|
|
6f208a6e0e | ||
|
|
1fb52003d5 | ||
|
|
98e1640d9b | ||
|
|
eb19a7a89f | ||
|
|
db8b8ac1d9 | ||
|
|
663ce93a3e | ||
|
|
a6408f26b0 | ||
|
|
1aa026c977 | ||
|
|
6008373960 | ||
|
|
414c03a874 | ||
|
|
4d34a9e3d7 | ||
|
|
cb9b560926 | ||
|
|
ef75d0496a | ||
|
|
e1e75a093b | ||
|
|
80143ffd50 | ||
|
|
3d54810f19 | ||
|
|
09dfe33a6a | ||
|
|
01ea36b462 | ||
|
|
bd448d8c29 | ||
|
|
b58ca3a7d7 | ||
|
|
52fb0948cb | ||
|
|
1b0fa587eb | ||
|
|
92655f1872 | ||
|
|
44bf846260 | ||
|
|
e6b433dcd7 | ||
|
|
3e0882dbc8 | ||
|
|
416609362d | ||
|
|
3d714dc124 | ||
|
|
bd01881dd3 | ||
|
|
ac6af13b07 | ||
|
|
8fb4b7d4a1 | ||
|
|
d4280b8d7e | ||
|
|
6e39b17e7c | ||
|
|
b1a9603faa | ||
|
|
0d4201a6c2 | ||
|
|
1734c906a9 | ||
|
|
184f054f2f | ||
|
|
126449b796 | ||
|
|
284e7da66f | ||
|
|
99e1589828 | ||
|
|
7cc2c3f4e9 | ||
|
|
ba07f99c6e | ||
|
|
d79972691e | ||
|
|
25f021e151 | ||
|
|
320353e561 | ||
|
|
5bd2af89e4 | ||
|
|
61d394f844 | ||
|
|
fc8f5a08fe | ||
|
|
ffcb7c4408 | ||
|
|
8d1a109f1c | ||
|
|
a19045419d | ||
|
|
7574335a8a | ||
|
|
72e97b9960 | ||
|
|
b3c6082a1e | ||
|
|
9a940096c9 | ||
|
|
f9becf39e5 | ||
|
|
e1160b8862 | ||
|
|
6472b221ed | ||
|
|
a2e5bbf26d | ||
|
|
8804496bb2 | ||
|
|
5de0a6d712 | ||
|
|
531cc4cf14 | ||
|
|
3e33290c4c | ||
|
|
824442b9ee | ||
|
|
34583352e5 | ||
|
|
5681228789 | ||
|
|
7237972b80 | ||
|
|
46fc65a988 | ||
|
|
44acfaed86 | ||
|
|
7ca087cac5 | ||
|
|
b2b640dc96 | ||
|
|
5b35e0b0d5 | ||
|
|
accd9ca038 | ||
|
|
e7b33bda26 | ||
|
|
08fbd26ec8 | ||
|
|
006b0c80cf | ||
|
|
b6f3fccbea | ||
|
|
bf79c25a8a | ||
|
|
630e802708 | ||
|
|
e5a1861cac | ||
|
|
246c1a3c2c | ||
|
|
a06e68945c | ||
|
|
61c9bc647c | ||
|
|
9c8b0377dc | ||
|
|
dfe0f5ea49 | ||
|
|
a1a2e5e00c | ||
|
|
20aa7657e4 | ||
|
|
7c1592e739 | ||
|
|
f60f2b1542 | ||
|
|
53377e994c | ||
|
|
d0893a5aa9 | ||
|
|
a7fff597fa | ||
|
|
a4128b5744 | ||
|
|
b349042265 | ||
|
|
40bdf0cd25 | ||
|
|
20d0ef8ed0 | ||
|
|
61034947fd | ||
|
|
ca7b85971b | ||
|
|
73e6a17527 | ||
|
|
9103a14506 | ||
|
|
d532f1633c | ||
|
|
3570b02427 | ||
|
|
994a4c282d | ||
|
|
eff1282e34 | ||
|
|
52a73e011c | ||
|
|
4ccef411ab | ||
|
|
dfb0a536b7 | ||
|
|
9ef64d0f8c | ||
|
|
5649283058 | ||
|
|
0481822555 | ||
|
|
bcbd9c2781 | ||
|
|
229b569b50 | ||
|
|
ef6eea62dc | ||
|
|
bb6614d1e8 | ||
|
|
784f3a71df | ||
|
|
3c9895e498 | ||
|
|
6dc83b16da | ||
|
|
e6da507d10 | ||
|
|
5bca3b7da7 | ||
|
|
2e188d26f9 | ||
|
|
3afee659ff | ||
|
|
c22cb6cc88 | ||
|
|
202a18c132 | ||
|
|
8441d8878a | ||
|
|
d5af190c51 | ||
|
|
82ae78b704 | ||
|
|
6c44a6a4d3 | ||
|
|
d6e7437b6c | ||
|
|
ac7114e975 | ||
|
|
2fdc08c2f4 | ||
|
|
c2cede6287 | ||
|
|
36c90d485e | ||
|
|
34c958371b | ||
|
|
e5f17d1e0d | ||
|
|
e1b203727d | ||
|
|
cec8cc0573 | ||
|
|
7ca9fe0c63 | ||
|
|
b87a6c022f | ||
|
|
01b75a5094 | ||
|
|
2c6dcf0dd7 | ||
|
|
7994ae1da1 | ||
|
|
12237ae106 | ||
|
|
d8449fee24 | ||
|
|
37ec9911d9 | ||
|
|
36124d2aba | ||
|
|
5ecabaad3e | ||
|
|
56adfe6a35 | ||
|
|
4119a69e02 | ||
|
|
51de469551 | ||
|
|
87a360bfaf | ||
|
|
bdce4a7b4f | ||
|
|
0dedd48789 | ||
|
|
dfb7a5e227 | ||
|
|
d78bcd8b00 | ||
|
|
0cad87e1ed | ||
|
|
74b0594cf4 | ||
|
|
7fef4e5237 | ||
|
|
4a7c522eb5 | ||
|
|
8319bd3a85 | ||
|
|
5d3770ae8d | ||
|
|
3fa78ea3df | ||
|
|
4fbede0989 | ||
|
|
d7b19a4930 | ||
|
|
452bceff34 | ||
|
|
2ea36db5d6 | ||
|
|
737b6ce65a | ||
|
|
666faeb72a | ||
|
|
4f34483dee | ||
|
|
e3b927f112 | ||
|
|
d9220f1e15 | ||
|
|
f03e36e774 | ||
|
|
7c30390206 | ||
|
|
16906db309 | ||
|
|
d25ed7f2df | ||
|
|
51f5bec5a6 | ||
|
|
d3f3f93a24 | ||
|
|
24bd363ee0 | ||
|
|
504241a948 | ||
|
|
d2700556dd | ||
|
|
89c66de7c6 | ||
|
|
a2109b74ef | ||
|
|
4dc92451ea | ||
|
|
46a7a0b917 | ||
|
|
1ed5d703e6 | ||
|
|
cb986384db | ||
|
|
49c8131eb5 | ||
|
|
82bbd238fb | ||
|
|
03eb6e633e | ||
|
|
6e24aad094 | ||
|
|
aa6881e32e | ||
|
|
98e441f1e9 | ||
|
|
007bdff512 | ||
|
|
a3c77b3531 | ||
|
|
3e38884a6c | ||
|
|
40130696bb | ||
|
|
12a8c469e8 | ||
|
|
27cdd84b3b | ||
|
|
f6fd0cfe3f | ||
|
|
0641350575 | ||
|
|
5aed7a01d5 | ||
|
|
8442eef72b | ||
|
|
d3bc9f4870 | ||
|
|
6541f19b67 | ||
|
|
45709f7bd3 | ||
|
|
2d628e1cd0 | ||
|
|
ea599ba6e6 |
12
.github/FUNDING.yml
vendored
@@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [prometherion]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -40,4 +40,5 @@ you'd get this by running `kubectl -n capsule-system logs deploy/capsule-control
|
||||
# Additional context
|
||||
|
||||
- Capsule version: (`capsule --version`)
|
||||
- Helm Chart version: (`helm list -n capsule-system`)
|
||||
- Kubernetes version: (`kubectl version`)
|
||||
|
||||
18
.github/maintainers.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
- name: Adriano Pezzuto
|
||||
github: https://github.com/bsctl
|
||||
company: Clastix
|
||||
projects:
|
||||
- https://github.com/clastix/capsule
|
||||
- https://github.com/clastix/capsule-proxy
|
||||
- name: Dario Tranchitella
|
||||
github: https://github.com/prometherion
|
||||
company: Clastix
|
||||
projects:
|
||||
- https://github.com/clastix/capsule
|
||||
- https://github.com/clastix/capsule-proxy
|
||||
- name: Maksim Fedotov
|
||||
github: https://github.com/MaxFedotov
|
||||
company: wargaming.net
|
||||
projects:
|
||||
- https://github.com/clastix/capsule
|
||||
- https://github.com/clastix/capsule-proxy
|
||||
46
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
jobs:
|
||||
commit_lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: wagoid/commitlint-github-action@v2
|
||||
with:
|
||||
firstParent: true
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2.3.0
|
||||
with:
|
||||
version: v1.45.2
|
||||
only-new-issues: false
|
||||
args: --timeout 2m --config .golangci.yml
|
||||
diff:
|
||||
name: diff
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.18'
|
||||
- run: make installer
|
||||
- name: Checking if YAML installer file is not aligned
|
||||
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi
|
||||
- name: Checking if YAML installer generated untracked files
|
||||
run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)"
|
||||
- name: Checking if source code is not formatted
|
||||
run: test -z "$(git diff 2> /dev/null)"
|
||||
97
.github/workflows/docker-ci.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: docker-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
docker-ci:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Generate build-args
|
||||
id: build-args
|
||||
run: |
|
||||
# Declare vars for internal use
|
||||
VERSION=$(git describe --abbrev=0 --tags)
|
||||
GIT_HEAD_COMMIT=$(git rev-parse --short HEAD)
|
||||
GIT_TAG_COMMIT=$(git rev-parse --short $VERSION)
|
||||
GIT_MODIFIED_1=$(git diff $GIT_HEAD_COMMIT $GIT_TAG_COMMIT --quiet && echo "" || echo ".dev")
|
||||
GIT_MODIFIED_2=$(git diff --quiet && echo "" || echo ".dirty")
|
||||
# Export to GH_ENV
|
||||
echo "GIT_LAST_TAG=$VERSION" >> $GITHUB_ENV
|
||||
echo "GIT_HEAD_COMMIT=$GIT_HEAD_COMMIT" >> $GITHUB_ENV
|
||||
echo "GIT_TAG_COMMIT=$GIT_TAG_COMMIT" >> $GITHUB_ENV
|
||||
echo "GIT_MODIFIED=$(echo "$GIT_MODIFIED_1""$GIT_MODIFIED_2")" >> $GITHUB_ENV
|
||||
echo "GIT_REPO=$(git config --get remote.origin.url)" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(git log -1 --format="%at" | xargs -I{} date -d @{} +%Y-%m-%dT%H:%M:%S)" >> $GITHUB_ENV
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
quay.io/${{ github.repository }}
|
||||
docker.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=semver,pattern={{raw}}
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm64,arm
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Inspect builder
|
||||
run: |
|
||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
||||
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
|
||||
echo "Status: ${{ steps.buildx.outputs.status }}"
|
||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
|
||||
- name: Login to quay.io Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ github.repository_owner }}+github
|
||||
password: ${{ secrets.BOT_QUAY_IO }}
|
||||
|
||||
- name: Login to docker.io Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.USER_DOCKER_IO }}
|
||||
password: ${{ secrets.BOT_DOCKER_IO }}
|
||||
|
||||
- name: Build and push
|
||||
id: build-release
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
GIT_HEAD_COMMIT=${{ env.GIT_HEAD_COMMIT }}
|
||||
GIT_TAG_COMMIT=${{ env.GIT_TAG_COMMIT }}
|
||||
GIT_REPO=${{ env.GIT_REPO }}
|
||||
GIT_LAST_TAG=${{ env.GIT_LAST_TAG }}
|
||||
GIT_MODIFIED=${{ env.GIT_MODIFIED }}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.build-release.outputs.digest }}
|
||||
57
.github/workflows/e2e.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: e2e
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
paths:
|
||||
- '.github/workflows/e2e.yml'
|
||||
- 'api/**'
|
||||
- 'controllers/**'
|
||||
- 'pkg/**'
|
||||
- 'e2e/*'
|
||||
- 'Dockerfile'
|
||||
- 'go.*'
|
||||
- 'main.go'
|
||||
- 'Makefile'
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
paths:
|
||||
- '.github/workflows/e2e.yml'
|
||||
- 'api/**'
|
||||
- 'controllers/**'
|
||||
- 'pkg/**'
|
||||
- 'e2e/*'
|
||||
- 'Dockerfile'
|
||||
- 'go.*'
|
||||
- 'main.go'
|
||||
- 'Makefile'
|
||||
|
||||
jobs:
|
||||
kind:
|
||||
name: Kubernetes
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.4', 'v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.1']
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.18'
|
||||
- run: make manifests
|
||||
- name: Checking if manifests are disaligned
|
||||
run: test -z "$(git diff 2> /dev/null)"
|
||||
- name: Checking if manifests generated untracked files
|
||||
run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)"
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
skipClusterCreation: true
|
||||
version: v0.14.0
|
||||
- uses: azure/setup-helm@v1
|
||||
with:
|
||||
version: 3.3.4
|
||||
- name: e2e testing
|
||||
run: make e2e/${{ matrix.k8s-version }}
|
||||
18
.github/workflows/gosec.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: CI gosec
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: on
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
- name: Run Gosec Security Scanner
|
||||
uses: securego/gosec@master
|
||||
with:
|
||||
args: ./...
|
||||
36
.github/workflows/helm.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Helm Chart
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
tags: [ "helm-v*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: azure/setup-helm@v1
|
||||
with:
|
||||
version: 3.3.4
|
||||
- name: Linting Chart
|
||||
run: helm lint ./charts/capsule
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/helm-v')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Publish Helm chart
|
||||
uses: stefanprodan/helm-gh-pages@master
|
||||
with:
|
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
charts_dir: charts
|
||||
charts_url: https://clastix.github.io/charts
|
||||
owner: clastix
|
||||
repository: charts
|
||||
branch: gh-pages
|
||||
target_dir: .
|
||||
commit_username: prometherion
|
||||
commit_email: dario@tranchitella.eu
|
||||
61
.github/workflows/main.yml
vendored
@@ -1,61 +0,0 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2.2.0
|
||||
with:
|
||||
# version of golangci-lint to use in form of v1.2.3
|
||||
version: latest
|
||||
# if set to true and the action runs on a pull request - the action outputs only newly found issues
|
||||
only-new-issues: false
|
||||
args: --timeout 2m
|
||||
kind:
|
||||
name: e2e
|
||||
strategy:
|
||||
matrix:
|
||||
k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.1']
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Cache Go modules and Docker images
|
||||
uses: actions/cache@v1
|
||||
env:
|
||||
cache-name: gomod-docker
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
/var/lib/docker
|
||||
/home/runner/work/capsule/capsule
|
||||
key: ${{ matrix.k8s-version }}-build-${{ env.cache-name }}
|
||||
restore-keys: |
|
||||
${{ matrix.k8s-version }}-build-
|
||||
${{ matrix.k8s-version }}-
|
||||
- name: Removing kustomize
|
||||
run: sudo snap remove kustomize && sudo rm -rf $(which kustomize)
|
||||
- name: Installing Ginkgo
|
||||
run: go get github.com/onsi/ginkgo/ginkgo
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.13.8'
|
||||
- uses: engineerd/setup-kind@v0.4.0
|
||||
with:
|
||||
skipClusterCreation: true
|
||||
- name: e2e testing
|
||||
run: make e2e/${{ matrix.k8s-version }}
|
||||
8
.gitignore
vendored
@@ -22,5 +22,11 @@ bin
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.vscode
|
||||
|
||||
**/*.kubeconfig
|
||||
**/*.crt
|
||||
**/*.key
|
||||
.DS_Store
|
||||
*.tgz
|
||||
|
||||
hack/*.kubeconfig
|
||||
|
||||
47
.golangci.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
dupl:
|
||||
threshold: 100
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
cyclop:
|
||||
max-complexity: 27
|
||||
gocognit:
|
||||
min-complexity: 50
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/clastix/capsule)
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- lll
|
||||
- exhaustivestruct
|
||||
- maligned
|
||||
- interfacer
|
||||
- scopelint
|
||||
- golint
|
||||
- gochecknoglobals
|
||||
- goerr113
|
||||
- gomnd
|
||||
- paralleltest
|
||||
- ireturn
|
||||
- testpackage
|
||||
- varnamelen
|
||||
- wrapcheck
|
||||
|
||||
issues:
|
||||
exclude:
|
||||
- Using the variable on range scope .* in function literal
|
||||
|
||||
service:
|
||||
golangci-lint-version: 1.33.x
|
||||
|
||||
run:
|
||||
skip-files:
|
||||
- "zz_.*\\.go$"
|
||||
5
ADOPTERS.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Adopters
|
||||
|
||||
This is a list of companies that have adopted Capsule, feel free to open a Pull-Request to get yours listed.
|
||||
|
||||
## Adopters list (alphabetically)
|
||||
19
Dockerfile
@@ -1,5 +1,13 @@
|
||||
# Build the manager binary
|
||||
FROM golang:1.13 as builder
|
||||
FROM golang:1.18 as builder
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG GIT_HEAD_COMMIT
|
||||
ARG GIT_TAG_COMMIT
|
||||
ARG GIT_LAST_TAG
|
||||
ARG GIT_MODIFIED
|
||||
ARG GIT_REPO
|
||||
ARG BUILD_DATE
|
||||
|
||||
WORKDIR /workspace
|
||||
# Copy the Go Modules manifests
|
||||
@@ -11,15 +19,16 @@ RUN go mod download
|
||||
|
||||
# Copy the go source
|
||||
COPY main.go main.go
|
||||
COPY version.go version.go
|
||||
COPY api/ api/
|
||||
COPY controllers/ controllers/
|
||||
COPY pkg/ pkg/
|
||||
COPY version/ version/
|
||||
|
||||
ARG VERSION
|
||||
|
||||
# Build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -ldflags "-X github.com/clastix/capsule/version.Version=${VERSION}" -o manager main.go
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH GO111MODULE=on go build \
|
||||
-gcflags "-N -l" \
|
||||
-ldflags "-X main.GitRepo=$GIT_REPO -X main.GitTag=$GIT_LAST_TAG -X main.GitCommit=$GIT_HEAD_COMMIT -X main.GitDirty=$GIT_MODIFIED -X main.BuildTime=$BUILD_DATE" \
|
||||
-o manager
|
||||
|
||||
# Use distroless as minimal base image to package the manager binary
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
|
||||
194
Makefile
@@ -1,8 +1,8 @@
|
||||
# Current Operator version
|
||||
VERSION ?= 0.0.1
|
||||
VERSION ?= $$(git describe --abbrev=0 --tags --match "v*")
|
||||
|
||||
# Default bundle image tag
|
||||
BUNDLE_IMG ?= quay.io/clastix/capsule:$(VERSION)-bundle
|
||||
BUNDLE_IMG ?= clastix/capsule:$(VERSION)-bundle
|
||||
# Options for 'bundle-build'
|
||||
ifneq ($(origin CHANNELS), undefined)
|
||||
BUNDLE_CHANNELS := --channels=$(CHANNELS)
|
||||
@@ -13,9 +13,9 @@ endif
|
||||
BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= quay.io/clastix/capsule:$(VERSION)
|
||||
IMG ?= clastix/capsule:$(VERSION)
|
||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||
CRD_OPTIONS ?= "crd:trivialVersions=true"
|
||||
CRD_OPTIONS ?= "crd:preserveUnknownFields=false"
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
@@ -24,94 +24,156 @@ else
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
# Get information about git current status
|
||||
GIT_HEAD_COMMIT ?= $$(git rev-parse --short HEAD)
|
||||
GIT_TAG_COMMIT ?= $$(git rev-parse --short $(VERSION))
|
||||
GIT_MODIFIED_1 ?= $$(git diff $(GIT_HEAD_COMMIT) $(GIT_TAG_COMMIT) --quiet && echo "" || echo ".dev")
|
||||
GIT_MODIFIED_2 ?= $$(git diff --quiet && echo "" || echo ".dirty")
|
||||
GIT_MODIFIED ?= $$(echo "$(GIT_MODIFIED_1)$(GIT_MODIFIED_2)")
|
||||
GIT_REPO ?= $$(git config --get remote.origin.url)
|
||||
BUILD_DATE ?= $$(git log -1 --format="%at" | xargs -I{} date -d @{} +%Y-%m-%dT%H:%M:%S)
|
||||
|
||||
all: manager
|
||||
|
||||
# Run tests
|
||||
test: generate fmt vet manifests
|
||||
test: generate manifests
|
||||
go test ./... -coverprofile cover.out
|
||||
|
||||
# Build manager binary
|
||||
manager: generate fmt vet
|
||||
go build -o bin/manager main.go
|
||||
manager: generate golint
|
||||
go build -o bin/manager
|
||||
|
||||
# Run against the configured Kubernetes cluster in ~/.kube/config
|
||||
run: generate fmt vet manifests
|
||||
go run ./main.go
|
||||
run: generate manifests
|
||||
go run .
|
||||
|
||||
# Creates the single file to install Capsule without any external dependency
|
||||
installer: manifests kustomize
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default > config/install.yaml
|
||||
|
||||
# Install CRDs into a cluster
|
||||
install: manifests kustomize
|
||||
install: installer
|
||||
$(KUSTOMIZE) build config/crd | kubectl apply -f -
|
||||
|
||||
# Uninstall CRDs from a cluster
|
||||
uninstall: manifests kustomize
|
||||
uninstall: installer
|
||||
$(KUSTOMIZE) build config/crd | kubectl delete -f -
|
||||
|
||||
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
|
||||
deploy: manifests kustomize
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default | kubectl apply -f -
|
||||
deploy: installer
|
||||
kubectl apply -f config/install.yaml
|
||||
|
||||
# Remove controller in the configured Kubernetes cluster in ~/.kube/config
|
||||
remove: manifests kustomize
|
||||
$(KUSTOMIZE) build config/default | kubectl delete -f -
|
||||
remove: installer
|
||||
kubectl delete -f config/install.yaml
|
||||
kubectl delete clusterroles.rbac.authorization.k8s.io capsule-namespace-deleter capsule-namespace-provisioner --ignore-not-found
|
||||
kubectl delete clusterrolebindings.rbac.authorization.k8s.io capsule-namespace-provisioner --ignore-not-found
|
||||
kubectl delete clusterrolebindings.rbac.authorization.k8s.io capsule-namespace-deleter capsule-namespace-provisioner --ignore-not-found
|
||||
|
||||
# Generate manifests e.g. CRD, RBAC etc.
|
||||
manifests: controller-gen
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
|
||||
# Run go fmt against code
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
# Run go vet against code
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
# Generate code
|
||||
generate: controller-gen
|
||||
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
# Setup development env
|
||||
# Usage:
|
||||
# LAPTOP_HOST_IP=<YOUR_LAPTOP_IP> make dev-setup
|
||||
# For example:
|
||||
# LAPTOP_HOST_IP=192.168.10.101 make dev-setup
|
||||
define TLS_CNF
|
||||
[ req ]
|
||||
default_bits = 4096
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
[ req_distinguished_name ]
|
||||
countryName = SG
|
||||
stateOrProvinceName = SG
|
||||
localityName = SG
|
||||
organizationName = CAPSULE
|
||||
commonName = CAPSULE
|
||||
[ req_ext ]
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = $(LAPTOP_HOST_IP)
|
||||
endef
|
||||
export TLS_CNF
|
||||
dev-setup:
|
||||
kubectl -n capsule-system scale deployment capsule-controller-manager --replicas=0
|
||||
mkdir -p /tmp/k8s-webhook-server/serving-certs
|
||||
echo "$${TLS_CNF}" > _tls.cnf
|
||||
openssl req -newkey rsa:4096 -days 3650 -nodes -x509 \
|
||||
-subj "/C=SG/ST=SG/L=SG/O=CAPSULE/CN=CAPSULE" \
|
||||
-extensions req_ext \
|
||||
-config _tls.cnf \
|
||||
-keyout /tmp/k8s-webhook-server/serving-certs/tls.key \
|
||||
-out /tmp/k8s-webhook-server/serving-certs/tls.crt
|
||||
rm -f _tls.cnf
|
||||
export WEBHOOK_URL="https://$${LAPTOP_HOST_IP}:9443"; \
|
||||
export CA_BUNDLE=`openssl base64 -in /tmp/k8s-webhook-server/serving-certs/tls.crt | tr -d '\n'`; \
|
||||
kubectl patch MutatingWebhookConfiguration capsule-mutating-webhook-configuration \
|
||||
--type='json' -p="[\
|
||||
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/mutate-v1-namespace-owner-reference\",'caBundle':\"$${CA_BUNDLE}\"}}\
|
||||
]" && \
|
||||
kubectl patch ValidatingWebhookConfiguration capsule-validating-webhook-configuration \
|
||||
--type='json' -p="[\
|
||||
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/cordoning\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/ingresses\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespaces\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/networkpolicies\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/4/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/pods\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/nodes\",'caBundle':\"$${CA_BUNDLE}\"}}\
|
||||
]";
|
||||
|
||||
# Build the docker image
|
||||
docker-build: test
|
||||
docker build . --build-arg=VERSION=${VERSION} -t ${IMG}
|
||||
docker build . -t ${IMG} --build-arg GIT_HEAD_COMMIT=$(GIT_HEAD_COMMIT) \
|
||||
--build-arg GIT_TAG_COMMIT=$(GIT_TAG_COMMIT) \
|
||||
--build-arg GIT_MODIFIED=$(GIT_MODIFIED) \
|
||||
--build-arg GIT_REPO=$(GIT_REPO) \
|
||||
--build-arg GIT_LAST_TAG=$(VERSION) \
|
||||
--build-arg BUILD_DATE=$(BUILD_DATE)
|
||||
|
||||
# Push the docker image
|
||||
docker-push:
|
||||
docker push ${IMG}
|
||||
|
||||
# find or download controller-gen
|
||||
# download controller-gen if necessary
|
||||
controller-gen:
|
||||
ifeq (, $(shell which controller-gen))
|
||||
@{ \
|
||||
set -e ;\
|
||||
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0 ;\
|
||||
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
}
|
||||
CONTROLLER_GEN=$(GOBIN)/controller-gen
|
||||
else
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
endif
|
||||
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
|
||||
controller-gen: ## Download controller-gen locally if necessary.
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0)
|
||||
|
||||
kustomize:
|
||||
ifeq (, $(shell which kustomize))
|
||||
@{ \
|
||||
set -e ;\
|
||||
KUSTOMIZE_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$KUSTOMIZE_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/kustomize/kustomize/v3@v3.5.4 ;\
|
||||
rm -rf $$KUSTOMIZE_GEN_TMP_DIR ;\
|
||||
}
|
||||
KUSTOMIZE=$(GOBIN)/kustomize
|
||||
else
|
||||
KUSTOMIZE=$(shell which kustomize)
|
||||
endif
|
||||
GINKGO = $(shell pwd)/bin/ginkgo
|
||||
ginkgo: ## Download ginkgo locally if necessary.
|
||||
$(call go-install-tool,$(KUSTOMIZE),github.com/onsi/ginkgo/ginkgo@v1.16.5)
|
||||
|
||||
KUSTOMIZE = $(shell pwd)/bin/kustomize
|
||||
kustomize: ## Download kustomize locally if necessary.
|
||||
$(call install-kustomize,$(KUSTOMIZE),3.8.7)
|
||||
|
||||
define install-kustomize
|
||||
@[ -f $(1) ] || { \
|
||||
set -e ;\
|
||||
echo "Installing v$(2)" ;\
|
||||
cd bin ;\
|
||||
wget "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" ;\
|
||||
bash ./install_kustomize.sh $(2) ;\
|
||||
}
|
||||
endef
|
||||
|
||||
# go-install-tool will 'go install' any package $2 and install it to $1.
|
||||
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
define go-install-tool
|
||||
@[ -f $(1) ] || { \
|
||||
set -e ;\
|
||||
echo "Installing $(2)" ;\
|
||||
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
|
||||
}
|
||||
endef
|
||||
|
||||
# Generate bundle manifests and metadata, then validate generated files.
|
||||
bundle: manifests
|
||||
@@ -131,15 +193,25 @@ goimports:
|
||||
# Linting code as PR is expecting
|
||||
.PHONY: golint
|
||||
golint:
|
||||
golangci-lint run
|
||||
golangci-lint run -c .golangci.yml
|
||||
|
||||
# Running e2e tests in a KinD instance
|
||||
.PHONY: e2e
|
||||
e2e/%:
|
||||
e2e/%: ginkgo
|
||||
kind create cluster --name capsule --image=kindest/node:$*
|
||||
make docker-build
|
||||
kind load docker-image --nodes capsule-control-plane --name capsule $(IMG)
|
||||
make deploy
|
||||
while [ -z $$(kubectl -n capsule-system get secret capsule-tls -o jsonpath='{.data.tls\.crt}') ]; do echo "waiting Capsule to be up and running..." && sleep 5; done
|
||||
ginkgo -v -tags e2e ./e2e
|
||||
helm upgrade \
|
||||
--debug \
|
||||
--install \
|
||||
--namespace capsule-system \
|
||||
--create-namespace \
|
||||
--set 'manager.image.pullPolicy=Never' \
|
||||
--set 'manager.resources=null'\
|
||||
--set "manager.image.tag=$(VERSION)" \
|
||||
--set 'manager.livenessProbe.failureThreshold=10' \
|
||||
--set 'manager.readinessProbe.failureThreshold=10' \
|
||||
capsule \
|
||||
./charts/capsule
|
||||
$(GINKGO) -v -tags e2e ./e2e
|
||||
kind delete cluster --name capsule
|
||||
|
||||
41
PROJECT
@@ -1,10 +1,39 @@
|
||||
domain: github.com/clastix/capsule
|
||||
layout: go.kubebuilder.io/v2
|
||||
domain: clastix.io
|
||||
layout:
|
||||
- go.kubebuilder.io/v3
|
||||
plugins:
|
||||
manifests.sdk.operatorframework.io/v2: {}
|
||||
scorecard.sdk.operatorframework.io/v2: {}
|
||||
projectName: capsule
|
||||
repo: github.com/clastix/capsule
|
||||
resources:
|
||||
- group: capsule.clastix.io
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: false
|
||||
controller: true
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: Tenant
|
||||
path: github.com/clastix/capsule/api/v1alpha1
|
||||
version: v1alpha1
|
||||
version: 3-alpha
|
||||
plugins:
|
||||
go.operator-sdk.io/v2-alpha: {}
|
||||
webhooks:
|
||||
conversion: true
|
||||
webhookVersion: v1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: false
|
||||
controller: true
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: CapsuleConfiguration
|
||||
path: github.com/clastix/capsule/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: false
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: Tenant
|
||||
path: github.com/clastix/capsule/api/v1beta1
|
||||
version: v1beta1
|
||||
version: "3"
|
||||
|
||||
205
README.md
@@ -1,162 +1,115 @@
|
||||
# Capsule
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/logo/space-capsule3.png" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<p align="left">
|
||||
<img src="https://img.shields.io/github/license/clastix/capsule"/>
|
||||
<img src="https://img.shields.io/github/go-mod/go-version/clastix/capsule"/>
|
||||
<a href="https://github.com/clastix/capsule/releases">
|
||||
<img src="https://img.shields.io/github/v/release/clastix/capsule"/>
|
||||
</a>
|
||||
<a href="https://charmhub.io/capsule-k8s">
|
||||
<img src="https://charmhub.io/capsule-k8s/badge.svg"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/logo/capsule_medium.png" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
**Join the community** on the [#capsule](https://kubernetes.slack.com/archives/C03GETTJQRL) channel in the [Kubernetes Slack](https://slack.k8s.io/).
|
||||
|
||||
# Kubernetes multi-tenancy made easy
|
||||
|
||||
# A multi-tenant operator for Kubernetes
|
||||
This project provides a custom operator for implementing a strong
|
||||
multi-tenant environment in _Kubernetes_. **Capsule** is not intended to be yet another _PaaS_, instead, it has been designed as a lightweight tool with a minimalist approach leveraging only the standard features of upstream Kubernetes.
|
||||
**Capsule** implements a multi-tenant and policy-based environment in your Kubernetes cluster. It is designed as a micro-services-based ecosystem with the minimalist approach, leveraging only on upstream Kubernetes.
|
||||
|
||||
# Which is the problem to solve?
|
||||
Kubernetes introduced the _namespace_ resource to create logical partitions of the
|
||||
cluster. A Kubernetes namespace creates a sort of isolated *slice* in the
|
||||
cluster: _Network and Security Policies_, _Resource Quota_, _Limit Ranges_, and
|
||||
_RBAC_ can be used to enforce isolation among different namespaces. Namespace isolation shines when Kubernetes is used to isolate the different environments or the different types of applications. Also, it works well to isolate applications serving different users when implementing the SaaS delivery model.
|
||||
# What's the problem with the current status?
|
||||
|
||||
However, implementing advanced multi-tenancy scenarios, for example, a private or public _Container-as-a-Service_ platform, it becomes soon complicated because of the flat structure of Kubernetes namespaces. In such scenarios, different groups of users get assigned a pool of namespaces with a limited amount of resources (e.g.: _nodes_, _vCPU_, _RAM_, _ephemeral and persistent storage_). When users need more namespaces or move resources from one namespace to another, they always need the intervention of the cluster admin because each namespace still works as an isolated environment. To work around this, and not being overwhelmed by continuous users' requests, cluster admins often choose to create multiple smaller clusters and assign a dedicated cluster to each organization or group of users leading to the well know and painful phenomena of the _clusters sprawl_.
|
||||
Kubernetes introduces the _Namespace_ object type to create logical partitions of the cluster as isolated *slices*. However, implementing advanced multi-tenancy scenarios, it soon becomes complicated because of the flat structure of Kubernetes namespaces and the impossibility to share resources among namespaces belonging to the same tenant. To overcome this, cluster admins tend to provision a dedicated cluster for each groups of users, teams, or departments. As an organization grows, the number of clusters to manage and keep aligned becomes an operational nightmare, described as the well known phenomena of the _clusters sprawl_.
|
||||
|
||||
**Capsule** takes a different approach. It aggregates multiple namespaces assigned to an organization or group of users in a lightweight abstraction called _Tenant_. Within each tenant, users are free to create their namespaces and share all the assigned resources between the namespaces of the tenant. The _Network and Security Policies_, _Resource Quota_, _Limit Ranges_, _RBAC_, and other constraints defined at the tenant level are automatically inherited by all the namespaces in the tenant leaving the tenant's users to freely allocate resources without any intervention of the cluster administrator.
|
||||
# Entering Capsule
|
||||
|
||||
# Use cases for Capsule
|
||||
Please, refer to the corresponding [section](use_cases.md) for a more detailed list of use cases that Capsule can address.
|
||||
Capsule takes a different approach. In a single cluster, the Capsule Controller aggregates multiple namespaces in a lightweight abstraction called _Tenant_, basically a grouping of Kubernetes Namespaces. Within each tenant, users are free to create their namespaces and share all the assigned resources.
|
||||
|
||||
# Installation
|
||||
Make sure you have access to a Kubernetes cluster as an administrator.
|
||||
On the other side, the Capsule Policy Engine keeps the different tenants isolated from each other. _Network and Security Policies_, _Resource Quota_, _Limit Ranges_, _RBAC_, and other policies defined at the tenant level are automatically inherited by all the namespaces in the tenant. Then users are free to operate their tenants in autonomy, without the intervention of the cluster administrator.
|
||||
|
||||
There are two ways to install Capsule:
|
||||
# Features
|
||||
|
||||
* Use the Helm Chart available [here](https://github.com/clastix/capsule-helm-chart)
|
||||
* Use [`kustomize`](https://github.com/kubernetes-sigs/kustomize)
|
||||
## Self-Service
|
||||
|
||||
## Install with kustomize
|
||||
Ensure you have `kubectl` and `kustomize` installed in your `PATH`.
|
||||
Leave developers the freedom to self-provision their cluster resources according to the assigned boundaries.
|
||||
|
||||
Clone this repository and move to the repo folder:
|
||||
## Preventing Clusters Sprawl
|
||||
|
||||
Share a single cluster with multiple teams, groups of users, or departments by saving operational and management efforts.
|
||||
|
||||
## Governance
|
||||
|
||||
Leverage Kubernetes Admission Controllers to enforce the industry security best practices and meet policy requirements.
|
||||
|
||||
## Resources Control
|
||||
|
||||
Take control of the resources consumed by users while preventing them to overtake.
|
||||
|
||||
## Native Experience
|
||||
|
||||
Provide multi-tenancy with a native Kubernetes experience without introducing additional management layers, plugins, or customized binaries.
|
||||
|
||||
## GitOps ready
|
||||
|
||||
Capsule is completely declarative and GitOps ready.
|
||||
|
||||
## Bring your own device (BYOD)
|
||||
|
||||
Assign to tenants a dedicated set of compute, storage, and network resources and avoid the noisy neighbors' effect.
|
||||
|
||||
# Documentation
|
||||
|
||||
Please, check the project [documentation](https://capsule.clastix.io) for the cool things you can do with Capsule.
|
||||
|
||||
# Contributions
|
||||
|
||||
Capsule is Open Source with Apache 2 license and any contribution is welcome.
|
||||
|
||||
## Chart Development
|
||||
|
||||
The documentation for each chart is done with [helm-docs](https://github.com/norwoodj/helm-docs). This way we can ensure that values are consistent with the chart documentation.
|
||||
|
||||
We have a script on the repository which will execute the helm-docs docker container, so that you don't have to worry about downloading the binary etc. Simply execute the script (Bash compatible):
|
||||
|
||||
```
|
||||
make deploy
|
||||
# /home/prometherion/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
# cd config/manager && /usr/local/bin/kustomize edit set image controller=quay.io/clastix/capsule:latest
|
||||
# /usr/local/bin/kustomize build config/default | kubectl apply -f -
|
||||
# namespace/capsule-system created
|
||||
# customresourcedefinition.apiextensions.k8s.io/tenants.capsule.clastix.io created
|
||||
# clusterrole.rbac.authorization.k8s.io/capsule-proxy-role created
|
||||
# clusterrole.rbac.authorization.k8s.io/capsule-metrics-reader created
|
||||
# clusterrolebinding.rbac.authorization.k8s.io/capsule-manager-rolebinding created
|
||||
# clusterrolebinding.rbac.authorization.k8s.io/capsule-proxy-rolebinding created
|
||||
# secret/capsule-ca created
|
||||
# secret/capsule-tls created
|
||||
# service/capsule-controller-manager-metrics-service created
|
||||
# service/capsule-webhook-service created
|
||||
# deployment.apps/capsule-controller-manager created
|
||||
# mutatingwebhookconfiguration.admissionregistration.k8s.io/capsule-mutating-webhook-configuration created
|
||||
# validatingwebhookconfiguration.admissionregistration.k8s.io/capsule-validating-webhook-configuration created
|
||||
bash scripts/helm-docs.sh
|
||||
```
|
||||
|
||||
Log verbosity of the Capsule controller can be increased by passing the `--zap-log-level` option with a value from `1` to `10` or the [basic keywords](https://godoc.org/go.uber.org/zap/zapcore#Level) although it is suggested to use the `--zap-devel` flag to get also stack traces.
|
||||
## Community
|
||||
|
||||
During startup Capsule controller will create additional ClusterRoles `capsule-namespace-deleter`, `capsule-namespace-provisioner` and ClusterRoleBinding `capsule-namespace-provisioner`. These resources are used in order to allow Capsule users to manage their namespaces in tenants.
|
||||
Join the community, share and learn from it. You can find all the resources to how to contribute code and docs, connect with people in the [community repository](https://github.com/clastix/capsule-community).
|
||||
|
||||
You can disallow users to create namespaces matching a particular regexp by passing `--protected-namespace-regex` option with a value of regular expression.
|
||||
## Adopters
|
||||
|
||||
## Admission Controllers
|
||||
Capsule implements Kubernetes multi-tenancy capabilities using a minimum set of standard [Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) enabled on the Kubernetes APIs server: `--enable-admission-plugins=PodNodeSelector,LimitRanger,ResourceQuota,MutatingAdmissionWebhook,ValidatingAdmissionWebhook`. In addition to these default controllers, Capsule implements its own set of Admission Controllers through the [Dynamic Admission Controller](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/), providing callbacks to add further validation or resource patching.
|
||||
See the [ADOPTERS.md](ADOPTERS.md) file for a list of companies that are using Capsule.
|
||||
|
||||
All these requests must be served via HTTPS and a CA must be provided to ensure that
|
||||
the API Server is communicating with the right client. Capsule upon installation is setting its custom Certificate Authority as a client certificate as well, updating all the required resources to minimize the operational tasks.
|
||||
# Governance
|
||||
|
||||
## Tenant users
|
||||
Each tenant comes with a delegated user acting as the tenant admin. In the Capsule jargon, this user is called the _Tenant Owner_. Other users can operate inside a tenant with different levels of permissions and authorizations assigned directly by the Tenant owner.
|
||||
You can find how the Capsule project is governed [here](https://capsule.clastix.io/docs/contributing/governance).
|
||||
|
||||
Capsule does not care about the authentication strategy used in the cluster and all the Kubernetes methods of [authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/) are supported. The only requirement to use Capsule is to assign tenant users to the the group defined by `--capsule-user-group` option, which defaults to `capsule.clastix.io`.
|
||||
## Maintainers
|
||||
|
||||
Assignment to a group depends on the authentication strategy in your cluster. For example, if you are using `capsule.clastix.io` as your `--capsule-user-group`, users authenticated through a _X.509_ certificate must have `capsule.clastix.io` as _Organization_: `-subj "/CN=${USER}/O=capsule.clastix.io"`
|
||||
|
||||
Users authenticated through an _OIDC token_ must have
|
||||
|
||||
```json
|
||||
...
|
||||
"users_groups": [
|
||||
"/capsule.clastix.io",
|
||||
"other_group"
|
||||
]
|
||||
```
|
||||
|
||||
in their token.
|
||||
|
||||
The [hack/create-user.sh](hack/create-user.sh) can help you set up a dummy `kubeconfig` for the `alice` user acting as owner of a tenant called `oil`
|
||||
|
||||
```bash
|
||||
./hack/create-user.sh alice oil
|
||||
creating certs in TMPDIR /tmp/tmp.4CLgpuime3
|
||||
Generating RSA private key, 2048 bit long modulus (2 primes)
|
||||
............+++++
|
||||
........................+++++
|
||||
e is 65537 (0x010001)
|
||||
certificatesigningrequest.certificates.k8s.io/alice-oil created
|
||||
certificatesigningrequest.certificates.k8s.io/alice-oil approved
|
||||
kubeconfig file is: alice-oil.kubeconfig
|
||||
to use it as alice export KUBECONFIG=alice-oil.kubeconfig
|
||||
```
|
||||
|
||||
## How to create a Tenant
|
||||
Use the [scaffold Tenant](config/samples/capsule_v1alpha1_tenant.yaml)
|
||||
and simply apply as Cluster Admin.
|
||||
|
||||
```
|
||||
kubectl apply -f config/samples/capsule_v1alpha1_tenant.yaml
|
||||
tenant.capsule.clastix.io/oil created
|
||||
```
|
||||
|
||||
The related Tenant owner `alice` can create Namespaces according to their assigned quota: happy Kubernetes cluster administration!
|
||||
|
||||
# Removal
|
||||
Similar to `deploy`, you can get rid of Capsule using the `remove` target.
|
||||
|
||||
```
|
||||
make remove
|
||||
# /home/prometherion/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
# /usr/local/bin/kustomize build config/default | kubectl delete -f -
|
||||
# namespace "capsule-system" deleted
|
||||
# customresourcedefinition.apiextensions.k8s.io "tenants.capsule.clastix.io" deleted
|
||||
# clusterrole.rbac.authorization.k8s.io "capsule-proxy-role" deleted
|
||||
# clusterrole.rbac.authorization.k8s.io "capsule-metrics-reader" deleted
|
||||
# clusterrolebinding.rbac.authorization.k8s.io "capsule-manager-rolebinding" deleted
|
||||
# clusterrolebinding.rbac.authorization.k8s.io "capsule-proxy-rolebinding" deleted
|
||||
# secret "capsule-ca" deleted
|
||||
# secret "capsule-tls" deleted
|
||||
# service "capsule-controller-manager-metrics-service" deleted
|
||||
# service "capsule-webhook-service" deleted
|
||||
# deployment.apps "capsule-controller-manager" deleted
|
||||
# mutatingwebhookconfiguration.admissionregistration.k8s.io "capsule-mutating-webhook-configuration" deleted
|
||||
# validatingwebhookconfiguration.admissionregistration.k8s.io "capsule-validating-webhook-configuration" deleted
|
||||
```
|
||||
|
||||
# How to contribute
|
||||
Any contribution is welcome! Please refer to the corresponding [section](contributing.md).
|
||||
|
||||
# Production Grade
|
||||
|
||||
Although under frequent development and improvements, Capsule is ready to be used in production environments: check out the **Release** page for a detailed list of available versions.
|
||||
Please, refer to the maintainers file available [here](.github/maintainers.yaml).
|
||||
|
||||
# FAQ
|
||||
tbd
|
||||
|
||||
# Changelog
|
||||
tbd
|
||||
- Q. How to pronounce Capsule?
|
||||
|
||||
# Roadmap
|
||||
tbd
|
||||
A. It should be pronounced as `/ˈkæpsjuːl/`.
|
||||
|
||||
- Q. Is it production grade?
|
||||
|
||||
A. Although under frequent development and improvements, Capsule is ready to be used in production environments as currently, people are using it in public and private deployments. Check out the [release](https://github.com/clastix/capsule/releases) page for a detailed list of available versions.
|
||||
|
||||
- Q. Does it work with my Kubernetes XYZ distribution?
|
||||
|
||||
A. We tested Capsule with vanilla Kubernetes 1.16+ on private environments and public clouds. We expect it to work smoothly on any other Kubernetes distribution. Please, let us know if you find it doesn't.
|
||||
|
||||
- Q. Do you provide commercial support?
|
||||
|
||||
A. Yes, we're available to help and provide commercial support. [Clastix](https://clastix.io) is the company behind Capsule. Please, contact us for a quote.
|
||||
|
||||
9
api/v1alpha1/additional_metadata.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
type AdditionalMetadataSpec struct {
|
||||
AdditionalLabels map[string]string `json:"additionalLabels,omitempty"`
|
||||
AdditionalAnnotations map[string]string `json:"additionalAnnotations,omitempty"`
|
||||
}
|
||||
12
api/v1alpha1/additional_role_bindings.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import rbacv1 "k8s.io/api/rbac/v1"
|
||||
|
||||
type AdditionalRoleBindingsSpec struct {
|
||||
ClusterRoleName string `json:"clusterRoleName"`
|
||||
// kubebuilder:validation:Minimum=1
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
}
|
||||
37
api/v1alpha1/allowed_list.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AllowedListSpec struct {
|
||||
Exact []string `json:"allowed,omitempty"`
|
||||
Regex string `json:"allowedRegex,omitempty"`
|
||||
}
|
||||
|
||||
func (in *AllowedListSpec) ExactMatch(value string) (ok bool) {
|
||||
if len(in.Exact) > 0 {
|
||||
sort.SliceStable(in.Exact, func(i, j int) bool {
|
||||
return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j])
|
||||
})
|
||||
|
||||
i := sort.SearchStrings(in.Exact, value)
|
||||
|
||||
ok = i < len(in.Exact) && in.Exact[i] == value
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (in AllowedListSpec) RegexMatch(value string) (ok bool) {
|
||||
if len(in.Regex) > 0 {
|
||||
ok = regexp.MustCompile(in.Regex).MatchString(value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
73
api/v1alpha1/allowed_list_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAllowedListSpec_ExactMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
In []string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
|
||||
for _, tc := range []tc{
|
||||
{
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"bing", "bong"},
|
||||
},
|
||||
{
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
nil,
|
||||
nil,
|
||||
[]string{"any", "value"},
|
||||
},
|
||||
} {
|
||||
a := AllowedListSpec{
|
||||
Exact: tc.In,
|
||||
}
|
||||
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.ExactMatch(ok))
|
||||
}
|
||||
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.ExactMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedListSpec_RegexMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
Regex string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
|
||||
for _, tc := range []tc{
|
||||
{`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}},
|
||||
{``, nil, []string{"any", "value"}},
|
||||
} {
|
||||
a := AllowedListSpec{
|
||||
Regex: tc.Regex,
|
||||
}
|
||||
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.RegexMatch(ok))
|
||||
}
|
||||
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.RegexMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
12
api/v1alpha1/capsuleconfiguration_annotations.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package v1alpha1
|
||||
|
||||
const (
|
||||
ForbiddenNodeLabelsAnnotation = "capsule.clastix.io/forbidden-node-labels"
|
||||
ForbiddenNodeLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-node-labels-regexp"
|
||||
ForbiddenNodeAnnotationsAnnotation = "capsule.clastix.io/forbidden-node-annotations"
|
||||
ForbiddenNodeAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-node-annotations-regexp"
|
||||
TLSSecretNameAnnotation = "capsule.clastix.io/tls-secret-name"
|
||||
MutatingWebhookConfigurationName = "capsule.clastix.io/mutating-webhook-configuration-name"
|
||||
ValidatingWebhookConfigurationName = "capsule.clastix.io/validating-webhook-configuration-name"
|
||||
EnableTLSConfigurationAnnotationName = "capsule.clastix.io/enable-tls-configuration"
|
||||
)
|
||||
45
api/v1alpha1/capsuleconfiguration_types.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// CapsuleConfigurationSpec defines the Capsule configuration.
|
||||
type CapsuleConfigurationSpec struct {
|
||||
// Names of the groups for Capsule users.
|
||||
// +kubebuilder:default={capsule.clastix.io}
|
||||
UserGroups []string `json:"userGroups,omitempty"`
|
||||
// Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix,
|
||||
// separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
|
||||
// +kubebuilder:default=false
|
||||
ForceTenantPrefix bool `json:"forceTenantPrefix,omitempty"`
|
||||
// Disallow creation of namespaces, whose name matches this regexp
|
||||
ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
|
||||
// CapsuleConfiguration is the Schema for the Capsule configuration API.
|
||||
type CapsuleConfiguration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec CapsuleConfigurationSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// CapsuleConfigurationList contains a list of CapsuleConfiguration.
|
||||
type CapsuleConfigurationList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []CapsuleConfiguration `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&CapsuleConfiguration{}, &CapsuleConfigurationList{})
|
||||
}
|
||||
623
api/v1alpha1/conversion_hub.go
Normal file
@@ -0,0 +1,623 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/conversion"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceQuotaScopeAnnotation = "capsule.clastix.io/resource-quota-scope"
|
||||
|
||||
podAllowedImagePullPolicyAnnotation = "capsule.clastix.io/allowed-image-pull-policy"
|
||||
|
||||
podPriorityAllowedAnnotation = "priorityclass.capsule.clastix.io/allowed"
|
||||
podPriorityAllowedRegexAnnotation = "priorityclass.capsule.clastix.io/allowed-regex"
|
||||
|
||||
enableNodePortsAnnotation = "capsule.clastix.io/enable-node-ports"
|
||||
enableExternalNameAnnotation = "capsule.clastix.io/enable-external-name"
|
||||
enableLoadBalancerAnnotation = "capsule.clastix.io/enable-loadbalancer-service"
|
||||
|
||||
ownerGroupsAnnotation = "owners.capsule.clastix.io/group"
|
||||
ownerUsersAnnotation = "owners.capsule.clastix.io/user"
|
||||
ownerServiceAccountAnnotation = "owners.capsule.clastix.io/serviceaccount"
|
||||
|
||||
enableNodeListingAnnotation = "capsule.clastix.io/enable-node-listing"
|
||||
enableNodeUpdateAnnotation = "capsule.clastix.io/enable-node-update"
|
||||
enableNodeDeletionAnnotation = "capsule.clastix.io/enable-node-deletion"
|
||||
enableStorageClassListingAnnotation = "capsule.clastix.io/enable-storageclass-listing"
|
||||
enableStorageClassUpdateAnnotation = "capsule.clastix.io/enable-storageclass-update"
|
||||
enableStorageClassDeletionAnnotation = "capsule.clastix.io/enable-storageclass-deletion"
|
||||
enableIngressClassListingAnnotation = "capsule.clastix.io/enable-ingressclass-listing"
|
||||
enableIngressClassUpdateAnnotation = "capsule.clastix.io/enable-ingressclass-update"
|
||||
enableIngressClassDeletionAnnotation = "capsule.clastix.io/enable-ingressclass-deletion"
|
||||
enablePriorityClassListingAnnotation = "capsule.clastix.io/enable-priorityclass-listing"
|
||||
enablePriorityClassUpdateAnnotation = "capsule.clastix.io/enable-priorityclass-update"
|
||||
enablePriorityClassDeletionAnnotation = "capsule.clastix.io/enable-priorityclass-deletion"
|
||||
|
||||
ingressHostnameCollisionScope = "ingress.capsule.clastix.io/hostname-collision-scope"
|
||||
)
|
||||
|
||||
func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
|
||||
serviceKindToAnnotationMap := map[capsulev1beta1.ProxyServiceKind][]string{
|
||||
capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
|
||||
capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
|
||||
capsulev1beta1.IngressClassesProxy: {enableIngressClassListingAnnotation, enableIngressClassUpdateAnnotation, enableIngressClassDeletionAnnotation},
|
||||
capsulev1beta1.PriorityClassesProxy: {enablePriorityClassListingAnnotation, enablePriorityClassUpdateAnnotation, enablePriorityClassDeletionAnnotation},
|
||||
}
|
||||
annotationToOperationMap := map[string]capsulev1beta1.ProxyOperation{
|
||||
enableNodeListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enableNodeUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enableNodeDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
enableStorageClassListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enableStorageClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enableStorageClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
enableIngressClassListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enableIngressClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enableIngressClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
enablePriorityClassListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enablePriorityClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enablePriorityClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
}
|
||||
annotationToOwnerKindMap := map[string]capsulev1beta1.OwnerKind{
|
||||
ownerUsersAnnotation: capsulev1beta1.UserOwner,
|
||||
ownerGroupsAnnotation: capsulev1beta1.GroupOwner,
|
||||
ownerServiceAccountAnnotation: capsulev1beta1.ServiceAccountOwner,
|
||||
}
|
||||
|
||||
annotations := t.GetAnnotations()
|
||||
|
||||
operations := make(map[string]map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation)
|
||||
|
||||
for serviceKind, operationAnnotations := range serviceKindToAnnotationMap {
|
||||
for _, operationAnnotation := range operationAnnotations {
|
||||
val, ok := annotations[operationAnnotation]
|
||||
if ok {
|
||||
for _, owner := range strings.Split(val, ",") {
|
||||
if _, exists := operations[owner]; !exists {
|
||||
operations[owner] = make(map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation)
|
||||
}
|
||||
|
||||
operations[owner][serviceKind] = append(operations[owner][serviceKind], annotationToOperationMap[operationAnnotation])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var owners capsulev1beta1.OwnerListSpec
|
||||
|
||||
getProxySettingsForOwner := func(ownerName string) (settings []capsulev1beta1.ProxySettings) {
|
||||
ownerOperations, ok := operations[ownerName]
|
||||
if ok {
|
||||
for k, v := range ownerOperations {
|
||||
settings = append(settings, capsulev1beta1.ProxySettings{
|
||||
Kind: k,
|
||||
Operations: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
owners = append(owners, capsulev1beta1.OwnerSpec{
|
||||
Kind: capsulev1beta1.OwnerKind(t.Spec.Owner.Kind),
|
||||
Name: t.Spec.Owner.Name,
|
||||
ProxyOperations: getProxySettingsForOwner(t.Spec.Owner.Name),
|
||||
})
|
||||
|
||||
for ownerAnnotation, ownerKind := range annotationToOwnerKindMap {
|
||||
val, ok := annotations[ownerAnnotation]
|
||||
if ok {
|
||||
for _, owner := range strings.Split(val, ",") {
|
||||
owners = append(owners, capsulev1beta1.OwnerSpec{
|
||||
Kind: ownerKind,
|
||||
Name: owner,
|
||||
ProxyOperations: getProxySettingsForOwner(owner),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return owners
|
||||
}
|
||||
|
||||
// nolint:gocognit,gocyclo,cyclop,maintidx
|
||||
func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
dst, ok := dstRaw.(*capsulev1beta1.Tenant)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected type *capsulev1beta1.Tenant, got %T", dst)
|
||||
}
|
||||
|
||||
annotations := t.GetAnnotations()
|
||||
|
||||
// ObjectMeta
|
||||
dst.ObjectMeta = t.ObjectMeta
|
||||
|
||||
// Spec
|
||||
if t.Spec.NamespaceQuota != nil {
|
||||
if dst.Spec.NamespaceOptions == nil {
|
||||
dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{}
|
||||
}
|
||||
|
||||
dst.Spec.NamespaceOptions.Quota = t.Spec.NamespaceQuota
|
||||
}
|
||||
|
||||
dst.Spec.NodeSelector = t.Spec.NodeSelector
|
||||
|
||||
dst.Spec.Owners = t.convertV1Alpha1OwnerToV1Beta1()
|
||||
|
||||
if t.Spec.NamespacesMetadata != nil {
|
||||
if dst.Spec.NamespaceOptions == nil {
|
||||
dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{}
|
||||
}
|
||||
|
||||
dst.Spec.NamespaceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{
|
||||
Labels: t.Spec.NamespacesMetadata.AdditionalLabels,
|
||||
Annotations: t.Spec.NamespacesMetadata.AdditionalAnnotations,
|
||||
}
|
||||
}
|
||||
|
||||
if t.Spec.ServicesMetadata != nil {
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{
|
||||
AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{
|
||||
Labels: t.Spec.ServicesMetadata.AdditionalLabels,
|
||||
Annotations: t.Spec.ServicesMetadata.AdditionalAnnotations,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t.Spec.StorageClasses != nil {
|
||||
dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.StorageClasses.Exact,
|
||||
Regex: t.Spec.StorageClasses.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
if v, annotationOk := t.Annotations[ingressHostnameCollisionScope]; annotationOk {
|
||||
switch v {
|
||||
case string(capsulev1beta1.HostnameCollisionScopeCluster), string(capsulev1beta1.HostnameCollisionScopeTenant), string(capsulev1beta1.HostnameCollisionScopeNamespace):
|
||||
dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(v)
|
||||
default:
|
||||
dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScopeDisabled
|
||||
}
|
||||
}
|
||||
|
||||
if t.Spec.IngressClasses != nil {
|
||||
dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.IngressClasses.Exact,
|
||||
Regex: t.Spec.IngressClasses.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
if t.Spec.IngressHostnames != nil {
|
||||
dst.Spec.IngressOptions.AllowedHostnames = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.IngressHostnames.Exact,
|
||||
Regex: t.Spec.IngressHostnames.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
if t.Spec.ContainerRegistries != nil {
|
||||
dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.ContainerRegistries.Exact,
|
||||
Regex: t.Spec.ContainerRegistries.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Spec.NetworkPolicies) > 0 {
|
||||
dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{
|
||||
Items: t.Spec.NetworkPolicies,
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Spec.LimitRanges) > 0 {
|
||||
dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{
|
||||
Items: t.Spec.LimitRanges,
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Spec.ResourceQuota) > 0 {
|
||||
dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{
|
||||
Scope: func() capsulev1beta1.ResourceQuotaScope {
|
||||
if v, annotationOk := t.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk {
|
||||
switch v {
|
||||
case string(capsulev1beta1.ResourceQuotaScopeNamespace):
|
||||
return capsulev1beta1.ResourceQuotaScopeNamespace
|
||||
case string(capsulev1beta1.ResourceQuotaScopeTenant):
|
||||
return capsulev1beta1.ResourceQuotaScopeTenant
|
||||
}
|
||||
}
|
||||
|
||||
return capsulev1beta1.ResourceQuotaScopeTenant
|
||||
}(),
|
||||
Items: t.Spec.ResourceQuota,
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Spec.AdditionalRoleBindings) > 0 {
|
||||
for _, rb := range t.Spec.AdditionalRoleBindings {
|
||||
dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{
|
||||
ClusterRoleName: rb.ClusterRoleName,
|
||||
Subjects: rb.Subjects,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if t.Spec.ExternalServiceIPs != nil {
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
|
||||
}
|
||||
|
||||
dst.Spec.ServiceOptions.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{
|
||||
Allowed: make([]capsulev1beta1.AllowedIP, len(t.Spec.ExternalServiceIPs.Allowed)),
|
||||
}
|
||||
|
||||
for i, IP := range t.Spec.ExternalServiceIPs.Allowed {
|
||||
dst.Spec.ServiceOptions.ExternalServiceIPs.Allowed[i] = capsulev1beta1.AllowedIP(IP)
|
||||
}
|
||||
}
|
||||
|
||||
pullPolicies, ok := annotations[podAllowedImagePullPolicyAnnotation]
|
||||
if ok {
|
||||
for _, policy := range strings.Split(pullPolicies, ",") {
|
||||
dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(policy))
|
||||
}
|
||||
}
|
||||
|
||||
priorityClasses := capsulev1beta1.AllowedListSpec{}
|
||||
|
||||
priorityClassAllowed, ok := annotations[podPriorityAllowedAnnotation]
|
||||
|
||||
if ok {
|
||||
priorityClasses.Exact = strings.Split(priorityClassAllowed, ",")
|
||||
}
|
||||
|
||||
priorityClassesRegexp, ok := annotations[podPriorityAllowedRegexAnnotation]
|
||||
|
||||
if ok {
|
||||
priorityClasses.Regex = priorityClassesRegexp
|
||||
}
|
||||
|
||||
if !reflect.ValueOf(priorityClasses).IsZero() {
|
||||
dst.Spec.PriorityClasses = &priorityClasses
|
||||
}
|
||||
|
||||
enableNodePorts, ok := annotations[enableNodePortsAnnotation]
|
||||
if ok {
|
||||
val, err := strconv.ParseBool(enableNodePorts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, t.GetName()))
|
||||
}
|
||||
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
|
||||
}
|
||||
|
||||
if dst.Spec.ServiceOptions.AllowedServices == nil {
|
||||
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
|
||||
}
|
||||
|
||||
dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.BoolPtr(val)
|
||||
}
|
||||
|
||||
enableExternalName, ok := annotations[enableExternalNameAnnotation]
|
||||
if ok {
|
||||
val, err := strconv.ParseBool(enableExternalName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, t.GetName()))
|
||||
}
|
||||
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
|
||||
}
|
||||
|
||||
if dst.Spec.ServiceOptions.AllowedServices == nil {
|
||||
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
|
||||
}
|
||||
|
||||
dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.BoolPtr(val)
|
||||
}
|
||||
|
||||
loadBalancerService, ok := annotations[enableLoadBalancerAnnotation]
|
||||
if ok {
|
||||
val, err := strconv.ParseBool(loadBalancerService)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, t.GetName()))
|
||||
}
|
||||
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
|
||||
}
|
||||
|
||||
if dst.Spec.ServiceOptions.AllowedServices == nil {
|
||||
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
|
||||
}
|
||||
|
||||
dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.BoolPtr(val)
|
||||
}
|
||||
// Status
|
||||
dst.Status = capsulev1beta1.TenantStatus{
|
||||
Size: t.Status.Size,
|
||||
Namespaces: t.Status.Namespaces,
|
||||
}
|
||||
// Remove unneeded annotations
|
||||
delete(dst.ObjectMeta.Annotations, podAllowedImagePullPolicyAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, podPriorityAllowedAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, podPriorityAllowedRegexAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodePortsAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableExternalNameAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableLoadBalancerAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerGroupsAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerUsersAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerServiceAccountAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodeListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodeUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodeDeletionAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableStorageClassListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableStorageClassUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableStorageClassDeletionAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassDeletionAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enablePriorityClassListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enablePriorityClassUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enablePriorityClassDeletionAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, resourceQuotaScopeAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ingressHostnameCollisionScope)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint:gocognit,gocyclo,cyclop
|
||||
func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
|
||||
ownersAnnotations := map[string][]string{
|
||||
ownerGroupsAnnotation: nil,
|
||||
ownerUsersAnnotation: nil,
|
||||
ownerServiceAccountAnnotation: nil,
|
||||
}
|
||||
|
||||
proxyAnnotations := map[string][]string{
|
||||
enableNodeListingAnnotation: nil,
|
||||
enableNodeUpdateAnnotation: nil,
|
||||
enableNodeDeletionAnnotation: nil,
|
||||
enableStorageClassListingAnnotation: nil,
|
||||
enableStorageClassUpdateAnnotation: nil,
|
||||
enableStorageClassDeletionAnnotation: nil,
|
||||
enableIngressClassListingAnnotation: nil,
|
||||
enableIngressClassUpdateAnnotation: nil,
|
||||
enableIngressClassDeletionAnnotation: nil,
|
||||
}
|
||||
|
||||
for i, owner := range src.Spec.Owners {
|
||||
if i == 0 {
|
||||
t.Spec.Owner = OwnerSpec{
|
||||
Name: owner.Name,
|
||||
Kind: Kind(owner.Kind),
|
||||
}
|
||||
} else {
|
||||
switch owner.Kind {
|
||||
case capsulev1beta1.UserOwner:
|
||||
ownersAnnotations[ownerUsersAnnotation] = append(ownersAnnotations[ownerUsersAnnotation], owner.Name)
|
||||
case capsulev1beta1.GroupOwner:
|
||||
ownersAnnotations[ownerGroupsAnnotation] = append(ownersAnnotations[ownerGroupsAnnotation], owner.Name)
|
||||
case capsulev1beta1.ServiceAccountOwner:
|
||||
ownersAnnotations[ownerServiceAccountAnnotation] = append(ownersAnnotations[ownerServiceAccountAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, setting := range owner.ProxyOperations {
|
||||
switch setting.Kind {
|
||||
case capsulev1beta1.NodesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enableNodeListingAnnotation] = append(proxyAnnotations[enableNodeListingAnnotation], owner.Name)
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enableNodeUpdateAnnotation] = append(proxyAnnotations[enableNodeUpdateAnnotation], owner.Name)
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enableNodeDeletionAnnotation] = append(proxyAnnotations[enableNodeDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case capsulev1beta1.PriorityClassesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enablePriorityClassListingAnnotation] = append(proxyAnnotations[enablePriorityClassListingAnnotation], owner.Name)
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enablePriorityClassUpdateAnnotation] = append(proxyAnnotations[enablePriorityClassUpdateAnnotation], owner.Name)
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enablePriorityClassDeletionAnnotation] = append(proxyAnnotations[enablePriorityClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case capsulev1beta1.StorageClassesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enableStorageClassListingAnnotation] = append(proxyAnnotations[enableStorageClassListingAnnotation], owner.Name)
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enableStorageClassUpdateAnnotation] = append(proxyAnnotations[enableStorageClassUpdateAnnotation], owner.Name)
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enableStorageClassDeletionAnnotation] = append(proxyAnnotations[enableStorageClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case capsulev1beta1.IngressClassesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enableIngressClassListingAnnotation] = append(proxyAnnotations[enableIngressClassListingAnnotation], owner.Name)
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enableIngressClassUpdateAnnotation] = append(proxyAnnotations[enableIngressClassUpdateAnnotation], owner.Name)
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enableIngressClassDeletionAnnotation] = append(proxyAnnotations[enableIngressClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range ownersAnnotations {
|
||||
if len(v) > 0 {
|
||||
t.Annotations[k] = strings.Join(v, ",")
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range proxyAnnotations {
|
||||
if len(v) > 0 {
|
||||
t.Annotations[k] = strings.Join(v, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:gocyclo,cyclop
|
||||
func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
|
||||
src, ok := srcRaw.(*capsulev1beta1.Tenant)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", srcRaw)
|
||||
}
|
||||
|
||||
// ObjectMeta
|
||||
t.ObjectMeta = src.ObjectMeta
|
||||
|
||||
// Spec
|
||||
if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.Quota != nil {
|
||||
t.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota
|
||||
}
|
||||
|
||||
t.Spec.NodeSelector = src.Spec.NodeSelector
|
||||
|
||||
if t.Annotations == nil {
|
||||
t.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
t.convertV1Beta1OwnerToV1Alpha1(src)
|
||||
|
||||
if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.AdditionalMetadata != nil {
|
||||
t.Spec.NamespacesMetadata = &AdditionalMetadataSpec{
|
||||
AdditionalLabels: src.Spec.NamespaceOptions.AdditionalMetadata.Labels,
|
||||
AdditionalAnnotations: src.Spec.NamespaceOptions.AdditionalMetadata.Annotations,
|
||||
}
|
||||
}
|
||||
|
||||
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AdditionalMetadata != nil {
|
||||
t.Spec.ServicesMetadata = &AdditionalMetadataSpec{
|
||||
AdditionalLabels: src.Spec.ServiceOptions.AdditionalMetadata.Labels,
|
||||
AdditionalAnnotations: src.Spec.ServiceOptions.AdditionalMetadata.Annotations,
|
||||
}
|
||||
}
|
||||
|
||||
if src.Spec.StorageClasses != nil {
|
||||
t.Spec.StorageClasses = &AllowedListSpec{
|
||||
Exact: src.Spec.StorageClasses.Exact,
|
||||
Regex: src.Spec.StorageClasses.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
t.Annotations[ingressHostnameCollisionScope] = string(src.Spec.IngressOptions.HostnameCollisionScope)
|
||||
|
||||
if src.Spec.IngressOptions.AllowedClasses != nil {
|
||||
t.Spec.IngressClasses = &AllowedListSpec{
|
||||
Exact: src.Spec.IngressOptions.AllowedClasses.Exact,
|
||||
Regex: src.Spec.IngressOptions.AllowedClasses.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
if src.Spec.IngressOptions.AllowedHostnames != nil {
|
||||
t.Spec.IngressHostnames = &AllowedListSpec{
|
||||
Exact: src.Spec.IngressOptions.AllowedHostnames.Exact,
|
||||
Regex: src.Spec.IngressOptions.AllowedHostnames.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
if src.Spec.ContainerRegistries != nil {
|
||||
t.Spec.ContainerRegistries = &AllowedListSpec{
|
||||
Exact: src.Spec.ContainerRegistries.Exact,
|
||||
Regex: src.Spec.ContainerRegistries.Regex,
|
||||
}
|
||||
}
|
||||
|
||||
if len(src.Spec.NetworkPolicies.Items) > 0 {
|
||||
t.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items
|
||||
}
|
||||
|
||||
if len(src.Spec.LimitRanges.Items) > 0 {
|
||||
t.Spec.LimitRanges = src.Spec.LimitRanges.Items
|
||||
}
|
||||
|
||||
if len(src.Spec.ResourceQuota.Items) > 0 {
|
||||
t.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope)
|
||||
t.Spec.ResourceQuota = src.Spec.ResourceQuota.Items
|
||||
}
|
||||
|
||||
if len(src.Spec.AdditionalRoleBindings) > 0 {
|
||||
for _, rb := range src.Spec.AdditionalRoleBindings {
|
||||
t.Spec.AdditionalRoleBindings = append(t.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{
|
||||
ClusterRoleName: rb.ClusterRoleName,
|
||||
Subjects: rb.Subjects,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.ExternalServiceIPs != nil {
|
||||
t.Spec.ExternalServiceIPs = &ExternalServiceIPsSpec{
|
||||
Allowed: make([]AllowedIP, len(src.Spec.ServiceOptions.ExternalServiceIPs.Allowed)),
|
||||
}
|
||||
|
||||
for i, IP := range src.Spec.ServiceOptions.ExternalServiceIPs.Allowed {
|
||||
t.Spec.ExternalServiceIPs.Allowed[i] = AllowedIP(IP)
|
||||
}
|
||||
}
|
||||
|
||||
if len(src.Spec.ImagePullPolicies) != 0 {
|
||||
var pullPolicies []string
|
||||
|
||||
for _, policy := range src.Spec.ImagePullPolicies {
|
||||
pullPolicies = append(pullPolicies, string(policy))
|
||||
}
|
||||
|
||||
t.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",")
|
||||
}
|
||||
|
||||
if src.Spec.PriorityClasses != nil {
|
||||
if len(src.Spec.PriorityClasses.Exact) != 0 {
|
||||
t.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",")
|
||||
}
|
||||
|
||||
if src.Spec.PriorityClasses.Regex != "" {
|
||||
t.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil {
|
||||
if src.Spec.ServiceOptions.AllowedServices.NodePort != nil {
|
||||
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
|
||||
}
|
||||
|
||||
if src.Spec.ServiceOptions.AllowedServices.ExternalName != nil {
|
||||
t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
|
||||
}
|
||||
|
||||
if src.Spec.ServiceOptions.AllowedServices.LoadBalancer != nil {
|
||||
t.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer)
|
||||
}
|
||||
}
|
||||
|
||||
// Status
|
||||
t.Status = TenantStatus{
|
||||
Size: src.Status.Size,
|
||||
Namespaces: src.Status.Namespaces,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
390
api/v1alpha1/conversion_hub_test.go
Normal file
@@ -0,0 +1,390 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
// nolint:maintidx
|
||||
func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
var namespaceQuota int32 = 5
|
||||
|
||||
nodeSelector := map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
v1alpha1AdditionalMetadataSpec := &AdditionalMetadataSpec{
|
||||
AdditionalLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
AdditionalAnnotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
v1alpha1AllowedListSpec := &AllowedListSpec{
|
||||
Exact: []string{"foo", "bar"},
|
||||
Regex: "^foo*",
|
||||
}
|
||||
v1beta1AdditionalMetadataSpec := &capsulev1beta1.AdditionalMetadataSpec{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
v1beta1NamespaceOptions := &capsulev1beta1.NamespaceOptions{
|
||||
Quota: &namespaceQuota,
|
||||
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
|
||||
}
|
||||
v1beta1ServiceOptions := &capsulev1beta1.ServiceOptions{
|
||||
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
|
||||
AllowedServices: &capsulev1beta1.AllowedServices{
|
||||
NodePort: pointer.BoolPtr(false),
|
||||
ExternalName: pointer.BoolPtr(false),
|
||||
LoadBalancer: pointer.BoolPtr(false),
|
||||
},
|
||||
ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{
|
||||
Allowed: []capsulev1beta1.AllowedIP{"192.168.0.1"},
|
||||
},
|
||||
}
|
||||
v1beta1AllowedListSpec := &capsulev1beta1.AllowedListSpec{
|
||||
Exact: []string{"foo", "bar"},
|
||||
Regex: "^foo*",
|
||||
}
|
||||
networkPolicies := []networkingv1.NetworkPolicySpec{
|
||||
{
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{
|
||||
{
|
||||
From: []networkingv1.NetworkPolicyPeer{
|
||||
{
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "tenant-resources",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PodSelector: &metav1.LabelSelector{},
|
||||
},
|
||||
{
|
||||
IPBlock: &networkingv1.IPBlock{
|
||||
CIDR: "192.168.0.0/12",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
limitRanges := []corev1.LimitRangeSpec{
|
||||
{
|
||||
Limits: []corev1.LimitRangeItem{
|
||||
{
|
||||
Type: corev1.LimitTypePod,
|
||||
Min: map[corev1.ResourceName]resource.Quantity{
|
||||
corev1.ResourceCPU: resource.MustParse("50m"),
|
||||
corev1.ResourceMemory: resource.MustParse("5Mi"),
|
||||
},
|
||||
Max: map[corev1.ResourceName]resource.Quantity{
|
||||
corev1.ResourceCPU: resource.MustParse("1"),
|
||||
corev1.ResourceMemory: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resourceQuotas := []corev1.ResourceQuotaSpec{
|
||||
{
|
||||
Hard: map[corev1.ResourceName]resource.Quantity{
|
||||
corev1.ResourceLimitsCPU: resource.MustParse("8"),
|
||||
corev1.ResourceLimitsMemory: resource.MustParse("16Gi"),
|
||||
corev1.ResourceRequestsCPU: resource.MustParse("8"),
|
||||
corev1.ResourceRequestsMemory: resource.MustParse("16Gi"),
|
||||
},
|
||||
Scopes: []corev1.ResourceQuotaScope{
|
||||
corev1.ResourceQuotaScopeNotTerminating,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
v1beta1Tnt := capsulev1beta1.Tenant{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "alice",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "alice",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List", "Update", "Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "Nodes",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update", "Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update", "Delete"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "bob",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "jack",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "Nodes",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
{
|
||||
Kind: "PriorityClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: "owner-foo",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: "owner-bar",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "system:serviceaccount:oil-production:default",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "Nodes",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "system:serviceaccount:gas-production:gas",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceOptions: v1beta1NamespaceOptions,
|
||||
ServiceOptions: v1beta1ServiceOptions,
|
||||
StorageClasses: v1beta1AllowedListSpec,
|
||||
IngressOptions: capsulev1beta1.IngressOptions{
|
||||
HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled,
|
||||
AllowedClasses: v1beta1AllowedListSpec,
|
||||
AllowedHostnames: v1beta1AllowedListSpec,
|
||||
},
|
||||
ContainerRegistries: v1beta1AllowedListSpec,
|
||||
NodeSelector: nodeSelector,
|
||||
NetworkPolicies: capsulev1beta1.NetworkPolicySpec{
|
||||
Items: networkPolicies,
|
||||
},
|
||||
LimitRanges: capsulev1beta1.LimitRangesSpec{
|
||||
Items: limitRanges,
|
||||
},
|
||||
ResourceQuota: capsulev1beta1.ResourceQuotaSpec{
|
||||
Scope: capsulev1beta1.ResourceQuotaScopeNamespace,
|
||||
Items: resourceQuotas,
|
||||
},
|
||||
AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{
|
||||
{
|
||||
ClusterRoleName: "crds-rolebinding",
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "Group",
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Name: "system:authenticated",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"},
|
||||
PriorityClasses: &capsulev1beta1.AllowedListSpec{
|
||||
Exact: []string{"default"},
|
||||
Regex: "^tier-.*$",
|
||||
},
|
||||
},
|
||||
Status: capsulev1beta1.TenantStatus{
|
||||
Size: 1,
|
||||
Namespaces: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
v1alpha1Tnt := Tenant{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "alice",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
podAllowedImagePullPolicyAnnotation: "Always,IfNotPresent",
|
||||
enableExternalNameAnnotation: "false",
|
||||
enableNodePortsAnnotation: "false",
|
||||
enableLoadBalancerAnnotation: "false",
|
||||
podPriorityAllowedAnnotation: "default",
|
||||
podPriorityAllowedRegexAnnotation: "^tier-.*$",
|
||||
ownerGroupsAnnotation: "owner-foo,owner-bar",
|
||||
ownerUsersAnnotation: "bob,jack",
|
||||
ownerServiceAccountAnnotation: "system:serviceaccount:oil-production:default,system:serviceaccount:gas-production:gas",
|
||||
enableNodeUpdateAnnotation: "alice,system:serviceaccount:oil-production:default",
|
||||
enableNodeDeletionAnnotation: "alice,jack",
|
||||
enableStorageClassListingAnnotation: "bob,jack",
|
||||
enableStorageClassUpdateAnnotation: "alice,system:serviceaccount:gas-production:gas",
|
||||
enableStorageClassDeletionAnnotation: "alice,owner-bar",
|
||||
enableIngressClassListingAnnotation: "alice,owner-foo,owner-bar",
|
||||
enableIngressClassUpdateAnnotation: "alice,bob",
|
||||
enableIngressClassDeletionAnnotation: "alice,jack",
|
||||
enablePriorityClassListingAnnotation: "jack",
|
||||
resourceQuotaScopeAnnotation: "Namespace",
|
||||
ingressHostnameCollisionScope: "Disabled",
|
||||
},
|
||||
},
|
||||
Spec: TenantSpec{
|
||||
Owner: OwnerSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
NamespaceQuota: &namespaceQuota,
|
||||
NamespacesMetadata: v1alpha1AdditionalMetadataSpec,
|
||||
ServicesMetadata: v1alpha1AdditionalMetadataSpec,
|
||||
StorageClasses: v1alpha1AllowedListSpec,
|
||||
IngressClasses: v1alpha1AllowedListSpec,
|
||||
IngressHostnames: v1alpha1AllowedListSpec,
|
||||
ContainerRegistries: v1alpha1AllowedListSpec,
|
||||
NodeSelector: nodeSelector,
|
||||
NetworkPolicies: networkPolicies,
|
||||
LimitRanges: limitRanges,
|
||||
ResourceQuota: resourceQuotas,
|
||||
AdditionalRoleBindings: []AdditionalRoleBindingsSpec{
|
||||
{
|
||||
ClusterRoleName: "crds-rolebinding",
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "Group",
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Name: "system:authenticated",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExternalServiceIPs: &ExternalServiceIPsSpec{
|
||||
Allowed: []AllowedIP{"192.168.0.1"},
|
||||
},
|
||||
},
|
||||
Status: TenantStatus{
|
||||
Size: 1,
|
||||
Namespaces: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
return v1alpha1Tnt, v1beta1Tnt
|
||||
}
|
||||
|
||||
func TestConversionHub_ConvertTo(t *testing.T) {
|
||||
v1beta1ConvertedTnt := capsulev1beta1.Tenant{}
|
||||
|
||||
v1alpha1Tnt, v1beta1tnt := generateTenantsSpecs()
|
||||
err := v1alpha1Tnt.ConvertTo(&v1beta1ConvertedTnt)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
sort.Slice(v1beta1tnt.Spec.Owners, func(i, j int) bool {
|
||||
return v1beta1tnt.Spec.Owners[i].Name < v1beta1tnt.Spec.Owners[j].Name
|
||||
})
|
||||
sort.Slice(v1beta1ConvertedTnt.Spec.Owners, func(i, j int) bool {
|
||||
return v1beta1ConvertedTnt.Spec.Owners[i].Name < v1beta1ConvertedTnt.Spec.Owners[j].Name
|
||||
})
|
||||
|
||||
for _, owner := range v1beta1tnt.Spec.Owners {
|
||||
sort.Slice(owner.ProxyOperations, func(i, j int) bool {
|
||||
return owner.ProxyOperations[i].Kind < owner.ProxyOperations[j].Kind
|
||||
})
|
||||
}
|
||||
|
||||
for _, owner := range v1beta1ConvertedTnt.Spec.Owners {
|
||||
sort.Slice(owner.ProxyOperations, func(i, j int) bool {
|
||||
return owner.ProxyOperations[i].Kind < owner.ProxyOperations[j].Kind
|
||||
})
|
||||
}
|
||||
|
||||
assert.Equal(t, v1beta1tnt, v1beta1ConvertedTnt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConversionHub_ConvertFrom(t *testing.T) {
|
||||
v1alpha1ConvertedTnt := Tenant{}
|
||||
|
||||
v1alpha1Tnt, v1beta1tnt := generateTenantsSpecs()
|
||||
|
||||
err := v1alpha1ConvertedTnt.ConvertFrom(&v1beta1tnt)
|
||||
if assert.NoError(t, err) {
|
||||
assert.EqualValues(t, v1alpha1Tnt, v1alpha1ConvertedTnt)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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 domain
|
||||
|
||||
type SearchIn interface {
|
||||
IsStringInList(value string) bool
|
||||
}
|
||||
11
api/v1alpha1/external_service_ips.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||
type AllowedIP string
|
||||
|
||||
type ExternalServiceIPsSpec struct {
|
||||
Allowed []AllowedIP `json:"allowed"`
|
||||
}
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the capsule.clastix.io v1alpha1 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
@@ -25,10 +12,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects
|
||||
// GroupVersion is group version used to register these objects.
|
||||
GroupVersion = schema.GroupVersion{Group: "capsule.clastix.io", Version: "v1alpha1"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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 (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IngressClassList []string
|
||||
|
||||
func (n IngressClassList) Len() int {
|
||||
return len(n)
|
||||
}
|
||||
|
||||
func (n IngressClassList) Swap(i, j int) {
|
||||
n[i], n[j] = n[j], n[i]
|
||||
}
|
||||
|
||||
func (n IngressClassList) Less(i, j int) bool {
|
||||
return strings.ToLower(n[i]) < strings.ToLower(n[j])
|
||||
}
|
||||
|
||||
func (n IngressClassList) IsStringInList(value string) (ok bool) {
|
||||
sort.Sort(n)
|
||||
i := sort.SearchStrings(n, value)
|
||||
ok = i < n.Len() && n[i] == value
|
||||
return
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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 (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NamespaceList []string
|
||||
|
||||
func (n NamespaceList) Len() int {
|
||||
return len(n)
|
||||
}
|
||||
|
||||
func (n NamespaceList) Swap(i, j int) {
|
||||
n[i], n[j] = n[j], n[i]
|
||||
}
|
||||
|
||||
func (n NamespaceList) Less(i, j int) bool {
|
||||
return strings.ToLower(n[i]) < strings.ToLower(n[j])
|
||||
}
|
||||
|
||||
func (n NamespaceList) IsStringInList(value string) (ok bool) {
|
||||
sort.Sort(n)
|
||||
i := sort.SearchStrings(n, value)
|
||||
ok = i < n.Len() && n[i] == value
|
||||
return
|
||||
}
|
||||
17
api/v1alpha1/owner.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// OwnerSpec defines tenant owner name and kind.
|
||||
type OwnerSpec struct {
|
||||
Name string `json:"name"`
|
||||
Kind Kind `json:"kind"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=User;Group
|
||||
type Kind string
|
||||
|
||||
func (k Kind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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 (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StorageClassList []string
|
||||
|
||||
func (n StorageClassList) Len() int {
|
||||
return len(n)
|
||||
}
|
||||
|
||||
func (n StorageClassList) Swap(i, j int) {
|
||||
n[i], n[j] = n[j], n[i]
|
||||
}
|
||||
|
||||
func (n StorageClassList) Less(i, j int) bool {
|
||||
return strings.ToLower(n[i]) < strings.ToLower(n[j])
|
||||
}
|
||||
|
||||
func (n StorageClassList) IsStringInList(value string) (ok bool) {
|
||||
sort.Sort(n)
|
||||
i := sort.SearchStrings(n, value)
|
||||
ok = i < n.Len() && n[i] == value
|
||||
return
|
||||
}
|
||||
@@ -1,23 +1,10 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,8 +12,14 @@ const (
|
||||
AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp"
|
||||
AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes"
|
||||
AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp"
|
||||
AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries"
|
||||
AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp"
|
||||
)
|
||||
|
||||
func UsedQuotaFor(resource corev1.ResourceName) string {
|
||||
func UsedQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/used-" + resource.String()
|
||||
}
|
||||
|
||||
func HardQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/hard-" + resource.String()
|
||||
}
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -22,17 +9,32 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func (t *Tenant) IsCordoned() bool {
|
||||
if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tenant) IsFull() bool {
|
||||
return t.Status.Namespaces.Len() >= int(t.Spec.NamespaceQuota)
|
||||
// we don't have limits on assigned Namespaces
|
||||
if t.Spec.NamespaceQuota == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceQuota)
|
||||
}
|
||||
|
||||
func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
|
||||
var l []string
|
||||
|
||||
for _, ns := range namespaces {
|
||||
if ns.Status.Phase == corev1.NamespaceActive {
|
||||
l = append(l, ns.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(l)
|
||||
|
||||
t.Status.Namespaces = l
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -21,6 +8,7 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -34,8 +22,11 @@ func GetTypeLabel(t runtime.Object) (label string, err error) {
|
||||
return "capsule.clastix.io/network-policy", nil
|
||||
case *corev1.ResourceQuota:
|
||||
return "capsule.clastix.io/resource-quota", nil
|
||||
case *rbacv1.RoleBinding:
|
||||
return "capsule.clastix.io/role-binding", nil
|
||||
default:
|
||||
err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -22,67 +9,30 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
type NamespaceQuota uint
|
||||
|
||||
type AdditionalMetadata struct {
|
||||
// +nullable
|
||||
AdditionalLabels map[string]string `json:"additionalLabels"`
|
||||
// +nullable
|
||||
AdditionalAnnotations map[string]string `json:"additionalAnnotations"`
|
||||
}
|
||||
|
||||
type StorageClassesSpec struct {
|
||||
// +nullable
|
||||
Allowed StorageClassList `json:"allowed"`
|
||||
// +nullable
|
||||
AllowedRegex string `json:"allowedRegex"`
|
||||
}
|
||||
|
||||
type IngressClassesSpec struct {
|
||||
// +nullable
|
||||
Allowed IngressClassList `json:"allowed"`
|
||||
// +nullable
|
||||
AllowedRegex string `json:"allowedRegex"`
|
||||
}
|
||||
|
||||
// TenantSpec defines the desired state of Tenant
|
||||
// TenantSpec defines the desired state of Tenant.
|
||||
type TenantSpec struct {
|
||||
Owner OwnerSpec `json:"owner"`
|
||||
// +kubebuilder:validation:Optional
|
||||
NamespacesMetadata AdditionalMetadata `json:"namespacesMetadata"`
|
||||
// +kubebuilder:validation:Optional
|
||||
ServicesMetadata AdditionalMetadata `json:"servicesMetadata"`
|
||||
StorageClasses StorageClassesSpec `json:"storageClasses"`
|
||||
IngressClasses IngressClassesSpec `json:"ingressClasses"`
|
||||
// +kubebuilder:validation:Optional
|
||||
NodeSelector map[string]string `json:"nodeSelector"`
|
||||
NamespaceQuota NamespaceQuota `json:"namespaceQuota"`
|
||||
NetworkPolicies []networkingv1.NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
LimitRanges []corev1.LimitRangeSpec `json:"limitRanges"`
|
||||
// +kubebuilder:validation:Optional
|
||||
ResourceQuota []corev1.ResourceQuotaSpec `json:"resourceQuotas"`
|
||||
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
NamespaceQuota *int32 `json:"namespaceQuota,omitempty"`
|
||||
NamespacesMetadata *AdditionalMetadataSpec `json:"namespacesMetadata,omitempty"`
|
||||
ServicesMetadata *AdditionalMetadataSpec `json:"servicesMetadata,omitempty"`
|
||||
StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"`
|
||||
IngressClasses *AllowedListSpec `json:"ingressClasses,omitempty"`
|
||||
IngressHostnames *AllowedListSpec `json:"ingressHostnames,omitempty"`
|
||||
ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"`
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
NetworkPolicies []networkingv1.NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
LimitRanges []corev1.LimitRangeSpec `json:"limitRanges,omitempty"`
|
||||
ResourceQuota []corev1.ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
|
||||
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"`
|
||||
}
|
||||
|
||||
// OwnerSpec defines tenant owner name and kind
|
||||
type OwnerSpec struct {
|
||||
Name string `json:"name"`
|
||||
Kind Kind `json:"kind"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=User;Group
|
||||
type Kind string
|
||||
|
||||
func (k Kind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
|
||||
// TenantStatus defines the observed state of Tenant
|
||||
// TenantStatus defines the observed state of Tenant.
|
||||
type TenantStatus struct {
|
||||
Size uint `json:"size"`
|
||||
Namespaces NamespaceList `json:"namespaces,omitempty"`
|
||||
Users []string `json:"users,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Size uint `json:"size"`
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
@@ -92,9 +42,10 @@ type TenantStatus struct {
|
||||
// +kubebuilder:printcolumn:name="Namespace count",type="integer",JSONPath=".status.size",description="The total amount of Namespaces in use"
|
||||
// +kubebuilder:printcolumn:name="Owner name",type="string",JSONPath=".spec.owner.name",description="The assigned Tenant owner"
|
||||
// +kubebuilder:printcolumn:name="Owner kind",type="string",JSONPath=".spec.owner.kind",description="The assigned Tenant owner kind"
|
||||
// +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
|
||||
|
||||
// Tenant is the Schema for the tenants API
|
||||
// Tenant is the Schema for the tenants API.
|
||||
type Tenant struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
@@ -105,7 +56,7 @@ type Tenant struct {
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TenantList contains a list of Tenant
|
||||
// TenantList contains a list of Tenant.
|
||||
type TenantList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
21
api/v1alpha1/tenant_webhook.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
)
|
||||
|
||||
func (t *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error {
|
||||
certData, _ := ioutil.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt")
|
||||
if len(certData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ctrl.NewWebhookManagedBy(mgr).
|
||||
For(t).
|
||||
Complete()
|
||||
}
|
||||
@@ -1,20 +1,8 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
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.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
@@ -22,12 +10,13 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) {
|
||||
func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
|
||||
*out = *in
|
||||
if in.AdditionalLabels != nil {
|
||||
in, out := &in.AdditionalLabels, &out.AdditionalLabels
|
||||
@@ -45,74 +34,154 @@ func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) {
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadata.
|
||||
func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec.
|
||||
func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalMetadata)
|
||||
out := new(AdditionalMetadataSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in IngressClassList) DeepCopyInto(out *IngressClassList) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(IngressClassList, len(*in))
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) {
|
||||
*out = *in
|
||||
if in.Subjects != nil {
|
||||
in, out := &in.Subjects, &out.Subjects
|
||||
*out = make([]v1.Subject, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressClassList.
|
||||
func (in IngressClassList) DeepCopy() IngressClassList {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec.
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IngressClassList)
|
||||
out := new(AdditionalRoleBindingsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressClassesSpec) DeepCopyInto(out *IngressClassesSpec) {
|
||||
func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) {
|
||||
*out = *in
|
||||
if in.Exact != nil {
|
||||
in, out := &in.Exact, &out.Exact
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec.
|
||||
func (in *AllowedListSpec) DeepCopy() *AllowedListSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AllowedListSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CapsuleConfiguration) DeepCopyInto(out *CapsuleConfiguration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfiguration.
|
||||
func (in *CapsuleConfiguration) DeepCopy() *CapsuleConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CapsuleConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *CapsuleConfiguration) 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 *CapsuleConfigurationList) DeepCopyInto(out *CapsuleConfigurationList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]CapsuleConfiguration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationList.
|
||||
func (in *CapsuleConfigurationList) DeepCopy() *CapsuleConfigurationList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CapsuleConfigurationList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *CapsuleConfigurationList) 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 *CapsuleConfigurationSpec) DeepCopyInto(out *CapsuleConfigurationSpec) {
|
||||
*out = *in
|
||||
if in.UserGroups != nil {
|
||||
in, out := &in.UserGroups, &out.UserGroups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationSpec.
|
||||
func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CapsuleConfigurationSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) {
|
||||
*out = *in
|
||||
if in.Allowed != nil {
|
||||
in, out := &in.Allowed, &out.Allowed
|
||||
*out = make(IngressClassList, len(*in))
|
||||
*out = make([]AllowedIP, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressClassesSpec.
|
||||
func (in *IngressClassesSpec) DeepCopy() *IngressClassesSpec {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IngressClassesSpec)
|
||||
out := new(ExternalServiceIPsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in NamespaceList) DeepCopyInto(out *NamespaceList) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(NamespaceList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceList.
|
||||
func (in NamespaceList) DeepCopy() NamespaceList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamespaceList)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) {
|
||||
*out = *in
|
||||
@@ -128,45 +197,6 @@ func (in *OwnerSpec) DeepCopy() *OwnerSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in StorageClassList) DeepCopyInto(out *StorageClassList) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(StorageClassList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageClassList.
|
||||
func (in StorageClassList) DeepCopy() StorageClassList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StorageClassList)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StorageClassesSpec) DeepCopyInto(out *StorageClassesSpec) {
|
||||
*out = *in
|
||||
if in.Allowed != nil {
|
||||
in, out := &in.Allowed, &out.Allowed
|
||||
*out = make(StorageClassList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageClassesSpec.
|
||||
func (in *StorageClassesSpec) DeepCopy() *StorageClassesSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StorageClassesSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Tenant) DeepCopyInto(out *Tenant) {
|
||||
*out = *in
|
||||
@@ -230,10 +260,41 @@ func (in *TenantList) DeepCopyObject() runtime.Object {
|
||||
func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = *in
|
||||
out.Owner = in.Owner
|
||||
in.NamespacesMetadata.DeepCopyInto(&out.NamespacesMetadata)
|
||||
in.ServicesMetadata.DeepCopyInto(&out.ServicesMetadata)
|
||||
in.StorageClasses.DeepCopyInto(&out.StorageClasses)
|
||||
in.IngressClasses.DeepCopyInto(&out.IngressClasses)
|
||||
if in.NamespaceQuota != nil {
|
||||
in, out := &in.NamespaceQuota, &out.NamespaceQuota
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.NamespacesMetadata != nil {
|
||||
in, out := &in.NamespacesMetadata, &out.NamespacesMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServicesMetadata != nil {
|
||||
in, out := &in.ServicesMetadata, &out.ServicesMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.StorageClasses != nil {
|
||||
in, out := &in.StorageClasses, &out.StorageClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.IngressClasses != nil {
|
||||
in, out := &in.IngressClasses, &out.IngressClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.IngressHostnames != nil {
|
||||
in, out := &in.IngressHostnames, &out.IngressHostnames
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ContainerRegistries != nil {
|
||||
in, out := &in.ContainerRegistries, &out.ContainerRegistries
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
@@ -243,7 +304,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
}
|
||||
if in.NetworkPolicies != nil {
|
||||
in, out := &in.NetworkPolicies, &out.NetworkPolicies
|
||||
*out = make([]v1.NetworkPolicySpec, len(*in))
|
||||
*out = make([]networkingv1.NetworkPolicySpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -262,6 +323,18 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AdditionalRoleBindings != nil {
|
||||
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
|
||||
*out = make([]AdditionalRoleBindingsSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ExternalServiceIPs != nil {
|
||||
in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs
|
||||
*out = new(ExternalServiceIPsSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec.
|
||||
@@ -279,16 +352,6 @@ func (in *TenantStatus) DeepCopyInto(out *TenantStatus) {
|
||||
*out = *in
|
||||
if in.Namespaces != nil {
|
||||
in, out := &in.Namespaces, &out.Namespaces
|
||||
*out = make(NamespaceList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Users != nil {
|
||||
in, out := &in.Users, &out.Users
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Groups != nil {
|
||||
in, out := &in.Groups, &out.Groups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
|
||||
9
api/v1beta1/additional_metadata.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
type AdditionalMetadataSpec struct {
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
12
api/v1beta1/additional_role_bindings.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import rbacv1 "k8s.io/api/rbac/v1"
|
||||
|
||||
type AdditionalRoleBindingsSpec struct {
|
||||
ClusterRoleName string `json:"clusterRoleName"`
|
||||
// kubebuilder:validation:Minimum=1
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
}
|
||||
37
api/v1beta1/allowed_list.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//nolint:dupl
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AllowedListSpec struct {
|
||||
Exact []string `json:"allowed,omitempty"`
|
||||
Regex string `json:"allowedRegex,omitempty"`
|
||||
}
|
||||
|
||||
func (in *AllowedListSpec) ExactMatch(value string) (ok bool) {
|
||||
if len(in.Exact) > 0 {
|
||||
sort.SliceStable(in.Exact, func(i, j int) bool {
|
||||
return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j])
|
||||
})
|
||||
|
||||
i := sort.SearchStrings(in.Exact, value)
|
||||
|
||||
ok = i < len(in.Exact) && in.Exact[i] == value
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (in AllowedListSpec) RegexMatch(value string) (ok bool) {
|
||||
if len(in.Regex) > 0 {
|
||||
ok = regexp.MustCompile(in.Regex).MatchString(value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
73
api/v1beta1/allowed_list_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//nolint:dupl
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAllowedListSpec_ExactMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
In []string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
|
||||
for _, tc := range []tc{
|
||||
{
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"bing", "bong"},
|
||||
},
|
||||
{
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
nil,
|
||||
nil,
|
||||
[]string{"any", "value"},
|
||||
},
|
||||
} {
|
||||
a := AllowedListSpec{
|
||||
Exact: tc.In,
|
||||
}
|
||||
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.ExactMatch(ok))
|
||||
}
|
||||
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.ExactMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedListSpec_RegexMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
Regex string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
|
||||
for _, tc := range []tc{
|
||||
{`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}},
|
||||
{``, nil, []string{"any", "value"}},
|
||||
} {
|
||||
a := AllowedListSpec{
|
||||
Regex: tc.Regex,
|
||||
}
|
||||
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.RegexMatch(ok))
|
||||
}
|
||||
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.RegexMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
59
api/v1beta1/custom_resource_quota.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
ResourceQuotaAnnotationPrefix = "quota.resources.capsule.clastix.io"
|
||||
ResourceUsedAnnotationPrefix = "used.resources.capsule.clastix.io"
|
||||
)
|
||||
|
||||
func UsedAnnotationForResource(kindGroup string) string {
|
||||
return fmt.Sprintf("%s/%s", ResourceUsedAnnotationPrefix, kindGroup)
|
||||
}
|
||||
|
||||
func LimitAnnotationForResource(kindGroup string) string {
|
||||
return fmt.Sprintf("%s/%s", ResourceQuotaAnnotationPrefix, kindGroup)
|
||||
}
|
||||
|
||||
func GetUsedResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) {
|
||||
usedStr, ok := tenant.GetAnnotations()[UsedAnnotationForResource(kindGroup)]
|
||||
if !ok {
|
||||
usedStr = "0"
|
||||
}
|
||||
|
||||
used, _ := strconv.ParseInt(usedStr, 10, 10)
|
||||
|
||||
return used, nil
|
||||
}
|
||||
|
||||
type NonLimitedResourceError struct {
|
||||
kindGroup string
|
||||
}
|
||||
|
||||
func NewNonLimitedResourceError(kindGroup string) *NonLimitedResourceError {
|
||||
return &NonLimitedResourceError{kindGroup: kindGroup}
|
||||
}
|
||||
|
||||
func (n NonLimitedResourceError) Error() string {
|
||||
return fmt.Sprintf("resource %s is not limited for the current tenant", n.kindGroup)
|
||||
}
|
||||
|
||||
func GetLimitResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) {
|
||||
limitStr, ok := tenant.GetAnnotations()[LimitAnnotationForResource(kindGroup)]
|
||||
if !ok {
|
||||
return 0, NewNonLimitedResourceError(kindGroup)
|
||||
}
|
||||
|
||||
limit, err := strconv.ParseInt(limitStr, 10, 10)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("resource %s limit cannot be parsed, %w", kindGroup, err)
|
||||
}
|
||||
|
||||
return limit, nil
|
||||
}
|
||||
16
api/v1beta1/deny_wildcard.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
const (
|
||||
denyWildcard = "capsule.clastix.io/deny-wildcard"
|
||||
)
|
||||
|
||||
func (t *Tenant) IsWildcardDenied() bool {
|
||||
if v, ok := t.Annotations[denyWildcard]; ok && v == "true" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
37
api/v1beta1/forbidden_list.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//nolint:dupl
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ForbiddenListSpec struct {
|
||||
Exact []string `json:"denied,omitempty"`
|
||||
Regex string `json:"deniedRegex,omitempty"`
|
||||
}
|
||||
|
||||
func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) {
|
||||
if len(in.Exact) > 0 {
|
||||
sort.SliceStable(in.Exact, func(i, j int) bool {
|
||||
return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j])
|
||||
})
|
||||
|
||||
i := sort.SearchStrings(in.Exact, value)
|
||||
|
||||
ok = i < len(in.Exact) && in.Exact[i] == value
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (in ForbiddenListSpec) RegexMatch(value string) (ok bool) {
|
||||
if len(in.Regex) > 0 {
|
||||
ok = regexp.MustCompile(in.Regex).MatchString(value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
73
api/v1beta1/forbidden_list_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//nolint:dupl
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestForbiddenListSpec_ExactMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
In []string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
|
||||
for _, tc := range []tc{
|
||||
{
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"bing", "bong"},
|
||||
},
|
||||
{
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
nil,
|
||||
nil,
|
||||
[]string{"any", "value"},
|
||||
},
|
||||
} {
|
||||
a := ForbiddenListSpec{
|
||||
Exact: tc.In,
|
||||
}
|
||||
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.ExactMatch(ok))
|
||||
}
|
||||
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.ExactMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbiddenListSpec_RegexMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
Regex string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
|
||||
for _, tc := range []tc{
|
||||
{`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}},
|
||||
{``, nil, []string{"any", "value"}},
|
||||
} {
|
||||
a := ForbiddenListSpec{
|
||||
Regex: tc.Regex,
|
||||
}
|
||||
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.RegexMatch(ok))
|
||||
}
|
||||
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.RegexMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
23
api/v1beta1/groupversion_info.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package v1beta1 contains API Schema definitions for the capsule v1beta1 API group
|
||||
//+kubebuilder:object:generate=true
|
||||
//+groupName=capsule.clastix.io
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects.
|
||||
GroupVersion = schema.GroupVersion{Group: "capsule.clastix.io", Version: "v1beta1"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
14
api/v1beta1/hostname_collision_scope.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
const (
|
||||
HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster"
|
||||
HostnameCollisionScopeTenant HostnameCollisionScope = "Tenant"
|
||||
HostnameCollisionScopeNamespace HostnameCollisionScope = "Namespace"
|
||||
HostnameCollisionScopeDisabled HostnameCollisionScope = "Disabled"
|
||||
)
|
||||
|
||||
// +kubebuilder:validation:Enum=Cluster;Tenant;Namespace;Disabled
|
||||
type HostnameCollisionScope string
|
||||
11
api/v1beta1/image_pull_policy.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
// +kubebuilder:validation:Enum=Always;Never;IfNotPresent
|
||||
type ImagePullPolicySpec string
|
||||
|
||||
func (i ImagePullPolicySpec) String() string {
|
||||
return string(i)
|
||||
}
|
||||
24
api/v1beta1/ingress_options.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
type IngressOptions struct {
|
||||
// Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
|
||||
AllowedClasses *AllowedListSpec `json:"allowedClasses,omitempty"`
|
||||
// Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames.
|
||||
//
|
||||
//
|
||||
// - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule.
|
||||
//
|
||||
// - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant.
|
||||
//
|
||||
// - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace.
|
||||
//
|
||||
//
|
||||
// Optional.
|
||||
// +kubebuilder:default=Disabled
|
||||
HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"`
|
||||
// Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional.
|
||||
AllowedHostnames *AllowedListSpec `json:"allowedHostnames,omitempty"`
|
||||
}
|
||||
10
api/v1beta1/limit_ranges.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
type LimitRangesSpec struct {
|
||||
Items []corev1.LimitRangeSpec `json:"items,omitempty"`
|
||||
}
|
||||
57
api/v1beta1/namespace_options.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package v1beta1
|
||||
|
||||
import "strings"
|
||||
|
||||
type NamespaceOptions struct {
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
// Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
|
||||
Quota *int32 `json:"quota,omitempty"`
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
|
||||
AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool {
|
||||
if _, ok := t.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool {
|
||||
if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tenant) ForbiddenUserNamespaceLabels() *ForbiddenListSpec {
|
||||
if !t.hasForbiddenNamespaceLabelsAnnotations() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ForbiddenListSpec{
|
||||
Exact: strings.Split(t.Annotations[ForbiddenNamespaceLabelsAnnotation], ","),
|
||||
Regex: t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation],
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tenant) ForbiddenUserNamespaceAnnotations() *ForbiddenListSpec {
|
||||
if !t.hasForbiddenNamespaceAnnotationsAnnotations() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ForbiddenListSpec{
|
||||
Exact: strings.Split(t.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","),
|
||||
Regex: t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation],
|
||||
}
|
||||
}
|
||||
12
api/v1beta1/network_policy.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
)
|
||||
|
||||
type NetworkPolicySpec struct {
|
||||
Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"`
|
||||
}
|
||||
54
api/v1beta1/owner.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
type OwnerSpec struct {
|
||||
// Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
|
||||
Kind OwnerKind `json:"kind"`
|
||||
// Name of tenant owner.
|
||||
Name string `json:"name"`
|
||||
// Proxy settings for tenant owner.
|
||||
ProxyOperations []ProxySettings `json:"proxySettings,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=User;Group;ServiceAccount
|
||||
type OwnerKind string
|
||||
|
||||
func (k OwnerKind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
|
||||
type ProxySettings struct {
|
||||
Kind ProxyServiceKind `json:"kind"`
|
||||
Operations []ProxyOperation `json:"operations"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=List;Update;Delete
|
||||
type ProxyOperation string
|
||||
|
||||
func (p ProxyOperation) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses;PriorityClasses
|
||||
type ProxyServiceKind string
|
||||
|
||||
func (p ProxyServiceKind) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
const (
|
||||
NodesProxy ProxyServiceKind = "Nodes"
|
||||
StorageClassesProxy ProxyServiceKind = "StorageClasses"
|
||||
IngressClassesProxy ProxyServiceKind = "IngressClasses"
|
||||
PriorityClassesProxy ProxyServiceKind = "PriorityClasses"
|
||||
|
||||
ListOperation ProxyOperation = "List"
|
||||
UpdateOperation ProxyOperation = "Update"
|
||||
DeleteOperation ProxyOperation = "Delete"
|
||||
|
||||
UserOwner OwnerKind = "User"
|
||||
GroupOwner OwnerKind = "Group"
|
||||
ServiceAccountOwner OwnerKind = "ServiceAccount"
|
||||
)
|
||||
38
api/v1beta1/owner_list.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
type OwnerListSpec []OwnerSpec
|
||||
|
||||
func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) {
|
||||
sort.Sort(ByKindAndName(o))
|
||||
i := sort.Search(len(o), func(i int) bool {
|
||||
return o[i].Kind >= kind && o[i].Name >= name
|
||||
})
|
||||
|
||||
if i < len(o) && o[i].Kind == kind && o[i].Name == name {
|
||||
return o[i]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ByKindAndName OwnerListSpec
|
||||
|
||||
func (b ByKindAndName) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
func (b ByKindAndName) Less(i, j int) bool {
|
||||
if b[i].Kind.String() != b[j].Kind.String() {
|
||||
return b[i].Kind.String() < b[j].Kind.String()
|
||||
}
|
||||
|
||||
return b[i].Name < b[j].Name
|
||||
}
|
||||
|
||||
func (b ByKindAndName) Swap(i, j int) {
|
||||
b[i], b[j] = b[j], b[i]
|
||||
}
|
||||
83
api/v1beta1/owner_list_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
bla := OwnerSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "bla",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: IngressClassesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
bar := OwnerSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bar",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
baz := OwnerSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "baz",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"Update"},
|
||||
},
|
||||
},
|
||||
}
|
||||
fim := OwnerSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "fim",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: NodesProxy,
|
||||
Operations: []ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
}
|
||||
bom := OwnerSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bom",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
{
|
||||
Kind: NodesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
qip := OwnerSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "qip",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"List", "Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
owners := OwnerListSpec{bom, qip, bla, bar, baz, fim}
|
||||
|
||||
assert.Equal(t, owners.FindOwner("bom", GroupOwner), bom)
|
||||
assert.Equal(t, owners.FindOwner("qip", ServiceAccountOwner), qip)
|
||||
assert.Equal(t, owners.FindOwner("bla", UserOwner), bla)
|
||||
assert.Equal(t, owners.FindOwner("bar", GroupOwner), bar)
|
||||
assert.Equal(t, owners.FindOwner("baz", UserOwner), baz)
|
||||
assert.Equal(t, owners.FindOwner("fim", ServiceAccountOwner), fim)
|
||||
assert.Equal(t, owners.FindOwner("notfound", ServiceAccountOwner), OwnerSpec{})
|
||||
}
|
||||
74
api/v1beta1/owner_role.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ClusterRoleNamesAnnotation = "clusterrolenames.capsule.clastix.io"
|
||||
)
|
||||
|
||||
// GetRoles read the annotation available in the Tenant specification and if it matches the pattern
|
||||
// clusterrolenames.capsule.clastix.io/${KIND}.${NAME} returns the associated roles.
|
||||
// Kubernetes annotations and labels must respect RFC 1123 about DNS names and this could be cumbersome in two cases:
|
||||
// 1. identifying users based on their email address
|
||||
// 2. the overall length of the annotation key that is exceeding 63 characters
|
||||
// For emails, the symbol @ can be replaced with the placeholder __AT__.
|
||||
// For the latter one, the index of the owner can be used to force the retrieval.
|
||||
func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string {
|
||||
for key, value := range tenant.GetAnnotations() {
|
||||
if !strings.HasPrefix(key, fmt.Sprintf("%s/", ClusterRoleNamesAnnotation)) {
|
||||
continue
|
||||
}
|
||||
|
||||
for symbol, replace := range in.convertMap() {
|
||||
key = strings.ReplaceAll(key, symbol, replace)
|
||||
}
|
||||
|
||||
nameBased := key == fmt.Sprintf("%s/%s.%s", ClusterRoleNamesAnnotation, strings.ToLower(in.Kind.String()), strings.ToLower(in.Name))
|
||||
|
||||
indexBased := key == fmt.Sprintf("%s/%d", ClusterRoleNamesAnnotation, index)
|
||||
|
||||
if nameBased || indexBased {
|
||||
return strings.Split(value, ",")
|
||||
}
|
||||
}
|
||||
|
||||
roles := []string{"admin", "capsule-namespace-deleter"}
|
||||
|
||||
if tenant.Spec.GitOpsReady {
|
||||
roles = append(roles, in.getGitOpsRoles(tenant)...)
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func (in OwnerSpec) GetClusterRoles(tenant Tenant) []string {
|
||||
if tenant.Spec.GitOpsReady {
|
||||
return in.getGitOpsClusterRoles(tenant)
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (in OwnerSpec) getGitOpsClusterRoles(tenant Tenant) []string {
|
||||
return []string{
|
||||
"capsule-tenant-impersonator-" + tenant.Name + "-" + in.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (in OwnerSpec) getGitOpsRoles(tenant Tenant) []string {
|
||||
return []string{
|
||||
"cluster-admin",
|
||||
}
|
||||
}
|
||||
|
||||
func (in OwnerSpec) convertMap() map[string]string {
|
||||
return map[string]string{
|
||||
"__AT__": "@",
|
||||
}
|
||||
}
|
||||
21
api/v1beta1/resource_quota.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
// +kubebuilder:validation:Enum=Tenant;Namespace
|
||||
type ResourceQuotaScope string
|
||||
|
||||
const (
|
||||
ResourceQuotaScopeTenant ResourceQuotaScope = "Tenant"
|
||||
ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace"
|
||||
)
|
||||
|
||||
type ResourceQuotaSpec struct {
|
||||
// +kubebuilder:default=Tenant
|
||||
// Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
|
||||
Scope ResourceQuotaScope `json:"scope,omitempty"`
|
||||
Items []corev1.ResourceQuotaSpec `json:"items,omitempty"`
|
||||
}
|
||||
11
api/v1beta1/service_allowed_ips.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||
type AllowedIP string
|
||||
|
||||
type ExternalServiceIPsSpec struct {
|
||||
Allowed []AllowedIP `json:"allowed"`
|
||||
}
|
||||
16
api/v1beta1/service_allowed_types.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
type AllowedServices struct {
|
||||
//+kubebuilder:default=true
|
||||
// Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
NodePort *bool `json:"nodePort,omitempty"`
|
||||
//+kubebuilder:default=true
|
||||
// Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
ExternalName *bool `json:"externalName,omitempty"`
|
||||
//+kubebuilder:default=true
|
||||
// Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
LoadBalancer *bool `json:"loadBalancer,omitempty"`
|
||||
}
|
||||
13
api/v1beta1/service_options.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
type ServiceOptions struct {
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
|
||||
AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
|
||||
// Block or deny certain type of Services. Optional.
|
||||
AllowedServices *AllowedServices `json:"allowedServices,omitempty"`
|
||||
// Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
|
||||
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"`
|
||||
}
|
||||
31
api/v1beta1/tenant_annotations.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes"
|
||||
AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp"
|
||||
AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes"
|
||||
AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp"
|
||||
AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries"
|
||||
AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp"
|
||||
ForbiddenNamespaceLabelsAnnotation = "capsule.clastix.io/forbidden-namespace-labels"
|
||||
ForbiddenNamespaceLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-labels-regexp"
|
||||
ForbiddenNamespaceAnnotationsAnnotation = "capsule.clastix.io/forbidden-namespace-annotations"
|
||||
ForbiddenNamespaceAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-annotations-regexp"
|
||||
ProtectedTenantAnnotation = "capsule.clastix.io/protected"
|
||||
)
|
||||
|
||||
func UsedQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_")
|
||||
}
|
||||
|
||||
func HardQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_")
|
||||
}
|
||||
46
api/v1beta1/tenant_func.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func (t *Tenant) IsCordoned() bool {
|
||||
if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tenant) IsFull() bool {
|
||||
// we don't have limits on assigned Namespaces
|
||||
if t.Spec.NamespaceOptions == nil || t.Spec.NamespaceOptions.Quota == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceOptions.Quota)
|
||||
}
|
||||
|
||||
func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
|
||||
var l []string
|
||||
|
||||
for _, ns := range namespaces {
|
||||
if ns.Status.Phase == corev1.NamespaceActive {
|
||||
l = append(l, ns.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(l)
|
||||
|
||||
t.Status.Namespaces = l
|
||||
t.Status.Size = uint(len(l))
|
||||
}
|
||||
|
||||
func (t *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings {
|
||||
return t.Spec.Owners.FindOwner(name, kind).ProxyOperations
|
||||
}
|
||||
34
api/v1beta1/tenant_labels.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func GetTypeLabel(t runtime.Object) (label string, err error) {
|
||||
switch v := t.(type) {
|
||||
case *Tenant:
|
||||
return "capsule.clastix.io/tenant", nil
|
||||
case *corev1.LimitRange:
|
||||
return "capsule.clastix.io/limit-range", nil
|
||||
case *networkingv1.NetworkPolicy:
|
||||
return "capsule.clastix.io/network-policy", nil
|
||||
case *corev1.ResourceQuota:
|
||||
return "capsule.clastix.io/resource-quota", nil
|
||||
case *rbacv1.RoleBinding:
|
||||
return "capsule.clastix.io/role-binding", nil
|
||||
case *rbacv1.ClusterRoleBinding:
|
||||
return "capsule.clastix.io/cluster-role-binding", nil
|
||||
default:
|
||||
err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
23
api/v1beta1/tenant_status.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
// +kubebuilder:validation:Enum=Cordoned;Active
|
||||
type tenantState string
|
||||
|
||||
const (
|
||||
TenantStateActive tenantState = "Active"
|
||||
TenantStateCordoned tenantState = "Cordoned"
|
||||
)
|
||||
|
||||
// Returns the observed state of the Tenant.
|
||||
type TenantStatus struct {
|
||||
//+kubebuilder:default=Active
|
||||
// The operational state of the Tenant. Possible values are "Active", "Cordoned".
|
||||
State tenantState `json:"state"`
|
||||
// How many namespaces are assigned to the Tenant.
|
||||
Size uint `json:"size"`
|
||||
// List of namespaces assigned to the Tenant.
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
}
|
||||
74
api/v1beta1/tenant_types.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// TenantSpec defines the desired state of Tenant.
|
||||
type TenantSpec struct {
|
||||
// Specifies the owners of the Tenant. Mandatory.
|
||||
Owners OwnerListSpec `json:"owners"`
|
||||
// Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
|
||||
NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"`
|
||||
// Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
|
||||
ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"`
|
||||
// Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
|
||||
StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"`
|
||||
// Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
|
||||
IngressOptions IngressOptions `json:"ingressOptions,omitempty"`
|
||||
// Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
|
||||
ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"`
|
||||
// Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
// Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
|
||||
LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"`
|
||||
// Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
|
||||
ResourceQuota ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
// Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
|
||||
AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
|
||||
// Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
|
||||
ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"`
|
||||
// Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
|
||||
PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"`
|
||||
// Configured RBAC for machine owners tailored for GitOps controllers.
|
||||
GitOpsReady bool `json:"gitOpsReady,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
//+kubebuilder:storageversion
|
||||
// +kubebuilder:resource:scope=Cluster,shortName=tnt
|
||||
// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant"
|
||||
// +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created"
|
||||
// +kubebuilder:printcolumn:name="Namespace count",type="integer",JSONPath=".status.size",description="The total amount of Namespaces in use"
|
||||
// +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
|
||||
|
||||
// Tenant is the Schema for the tenants API.
|
||||
type Tenant struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec TenantSpec `json:"spec,omitempty"`
|
||||
Status TenantStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Tenant) Hub() {}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
// TenantList contains a list of Tenant.
|
||||
type TenantList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Tenant `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Tenant{}, &TenantList{})
|
||||
}
|
||||
549
api/v1beta1/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,549 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
|
||||
*out = *in
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec.
|
||||
func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalMetadataSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) {
|
||||
*out = *in
|
||||
if in.Subjects != nil {
|
||||
in, out := &in.Subjects, &out.Subjects
|
||||
*out = make([]v1.Subject, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec.
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalRoleBindingsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) {
|
||||
*out = *in
|
||||
if in.Exact != nil {
|
||||
in, out := &in.Exact, &out.Exact
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec.
|
||||
func (in *AllowedListSpec) DeepCopy() *AllowedListSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AllowedListSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AllowedServices) DeepCopyInto(out *AllowedServices) {
|
||||
*out = *in
|
||||
if in.NodePort != nil {
|
||||
in, out := &in.NodePort, &out.NodePort
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.ExternalName != nil {
|
||||
in, out := &in.ExternalName, &out.ExternalName
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.LoadBalancer != nil {
|
||||
in, out := &in.LoadBalancer, &out.LoadBalancer
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices.
|
||||
func (in *AllowedServices) DeepCopy() *AllowedServices {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AllowedServices)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ByKindAndName, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByKindAndName.
|
||||
func (in ByKindAndName) DeepCopy() ByKindAndName {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ByKindAndName)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) {
|
||||
*out = *in
|
||||
if in.Allowed != nil {
|
||||
in, out := &in.Allowed, &out.Allowed
|
||||
*out = make([]AllowedIP, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExternalServiceIPsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) {
|
||||
*out = *in
|
||||
if in.Exact != nil {
|
||||
in, out := &in.Exact, &out.Exact
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec.
|
||||
func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ForbiddenListSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressOptions) DeepCopyInto(out *IngressOptions) {
|
||||
*out = *in
|
||||
if in.AllowedClasses != nil {
|
||||
in, out := &in.AllowedClasses, &out.AllowedClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AllowedHostnames != nil {
|
||||
in, out := &in.AllowedHostnames, &out.AllowedHostnames
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressOptions.
|
||||
func (in *IngressOptions) DeepCopy() *IngressOptions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IngressOptions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) {
|
||||
*out = *in
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]corev1.LimitRangeSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec.
|
||||
func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LimitRangesSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) {
|
||||
*out = *in
|
||||
if in.Quota != nil {
|
||||
in, out := &in.Quota, &out.Quota
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.AdditionalMetadata != nil {
|
||||
in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceOptions.
|
||||
func (in *NamespaceOptions) DeepCopy() *NamespaceOptions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamespaceOptions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) {
|
||||
*out = *in
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]networkingv1.NetworkPolicySpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec.
|
||||
func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NetworkPolicySpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NonLimitedResourceError.
|
||||
func (in *NonLimitedResourceError) DeepCopy() *NonLimitedResourceError {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NonLimitedResourceError)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(OwnerListSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerListSpec.
|
||||
func (in OwnerListSpec) DeepCopy() OwnerListSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerListSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) {
|
||||
*out = *in
|
||||
if in.ProxyOperations != nil {
|
||||
in, out := &in.ProxyOperations, &out.ProxyOperations
|
||||
*out = make([]ProxySettings, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerSpec.
|
||||
func (in *OwnerSpec) DeepCopy() *OwnerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProxySettings) DeepCopyInto(out *ProxySettings) {
|
||||
*out = *in
|
||||
if in.Operations != nil {
|
||||
in, out := &in.Operations, &out.Operations
|
||||
*out = make([]ProxyOperation, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxySettings.
|
||||
func (in *ProxySettings) DeepCopy() *ProxySettings {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProxySettings)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) {
|
||||
*out = *in
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]corev1.ResourceQuotaSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec.
|
||||
func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResourceQuotaSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) {
|
||||
*out = *in
|
||||
if in.AdditionalMetadata != nil {
|
||||
in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AllowedServices != nil {
|
||||
in, out := &in.AllowedServices, &out.AllowedServices
|
||||
*out = new(AllowedServices)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ExternalServiceIPs != nil {
|
||||
in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs
|
||||
*out = new(ExternalServiceIPsSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions.
|
||||
func (in *ServiceOptions) DeepCopy() *ServiceOptions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceOptions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Tenant) DeepCopyInto(out *Tenant) {
|
||||
*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 Tenant.
|
||||
func (in *Tenant) DeepCopy() *Tenant {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Tenant)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Tenant) 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 *TenantList) DeepCopyInto(out *TenantList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Tenant, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantList.
|
||||
func (in *TenantList) DeepCopy() *TenantList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TenantList) 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 *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = *in
|
||||
if in.Owners != nil {
|
||||
in, out := &in.Owners, &out.Owners
|
||||
*out = make(OwnerListSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.NamespaceOptions != nil {
|
||||
in, out := &in.NamespaceOptions, &out.NamespaceOptions
|
||||
*out = new(NamespaceOptions)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServiceOptions != nil {
|
||||
in, out := &in.ServiceOptions, &out.ServiceOptions
|
||||
*out = new(ServiceOptions)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.StorageClasses != nil {
|
||||
in, out := &in.StorageClasses, &out.StorageClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.IngressOptions.DeepCopyInto(&out.IngressOptions)
|
||||
if in.ContainerRegistries != nil {
|
||||
in, out := &in.ContainerRegistries, &out.ContainerRegistries
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
in.NetworkPolicies.DeepCopyInto(&out.NetworkPolicies)
|
||||
in.LimitRanges.DeepCopyInto(&out.LimitRanges)
|
||||
in.ResourceQuota.DeepCopyInto(&out.ResourceQuota)
|
||||
if in.AdditionalRoleBindings != nil {
|
||||
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
|
||||
*out = make([]AdditionalRoleBindingsSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ImagePullPolicies != nil {
|
||||
in, out := &in.ImagePullPolicies, &out.ImagePullPolicies
|
||||
*out = make([]ImagePullPolicySpec, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.PriorityClasses != nil {
|
||||
in, out := &in.PriorityClasses, &out.PriorityClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec.
|
||||
func (in *TenantSpec) DeepCopy() *TenantSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantStatus) DeepCopyInto(out *TenantStatus) {
|
||||
*out = *in
|
||||
if in.Namespaces != nil {
|
||||
in, out := &in.Namespaces, &out.Namespaces
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus.
|
||||
func (in *TenantStatus) DeepCopy() *TenantStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Icons made by [Roundicons](https://www.flaticon.com/authors/roundicons) from [www.flaticon.com](https://www.flaticon.com).
|
||||
BIN
assets/logo/capsule.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
101
assets/logo/capsule.svg
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 595.28 841.89" style="enable-background:new 0 0 595.28 841.89;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#274872;}
|
||||
.st1{fill:#314A70;}
|
||||
.st2{fill:#5783AB;}
|
||||
.st3{fill:#EAECEC;}
|
||||
</style>
|
||||
<path class="st0" d="M243.53,178.65c-0.06-4.5-0.37-9.02,0-13.49c0.1-1.22,2.13-3.09,3.45-3.25c6.99-0.88,14.03-1.47,21.07-1.8
|
||||
c2.43-0.12,3.48-1.05,4.29-3.12c2-5.14,4.08-10.25,6.32-15.29c0.86-1.93,0.56-2.83-1.2-4.09c-4.42-3.15-4.97-8.41-1.6-12.08
|
||||
c3.7-4.04,8.88-4.09,12.65-0.12c3.5,3.68,3.07,8.88-1.39,12.08c-1.93,1.39-2.08,2.44-1.22,4.44c2.19,5.06,3.96,10.31,6.33,15.27
|
||||
c0.65,1.37,2.73,2.73,4.28,2.89c7.57,0.77,15.19,1.17,22.79,1.64c2.69,0.16,4.13,1.28,4.21,4.15c0.1,3.95,0.43,7.89,0.66,11.84
|
||||
c-1.51,0.05-3.03,0.22-4.53,0.13c-12.54-0.76-37.47-2.65-37.47-2.65S254.81,177.52,243.53,178.65z"/>
|
||||
<g>
|
||||
<path class="st1" d="M73.32,483.91c-5.2-2.69-9.26-6.43-12.18-11.22c-2.92-4.78-4.38-10.21-4.38-16.28c0-6.07,1.46-11.5,4.38-16.28
|
||||
c2.92-4.78,6.98-8.52,12.18-11.22c5.2-2.69,11.06-4.04,17.59-4.04c6.45,0,12.09,1.35,16.91,4.04c4.82,2.7,8.33,6.55,10.53,11.56
|
||||
l-13.78,7.4c-3.19-5.62-7.78-8.43-13.78-8.43c-4.63,0-8.47,1.52-11.5,4.55c-3.04,3.04-4.55,7.17-4.55,12.41
|
||||
c0,5.24,1.52,9.38,4.55,12.41c3.04,3.04,6.87,4.55,11.5,4.55c6.07,0,10.66-2.81,13.78-8.43l13.78,7.52
|
||||
c-2.2,4.86-5.71,8.65-10.53,11.39c-4.82,2.73-10.46,4.1-16.91,4.1C84.38,487.95,78.52,486.6,73.32,483.91z"/>
|
||||
<path class="st1" d="M175.17,431.64c5.08,4.52,7.63,11.33,7.63,20.44v34.96h-16.62v-7.63c-3.34,5.69-9.56,8.54-18.67,8.54
|
||||
c-4.71,0-8.79-0.8-12.24-2.39c-3.46-1.59-6.09-3.79-7.91-6.6c-1.82-2.81-2.73-6-2.73-9.56c0-5.69,2.14-10.17,6.43-13.44
|
||||
c4.29-3.26,10.91-4.9,19.87-4.9h14.12c0-3.87-1.18-6.85-3.53-8.94c-2.35-2.09-5.88-3.13-10.59-3.13c-3.26,0-6.47,0.51-9.62,1.54
|
||||
c-3.15,1.03-5.83,2.41-8.03,4.16l-6.38-12.41c3.34-2.35,7.34-4.17,12.01-5.47c4.67-1.29,9.47-1.94,14.4-1.94
|
||||
C162.8,424.87,170.08,427.13,175.17,431.64z M160.03,473.89c2.35-1.4,4.02-3.47,5.01-6.21v-6.26h-12.18
|
||||
c-7.29,0-10.93,2.39-10.93,7.17c0,2.28,0.89,4.08,2.68,5.41c1.78,1.33,4.23,1.99,7.34,1.99
|
||||
C154.98,475.99,157.67,475.29,160.03,473.89z"/>
|
||||
<path class="st1" d="M250.6,428.8c4.67,2.62,8.33,6.3,10.99,11.04c2.66,4.75,3.99,10.27,3.99,16.57s-1.33,11.82-3.99,16.57
|
||||
c-2.66,4.75-6.32,8.43-10.99,11.04s-9.85,3.93-15.54,3.93c-7.82,0-13.97-2.47-18.45-7.4v28.58h-17.76v-83.35h16.97v7.06
|
||||
c4.4-5.31,10.82-7.97,19.24-7.97C240.76,424.87,245.94,426.18,250.6,428.8z M243.2,468.76c2.92-3.07,4.38-7.19,4.38-12.35
|
||||
s-1.46-9.28-4.38-12.35c-2.92-3.07-6.66-4.61-11.22-4.61s-8.29,1.54-11.22,4.61c-2.92,3.07-4.38,7.19-4.38,12.35
|
||||
s1.46,9.28,4.38,12.35c2.92,3.07,6.66,4.61,11.22,4.61S240.28,471.84,243.2,468.76z"/>
|
||||
<path class="st1" d="M283.11,486.07c-4.86-1.25-8.73-2.83-11.61-4.73l5.92-12.75c2.73,1.75,6.03,3.17,9.91,4.27
|
||||
c3.87,1.1,7.67,1.65,11.39,1.65c7.51,0,11.27-1.86,11.27-5.58c0-1.75-1.03-3-3.07-3.76c-2.05-0.76-5.2-1.4-9.45-1.94
|
||||
c-5.01-0.76-9.15-1.63-12.41-2.62c-3.26-0.99-6.09-2.73-8.48-5.24s-3.59-6.07-3.59-10.7c0-3.87,1.12-7.3,3.36-10.3
|
||||
c2.24-3,5.5-5.33,9.79-7c4.29-1.67,9.35-2.5,15.2-2.5c4.33,0,8.63,0.48,12.92,1.42c4.29,0.95,7.84,2.26,10.65,3.93l-5.92,12.64
|
||||
c-5.39-3.04-11.27-4.55-17.65-4.55c-3.8,0-6.64,0.53-8.54,1.59c-1.9,1.06-2.85,2.43-2.85,4.1c0,1.9,1.02,3.23,3.07,3.99
|
||||
c2.05,0.76,5.31,1.48,9.79,2.16c5.01,0.84,9.11,1.73,12.3,2.68c3.19,0.95,5.96,2.68,8.31,5.18c2.35,2.5,3.53,6,3.53,10.48
|
||||
c0,3.8-1.14,7.17-3.42,10.13c-2.28,2.96-5.6,5.26-9.96,6.89c-4.37,1.63-9.55,2.45-15.54,2.45
|
||||
C292.94,487.95,287.97,487.32,283.11,486.07z"/>
|
||||
<path class="st1" d="M399.59,425.78v61.26h-16.85v-7.29c-2.35,2.66-5.16,4.69-8.43,6.09c-3.26,1.4-6.79,2.11-10.59,2.11
|
||||
c-8.05,0-14.42-2.31-19.13-6.95c-4.71-4.63-7.06-11.5-7.06-20.61v-34.61h17.76v32c0,9.87,4.14,14.8,12.41,14.8
|
||||
c4.25,0,7.67-1.38,10.25-4.16c2.58-2.77,3.87-6.89,3.87-12.35v-30.29H399.59z"/>
|
||||
<path class="st1" d="M416.1,402.55h17.76v84.49H416.1V402.55z"/>
|
||||
<path class="st1" d="M510.04,461.42H463.7c0.83,3.8,2.81,6.79,5.92,9c3.11,2.2,6.98,3.3,11.61,3.3c3.19,0,6.01-0.47,8.48-1.42
|
||||
c2.47-0.95,4.76-2.45,6.89-4.5l9.45,10.25c-5.77,6.6-14.2,9.91-25.28,9.91c-6.91,0-13.02-1.35-18.33-4.04
|
||||
c-5.31-2.69-9.41-6.43-12.3-11.22c-2.89-4.78-4.33-10.21-4.33-16.28c0-6,1.42-11.4,4.27-16.23c2.85-4.82,6.76-8.58,11.73-11.27
|
||||
c4.97-2.69,10.53-4.04,16.68-4.04c6,0,11.42,1.29,16.28,3.87c4.86,2.58,8.67,6.28,11.44,11.1c2.77,4.82,4.16,10.42,4.16,16.79
|
||||
C510.38,456.86,510.27,458.46,510.04,461.42z M468.48,441.72c-2.73,2.28-4.4,5.39-5.01,9.34h30.17c-0.61-3.87-2.28-6.96-5.01-9.28
|
||||
c-2.73-2.31-6.07-3.47-10.02-3.47C474.59,438.3,471.21,439.44,468.48,441.72z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M144.97,316.25c2.88-4.14,5.7-8.31,8.68-12.38c0.84-1.14,2.13-1.94,3.22-2.9c8.67,2.77,17.24,5.98,26.06,8.18
|
||||
c7.28,1.81,7.49,1.33,11.08-5.55c9.52-18.28,18.99-36.58,28.42-54.91c3.55-6.9,7.04-13.85,10.34-20.87c1.87-3.99,1-5.28-3.27-5.1
|
||||
c-5.07,0.21-10.13,0.68-15.19,1.04c1.72-2.35,3.24-4.87,5.2-7.01c4.47-4.88,9.14-9.57,13.74-14.34c1.84-0.03,3.68,0.02,5.52-0.1
|
||||
c14.62-1.03,29.24-2.1,43.86-3.16c-0.08,0.84-0.24,1.68-0.24,2.52c0.01,48.41,0.03,96.83,0.05,145.24
|
||||
c-15.73,0.85-30.48,0.97-47.48-0.65c-16.01-1.04-30.66-3.54-46.6-5.49c-13.64-1.67-26.85-5.2-39.21-11.4
|
||||
c-4.77-2.4-5.86-5.41-4.24-10.45C145.16,318.1,144.96,317.14,144.97,316.25z"/>
|
||||
<path class="st3" d="M282.42,346.9c-0.02-48.41-0.04-96.83-0.05-145.24c0-0.84,0.05-1.64,0.04-2.48
|
||||
c5.63,0.1,11.47-0.06,17.08,0.32c11.35,0.78,22.67,1.83,34.01,2.77c2.69,3.09,5.47,6.1,8.05,9.28c3.38,4.17,6.61,8.47,9.9,12.71
|
||||
c-6.04-0.52-12.07-1.2-18.13-1.49c-4.12-0.2-4.91,1.24-3.08,4.81c9.87,19.27,19.73,38.54,29.65,57.78
|
||||
c4.02,7.79,8.22,15.49,12.24,23.29c1.46,2.83,3.6,3.9,6.61,3.17c11.52-2.81,23.03-5.68,34.54-8.52c1.8,3.04,3.52,6.13,5.42,9.1
|
||||
c0.89,1.39,2.13,2.56,3.21,3.83c0,0.56-0.19,1.22,0.04,1.66c3.28,6.31-0.16,9.95-5.82,12.53c-14.18,6.44-29.11,9.85-44.52,11.41
|
||||
c-12.89,1.31-25.79,2.51-38.68,3.77c-6.24,0.61-12.47,1.45-18.72,1.79c-4.58,0.24-9.2-0.17-13.81-0.3
|
||||
c-5.95-0.04-11.9-0.08-17.85-0.12L282.42,346.9z"/>
|
||||
<path class="st2" d="M413.28,303.3c-11.51,2.84-23.02,5.71-34.54,8.52c-3.01,0.74-5.15-0.34-6.61-3.17
|
||||
c-4.02-7.79-8.22-15.49-12.24-23.29c-9.92-19.24-19.79-38.51-29.65-57.78c-1.83-3.57-1.04-5.01,3.08-4.81
|
||||
c6.05,0.29,12.09,0.97,18.13,1.49c1.89,0.4,2.54,0.15,5.06,3.74c17.1,24.41,37.01,47.73,54.85,71.62
|
||||
C412.17,300.72,412.64,302.07,413.28,303.3z"/>
|
||||
<path class="st3" d="M155.06,302.38c11.51,2.84,22.26,5.47,33.78,8.28c3.01,0.74,5.15-0.34,6.61-3.17
|
||||
c4.02-7.79,8.22-15.49,12.24-23.29c9.92-19.24,17.3-37.26,26.37-56.7c1.83-3.57,0.68-4.95-3.44-4.75
|
||||
c-6.05,0.29-10.08,0.42-16.13,0.94c-2.11,1.25-2.46,1.66-3.84,3.47c-18.01,23.75-35.83,47.64-53.67,71.53
|
||||
C156.18,299.79,155.7,301.14,155.06,302.38z"/>
|
||||
<path class="st0" d="M421.92,316.24c0,0.56-0.19,1.22,0.04,1.66c3.28,6.31-0.16,9.95-5.82,12.53
|
||||
c-14.18,6.44-29.11,9.85-44.52,11.41c-12.89,1.31-25.79,2.51-38.68,3.77c-6.24,0.61-12.94,1.22-18.94,1.29
|
||||
c-4.59,0.05-8.98,0.32-13.59,0.2c-5.95-0.04-11.9-0.08-17.85-0.12c0,0-0.12-0.08-0.12-0.08c-15.36,0.35-28.73,0.35-46.17-1.19
|
||||
c-15.98-1.41-31.97-2.99-47.91-4.95c-13.64-1.67-26.85-5.2-39.21-11.4c-4.77-2.4-5.86-5.41-4.24-10.45
|
||||
c0.26-0.81,0.06-1.77,0.07-2.66c-6.55,2.47-11.33,6.45-12.86,13.75c-1.74,8.28,0.69,15.31,5.77,21.67
|
||||
c1.43,1.79,2.4,3.22,0.07,5.22c-0.71,0.61-0.81,3.27-0.15,3.89c6.36,6.04,13.89,10.11,22.37,12.36c2.35,0.62,4.12,0.02,4.62-2.85
|
||||
c0.11-0.64,1.63-1.63,2.27-1.49c8.66,1.96,17.26,4.13,25.91,6.14c1.98,0.46,2.73,1,1.52,3.01c-1.45,2.4-0.41,3.92,2,4.93
|
||||
c8.64,3.63,17.82,3.98,26.97,4.34c2.18,0.08,4.54-0.9,3.51-3.88c-1.11-3.22,0.45-3.2,2.83-2.99c8.57,0.73,17.14,1.44,25.72,1.95
|
||||
c3.13,0.19,3.98,1.04,2.41,3.98c-1.6,2.98-0.26,4.76,2.9,4.77c14.82,0.08,29.65,0.17,44.46-0.08c4.59-0.08,5.1-1.29,3.36-5.63
|
||||
c-0.84-2.1-0.97-2.87,1.76-3.02c9.16-0.52,18.32-1.21,27.45-2.12c2.5-0.25,3.06,0.34,2.55,2.56c-0.53,2.31,0.05,4.05,2.72,4.11
|
||||
c9.52,0.21,18.91-0.53,27.82-4.34c1.95-0.83,3.09-2.06,1.71-4.23c-1.72-2.71-0.09-3.15,2.17-3.67c8.24-1.87,16.46-3.83,24.64-5.93
|
||||
c1.82-0.47,3-0.77,3.21,1.6c0.26,2.99,2.1,3.32,4.53,2.61c8.11-2.36,15.55-5.98,21.6-11.99c0.69-0.69,1.03-2.99,0.55-3.39
|
||||
c-3.18-2.71-1.41-4.64,0.51-6.95C437.87,340.92,439.33,322.67,421.92,316.24z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st3" d="M324.35,192.94c-6.72-0.27-13.4-0.35-20.23-0.52c-7.13-0.17-18.9-0.51-18.9-0.51s-1.27,0.04-2.44,0
|
||||
c0,0-0.63-0.01-0.63,0.18c-0.01-5.67,0.01-11.83,0-17.5c12.58,0.95,24.65,1.94,37.19,2.72c1.5,0.09,3.29-0.07,4.8-0.12
|
||||
C324.19,182.43,324.33,187.69,324.35,192.94z"/>
|
||||
<path class="st2" d="M243.35,193.45c6.72-0.27,10.02-0.35,16.86-0.52c7.13-0.17,18.9-0.51,18.9-0.51s1.27,0.04,2.44,0
|
||||
c0,0,0.63-0.53,0.63-0.34c0.01-5.67-0.01-11.83,0-17.5c-12.58,0.95-21.28,1.94-33.82,2.72c-1.5,0.09-3.29-0.07-4.8-0.12
|
||||
C243.51,182.43,243.38,188.21,243.35,193.45z"/>
|
||||
<path class="st0" d="M327.57,193.15c-1.31-0.1-2.62-0.17-3.93-0.26c-13.33-0.32-26.66-0.63-39.99-0.95v0c-0.03,0-0.06,0-0.1,0
|
||||
c-0.03,0-0.06,0-0.1,0v0c-13.33,0.32-26.66,0.63-39.99,0.95c-1.31,0.08-2.62,0.15-3.93,0.26c-6.26,0.5-6.88,1.16-6.73,7.17
|
||||
c0.02,0.7,0.18,1.39,0.27,2.09c1.91-0.03,3.82,0.02,5.72-0.1c14.92-1.02,28.65-2.07,43.57-3.11c14.92,1.04,31.01,2.1,45.93,3.11
|
||||
c1.9,0.13,3.81,0.07,5.72,0.1c0.09-0.7,0.25-1.39,0.27-2.09C334.45,194.31,333.82,193.65,327.57,193.15z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
BIN
assets/logo/capsule_medium.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
assets/logo/capsule_raw.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
assets/logo/capsule_small.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,107 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 504.123 504.123" style="enable-background:new 0 0 504.123 504.123;" xml:space="preserve">
|
||||
<path style="fill:#2477AA;" d="M265.665,468.582c0.378-1.276,0.646-2.615,0.646-4.033v-63.827c0-7.483-5.805-13.564-13.162-14.131
|
||||
l0,0c-0.354-0.039-0.709-0.118-1.087-0.11c-7.869,0-14.249,6.372-14.249,14.241v63.835c0,1.41,0.268,2.749,0.646,4.025
|
||||
c-4.112,1.772-7.026,5.868-7.026,10.65v3.812v3.419c0,6.396,5.175,11.587,11.579,11.587h18.093c6.396,0,11.571-5.183,11.571-11.587
|
||||
v-7.231C272.675,474.451,269.777,470.355,265.665,468.582z"/>
|
||||
<ellipse style="fill:#7BC6C6;" cx="252.062" cy="85.851" rx="7.404" ry="67.245"/>
|
||||
<circle style="fill:#FF7F00;" cx="252.062" cy="269.722" r="183.863"/>
|
||||
<path style="fill:#FF5B00;" d="M252.062,85.858c101.541,0,183.863,82.322,183.863,183.863c0,101.557-82.322,183.879-183.863,183.879
|
||||
"/>
|
||||
<path style="fill:#25618E;" d="M110.222,386.733h283.672c25.647-31.051,41.173-70.719,41.874-113.971H68.348
|
||||
C69.065,316.014,84.582,355.675,110.222,386.733z"/>
|
||||
<g>
|
||||
<path style="fill:#18456D;" d="M252.062,272.762v113.971h141.832c0-0.008,0.024-0.024,0.031-0.039
|
||||
c0.055-0.063,0.095-0.142,0.165-0.197c3.411-4.151,6.609-8.476,9.657-12.926c1.166-1.686,2.198-3.45,3.277-5.167
|
||||
c1.827-2.851,3.631-5.742,5.309-8.704c1.26-2.229,2.41-4.537,3.584-6.829c1.276-2.505,2.489-5.049,3.671-7.625
|
||||
c1.197-2.67,2.355-5.38,3.426-8.113c0.874-2.221,1.678-4.474,2.465-6.735c1.087-3.119,2.15-6.246,3.072-9.429
|
||||
c0.512-1.757,0.922-3.545,1.378-5.325c3.497-13.674,5.585-27.932,5.837-42.646c0-0.079,0-0.15,0.016-0.221H252.062V272.762z"/>
|
||||
<path style="fill:#18456D;" d="M152.174,298.977c0,1.883-1.528,3.426-3.419,3.426h-28.499c-1.883,0-3.419-1.536-3.419-3.426l0,0
|
||||
c0-1.883,1.528-3.426,3.419-3.426h28.499C150.646,295.55,152.174,297.094,152.174,298.977L152.174,298.977z"/>
|
||||
<path style="fill:#18456D;" d="M152.174,318.354c0,1.883-1.528,3.419-3.419,3.419h-28.499c-1.883,0-3.419-1.528-3.419-3.419l0,0
|
||||
c0-1.89,1.528-3.426,3.419-3.426h28.499C150.646,314.927,152.174,316.463,152.174,318.354L152.174,318.354z"/>
|
||||
<path style="fill:#18456D;" d="M152.174,337.723c0,1.89-1.528,3.426-3.419,3.426h-28.499c-1.883,0-3.419-1.528-3.419-3.426l0,0
|
||||
c0-1.883,1.528-3.419,3.419-3.419h28.499C150.646,334.305,152.174,335.841,152.174,337.723L152.174,337.723z"/>
|
||||
</g>
|
||||
<path style="fill:#25618E;" d="M380.109,352.54c0,10.075-8.153,18.235-18.219,18.235h-67.253c-10.059,0-18.219-8.16-18.219-18.235
|
||||
v-45.584c0-10.067,8.16-18.227,18.219-18.227h67.253c10.067,0,18.219,8.16,18.219,18.227V352.54z"/>
|
||||
<g>
|
||||
<path style="fill:#3479A3;" d="M367.577,347.034c0,7.633-6.183,13.832-13.824,13.832h-50.987c-7.641,0-13.832-6.199-13.832-13.832
|
||||
v-34.572c0-7.633,6.191-13.824,13.832-13.824h50.987c7.641,0,13.824,6.191,13.824,13.824V347.034z"/>
|
||||
<path style="fill:#3479A3;" d="M289.666,81.865c0,7.239-5.868,13.107-13.107,13.107h-49.01c-7.231,0-13.099-5.868-13.099-13.107
|
||||
l0,0c0-7.239,5.868-13.107,13.099-13.107h49.01C283.798,68.758,289.666,74.626,289.666,81.865L289.666,81.865z"/>
|
||||
</g>
|
||||
<path style="fill:#18456D;" d="M276.559,68.758c7.239,0,13.107,5.868,13.107,13.107l0,0c0,7.239-5.868,13.107-13.107,13.107h-49.01
|
||||
c-7.231,0-13.099-5.868-13.099-13.107l0,0"/>
|
||||
<circle style="fill:#B4E7ED;" cx="252.062" cy="152.718" r="14.438"/>
|
||||
<path style="fill:#7BC6C6;" d="M252.062,138.279c7.979,0,14.438,6.467,14.438,14.438s-6.459,14.438-14.438,14.438"/>
|
||||
<circle style="fill:#B4E7ED;" cx="252.062" cy="198.309" r="14.438"/>
|
||||
<path style="fill:#7BC6C6;" d="M252.062,183.871c7.979,0,14.438,6.459,14.438,14.438c0,7.971-6.459,14.438-14.438,14.438"/>
|
||||
<circle style="fill:#B4E7ED;" cx="252.069" cy="14.438" r="14.438"/>
|
||||
<path style="fill:#7BC6C6;" d="M262.262,4.23c5.648,5.64,5.648,14.785,0.016,20.417c-5.64,5.632-14.785,5.632-20.417,0"/>
|
||||
<circle style="fill:#B4E7ED;" cx="252.062" cy="243.893" r="14.438"/>
|
||||
<g>
|
||||
<path style="fill:#7BC6C6;" d="M252.062,229.455c7.979,0,14.438,6.467,14.438,14.438s-6.459,14.438-14.438,14.438"/>
|
||||
<path style="fill:#7BC6C6;" d="M353.319,332.312c0,2.056-1.646,3.71-3.694,3.71h-13.107c-2.048,0-3.71-1.654-3.71-3.71l0,0
|
||||
c0-2.048,1.662-3.702,3.71-3.702h13.107C351.673,328.609,353.319,330.264,353.319,332.312L353.319,332.312z"/>
|
||||
</g>
|
||||
<path style="fill:#FF5B00;" d="M185.194,440.95c-0.457-18.692-14.612-33.705-32.106-33.705c-6.231,0-12.012,2.001-16.951,5.309
|
||||
C150.772,424.432,167.329,433.995,185.194,440.95z"/>
|
||||
<path style="fill:#7BC6C6;" d="M161.225,412.782c5.561,5.569,5.561,14.588,0,20.157l-45.127,45.127
|
||||
c-5.569,5.569-14.588,5.569-20.149,0l0,0c-5.561-5.553-5.561-14.58,0-20.149l45.135-45.127
|
||||
C146.637,407.221,155.656,407.221,161.225,412.782L161.225,412.782z"/>
|
||||
<path style="fill:#8DD8D6;" d="M141.084,412.782l-45.135,45.127c-1,1.008-1.78,2.143-2.41,3.34c0.228,0.276,0.433,0.583,0.693,0.851
|
||||
c5.585,5.569,14.588,5.569,20.157,0l45.127-45.127c1.016-1.008,1.764-2.143,2.41-3.34c-0.236-0.284-0.433-0.583-0.701-0.851
|
||||
C155.656,407.221,146.637,407.221,141.084,412.782z"/>
|
||||
<path style="fill:#25618E;" d="M130.859,485.888c0,10.067-8.153,18.235-18.227,18.235H84.141c-10.059,0-18.235-8.168-18.235-18.235
|
||||
v-11.39c0-10.075,8.176-18.235,18.235-18.235h28.491c10.075,0,18.227,8.16,18.227,18.235V485.888z"/>
|
||||
<path style="fill:#2477AA;" d="M117.752,457.074c-1.638-0.488-3.332-0.819-5.12-0.819H84.141c-10.059,0-18.235,8.16-18.235,18.235
|
||||
v6.018c1.638,0.48,3.332,0.819,5.128,0.819h28.491c10.075,0,18.227-8.16,18.227-18.227V457.074z"/>
|
||||
<path style="fill:#FF7F00;" d="M318.921,440.95c0.465-18.692,14.62-33.705,32.114-33.705c6.223,0,12.012,2.001,16.951,5.309
|
||||
C353.351,424.432,336.786,433.995,318.921,440.95z"/>
|
||||
<path style="fill:#7BC6C6;" d="M342.898,412.782c-5.561,5.569-5.561,14.588,0,20.157l45.127,45.127
|
||||
c5.577,5.569,14.588,5.569,20.157,0l0,0c5.561-5.553,5.561-14.58,0-20.149L363.04,412.79
|
||||
C357.486,407.221,348.459,407.221,342.898,412.782L342.898,412.782z"/>
|
||||
<path style="fill:#8DD8D6;" d="M363.032,412.782l45.143,45.127c1,1.008,1.772,2.143,2.41,3.34c-0.228,0.276-0.433,0.583-0.693,0.851
|
||||
c-5.577,5.569-14.588,5.569-20.165,0L344.6,416.973c-1.016-1.008-1.764-2.143-2.41-3.34c0.236-0.284,0.433-0.583,0.701-0.851
|
||||
C348.459,407.221,357.486,407.221,363.032,412.782z"/>
|
||||
<path style="fill:#25618E;" d="M373.264,485.888c0,10.067,8.153,18.235,18.227,18.235h28.491c10.059,0,18.235-8.168,18.235-18.235
|
||||
v-11.39c0-10.075-8.176-18.235-18.235-18.235h-28.491c-10.075,0-18.227,8.16-18.227,18.235V485.888z"/>
|
||||
<path style="fill:#2477AA;" d="M386.371,457.074c1.638-0.488,3.332-0.819,5.12-0.819h28.491c10.059,0,18.235,8.16,18.235,18.235
|
||||
v6.018c-1.638,0.48-3.332,0.819-5.128,0.819h-28.491c-10.075,0-18.227-8.16-18.227-18.227V457.074z"/>
|
||||
<path style="fill:#B4E7ED;" d="M72.428,230.747c1.788,0.591,3.679,0.985,5.671,0.985h98.013c10.067,0,18.219-8.168,18.219-18.235
|
||||
V95.232C133.152,115.468,86.221,166.896,72.428,230.747z"/>
|
||||
<path style="fill:#7BC6C6;" d="M78.1,231.731h98.013c10.067,0,18.219-8.168,18.219-18.235V95.232"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
24
charts/capsule/.helmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
README.md.gotmpl
|
||||
28
charts/capsule/Chart.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
description: A Helm chart to deploy the Capsule Operator for easily implementing,
|
||||
managing, and maintaining mutitenancy and access control in Kubernetes.
|
||||
home: https://github.com/clastix/capsule
|
||||
icon: https://github.com/clastix/capsule/raw/master/assets/logo/capsule_small.png
|
||||
keywords:
|
||||
- kubernetes
|
||||
- operator
|
||||
- multi-tenancy
|
||||
- multi-tenant
|
||||
- multitenancy
|
||||
- multitenant
|
||||
- namespace
|
||||
maintainers:
|
||||
- email: hello@clastix.io
|
||||
name: Clastix Labs Team
|
||||
name: capsule
|
||||
sources:
|
||||
- https://github.com/clastix/capsule
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.11
|
||||
|
||||
# This is the version number of the application being deployed.
|
||||
# This version number should be incremented each time you make changes to the application.
|
||||
appVersion: 0.1.2
|
||||
9
charts/capsule/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
docs: HELMDOCS_VERSION := v1.8.1
|
||||
docs: docker
|
||||
@docker run --rm -v "$$(pwd):/helm-docs" -u $$(id -u) jnorwood/helm-docs:$(HELMDOCS_VERSION)
|
||||
|
||||
docker:
|
||||
@hash docker 2>/dev/null || {\
|
||||
echo "You need docker" &&\
|
||||
exit 1;\
|
||||
}
|
||||
211
charts/capsule/README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# Deploying the Capsule Operator
|
||||
|
||||
Use the Capsule Operator for easily implementing, managing, and maintaining multitenancy and access control in Kubernetes.
|
||||
|
||||
## Requirements
|
||||
|
||||
* [Helm 3](https://github.com/helm/helm/releases) is required when installing the Capsule Operator chart. Follow Helm’s official [steps](https://helm.sh/docs/intro/install/) for installing helm on your particular operating system.
|
||||
|
||||
* A Kubernetes cluster 1.16+ with following [Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) enabled:
|
||||
|
||||
* PodNodeSelector
|
||||
* LimitRanger
|
||||
* ResourceQuota
|
||||
* MutatingAdmissionWebhook
|
||||
* ValidatingAdmissionWebhook
|
||||
|
||||
* A [`kubeconfig`](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) file accessing the Kubernetes cluster with cluster admin permissions.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The Capsule Operator Chart can be used to instantly deploy the Capsule Operator on your Kubernetes cluster.
|
||||
|
||||
1. Add this repository:
|
||||
|
||||
$ helm repo add clastix https://clastix.github.io/charts
|
||||
|
||||
2. Install the Chart:
|
||||
|
||||
$ helm install capsule clastix/capsule -n capsule-system --create-namespace
|
||||
|
||||
3. Show the status:
|
||||
|
||||
$ helm status capsule -n capsule-system
|
||||
|
||||
4. Upgrade the Chart
|
||||
|
||||
$ helm upgrade capsule clastix/capsule -n capsule-system
|
||||
|
||||
5. Uninstall the Chart
|
||||
|
||||
$ helm uninstall capsule -n capsule-system
|
||||
|
||||
## Customize the installation
|
||||
|
||||
There are two methods for specifying overrides of values during chart installation: `--values` and `--set`.
|
||||
|
||||
The `--values` option is the preferred method because it allows you to keep your overrides in a YAML file, rather than specifying them all on the command line. Create a copy of the YAML file `values.yaml` and add your overrides to it.
|
||||
|
||||
Specify your overrides file when you install the chart:
|
||||
|
||||
$ helm install capsule capsule-helm-chart --values myvalues.yaml -n capsule-system
|
||||
|
||||
The values in your overrides file `myvalues.yaml` will override their counterparts in the chart’s values.yaml file. Any values in `values.yaml` that weren’t overridden will keep their defaults.
|
||||
|
||||
If you only need to make minor customizations, you can specify them on the command line by using the `--set` option. For example:
|
||||
|
||||
$ helm install capsule capsule-helm-chart --set manager.options.forceTenantPrefix=false -n capsule-system
|
||||
|
||||
Here the values you can override:
|
||||
|
||||
### General Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| affinity | object | `{}` | Set affinity rules for the Capsule pod |
|
||||
| certManager.generateCertificates | bool | `false` | Specifies whether capsule webhooks certificates should be generated using cert-manager |
|
||||
| customAnnotations | object | `{}` | Additional annotations which will be added to all resources created by Capsule helm chart |
|
||||
| customLabels | object | `{}` | Additional labels which will be added to all resources created by Capsule helm chart |
|
||||
| jobs.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy of the helm chart job |
|
||||
| jobs.image.repository | string | `"quay.io/clastix/kubectl"` | Set the image repository of the helm chart job |
|
||||
| jobs.image.tag | string | `""` | Set the image tag of the helm chart job |
|
||||
| mutatingWebhooksTimeoutSeconds | int | `30` | Timeout in seconds for mutating webhooks |
|
||||
| nodeSelector | object | `{}` | Set the node selector for the Capsule pod |
|
||||
| podAnnotations | object | `{}` | Annotations to add to the capsule pod. |
|
||||
| podSecurityPolicy.enabled | bool | `false` | Specify if a Pod Security Policy must be created |
|
||||
| priorityClassName | string | `""` | Set the priority class name of the Capsule pod |
|
||||
| replicaCount | int | `1` | Set the replica count for capsule pod |
|
||||
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account. |
|
||||
| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. |
|
||||
| serviceAccount.name | string | `"capsule"` | The name of the service account to use. If not set and `serviceAccount.create=true`, a name is generated using the fullname template |
|
||||
| tls.create | bool | `true` | When cert-manager is disabled, Capsule will generate the TLS certificate for webhook and CRDs conversion. |
|
||||
| tls.enableController | bool | `true` | Start the Capsule controller that injects the CA into mutating and validating webhooks, and CRD as well. |
|
||||
| tls.name | string | `""` | Override name of the Capsule TLS Secret name when externally managed. |
|
||||
| tolerations | list | `[]` | Set list of tolerations for the Capsule pod |
|
||||
| validatingWebhooksTimeoutSeconds | int | `30` | Timeout in seconds for validating webhooks |
|
||||
|
||||
### Manager Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| manager.hostNetwork | bool | `false` | Specifies if the container should be started in hostNetwork mode. Required for use in some managed kubernetes clusters (such as AWS EKS) with custom CNI (such as calico), because control-plane managed by AWS cannot communicate with pods' IP CIDR and admission webhooks are not working |
|
||||
| manager.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy. |
|
||||
| manager.image.repository | string | `"clastix/capsule"` | Set the image repository of the capsule. |
|
||||
| manager.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
|
||||
| manager.imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. |
|
||||
| manager.kind | string | `"Deployment"` | Set the controller deployment mode as `Deployment` or `DaemonSet`. |
|
||||
| manager.livenessProbe | object | `{"httpGet":{"path":"/healthz","port":10080}}` | Configure the liveness probe using Deployment probe spec |
|
||||
| manager.options.capsuleUserGroups | list | `["capsule.clastix.io"]` | Override the Capsule user groups |
|
||||
| manager.options.forceTenantPrefix | bool | `false` | Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash |
|
||||
| manager.options.generateCertificates | bool | `true` | Specifies whether capsule webhooks certificates should be generated by capsule operator |
|
||||
| manager.options.logLevel | string | `"4"` | Set the log verbosity of the capsule with a value from 1 to 10 |
|
||||
| manager.options.protectedNamespaceRegex | string | `""` | If specified, disallows creation of namespaces matching the passed regexp |
|
||||
| manager.readinessProbe | object | `{"httpGet":{"path":"/readyz","port":10080}}` | Configure the readiness probe using Deployment probe spec |
|
||||
| manager.resources.limits.cpu | string | `"200m"` | |
|
||||
| manager.resources.limits.memory | string | `"128Mi"` | |
|
||||
| manager.resources.requests.cpu | string | `"200m"` | |
|
||||
| manager.resources.requests.memory | string | `"128Mi"` | |
|
||||
|
||||
### ServiceMonitor Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| serviceMonitor.annotations | object | `{}` | Assign additional Annotations |
|
||||
| serviceMonitor.enabled | bool | `false` | Enable ServiceMonitor |
|
||||
| serviceMonitor.endpoint.interval | string | `"15s"` | Set the scrape interval for the endpoint of the serviceMonitor |
|
||||
| serviceMonitor.endpoint.metricRelabelings | list | `[]` | Set metricRelabelings for the endpoint of the serviceMonitor |
|
||||
| serviceMonitor.endpoint.relabelings | list | `[]` | Set relabelings for the endpoint of the serviceMonitor |
|
||||
| serviceMonitor.endpoint.scrapeTimeout | string | `""` | Set the scrape timeout for the endpoint of the serviceMonitor |
|
||||
| serviceMonitor.labels | object | `{}` | Assign additional labels according to Prometheus' serviceMonitorSelector matching labels |
|
||||
| serviceMonitor.matchLabels | object | `{}` | Change matching labels |
|
||||
| serviceMonitor.namespace | string | `""` | Install the ServiceMonitor into a different Namespace, as the monitoring stack one (default: the release one) |
|
||||
| serviceMonitor.serviceAccount.name | string | `"capsule"` | ServiceAccount for Metrics RBAC |
|
||||
| serviceMonitor.serviceAccount.namespace | string | `"capsule-system"` | ServiceAccount Namespace for Metrics RBAC |
|
||||
| serviceMonitor.targetLabels | list | `[]` | Set targetLabels for the serviceMonitor |
|
||||
|
||||
### Webhook Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| webhooks.cordoning.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.cordoning.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
|
||||
| webhooks.cordoning.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
|
||||
| webhooks.ingresses.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.ingresses.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
|
||||
| webhooks.ingresses.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
|
||||
| webhooks.namespaceOwnerReference.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.namespaces.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.networkpolicies.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.networkpolicies.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
|
||||
| webhooks.networkpolicies.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
|
||||
| webhooks.nodes.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.persistentvolumeclaims.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.persistentvolumeclaims.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
|
||||
| webhooks.persistentvolumeclaims.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
|
||||
| webhooks.pods.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.pods.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
|
||||
| webhooks.pods.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
|
||||
| webhooks.services.failurePolicy | string | `"Fail"` | |
|
||||
| webhooks.services.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
|
||||
| webhooks.services.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
|
||||
| webhooks.tenants.failurePolicy | string | `"Fail"` | |
|
||||
|
||||
## Created resources
|
||||
|
||||
This Helm Chart creates the following Kubernetes resources in the release namespace:
|
||||
|
||||
* Capsule Namespace
|
||||
* Capsule Operator Deployment
|
||||
* Capsule Service
|
||||
* CA Secret
|
||||
* Certificate Secret
|
||||
* Tenant Custom Resource Definition
|
||||
* CapsuleConfiguration Custom Resource Definition
|
||||
* MutatingWebHookConfiguration
|
||||
* ValidatingWebHookConfiguration
|
||||
* RBAC Cluster Roles
|
||||
* Metrics Service
|
||||
|
||||
And optionally, depending on the values set:
|
||||
|
||||
* Capsule ServiceAccount
|
||||
* Capsule Service Monitor
|
||||
* PodSecurityPolicy
|
||||
* RBAC ClusterRole and RoleBinding for pod security policy
|
||||
* RBAC Role and Rolebinding for metrics scrape
|
||||
|
||||
## Notes on installing Custom Resource Definitions with Helm3
|
||||
|
||||
Capsule, as many other add-ons, defines its own set of Custom Resource Definitions (CRDs). Helm3 removed the old CRDs installation method for a more simple methodology. In the Helm Chart, there is now a special directory called `crds` to hold the CRDs. These CRDs are not templated, but will be installed by default when running a `helm install` for the chart. If the CRDs already exist (for example, you already executed `helm install`), it will be skipped with a warning. When you wish to skip the CRDs installation, and do not see the warning, you can pass the `--skip-crds` flag to the `helm install` command.
|
||||
|
||||
## Cert-Manager integration
|
||||
|
||||
You can enable the generation of certificates using `cert-manager` as follows.
|
||||
|
||||
```
|
||||
helm upgrade --install capsule clastix/capsule --namespace capsule-system --create-namespace \
|
||||
--set "certManager.generateCertificates=true" \
|
||||
--set "tls.create=false" \
|
||||
--set "tls.enableController=false"
|
||||
```
|
||||
|
||||
With the usage of `tls.enableController=false` value, you're delegating the injection of the Validating and Mutating Webhooks' CA to `cert-manager`.
|
||||
Since Helm3 doesn't allow to template _CRDs_, you have to patch manually the Custom Resource Definition `tenants.capsule.clastix.io` adding the proper annotation (YMMV).
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.5.0
|
||||
cert-manager.io/inject-ca-from: capsule-system/capsule-webhook-cert
|
||||
creationTimestamp: "2022-07-22T08:32:51Z"
|
||||
generation: 45
|
||||
name: tenants.capsule.clastix.io
|
||||
resourceVersion: "9832"
|
||||
uid: 61e287df-319b-476d-88d5-bdb8dc14d4a6
|
||||
```
|
||||
|
||||
## More
|
||||
|
||||
See Capsule [tutorial](https://github.com/clastix/capsule/blob/master/docs/content/general/tutorial.md) for more information about how to use Capsule.
|
||||
160
charts/capsule/README.md.gotmpl
Normal file
@@ -0,0 +1,160 @@
|
||||
# Deploying the Capsule Operator
|
||||
|
||||
Use the Capsule Operator for easily implementing, managing, and maintaining multitenancy and access control in Kubernetes.
|
||||
|
||||
## Requirements
|
||||
|
||||
* [Helm 3](https://github.com/helm/helm/releases) is required when installing the Capsule Operator chart. Follow Helm’s official [steps](https://helm.sh/docs/intro/install/) for installing helm on your particular operating system.
|
||||
|
||||
* A Kubernetes cluster 1.16+ with following [Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) enabled:
|
||||
|
||||
* PodNodeSelector
|
||||
* LimitRanger
|
||||
* ResourceQuota
|
||||
* MutatingAdmissionWebhook
|
||||
* ValidatingAdmissionWebhook
|
||||
|
||||
* A [`kubeconfig`](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) file accessing the Kubernetes cluster with cluster admin permissions.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The Capsule Operator Chart can be used to instantly deploy the Capsule Operator on your Kubernetes cluster.
|
||||
|
||||
1. Add this repository:
|
||||
|
||||
$ helm repo add clastix https://clastix.github.io/charts
|
||||
|
||||
2. Install the Chart:
|
||||
|
||||
$ helm install capsule clastix/capsule -n capsule-system --create-namespace
|
||||
|
||||
3. Show the status:
|
||||
|
||||
$ helm status capsule -n capsule-system
|
||||
|
||||
4. Upgrade the Chart
|
||||
|
||||
$ helm upgrade capsule clastix/capsule -n capsule-system
|
||||
|
||||
5. Uninstall the Chart
|
||||
|
||||
$ helm uninstall capsule -n capsule-system
|
||||
|
||||
## Customize the installation
|
||||
|
||||
There are two methods for specifying overrides of values during chart installation: `--values` and `--set`.
|
||||
|
||||
The `--values` option is the preferred method because it allows you to keep your overrides in a YAML file, rather than specifying them all on the command line. Create a copy of the YAML file `values.yaml` and add your overrides to it.
|
||||
|
||||
Specify your overrides file when you install the chart:
|
||||
|
||||
$ helm install capsule capsule-helm-chart --values myvalues.yaml -n capsule-system
|
||||
|
||||
The values in your overrides file `myvalues.yaml` will override their counterparts in the chart’s values.yaml file. Any values in `values.yaml` that weren’t overridden will keep their defaults.
|
||||
|
||||
If you only need to make minor customizations, you can specify them on the command line by using the `--set` option. For example:
|
||||
|
||||
$ helm install capsule capsule-helm-chart --set manager.options.forceTenantPrefix=false -n capsule-system
|
||||
|
||||
Here the values you can override:
|
||||
|
||||
|
||||
### General Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
{{- range .Values }}
|
||||
{{- if not (or (hasPrefix "manager" .Key) (hasPrefix "serviceMonitor" .Key) (hasPrefix "webhook" .Key) (hasPrefix "capsule-proxy" .Key) ) }}
|
||||
| {{ .Key }} | {{ .Type }} | {{ if .Default }}{{ .Default }}{{ else }}{{ .AutoDefault }}{{ end }} | {{ if .Description }}{{ .Description }}{{ else }}{{ .AutoDescription }}{{ end }} |
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
### Manager Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
{{- range .Values }}
|
||||
{{- if hasPrefix "manager" .Key }}
|
||||
| {{ .Key }} | {{ .Type }} | {{ if .Default }}{{ .Default }}{{ else }}{{ .AutoDefault }}{{ end }} | {{ if .Description }}{{ .Description }}{{ else }}{{ .AutoDescription }}{{ end }} |
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
### ServiceMonitor Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
{{- range .Values }}
|
||||
{{- if hasPrefix "serviceMonitor" .Key }}
|
||||
| {{ .Key }} | {{ .Type }} | {{ if .Default }}{{ .Default }}{{ else }}{{ .AutoDefault }}{{ end }} | {{ if .Description }}{{ .Description }}{{ else }}{{ .AutoDescription }}{{ end }} |
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
### Webhook Parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
{{- range .Values }}
|
||||
{{- if hasPrefix "webhook" .Key }}
|
||||
| {{ .Key }} | {{ .Type }} | {{ if .Default }}{{ .Default }}{{ else }}{{ .AutoDefault }}{{ end }} | {{ if .Description }}{{ .Description }}{{ else }}{{ .AutoDescription }}{{ end }} |
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
## Created resources
|
||||
|
||||
This Helm Chart creates the following Kubernetes resources in the release namespace:
|
||||
|
||||
* Capsule Namespace
|
||||
* Capsule Operator Deployment
|
||||
* Capsule Service
|
||||
* CA Secret
|
||||
* Certificate Secret
|
||||
* Tenant Custom Resource Definition
|
||||
* CapsuleConfiguration Custom Resource Definition
|
||||
* MutatingWebHookConfiguration
|
||||
* ValidatingWebHookConfiguration
|
||||
* RBAC Cluster Roles
|
||||
* Metrics Service
|
||||
|
||||
And optionally, depending on the values set:
|
||||
|
||||
* Capsule ServiceAccount
|
||||
* Capsule Service Monitor
|
||||
* PodSecurityPolicy
|
||||
* RBAC ClusterRole and RoleBinding for pod security policy
|
||||
* RBAC Role and Rolebinding for metrics scrape
|
||||
|
||||
## Notes on installing Custom Resource Definitions with Helm3
|
||||
|
||||
Capsule, as many other add-ons, defines its own set of Custom Resource Definitions (CRDs). Helm3 removed the old CRDs installation method for a more simple methodology. In the Helm Chart, there is now a special directory called `crds` to hold the CRDs. These CRDs are not templated, but will be installed by default when running a `helm install` for the chart. If the CRDs already exist (for example, you already executed `helm install`), it will be skipped with a warning. When you wish to skip the CRDs installation, and do not see the warning, you can pass the `--skip-crds` flag to the `helm install` command.
|
||||
|
||||
## Cert-Manager integration
|
||||
|
||||
You can enable the generation of certificates using `cert-manager` as follows.
|
||||
|
||||
```
|
||||
helm upgrade --install capsule clastix/capsule --namespace capsule-system --create-namespace \
|
||||
--set "certManager.generateCertificates=true" \
|
||||
--set "tls.create=false" \
|
||||
--set "tls.enableController=false"
|
||||
```
|
||||
|
||||
With the usage of `tls.enableController=false` value, you're delegating the injection of the Validating and Mutating Webhooks' CA to `cert-manager`.
|
||||
Since Helm3 doesn't allow to template _CRDs_, you have to patch manually the Custom Resource Definition `tenants.capsule.clastix.io` adding the proper annotation (YMMV).
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.5.0
|
||||
cert-manager.io/inject-ca-from: capsule-system/capsule-webhook-cert
|
||||
creationTimestamp: "2022-07-22T08:32:51Z"
|
||||
generation: 45
|
||||
name: tenants.capsule.clastix.io
|
||||
resourceVersion: "9832"
|
||||
uid: 61e287df-319b-476d-88d5-bdb8dc14d4a6
|
||||
```
|
||||
|
||||
## More
|
||||
|
||||
See Capsule [tutorial](https://github.com/clastix/capsule/blob/master/docs/content/general/tutorial.md) for more information about how to use Capsule.
|
||||
56
charts/capsule/crds/capsuleconfiguration-crd.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.5.0
|
||||
creationTimestamp: null
|
||||
name: capsuleconfigurations.capsule.clastix.io
|
||||
spec:
|
||||
group: capsule.clastix.io
|
||||
names:
|
||||
kind: CapsuleConfiguration
|
||||
listKind: CapsuleConfigurationList
|
||||
plural: capsuleconfigurations
|
||||
singular: capsuleconfiguration
|
||||
scope: Cluster
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: CapsuleConfiguration is the Schema for the Capsule configuration 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: CapsuleConfigurationSpec defines the Capsule configuration.
|
||||
properties:
|
||||
forceTenantPrefix:
|
||||
default: false
|
||||
description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
|
||||
type: boolean
|
||||
protectedNamespaceRegex:
|
||||
description: Disallow creation of namespaces, whose name matches this regexp
|
||||
type: string
|
||||
userGroups:
|
||||
default:
|
||||
- capsule.clastix.io
|
||||
description: Names of the groups for Capsule users.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
1268
charts/capsule/crds/tenant-crd.yaml
Normal file
19
charts/capsule/templates/NOTES.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
- Capsule Operator Helm Chart deployed:
|
||||
|
||||
# Check the capsule logs
|
||||
$ kubectl logs -f deployment/{{ template "capsule.fullname" . }}-controller-manager -c manager -n {{ .Release.Namespace }}
|
||||
|
||||
|
||||
# Check the capsule logs
|
||||
$ kubectl logs -f deployment/{{ template "capsule.fullname" . }}-controller-manager -c manager -n {{ .Release.Namespace }}
|
||||
|
||||
- Manage this chart:
|
||||
|
||||
# Upgrade Capsule
|
||||
$ helm upgrade {{ .Release.Name }} -f <values.yaml> capsule -n {{ .Release.Namespace }}
|
||||
|
||||
# Show this status again
|
||||
$ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}
|
||||
|
||||
# Uninstall Capsule
|
||||
$ helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }}
|
||||
127
charts/capsule/templates/_helpers.tpl
Normal file
@@ -0,0 +1,127 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "capsule.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "capsule.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "capsule.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "capsule.labels" -}}
|
||||
helm.sh/chart: {{ include "capsule.chart" . }}
|
||||
{{ include "capsule.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- if .Values.customLabels }}
|
||||
{{ toYaml .Values.customLabels }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "capsule.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "capsule.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
ServiceAccount annotations
|
||||
*/}}
|
||||
{{- define "capsule.serviceAccountAnnotations" -}}
|
||||
{{- if .Values.serviceAccount.annotations }}
|
||||
{{- toYaml .Values.serviceAccount.annotations }}
|
||||
{{- end }}
|
||||
{{- if .Values.customAnnotations }}
|
||||
{{ toYaml .Values.customAnnotations }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "capsule.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "capsule.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the manager fully-qualified Docker image to use
|
||||
*/}}
|
||||
{{- define "capsule.managerFullyQualifiedDockerImage" -}}
|
||||
{{- printf "%s:%s" .Values.manager.image.repository ( .Values.manager.image.tag | default (printf "v%s" .Chart.AppVersion) ) -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the proxy fully-qualified Docker image to use
|
||||
*/}}
|
||||
{{- define "capsule.proxyFullyQualifiedDockerImage" -}}
|
||||
{{- printf "%s:%s" .Values.proxy.image.repository .Values.proxy.image.tag -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Determine the Kubernetes version to use for jobsFullyQualifiedDockerImage tag
|
||||
*/}}
|
||||
{{- define "capsule.jobsTagKubeVersion" -}}
|
||||
{{- if contains "-eks-" .Capabilities.KubeVersion.GitVersion }}
|
||||
{{- print "v" .Capabilities.KubeVersion.Major "." (.Capabilities.KubeVersion.Minor | replace "+" "") -}}
|
||||
{{- else }}
|
||||
{{- print "v" .Capabilities.KubeVersion.Major "." .Capabilities.KubeVersion.Minor -}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the jobs fully-qualified Docker image to use
|
||||
*/}}
|
||||
{{- define "capsule.jobsFullyQualifiedDockerImage" -}}
|
||||
{{- if .Values.jobs.image.tag }}
|
||||
{{- printf "%s:%s" .Values.jobs.image.repository .Values.jobs.image.tag -}}
|
||||
{{- else }}
|
||||
{{- printf "%s:%s" .Values.jobs.image.repository (include "capsule.jobsTagKubeVersion" .) -}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the Capsule controller name to use
|
||||
*/}}
|
||||
{{- define "capsule.controllerName" -}}
|
||||
{{- printf "%s-controller-manager" (include "capsule.fullname" .) -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the Capsule TLS Secret name to use
|
||||
*/}}
|
||||
{{- define "capsule.secretTlsName" -}}
|
||||
{{ default ( printf "%s-tls" ( include "capsule.fullname" . ) ) .Values.tls.name }}
|
||||
{{- end }}
|
||||
36
charts/capsule/templates/certificate.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
{{- if .Values.certManager.generateCertificates }}
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Issuer
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-selfsigned
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-cert
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
dnsNames:
|
||||
- {{ include "capsule.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc
|
||||
- {{ include "capsule.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc.cluster.local
|
||||
issuerRef:
|
||||
kind: Issuer
|
||||
name: {{ include "capsule.fullname" . }}-webhook-selfsigned
|
||||
secretName: {{ include "capsule.secretTlsName" . }}
|
||||
subject:
|
||||
organizations:
|
||||
- clastix.io
|
||||
{{- end }}
|
||||
12
charts/capsule/templates/certs.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
{{- if or (not .Values.certManager.generateCertificates) (.Values.tls.create) }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "capsule.secretTlsName" . }}
|
||||
{{- end }}
|
||||
21
charts/capsule/templates/configuration-default.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: capsule.clastix.io/v1alpha1
|
||||
kind: CapsuleConfiguration
|
||||
metadata:
|
||||
name: default
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
capsule.clastix.io/mutating-webhook-configuration-name: {{ include "capsule.fullname" . }}-mutating-webhook-configuration
|
||||
capsule.clastix.io/tls-secret-name: {{ include "capsule.secretTlsName" . }}
|
||||
capsule.clastix.io/validating-webhook-configuration-name: {{ include "capsule.fullname" . }}-validating-webhook-configuration
|
||||
capsule.clastix.io/enable-tls-configuration: "{{ .Values.tls.enableController }}"
|
||||
{{- with .Values.customAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
forceTenantPrefix: {{ .Values.manager.options.forceTenantPrefix }}
|
||||
userGroups:
|
||||
{{- range .Values.manager.options.capsuleUserGroups }}
|
||||
- {{ . }}
|
||||
{{- end}}
|
||||
protectedNamespaceRegex: {{ .Values.manager.options.protectedNamespaceRegex | quote }}
|
||||
88
charts/capsule/templates/daemonset.yaml
Normal file
@@ -0,0 +1,88 @@
|
||||
{{- if eq .Values.manager.kind "DaemonSet" }}
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: {{ include "capsule.controllerName" . }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "capsule.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
|
||||
{{- if .Values.manager.hostNetwork }}
|
||||
hostNetwork: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
{{- end }}
|
||||
priorityClassName: {{ .Values.priorityClassName }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: cert
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: {{ include "capsule.secretTlsName" . }}
|
||||
containers:
|
||||
- name: manager
|
||||
command:
|
||||
- /manager
|
||||
args:
|
||||
- --enable-leader-election
|
||||
- --zap-log-level={{ default 4 .Values.manager.options.logLevel }}
|
||||
- --configuration-name=default
|
||||
image: {{ include "capsule.managerFullyQualifiedDockerImage" . }}
|
||||
imagePullPolicy: {{ .Values.manager.image.pullPolicy }}
|
||||
env:
|
||||
- name: NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- name: webhook-server
|
||||
containerPort: 9443
|
||||
protocol: TCP
|
||||
- name: metrics
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.manager.livenessProbe | nindent 12}}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.manager.readinessProbe | nindent 12}}
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||
name: cert
|
||||
readOnly: true
|
||||
resources:
|
||||
{{- toYaml .Values.manager.resources | nindent 12 }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
{{- end }}
|
||||
87
charts/capsule/templates/deployment.yaml
Normal file
@@ -0,0 +1,87 @@
|
||||
{{- if eq .Values.manager.kind "Deployment" }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "capsule.controllerName" . }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "capsule.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
|
||||
{{- if .Values.manager.hostNetwork }}
|
||||
hostNetwork: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
{{- end }}
|
||||
priorityClassName: {{ .Values.priorityClassName }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: cert
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: {{ include "capsule.secretTlsName" . }}
|
||||
containers:
|
||||
- name: manager
|
||||
command:
|
||||
- /manager
|
||||
args:
|
||||
- --enable-leader-election
|
||||
- --zap-log-level={{ default 4 .Values.manager.options.logLevel }}
|
||||
- --configuration-name=default
|
||||
image: {{ include "capsule.managerFullyQualifiedDockerImage" . }}
|
||||
imagePullPolicy: {{ .Values.manager.image.pullPolicy }}
|
||||
env:
|
||||
- name: NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
ports:
|
||||
- name: webhook-server
|
||||
containerPort: 9443
|
||||
protocol: TCP
|
||||
- name: metrics
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.manager.livenessProbe | nindent 12}}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.manager.readinessProbe | nindent 12}}
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||
name: cert
|
||||
readOnly: true
|
||||
resources:
|
||||
{{- toYaml .Values.manager.resources | nindent 12 }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
{{- end }}
|
||||
46
charts/capsule/templates/metrics-rbac.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- if .Values.serviceMonitor.labels }}
|
||||
{{- toYaml .Values.serviceMonitor.labels | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "capsule.fullname" . }}-metrics-role
|
||||
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- if .Values.serviceMonitor.labels }}
|
||||
{{- toYaml .Values.serviceMonitor.labels | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "capsule.fullname" . }}-metrics-rolebinding
|
||||
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ include "capsule.fullname" . }}-metrics-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ .Values.serviceMonitor.serviceAccount.name }}
|
||||
namespace: {{ .Values.serviceMonitor.serviceAccount.namespace | default .Release.Namespace }}
|
||||
{{- end }}
|
||||
20
charts/capsule/templates/metrics-service.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-controller-manager-metrics-service
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
name: metrics
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
{{- include "capsule.selectorLabels" . | nindent 4 }}
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
45
charts/capsule/templates/mutatingwebhookconfiguration.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: MutatingWebhookConfiguration
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-mutating-webhook-configuration
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- if .Values.certManager.generateCertificates }}
|
||||
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "capsule.fullname" . }}-webhook-cert
|
||||
{{- end }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
{{- if not .Values.certManager.generateCertificates }}
|
||||
caBundle: Cg==
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /namespace-owner-reference
|
||||
port: 443
|
||||
failurePolicy: {{ .Values.webhooks.namespaceOwnerReference.failurePolicy }}
|
||||
matchPolicy: Equivalent
|
||||
name: owner.namespace.capsule.clastix.io
|
||||
namespaceSelector: {}
|
||||
objectSelector: {}
|
||||
reinvocationPolicy: Never
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- namespaces
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.mutatingWebhooksTimeoutSeconds }}
|
||||
58
charts/capsule/templates/podsecuritypolicy.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
{{- if .Values.podSecurityPolicy.enabled }}
|
||||
kind: PodSecurityPolicy
|
||||
apiVersion: policy/v1beta1
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
fsGroup:
|
||||
rule: RunAsAny
|
||||
hostPorts:
|
||||
- max: 0
|
||||
min: 0
|
||||
runAsUser:
|
||||
rule: RunAsAny
|
||||
seLinux:
|
||||
rule: RunAsAny
|
||||
supplementalGroups:
|
||||
rule: RunAsAny
|
||||
volumes:
|
||||
- secret
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-use-psp
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- podsecuritypolicies
|
||||
resourceNames:
|
||||
- {{ include "capsule.fullname" . }}
|
||||
verbs:
|
||||
- use
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-use-psp
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "capsule.fullname" . }}-use-psp
|
||||
subjects:
|
||||
- apiGroup: ""
|
||||
kind: ServiceAccount
|
||||
name: {{ include "capsule.serviceAccountName" . }}
|
||||
{{- end }}
|
||||
49
charts/capsule/templates/post-install-job.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
{{- if .Values.tls.create }}
|
||||
{{- $cmd := printf "while [ -z $$(kubectl -n $NAMESPACE get secret %s -o jsonpath='{.data.tls\\\\.crt}') ];" (include "capsule.secretTlsName" .) -}}
|
||||
{{- $cmd = printf "%s do echo 'waiting Capsule to be up and running...' && sleep 5;" $cmd -}}
|
||||
{{- $cmd = printf "%s done" $cmd -}}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}-waiting-certs"
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
# This is what defines this resource as a hook. Without this line, the
|
||||
# job is considered part of the release.
|
||||
"helm.sh/hook": post-install
|
||||
"helm.sh/hook-weight": "-5"
|
||||
"helm.sh/hook-delete-policy": hook-succeeded
|
||||
{{- with .Values.customAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}"
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name | quote }}
|
||||
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: post-install-job
|
||||
image: {{ include "capsule.jobsFullyQualifiedDockerImage" . }}
|
||||
imagePullPolicy: {{ .Values.jobs.image.pullPolicy }}
|
||||
command: ["sh", "-c", "{{ $cmd }}"]
|
||||
env:
|
||||
- name: NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
|
||||
{{- end }}
|
||||
50
charts/capsule/templates/pre-delete-job.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
{{- $cmd := ""}}
|
||||
{{- if or (.Values.tls.create) (.Values.certManager.generateCertificates) }}
|
||||
{{- $cmd = printf "%s kubectl delete secret -n $NAMESPACE %s --ignore-not-found &&" $cmd (include "capsule.secretTlsName" .) -}}
|
||||
{{- end }}
|
||||
{{- $cmd = printf "%s kubectl delete clusterroles.rbac.authorization.k8s.io capsule-namespace-deleter capsule-namespace-provisioner --ignore-not-found &&" $cmd -}}
|
||||
{{- $cmd = printf "%s kubectl delete clusterrolebindings.rbac.authorization.k8s.io capsule-namespace-deleter capsule-namespace-provisioner --ignore-not-found" $cmd -}}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}-rbac-cleaner"
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
# This is what defines this resource as a hook. Without this line, the
|
||||
# job is considered part of the release.
|
||||
"helm.sh/hook": pre-delete
|
||||
"helm.sh/hook-weight": "-5"
|
||||
"helm.sh/hook-delete-policy": hook-succeeded
|
||||
{{- with .Values.customAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}"
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name | quote }}
|
||||
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: pre-delete-job
|
||||
image: {{ include "capsule.jobsFullyQualifiedDockerImage" . }}
|
||||
imagePullPolicy: {{ .Values.jobs.image.pullPolicy }}
|
||||
command: [ "sh", "-c", "{{ $cmd }}"]
|
||||
env:
|
||||
- name: NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
|
||||
77
charts/capsule/templates/rbac.yaml
Normal file
@@ -0,0 +1,77 @@
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-proxy-role
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- authentication.k8s.io
|
||||
resources:
|
||||
- tokenreviews
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- authorization.k8s.io
|
||||
resources:
|
||||
- subjectaccessreviews
|
||||
verbs:
|
||||
- create
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-metrics-reader
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- /metrics
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-proxy-rolebinding
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "capsule.fullname" . }}-proxy-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "capsule.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-manager-rolebinding
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "capsule.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
12
charts/capsule/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "capsule.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- if or (.Values.serviceAccount.annotations) (.Values.customAnnotations) }}
|
||||
annotations:
|
||||
{{- include "capsule.serviceAccountAnnotations" . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
47
charts/capsule/templates/servicemonitor.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-monitor
|
||||
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceMonitor.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.serviceMonitor.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
endpoints:
|
||||
{{- with .Values.serviceMonitor.endpoint }}
|
||||
- interval: {{ .interval }}
|
||||
port: metrics
|
||||
path: /metrics
|
||||
{{- with .scrapeTimeout }}
|
||||
scrapeTimeout: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .metricRelabelings }}
|
||||
metricRelabelings: {{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .relabelings }}
|
||||
relabelings: {{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
jobLabel: app.kubernetes.io/name
|
||||
{{- with .Values.serviceMonitor.targetLabels }}
|
||||
targetLabels: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- if .Values.serviceMonitor.matchLabels }}
|
||||
{{- toYaml .Values.serviceMonitor.matchLabels | nindent 6 }}
|
||||
{{- else }}
|
||||
{{- include "capsule.labels" . | nindent 6 }}
|
||||
{{- end }}
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
|
||||