Compare commits

..

108 Commits

Author SHA1 Message Date
Dario Tranchitella
d92f1e7825 chore(helm): bumping up to v0.2.1 2023-02-01 09:21:09 +01:00
Dario Tranchitella
ee813c5343 chore(kustomize): bumping up to v0.2.1 2023-02-01 09:21:09 +01:00
Dario Tranchitella
0fbf43ba0f docs: bumping up to v0.2.1 2023-02-01 09:21:09 +01:00
Dario Tranchitella
7ec7f3c69c fix: converting the status between v1beta1 and v1beta2 2023-01-31 19:54:14 +01:00
Dario Tranchitella
de587919f8 fix(tenantresources): using actual resourceversion during createorupdate 2023-01-31 16:57:07 +01:00
Dario Tranchitella
a334e68256 chore(helm): release v0.3.0 2023-01-28 11:47:00 +01:00
Dario Tranchitella
36f0281355 chore(kustomize): bumping up to Capsule v0.2.0 2023-01-28 11:47:00 +01:00
Dario Tranchitella
6a5fff01cd docs: migrating guidelines for v1beta2 api version 2023-01-28 11:42:34 +01:00
Dario Tranchitella
f8e212c291 docs: refactoring for v1beta2 2023-01-28 11:42:34 +01:00
Dario Tranchitella
91a979edc4 chore(helm): adding required omitempty for ux 2023-01-28 11:42:34 +01:00
Dario Tranchitella
1b51a4777d chore(kustomize): adding required omitempty for ux 2023-01-28 11:42:34 +01:00
Dario Tranchitella
8e827b2f5b fix(api): adding required omitempty for ux 2023-01-28 11:42:34 +01:00
Dario Tranchitella
1f0ac0724b docs: preventing persistentvolume cross tenant mount 2023-01-26 09:31:16 +01:00
Dario Tranchitella
18a9e77a8a test(e2e): preventing persistentvolume cross tenant mount 2023-01-26 09:31:16 +01:00
Dario Tranchitella
ea88b102e5 feat: pv labelling and preventing cross-tenant mount 2023-01-26 09:31:16 +01:00
Oliver Bähler
b58cb19ea1 chore(gitignore): add kind.yaml
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
25095bcf2d chore(makefile): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
ea71dd7e65 fix(e2e): correct e2e test for container patches
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
68c86a6509 test(e2e): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
7784e8df50 docs: add defaults handler for storage, ingress, and priority classes
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
8f933cd2b3 chore(helm): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
a1b624f239 chore(kustomize): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
ab0fe91c58 feat: add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Max Fedotov
fbea737a51 feat: add Wargaming to adopters
Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2023-01-11 15:45:55 +01:00
Dario Tranchitella
51288f30c5 fix(docs): limit range spec for pods and containers limits 2023-01-02 17:43:25 +01:00
Dario Tranchitella
f73a5b17f4 fix: using embedded struct for selector 2022-12-29 17:49:45 +01:00
Oliver Bähler
f6c1ad68da chore(makefile): add crds to dev-setup
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
9f10923d21 fix: use v1beta2 for capsuleconfiguration kind
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
7591140fc7 test(e2e): validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
34cfb16ea3 chore(helm): validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
f5cd194c05 chore(kustomize): validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
628efbb30f fix: validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
79391f863a feat: add runtimeclass control
Signed-off-by: Oliver Baehler <oliver.baehler@hotmail.com>
2022-12-28 15:01:28 +01:00
Oliver Bähler
ee0fdc9efa feat: ct install performs locally & ci loads current capsule build
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-27 17:53:17 +01:00
Dario Tranchitella
e964f34086 fix: avoiding nil pointer when empty map for labels and annotations 2022-12-27 17:53:17 +01:00
Dario Tranchitella
0d9054db34 docs(api): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
7c4b46eeb1 chore(helm): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
dbbd9e64c0 chore(kustomize): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
462d7213b9 refactor(e2e): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
93fbca9b18 feat(api): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
289b079530 docs(api): aligning to latest changes for capsule configuration 2022-12-27 17:53:17 +01:00
Dario Tranchitella
5af3c9828e chore(helm): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
77a8c9ab62 chore(kustomize): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
791dde5bf6 refactor(e2e): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
43bd2491ae refactor(api): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
2cb37abc51 chore(helm): promoting v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
0b057d63b6 chore(kustomize): promoting v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
0f580ef379 feat(api): promoting v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
34a948da81 chore(helm)!: release of v0.2.0 2022-12-27 17:53:17 +01:00
Dario Tranchitella
e14e12c287 refactor: abstracting types used by several api versions 2022-12-27 17:53:17 +01:00
Dario Tranchitella
9d6f766cc1 fix(makefile): avoid race condition for local kind cluster 2022-12-26 14:27:26 +01:00
Dario Tranchitella
ea01b9d1f9 test: support for endpointslice/v1 for k8s v1.25 2022-12-26 14:27:26 +01:00
Dario Tranchitella
d58c5124b2 chore(helm)!: release of v0.2.0 2022-12-26 14:27:26 +01:00
Dario Tranchitella
6a380b00ad style: kubebuilder annotations start with empty space 2022-12-26 14:27:26 +01:00
Dario Tranchitella
2808344847 refactor: deprecating tenant v1alpha1 version 2022-12-26 14:27:26 +01:00
Max Fedotov
b1ec9fed50 feat: refactor resources controller
Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2022-12-26 14:27:26 +01:00
Dario Tranchitella
6403b60590 refactor(test): generating namespace names avoiding collision 2022-12-26 14:27:26 +01:00
Dario Tranchitella
369feb23c6 docs: globaltenantresource and tenantresource support 2022-12-26 14:27:26 +01:00
Dario Tranchitella
9f1165ea40 test: globaltenantresource and tenantresource support 2022-12-26 14:27:26 +01:00
Dario Tranchitella
503e3fc1d0 feat: globaltenantresource and tenantresource reconciliation 2022-12-26 14:27:26 +01:00
Dario Tranchitella
d01c9035ac chore(helm)!: globaltenantresource and tenantresource support 2022-12-26 14:27:26 +01:00
Dario Tranchitella
b7038d00a2 chore(kustomize): globaltenantresource and tenantresource support 2022-12-26 14:27:26 +01:00
Dario Tranchitella
cade41da81 feat(api): globaltenantresource and tenantresource support 2022-12-26 14:27:26 +01:00
Dario Tranchitella
360a8d2b56 refactor: using interfaces for accessing tenant namespaces 2022-12-26 14:27:26 +01:00
Dario Tranchitella
4835b94839 style: conforming go files headers 2022-12-26 14:27:26 +01:00
Dario Tranchitella
abbb281d45 chore(lint): adding goheader rule and removing unused ones 2022-12-26 14:27:26 +01:00
Dario Tranchitella
cf52924870 refactor: abstracting types used by several api versions 2022-12-26 14:27:26 +01:00
Dario Tranchitella
f5c2932137 docs: introducing v1beta2 api group 2022-12-26 14:27:26 +01:00
Dario Tranchitella
d20e466732 feat: support for ca update on crds objects 2022-12-26 14:27:26 +01:00
Dario Tranchitella
85aa53df6f chore(helm)!: introducing v1beta2 api group 2022-12-26 14:27:26 +01:00
Dario Tranchitella
a3391d68af chore(kustomize): introducing v1beta2 api group 2022-12-26 14:27:26 +01:00
Dario Tranchitella
bca70e634d feat: introducing v1beta2 api group 2022-12-26 14:27:26 +01:00
Dario Tranchitella
9f9ccf0bb1 chore(controller-gen): upgrading to 0.10.0 2022-12-26 14:27:26 +01:00
Dario Tranchitella
4100b98fad chore(makefile): downloading locally golangci-lint 2022-12-26 14:27:26 +01:00
Dario Tranchitella
793d847a0b perf(dockerfile): using go modules caching 2022-12-26 14:27:26 +01:00
Dario Tranchitella
1d431346a3 test: update of namespace forbidden metadata should not succeed 2022-12-23 15:34:28 +01:00
Dario Tranchitella
1087ea853b fix: inverted logic in forbidden user namespace metadata 2022-12-23 15:34:28 +01:00
Dario Tranchitella
d2a6358517 test: supporting k8s >= 1.25 2022-12-18 11:20:25 +01:00
Dario Tranchitella
fb5c1a1fa6 feat: supporting k8s >= 1.25 2022-12-18 11:20:25 +01:00
Dario Tranchitella
ee213aab49 chore(gh): updating compatibility matrix 2022-12-18 11:20:25 +01:00
dependabot[bot]
3efb91f264 build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 in /docs
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-17 15:49:27 +01:00
dependabot[bot]
4988655efc build(deps): bump express from 4.17.1 to 4.18.2 in /docs
Bumps [express](https://github.com/expressjs/express) from 4.17.1 to 4.18.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.17.1...4.18.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-17 15:48:44 +01:00
dependabot[bot]
514aad04cd build(deps): bump qs from 6.5.2 to 6.5.3 in /docs
Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 19:49:06 +01:00
Dario Tranchitella
eba072c88d chore(helm): release v0.1.3 2022-12-02 15:43:41 +01:00
Dario Tranchitella
9d02fb39eb chore(kustomize): release v0.1.3 2022-12-02 15:43:41 +01:00
Dario Tranchitella
1df430e71b test: preventing serviceaccount privilege escalation 2022-12-02 15:19:06 +01:00
Dario Tranchitella
75525ac192 fix: preventing serviceaccount privilege escalation 2022-12-02 15:19:06 +01:00
Dario Tranchitella
132ffd57ea chore(maintainers): welcome oliver 2022-11-21 18:56:44 +01:00
dependabot[bot]
7602114835 build(deps): bump minimatch from 3.0.4 to 3.1.2 in /docs
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-15 18:43:26 +01:00
Dario Tranchitella
82996c1c83 fix(docs): typo in the velero guide 2022-11-15 10:58:19 +01:00
Alessio Pragliola
ede96f5cf4 fix: service controller not skipping sentinel errs 2022-10-14 20:21:32 +02:00
Oliver Bähler
2fc1be8bfe feat: improve local development experience
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-10-13 16:02:23 +02:00
Lajos Papp
e0dbf47723 fix(docs): clarifying serviceaccount as tenant owner 2022-09-27 14:15:38 +02:00
Dario Tranchitella
fb68795e90 chore: adding code of conduct 2022-09-13 15:19:19 +02:00
Oliver Bähler
a026e2f00c feat: add bedag as adopters 2022-08-30 15:02:00 +02:00
Massimiliano Giovagnoli
413208e7fe docs(capsule-flux): remove no longer needed clusterrole for ns patch
Signed-off-by: Massimiliano Giovagnoli <me@maxgio.it>
2022-08-30 10:34:21 +02:00
Oliver Bähler
2771b63c18 feat: add helm-docs check 2022-08-30 10:33:18 +02:00
Oliver Bähler
9a1520ff66 feat: add github config, correct slack link 2022-08-30 10:33:18 +02:00
Oliver Bähler
c304fb2438 feat: chart testing and docs improvements 2022-08-30 10:33:18 +02:00
Dario Tranchitella
6d56237e23 style(docs): removing parent link for crds docs 2022-08-17 18:49:57 +02:00
Dario Tranchitella
c32166ba45 chore(github): ensuring autogenerated crds docs is committed 2022-08-17 18:49:57 +02:00
Michele Mastrogiovanni
e4ecbe30d1 docs: autogeneration for crds documentation
Co-authored-by: mastrogiovanni <mastrogiovanni@localhost>
2022-08-17 18:36:22 +02:00
Dario Tranchitella
3435f5464b chore: adding maintainers list 2022-08-05 11:48:31 +02:00
Dario Tranchitella
f216d0bd8d docs: adding adopters list section 2022-08-02 10:18:04 +02:00
alegrey91
f9e7256746 ci: add gosec pipeline 2022-08-01 09:37:12 +02:00
Adriano Pezzuto
5b46e8eb81 docs(tutorial): tenant backup and restore with velero (#626) 2022-07-31 11:00:29 +02:00
Adriano Pezzuto
dd5ed4575e Clarify tenant owner permissions in documentation (#625)
* docs(tutorial): clarify tenant owner permissions and minor improvements
2022-07-31 09:37:12 +02:00
Adriano Pezzuto
f9554d4cae Document how to implement Pod Security Standard (#624)
* docs(guides): add pod security guide and other minor enhancements
2022-07-30 21:30:14 +02:00
281 changed files with 24039 additions and 3661 deletions

View File

@@ -9,10 +9,7 @@ assignees: ''
<!--
Thanks for taking time reporting a Capsule bug!
We do our best to keep it reliable and working, so don't hesitate adding
as many information as you can and keep in mind you can reach us on our
Clastix Slack workspace: https://clastix.slack.com, #capsule channel.
-->
# Bug description

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Chat on Slack
url: https://kubernetes.slack.com/archives/C03GETTJQRL
about: Maybe chatting with the community can help

View File

@@ -14,8 +14,6 @@ We're trying to build a community drive Open Source project, so don't
hesitate proposing your enhancement ideas: keep in mind, since we would like
to keep it as agnostic as possible, to motivate all your assumptions.
If you need to reach the maintainers, please join the Clastix Slack workspace:
https://clastix.slack.com, #capsule channel.
-->
# Describe the feature

10
.github/configs/ct.yaml vendored Normal file
View File

@@ -0,0 +1,10 @@
remote: origin
target-branch: master
chart-dirs:
- charts
helm-extra-args: "--timeout 600s"
validate-chart-schema: false
validate-maintainers: false
validate-yaml: true
exclude-deprecated: true
check-version-increment: false

43
.github/configs/lintconf.yaml vendored Normal file
View File

@@ -0,0 +1,43 @@
---
rules:
braces:
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
colons:
max-spaces-before: 0
max-spaces-after: 1
commas:
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
comments:
require-starting-space: true
min-spaces-from-content: 1
document-end: disable
document-start: disable # No --- to start a file
empty-lines:
max: 2
max-start: 0
max-end: 0
hyphens:
max-spaces-after: 1
indentation:
spaces: consistent
indent-sequences: whatever # - list indentation will handle both indentation and without
check-multi-line-strings: false
key-duplicates: enable
line-length: disable # Lines can be any length
new-line-at-end-of-file: enable
new-lines:
type: unix
trailing-spaces: enable
truthy:
level: warning

23
.github/maintainers.yaml vendored Normal file
View File

@@ -0,0 +1,23 @@
- name: Adriano Pezzuto
github: https://github.com/bsctl
company: Clastix
projects:
- https://github.com/clastix/capsule
- https://github.com/clastix/capsule-proxy
- name: Dario Tranchitella
github: https://github.com/prometherion
company: Clastix
projects:
- https://github.com/clastix/capsule
- https://github.com/clastix/capsule-proxy
- name: Maksim Fedotov
github: https://github.com/MaxFedotov
company: wargaming.net
projects:
- https://github.com/clastix/capsule
- https://github.com/clastix/capsule-proxy
- name: Oliver Bähler
github: https://github.com/oliverbaehler
company: Bedag Informatik AG
projects:
- https://github.com/clastix/capsule

View File

@@ -40,6 +40,9 @@ jobs:
- 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
- run: make apidoc
- name: Checking if the CRDs documentation is not aligned
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> CRDs generated documentation 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

View File

@@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.4', 'v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.1']
k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.4', 'v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.0']
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2

18
.github/workflows/gosec.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: CI gosec
on:
push:
branches: [ "*" ]
pull_request:
branches: [ "*" ]
jobs:
tests:
runs-on: ubuntu-latest
env:
GO111MODULE: on
steps:
- name: Checkout Source
uses: actions/checkout@v2
- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: ./...

View File

@@ -12,11 +12,61 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: azure/setup-helm@v1
with:
version: 3.3.4
- name: Linting Chart
run: helm lint ./charts/capsule
- name: Setup Chart Linting
id: lint
uses: helm/chart-testing-action@v2.3.0
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed --config ./.github/configs/ct.yaml)
if [[ -n "$changed" ]]; then
echo "::set-output name=changed::true"
fi
- name: Run chart-testing (lint)
run: ct lint --debug --config ./.github/configs/ct.yaml --lint-conf ./.github/configs/lintconf.yaml
- name: Run docs-testing (helm-docs)
id: helm-docs
run: |
make helm-docs
if [[ $(git diff --stat) != '' ]]; then
echo -e '\033[0;31mDocumentation outdated! (Run make helm-docs locally and commit)\033[0m ❌'
git diff --color
exit 1
else
echo -e '\033[0;32mDocumentation up to date\033[0m ✔'
fi
# ATTENTION: This is a workaround for the upcoming ApiVersion Conversions for the capsule CRDs
# With this workflow the current docker image is build and loaded into kind, otherwise the install fails
# In the future this must be removed and the chart-testing-action must be used
- name: Run chart-testing (install)
run: make helm-test
if: steps.list-changed.outputs.changed == 'true'
## Create KIND Cluster
#- name: Create kind cluster
# uses: helm/kind-action@v1.2.0
# if: steps.list-changed.outputs.changed == 'true'
## Install Required Operators/CRDs
#- name: Prepare Cluster Operators/CRDs
# run: |
# # Cert-Manager CRDs
# kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml
#
# # Prometheus CRDs
# kubectl create -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.58.0/bundle.yaml
# if: steps.list-changed.outputs.changed == 'true'
## Install Charts
#- name: Run chart-testing (install)
# run: ct install --debug --config ./.github/configs/ct.yaml
# if: steps.list-changed.outputs.changed == 'true'
release:
if: startsWith(github.ref, 'refs/tags/helm-v')
runs-on: ubuntu-latest

2
.gitignore vendored
View File

@@ -29,4 +29,4 @@ bin
**/*.key
.DS_Store
*.tgz
kind.yaml

View File

@@ -15,6 +15,11 @@ linters-settings:
- standard
- default
- prefix(github.com/clastix/capsule)
goheader:
template: |-
Copyright 2020-2021 Clastix Labs
SPDX-License-Identifier: Apache-2.0
linters:
enable-all: true
disable:
@@ -35,12 +40,8 @@ linters:
- varnamelen
- wrapcheck
issues:
exclude:
- Using the variable on range scope .* in function literal
service:
golangci-lint-version: 1.33.x
golangci-lint-version: 1.45.2
run:
skip-files:

11
ADOPTERS.md Normal file
View File

@@ -0,0 +1,11 @@
# Adopters
This is a list of companies that have adopted Capsule, feel free to open a Pull-Request to get yours listed.
## Adopters list (alphabetically)
### [Bedag Informatik AG](https://www.bedag.ch/)
![Bedag](https://www.bedag.ch/wGlobal/wGlobal/layout/images/logo.svg)
### [Wargaming.net](https://www.wargaming.net/)
![Wargaming.net](https://static-cspbe-eu.wargaming.net/images/logo@2x.png)

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement by contacting
one of the [maintainers](https://raw.githubusercontent.com/clastix/capsule/master/.github/maintainers.yaml).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,14 +1,6 @@
# Build the manager binary
FROM golang:1.18 as builder
ARG TARGETARCH
ARG GIT_HEAD_COMMIT
ARG GIT_TAG_COMMIT
ARG GIT_LAST_TAG
ARG GIT_MODIFIED
ARG GIT_REPO
ARG BUILD_DATE
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
@@ -17,6 +9,14 @@ COPY go.sum go.sum
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
ARG TARGETARCH
ARG GIT_HEAD_COMMIT
ARG GIT_TAG_COMMIT
ARG GIT_LAST_TAG
ARG GIT_MODIFIED
ARG GIT_REPO
ARG BUILD_DATE
# Copy the go source
COPY main.go main.go
COPY version.go version.go

View File

@@ -14,8 +14,6 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
# Image URL to use all building/pushing image targets
IMG ?= clastix/capsule:$(VERSION)
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:preserveUnknownFields=false"
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@@ -72,12 +70,38 @@ remove: installer
# 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
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
# Generate code
generate: controller-gen
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
apidoc: apidocs-gen
$(APIDOCS_GEN) crdoc --resources config/crd/bases --output docs/content/general/crds-apis.md --template docs/template/reference-cr.tmpl
# Helm
SRC_ROOT = $(shell git rev-parse --show-toplevel)
helm-docs: HELMDOCS_VERSION := v1.11.0
helm-docs: docker
@docker run -v "$(SRC_ROOT):/helm-docs" jnorwood/helm-docs:$(HELMDOCS_VERSION) --chart-search-root /helm-docs
helm-lint: ct
@ct lint --config $(SRC_ROOT)/.github/configs/ct.yaml --lint-conf $(SRC_ROOT)/.github/configs/lintconf.yaml --all --debug
helm-test: kind ct docker-build
@kind create cluster --wait=60s --name capsule-charts
@kind load docker-image --name capsule-charts ${IMG}
@kubectl create ns capsule-system
@ct install --config $(SRC_ROOT)/.github/configs/ct.yaml --namespace=capsule-system --all --debug
@kind delete cluster --name capsule-charts
docker:
@hash docker 2>/dev/null || {\
echo "You need docker" &&\
exit 1;\
}
# Setup development env
# Usage:
# LAPTOP_HOST_IP=<YOUR_LAPTOP_IP> make dev-setup
@@ -115,7 +139,10 @@ dev-setup:
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}\"}}\
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespace-owner-reference\",'caBundle':\"$${CA_BUNDLE}\"}}\
]" && \
kubectl patch ValidatingWebhookConfiguration capsule-validating-webhook-configuration \
--type='json' -p="[\
@@ -123,13 +150,22 @@ dev-setup:
{'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/ingresses\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespaces\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/networkpolicies\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/4/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/pods\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/nodes\",'caBundle':\"$${CA_BUNDLE}\"}}\
{'op': 'replace', 'path': '/webhooks/4/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/nodes\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/pods\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}}\
]" && \
kubectl patch crd tenants.capsule.clastix.io \
--type='json' -p="[\
{'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig', 'value':{'url': \"$${WEBHOOK_URL}\", 'caBundle': \"$${CA_BUNDLE}\"}}\
]" && \
kubectl patch crd capsuleconfigurations.capsule.clastix.io \
--type='json' -p="[\
{'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig', 'value':{'url': \"$${WEBHOOK_URL}\", 'caBundle': \"$${CA_BUNDLE}\"}}\
]";
# Build the docker image
docker-build: test
docker build . -t ${IMG} --build-arg GIT_HEAD_COMMIT=$(GIT_HEAD_COMMIT) \
@@ -145,11 +181,23 @@ docker-push:
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
controller-gen: ## Download controller-gen locally if necessary.
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0)
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0)
APIDOCS_GEN = $(shell pwd)/bin/crdoc
apidocs-gen: ## Download crdoc locally if necessary.
$(call go-install-tool,$(APIDOCS_GEN),fybrik.io/crdoc@latest)
GINKGO = $(shell pwd)/bin/ginkgo
ginkgo: ## Download ginkgo locally if necessary.
$(call go-install-tool,$(KUSTOMIZE),github.com/onsi/ginkgo/ginkgo@v1.16.5)
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/ginkgo@v1.16.5)
CT = $(shell pwd)/bin/ct
ct: ## Download ct locally if necessary.
$(call go-install-tool,$(CT),github.com/helm/chart-testing/v3/ct@v3.7.1)
KIND = $(shell pwd)/bin/kind
kind: ## Download kind locally if necessary.
$(call go-install-tool,$(KIND),sigs.k8s.io/kind/cmd/kind@v0.17.0)
KUSTOMIZE = $(shell pwd)/bin/kustomize
kustomize: ## Download kustomize locally if necessary.
@@ -190,15 +238,22 @@ bundle-build:
goimports:
goimports -w -l -local "github.com/clastix/capsule" .
GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
golangci-lint: ## Download golangci-lint locally if necessary.
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2)
# Linting code as PR is expecting
.PHONY: golint
golint:
golangci-lint run -c .golangci.yml
golint: golangci-lint
$(GOLANGCI_LINT) run -c .golangci.yml
# Running e2e tests in a KinD instance
.PHONY: e2e
e2e/%: ginkgo
kind create cluster --name capsule --image=kindest/node:$*
$(MAKE) e2e-build/$* && $(MAKE) e2e-exec || $(MAKE) e2e-destroy
e2e-build/%:
kind create cluster --wait=60s --name capsule --image=kindest/node:$*
make docker-build
kind load docker-image --nodes capsule-control-plane --name capsule $(IMG)
helm upgrade \
@@ -213,5 +268,9 @@ e2e/%: ginkgo
--set 'manager.readinessProbe.failureThreshold=10' \
capsule \
./charts/capsule
e2e-exec: ginkgo
$(GINKGO) -v -tags e2e ./e2e
e2e-destroy:
kind delete cluster --name capsule

33
PROJECT
View File

@@ -9,7 +9,6 @@ repo: github.com/clastix/capsule
resources:
- api:
crdVersion: v1
namespaced: false
controller: true
domain: clastix.io
group: capsule
@@ -21,7 +20,6 @@ resources:
webhookVersion: v1
- api:
crdVersion: v1
namespaced: false
controller: true
domain: clastix.io
group: capsule
@@ -30,10 +28,39 @@ resources:
version: v1alpha1
- api:
crdVersion: v1
namespaced: false
domain: clastix.io
group: capsule
kind: Tenant
path: github.com/clastix/capsule/api/v1beta1
version: v1beta1
- api:
crdVersion: v1
domain: clastix.io
group: capsule
kind: Tenant
path: github.com/clastix/capsule/api/v1beta2
version: v1beta2
- api:
crdVersion: v1
controller: true
domain: clastix.io
group: capsule
kind: CapsuleConfiguration
path: github.com/clastix/capsule/api/v1beta2
version: v1beta2
- api:
crdVersion: v1
namespaced: true
domain: clastix.io
group: capsule
kind: TenantResource
path: github.com/clastix/capsule/api/v1beta2
version: v1beta2
- api:
crdVersion: v1
domain: clastix.io
group: capsule
kind: GlobalTenantResource
path: github.com/clastix/capsule/api/v1beta2
version: v1beta2
version: "3"

View File

@@ -72,22 +72,38 @@ Capsule is Open Source with Apache 2 license and any contribution is welcome.
## Chart Development
The documentation for each chart is done with [helm-docs](https://github.com/norwoodj/helm-docs). This way we can ensure that values are consistent with the chart documentation.
### Chart Linting
We have a script on the repository which will execute the helm-docs docker container, so that you don't have to worry about downloading the binary etc. Simply execute the script (Bash compatible):
The chart is linted with [ct](https://github.com/helm/chart-testing). You can run the linter locally with this command:
```
bash scripts/helm-docs.sh
make helm-lint
```
### Chart Documentation
The documentation for each chart is done with [helm-docs](https://github.com/norwoodj/helm-docs). This way we can ensure that values are consistent with the chart documentation. Run this anytime you make changes to a `values.yaml` file:
```
make helm-docs
```
## Community
Join the community, share and learn from it. You can find all the resources to how to contribute code and docs, connect with people in the [community repository](https://github.com/clastix/capsule-community).
## Adopters
See the [ADOPTERS.md](ADOPTERS.md) file for a list of companies that are using Capsule.
# Governance
You can find how the Capsule project is governed [here](https://capsule.clastix.io/docs/contributing/governance).
## Maintainers
Please, refer to the maintainers file available [here](.github/maintainers.yaml).
# FAQ
- Q. How to pronounce Capsule?

View File

@@ -3,7 +3,7 @@
package v1alpha1
type AdditionalMetadataSpec struct {
AdditionalLabels map[string]string `json:"additionalLabels,omitempty"`
AdditionalAnnotations map[string]string `json:"additionalAnnotations,omitempty"`
type AdditionalMetadata struct {
Labels map[string]string `json:"additionalLabels,omitempty"`
Annotations map[string]string `json:"additionalAnnotations,omitempty"`
}

View File

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

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

@@ -1,3 +1,6 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
const (

View File

@@ -31,6 +31,8 @@ type CapsuleConfiguration struct {
Spec CapsuleConfigurationSpec `json:"spec,omitempty"`
}
func (in *CapsuleConfiguration) Hub() {}
// +kubebuilder:object:root=true
// CapsuleConfigurationList contains a list of CapsuleConfiguration.

View File

@@ -0,0 +1,21 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"os"
ctrl "sigs.k8s.io/controller-runtime"
)
func (in *CapsuleConfiguration) SetupWebhookWithManager(mgr ctrl.Manager) error {
certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt")
if len(certData) == 0 {
return nil
}
return ctrl.NewWebhookManagedBy(mgr).
For(in).
Complete()
}

View File

@@ -14,6 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/conversion"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
"github.com/clastix/capsule/pkg/api"
)
const (
@@ -48,7 +49,7 @@ const (
ingressHostnameCollisionScope = "ingress.capsule.clastix.io/hostname-collision-scope"
)
func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
func (in *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
serviceKindToAnnotationMap := map[capsulev1beta1.ProxyServiceKind][]string{
capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
@@ -75,7 +76,7 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
ownerServiceAccountAnnotation: capsulev1beta1.ServiceAccountOwner,
}
annotations := t.GetAnnotations()
annotations := in.GetAnnotations()
operations := make(map[string]map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation)
@@ -111,9 +112,9 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
}
owners = append(owners, capsulev1beta1.OwnerSpec{
Kind: capsulev1beta1.OwnerKind(t.Spec.Owner.Kind),
Name: t.Spec.Owner.Name,
ProxyOperations: getProxySettingsForOwner(t.Spec.Owner.Name),
Kind: capsulev1beta1.OwnerKind(in.Spec.Owner.Kind),
Name: in.Spec.Owner.Name,
ProxyOperations: getProxySettingsForOwner(in.Spec.Owner.Name),
})
for ownerAnnotation, ownerKind := range annotationToOwnerKindMap {
@@ -133,150 +134,134 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
}
// nolint:gocognit,gocyclo,cyclop,maintidx
func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error {
dst, ok := dstRaw.(*capsulev1beta1.Tenant)
if !ok {
return fmt.Errorf("expected type *capsulev1beta1.Tenant, got %T", dst)
}
annotations := t.GetAnnotations()
annotations := in.GetAnnotations()
// ObjectMeta
dst.ObjectMeta = t.ObjectMeta
dst.ObjectMeta = in.ObjectMeta
// Spec
if t.Spec.NamespaceQuota != nil {
if in.Spec.NamespaceQuota != nil {
if dst.Spec.NamespaceOptions == nil {
dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{}
}
dst.Spec.NamespaceOptions.Quota = t.Spec.NamespaceQuota
dst.Spec.NamespaceOptions.Quota = in.Spec.NamespaceQuota
}
dst.Spec.NodeSelector = t.Spec.NodeSelector
dst.Spec.NodeSelector = in.Spec.NodeSelector
dst.Spec.Owners = t.convertV1Alpha1OwnerToV1Beta1()
dst.Spec.Owners = in.convertV1Alpha1OwnerToV1Beta1()
if t.Spec.NamespacesMetadata != nil {
if in.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,
dst.Spec.NamespaceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{
Labels: in.Spec.NamespacesMetadata.Labels,
Annotations: in.Spec.NamespacesMetadata.Annotations,
}
}
if t.Spec.ServicesMetadata != nil {
if in.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,
},
}
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
dst.Spec.ServiceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{
Labels: in.Spec.ServicesMetadata.Labels,
Annotations: in.Spec.ServicesMetadata.Annotations,
}
}
if t.Spec.StorageClasses != nil {
dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{
Exact: t.Spec.StorageClasses.Exact,
Regex: t.Spec.StorageClasses.Regex,
}
if in.Spec.StorageClasses != nil {
dst.Spec.StorageClasses = in.Spec.StorageClasses
}
if v, annotationOk := t.Annotations[ingressHostnameCollisionScope]; annotationOk {
if v, annotationOk := in.Annotations[ingressHostnameCollisionScope]; annotationOk {
switch v {
case string(capsulev1beta1.HostnameCollisionScopeCluster), string(capsulev1beta1.HostnameCollisionScopeTenant), string(capsulev1beta1.HostnameCollisionScopeNamespace):
dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(v)
case string(api.HostnameCollisionScopeCluster), string(api.HostnameCollisionScopeTenant), string(api.HostnameCollisionScopeNamespace):
dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScope(v)
default:
dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScopeDisabled
dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScopeDisabled
}
}
if t.Spec.IngressClasses != nil {
dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{
Exact: t.Spec.IngressClasses.Exact,
Regex: t.Spec.IngressClasses.Regex,
if in.Spec.IngressClasses != nil {
dst.Spec.IngressOptions.AllowedClasses = &api.AllowedListSpec{
Exact: in.Spec.IngressClasses.Exact,
Regex: in.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 in.Spec.IngressHostnames != nil {
dst.Spec.IngressOptions.AllowedHostnames = &api.AllowedListSpec{
Exact: in.Spec.IngressHostnames.Exact,
Regex: in.Spec.IngressHostnames.Regex,
}
}
if t.Spec.ContainerRegistries != nil {
dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{
Exact: t.Spec.ContainerRegistries.Exact,
Regex: t.Spec.ContainerRegistries.Regex,
if in.Spec.ContainerRegistries != nil {
dst.Spec.ContainerRegistries = &api.AllowedListSpec{
Exact: in.Spec.ContainerRegistries.Exact,
Regex: in.Spec.ContainerRegistries.Regex,
}
}
if len(t.Spec.NetworkPolicies) > 0 {
dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{
Items: t.Spec.NetworkPolicies,
if len(in.Spec.NetworkPolicies) > 0 {
dst.Spec.NetworkPolicies = api.NetworkPolicySpec{
Items: in.Spec.NetworkPolicies,
}
}
if len(t.Spec.LimitRanges) > 0 {
dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{
Items: t.Spec.LimitRanges,
if len(in.Spec.LimitRanges) > 0 {
dst.Spec.LimitRanges = api.LimitRangesSpec{
Items: in.Spec.LimitRanges,
}
}
if len(t.Spec.ResourceQuota) > 0 {
dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{
Scope: func() capsulev1beta1.ResourceQuotaScope {
if v, annotationOk := t.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk {
if len(in.Spec.ResourceQuota) > 0 {
dst.Spec.ResourceQuota = api.ResourceQuotaSpec{
Scope: func() api.ResourceQuotaScope {
if v, annotationOk := in.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk {
switch v {
case string(capsulev1beta1.ResourceQuotaScopeNamespace):
return capsulev1beta1.ResourceQuotaScopeNamespace
case string(capsulev1beta1.ResourceQuotaScopeTenant):
return capsulev1beta1.ResourceQuotaScopeTenant
case string(api.ResourceQuotaScopeNamespace):
return api.ResourceQuotaScopeNamespace
case string(api.ResourceQuotaScopeTenant):
return api.ResourceQuotaScopeTenant
}
}
return capsulev1beta1.ResourceQuotaScopeTenant
return api.ResourceQuotaScopeTenant
}(),
Items: t.Spec.ResourceQuota,
Items: in.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,
})
}
}
dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings
if t.Spec.ExternalServiceIPs != nil {
if in.Spec.ExternalServiceIPs != nil {
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
dst.Spec.ServiceOptions = &api.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)
}
dst.Spec.ServiceOptions.ExternalServiceIPs = in.Spec.ExternalServiceIPs
}
pullPolicies, ok := annotations[podAllowedImagePullPolicyAnnotation]
if ok {
for _, policy := range strings.Split(pullPolicies, ",") {
dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(policy))
dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, api.ImagePullPolicySpec(policy))
}
}
priorityClasses := capsulev1beta1.AllowedListSpec{}
priorityClasses := api.AllowedListSpec{}
priorityClassAllowed, ok := annotations[podPriorityAllowedAnnotation]
@@ -298,15 +283,15 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
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()))
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, in.GetName()))
}
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.BoolPtr(val)
@@ -316,15 +301,15 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
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()))
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, in.GetName()))
}
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.BoolPtr(val)
@@ -334,23 +319,23 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
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()))
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, in.GetName()))
}
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.BoolPtr(val)
}
// Status
dst.Status = capsulev1beta1.TenantStatus{
Size: t.Status.Size,
Namespaces: t.Status.Namespaces,
Size: in.Status.Size,
Namespaces: in.Status.Namespaces,
}
// Remove unneeded annotations
delete(dst.ObjectMeta.Annotations, podAllowedImagePullPolicyAnnotation)
@@ -381,7 +366,7 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
}
// nolint:gocognit,gocyclo,cyclop
func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
func (in *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
ownersAnnotations := map[string][]string{
ownerGroupsAnnotation: nil,
ownerUsersAnnotation: nil,
@@ -402,7 +387,7 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
for i, owner := range src.Spec.Owners {
if i == 0 {
t.Spec.Owner = OwnerSpec{
in.Spec.Owner = OwnerSpec{
Name: owner.Name,
Kind: Kind(owner.Kind),
}
@@ -469,114 +454,89 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
for k, v := range ownersAnnotations {
if len(v) > 0 {
t.Annotations[k] = strings.Join(v, ",")
in.Annotations[k] = strings.Join(v, ",")
}
}
for k, v := range proxyAnnotations {
if len(v) > 0 {
t.Annotations[k] = strings.Join(v, ",")
in.Annotations[k] = strings.Join(v, ",")
}
}
}
// nolint:gocyclo,cyclop
func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
//nolint:cyclop
func (in *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
src, ok := srcRaw.(*capsulev1beta1.Tenant)
if !ok {
return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", srcRaw)
}
// ObjectMeta
t.ObjectMeta = src.ObjectMeta
in.ObjectMeta = src.ObjectMeta
// Spec
if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.Quota != nil {
t.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota
in.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota
}
t.Spec.NodeSelector = src.Spec.NodeSelector
in.Spec.NodeSelector = src.Spec.NodeSelector
if t.Annotations == nil {
t.Annotations = make(map[string]string)
if in.Annotations == nil {
in.Annotations = make(map[string]string)
}
t.convertV1Beta1OwnerToV1Alpha1(src)
in.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,
in.Spec.NamespacesMetadata = &AdditionalMetadata{
Labels: src.Spec.NamespaceOptions.AdditionalMetadata.Labels,
Annotations: 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,
in.Spec.ServicesMetadata = &AdditionalMetadata{
Labels: src.Spec.ServiceOptions.AdditionalMetadata.Labels,
Annotations: src.Spec.ServiceOptions.AdditionalMetadata.Annotations,
}
}
if src.Spec.StorageClasses != nil {
t.Spec.StorageClasses = &AllowedListSpec{
Exact: src.Spec.StorageClasses.Exact,
Regex: src.Spec.StorageClasses.Regex,
}
in.Spec.StorageClasses = src.Spec.StorageClasses
}
t.Annotations[ingressHostnameCollisionScope] = string(src.Spec.IngressOptions.HostnameCollisionScope)
in.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,
}
in.Spec.IngressClasses = src.Spec.IngressOptions.AllowedClasses
}
if src.Spec.IngressOptions.AllowedHostnames != nil {
t.Spec.IngressHostnames = &AllowedListSpec{
Exact: src.Spec.IngressOptions.AllowedHostnames.Exact,
Regex: src.Spec.IngressOptions.AllowedHostnames.Regex,
}
in.Spec.IngressHostnames = src.Spec.IngressOptions.AllowedHostnames
}
if src.Spec.ContainerRegistries != nil {
t.Spec.ContainerRegistries = &AllowedListSpec{
Exact: src.Spec.ContainerRegistries.Exact,
Regex: src.Spec.ContainerRegistries.Regex,
}
in.Spec.ContainerRegistries = src.Spec.ContainerRegistries
}
if len(src.Spec.NetworkPolicies.Items) > 0 {
t.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items
in.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items
}
if len(src.Spec.LimitRanges.Items) > 0 {
t.Spec.LimitRanges = src.Spec.LimitRanges.Items
in.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
in.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope)
in.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,
})
}
}
in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings
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)
}
in.Spec.ExternalServiceIPs = src.Spec.ServiceOptions.ExternalServiceIPs
}
if len(src.Spec.ImagePullPolicies) != 0 {
@@ -586,35 +546,35 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
pullPolicies = append(pullPolicies, string(policy))
}
t.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",")
in.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, ",")
in.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",")
}
if src.Spec.PriorityClasses.Regex != "" {
t.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex
in.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex
}
}
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil {
if src.Spec.ServiceOptions.AllowedServices.NodePort != nil {
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
in.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
}
if src.Spec.ServiceOptions.AllowedServices.ExternalName != nil {
t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
in.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
}
if src.Spec.ServiceOptions.AllowedServices.LoadBalancer != nil {
t.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer)
in.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer)
}
}
// Status
t.Status = TenantStatus{
in.Status = TenantStatus{
Size: src.Status.Size,
Namespaces: src.Status.Namespaces,
}

View File

@@ -16,6 +16,7 @@ import (
"k8s.io/utils/pointer"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
"github.com/clastix/capsule/pkg/api"
)
// nolint:maintidx
@@ -25,19 +26,19 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
nodeSelector := map[string]string{
"foo": "bar",
}
v1alpha1AdditionalMetadataSpec := &AdditionalMetadataSpec{
AdditionalLabels: map[string]string{
v1alpha1AdditionalMetadataSpec := &AdditionalMetadata{
Labels: map[string]string{
"foo": "bar",
},
AdditionalAnnotations: map[string]string{
Annotations: map[string]string{
"foo": "bar",
},
}
v1alpha1AllowedListSpec := &AllowedListSpec{
v1alpha1AllowedListSpec := &api.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
}
v1beta1AdditionalMetadataSpec := &capsulev1beta1.AdditionalMetadataSpec{
v1beta1AdditionalMetadataSpec := &api.AdditionalMetadataSpec{
Labels: map[string]string{
"foo": "bar",
},
@@ -49,20 +50,22 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
Quota: &namespaceQuota,
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
}
v1beta1ServiceOptions := &capsulev1beta1.ServiceOptions{
v1beta1ServiceOptions := &api.ServiceOptions{
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
AllowedServices: &capsulev1beta1.AllowedServices{
AllowedServices: &api.AllowedServices{
NodePort: pointer.BoolPtr(false),
ExternalName: pointer.BoolPtr(false),
LoadBalancer: pointer.BoolPtr(false),
},
ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{
Allowed: []capsulev1beta1.AllowedIP{"192.168.0.1"},
ExternalServiceIPs: &api.ExternalServiceIPsSpec{
Allowed: []api.AllowedIP{"192.168.0.1"},
},
}
v1beta1AllowedListSpec := &capsulev1beta1.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
v1beta2AllowedListSpec := &api.SelectorAllowedListSpec{
AllowedListSpec: api.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
},
}
networkPolicies := []networkingv1.NetworkPolicySpec{
{
@@ -234,25 +237,25 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
},
NamespaceOptions: v1beta1NamespaceOptions,
ServiceOptions: v1beta1ServiceOptions,
StorageClasses: v1beta1AllowedListSpec,
StorageClasses: &v1beta2AllowedListSpec.AllowedListSpec,
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled,
AllowedClasses: v1beta1AllowedListSpec,
AllowedHostnames: v1beta1AllowedListSpec,
HostnameCollisionScope: api.HostnameCollisionScopeDisabled,
AllowedClasses: &v1beta2AllowedListSpec.AllowedListSpec,
AllowedHostnames: &v1beta2AllowedListSpec.AllowedListSpec,
},
ContainerRegistries: v1beta1AllowedListSpec,
ContainerRegistries: &v1beta2AllowedListSpec.AllowedListSpec,
NodeSelector: nodeSelector,
NetworkPolicies: capsulev1beta1.NetworkPolicySpec{
NetworkPolicies: api.NetworkPolicySpec{
Items: networkPolicies,
},
LimitRanges: capsulev1beta1.LimitRangesSpec{
LimitRanges: api.LimitRangesSpec{
Items: limitRanges,
},
ResourceQuota: capsulev1beta1.ResourceQuotaSpec{
Scope: capsulev1beta1.ResourceQuotaScopeNamespace,
ResourceQuota: api.ResourceQuotaSpec{
Scope: api.ResourceQuotaScopeNamespace,
Items: resourceQuotas,
},
AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{
AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{
{
ClusterRoleName: "crds-rolebinding",
Subjects: []rbacv1.Subject{
@@ -264,8 +267,8 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
},
},
},
ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"},
PriorityClasses: &capsulev1beta1.AllowedListSpec{
ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"},
PriorityClasses: &api.AllowedListSpec{
Exact: []string{"default"},
Regex: "^tier-.*$",
},
@@ -323,7 +326,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
NetworkPolicies: networkPolicies,
LimitRanges: limitRanges,
ResourceQuota: resourceQuotas,
AdditionalRoleBindings: []AdditionalRoleBindingsSpec{
AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{
{
ClusterRoleName: "crds-rolebinding",
Subjects: []rbacv1.Subject{
@@ -335,8 +338,8 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
},
},
},
ExternalServiceIPs: &ExternalServiceIPsSpec{
Allowed: []AllowedIP{"192.168.0.1"},
ExternalServiceIPs: &api.ExternalServiceIPsSpec{
Allowed: []api.AllowedIP{"192.168.0.1"},
},
},
Status: TenantStatus{

View File

@@ -9,24 +9,16 @@ 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 {
func (in *Tenant) IsFull() bool {
// we don't have limits on assigned Namespaces
if t.Spec.NamespaceQuota == nil {
if in.Spec.NamespaceQuota == nil {
return false
}
return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceQuota)
return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceQuota)
}
func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
var l []string
for _, ns := range namespaces {
@@ -37,6 +29,6 @@ func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
sort.Strings(l)
t.Status.Namespaces = l
t.Status.Size = uint(len(l))
in.Status.Namespaces = l
in.Status.Size = uint(len(l))
}

View File

@@ -7,26 +7,28 @@ import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/clastix/capsule/pkg/api"
)
// TenantSpec defines the desired state of Tenant.
type TenantSpec struct {
Owner OwnerSpec `json:"owner"`
//+kubebuilder:validation:Minimum=1
// +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"`
NamespacesMetadata *AdditionalMetadata `json:"namespacesMetadata,omitempty"`
ServicesMetadata *AdditionalMetadata `json:"servicesMetadata,omitempty"`
StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"`
IngressClasses *api.AllowedListSpec `json:"ingressClasses,omitempty"`
IngressHostnames *api.AllowedListSpec `json:"ingressHostnames,omitempty"`
ContainerRegistries *api.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"`
AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
ExternalServiceIPs *api.ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"`
}
// TenantStatus defines the observed state of Tenant.
@@ -44,6 +46,7 @@ type TenantStatus struct {
// +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"
// +kubebuilder:deprecatedversion:warning="This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version."
// Tenant is the Schema for the tenants API.
type Tenant struct {

View File

@@ -4,18 +4,18 @@
package v1alpha1
import (
"io/ioutil"
"os"
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")
func (in *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error {
certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt")
if len(certData) == 0 {
return nil
}
return ctrl.NewWebhookManagedBy(mgr).
For(t).
For(in).
Complete()
}

View File

@@ -9,24 +9,24 @@
package v1alpha1
import (
"github.com/clastix/capsule/pkg/api"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/api/networking/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) {
*out = *in
if in.AdditionalLabels != nil {
in, out := &in.AdditionalLabels, &out.AdditionalLabels
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.AdditionalAnnotations != nil {
in, out := &in.AdditionalAnnotations, &out.AdditionalAnnotations
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
@@ -34,52 +34,12 @@ func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec.
func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadata.
func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
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)
out := new(AdditionalMetadata)
in.DeepCopyInto(out)
return out
}
@@ -162,26 +122,6 @@ func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec {
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 *OwnerSpec) DeepCopyInto(out *OwnerSpec) {
*out = *in
@@ -267,32 +207,32 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
}
if in.NamespacesMetadata != nil {
in, out := &in.NamespacesMetadata, &out.NamespacesMetadata
*out = new(AdditionalMetadataSpec)
*out = new(AdditionalMetadata)
(*in).DeepCopyInto(*out)
}
if in.ServicesMetadata != nil {
in, out := &in.ServicesMetadata, &out.ServicesMetadata
*out = new(AdditionalMetadataSpec)
*out = new(AdditionalMetadata)
(*in).DeepCopyInto(*out)
}
if in.StorageClasses != nil {
in, out := &in.StorageClasses, &out.StorageClasses
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.IngressClasses != nil {
in, out := &in.IngressClasses, &out.IngressClasses
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.IngressHostnames != nil {
in, out := &in.IngressHostnames, &out.IngressHostnames
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.ContainerRegistries != nil {
in, out := &in.ContainerRegistries, &out.ContainerRegistries
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.NodeSelector != nil {
@@ -304,7 +244,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
}
if in.NetworkPolicies != nil {
in, out := &in.NetworkPolicies, &out.NetworkPolicies
*out = make([]networkingv1.NetworkPolicySpec, len(*in))
*out = make([]v1.NetworkPolicySpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -325,14 +265,14 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
}
if in.AdditionalRoleBindings != nil {
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
*out = make([]AdditionalRoleBindingsSpec, len(*in))
*out = make([]api.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)
*out = new(api.ExternalServiceIPsSpec)
(*in).DeepCopyInto(*out)
}
}

View File

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

@@ -4,11 +4,11 @@
package v1beta1
const (
denyWildcard = "capsule.clastix.io/deny-wildcard"
DenyWildcard = "capsule.clastix.io/deny-wildcard"
)
func (t *Tenant) IsWildcardDenied() bool {
if v, ok := t.Annotations[denyWildcard]; ok && v == "true" {
func (in *Tenant) IsWildcardDenied() bool {
if v, ok := in.Annotations[DenyWildcard]; ok && v == "true" {
return true
}

View File

@@ -2,8 +2,8 @@
// 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
// +kubebuilder:object:generate=true
// +groupName=capsule.clastix.io
package v1beta1
import (

View File

@@ -3,9 +3,13 @@
package v1beta1
import (
"github.com/clastix/capsule/pkg/api"
)
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"`
AllowedClasses *api.AllowedListSpec `json:"allowedClasses,omitempty"`
// Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames.
//
//
@@ -18,7 +22,7 @@ type IngressOptions struct {
//
// Optional.
// +kubebuilder:default=Disabled
HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"`
HostnameCollisionScope api.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"`
AllowedHostnames *api.AllowedListSpec `json:"allowedHostnames,omitempty"`
}

View File

@@ -1,57 +1,64 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
import "strings"
import (
"strings"
"github.com/clastix/capsule/pkg/api"
)
type NamespaceOptions struct {
//+kubebuilder:validation:Minimum=1
// +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"`
AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
}
func (t *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool {
if _, ok := t.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok {
func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool {
if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok {
return true
}
if _, ok := t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
return true
}
return false
}
func (t *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool {
if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok {
func (in *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool {
if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok {
return true
}
if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
return true
}
return false
}
func (t *Tenant) ForbiddenUserNamespaceLabels() *ForbiddenListSpec {
if !t.hasForbiddenNamespaceLabelsAnnotations() {
func (in *Tenant) ForbiddenUserNamespaceLabels() *api.ForbiddenListSpec {
if !in.hasForbiddenNamespaceLabelsAnnotations() {
return nil
}
return &ForbiddenListSpec{
Exact: strings.Split(t.Annotations[ForbiddenNamespaceLabelsAnnotation], ","),
Regex: t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation],
return &api.ForbiddenListSpec{
Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceLabelsAnnotation], ","),
Regex: in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation],
}
}
func (t *Tenant) ForbiddenUserNamespaceAnnotations() *ForbiddenListSpec {
if !t.hasForbiddenNamespaceAnnotationsAnnotations() {
func (in *Tenant) ForbiddenUserNamespaceAnnotations() *api.ForbiddenListSpec {
if !in.hasForbiddenNamespaceAnnotationsAnnotations() {
return nil
}
return &ForbiddenListSpec{
Exact: strings.Split(t.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","),
Regex: t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation],
return &api.ForbiddenListSpec{
Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation], ","),
Regex: in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation],
}
}

View File

@@ -1,3 +1,6 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
import (
@@ -6,14 +9,14 @@ import (
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
func (in OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) {
sort.Sort(ByKindAndName(in))
i := sort.Search(len(in), func(i int) bool {
return in[i].Kind >= kind && in[i].Name >= name
})
if i < len(o) && o[i].Kind == kind && o[i].Name == name {
return o[i]
if i < len(in) && in[i].Kind == kind && in[i].Name == name {
return in[i]
}
return
@@ -21,18 +24,18 @@ func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec)
type ByKindAndName OwnerListSpec
func (b ByKindAndName) Len() int {
return len(b)
func (in ByKindAndName) Len() int {
return len(in)
}
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()
func (in ByKindAndName) Less(i, j int) bool {
if in[i].Kind.String() != in[j].Kind.String() {
return in[i].Kind.String() < in[j].Kind.String()
}
return b[i].Name < b[j].Name
return in[i].Name < in[j].Name
}
func (b ByKindAndName) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
func (in ByKindAndName) Swap(i, j int) {
in[i], in[j] = in[j], in[i]
}

View File

@@ -1,3 +1,6 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
import (

View File

@@ -19,7 +19,7 @@ const (
// 2. the overall length of the annotation key that is exceeding 63 characters
// For emails, the symbol @ can be replaced with the placeholder __AT__.
// For the latter one, the index of the owner can be used to force the retrieval.
func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string {
func (in *OwnerSpec) GetRoles(tenant Tenant, index int) []string {
for key, value := range tenant.GetAnnotations() {
if !strings.HasPrefix(key, fmt.Sprintf("%s/", ClusterRoleNamesAnnotation)) {
continue
@@ -41,7 +41,7 @@ func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string {
return []string{"admin", "capsule-namespace-deleter"}
}
func (in OwnerSpec) convertMap() map[string]string {
func (in *OwnerSpec) convertMap() map[string]string {
return map[string]string{
"__AT__": "@",
}

View File

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

@@ -4,13 +4,13 @@
package v1beta1
type AllowedServices struct {
//+kubebuilder:default=true
// +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
// +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
// +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

@@ -3,11 +3,15 @@
package v1beta1
import (
"github.com/clastix/capsule/pkg/api"
)
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"`
AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
// Block or deny certain type of Services. Optional.
AllowedServices *AllowedServices `json:"allowedServices,omitempty"`
AllowedServices *api.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"`
ExternalServiceIPs *api.ExternalServiceIPsSpec `json:"externalIPs,omitempty"`
}

View File

@@ -1,31 +0,0 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
import (
"fmt"
"strings"
)
const (
AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes"
AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp"
AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes"
AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp"
AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries"
AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp"
ForbiddenNamespaceLabelsAnnotation = "capsule.clastix.io/forbidden-namespace-labels"
ForbiddenNamespaceLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-labels-regexp"
ForbiddenNamespaceAnnotationsAnnotation = "capsule.clastix.io/forbidden-namespace-annotations"
ForbiddenNamespaceAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-annotations-regexp"
ProtectedTenantAnnotation = "capsule.clastix.io/protected"
)
func UsedQuotaFor(resource fmt.Stringer) string {
return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_")
}
func HardQuotaFor(resource fmt.Stringer) string {
return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_")
}

View File

@@ -9,24 +9,24 @@ import (
corev1 "k8s.io/api/core/v1"
)
func (t *Tenant) IsCordoned() bool {
if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" {
func (in *Tenant) IsCordoned() bool {
if v, ok := in.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" {
return true
}
return false
}
func (t *Tenant) IsFull() bool {
func (in *Tenant) IsFull() bool {
// we don't have limits on assigned Namespaces
if t.Spec.NamespaceOptions == nil || t.Spec.NamespaceOptions.Quota == nil {
if in.Spec.NamespaceOptions == nil || in.Spec.NamespaceOptions.Quota == nil {
return false
}
return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceOptions.Quota)
return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceOptions.Quota)
}
func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
var l []string
for _, ns := range namespaces {
@@ -37,10 +37,10 @@ func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
sort.Strings(l)
t.Status.Namespaces = l
t.Status.Size = uint(len(l))
in.Status.Namespaces = l
in.Status.Size = uint(len(l))
}
func (t *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings {
return t.Spec.Owners.FindOwner(name, kind).ProxyOperations
func (in *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings {
return in.Spec.Owners.FindOwner(name, kind).ProxyOperations
}

View File

@@ -13,7 +13,7 @@ const (
// Returns the observed state of the Tenant.
type TenantStatus struct {
//+kubebuilder:default=Active
// +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.

View File

@@ -5,6 +5,8 @@ package v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/clastix/capsule/pkg/api"
)
// TenantSpec defines the desired state of Tenant.
@@ -14,32 +16,31 @@ type TenantSpec struct {
// 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"`
ServiceOptions *api.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"`
StorageClasses *api.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"`
ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"`
// Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"`
NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"`
// Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"`
LimitRanges api.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"`
ResourceQuota api.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"`
AdditionalRoleBindings []api.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"`
ImagePullPolicies []api.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"`
PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"`
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:storageversion
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +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"
@@ -56,9 +57,9 @@ type Tenant struct {
Status TenantStatus `json:"status,omitempty"`
}
func (t *Tenant) Hub() {}
func (in *Tenant) Hub() {}
//+kubebuilder:object:root=true
// +kubebuilder:object:root=true
// TenantList contains a list of Tenant.
type TenantList struct {
@@ -70,3 +71,13 @@ type TenantList struct {
func init() {
SchemeBuilder.Register(&Tenant{}, &TenantList{})
}
func (in *Tenant) GetNamespaces() (res []string) {
res = make([]string, 0, len(in.Status.Namespaces))
for _, ns := range in.Status.Namespaces {
res = append(res, ns)
}
return
}

View File

@@ -0,0 +1,21 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
import (
"os"
ctrl "sigs.k8s.io/controller-runtime"
)
func (in *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error {
certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt")
if len(certData) == 0 {
return nil
}
return ctrl.NewWebhookManagedBy(mgr).
For(in).
Complete()
}

View File

@@ -9,81 +9,10 @@
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"
"github.com/clastix/capsule/pkg/api"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *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
@@ -135,57 +64,17 @@ func (in ByKindAndName) DeepCopy() ByKindAndName {
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)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.AllowedHostnames != nil {
in, out := &in.AllowedHostnames, &out.AllowedHostnames
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
}
@@ -200,28 +89,6 @@ func (in *IngressOptions) DeepCopy() *IngressOptions {
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
@@ -232,7 +99,7 @@ func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) {
}
if in.AdditionalMetadata != nil {
in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
*out = new(AdditionalMetadataSpec)
*out = new(api.AdditionalMetadataSpec)
(*in).DeepCopyInto(*out)
}
}
@@ -247,28 +114,6 @@ func (in *NamespaceOptions) DeepCopy() *NamespaceOptions {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) {
*out = *in
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]networkingv1.NetworkPolicySpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec.
func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec {
if in == nil {
return nil
}
out := new(NetworkPolicySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) {
*out = *in
@@ -347,44 +192,22 @@ func (in *ProxySettings) DeepCopy() *ProxySettings {
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)
*out = new(api.AdditionalMetadataSpec)
(*in).DeepCopyInto(*out)
}
if in.AllowedServices != nil {
in, out := &in.AllowedServices, &out.AllowedServices
*out = new(AllowedServices)
*out = new(api.AllowedServices)
(*in).DeepCopyInto(*out)
}
if in.ExternalServiceIPs != nil {
in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs
*out = new(ExternalServiceIPsSpec)
*out = new(api.ExternalServiceIPsSpec)
(*in).DeepCopyInto(*out)
}
}
@@ -475,18 +298,18 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
}
if in.ServiceOptions != nil {
in, out := &in.ServiceOptions, &out.ServiceOptions
*out = new(ServiceOptions)
*out = new(api.ServiceOptions)
(*in).DeepCopyInto(*out)
}
if in.StorageClasses != nil {
in, out := &in.StorageClasses, &out.StorageClasses
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
in.IngressOptions.DeepCopyInto(&out.IngressOptions)
if in.ContainerRegistries != nil {
in, out := &in.ContainerRegistries, &out.ContainerRegistries
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.NodeSelector != nil {
@@ -501,19 +324,19 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
in.ResourceQuota.DeepCopyInto(&out.ResourceQuota)
if in.AdditionalRoleBindings != nil {
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
*out = make([]AdditionalRoleBindingsSpec, len(*in))
*out = make([]api.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))
*out = make([]api.ImagePullPolicySpec, len(*in))
copy(*out, *in)
}
if in.PriorityClasses != nil {
in, out := &in.PriorityClasses, &out.PriorityClasses
*out = new(AllowedListSpec)
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
}

View File

@@ -1,7 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
package v1beta2
import rbacv1 "k8s.io/api/rbac/v1"

View File

@@ -0,0 +1,142 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"fmt"
"strconv"
"strings"
"sigs.k8s.io/controller-runtime/pkg/conversion"
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
)
func (in *CapsuleConfiguration) ConvertTo(raw conversion.Hub) error {
dst, ok := raw.(*capsulev1alpha1.CapsuleConfiguration)
if !ok {
return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", dst)
}
dst.ObjectMeta = in.ObjectMeta
dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString
dst.Spec.UserGroups = in.Spec.UserGroups
dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString
annotations := dst.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
if in.Spec.NodeMetadata != nil {
if len(in.Spec.NodeMetadata.ForbiddenLabels.Exact) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",")
}
if len(in.Spec.NodeMetadata.ForbiddenLabels.Regex) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex
}
if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",")
}
if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Regex) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex
}
}
annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName] = fmt.Sprintf("%t", in.Spec.EnableTLSReconciler)
annotations[capsulev1alpha1.TLSSecretNameAnnotation] = in.Spec.CapsuleResources.TLSSecretName
annotations[capsulev1alpha1.MutatingWebhookConfigurationName] = in.Spec.CapsuleResources.MutatingWebhookConfigurationName
annotations[capsulev1alpha1.ValidatingWebhookConfigurationName] = in.Spec.CapsuleResources.ValidatingWebhookConfigurationName
dst.SetAnnotations(annotations)
return nil
}
func (in *CapsuleConfiguration) ConvertFrom(raw conversion.Hub) error {
src, ok := raw.(*capsulev1alpha1.CapsuleConfiguration)
if !ok {
return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", src)
}
in.ObjectMeta = src.ObjectMeta
in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString
in.Spec.UserGroups = src.Spec.UserGroups
in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString
annotations := src.GetAnnotations()
if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenLabels.Exact = strings.Split(value, ",")
delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsAnnotation)
}
if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenLabels.Regex = value
delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation)
}
if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenAnnotations.Exact = strings.Split(value, ",")
delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation)
}
if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenAnnotations.Regex = value
delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation)
}
if value, found := annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName]; found {
v, _ := strconv.ParseBool(value)
in.Spec.EnableTLSReconciler = v
delete(annotations, capsulev1alpha1.EnableTLSConfigurationAnnotationName)
}
if value, found := annotations[capsulev1alpha1.TLSSecretNameAnnotation]; found {
in.Spec.CapsuleResources.TLSSecretName = value
delete(annotations, capsulev1alpha1.TLSSecretNameAnnotation)
}
if value, found := annotations[capsulev1alpha1.MutatingWebhookConfigurationName]; found {
in.Spec.CapsuleResources.MutatingWebhookConfigurationName = value
delete(annotations, capsulev1alpha1.MutatingWebhookConfigurationName)
}
if value, found := annotations[capsulev1alpha1.ValidatingWebhookConfigurationName]; found {
in.Spec.CapsuleResources.ValidatingWebhookConfigurationName = value
delete(annotations, capsulev1alpha1.ValidatingWebhookConfigurationName)
}
in.SetAnnotations(annotations)
return nil
}

View File

@@ -0,0 +1,79 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/clastix/capsule/pkg/api"
)
// 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"`
// Allows to set different name rather than the canonical one for the Capsule configuration objects,
// such as webhook secret or configurations.
// +kubebuilder:default={TLSSecretName:"capsule-tls",mutatingWebhookConfigurationName:"capsule-mutating-webhook-configuration",validatingWebhookConfigurationName:"capsule-validating-webhook-configuration"}
CapsuleResources CapsuleResources `json:"overrides,omitempty"`
// Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant.
// This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes.
NodeMetadata *NodeMetadata `json:"nodeMetadata,omitempty"`
// Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks
// when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager.
// +kubebuilder:default=true
EnableTLSReconciler bool `json:"enableTLSReconciler"` //nolint:tagliatelle
}
type NodeMetadata struct {
// Define the labels that a Tenant Owner cannot set for their nodes.
ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels"`
// Define the annotations that a Tenant Owner cannot set for their nodes.
ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"`
}
type CapsuleResources struct {
// Defines the Secret name used for the webhook server.
// Must be in the same Namespace where the Capsule Deployment is deployed.
// +kubebuilder:default=capsule-tls
TLSSecretName string `json:"TLSSecretName"` //nolint:tagliatelle
// Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
// +kubebuilder:default=capsule-mutating-webhook-configuration
MutatingWebhookConfigurationName string `json:"mutatingWebhookConfigurationName"`
// Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
// +kubebuilder:default=capsule-validating-webhook-configuration
ValidatingWebhookConfigurationName string `json:"validatingWebhookConfigurationName"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:storageversion
// 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,59 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"fmt"
"strconv"
)
const (
ResourceQuotaAnnotationPrefix = "quota.resources.capsule.clastix.io"
ResourceUsedAnnotationPrefix = "used.resources.capsule.clastix.io"
)
func UsedAnnotationForResource(kindGroup string) string {
return fmt.Sprintf("%s/%s", ResourceUsedAnnotationPrefix, kindGroup)
}
func LimitAnnotationForResource(kindGroup string) string {
return fmt.Sprintf("%s/%s", ResourceQuotaAnnotationPrefix, kindGroup)
}
func GetUsedResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) {
usedStr, ok := tenant.GetAnnotations()[UsedAnnotationForResource(kindGroup)]
if !ok {
usedStr = "0"
}
used, _ := strconv.ParseInt(usedStr, 10, 10)
return used, nil
}
type NonLimitedResourceError struct {
kindGroup string
}
func NewNonLimitedResourceError(kindGroup string) *NonLimitedResourceError {
return &NonLimitedResourceError{kindGroup: kindGroup}
}
func (n NonLimitedResourceError) Error() string {
return fmt.Sprintf("resource %s is not limited for the current tenant", n.kindGroup)
}
func GetLimitResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) {
limitStr, ok := tenant.GetAnnotations()[LimitAnnotationForResource(kindGroup)]
if !ok {
return 0, NewNonLimitedResourceError(kindGroup)
}
limit, err := strconv.ParseInt(limitStr, 10, 10)
if err != nil {
return 0, fmt.Errorf("resource %s limit cannot be parsed, %w", kindGroup, err)
}
return limit, nil
}

View File

@@ -0,0 +1,23 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
// Package v1beta2 contains API Schema definitions for the capsule v1beta2 API group
// +kubebuilder:object:generate=true
// +groupName=capsule.clastix.io
package v1beta2
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: "v1beta2"}
// 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,33 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"github.com/clastix/capsule/pkg/api"
)
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.
// A default value can be specified, and all the Ingress resources created will inherit the declared class.
// Optional.
AllowedClasses *api.DefaultAllowedListSpec `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 api.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 *api.AllowedListSpec `json:"allowedHostnames,omitempty"`
// Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.
AllowWildcardHostnames bool `json:"allowWildcardHostnames,omitempty"`
}

View File

@@ -0,0 +1,20 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"github.com/clastix/capsule/pkg/api"
)
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 *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
// Define the labels that a Tenant Owner cannot set for their Namespace resources.
ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitempty"`
// Define the annotations that a Tenant Owner cannot set for their Namespace resources.
ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations,omitempty"`
}

57
api/v1beta2/owner.go Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
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"`
// Defines additional cluster-roles for the specific Owner.
// +kubebuilder:default={admin,capsule-namespace-deleter}
ClusterRoles []string `json:"clusterRoles,omitempty"`
// 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"
)

41
api/v1beta2/owner_list.go Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
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,86 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOwnerListSpec_FindOwner(t *testing.T) {
bla := OwnerSpec{
Kind: UserOwner,
Name: "bla",
ProxyOperations: []ProxySettings{
{
Kind: IngressClassesProxy,
Operations: []ProxyOperation{"Delete"},
},
},
}
bar := OwnerSpec{
Kind: GroupOwner,
Name: "bar",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"Delete"},
},
},
}
baz := OwnerSpec{
Kind: UserOwner,
Name: "baz",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"Update"},
},
},
}
fim := OwnerSpec{
Kind: ServiceAccountOwner,
Name: "fim",
ProxyOperations: []ProxySettings{
{
Kind: NodesProxy,
Operations: []ProxyOperation{"List"},
},
},
}
bom := OwnerSpec{
Kind: GroupOwner,
Name: "bom",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"Delete"},
},
{
Kind: NodesProxy,
Operations: []ProxyOperation{"Delete"},
},
},
}
qip := OwnerSpec{
Kind: ServiceAccountOwner,
Name: "qip",
ProxyOperations: []ProxySettings{
{
Kind: StorageClassesProxy,
Operations: []ProxyOperation{"List", "Delete"},
},
},
}
owners := OwnerListSpec{bom, qip, bla, bar, baz, fim}
assert.Equal(t, owners.FindOwner("bom", GroupOwner), bom)
assert.Equal(t, owners.FindOwner("qip", ServiceAccountOwner), qip)
assert.Equal(t, owners.FindOwner("bla", UserOwner), bla)
assert.Equal(t, owners.FindOwner("bar", GroupOwner), bar)
assert.Equal(t, owners.FindOwner("baz", UserOwner), baz)
assert.Equal(t, owners.FindOwner("fim", ServiceAccountOwner), fim)
assert.Equal(t, owners.FindOwner("notfound", ServiceAccountOwner), OwnerSpec{})
}

View File

@@ -0,0 +1,17 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"fmt"
"strings"
)
func UsedQuotaFor(resource fmt.Stringer) string {
return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_")
}
func HardQuotaFor(resource fmt.Stringer) string {
return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_")
}

View File

@@ -0,0 +1,289 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"fmt"
"strconv"
"strings"
"sigs.k8s.io/controller-runtime/pkg/conversion"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
"github.com/clastix/capsule/pkg/api"
)
func (in *Tenant) ConvertFrom(raw conversion.Hub) error {
src, ok := raw.(*capsulev1beta1.Tenant)
if !ok {
return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw)
}
annotations := src.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
in.ObjectMeta = src.ObjectMeta
in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners))
for index, owner := range src.Spec.Owners {
proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations))
for _, proxyOp := range owner.ProxyOperations {
ops := make([]ProxyOperation, 0, len(proxyOp.Operations))
for _, op := range proxyOp.Operations {
ops = append(ops, ProxyOperation(op))
}
proxySettings = append(proxySettings, ProxySettings{
Kind: ProxyServiceKind(proxyOp.Kind),
Operations: ops,
})
}
in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{
Kind: OwnerKind(owner.Kind),
Name: owner.Name,
ClusterRoles: owner.GetRoles(*src, index),
ProxyOperations: proxySettings,
})
}
if nsOpts := src.Spec.NamespaceOptions; nsOpts != nil {
in.Spec.NamespaceOptions = &NamespaceOptions{}
in.Spec.NamespaceOptions.Quota = src.Spec.NamespaceOptions.Quota
in.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata
if value, found := annotations[api.ForbiddenNamespaceLabelsAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",")
delete(annotations, api.ForbiddenNamespaceLabelsAnnotation)
}
if value, found := annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value
delete(annotations, api.ForbiddenNamespaceLabelsRegexpAnnotation)
}
if value, found := annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",")
delete(annotations, api.ForbiddenNamespaceAnnotationsAnnotation)
}
if value, found := annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value
delete(annotations, api.ForbiddenNamespaceAnnotationsRegexpAnnotation)
}
}
in.Spec.ServiceOptions = src.Spec.ServiceOptions
if src.Spec.StorageClasses != nil {
in.Spec.StorageClasses = &api.DefaultAllowedListSpec{
SelectorAllowedListSpec: api.SelectorAllowedListSpec{
AllowedListSpec: *src.Spec.StorageClasses,
},
}
}
if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 {
in.Spec.IngressOptions.HostnameCollisionScope = scope
}
v, found := annotations[capsulev1beta1.DenyWildcard]
if found {
value, err := strconv.ParseBool(v)
if err == nil {
in.Spec.IngressOptions.AllowWildcardHostnames = !value
delete(annotations, capsulev1beta1.DenyWildcard)
}
}
if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil {
in.Spec.IngressOptions.AllowedClasses = &api.DefaultAllowedListSpec{
SelectorAllowedListSpec: api.SelectorAllowedListSpec{
AllowedListSpec: *ingressClass,
},
}
}
if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil {
in.Spec.IngressOptions.AllowedHostnames = hostnames
}
in.Spec.ContainerRegistries = src.Spec.ContainerRegistries
in.Spec.NodeSelector = src.Spec.NodeSelector
in.Spec.NetworkPolicies = src.Spec.NetworkPolicies
in.Spec.LimitRanges = src.Spec.LimitRanges
in.Spec.ResourceQuota = src.Spec.ResourceQuota
in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings
in.Spec.ImagePullPolicies = src.Spec.ImagePullPolicies
if src.Spec.PriorityClasses != nil {
in.Spec.PriorityClasses = &api.DefaultAllowedListSpec{
SelectorAllowedListSpec: api.SelectorAllowedListSpec{
AllowedListSpec: *src.Spec.PriorityClasses,
},
}
}
if v, found := annotations["capsule.clastix.io/cordon"]; found {
value, err := strconv.ParseBool(v)
if err == nil {
delete(annotations, "capsule.clastix.io/cordon")
}
in.Spec.Cordoned = value
}
if _, found := annotations[api.ProtectedTenantAnnotation]; found {
in.Spec.PreventDeletion = true
delete(annotations, api.ProtectedTenantAnnotation)
}
in.SetAnnotations(annotations)
in.Status.Namespaces = src.Status.Namespaces
in.Status.Size = src.Status.Size
switch src.Status.State {
case capsulev1beta1.TenantStateActive:
in.Status.State = TenantStateActive
case capsulev1beta1.TenantStateCordoned:
in.Status.State = TenantStateCordoned
default:
in.Status.State = TenantStateActive
}
return nil
}
func (in *Tenant) ConvertTo(raw conversion.Hub) error {
dst, ok := raw.(*capsulev1beta1.Tenant)
if !ok {
return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw)
}
annotations := in.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
dst.ObjectMeta = in.ObjectMeta
dst.Spec.Owners = make(capsulev1beta1.OwnerListSpec, 0, len(in.Spec.Owners))
for index, owner := range in.Spec.Owners {
proxySettings := make([]capsulev1beta1.ProxySettings, 0, len(owner.ProxyOperations))
for _, proxyOp := range owner.ProxyOperations {
ops := make([]capsulev1beta1.ProxyOperation, 0, len(proxyOp.Operations))
for _, op := range proxyOp.Operations {
ops = append(ops, capsulev1beta1.ProxyOperation(op))
}
proxySettings = append(proxySettings, capsulev1beta1.ProxySettings{
Kind: capsulev1beta1.ProxyServiceKind(proxyOp.Kind),
Operations: ops,
})
}
dst.Spec.Owners = append(dst.Spec.Owners, capsulev1beta1.OwnerSpec{
Kind: capsulev1beta1.OwnerKind(owner.Kind),
Name: owner.Name,
ProxyOperations: proxySettings,
})
if clusterRoles := owner.ClusterRoles; len(clusterRoles) > 0 {
annotations[fmt.Sprintf("%s/%d", capsulev1beta1.ClusterRoleNamesAnnotation, index)] = strings.Join(owner.ClusterRoles, ",")
}
}
if nsOpts := in.Spec.NamespaceOptions; nsOpts != nil {
dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{}
dst.Spec.NamespaceOptions.Quota = nsOpts.Quota
dst.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata
if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 {
annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",")
}
if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 {
annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex
}
if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 {
annotations[api.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",")
}
if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 {
annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = regex
}
}
dst.Spec.ServiceOptions = in.Spec.ServiceOptions
if in.Spec.StorageClasses != nil {
dst.Spec.StorageClasses = &in.Spec.StorageClasses.AllowedListSpec
}
dst.Spec.IngressOptions.HostnameCollisionScope = in.Spec.IngressOptions.HostnameCollisionScope
if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil {
dst.Spec.IngressOptions.AllowedClasses = &allowed.AllowedListSpec
}
if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil {
dst.Spec.IngressOptions.AllowedHostnames = allowed
}
annotations[capsulev1beta1.DenyWildcard] = fmt.Sprintf("%t", !in.Spec.IngressOptions.AllowWildcardHostnames)
if allowed := in.Spec.ContainerRegistries; allowed != nil {
dst.Spec.ContainerRegistries = allowed
}
dst.Spec.NodeSelector = in.Spec.NodeSelector
dst.Spec.NetworkPolicies = in.Spec.NetworkPolicies
dst.Spec.LimitRanges = in.Spec.LimitRanges
dst.Spec.ResourceQuota = in.Spec.ResourceQuota
dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings
dst.Spec.ImagePullPolicies = in.Spec.ImagePullPolicies
if in.Spec.PriorityClasses != nil {
dst.Spec.PriorityClasses = &in.Spec.PriorityClasses.AllowedListSpec
}
if in.Spec.PreventDeletion {
annotations[api.ProtectedTenantAnnotation] = "true" //nolint:goconst
}
if in.Spec.Cordoned {
annotations["capsule.clastix.io/cordon"] = "true"
}
dst.SetAnnotations(annotations)
dst.Status.Size = in.Status.Size
dst.Status.Namespaces = in.Status.Namespaces
switch in.Status.State {
case TenantStateActive:
dst.Status.State = capsulev1beta1.TenantStateActive
case TenantStateCordoned:
dst.Status.State = capsulev1beta1.TenantStateCordoned
default:
dst.Status.State = capsulev1beta1.TenantStateActive
}
return nil
}

View File

@@ -0,0 +1,38 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"sort"
corev1 "k8s.io/api/core/v1"
)
func (in *Tenant) IsFull() bool {
// we don't have limits on assigned Namespaces
if in.Spec.NamespaceOptions == nil || in.Spec.NamespaceOptions.Quota == nil {
return false
}
return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceOptions.Quota)
}
func (in *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)
in.Status.Namespaces = l
in.Status.Size = uint(len(l))
}
func (in *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings {
return in.Spec.Owners.FindOwner(name, kind).ProxyOperations
}

View File

@@ -1,7 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
package v1beta2
import (
"fmt"
@@ -9,10 +9,10 @@ import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func GetTypeLabel(t runtime.Object) (label string, err error) {
func GetTypeLabel(t metav1.Object) (label string, err error) {
switch v := t.(type) {
case *Tenant:
return "capsule.clastix.io/tenant", nil

View File

@@ -0,0 +1,23 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
// +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,97 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/clastix/capsule/pkg/api"
)
// 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 *api.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.
// A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class.
// Optional.
StorageClasses *api.DefaultAllowedListSpec `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 *api.AllowedListSpec `json:"containerRegistries,omitempty"`
// Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"`
// Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
LimitRanges api.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 api.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 []api.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 []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"`
// Specifies the allowed RuntimeClasses assigned to the Tenant.
// Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses.
// Optional.
RuntimeClasses *api.SelectorAllowedListSpec `json:"runtimeClasses,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.
// A default value can be specified, and all the Pod resources created will inherit the declared class.
// Optional.
PriorityClasses *api.DefaultAllowedListSpec `json:"priorityClasses,omitempty"`
// Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
Cordoned bool `json:"cordoned,omitempty"`
// Prevent accidental deletion of the Tenant.
// When enabled, the deletion request will be declined.
PreventDeletion bool `json:"preventDeletion,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:storageversion
// +kubebuilder:subresource:status
// +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 (in *Tenant) GetNamespaces() (res []string) {
res = make([]string, 0, len(in.Status.Namespaces))
for _, ns := range in.Status.Namespaces {
res = append(res, ns)
}
return
}
// +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,61 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
)
// GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
type GlobalTenantResourceSpec struct {
// Defines the Tenant selector used target the tenants on which resources must be propagated.
TenantSelector metav1.LabelSelector `json:"tenantSelector,omitempty"`
TenantResourceSpec `json:",inline"`
}
// GlobalTenantResourceStatus defines the observed state of GlobalTenantResource.
type GlobalTenantResourceStatus struct {
// List of Tenants addressed by the GlobalTenantResource.
SelectedTenants []string `json:"selectedTenants"`
// List of the replicated resources for the given TenantResource.
ProcessedItems ProcessedItems `json:"processedItems"`
}
type ProcessedItems []ObjectReferenceStatus
func (p *ProcessedItems) AsSet() sets.String {
set := sets.NewString()
for _, i := range *p {
set.Insert(i.String())
}
return set
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources.
type GlobalTenantResource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec GlobalTenantResourceSpec `json:"spec,omitempty"`
Status GlobalTenantResourceStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// GlobalTenantResourceList contains a list of GlobalTenantResource.
type GlobalTenantResourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []GlobalTenantResource `json:"items"`
}
func init() {
SchemeBuilder.Register(&GlobalTenantResource{}, &GlobalTenantResourceList{})
}

View File

@@ -0,0 +1,77 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/clastix/capsule/pkg/api"
)
// TenantResourceSpec defines the desired state of TenantResource.
type TenantResourceSpec struct {
// Define the period of time upon a second reconciliation must be invoked.
// Keep in mind that any change to the manifests will trigger a new reconciliation.
// +kubebuilder:default="60s"
ResyncPeriod metav1.Duration `json:"resyncPeriod"`
// When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted.
// Disable this to keep replicated resources although the deletion of the replication manifest.
// +kubebuilder:default=true
PruningOnDelete *bool `json:"pruningOnDelete,omitempty"`
// Defines the rules to select targeting Namespace, along with the objects that must be replicated.
Resources []ResourceSpec `json:"resources"`
}
type ResourceSpec struct {
// Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated.
// In case of nil value, all the Tenant Namespaces are targeted.
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
// List of the resources already existing in other Namespaces that must be replicated.
NamespacedItems []ObjectReference `json:"namespacedItems,omitempty"`
// List of raw resources that must be replicated.
RawItems []RawExtension `json:"rawItems,omitempty"`
// Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be
// added to the replicated resources.
AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
}
// +kubebuilder:validation:XEmbeddedResource
// +kubebuilder:validation:XPreserveUnknownFields
type RawExtension struct {
runtime.RawExtension `json:",inline"`
}
// TenantResourceStatus defines the observed state of TenantResource.
type TenantResourceStatus struct {
// List of the replicated resources for the given TenantResource.
ProcessedItems ProcessedItems `json:"processedItems"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace.
// The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces.
// For such cases, the GlobalTenantResource must be used.
type TenantResource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec TenantResourceSpec `json:"spec,omitempty"`
Status TenantResourceStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// TenantResourceList contains a list of TenantResource.
type TenantResourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []TenantResource `json:"items"`
}
func init() {
SchemeBuilder.Register(&TenantResource{}, &TenantResourceList{})
}

View File

@@ -0,0 +1,72 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"fmt"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ObjectReferenceAbstract struct {
// Kind of the referent.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
Kind string `json:"kind"`
// Namespace of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
Namespace string `json:"namespace"`
// API version of the referent.
APIVersion string `json:"apiVersion,omitempty"`
}
type ObjectReferenceStatus struct {
ObjectReferenceAbstract `json:",inline"`
// Name of the referent.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Name string `json:"name"`
}
type ObjectReference struct {
ObjectReferenceAbstract `json:",inline"`
// Label selector used to select the given resources in the given Namespace.
Selector metav1.LabelSelector `json:"selector"`
}
func (in *ObjectReferenceStatus) String() string {
return fmt.Sprintf("Kind=%s,APIVersion=%s,Namespace=%s,Name=%s", in.Kind, in.APIVersion, in.Namespace, in.Name)
}
func (in *ObjectReferenceStatus) ParseFromString(value string) error {
rawParts := strings.Split(value, ",")
if len(rawParts) != 4 {
return fmt.Errorf("unexpected raw parts")
}
for _, i := range rawParts {
parts := strings.Split(i, "=")
if len(parts) != 2 {
return fmt.Errorf("unrecognized separator")
}
k, v := parts[0], parts[1]
switch k {
case "Kind":
in.Kind = v
case "APIVersion":
in.APIVersion = v
case "Namespace":
in.Namespace = v
case "Name":
in.Name = v
default:
return fmt.Errorf("unrecognized marker: %s", k)
}
}
return nil
}

View File

@@ -0,0 +1,792 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
// Code generated by controller-gen. DO NOT EDIT.
package v1beta2
import (
"github.com/clastix/capsule/pkg/api"
"k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/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 *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 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 *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)
}
out.CapsuleResources = in.CapsuleResources
if in.NodeMetadata != nil {
in, out := &in.NodeMetadata, &out.NodeMetadata
*out = new(NodeMetadata)
(*in).DeepCopyInto(*out)
}
}
// 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 *CapsuleResources) DeepCopyInto(out *CapsuleResources) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleResources.
func (in *CapsuleResources) DeepCopy() *CapsuleResources {
if in == nil {
return nil
}
out := new(CapsuleResources)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalTenantResource) DeepCopyInto(out *GlobalTenantResource) {
*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 GlobalTenantResource.
func (in *GlobalTenantResource) DeepCopy() *GlobalTenantResource {
if in == nil {
return nil
}
out := new(GlobalTenantResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *GlobalTenantResource) 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 *GlobalTenantResourceList) DeepCopyInto(out *GlobalTenantResourceList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]GlobalTenantResource, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceList.
func (in *GlobalTenantResourceList) DeepCopy() *GlobalTenantResourceList {
if in == nil {
return nil
}
out := new(GlobalTenantResourceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *GlobalTenantResourceList) 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 *GlobalTenantResourceSpec) DeepCopyInto(out *GlobalTenantResourceSpec) {
*out = *in
in.TenantSelector.DeepCopyInto(&out.TenantSelector)
in.TenantResourceSpec.DeepCopyInto(&out.TenantResourceSpec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceSpec.
func (in *GlobalTenantResourceSpec) DeepCopy() *GlobalTenantResourceSpec {
if in == nil {
return nil
}
out := new(GlobalTenantResourceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GlobalTenantResourceStatus) DeepCopyInto(out *GlobalTenantResourceStatus) {
*out = *in
if in.SelectedTenants != nil {
in, out := &in.SelectedTenants, &out.SelectedTenants
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ProcessedItems != nil {
in, out := &in.ProcessedItems, &out.ProcessedItems
*out = make(ProcessedItems, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceStatus.
func (in *GlobalTenantResourceStatus) DeepCopy() *GlobalTenantResourceStatus {
if in == nil {
return nil
}
out := new(GlobalTenantResourceStatus)
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(api.DefaultAllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.AllowedHostnames != nil {
in, out := &in.AllowedHostnames, &out.AllowedHostnames
*out = new(api.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 *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(api.AdditionalMetadataSpec)
(*in).DeepCopyInto(*out)
}
in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels)
in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations)
}
// 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 *NodeMetadata) DeepCopyInto(out *NodeMetadata) {
*out = *in
in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels)
in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetadata.
func (in *NodeMetadata) DeepCopy() *NodeMetadata {
if in == nil {
return nil
}
out := new(NodeMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NonLimitedResourceError.
func (in *NonLimitedResourceError) DeepCopy() *NonLimitedResourceError {
if in == nil {
return nil
}
out := new(NonLimitedResourceError)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectReference) DeepCopyInto(out *ObjectReference) {
*out = *in
out.ObjectReferenceAbstract = in.ObjectReferenceAbstract
in.Selector.DeepCopyInto(&out.Selector)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference.
func (in *ObjectReference) DeepCopy() *ObjectReference {
if in == nil {
return nil
}
out := new(ObjectReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectReferenceAbstract) DeepCopyInto(out *ObjectReferenceAbstract) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferenceAbstract.
func (in *ObjectReferenceAbstract) DeepCopy() *ObjectReferenceAbstract {
if in == nil {
return nil
}
out := new(ObjectReferenceAbstract)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectReferenceStatus) DeepCopyInto(out *ObjectReferenceStatus) {
*out = *in
out.ObjectReferenceAbstract = in.ObjectReferenceAbstract
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferenceStatus.
func (in *ObjectReferenceStatus) DeepCopy() *ObjectReferenceStatus {
if in == nil {
return nil
}
out := new(ObjectReferenceStatus)
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.ClusterRoles != nil {
in, out := &in.ClusterRoles, &out.ClusterRoles
*out = make([]string, len(*in))
copy(*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 ProcessedItems) DeepCopyInto(out *ProcessedItems) {
{
in := &in
*out = make(ProcessedItems, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProcessedItems.
func (in ProcessedItems) DeepCopy() ProcessedItems {
if in == nil {
return nil
}
out := new(ProcessedItems)
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 *RawExtension) DeepCopyInto(out *RawExtension) {
*out = *in
in.RawExtension.DeepCopyInto(&out.RawExtension)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RawExtension.
func (in *RawExtension) DeepCopy() *RawExtension {
if in == nil {
return nil
}
out := new(RawExtension)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) {
*out = *in
if in.NamespaceSelector != nil {
in, out := &in.NamespaceSelector, &out.NamespaceSelector
*out = new(metav1.LabelSelector)
(*in).DeepCopyInto(*out)
}
if in.NamespacedItems != nil {
in, out := &in.NamespacedItems, &out.NamespacedItems
*out = make([]ObjectReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.RawItems != nil {
in, out := &in.RawItems, &out.RawItems
*out = make([]RawExtension, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AdditionalMetadata != nil {
in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
*out = new(api.AdditionalMetadataSpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSpec.
func (in *ResourceSpec) DeepCopy() *ResourceSpec {
if in == nil {
return nil
}
out := new(ResourceSpec)
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 *TenantResource) DeepCopyInto(out *TenantResource) {
*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 TenantResource.
func (in *TenantResource) DeepCopy() *TenantResource {
if in == nil {
return nil
}
out := new(TenantResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TenantResource) 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 *TenantResourceList) DeepCopyInto(out *TenantResourceList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]TenantResource, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceList.
func (in *TenantResourceList) DeepCopy() *TenantResourceList {
if in == nil {
return nil
}
out := new(TenantResourceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TenantResourceList) 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 *TenantResourceSpec) DeepCopyInto(out *TenantResourceSpec) {
*out = *in
out.ResyncPeriod = in.ResyncPeriod
if in.PruningOnDelete != nil {
in, out := &in.PruningOnDelete, &out.PruningOnDelete
*out = new(bool)
**out = **in
}
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
*out = make([]ResourceSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceSpec.
func (in *TenantResourceSpec) DeepCopy() *TenantResourceSpec {
if in == nil {
return nil
}
out := new(TenantResourceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantResourceStatus) DeepCopyInto(out *TenantResourceStatus) {
*out = *in
if in.ProcessedItems != nil {
in, out := &in.ProcessedItems, &out.ProcessedItems
*out = make(ProcessedItems, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceStatus.
func (in *TenantResourceStatus) DeepCopy() *TenantResourceStatus {
if in == nil {
return nil
}
out := new(TenantResourceStatus)
in.DeepCopyInto(out)
return out
}
// 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(api.ServiceOptions)
(*in).DeepCopyInto(*out)
}
if in.StorageClasses != nil {
in, out := &in.StorageClasses, &out.StorageClasses
*out = new(api.DefaultAllowedListSpec)
(*in).DeepCopyInto(*out)
}
in.IngressOptions.DeepCopyInto(&out.IngressOptions)
if in.ContainerRegistries != nil {
in, out := &in.ContainerRegistries, &out.ContainerRegistries
*out = new(api.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([]api.AdditionalRoleBindingsSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ImagePullPolicies != nil {
in, out := &in.ImagePullPolicies, &out.ImagePullPolicies
*out = make([]api.ImagePullPolicySpec, len(*in))
copy(*out, *in)
}
if in.RuntimeClasses != nil {
in, out := &in.RuntimeClasses, &out.RuntimeClasses
*out = new(api.SelectorAllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.PriorityClasses != nil {
in, out := &in.PriorityClasses, &out.PriorityClasses
*out = new(api.DefaultAllowedListSpec)
(*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
}

View File

@@ -21,8 +21,8 @@ sources:
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
version: 0.1.11
version: 0.3.1
# This is the version number of the application being deployed.
# This version number should be incremented each time you make changes to the application.
appVersion: 0.1.2
appVersion: 0.2.1

View File

@@ -1,9 +0,0 @@
docs: HELMDOCS_VERSION := v1.8.1
docs: docker
@docker run --rm -v "$$(pwd):/helm-docs" -u $$(id -u) jnorwood/helm-docs:$(HELMDOCS_VERSION)
docker:
@hash docker 2>/dev/null || {\
echo "You need docker" &&\
exit 1;\
}

View File

@@ -64,10 +64,10 @@ Here the values you can override:
|-----|------|---------|-------------|
| affinity | object | `{}` | Set affinity rules for the Capsule pod |
| certManager.generateCertificates | bool | `false` | Specifies whether capsule webhooks certificates should be generated using cert-manager |
| customAnnotations | object | `{}` | Additional annotations which will be added to all resources created by Capsule helm chart |
| customLabels | object | `{}` | Additional labels which will be added to all resources created by Capsule helm chart |
| customAnnotations | object | `{}` | Additional annotations which will be added to all resources created by Capsule helm chart |
| customLabels | object | `{}` | Additional labels which will be added to all resources created by Capsule helm chart |
| jobs.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy of the helm chart job |
| jobs.image.repository | string | `"quay.io/clastix/kubectl"` | Set the image repository of the helm chart job |
| jobs.image.repository | string | `"clastix/kubectl"` | Set the image repository of the helm chart job |
| jobs.image.tag | string | `""` | Set the image tag of the helm chart job |
| mutatingWebhooksTimeoutSeconds | int | `30` | Timeout in seconds for mutating webhooks |
| nodeSelector | object | `{}` | Set the node selector for the Capsule pod |
@@ -88,18 +88,18 @@ Here the values you can override:
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| manager.hostNetwork | bool | `false` | Specifies if the container should be started in hostNetwork mode. Required for use in some managed kubernetes clusters (such as AWS EKS) with custom CNI (such as calico), because control-plane managed by AWS cannot communicate with pods' IP CIDR and admission webhooks are not working |
| manager.hostNetwork | bool | `false` | Specifies if the container should be started in hostNetwork mode. Required for use in some managed kubernetes clusters (such as AWS EKS) with custom CNI (such as calico), because control-plane managed by AWS cannot communicate with pods' IP CIDR and admission webhooks are not working |
| manager.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy. |
| manager.image.repository | string | `"clastix/capsule"` | Set the image repository of the capsule. |
| manager.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
| manager.imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. |
| manager.kind | string | `"Deployment"` | Set the controller deployment mode as `Deployment` or `DaemonSet`. |
| manager.livenessProbe | object | `{"httpGet":{"path":"/healthz","port":10080}}` | Configure the liveness probe using Deployment probe spec |
| manager.options.capsuleUserGroups | list | `["capsule.clastix.io"]` | Override the Capsule user groups |
| manager.imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. |
| manager.kind | string | `"Deployment"` | Set the controller deployment mode as `Deployment` or `DaemonSet`. |
| manager.livenessProbe | object | `{"httpGet":{"path":"/healthz","port":10080}}` | Configure the liveness probe using Deployment probe spec |
| manager.options.capsuleUserGroups | list | `["capsule.clastix.io"]` | Override the Capsule user groups |
| manager.options.forceTenantPrefix | bool | `false` | Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash |
| manager.options.generateCertificates | bool | `true` | Specifies whether capsule webhooks certificates should be generated by capsule operator |
| manager.options.logLevel | string | `"4"` | Set the log verbosity of the capsule with a value from 1 to 10 |
| manager.options.protectedNamespaceRegex | string | `""` | If specified, disallows creation of namespaces matching the passed regexp |
| manager.options.protectedNamespaceRegex | string | `""` | If specified, disallows creation of namespaces matching the passed regexp |
| manager.readinessProbe | object | `{"httpGet":{"path":"/readyz","port":10080}}` | Configure the readiness probe using Deployment probe spec |
| manager.resources.limits.cpu | string | `"200m"` | |
| manager.resources.limits.memory | string | `"128Mi"` | |
@@ -130,6 +130,15 @@ Here the values you can override:
| webhooks.cordoning.failurePolicy | string | `"Fail"` | |
| webhooks.cordoning.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.cordoning.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.defaults.ingress.failurePolicy | string | `"Fail"` | |
| webhooks.defaults.ingress.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.defaults.ingress.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.defaults.pods.failurePolicy | string | `"Fail"` | |
| webhooks.defaults.pods.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.defaults.pods.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.defaults.pvc.failurePolicy | string | `"Fail"` | |
| webhooks.defaults.pvc.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.defaults.pvc.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.ingresses.failurePolicy | string | `"Fail"` | |
| webhooks.ingresses.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.ingresses.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |

View File

@@ -0,0 +1,9 @@
fullnameOverride: capsule
manager:
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 200m
memory: 128Mi

View File

@@ -1,11 +1,24 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.5.0
creationTimestamp: null
controller-gen.kubebuilder.io/version: v0.10.0
name: capsuleconfigurations.capsule.clastix.io
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
name: capsule-webhook-service
namespace: capsule-system
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2
group: capsule.clastix.io
names:
kind: CapsuleConfiguration
@@ -47,10 +60,95 @@ spec:
type: object
type: object
served: true
storage: false
- name: v1beta2
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:
enableTLSReconciler:
default: true
description: Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager.
type: boolean
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
nodeMetadata:
description: Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes.
properties:
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot set for their nodes.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set for their nodes.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
required:
- forbiddenAnnotations
- forbiddenLabels
type: object
overrides:
default:
TLSSecretName: capsule-tls
mutatingWebhookConfigurationName: capsule-mutating-webhook-configuration
validatingWebhookConfigurationName: capsule-validating-webhook-configuration
description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.
properties:
TLSSecretName:
default: capsule-tls
description: Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed.
type: string
mutatingWebhookConfigurationName:
default: capsule-mutating-webhook-configuration
description: Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
type: string
validatingWebhookConfigurationName:
default: capsule-validating-webhook-configuration
description: Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
type: string
required:
- TLSSecretName
- mutatingWebhookConfigurationName
- validatingWebhookConfigurationName
type: object
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
required:
- enableTLSReconciler
type: object
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,222 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.10.0
creationTimestamp: null
name: globaltenantresources.capsule.clastix.io
spec:
group: capsule.clastix.io
names:
kind: GlobalTenantResource
listKind: GlobalTenantResourceList
plural: globaltenantresources
singular: globaltenantresource
scope: Cluster
versions:
- name: v1beta2
schema:
openAPIV3Schema:
description: GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources.
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: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
properties:
pruningOnDelete:
default: true
description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest.
type: boolean
resources:
description: Defines the rules to select targeting Namespace, along with the objects that must be replicated.
items:
properties:
additionalMetadata:
description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
namespaceSelector:
description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
namespacedItems:
description: List of the resources already existing in other Namespaces that must be replicated.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
selector:
description: Label selector used to select the given resources in the given Namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- kind
- namespace
- selector
type: object
type: array
rawItems:
description: List of raw resources that must be replicated.
items:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type: array
type: object
type: array
resyncPeriod:
default: 60s
description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation.
type: string
tenantSelector:
description: Defines the Tenant selector used target the tenants on which resources must be propagated.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- resources
- resyncPeriod
type: object
status:
description: GlobalTenantResourceStatus defines the observed state of GlobalTenantResource.
properties:
processedItems:
description: List of the replicated resources for the given TenantResource.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
required:
- kind
- name
- namespace
type: object
type: array
selectedTenants:
description: List of Tenants addressed by the GlobalTenantResource.
items:
type: string
type: array
required:
- processedItems
- selectedTenants
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -1,9 +1,9 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.5.0
creationTimestamp: null
controller-gen.kubebuilder.io/version: v0.10.0
name: tenants.capsule.clastix.io
spec:
conversion:
@@ -14,10 +14,10 @@ spec:
name: capsule-webhook-service
namespace: capsule-system
path: /convert
port: 443
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2
group: capsule.clastix.io
names:
kind: Tenant
@@ -53,6 +53,8 @@ spec:
jsonPath: .metadata.creationTimestamp
name: Age
type: date
deprecated: true
deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version.
name: v1alpha1
schema:
openAPIV3Schema:
@@ -95,6 +97,7 @@ spec:
- kind
- name
type: object
x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
@@ -297,6 +300,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
properties:
@@ -327,6 +331,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
type: object
@@ -385,6 +390,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
properties:
@@ -415,6 +421,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
ports:
@@ -470,6 +477,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
policyTypes:
description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
items:
@@ -536,6 +544,7 @@ spec:
type: object
type: array
type: object
x-kubernetes-map-type: atomic
scopes:
description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
items:
@@ -648,6 +657,7 @@ spec:
- kind
- name
type: object
x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
@@ -878,6 +888,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
properties:
@@ -908,6 +919,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
type: object
@@ -966,6 +978,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
properties:
@@ -996,6 +1009,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
ports:
@@ -1051,6 +1065,7 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
policyTypes:
description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
items:
@@ -1161,6 +1176,7 @@ spec:
type: object
type: array
type: object
x-kubernetes-map-type: atomic
scopes:
description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
items:
@@ -1257,12 +1273,850 @@ spec:
type: object
type: object
served: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
- description: The actual state of the Tenant
jsonPath: .status.state
name: State
type: string
- description: The max amount of Namespaces can be created
jsonPath: .spec.namespaceOptions.quota
name: Namespace quota
type: integer
- description: The total amount of Namespaces in use
jsonPath: .status.size
name: Namespace count
type: integer
- description: Node Selector applied to Pods
jsonPath: .spec.nodeSelector
name: Node selector
type: string
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1beta2
schema:
openAPIV3Schema:
description: Tenant is the Schema for the tenants 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: TenantSpec defines the desired state of Tenant.
properties:
additionalRoleBindings:
description: 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.
items:
properties:
clusterRoleName:
type: string
subjects:
description: kubebuilder:validation:Minimum=1
items:
description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names.
properties:
apiGroup:
description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
type: string
kind:
description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error.
type: string
name:
description: Name of the object being referenced.
type: string
namespace:
description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error.
type: string
required:
- kind
- name
type: object
x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
- subjects
type: object
type: array
containerRegistries:
description: 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.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
cordoned:
description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
type: boolean
imagePullPolicies:
description: 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.
items:
enum:
- Always
- Never
- IfNotPresent
type: string
type: array
ingressOptions:
description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
properties:
allowWildcardHostnames:
description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.
type: boolean
allowedClasses:
description: 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. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
default:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
allowedHostnames:
description: 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.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
hostnameCollisionScope:
default: Disabled
description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional."
enum:
- Cluster
- Tenant
- Namespace
- Disabled
type: string
type: object
limitRanges:
description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
properties:
items:
items:
description: LimitRangeSpec defines a min/max usage limit for resources that match on kind.
properties:
limits:
description: Limits is the list of LimitRangeItem objects that are enforced.
items:
description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
properties:
default:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Default resource requirement limit value by resource name if resource limit is omitted.
type: object
defaultRequest:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
type: object
max:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Max usage constraints on this kind by resource name.
type: object
maxLimitRequestRatio:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.
type: object
min:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Min usage constraints on this kind by resource name.
type: object
type:
description: Type of resource that this limit applies to.
type: string
required:
- type
type: object
type: array
required:
- limits
type: object
type: array
type: object
namespaceOptions:
description: 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.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot set for their Namespace resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set for their Namespace resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
quota:
description: 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.
format: int32
minimum: 1
type: integer
type: object
networkPolicies:
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
properties:
items:
items:
description: NetworkPolicySpec provides the specification of a NetworkPolicy
properties:
egress:
description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
items:
description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8
properties:
ports:
description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow traffic on
properties:
endPort:
description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
type: string
type: object
type: array
to:
description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
items:
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
properties:
ipBlock:
description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
properties:
cidr:
description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
type: string
except:
description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
type: object
type: array
ingress:
description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
items:
description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from.
properties:
from:
description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
items:
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
properties:
ipBlock:
description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
properties:
cidr:
description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
type: string
except:
description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
ports:
description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow traffic on
properties:
endPort:
description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
type: string
type: object
type: array
type: object
type: array
podSelector:
description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
policyTypes:
description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
items:
description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8
type: string
type: array
required:
- podSelector
type: object
type: array
type: object
nodeSelector:
additionalProperties:
type: string
description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
type: object
owners:
description: Specifies the owners of the Tenant. Mandatory.
items:
properties:
clusterRoles:
default:
- admin
- capsule-namespace-deleter
description: Defines additional cluster-roles for the specific Owner.
items:
type: string
type: array
kind:
description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
enum:
- User
- Group
- ServiceAccount
type: string
name:
description: Name of tenant owner.
type: string
proxySettings:
description: Proxy settings for tenant owner.
items:
properties:
kind:
enum:
- Nodes
- StorageClasses
- IngressClasses
- PriorityClasses
type: string
operations:
items:
enum:
- List
- Update
- Delete
type: string
type: array
required:
- kind
- operations
type: object
type: array
required:
- kind
- name
type: object
type: array
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
type: boolean
priorityClasses:
description: 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. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
default:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
resourceQuotas:
description: 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.
properties:
items:
items:
description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
properties:
hard:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/'
type: object
scopeSelector:
description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
properties:
matchExpressions:
description: A list of scope selector requirements by scope of the resources.
items:
description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
properties:
operator:
description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
type: string
scopeName:
description: The name of the scope that the selector applies to.
type: string
values:
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- operator
- scopeName
type: object
type: array
type: object
x-kubernetes-map-type: atomic
scopes:
description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
items:
description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota
type: string
type: array
type: object
type: array
scope:
default: Tenant
description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
enum:
- Tenant
- Namespace
type: string
type: object
runtimeClasses:
description: Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
serviceOptions:
description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
allowedServices:
description: Block or deny certain type of Services. Optional.
properties:
externalName:
default: true
description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
loadBalancer:
default: true
description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
nodePort:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
type: object
externalIPs:
description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
properties:
allowed:
items:
pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$
type: string
type: array
required:
- allowed
type: object
type: object
storageClasses:
description: 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. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
default:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- owners
type: object
status:
description: Returns the observed state of the Tenant.
properties:
namespaces:
description: List of namespaces assigned to the Tenant.
items:
type: string
type: array
size:
description: How many namespaces are assigned to the Tenant.
type: integer
state:
default: Active
description: The operational state of the Tenant. Possible values are "Active", "Cordoned".
enum:
- Cordoned
- Active
type: string
required:
- size
- state
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,185 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.10.0
creationTimestamp: null
name: tenantresources.capsule.clastix.io
spec:
group: capsule.clastix.io
names:
kind: TenantResource
listKind: TenantResourceList
plural: tenantresources
singular: tenantresource
scope: Namespaced
versions:
- name: v1beta2
schema:
openAPIV3Schema:
description: TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. For such cases, the GlobalTenantResource must be used.
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: TenantResourceSpec defines the desired state of TenantResource.
properties:
pruningOnDelete:
default: true
description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest.
type: boolean
resources:
description: Defines the rules to select targeting Namespace, along with the objects that must be replicated.
items:
properties:
additionalMetadata:
description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
namespaceSelector:
description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
namespacedItems:
description: List of the resources already existing in other Namespaces that must be replicated.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
selector:
description: Label selector used to select the given resources in the given Namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- kind
- namespace
- selector
type: object
type: array
rawItems:
description: List of raw resources that must be replicated.
items:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type: array
type: object
type: array
resyncPeriod:
default: 60s
description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation.
type: string
required:
- resources
- resyncPeriod
type: object
status:
description: TenantResourceStatus defines the observed state of TenantResource.
properties:
processedItems:
description: List of the replicated resources for the given TenantResource.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
required:
- kind
- name
- namespace
type: object
type: array
required:
- processedItems
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -1,18 +1,19 @@
apiVersion: capsule.clastix.io/v1alpha1
apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: default
labels:
{{- include "capsule.labels" . | nindent 4 }}
annotations:
capsule.clastix.io/mutating-webhook-configuration-name: {{ include "capsule.fullname" . }}-mutating-webhook-configuration
capsule.clastix.io/tls-secret-name: {{ include "capsule.secretTlsName" . }}
capsule.clastix.io/validating-webhook-configuration-name: {{ include "capsule.fullname" . }}-validating-webhook-configuration
capsule.clastix.io/enable-tls-configuration: "{{ .Values.tls.enableController }}"
{{- with .Values.customAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
enableTLSReconciler: {{ .Values.tls.enableController }}
overrides:
mutatingWebhookConfigurationName: {{ include "capsule.fullname" . }}-mutating-webhook-configuration
TLSSecretName: {{ include "capsule.secretTlsName" . }}
validatingWebhookConfigurationName: {{ include "capsule.fullname" . }}-validating-webhook-configuration
forceTenantPrefix: {{ .Values.manager.options.forceTenantPrefix }}
userGroups:
{{- range .Values.manager.options.capsuleUserGroups }}

View File

@@ -12,6 +12,86 @@ metadata:
{{- toYaml . | nindent 4 }}
{{- end }}
webhooks:
{{- with .Values.webhooks.defaults.pods }}
- admissionReviewVersions:
- v1
clientConfig:
{{- if not $.Values.certManager.generateCertificates }}
caBundle: Cg==
{{- end }}
service:
name: {{ include "capsule.fullname" $ }}-webhook-service
namespace: {{ $.Release.Namespace }}
path: /defaults
failurePolicy: {{ .failurePolicy }}
name: pod.defaults.capsule.clastix.io
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
namespaceSelector:
{{- toYaml .namespaceSelector | nindent 4}}
sideEffects: None
{{- end }}
{{- with .Values.webhooks.defaults.pvc }}
- admissionReviewVersions:
- v1
clientConfig:
{{- if not $.Values.certManager.generateCertificates }}
caBundle: Cg==
{{- end }}
service:
name: {{ include "capsule.fullname" $ }}-webhook-service
namespace: {{ $.Release.Namespace }}
path: /defaults
failurePolicy: {{ .failurePolicy }}
name: storage.defaults.capsule.clastix.io
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- persistentvolumeclaims
namespaceSelector:
{{- toYaml .namespaceSelector | nindent 4}}
sideEffects: None
{{- end }}
{{- with .Values.webhooks.defaults.ingress }}
- admissionReviewVersions:
- v1
clientConfig:
{{- if not $.Values.certManager.generateCertificates }}
caBundle: Cg==
{{- end }}
service:
name: {{ include "capsule.fullname" $ }}-webhook-service
namespace: {{ $.Release.Namespace }}
path: /defaults
failurePolicy: {{ .failurePolicy }}
name: ingress.defaults.capsule.clastix.io
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1beta1
- v1
operations:
- CREATE
- UPDATE
resources:
- ingresses
namespaceSelector:
{{- toYaml .namespaceSelector | nindent 4}}
sideEffects: None
{{- end }}
- admissionReviewVersions:
- v1
- v1beta1

View File

@@ -145,6 +145,34 @@ webhooks:
clientConfig:
{{- if not .Values.certManager.generateCertificates }}
caBundle: Cg==
{{- end }}
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /nodes
port: 443
failurePolicy: {{ .Values.webhooks.nodes.failurePolicy }}
name: nodes.capsule.clastix.io
matchPolicy: Exact
namespaceSelector: {}
objectSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- UPDATE
resources:
- nodes
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
{{- if not .Values.certManager.generateCertificates }}
caBundle: Cg==
{{- end }}
service:
name: {{ include "capsule.fullname" . }}-webhook-service
@@ -164,6 +192,7 @@ webhooks:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods
scope: Namespaced
@@ -249,7 +278,7 @@ webhooks:
- apiGroups:
- capsule.clastix.io
apiVersions:
- v1beta1
- v1beta2
operations:
- CREATE
- UPDATE
@@ -259,31 +288,3 @@ webhooks:
scope: '*'
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
{{- if not .Values.certManager.generateCertificates }}
caBundle: Cg==
{{- end }}
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
path: /nodes
port: 443
failurePolicy: {{ .Values.webhooks.nodes.failurePolicy }}
name: nodes.capsule.clastix.io
matchPolicy: Exact
namespaceSelector: {}
objectSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- UPDATE
resources:
- nodes
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}

View File

@@ -14,7 +14,7 @@ tls:
# Manager Options
manager:
# -- Set the controller deployment mode as `Deployment` or `DaemonSet`.
# -- Set the controller deployment mode as `Deployment` or `DaemonSet`.
kind: Deployment
image:
@@ -25,7 +25,7 @@ manager:
# -- Overrides the image tag whose default is the chart appVersion.
tag: ''
# -- Configuration for `imagePullSecrets` so that you can use a private images registry.
# -- Configuration for `imagePullSecrets` so that you can use a private images registry.
imagePullSecrets: []
# -- Specifies if the container should be started in hostNetwork mode.
@@ -41,19 +41,19 @@ manager:
logLevel: '4'
# -- Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash
forceTenantPrefix: false
# -- Override the Capsule user groups
# -- Override the Capsule user groups
capsuleUserGroups: ["capsule.clastix.io"]
# -- If specified, disallows creation of namespaces matching the passed regexp
# -- If specified, disallows creation of namespaces matching the passed regexp
protectedNamespaceRegex: ""
# -- Specifies whether capsule webhooks certificates should be generated by capsule operator
generateCertificates: true
# -- Configure the liveness probe using Deployment probe spec
# -- Configure the liveness probe using Deployment probe spec
livenessProbe:
httpGet:
path: /healthz
port: 10080
# -- Configure the readiness probe using Deployment probe spec
readinessProbe:
httpGet:
@@ -75,7 +75,7 @@ podAnnotations: {}
# scheduler.alpha.kubernetes.io/critical-pod: ''
# -- Set the priority class name of the Capsule pod
priorityClassName: '' #system-cluster-critical
priorityClassName: '' # system-cluster-critical
# -- Set the node selector for the Capsule pod
nodeSelector: {}
@@ -83,10 +83,10 @@ nodeSelector: {}
# -- Set list of tolerations for the Capsule pod
tolerations: []
#- key: CriticalAddonsOnly
# operator: Exists
#- effect: NoSchedule
# key: node-role.kubernetes.io/master
# - key: CriticalAddonsOnly
# operator: Exists
# - effect: NoSchedule
# key: node-role.kubernetes.io/master
# -- Set the replica count for capsule pod
replicaCount: 1
@@ -101,7 +101,7 @@ podSecurityPolicy:
jobs:
image:
# -- Set the image repository of the helm chart job
repository: quay.io/clastix/kubectl
repository: clastix/kubectl
# -- Set the image pull policy of the helm chart job
pullPolicy: IfNotPresent
# -- Set the image tag of the helm chart job
@@ -120,10 +120,10 @@ certManager:
# -- Specifies whether capsule webhooks certificates should be generated using cert-manager
generateCertificates: false
# -- Additional labels which will be added to all resources created by Capsule helm chart
# -- Additional labels which will be added to all resources created by Capsule helm chart
customLabels: {}
# -- Additional annotations which will be added to all resources created by Capsule helm chart
# -- Additional annotations which will be added to all resources created by Capsule helm chart
customAnnotations: {}
# Webhooks configurations
@@ -172,6 +172,26 @@ webhooks:
operator: Exists
nodes:
failurePolicy: Fail
defaults:
ingress:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
pvc:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
pods:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
# -- Timeout in seconds for mutating webhooks
mutatingWebhooksTimeoutSeconds: 30

View File

@@ -1,10 +1,9 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.5.0
controller-gen.kubebuilder.io/version: v0.10.0
creationTimestamp: null
name: capsuleconfigurations.capsule.clastix.io
spec:
@@ -19,13 +18,18 @@ spec:
- name: v1alpha1
schema:
openAPIV3Schema:
description: CapsuleConfiguration is the Schema for the Capsule configuration API.
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'
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'
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
@@ -34,10 +38,14 @@ spec:
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.
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
description: Disallow creation of namespaces, whose name matches this
regexp
type: string
userGroups:
default:
@@ -49,10 +57,118 @@ spec:
type: object
type: object
served: true
storage: false
- name: v1beta2
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:
enableTLSReconciler:
default: true
description: Toggles the TLS reconciler, the controller that is able
to generate CA and certificates for the webhooks when not using
an already provided CA and certificate, or when these are managed
externally with Vault, or cert-manager.
type: boolean
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
nodeMetadata:
description: Allows to set the forbidden metadata for the worker nodes
that could be patched by a Tenant. This applies only if the Tenant
has an active NodeSelector, and the Owner have right to patch their
nodes.
properties:
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot
set for their nodes.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set
for their nodes.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
required:
- forbiddenAnnotations
- forbiddenLabels
type: object
overrides:
default:
TLSSecretName: capsule-tls
mutatingWebhookConfigurationName: capsule-mutating-webhook-configuration
validatingWebhookConfigurationName: capsule-validating-webhook-configuration
description: Allows to set different name rather than the canonical
one for the Capsule configuration objects, such as webhook secret
or configurations.
properties:
TLSSecretName:
default: capsule-tls
description: Defines the Secret name used for the webhook server.
Must be in the same Namespace where the Capsule Deployment is
deployed.
type: string
mutatingWebhookConfigurationName:
default: capsule-mutating-webhook-configuration
description: Name of the MutatingWebhookConfiguration which contains
the dynamic admission controller paths and resources.
type: string
validatingWebhookConfigurationName:
default: capsule-validating-webhook-configuration
description: Name of the ValidatingWebhookConfiguration which
contains the dynamic admission controller paths and resources.
type: string
required:
- TLSSecretName
- mutatingWebhookConfigurationName
- validatingWebhookConfigurationName
type: object
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
required:
- enableTLSReconciler
type: object
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,283 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.10.0
creationTimestamp: null
name: globaltenantresources.capsule.clastix.io
spec:
group: capsule.clastix.io
names:
kind: GlobalTenantResource
listKind: GlobalTenantResourceList
plural: globaltenantresources
singular: globaltenantresource
scope: Cluster
versions:
- name: v1beta2
schema:
openAPIV3Schema:
description: GlobalTenantResource allows to propagate resource replications
to a specific subset of Tenant resources.
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: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
properties:
pruningOnDelete:
default: true
description: When the replicated resource manifest is deleted, all
the objects replicated so far will be automatically deleted. Disable
this to keep replicated resources although the deletion of the replication
manifest.
type: boolean
resources:
description: Defines the rules to select targeting Namespace, along
with the objects that must be replicated.
items:
properties:
additionalMetadata:
description: Besides the Capsule metadata required by TenantResource
controller, defines additional metadata that must be added
to the replicated resources.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
namespaceSelector:
description: Defines the Namespace selector to select the Tenant
Namespaces on which the resources must be propagated. In case
of nil value, all the Tenant Namespaces are targeted.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that relates
the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In, NotIn,
Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values array
must be non-empty. If the operator is Exists or
DoesNotExist, the values array must be empty. This
array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field is
"key", the operator is "In", and the values array contains
only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
namespacedItems:
description: List of the resources already existing in other
Namespaces that must be replicated.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
selector:
description: Label selector used to select the given resources
in the given Namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values
array must be non-empty. If the operator is
Exists or DoesNotExist, the values array must
be empty. This array is replaced during a
strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field
is "key", the operator is "In", and the values array
contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- kind
- namespace
- selector
type: object
type: array
rawItems:
description: List of raw resources that must be replicated.
items:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type: array
type: object
type: array
resyncPeriod:
default: 60s
description: Define the period of time upon a second reconciliation
must be invoked. Keep in mind that any change to the manifests will
trigger a new reconciliation.
type: string
tenantSelector:
description: Defines the Tenant selector used target the tenants on
which resources must be propagated.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: A label selector requirement is a selector that
contains values, a key, and an operator that relates the key
and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship to
a set of values. Valid operators are In, NotIn, Exists
and DoesNotExist.
type: string
values:
description: values is an array of string values. If the
operator is In or NotIn, the values array must be non-empty.
If the operator is Exists or DoesNotExist, the values
array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator
is "In", and the values array contains only "value". The requirements
are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- resources
- resyncPeriod
type: object
status:
description: GlobalTenantResourceStatus defines the observed state of
GlobalTenantResource.
properties:
processedItems:
description: List of the replicated resources for the given TenantResource.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
required:
- kind
- name
- namespace
type: object
type: array
selectedTenants:
description: List of Tenants addressed by the GlobalTenantResource.
items:
type: string
type: array
required:
- processedItems
- selectedTenants
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,232 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.10.0
creationTimestamp: null
name: tenantresources.capsule.clastix.io
spec:
group: capsule.clastix.io
names:
kind: TenantResource
listKind: TenantResourceList
plural: tenantresources
singular: tenantresource
scope: Namespaced
versions:
- name: v1beta2
schema:
openAPIV3Schema:
description: TenantResource allows a Tenant Owner, if enabled with proper
RBAC, to propagate resources in its Namespace. The object must be deployed
in a Tenant Namespace, and cannot reference object living in non-Tenant
namespaces. For such cases, the GlobalTenantResource must be used.
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: TenantResourceSpec defines the desired state of TenantResource.
properties:
pruningOnDelete:
default: true
description: When the replicated resource manifest is deleted, all
the objects replicated so far will be automatically deleted. Disable
this to keep replicated resources although the deletion of the replication
manifest.
type: boolean
resources:
description: Defines the rules to select targeting Namespace, along
with the objects that must be replicated.
items:
properties:
additionalMetadata:
description: Besides the Capsule metadata required by TenantResource
controller, defines additional metadata that must be added
to the replicated resources.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
namespaceSelector:
description: Defines the Namespace selector to select the Tenant
Namespaces on which the resources must be propagated. In case
of nil value, all the Tenant Namespaces are targeted.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that relates
the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In, NotIn,
Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values array
must be non-empty. If the operator is Exists or
DoesNotExist, the values array must be empty. This
array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field is
"key", the operator is "In", and the values array contains
only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
namespacedItems:
description: List of the resources already existing in other
Namespaces that must be replicated.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
selector:
description: Label selector used to select the given resources
in the given Namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In,
NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values
array must be non-empty. If the operator is
Exists or DoesNotExist, the values array must
be empty. This array is replaced during a
strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field
is "key", the operator is "In", and the values array
contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- kind
- namespace
- selector
type: object
type: array
rawItems:
description: List of raw resources that must be replicated.
items:
type: object
x-kubernetes-embedded-resource: true
x-kubernetes-preserve-unknown-fields: true
type: array
type: object
type: array
resyncPeriod:
default: 60s
description: Define the period of time upon a second reconciliation
must be invoked. Keep in mind that any change to the manifests will
trigger a new reconciliation.
type: string
required:
- resources
- resyncPeriod
type: object
status:
description: TenantResourceStatus defines the observed state of TenantResource.
properties:
processedItems:
description: List of the replicated resources for the given TenantResource.
items:
properties:
apiVersion:
description: API version of the referent.
type: string
kind:
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
type: string
namespace:
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
type: string
required:
- kind
- name
- namespace
type: object
type: array
required:
- processedItems
type: object
type: object
served: true
storage: true
subresources:
status: {}

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,8 @@
resources:
- bases/capsule.clastix.io_tenants.yaml
- bases/capsule.clastix.io_capsuleconfigurations.yaml
- bases/capsule.clastix.io_tenantresources.yaml
- bases/capsule.clastix.io_globaltenantresources.yaml
# +kubebuilder:scaffold:crdkustomizeresource
# the following config is for teaching kustomize how to do kustomization for CRDs.
@@ -12,3 +14,4 @@ configurations:
patchesStrategicMerge:
- patches/webhook_in_tenants.yaml
- patches/webhook_in_capsuleconfiguration.yaml

View File

@@ -0,0 +1,7 @@
# The following patch adds a directive for certmanager to inject CA into the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: globaltenantresources.capsule.clastix.io

View File

@@ -0,0 +1,7 @@
# The following patch adds a directive for certmanager to inject CA into the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: tenantresources.capsule.clastix.io

View File

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

View File

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

View File

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

View File

@@ -15,3 +15,4 @@ spec:
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
apiVersion: capsule.clastix.io/v1alpha1
apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: default
@@ -6,3 +6,4 @@ spec:
userGroups: ["capsule.clastix.io"]
forceTenantPrefix: false
protectedNamespaceRegex: ""
enableTLSReconciler: true

View File

@@ -7,4 +7,4 @@ kind: Kustomization
images:
- name: controller
newName: clastix/capsule
newTag: v0.1.2
newTag: v0.2.1

View File

@@ -0,0 +1,24 @@
# permissions for end users to edit globaltenantresources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: globaltenantresource-editor-role
rules:
- apiGroups:
- capsule.clastix.io
resources:
- globaltenantresources
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- capsule.clastix.io
resources:
- globaltenantresources/status
verbs:
- get

View File

@@ -0,0 +1,20 @@
# permissions for end users to view globaltenantresources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: globaltenantresource-viewer-role
rules:
- apiGroups:
- capsule.clastix.io
resources:
- globaltenantresources
verbs:
- get
- list
- watch
- apiGroups:
- capsule.clastix.io
resources:
- globaltenantresources/status
verbs:
- get

View File

@@ -0,0 +1,24 @@
# permissions for end users to edit tenantresources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tenantresource-editor-role
rules:
- apiGroups:
- capsule.clastix.io
resources:
- tenantresources
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- capsule.clastix.io
resources:
- tenantresources/status
verbs:
- get

View File

@@ -0,0 +1,20 @@
# permissions for end users to view tenantresources.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tenantresource-viewer-role
rules:
- apiGroups:
- capsule.clastix.io
resources:
- tenantresources
verbs:
- get
- list
- watch
- apiGroups:
- capsule.clastix.io
resources:
- tenantresources/status
verbs:
- get

View File

@@ -0,0 +1,11 @@
---
apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: default
spec:
userGroups: ["capsule.clastix.io"]
forceTenantPrefix: false
protectedNamespaceRegex: ""
enableTLSReconciler: true

View File

@@ -0,0 +1,39 @@
apiVersion: capsule.clastix.io/v1beta2
kind: GlobalTenantResource
metadata:
name: green-production
spec:
tenantSelector:
matchLabels:
energy: green
resyncPeriod: 60s
pruningOnDelete: true
resources:
- namespaceSelector:
matchLabels:
environment: production
additionalMetadata:
labels:
labels.energy.io: green
annotations:
annotations.energy.io: green
namespacedItems:
- apiVersion: v1
kind: Secret
namespace: default
selector:
matchLabels:
replicate: green
rawItems:
- apiVersion: v1
kind: Secret
metadata:
name: raw-secret-1
- apiVersion: v1
kind: Secret
metadata:
name: raw-secret-2
- apiVersion: v1
kind: Secret
metadata:
name: raw-secret-3

View File

@@ -0,0 +1,36 @@
apiVersion: capsule.clastix.io/v1beta2
kind: TenantResource
metadata:
name: wind-objects
spec:
resyncPeriod: 60s
pruningOnDelete: true
resources:
- namespaceSelector:
matchLabels:
environment: production
additionalMetadata:
labels:
labels.energy.io: wind
annotations:
annotations.energy.io: wind
namespacedItems:
- apiVersion: v1
kind: Secret
namespace: wind-production
selector:
matchLabels:
replicate: solar
rawItems:
- apiVersion: v1
kind: Secret
metadata:
name: wind-secret-1
- apiVersion: v1
kind: Secret
metadata:
name: wind-secret-2
- apiVersion: v1
kind: Secret
metadata:
name: wind-secret-3

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