Compare commits

..

380 Commits

Author SHA1 Message Date
Dario Tranchitella
86f28a0202 feat: wip on business metrics 2021-12-21 14:13:37 +01:00
Dario Tranchitella
fa8e805842 build(ci): triggering e2e also for nested files 2021-10-28 17:53:17 +02:00
Dario Tranchitella
8df66fc232 test: resources are no more pointers 2021-10-28 17:53:17 +02:00
Dario Tranchitella
c2218912eb fix: pointer doesn't trigger resources pruning 2021-10-28 17:53:17 +02:00
Tom OBrien
e361e2d424 fix: allowing regex underscore for container registry enforcement
While not best practice, underscore can be used and so should be allowed.
2021-10-27 20:55:39 +02:00
Dario Tranchitella
260b60d263 build(helm): bumping up to new Helm version 2021-10-24 17:04:58 +02:00
maxgio
e0d5e6feb2 Refactor helper script to create a Capsule user (#454)
* chore(hack/create-user.sh): let pick bash interpreter from path

bash interpreter binary could be put at different paths than /bin/bash.

Signed-off-by: maxgio92 <massimiliano.giovagnoli.1992@gmail.com>

* refactor(hack/create-user.sh): add helper function to apply dry

add helper function to check commands existence.

Signed-off-by: maxgio92 <massimiliano.giovagnoli.1992@gmail.com>
2021-10-22 20:55:52 +02:00
Adriano Pezzuto
0784dc7177 docs: add service account group to Capsule group (#450) 2021-10-15 14:57:55 +02:00
Vivek Kumar Singh
b17c6c4636 fix(helm): do not hardcode namespace forwebhook configs 2021-10-07 16:14:22 +02:00
Bright Zheng
52cf597041 docs: use one patch for each webhook 2021-10-02 17:13:20 +02:00
Bright Zheng
b8dcded882 docs: add dev env diagram 2021-10-02 17:13:20 +02:00
Bright Zheng
6a175e9017 docs: explicitly add the contribution section 2021-10-02 17:13:20 +02:00
Bright Zheng
3c609f84db docs: tune the dev setup process 2021-10-02 17:13:20 +02:00
Bright Zheng
7c3a59c4e4 feat: ignore vscode 2021-10-02 17:13:20 +02:00
Bright Zheng
d3e3b8a881 docs: review and enhance dev guide 2021-09-30 21:26:31 +02:00
Bright Zheng
7a8148bd58 docs: add dev guide 2021-09-30 21:26:31 +02:00
Bright Zheng
405d3ac52d docs: move and refactor contributing.md 2021-09-30 21:26:31 +02:00
Bright Zheng
f92acf9a9d fix: correct the make run issue 2021-09-30 21:26:31 +02:00
Pietro Terrizzi
bbb7b850d6 fix: avoid CRD reinstall 2021-09-30 21:16:04 +02:00
Maksim Fedotov
0f7284d190 fix(helm): remove matchExpressions selector from ingresses webhook 2021-09-29 09:59:12 +02:00
Alessio Greggi
7db263b2b6 fix(documentation): add link to use case velero backup restoration 2021-09-23 18:34:46 +02:00
Alessio Greggi
0a8f50f761 docs(operator): add documentation for deny wildcard hostnames 2021-09-23 18:34:46 +02:00
Gonzalo Gabriel Jiménez Fuentes
7a66e8ea93 ci: limit e2e tests to specific paths 2021-09-23 17:57:25 +02:00
Gonzalo Gabriel Jiménez Fuentes
b5eb03ea76 chore: adding auto-generated code 2021-09-23 17:57:25 +02:00
Gonzalo Gabriel Jiménez Fuentes
681b514516 ci: allowing tag creation as trigger to push helm chart 2021-09-23 17:57:25 +02:00
Maksim Fedotov
b28b98a7bc feat: namespace labeling for tenant owners. fix linting issues 2021-09-23 14:10:24 +02:00
Maksim Fedotov
f6bf0ca446 build(installer): namespace labeling for tenant owners 2021-09-23 14:10:24 +02:00
Maksim Fedotov
1081bad7cb docs: namespace labeling for tenant owners 2021-09-23 14:10:24 +02:00
Maksim Fedotov
79372c7332 build(helm): namespace labeling for tenant owners 2021-09-23 14:10:24 +02:00
Maksim Fedotov
4e8faaf845 build(kustomize): namespace labeling for tenant owners 2021-09-23 14:10:24 +02:00
Maksim Fedotov
d1b008972c test(e2e): namespace labeling for tenant owners 2021-09-23 14:10:24 +02:00
Maksim Fedotov
a14c7609df feat: namespace labeling for tenant owners 2021-09-23 14:10:24 +02:00
Gonzalo Gabriel Jiménez Fuentes
03456c0b54 fix(ci): allowing tag creation as trigger to push helm chart 2021-09-23 14:01:57 +02:00
Maksim Fedotov
ddfe2219a0 build(helm): update chart version 2021-09-23 11:39:43 +02:00
Maksim Fedotov
6b68363a46 build(helm): additional webhook configuration in chart 2021-09-23 11:39:43 +02:00
alegrey91
357834c5b9 refactor(test): switch from kubernetes version control to NoKindMatchError 2021-09-21 19:14:49 +02:00
Dario Tranchitella
085d9f6503 test(e2e): disabled Ingress wildcard annotation 2021-09-21 19:14:49 +02:00
alegrey91
196e3c910d feat: add deny-wildcard annotation 2021-09-21 19:14:49 +02:00
Bright Zheng
0039c91c23 docs: fix doc minor issues (#425) 2021-09-20 14:35:33 +02:00
Dario Tranchitella
26965a5ea2 fix: skipping indexer if error is a NoKindMatch 2021-09-17 15:43:42 +02:00
Maksim Fedotov
422b6598ba fix: check if user is a member of capsuleUserGroup instead of tenantOwner when cordoning a tenant 2021-09-15 11:14:39 +02:00
Gonzalo Gabriel Jiménez Fuentes
61e6ab4088 fix(hack): jq installation checking 2021-09-13 12:04:49 +02:00
Dario Tranchitella
94c6a64fcb fix: validating Tenant owner name when is a ServiceAccount 2021-09-04 14:17:06 +02:00
Dario Tranchitella
75ebb571e4 fix(chore): ignoring Helm tags 2021-09-01 18:18:07 +02:00
Dario Tranchitella
8f3b3eac29 fix: deleting Pods upon TLS update for HA installations 2021-09-01 18:18:07 +02:00
Dario Tranchitella
7979c256d9 chore: ready for v0.1.0 release 2021-08-23 17:09:36 +02:00
bsctl
bdafbcf90a docs: fix minor issues 2021-08-23 16:38:17 +02:00
Dario Tranchitella
d0530bbbe3 docs: updating capsule-proxy (#406) 2021-08-23 12:00:47 +02:00
Adriano Pezzuto
1035afc7fe fix(grafana): change webhook metric used in dashboard (#404) 2021-08-20 17:39:00 +02:00
Dario Tranchitella
67046c5b54 fix(hack): supporting older versions of Kubernetes for certificates 2021-08-19 18:12:02 +02:00
Pietro Terrizzi
564c4db81a docs(monitor): capsule dashboard install and steps 2021-08-19 15:11:36 +02:00
Pietro Terrizzi
30c3ab078d docs(helm): added further servicemonitor values 2021-08-19 15:11:36 +02:00
Pietro Terrizzi
e9b803b9cd docs(monitoring): added screenshots 2021-08-19 15:11:36 +02:00
bsctl
cb8e504832 docs: add general contributions lineguides for capsule-proxy 2021-08-19 13:03:10 +02:00
bsctl
713867d916 docs: documenting required new-line at the end of the file 2021-08-19 13:03:10 +02:00
bsctl
23e55c685c docs: documenting the Conventional git Commit Messages 2021-08-19 13:03:10 +02:00
Adriano Pezzuto
6393541818 build(helm): update chart and app version (#395)
* build(helm): update chart and app version

* fix(docs): helm charts values descriptions

Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2021-08-18 23:38:35 +02:00
Dario Tranchitella
c140ab076e ci(gh): adding git semantic commit message check 2021-08-18 22:08:53 +02:00
Maxim Fedotov
6b629777b7 build(helm): add customLabels and customAnnotations params (#391)
Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2021-08-17 23:24:37 +03:00
Pietro Terrizzi
5554ed5f32 feat(helm): additional labels,annotations and matchlabels 2021-08-17 18:01:19 +02:00
Pietro Terrizzi
00ef9a2f67 chore(helm): added quotes to servicemonitor ns 2021-08-17 18:01:19 +02:00
Dario Tranchitella
46c2f0e997 build(helm): enforcement of LoadBalancer service kind 2021-08-17 17:21:59 +02:00
Dario Tranchitella
0c0a90a934 build(kustomize): enforcement of LoadBalancer service kind 2021-08-17 17:21:59 +02:00
Dario Tranchitella
9d65013a22 docs: enforcement of LoadBalancer service kind 2021-08-17 17:21:59 +02:00
Dario Tranchitella
60ab33337d feat: enforcement of LoadBalancer service kind 2021-08-17 17:21:59 +02:00
Adriano Pezzuto
225d671301 Fix PriorityClasses description in CRD (#389)
* fix(kustomize): update the PriorityClasses description in CRD

* fix(helm): update the PriorityClasses description in CRD

Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2021-08-17 15:19:10 +02:00
bsctl
7538926bae docs: update README for v1beta1 2021-08-17 14:58:56 +02:00
bsctl
0de0eca72a fix(gh): upgrade release version 2021-08-17 11:25:08 +02:00
bsctl
d5a702ceae fix(hack): add signerName to CSR 2021-08-17 11:25:08 +02:00
Dario Tranchitella
a2fda44110 fix: NewIngressHostnameCollision is returning pointer for error parsing 2021-08-12 19:30:27 +02:00
Dario Tranchitella
06330cf992 fix: example was wrong due to missing porting of NamespaceOptions 2021-08-12 19:30:27 +02:00
Dario Tranchitella
1ec9936158 docs: hostname collision is now managed at Tenant level 2021-08-12 19:30:27 +02:00
Dario Tranchitella
694b519af8 build(helm): hostname collision is now managed at Tenant level 2021-08-12 19:30:27 +02:00
Dario Tranchitella
0b34f04291 build(helm): removing deprecated collision values 2021-08-12 19:30:27 +02:00
Dario Tranchitella
a702ef2af2 docs(helm): deprecating hostname collision 2021-08-12 19:30:27 +02:00
Dario Tranchitella
04d91af9f5 build(kustomize): hostname collision is now managed at Tenant level 2021-08-12 19:30:27 +02:00
Dario Tranchitella
8949be7497 test(e2e): scoped Ingress hostname and path collision 2021-08-12 19:30:27 +02:00
Dario Tranchitella
df08c9e63e refactor: hostname collision is now managed at Tenant level 2021-08-12 19:30:27 +02:00
Dario Tranchitella
07daffd669 build(helm): Ingress hostname collision scope at Tenant level 2021-08-12 19:30:27 +02:00
Dario Tranchitella
3a42b90221 build(kustomize): Ingress hostname collision scope at Tenant level 2021-08-12 19:30:27 +02:00
Dario Tranchitella
09277e9f3d feat: Ingress hostname collision scope at Tenant level 2021-08-12 19:30:27 +02:00
Dario Tranchitella
47794c0cf8 style: no need of nolint here 2021-08-12 19:30:27 +02:00
Dario Tranchitella
e24394f329 refactor: avoiding init functions for direct registration 2021-08-12 19:30:27 +02:00
Dario Tranchitella
01053d5deb refactor: renaming struct field names for allowed hostnames and classes 2021-08-12 19:30:27 +02:00
Dario Tranchitella
b749e34547 refactor: grouping Ingress options into defined struct 2021-08-12 19:30:27 +02:00
bsctl
82480f3afd docs: fix minor issues 2021-08-11 09:38:41 +02:00
bsctl
88a9c242a4 docs: update links in documentation 2021-08-11 09:38:41 +02:00
bsctl
651c62ff4a docs: add further test cases 2021-08-11 09:38:41 +02:00
bsctl
dcb8b784d5 docs: additional test cases 2021-08-11 09:38:41 +02:00
bsctl
7a698633d7 docs: additional test cases 2021-08-11 09:38:41 +02:00
bsctl
894ea5016b docs: add few test cases 2021-08-11 09:38:41 +02:00
Dario Tranchitella
e4e3283b90 build(helm): Tenant status enums must be capitalized 2021-08-11 07:28:53 +02:00
Dario Tranchitella
007f0083c2 build(kustomize): Tenant status enums must be capitalized 2021-08-11 07:28:53 +02:00
Dario Tranchitella
bc6fc920d3 fix: Tenant status enums must be capitalized 2021-08-11 07:28:53 +02:00
Dario Tranchitella
01b511b509 test(e2e): fixing flakiness for Service and EP metadata 2021-08-10 15:39:43 +02:00
Dario Tranchitella
6223b1c297 chore(github): forcing Go 1.16 and removing caching 2021-08-10 15:39:43 +02:00
Dario Tranchitella
d5158f06be chore(github): updating Kubernetes supported matrix 2021-08-10 15:39:43 +02:00
Dario Tranchitella
047f4a0ff7 build(helm): aligning descriptions for v1.22.0 2021-08-10 15:39:43 +02:00
Dario Tranchitella
71cdb45925 build(kustomize): aligning descriptions for v1.22.0 2021-08-10 15:39:43 +02:00
Dario Tranchitella
9182895811 refactor:EndpointSlice v1beta1 deprecated for v1 2021-08-10 15:39:43 +02:00
Dario Tranchitella
2eceb0935a chore(gomod): updating Kubernetes deps to 1.22 2021-08-10 15:39:43 +02:00
Dario Tranchitella
8ead555743 docs: reference to admissionregistration.k8s.io/v1 for local debugging
Starting from Kubernetes v1.22+, admissionregistration.k8s.io/v1beta1 is
deprecated and an alignement of the docs referring to outdated APIs has
been put in place.
2021-08-10 15:39:43 +02:00
Dario Tranchitella
57bf3d1c1b feat: skipping Ingress indexer setup for deprecated APIs
Starting from Kubernetes v1.22+, networking.k8s.io/v1beta1 and
extensions/v1beta1 are no more supported and indexers must not be
started, otherwise the manager would panic.
2021-08-10 15:39:43 +02:00
Dario Tranchitella
bb58e90f5d test(e2e): skipping ingress class tests if running on Kubernetes 1.22 2021-08-10 15:39:43 +02:00
Dario Tranchitella
f8fa87a998 chore(hack)!: upgrading to certificates.k8s.io/v1
Kubernetes 1.22 dropped support for certificates.k8s.io/v1beta1, v1
version has been provided since 1.19 and this must be considered as a
breaking change for users using this hack script for older versions.
2021-08-10 15:39:43 +02:00
Maxim Fedotov
b3658b7bfc refactor AdditionalMetadataSpec struct. Remove Additional prefix from labels and annotations fields (#379)
* refactor: remove 'Additional' prefix from Labels and Annotations fields in AdditionalMetadataSpec

* test(e2e): aligning tests to use updated AdditionalMetadataSpec structure

* build(kustomize): CRD update for updated v1beta1 AdditionalMetadataSpec

* build(helm): CRD update for updated v1beta1 AdditionalMetadataSpec

* build(installer): CRD update for updated v1beta1 AdditionalMetadataSpec

Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2021-08-10 12:11:16 +03:00
Maksim Fedotov
54d0201161 test(e2e): fix linting issues for NamespaceOptions tests 2021-08-09 20:25:03 +02:00
Maksim Fedotov
44ffe0ddf5 build(installer): CRD update for v1beta1 NamespaceOptions 2021-08-09 20:25:03 +02:00
Maksim Fedotov
491ab71842 build(helm): CRD update for v1beta1 NamespaceOptions 2021-08-09 20:25:03 +02:00
Maksim Fedotov
4e9dbf8690 build(kustomize): CRD update for v1beta1 NamespaceOptions 2021-08-09 20:25:03 +02:00
Maksim Fedotov
34614015a0 test(e2e): aligning tests to use new NamespaceOptions structure 2021-08-09 20:25:03 +02:00
Maksim Fedotov
737fb26e39 refactor: use NamespaceOptions struct to store namespace-related tenant configurations 2021-08-09 20:25:03 +02:00
Pietro Terrizzi
b56015922f chore(gh): using build-args 2021-08-09 11:53:43 +02:00
Maxim Fedotov
ddb9ffd79e refactor: split tenant controller to separate files
Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2021-08-07 21:37:48 +02:00
Maksim Fedotov
cae65c9f84 fix: capsuleconfiguration controller package name should be config instead of rbac 2021-08-07 20:40:08 +02:00
Dario Tranchitella
befcf65bdd feat: adding webhook and rest client latency per endpoint 2021-08-03 09:51:33 +02:00
Dario Tranchitella
e1d98334a2 chore(gh): updating e2e workflow 2021-07-28 17:34:24 +02:00
Dario Tranchitella
848c6d99c2 refactor: using goroutines per Namespace for each resource Kind reconciliation 2021-07-28 17:34:24 +02:00
Dario Tranchitella
bd12068397 fix: handling multiple resources for hard ResourceQuota resources 2021-07-24 14:36:57 +02:00
Dario Tranchitella
4604e44c37 build(helm): Tenant or Namespace scope for resource quota budgets 2021-07-24 14:36:57 +02:00
Dario Tranchitella
31863b53af build(kustomize): Tenant or Namespace scope for resource quota budgets 2021-07-24 14:36:57 +02:00
Dario Tranchitella
7a055fcb9f fix(test): matching upon reconciliation, not retrieval 2021-07-24 14:36:57 +02:00
Dario Tranchitella
29ab5ca64a test: Tenant or Namespace scope for resource quota budgets 2021-07-24 14:36:57 +02:00
Dario Tranchitella
c52f7844db feat: Tenant or Namespace scope for resource quota budgets 2021-07-24 14:36:57 +02:00
spagno
9244122d42 docs (helm): added namespace creation 2021-07-23 19:38:10 +02:00
Dario Tranchitella
f883e7b662 fix: wrong description of Service external IPs 2021-07-23 08:28:20 +02:00
Dario Tranchitella
2f5f31b678 test(e2e): allowed external IPs is grouped in ServiceOptions 2021-07-23 08:28:20 +02:00
Dario Tranchitella
e7ef9642ad build(helm): allowed external IPs is grouped in ServiceOptions 2021-07-23 08:28:20 +02:00
Dario Tranchitella
34f73af5c4 build(kustomize): allowed external IPs is grouped in ServiceOptions 2021-07-23 08:28:20 +02:00
Dario Tranchitella
18912a002b feat: allowed external IPs is grouped in ServiceOptions 2021-07-23 08:28:20 +02:00
Dario Tranchitella
d43ad2f9f8 build(kustomize): updating to v0.1.0-rc5 2021-07-23 08:28:20 +02:00
Vivek Kumar Singh
9a595877ce docs: update capsule-proxy docs
Signed-off-by: Vivek Singh <vivekkmr45@yahoo.in>
2021-07-22 12:40:03 +02:00
Dario Tranchitella
c0d4aab582 build(helm): CRD update for PriorityClass enum 2021-07-21 16:48:13 +02:00
Dario Tranchitella
6761fb93dc build(kustomize): CRD update for PriorityClass enum 2021-07-21 16:48:13 +02:00
Dario Tranchitella
bf9e0f6b10 test: PriorityClass proxy operations conversion 2021-07-21 16:48:13 +02:00
Dario Tranchitella
f937942c49 feat: capsule-proxy operations for PriorityClass resources 2021-07-21 16:48:13 +02:00
Dario Tranchitella
89d7f301c6 build(helm): CRD update for v1beta1 service options 2021-07-21 14:34:56 +02:00
Dario Tranchitella
2a6ff09340 build(kustomize): CRD update for v1beta1 service options 2021-07-21 14:34:56 +02:00
Dario Tranchitella
35f48107fc test(e2e): aligning tests to new v1beta1 structure and ExternalName case 2021-07-21 14:34:56 +02:00
Dario Tranchitella
7aa62b6f1d test: conversion for new Service options 2021-07-21 14:34:56 +02:00
Dario Tranchitella
58645f39bb chore(samples): example for ServiceOptions 2021-07-21 14:34:56 +02:00
Dario Tranchitella
0e55823a0c feat: toggling ExternalName service 2021-07-21 14:34:56 +02:00
Maksim Fedotov
ba690480a7 refactor: use OwnerListSpec to store tenant owners information 2021-07-20 11:21:40 +02:00
Maksim Fedotov
faa2306a30 chore: support multiple groups in create-{user}/{user-openshift}.sh scripts 2021-07-20 11:21:40 +02:00
Dario Tranchitella
c1448c82e9 build(installer): add description fields in CRD 2021-07-19 17:07:19 +02:00
Dario Tranchitella
776a56b5bc build(helm): add description fields in CRD 2021-07-19 17:07:19 +02:00
Dario Tranchitella
e4883bb737 build(kustomize): add description fields in CRD 2021-07-19 17:07:19 +02:00
Dario Tranchitella
e70afb5e77 feat: add description fields in CRD 2021-07-19 17:07:19 +02:00
Dario Tranchitella
ee7af18f98 docs: bare installation of Capsule using kubectl 2021-07-19 15:21:56 +02:00
Dario Tranchitella
ac7de3bf88 chore(github): updating steps for single YAML file installer diffs 2021-07-19 15:21:56 +02:00
Dario Tranchitella
8883b15aa9 chore: single YAML file installer 2021-07-19 15:21:56 +02:00
Dario Tranchitella
e23132c820 chore(kustomize): using single YAML file to install Capsule 2021-07-19 15:21:56 +02:00
Dario Tranchitella
bec59a585e build(kustomize): updating to v0.1.0-rc3 2021-07-19 15:21:56 +02:00
Dario Tranchitella
9c649ac7eb chore(kustomize): adding v1beta1 Tenant 2021-07-19 15:05:22 +02:00
Dario Tranchitella
3455aed503 fix(samples): Tenant v1beta1 example 2021-07-19 15:05:22 +02:00
Dario Tranchitella
ad1edf57ac fix(samples): removing empty file 2021-07-19 15:05:22 +02:00
Dario Tranchitella
d64dcb5a44 fix: preserving v1alpha1 enable node ports false value avoiding CRD default 2021-07-19 08:15:24 +02:00
Dave
76d7697703 docs: minor improvements 2021-07-16 17:52:16 +02:00
alegrey91
96f4f31c17 docs(velero): add brief explanation about new cli flag 2021-07-16 09:19:36 +02:00
alegrey91
c3f9dfe652 feat(velero): improve usage function 2021-07-16 09:19:36 +02:00
alegrey91
502e9a556f feat(velero): add possibility to specify a tenant list by cli 2021-07-16 09:19:36 +02:00
alegrey91
6f208a6e0e fix(velero): fix wrong argument behaviour 2021-07-16 09:19:36 +02:00
alegrey91
1fb52003d5 fix(velero): add possibility to fix also apiVersion parameter 2021-07-16 09:19:36 +02:00
Dario Tranchitella
98e1640d9b fix: avoid nil slice during resource conversion 2021-07-14 20:54:43 +02:00
Maksim Fedotov
eb19a7a89f chore: fix linting issues 2021-07-12 11:27:13 +02:00
Maksim Fedotov
db8b8ac1d9 test(e2e): support multiple tenant owners(add applications to act as tenant owners) 2021-07-12 11:27:13 +02:00
Maksim Fedotov
663ce93a3e build(helm): support multiple tenant owners(add applications to act as tenant owners) 2021-07-12 11:27:13 +02:00
Maksim Fedotov
a6408f26b0 feat: support multiple tenant owners(add applications to act as tenant owners) 2021-07-12 11:27:13 +02:00
Dario Tranchitella
1aa026c977 chore(github): no need of fundings 2021-07-08 11:36:15 +02:00
Dario Tranchitella
6008373960 bug: ensuring to update the conversion webhook CA bundle 2021-07-05 17:58:49 +02:00
Dario Tranchitella
414c03a874 feat: reconciliation for Tenant state 2021-07-05 16:28:39 +02:00
Dario Tranchitella
4d34a9e3d7 build(helm): support for Tenant state 2021-07-05 16:28:39 +02:00
Dario Tranchitella
cb9b560926 build(kustomize): support for Tenant state 2021-07-05 16:28:39 +02:00
Dario Tranchitella
ef75d0496a feat(api): Tenant state 2021-07-05 16:28:39 +02:00
alegrey91
e1e75a093b docs(velero): add documentation about velero-restore script 2021-07-05 15:53:27 +02:00
alegrey91
80143ffd50 feat(velero): add script to manage velero backup restoration 2021-07-05 15:53:27 +02:00
Dario Tranchitella
3d54810f19 chore: bump-up to latest version 2021-07-05 13:55:39 +02:00
Dario Tranchitella
09dfe33a6a bug(kustomize): fixing JSON path for kustomize-based installation 2021-07-05 13:55:39 +02:00
Dario Tranchitella
01ea36b462 chore: updating kustomize 2021-07-05 13:55:39 +02:00
Dario Tranchitella
bd448d8c29 test(e2e): avoiding flaky tests for ingress hostnames collision 2021-07-02 11:16:52 +02:00
Maksim Fedotov
b58ca3a7d7 chore: v1beta1 goimports and formatting 2021-07-02 10:14:06 +02:00
Maksim Fedotov
52fb0948cb feat(v1beta1): add conversion webhook 2021-07-02 10:14:06 +02:00
Maksim Fedotov
1b0fa587eb chore: remove unused functions for v1alpha1 version 2021-07-02 10:14:06 +02:00
Maksim Fedotov
92655f1872 build(helm): update crds to use v1beta1 version 2021-07-02 10:14:06 +02:00
Maksim Fedotov
44bf846260 test(e2e): update tests to use v1beta1 version 2021-07-02 10:14:06 +02:00
Maksim Fedotov
e6b433dcd7 feat(v1beta1): update code to use v1beta1 version 2021-07-02 10:14:06 +02:00
Dario Tranchitella
3e0882dbc8 refactor: domains is now API utils 2021-07-02 10:14:06 +02:00
Dario Tranchitella
416609362d feat(v1beta1): tenant spec
feat(v1beta1): remove unused structs and functions from v1beta1. Rename v1alpha1 structs to follow new naming. Move v1alpha1 structs to separate files
2021-07-02 10:14:06 +02:00
Dario Tranchitella
3d714dc124 build(kustomize)!: adding the conversion endpoint for v1beta1 2021-07-02 10:14:06 +02:00
Dario Tranchitella
bd01881dd3 feat(v1beta1): scaffolding the Convertible interface 2021-07-02 10:14:06 +02:00
Dario Tranchitella
ac6af13b07 feat(v1beta1): registering conversion webhook 2021-07-02 10:14:06 +02:00
Dario Tranchitella
8fb4b7d4a1 feat: scaffolding v1beta1 Tenant version 2021-07-02 10:14:06 +02:00
Dario Tranchitella
d4280b8d7e chore(makefile): ensure validation for each version 2021-07-02 10:14:06 +02:00
Dario Tranchitella
6e39b17e7c chore(operatorsdk): required scaffolding for v1alpha2 2021-07-02 10:14:06 +02:00
Dario Tranchitella
b1a9603faa fix: ensuring single reconciliation for Capsule RoleBinding resources 2021-07-01 16:34:18 +02:00
alessio
0d4201a6c2 docs(helm): update documentation about hostNetwork 2021-06-29 11:03:07 +02:00
alegrey91
1734c906a9 build(helm): add hostNetwork for manager pod 2021-06-29 11:03:07 +02:00
Dario Tranchitella
184f054f2f test(e2e): adding further tests for collisions 2021-06-27 22:40:23 +02:00
Dario Tranchitella
126449b796 build(helm): fixing pairing between values and collision CRD keys 2021-06-27 22:40:23 +02:00
Dario Tranchitella
284e7da66f build(helm): support for admission review version to v1 2021-06-27 22:36:55 +02:00
Dario Tranchitella
99e1589828 build(helm)!: using multiple handlers per webhook 2021-06-27 22:36:55 +02:00
Dario Tranchitella
7cc2c3f4e9 build(kustomize)!: using multiple handlers per webhook 2021-06-27 22:36:55 +02:00
Dario Tranchitella
ba07f99c6e refactor!: using multiple handers per route 2021-06-27 22:36:55 +02:00
Petr Ruzicka
d79972691e docs: Amazon EKS documentation 2021-06-27 21:07:41 +02:00
ptx96
25f021e151 docs(helm): described serviceMonitor parameters 2021-06-25 12:03:07 +02:00
ptx96
320353e561 fix: adapted parameters for kustomize 2021-06-25 12:03:07 +02:00
ptx96
5bd2af89e4 feat(kustomize): added capsule dashboard 2021-06-25 12:03:07 +02:00
ptx96
61d394f844 feat(helm): added serviceMonitor parameters 2021-06-25 12:03:07 +02:00
ptx96
fc8f5a08fe feat(helm): added capsule dashboard 2021-06-25 12:03:07 +02:00
ptx96
ffcb7c4408 feat: added capsule dashboard 2021-06-25 12:03:07 +02:00
Dario Tranchitella
8d1a109f1c build(helm): webhook for Namespace handling when tenant is freezed 2021-06-24 13:47:43 +02:00
Dario Tranchitella
a19045419d build(kustomize): webhook for Namespace handling when tenant is freezed 2021-06-24 13:47:43 +02:00
Dario Tranchitella
7574335a8a refactor: using separated webhooks for Namespace handling 2021-06-24 13:47:43 +02:00
Dario Tranchitella
72e97b9960 feat: providing utility for webhook auth identification 2021-06-24 13:47:43 +02:00
Dario Tranchitella
b3c6082a1e feat: providing event for Tenant cordoning 2021-06-24 13:47:43 +02:00
Dario Tranchitella
9a940096c9 docs: fixing links 2021-06-24 13:47:43 +02:00
Dario Tranchitella
f9becf39e5 docs: Tenant cordoning 2021-06-24 13:47:43 +02:00
Dario Tranchitella
e1160b8862 test(e2e): Tenant cordoning webhook 2021-06-24 13:47:43 +02:00
Dario Tranchitella
6472b221ed build(helm): Tenant cordononing webhook 2021-06-24 13:47:43 +02:00
Dario Tranchitella
a2e5bbf26d build(kustomize): Tenant cordoning webhook 2021-06-24 13:47:43 +02:00
Dario Tranchitella
8804496bb2 feat: cordoning Tenant webhook 2021-06-24 13:47:43 +02:00
Dario Tranchitella
5de0a6d712 # This is a combination of 2 commits.
# This is the 1st commit message:

feat: cordoning Tenant webhook

# The commit message #2 will be skipped:

# 5cc
2021-06-24 13:47:43 +02:00
Dario Tranchitella
531cc4cf14 refactor: renaming Tenant webhook handler 2021-06-24 13:47:43 +02:00
alessio
3e33290c4c fix: fixed typo in script description 2021-06-23 17:57:09 +02:00
alessio
824442b9ee feat: add exits when encounters an error 2021-06-23 17:57:09 +02:00
Dario Tranchitella
34583352e5 refactor: meaningful error for complete block of Service external IPs 2021-06-21 12:12:04 +02:00
Dario Tranchitella
5681228789 fix: blocking non valid external IP 2021-06-21 12:12:04 +02:00
Dario Tranchitella
7237972b80 fix: using /32 in case of bare IPs 2021-06-21 12:12:04 +02:00
Dario Tranchitella
46fc65a988 fix: avoiding concurrent map write 2021-06-16 08:49:50 +02:00
bsctl
44acfaed86 feat: fix typo in event message 2021-06-15 21:42:39 +02:00
bsctl
7ca087cac5 feat: update event messages 2021-06-15 21:42:39 +02:00
Dario Tranchitella
b2b640dc96 test(e2e): refactoring to avoid flakiness 2021-06-15 21:42:39 +02:00
Dario Tranchitella
5b35e0b0d5 refactor(e2e): using non absolute version import name 2021-06-15 21:42:39 +02:00
Dario Tranchitella
accd9ca038 feat: emitting events for policies violations 2021-06-15 21:42:39 +02:00
Dario Tranchitella
e7b33bda26 docs: documenting ImagePullPolicy enforcement 2021-06-14 10:53:55 +02:00
Dario Tranchitella
08fbd26ec8 test(e2e): bug on PodPriorityClass case 2021-06-14 10:53:55 +02:00
Dario Tranchitella
006b0c80cf test(e2e): ImagePullPolicy for v1alpha using annotations 2021-06-14 10:53:55 +02:00
Dario Tranchitella
b6f3fccbea build(helm): webhook for ImagePullPolicy enforcement 2021-06-14 10:53:55 +02:00
Dario Tranchitella
bf79c25a8a build(kustomize): webhook for image PullPolicy 2021-06-14 10:53:55 +02:00
Dario Tranchitella
630e802708 feat: image PullPolicy webhook enforcer 2021-06-14 10:53:55 +02:00
Dario Tranchitella
e5a1861cac test: aligning to new additional RoleBinding name pattern 2021-06-07 14:56:03 +02:00
Dario Tranchitella
246c1a3c2c fix: misleading info message for additional RoleBindings sync 2021-06-07 14:56:03 +02:00
Dario Tranchitella
a06e68945c fix: avoiding Namespace's RoleBinding labels collision 2021-06-07 14:56:03 +02:00
Dario Tranchitella
61c9bc647c refactor: object labels must be set in the mutateFn 2021-06-06 22:18:51 +02:00
Dario Tranchitella
9c8b0377dc feat: emitting events for Tenant operations 2021-06-06 22:18:51 +02:00
ptx96
dfe0f5ea49 chore: do docker(x) build/push step in gh actions 2021-06-05 16:12:14 +02:00
ptx96
a1a2e5e00c build(helm): using arm compatible kubectl image 2021-06-05 16:12:14 +02:00
ptx96
20aa7657e4 build: using targetarch for arm support 2021-06-05 16:12:14 +02:00
Dario Tranchitella
7c1592e739 chore(license): switching over SPDX license header (#280) 2021-06-03 19:46:20 +02:00
Dario Tranchitella
f60f2b1542 build: using Quay.io-hoested builder image 2021-06-03 18:20:47 +02:00
alessio
53377e994c docs: Updated Golang version 2021-06-03 16:25:38 +02:00
alessio
d0893a5aa9 docs: Fixed typo 2021-06-03 14:32:54 +02:00
Dario Tranchitella
a7fff597fa feat: providing log upon CapsuleConfiguration change 2021-05-31 16:15:44 +02:00
Dario Tranchitella
a4128b5744 chore(make): reorg helm params 2021-05-31 16:15:44 +02:00
Dario Tranchitella
b349042265 chore: no need of fmt or vet, already managed by golanci-lint 2021-05-31 16:15:44 +02:00
Dario Tranchitella
40bdf0cd25 test(e2e): typo on feature documentation By group 2021-05-31 16:15:44 +02:00
Dario Tranchitella
20d0ef8ed0 docs: documenting CapsuleConfiguration CRD and options 2021-05-31 16:15:44 +02:00
Dario Tranchitella
61034947fd test(e2e): modifying CapsuleConfiguration at runtime 2021-05-31 16:15:44 +02:00
Dario Tranchitella
ca7b85971b build(helm): deletion of CRB using names rather than label 2021-05-31 16:15:44 +02:00
Dario Tranchitella
73e6a17527 build(helm)!: support for CapsuleConfiguration CRD 2021-05-31 16:15:44 +02:00
Dario Tranchitella
9103a14506 build(kustomize)!: support for CapsuleConfiguration CRD 2021-05-31 16:15:44 +02:00
Dario Tranchitella
d532f1633c refactor: simplifying RBAC managed with multiple user groups 2021-05-31 16:15:44 +02:00
Dario Tranchitella
3570b02427 feat!: using CapsuleConfiguration CRD with reload at runtime 2021-05-31 16:15:44 +02:00
Dario Tranchitella
994a4c282d chore: using last git commit as build date 2021-05-31 16:15:44 +02:00
Dario Tranchitella
eff1282e34 chore: upgrading kubebuilder project to v3 2021-05-31 16:15:44 +02:00
Dario Tranchitella
52a73e011c docs: block of NodePort services using Tenant annotation 2021-05-29 00:31:17 +02:00
Dario Tranchitella
4ccef411ab docs: Pod Priority Class enforcement using Tenant annotations 2021-05-29 00:31:17 +02:00
Dario Tranchitella
dfb0a536b7 test: testing enforced Pod Priority Class using Tenant annotations 2021-05-29 00:31:17 +02:00
Dario Tranchitella
9ef64d0f8c build(helm): providing webhook for Pod Priority Class 2021-05-29 00:31:17 +02:00
Dario Tranchitella
5649283058 build(kustomize): installing Pod Priority Class webhook 2021-05-29 00:31:17 +02:00
Dario Tranchitella
0481822555 feat: enforcing Pod Priority Class 2021-05-29 00:31:17 +02:00
Dario Tranchitella
bcbd9c2781 build(helm): using different names for Job hooks 2021-05-29 00:08:07 +02:00
Maksim Fedotov
229b569b50 fix: the ClusterRoleBindings capsule-namespace-provisioner are not reconciled when --capsule-user-group changes 2021-05-28 09:32:38 +02:00
Maksim Fedotov
ef6eea62dc fix: wrong order of checks in validating-external-service-ips webhook 2021-05-27 19:27:43 +02:00
Maksim Fedotov
bb6614d1e8 chore(ci): output diff files for manifests files 2021-05-25 14:46:05 +02:00
Maksim Fedotov
784f3a71df build(helm): use multiple groups as capsule-user-group. Remove capsule clusterrolebindings using label selector 2021-05-25 14:46:05 +02:00
Maksim Fedotov
3c9895e498 feat: use multiple groups as capsule-user-group 2021-05-25 14:46:05 +02:00
Dario Tranchitella
6dc83b16da fix: generating TLS certificate matching the deployed Namespace 2021-05-23 18:46:25 +02:00
Maksim Fedotov
e6da507d10 feat: block use of NodePort Services 2021-05-19 16:44:08 +02:00
Dario Tranchitella
5bca3b7da7 chore(go): upgrading to go 1.16 2021-05-14 13:55:51 +02:00
Dario Tranchitella
2e188d26f9 chore(operatorsdk): upgrading to v3 format 2021-05-14 13:55:51 +02:00
Dario Tranchitella
3afee659ff chore(kustomize): new CRD and webhooks for admission/v1 2021-05-14 13:55:51 +02:00
Dario Tranchitella
c22cb6cc88 refactor: moving to admission/v1 for Kubernetes +1.16 2021-05-14 13:55:51 +02:00
Dario Tranchitella
202a18c132 chore(mod): upgrading controller-runtime to v0.8.3 2021-05-14 13:55:51 +02:00
Dario Tranchitella
8441d8878a chore(make): upgrading to controller-tools v0.5.0 2021-05-14 13:55:51 +02:00
Dario Tranchitella
d5af190c51 test: checking runtime count for pods 2021-05-14 13:55:51 +02:00
Dario Tranchitella
82ae78b704 chore(kustomize): deprecating metrics RBAC proxy 2021-05-08 14:51:55 +02:00
Dario Tranchitella
6c44a6a4d3 chore(helm): deprecating metrics RBAC proxy 2021-05-08 14:51:55 +02:00
Adriano Pezzuto
d6e7437b6c docs: update capsule-proxy documentation 2021-05-07 20:07:30 +02:00
Dario Tranchitella
ac7114e975 chore: triggering Helm Charts CD upon tag release 2021-05-07 16:20:33 +02:00
Tim Bannister
2fdc08c2f4 docs: typo on README.md 2021-05-05 11:48:30 +02:00
Ludovico Russo
c2cede6287 refactor: better name variables in pkg/webhook/utils 2021-05-04 17:49:13 +02:00
Ludovico Russo
36c90d485e refactor: better name variables in pkg/webhook/tenantprefix 2021-05-04 17:49:13 +02:00
Ludovico Russo
34c958371b refactor: better name variables in pkg/webhook/tenant 2021-05-04 17:49:13 +02:00
Ludovico Russo
e5f17d1e0d refactor: better name variables in pkg/webhook/services 2021-05-04 17:49:13 +02:00
Ludovico Russo
e1b203727d refactor: better name variables in pkg/webhook/registry 2021-05-04 17:49:13 +02:00
Ludovico Russo
cec8cc0573 refactor: better name variables in pkg/webhook/pvc 2021-05-04 17:49:13 +02:00
Ludovico Russo
7ca9fe0c63 refactor: better name variables in pkg/webhook/ownerreference 2021-05-04 17:49:13 +02:00
Ludovico Russo
b87a6c022f refactor: better name variables in pkg/webhook/namespacequota 2021-05-04 17:49:13 +02:00
Ludovico Russo
01b75a5094 refactor: better name variables in pkg/webhook/ingress 2021-05-04 17:49:13 +02:00
Ludovico Russo
2c6dcf0dd7 refactor: better name variables in pkg/webhook 2021-05-04 17:49:13 +02:00
Ludovico Russo
7994ae1da1 refactor: better name variables in main.go 2021-05-04 17:49:13 +02:00
stg
12237ae106 feat: adding name label to each Namespace (#242)
Co-authored-by: Santiago Sanchez Paz <sanchezpaz@gmail.com>
2021-03-24 19:28:45 +01:00
Dario Tranchitella
d8449fee24 Helm and Kustomize to v0.0.5 (#239)
* build(kustomize): ready for v0.0.5

* build(helm): ready for release v0.0.5
2021-03-20 17:25:56 +01:00
Dario Tranchitella
37ec9911d9 chore: non embedding certs for kubeconfig file generation (#238) 2021-03-17 17:28:57 +01:00
Ludovico Russo
36124d2aba build(helm): remove options allow-ingress-hostname-collision and allow-tenant-ingress-hostnames-collision (#233)
These are going to be implemented once 0.0.5 is out with new flags.

Co-authored-by: Ludovico Russo <ludovico@ludusrusso.space>
2021-03-17 11:44:57 +01:00
Dario Tranchitella
5ecabaad3e refactor: ignoring requests from kube-system ServiceAccount resources 2021-03-17 11:43:11 +01:00
Valentino Uberti
56adfe6a35 feat: user script for Openshift (#230)
Tested by @ValentinoUberti on OCP 4.7.1
2021-03-10 15:11:15 +01:00
Dario Tranchitella
4119a69e02 fix: hostname collision between different Tenant namespaces 2021-03-06 20:50:55 +01:00
Dario Tranchitella
51de469551 bug: syncing Namespace annotations in a single place 2021-03-06 17:41:18 +01:00
Dario Tranchitella
87a360bfaf build(helm): support for --allow-tenant-ingress-hostnames-collision flag 2021-03-06 16:58:44 +01:00
Dario Tranchitella
bdce4a7b4f doc: documenting --allow-tenant-ingress-hostnames-collision new CLI flag 2021-03-06 16:58:44 +01:00
Dario Tranchitella
0dedd48789 test: new flag --allow-tenant-ingress-hostnames-collision 2021-03-06 16:58:44 +01:00
Dario Tranchitella
dfb7a5e227 feat: allowing Tenants with collided Ingress hostnames
A new flag (`--allow-tenant-ingress-hostnames-collision`) is added,
defaulted to false: when toggled, Capsule will not check if each
declared hostname in `.spec.IngressHostnames.allowed` is already in use
on any other Tenant.
2021-03-06 16:58:44 +01:00
Dario Tranchitella
d78bcd8b00 test(e2e): using default timeout and interval periods 2021-03-06 15:57:25 +01:00
Dario Tranchitella
0cad87e1ed test(e2e): avoiding reaping of unhealthy nodes blocking CI 2021-03-06 15:57:25 +01:00
Dario Tranchitella
74b0594cf4 feat(helm): customizable liveness and readiness probes 2021-03-06 15:57:25 +01:00
Dario Tranchitella
7fef4e5237 bug: type-switching on Ingress webhook for hostname collision 2021-03-06 15:06:18 +01:00
Dario Tranchitella
4a7c522eb5 bug: disabling ingresses.networking.k8s.io indexers on k8s < 1.19 2021-03-06 15:06:18 +01:00
Dario Tranchitella
8319bd3a85 build(helm): support for allow Ingress hostname collision 2021-03-05 22:50:35 +01:00
Dario Tranchitella
5d3770ae8d doc: documenting --allow-ingress-hostname-collision CLI flag 2021-03-05 22:50:35 +01:00
Dario Tranchitella
3fa78ea3df test: testing Ingress hostname collision 2021-03-05 22:50:35 +01:00
Dario Tranchitella
4fbede0989 feat: Ingress hostnames collision check
Disabled by default to avoid breaking changes for upcoming release,
although minor will be enabled by default.

Using the new `--allow-ingress-hostname-collision` flag Capsule can
ignore the Ingress hostnames collision allowing the Cluster
Administrator to put in place a non-opinionated hostnames allocation.
2021-03-05 22:50:35 +01:00
Davide Imola
d7b19a4930 build: using Docker build args for build metadata (#217) 2021-03-05 21:02:41 +01:00
Dario Tranchitella
452bceff34 fix: additional metadata must be controlled just from Tenant manifest (#211) 2021-03-04 10:02:14 +01:00
Erin Corson
2ea36db5d6 fix(typo): fixing typo in several webhook error messages (#212)
Co-authored-by: Erin Corson <ecorson@vmware.com>
2021-03-04 08:24:11 +01:00
Davide Imola
737b6ce65a Fix link to script (#210) 2021-03-01 22:50:24 +01:00
Unai Arríen
666faeb72a build(helm): making customizable components Docker images (#209) 2021-03-01 17:26:43 +01:00
Don High
4f34483dee Documentation Spelling Mistakes #197 (#203)
* Update README.md

Proof Read the README.md

* Update index.md

Proof Read index.md

* Update overview.md

Proof Read overview.md

* Update onboarding.md

Proof Read onboarding.md

* Update create-namespaces.md

Proof Read create-namespaces.md

* Update permissions.md

Proof Read permissons.md

* Update resources-quota-limits.md

Proof Read resources-quota-limits.md

* Update nodes-pool.md

Proof Read nodes-pool.md

* Update ingress-classes.md

Proof Read ingress-classes.md

* Update ingress-hostnames.md

Proof Read ingress-hostnames.md

* Update storage-classes.md

Proof Read storage-classes.md

* Update images-registries.md

Proof Read images-registries.md

* Update custom-resources.md

Proof Read custom-resources.md

* Update multiple-tenants.md

Proof Read multiple-tenants.md

* Update README.md

Updated the Suggested text

* Update README.md

Made the correction

* Update docs/operator/use-cases/images-registries.md

Co-authored-by: Don High <donghigh@yahoo.com>

Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2021-02-19 11:40:20 +01:00
Erin Corson
e3b927f112 some typos and whatnot (#201)
Co-authored-by: Erin Corson <ecorson@vmware.com>
2021-02-16 22:18:02 +01:00
Dario Tranchitella
d9220f1e15 build(helm): avoiding deletion of Capsule secrets on Helm upgrade (#194) 2021-02-08 17:23:44 +01:00
Dario Tranchitella
f03e36e774 test: creating namespace and forcing upload of last built image (#195) 2021-02-08 17:19:23 +01:00
Brian Fox
7c30390206 docs: fix type in README.md (#198) 2021-02-03 16:00:18 +01:00
Dario Tranchitella
16906db309 Validating Tenant also on UPDATE (#191) 2021-01-21 07:11:59 +01:00
Adriano Pezzuto
d25ed7f2df Helm Chart icon fix (#192) 2021-01-16 14:01:13 +01:00
Dario Tranchitella
51f5bec5a6 Fixing the IngressClass return logic breaking Hostnames check (#185) 2021-01-15 09:45:09 +01:00
Dario Tranchitella
d3f3f93a24 CRD schema do not must preserving unknown fields (#188) 2021-01-15 09:44:04 +01:00
Dario Tranchitella
24bd363ee0 Updating v0.0.4 also for Kustomization installation (#186) 2021-01-14 19:22:14 +01:00
Dario Tranchitella
504241a948 Bumping Capsule to v0.0.4 (#183) 2021-01-14 00:00:02 +01:00
Dario Tranchitella
d2700556dd Adding linters and aligning code (#169)
* Adding linters and aligning code

* Aligning ingressHostnames to AllowedListSpec
2021-01-13 23:49:11 +01:00
Paolo Carta
89c66de7c6 Implementing allowed Ingress hostnames (#162)
Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2021-01-13 22:18:09 +01:00
Adriano Pezzuto
a2109b74ef add architecture diagram in readme (#182) 2021-01-07 19:23:38 +01:00
Maxim Fedotov
4dc92451ea IsInCapsuleGroup binary search is case-sensitive broken (#181)
Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2021-01-05 13:10:27 +01:00
Adriano Pezzuto
46a7a0b917 Update documentation with capsule-proxy (#179)
* update docs for capsule-proxy
* update docs with minor enhancements
* fix broken link
2021-01-02 14:20:57 +01:00
Geofrey Ernest
1ed5d703e6 Short circuit error returns (#175) 2020-12-23 15:08:15 +01:00
Dario Tranchitella
cb986384db Letting tests to accept eventually value, rather than strict expectation (#176) 2020-12-23 10:49:14 +01:00
Dario Tranchitella
49c8131eb5 Adding k8s 1.20 to E2E testing matrix and updating 1.19 to latest stable release (#171) 2020-12-20 23:47:34 +01:00
Dario Tranchitella
82bbd238fb Making tests less flaky (#172) 2020-12-20 23:29:54 +01:00
Dario Tranchitella
03eb6e633e No loop on ResourceQuota outer updates and error handling improvements (#168)
* Avoiding loop on updating outer resource quota

* Using retryOnConflict on Tenant status update

* Using errgroup instead of bare go routines

* Testing Namespace Capsule default label presence
2020-12-20 12:25:41 +01:00
Adriano Pezzuto
6e24aad094 Improve documentation (#146)
* move docs in a separate folder
* review of readme and add faq
* rewrite use cases
* more use cases
* add new project logo
* minor improvements
2020-12-15 00:03:07 +01:00
bsctl
aa6881e32e delete conflicting doc files 2020-12-15 00:00:17 +01:00
Dario Tranchitella
98e441f1e9 Enforcing Service external IPs (#161) 2020-12-11 19:17:46 +01:00
Dario Tranchitella
007bdff512 Only owner Tenant specification key is mandatory (#153)
* Only Tenant owner specification key is mandatory

* Increasing default timeout to avoid e2e flakiness on GH Actions

* Ensuring also empty Namespace annotations and labels
2020-12-11 15:47:29 +01:00
Dario Tranchitella
a3c77b3531 Enhancing Helm Chart lifecycle (#156) 2020-12-10 14:21:41 +01:00
Dario Tranchitella
3e38884a6c Annotating Tenant's Namespaces with allowed registries (#154)
* Updating allowed registries docs w/ Namespace annotations
2020-12-09 15:20:14 +01:00
Dario Tranchitella
40130696bb Annotating ResourceQuota with Hard quota (#158) 2020-12-09 15:19:16 +01:00
Dario Tranchitella
12a8c469e8 Requiring Helm Chart version 2020-12-06 02:26:37 +01:00
Dario Tranchitella
27cdd84b3b Updating Helm instructions (#149) 2020-12-01 23:43:35 +01:00
Dario Tranchitella
f6fd0cfe3f Helm Charts are now inside of the repository (#147)
* Adding Helm chart source

* Pointing to new Chart location

* Setting GitHub Action for remote Helm Chart release

* Updating Go dependencies

* Using Helm as default installation tool

* Separating diff and e2e jobs

* Aligning tests to Helm labels

* Checking fmt and vet, and fixing it

* We don't need limits on E2E
2020-12-01 23:30:31 +01:00
Dario Tranchitella
0641350575 Releasing v0.0.3 (#144) 2020-11-25 17:15:20 +01:00
Dario Tranchitella
5aed7a01d5 Enforcing container registry via list or regex (#142)
Adding also NamespaceSelector to specific webhooks in order to decrease
the chance ov breaking other critical Namespaces in case of Capsule
failures.
2020-11-24 00:40:40 +01:00
Dario Tranchitella
8442eef72b Logging timestamp to ISO 8601 (#140) 2020-11-19 07:58:24 +01:00
Dario Tranchitella
d3bc9f4870 Provide a more meaningful error message when not admitted Storage/Ingress Classes are used (#141)
* Providing further details on non allowed Storage Classes

* Providing further details on non allowed Ingress Classes
2020-11-17 23:44:08 +01:00
Dario Tranchitella
6541f19b67 Automating version pick-up according to current git version and minor Kustomize hotfixes (#135) 2020-11-17 19:20:31 +01:00
Dario Tranchitella
45709f7bd3 Providing additional print column for the nodeSelector Tenant spec (#138) 2020-11-17 16:58:03 +01:00
Dario Tranchitella
2d628e1cd0 Upgrading GitHub actions (#136)
* New KinD GitHub action

* Upgrading golangci-lint
2020-11-17 09:27:53 +01:00
Dario Tranchitella
ea599ba6e6 Supporting additional Role Bindings per Tenant (#133)
* Enabling Capsule to run on a cluster with PodSecurityPolicy enabled

* Supporting additional Role Binding per Tenant

* Documenting the additionalRoleBindings specification
2020-11-16 13:51:44 +01:00
Maxim Fedotov
078588acb5 migrating service webhook to controller p1 (#130)
migrating service webhook to controller p2

migrating service webhook to controller p3. add tests

Using an abstract reconciler to avoid copy/paste code

update tests. remove service_labels webhook. fix bug in sync labels\endpoint func

apply review notes

disable EndpointSlicesLabelsReconciler for kubernetes versions <=1.16

Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2020-11-10 19:43:30 +03:00
Dario Tranchitella
2c54d91306 Enforcing back tenant selection using closest match (#129) 2020-11-05 11:20:48 +01:00
Dario Tranchitella
0ce901f8dd Using latest current version rather than latest tag (#127) 2020-11-02 16:35:21 +01:00
Adriano Pezzuto
ff67ab3dc1 update README with Helm installation method (#126)
* update README with Helm installation method

* fix typo in README
2020-11-02 14:41:09 +01:00
Dario Tranchitella
e764b976aa Allowing dash on Tenant namespace (#118)
* Allowing dashes in the Tenant name as DNS RFC-1123

* Allowing force tenant prefix with Namespaces with dash
2020-10-31 19:43:46 +01:00
Maxim Fedotov
7ae1c0ae32 issues/115. fix endpoint creation for services with selectors when servicesMetadata is specified for tenant (#116)
apply review notes

Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2020-10-31 15:10:08 +03:00
Dario Tranchitella
39d6638669 Duplicated code, can be easily removed (#119) 2020-10-31 12:50:51 +01:00
Dario Tranchitella
2af568f0ed Making e2e tests less flaky (#121) 2020-10-31 12:28:17 +01:00
Dario Tranchitella
ee6e3aa0df Using matrix strategy for e2e on multiple k8s versions (#111)
* Using matrix strategy for e2e on multiple k8s versions

* EndpointSlice version support according to Kubernetes release

* Utility helper for testing various Kubernetes versions
2020-10-29 09:39:22 +01:00
Dario Tranchitella
a7f7c00558 Supporting ingresses.networking.k8s.io/v1 (#110)
* Updating to controller-runtime v0.7.0-alpha.4 and k8s 0.19.3

* Implementing ingresses.networking.k8s.io/v1

* Aligning to latest zap signatures
2020-10-23 21:19:14 +02:00
Dario Tranchitella
b0310cd42f Handling all the events from Storage and Ingress classes (#108) 2020-10-17 14:40:07 +02:00
Dario Tranchitella
8f3fd8dfee Removing CR/B upon Capsule removal (#102) 2020-09-23 10:40:08 +02:00
Dario Tranchitella
feec653db4 No need to add a duplicated schema (#100) 2020-09-23 09:21:03 +02:00
357 changed files with 27897 additions and 7150 deletions

12
.github/FUNDING.yml vendored
View File

@@ -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']

View File

@@ -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`)

46
.github/workflows/ci.yml vendored Normal file
View 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: latest
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.16'
- 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)"

89
.github/workflows/docker-ci.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
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 }}
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: 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 }}

59
.github/workflows/e2e.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: e2e
on:
push:
branches: [ "*" ]
paths:
- '.github/workflows/e2e.yml'
- 'api/**'
- 'controllers/**'
- 'e2e/*'
- 'Dockerfile'
- 'go.*'
- 'main.go'
- 'Makefile'
pull_request:
branches: [ "*" ]
paths:
- '.github/workflows/e2e.yml'
- 'api/**'
- 'controllers/**'
- 'e2e/*'
- 'Dockerfile'
- 'go.*'
- 'main.go'
- 'Makefile'
jobs:
kind:
name: Kubernetes
strategy:
matrix:
k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.4', 'v1.20.7', 'v1.21.2', 'v1.22.0']
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v2
with:
go-version: '^1.16'
- 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)"
- name: Installing Ginkgo
run: go get github.com/onsi/ginkgo/ginkgo
- uses: actions/setup-go@v2
with:
go-version: '^1.16'
- uses: engineerd/setup-kind@v0.5.0
with:
skipClusterCreation: true
version: v0.11.1
- uses: azure/setup-helm@v1
with:
version: 3.3.4
- name: e2e testing
run: make e2e/${{ matrix.k8s-version }}

39
.github/workflows/helm.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Helm Chart
on:
push:
branches: [ "*" ]
tags: [ "helm-v*" ]
pull_request:
branches: [ "*" ]
create:
branches: [ "*" ]
tags: [ "helm-v*" ]
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

View File

@@ -1,58 +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
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: ${{ runner.os }}-build-${{ env.cache-name }}
restore-keys: |
${{ runner.os }}-build-
${{ runner.os }}-
- 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

7
.gitignore vendored
View File

@@ -22,5 +22,10 @@ bin
*.swp
*.swo
*~
.vscode
**/*.kubeconfig
**/*.crt
**/*.key
.DS_Store
hack/*.kubeconfig

59
.golangci.yml Normal file
View File

@@ -0,0 +1,59 @@
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
maligned:
suggest-new: true
goimports:
local-prefixes: github.com/clastix/capsule
dupl:
threshold: 100
goconst:
min-len: 2
min-occurrences: 2
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- goconst
- gocritic
- gofmt
- goimports
- golint
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- misspell
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
- maligned
issues:
exclude:
- Using the variable on range scope .* in function literal
service:
golangci-lint-version: 1.33.x
run:
skip-files:
- "zz_.*\\.go$"

View File

@@ -1,5 +1,13 @@
# Build the manager binary
FROM golang:1.13 as builder
FROM golang:1.16 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

179
Makefile
View File

@@ -1,5 +1,5 @@
# 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
@@ -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:latest
IMG ?= quay.io/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,10 +24,19 @@ 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
@@ -35,81 +44,125 @@ manager: generate fmt vet
go build -o bin/manager main.go
# 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-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}\"}}\
]";
# 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-get-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
KUSTOMIZE = $(shell pwd)/bin/kustomize
kustomize: ## Download kustomize locally if necessary.
$(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7)
# go-get-tool will 'go get' any package $2 and install it to $1.
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
define go-get-tool
@[ -f $(1) ] || { \
set -e ;\
TMP_DIR=$$(mktemp -d) ;\
cd $$TMP_DIR ;\
go mod init tmp ;\
echo "Downloading $(2)" ;\
GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\
rm -rf $$TMP_DIR ;\
}
endef
# Generate bundle manifests and metadata, then validate generated files.
bundle: manifests
@@ -129,15 +182,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:
kind create cluster --name capsule --image=kindest/node:v1.18.0
e2e/%:
kind create cluster --name capsule --image=kindest/node:$*
make docker-build
kind load docker-image --nodes capsule-control-plane --name capsule quay.io/clastix/capsule:latest
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
kind load docker-image --nodes capsule-control-plane --name capsule $(IMG)
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
View File

@@ -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"

220
README.md
View File

@@ -1,10 +1,5 @@
# 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">
@@ -12,144 +7,179 @@
</a>
</p>
<p align="center">
<img src="assets/logo/capsule_medium.png" />
</p>
---
# Kubernetes multi-tenancy made easy
**Capsule** helps to implement a multi-tenancy and policy-based environment in your Kubernetes cluster. It is not intended to be yet another _PaaS_, instead, it has been designed as a micro-services-based ecosystem with the minimalist approach, leveraging only on upstream Kubernetes.
# What's the problem with the current status?
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 know phenomena of the _clusters sprawl_.
# 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.
# Entering Capsule
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 while the Capsule Policy Engine keeps the different tenants isolated from each other.
# 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.
The _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. Take a look at following diagram:
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_.
<p align="center" style="padding: 60px 20px">
<img src="assets/capsule-operator.svg" />
</p>
**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.
# Features
## Self-Service
Leave to developers the freedom to self-provision their cluster resources according to the assigned boundaries.
# 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.
## 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 legal 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.
# Common use cases for Capsule
Please, refer to the corresponding [section](./docs/operator/use-cases/overview.md) in the project documentation for a detailed list of common use cases that Capsule can address.
# Installation
Ensure you have `kubectl` and [`kustomize`](https://github.com/kubernetes-sigs/kustomize)
installed in your `PATH`. Also, make sure you have access to a Kubernetes cluster as an administrator.
Make sure you have access to a Kubernetes cluster as administrator.
There are two ways to install Capsule:
* Use the Helm Chart available [here](./charts/capsule/README.md)
* Use the [single YAML file installer](./config/install.yaml)
## Install with the single YAML file installer
Ensure you have `kubectl` installed in your `PATH`.
Clone this repository and move to the repo folder:
```
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
$ kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/master/config/install.yaml
```
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.
It will install the Capsule controller in a dedicated namespace `capsule-system`.
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.
## How to create Tenants
Use the scaffold [Tenant](config/samples/capsule_v1beta1_tenant.yaml) and simply apply as cluster admin.
You can disallow users to create namespaces matching a particular regexp by passing `--protected-namespace-regex` option with a value of regular expression.
```
$ kubectl apply -f config/samples/capsule_v1beta1_tenant.yaml
tenant.capsule.clastix.io/gas created
```
## 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.
You can check the tenant just created as
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.
```
$ kubectl get tenants
NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE
gas Active 3 0 {"kubernetes.io/os":"linux"} 25s
```
## 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.
## Tenant owners
Each tenant comes with a delegated user or group of users acting as the tenant admin. In the Capsule jargon, this 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.
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`.
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"`
Assignment to a group depends on the authentication strategy in your cluster.
Users authenticated through an _OIDC token_ must have
For example, if you are using `capsule.clastix.io`, 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 in their token:
```json
...
"users_groups": [
"/capsule.clastix.io",
"other_group"
]
"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`
The [hack/create-user.sh](hack/create-user.sh) can help you set up a dummy `kubeconfig` for the `bob` user acting as owner of a tenant called `gas`
```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
./hack/create-user.sh bob gas
...
certificatesigningrequest.certificates.k8s.io/bob-gas created
certificatesigningrequest.certificates.k8s.io/bob-gas approved
kubeconfig file is: bob-gas.kubeconfig
to use it as bob export KUBECONFIG=bob-gas.kubeconfig
```
## How to create a Tenant
Use the [scaffold Tenant](config/samples/capsule_v1alpha1_tenant.yaml)
and simply apply as Cluster Admin.
## Working with Tenants
Log in to the Kubernetes cluster as `bob` tenant owner
```
kubectl apply -f config/samples/capsule_v1alpha1_tenant.yaml
tenant.capsule.clastix.io/oil created
$ export KUBECONFIG=bob-gas.kubeconfig
```
The related Tenant owner `alice` can create Namespaces according to their assigned quota: happy Kubernetes cluster administration!
and create a couple of new namespaces
```
$ kubectl create namespace gas-production
$ kubectl create namespace gas-development
```
As user `bob` you can operate with fully admin permissions:
```
$ kubectl -n gas-development run nginx --image=docker.io/nginx
$ kubectl -n gas-development get pods
```
but limited to only your own namespaces:
```
$ kubectl -n kube-system get pods
Error from server (Forbidden): pods is forbidden:
User "bob" cannot list resource "pods" in API group "" in the namespace "kube-system"
```
# 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
$ make remove
```
# How to contribute
Any contribution is welcome! Please refer to the corresponding [section](contributing.md).
# Documentation
Please, check the project [documentation](./docs/index.md) for more cool things you can do with Capsule.
# Production Grade
# Contribution
Capsule is Open Source with Apache 2 license and any contribution is welcome.
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 corresponding docs:
- [contributing.md](./docs/contributing.md) for the general guide; and
- [dev-guide.md](./docs/dev-guide.md) for how to set up the development env to get started.
# FAQ
tbd
- Q. How to pronounce Capsule?
# Changelog
tbd
A. It should be pronounced as `/ˈkæpsjuːl/`.
# Roadmap
tbd
- 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.

View 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"`
}

View 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"`
}

View File

@@ -0,0 +1,33 @@
// 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
}

View File

@@ -0,0 +1,67 @@
// 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))
}
}
}

View 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{})
}

View File

@@ -0,0 +1,560 @@
// 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 {
var 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},
}
var 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,
}
var annotationToOwnerKindMap = map[string]capsulev1beta1.OwnerKind{
ownerUsersAnnotation: capsulev1beta1.UserOwner,
ownerGroupsAnnotation: capsulev1beta1.GroupOwner,
ownerServiceAccountAnnotation: capsulev1beta1.ServiceAccountOwner,
}
annotations := t.GetAnnotations()
var 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
var 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
}
func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
dst := dstRaw.(*capsulev1beta1.Tenant)
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, ok := t.Annotations[ingressHostnameCollisionScope]; ok {
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, ok := t.GetAnnotations()[resourceQuotaScopeAnnotation]; ok {
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
}
func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
var ownersAnnotations = map[string][]string{
ownerGroupsAnnotation: nil,
ownerUsersAnnotation: nil,
ownerServiceAccountAnnotation: nil,
}
var 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, ",")
}
}
}
func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
src := srcRaw.(*capsulev1beta1.Tenant)
// 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 {
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
t.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer)
}
// Status
t.Status = TenantStatus{
Size: src.Status.Size,
Namespaces: src.Status.Namespaces,
}
return nil
}

View File

@@ -0,0 +1,384 @@
// 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"
)
func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
var namespaceQuota int32 = 5
var nodeSelector = map[string]string{
"foo": "bar",
}
var v1alpha1AdditionalMetadataSpec = &AdditionalMetadataSpec{
AdditionalLabels: map[string]string{
"foo": "bar",
},
AdditionalAnnotations: map[string]string{
"foo": "bar",
},
}
var v1alpha1AllowedListSpec = &AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
}
var v1beta1AdditionalMetadataSpec = &capsulev1beta1.AdditionalMetadataSpec{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
}
var v1beta1NamespaceOptions = &capsulev1beta1.NamespaceOptions{
Quota: &namespaceQuota,
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
}
var 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"},
},
}
var v1beta1AllowedListSpec = &capsulev1beta1.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
}
var 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",
},
},
},
},
},
},
}
var 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"),
},
},
},
},
}
var 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,
},
},
}
var 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: "rbac.authorization.k8s.io",
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"},
},
}
var 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: "rbac.authorization.k8s.io",
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) {
var 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) {
var v1alpha1ConvertedTnt = Tenant{}
v1alpha1Tnt, v1beta1tnt := generateTenantsSpecs()
err := v1alpha1ConvertedTnt.ConvertFrom(&v1beta1tnt)
if assert.NoError(t, err) {
assert.EqualValues(t, v1alpha1Tnt, v1alpha1ConvertedTnt)
}
}

View File

@@ -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
}

View 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"`
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
View 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)
}

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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,8 +9,19 @@ 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) {

View File

@@ -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,6 +22,8 @@ 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)
}

View File

@@ -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
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"`
}
// 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)
//+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"`
}
// 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,6 +42,7 @@ 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

View 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()
}

View File

@@ -1,20 +1,7 @@
// +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 +9,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 +33,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 +196,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 +259,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 +303,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 +322,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 +351,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)
}

View 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"`
}

View 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"`
}

View File

@@ -0,0 +1,33 @@
// 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
}

View File

@@ -0,0 +1,67 @@
// 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))
}
}
}

View File

@@ -0,0 +1,15 @@
// 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
}

View File

@@ -0,0 +1,33 @@
// 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
}

View File

@@ -0,0 +1,67 @@
// 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))
}
}
}

View 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
)

View 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

View 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)
}

View 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"`
}

View 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"`
}

View File

@@ -0,0 +1,51 @@
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],
}
}

View 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
View 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"
)

34
api/v1beta1/owner_list.go Normal file
View File

@@ -0,0 +1,34 @@
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]
}

View File

@@ -0,0 +1,83 @@
package v1beta1
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOwnerListSpec_FindOwner(t *testing.T) {
var bla = OwnerSpec{
Kind: UserOwner,
Name: "bla",
ProxyOperations: []ProxySettings{
{
Kind: IngressClassesProxy,
Operations: []ProxyOperation{"Delete"},
},
},
}
var bar = OwnerSpec{
Kind: GroupOwner,
Name: "bar",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"Delete"},
},
},
}
var baz = OwnerSpec{
Kind: UserOwner,
Name: "baz",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"Update"},
},
},
}
var fim = OwnerSpec{
Kind: ServiceAccountOwner,
Name: "fim",
ProxyOperations: []ProxySettings{
{
Kind: NodesProxy,
Operations: []ProxyOperation{"List"},
},
},
}
var bom = OwnerSpec{
Kind: GroupOwner,
Name: "bom",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"Delete"},
},
{
Kind: NodesProxy,
Operations: []ProxyOperation{"Delete"},
},
},
}
var qip = OwnerSpec{
Kind: ServiceAccountOwner,
Name: "qip",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"List", "Delete"},
},
},
}
var 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{})
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View File

@@ -0,0 +1,29 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
import (
"fmt"
)
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"
)
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()
}

View File

@@ -0,0 +1,42 @@
// 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
}

View File

@@ -0,0 +1,31 @@
// 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
default:
err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v)
}
return
}

View 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"`
}

View File

@@ -0,0 +1,72 @@
// 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 namesapces 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 NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies 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"`
}
//+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{})
}

View File

@@ -0,0 +1,533 @@
// +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 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
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

101
assets/logo/capsule.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
assets/logo/capsule_raw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,23 @@
# 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/

28
charts/capsule/Chart.yaml Normal file
View 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.3
# 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.0

132
charts/capsule/README.md Normal file
View File

@@ -0,0 +1,132 @@
# 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 Helms 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. Create the Namespace:
$ kubectl create namespace capsule-system
3. Install the Chart:
$ helm install capsule clastix/capsule -n capsule-system
4. Show the status:
$ helm status capsule -n capsule-system
5. Upgrade the Chart
$ helm upgrade capsule clastix/capsule -n capsule-system
6. 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 charts values.yaml file. Any values in `values.yaml` that werent 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 force_tenant_prefix=false -n capsule-system
Here the values you can override:
Parameter | Description | Default
--- | --- | ---
`manager.hostNetwork` | Specifies if the container should be started in `hostNetwork` mode. | `false`
`manager.options.logLevel` | Set the log verbosity of the controller with a value from 1 to 10.| `4`
`manager.options.forceTenantPrefix` | Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash | `false`
`manager.options.capsuleUserGroups` | Override the Capsule user groups | `[capsule.clastix.io]`
`manager.options.protectedNamespaceRegex` | If specified, disallows creation of namespaces matching the passed regexp | `null`
`manager.image.repository` | Set the image repository of the controller. | `quay.io/clastix/capsule`
`manager.image.tag` | Overrides the image tag whose default is the chart. `appVersion` | `null`
`manager.image.pullPolicy` | Set the image pull policy. | `IfNotPresent`
`manager.livenessProbe` | Configure the liveness probe using Deployment probe spec | `GET :10080/healthz`
`manager.readinessProbe` | Configure the readiness probe using Deployment probe spec | `GET :10080/readyz`
`manager.resources.requests/cpu` | Set the CPU requests assigned to the controller. | `200m`
`manager.resources.requests/memory` | Set the memory requests assigned to the controller. | `128Mi`
`manager.resources.limits/cpu` | Set the CPU limits assigned to the controller. | `200m`
`manager.resources.limits/cpu` | Set the memory limits assigned to the controller. | `128Mi`
`mutatingWebhooksTimeoutSeconds` | Timeout in seconds for mutating webhooks. | `30`
`validatingWebhooksTimeoutSeconds` | Timeout in seconds for validating webhooks. | `30`
`imagePullSecrets` | Configuration for `imagePullSecrets` so that you can use a private images registry. | `[]`
`serviceAccount.create` | Specifies whether a service account should be created. | `true`
`serviceAccount.annotations` | Annotations to add to the service account. | `{}`
`serviceAccount.name` | The name of the service account to use. If not set and `serviceAccount.create=true`, a name is generated using the fullname template | `capsule`
`podAnnotations` | Annotations to add to the Capsule pod. | `{}`
`priorityClassName` | Set the priority class name of the Capsule pod. | `null`
`nodeSelector` | Set the node selector for the Capsule pod. | `{}`
`tolerations` | Set list of tolerations for the Capsule pod. | `[]`
`replicaCount` | Set the replica count for Capsule pod. | `1`
`affinity` | Set affinity rules for the Capsule pod. | `{}`
`podSecurityPolicy.enabled` | Specify if a Pod Security Policy must be created. | `false`
`serviceMonitor.enabled` | Specifies if a service monitor must be created. | `false`
`serviceMonitor.labels` | Additional labels which will be added to service monitor. | `{}`
`serviceMonitor.annotations` | Additional annotations which will be added to service monitor. | `{}`
`serviceMonitor.matchLabels` | Additional matchLabels which will be added to service monitor. | `{}`
`serviceMonitor.serviceAccount.name` | Specifies service account name for metrics scrape. | `capsule`
`serviceMonitor.serviceAccount.namespace` | Specifies service account namespace for metrics scrape. | `capsule-system`
`customLabels` | Additional labels which will be added to all resources created by Capsule helm chart . | `{}`
`customAnnotations` | Additional annotations which will be added to all resources created by Capsule helm chart . | `{}`
## 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
* 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.
## More
See Capsule [use cases](https://github.com/clastix/capsule/blob/master/use_cases.md) for more information about how to use Capsule.

View 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: []

File diff suppressed because it is too large Load Diff

View 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 }}

View File

@@ -0,0 +1,120 @@
{{/*
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 }}
{{/*
Create the jobs fully-qualified Docker image to use
*/}}
{{- define "capsule.jobsFullyQualifiedDockerImage" -}}
{{- printf "%s:%s" .Values.jobs.image.repository .Values.jobs.image.tag -}}
{{- end }}
{{/*
Create the Capsule Deployment name to use
*/}}
{{- define "capsule.deploymentName" -}}
{{- printf "%s-controller-manager" (include "capsule.fullname" .) -}}
{{- end }}
{{/*
Create the Capsule CA Secret name to use
*/}}
{{- define "capsule.secretCaName" -}}
{{- printf "%s-ca" (include "capsule.fullname" .) -}}
{{- end }}
{{/*
Create the Capsule TLS Secret name to use
*/}}
{{- define "capsule.secretTlsName" -}}
{{- printf "%s-tls" (include "capsule.fullname" .) -}}
{{- end }}

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
name: {{ include "capsule.secretCaName" . }}
data:

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
name: {{ include "capsule.secretTlsName" . }}
data:

View File

@@ -0,0 +1,17 @@
apiVersion: capsule.clastix.io/v1alpha1
kind: CapsuleConfiguration
metadata:
name: default
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
forceTenantPrefix: {{ .Values.manager.options.forceTenantPrefix }}
userGroups:
{{- range .Values.manager.options.capsuleUserGroups }}
- {{ . }}
{{- end}}
protectedNamespaceRegex: {{ .Values.manager.options.protectedNamespaceRegex | quote }}

View File

@@ -0,0 +1,84 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "capsule.deploymentName" . }}
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
{{- 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.fullname" . }}-tls
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

View 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 }}

View 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

View File

@@ -0,0 +1,40 @@
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: {{ include "capsule.fullname" . }}-mutating-webhook-configuration
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
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 }}

View 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 }}

View File

@@ -0,0 +1,39 @@
{{- $cmd := "while [ -z $$(kubectl -n $NAMESPACE get secret capsule-tls -o jsonpath='{.data.tls\\\\.crt}') ];" -}}
{{- $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:
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" . }}

View File

@@ -0,0 +1,40 @@
{{- $cmd := printf "kubectl scale deployment -n $NAMESPACE %s --replicas 0 &&" (include "capsule.deploymentName" .) -}}
{{- $cmd = printf "%s kubectl delete secret -n $NAMESPACE %s %s --ignore-not-found &&" $cmd (include "capsule.secretTlsName" .) (include "capsule.secretCaName" .) -}}
{{- $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:
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" . }}

View 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 }}

View 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 }}

View File

@@ -0,0 +1,31 @@
{{- 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:
- interval: 15s
port: metrics
path: /metrics
jobLabel: app.kubernetes.io/name
selector:
matchLabels:
{{- include "capsule.labels" . | nindent 6 }}
{{- with .Values.serviceMonitor.matchLabels }}
{{- toYaml . | nindent 6 }}
{{- end }}
namespaceSelector:
matchNames:
- {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,242 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: {{ include "capsule.fullname" . }}-validating-webhook-configuration
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /cordoning
port: 443
failurePolicy: {{ .Values.webhooks.cordoning.failurePolicy }}
matchPolicy: Equivalent
name: cordoning.tenant.capsule.clastix.io
namespaceSelector:
{{- toYaml .Values.webhooks.cordoning.namespaceSelector | nindent 4}}
objectSelector: {}
rules:
- apiGroups:
- '*'
apiVersions:
- '*'
operations:
- CREATE
- UPDATE
- DELETE
resources:
- '*'
scope: Namespaced
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /ingresses
port: 443
failurePolicy: {{ .Values.webhooks.ingresses.failurePolicy }}
matchPolicy: Equivalent
name: ingress.capsule.clastix.io
namespaceSelector:
{{- toYaml .Values.webhooks.ingresses.namespaceSelector | nindent 4}}
objectSelector: {}
rules:
- apiGroups:
- networking.k8s.io
- extensions
apiVersions:
- v1
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- ingresses
scope: Namespaced
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /namespaces
port: 443
failurePolicy: {{ .Values.webhooks.namespaces.failurePolicy }}
matchPolicy: Equivalent
name: namespaces.capsule.clastix.io
namespaceSelector: {}
objectSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- namespaces
scope: '*'
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /networkpolicies
port: 443
failurePolicy: {{ .Values.webhooks.networkpolicies.failurePolicy }}
matchPolicy: Equivalent
name: networkpolicies.capsule.clastix.io
namespaceSelector:
{{- toYaml .Values.webhooks.networkpolicies.namespaceSelector | nindent 4}}
objectSelector: {}
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1
operations:
- UPDATE
- DELETE
resources:
- networkpolicies
scope: Namespaced
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /pods
port: 443
failurePolicy: {{ .Values.webhooks.pods.failurePolicy }}
matchPolicy: Exact
name: pods.capsule.clastix.io
namespaceSelector:
{{- toYaml .Values.webhooks.pods.namespaceSelector | nindent 4}}
objectSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: Namespaced
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /persistentvolumeclaims
failurePolicy: {{ .Values.webhooks.persistentvolumeclaims.failurePolicy }}
name: pvc.capsule.clastix.io
namespaceSelector:
{{- toYaml .Values.webhooks.persistentvolumeclaims.namespaceSelector | nindent 4}}
objectSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- persistentvolumeclaims
scope: Namespaced
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /services
port: 443
failurePolicy: {{ .Values.webhooks.services.failurePolicy }}
matchPolicy: Exact
name: services.capsule.clastix.io
namespaceSelector:
{{- toYaml .Values.webhooks.services.namespaceSelector | nindent 4}}
objectSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- services
scope: Namespaced
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
caBundle: Cg==
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /tenants
port: 443
failurePolicy: {{ .Values.webhooks.tenants.failurePolicy }}
matchPolicy: Exact
name: tenants.capsule.clastix.io
namespaceSelector: {}
objectSelector: {}
rules:
- apiGroups:
- capsule.clastix.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- tenants
scope: '*'
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}

View File

@@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "capsule.fullname" . }}-webhook-service
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ports:
- port: 443
name: https
protocol: TCP
targetPort: 9443
selector:
{{- include "capsule.selectorLabels" . | nindent 4 }}
sessionAffinity: None
type: ClusterIP

127
charts/capsule/values.yaml Normal file
View File

@@ -0,0 +1,127 @@
# Default values for capsule.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
manager:
image:
repository: quay.io/clastix/capsule
pullPolicy: IfNotPresent
tag: ''
# 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
hostNetwork: false
# Additional Capsule options
options:
logLevel: '4'
forceTenantPrefix: false
capsuleUserGroups: ["capsule.clastix.io"]
protectedNamespaceRegex: ""
livenessProbe:
httpGet:
path: /healthz
port: 10080
readinessProbe:
httpGet:
path: /readyz
port: 10080
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 200m
memory: 128Mi
jobs:
image:
repository: quay.io/clastix/kubectl
pullPolicy: IfNotPresent
tag: "v1.20.7"
imagePullSecrets: []
serviceAccount:
create: true
annotations: {}
name: "capsule"
podAnnotations: {}
priorityClassName: '' #system-cluster-critical
nodeSelector: {}
# node-role.kubernetes.io/master: ""
tolerations: []
#- key: CriticalAddonsOnly
# operator: Exists
#- effect: NoSchedule
# key: node-role.kubernetes.io/master
replicaCount: 1
affinity: {}
podSecurityPolicy:
enabled: false
serviceMonitor:
enabled: false
# Install the ServiceMonitor into a different Namespace, as the monitoring stack one (default: the release one)
namespace: ''
# Assign additional labels according to Prometheus' serviceMonitorSelector matching labels
labels: {}
annotations: {}
matchLabels: {}
serviceAccount:
name: capsule
namespace: capsule-system
# Additional labels
customLabels: {}
# Additional annotations
customAnnotations: {}
# Webhooks configurations
webhooks:
namespaceOwnerReference:
failurePolicy: Fail
cordoning:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
ingresses:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
namespaces:
failurePolicy: Fail
networkpolicies:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
pods:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
persistentvolumeclaims:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
tenants:
failurePolicy: Fail
services:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
mutatingWebhooksTimeoutSeconds: 30
validatingWebhooksTimeoutSeconds: 30

View File

@@ -0,0 +1,58 @@
---
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: []

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,12 @@
# It should be run by config/default
resources:
- bases/capsule.clastix.io_tenants.yaml
- bases/capsule.clastix.io_capsuleconfigurations.yaml
# +kubebuilder:scaffold:crdkustomizeresource
# the following config is for teaching kustomize how to do kustomization for CRDs.
configurations:
- kustomizeconfig.yaml
patchesStrategicMerge:
- patches/webhook_in_tenants.yaml

View File

@@ -4,13 +4,15 @@ nameReference:
version: v1
fieldSpecs:
- kind: CustomResourceDefinition
version: v1
group: apiextensions.k8s.io
path: spec/conversion/webhookClientConfig/service/name
path: spec/conversion/webhook/clientConfig/service/name
namespace:
- kind: CustomResourceDefinition
version: v1
group: apiextensions.k8s.io
path: spec/conversion/webhookClientConfig/service/namespace
path: spec/conversion/webhook/clientConfig/service/namespace
create: false
varReference:

View File

@@ -0,0 +1,17 @@
# The following patch enables a conversion webhook for the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: tenants.capsule.clastix.io
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
namespace: system
name: webhook-service
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1

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