diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 17c51753..cc15c588 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,7 +8,7 @@ on:
jobs:
commit_lint:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
@@ -18,25 +18,25 @@ jobs:
firstParent: true
golangci:
name: lint
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v2.3.0
with:
- version: v1.45.2
+ version: v1.51.2
only-new-issues: false
- args: --timeout 2m --config .golangci.yml
+ args: --timeout 5m --config .golangci.yml
diff:
name: diff
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v2
with:
- go-version: '1.18'
+ go-version: '1.19'
- 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
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index ed9944eb..61840464 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -32,15 +32,15 @@ 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']
- runs-on: ubuntu-18.04
+ k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.3', 'v1.27.2']
+ runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v2
with:
- go-version: '1.18'
+ go-version: '1.19'
- run: make manifests
- name: Checking if manifests are disaligned
run: test -z "$(git diff 2> /dev/null)"
diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml
index cf4b2b80..6c886d20 100644
--- a/.github/workflows/gosec.yml
+++ b/.github/workflows/gosec.yml
@@ -6,7 +6,7 @@ on:
branches: [ "*" ]
jobs:
tests:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
env:
GO111MODULE: on
steps:
diff --git a/.gitignore b/.gitignore
index 89bc04a5..f0009a7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,4 @@
**/*.key
.DS_Store
*.tgz
-
-capsule
-
+kind.yaml
diff --git a/.golangci.yml b/.golangci.yml
index 9980ae83..6adb72eb 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -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:
@@ -34,13 +39,16 @@ linters:
- testpackage
- varnamelen
- wrapcheck
-
-issues:
- exclude:
- - Using the variable on range scope .* in function literal
+ - exhaustruct
+ - varcheck
+ - structcheck
+ - nosnakecase
+ - deadcode
+ - ifshort
+ - nonamedreturns
service:
- golangci-lint-version: 1.33.x
+ golangci-lint-version: 1.51.2
run:
skip-files:
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 79f56d11..3b322a88 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -6,3 +6,27 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull-
### [Bedag Informatik AG](https://www.bedag.ch/)
![Bedag](https://www.bedag.ch/wGlobal/wGlobal/layout/images/logo.svg)
+
+### [Fastweb](https://www.fastweb.it/)
+![Fastweb](https://www.fastweb.it/grandi-aziende/gfx/common/logo-fastweb-header.svg)
+
+### [Klarrio](https://klarrio.com/)
+![Klarrio](https://klarrio.com/wp-content/uploads/klarrio.png)
+
+### [PITS Global Data Recovery Services](https://www.pitsdatarecovery.net)
+![PITS Global Data Recovery Services](https://www.pitsdatarecovery.net/wp-content/uploads/2020/09/pits-logo.svg)
+
+### [Politecnico di Torino](https://www.polito.it/)
+![Politecnico di Torino](https://www.polito.it/themes/custom/polito/logo.svg)
+
+### [Reevo](https://www.reevo.it/)
+![Reevo Cloud and CyberSecurity](https://www.dropbox.com/s/x3q6r0oqstgvtdr/Logo_ReeVo_270x200px.svg)
+
+### [University of Torino](https://www.unito.it)
+![University of Torino](https://www.unito.it/sites/all/themes/bsunito/img/logo_new_2022.svg)
+
+### [Velocity](https://velocity.tech/)
+![Velocity](https://raw.githubusercontent.com/yarelm/velocity-logo/main/velocity.png)
+
+### [Wargaming.net](https://www.wargaming.net/)
+![Wargaming.net](https://static-cspbe-eu.wargaming.net/images/logo@2x.png)
diff --git a/Dockerfile b/Dockerfile
index 12c1c5af..8cbb3417 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,5 @@
# Build the manager binary
-FROM golang:1.18 as builder
-
-ARG GIT_HEAD_COMMIT
-ARG GIT_TAG_COMMIT
-ARG GIT_LAST_TAG
-ARG GIT_MODIFIED
-ARG GIT_REPO
-ARG BUILD_DATE
+FROM golang:1.19.10 as builder
WORKDIR /workspace
# Copy the Go Modules manifests
@@ -16,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
diff --git a/Makefile b/Makefile
index 65be21d0..9c9d6467 100644
--- a/Makefile
+++ b/Makefile
@@ -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,14 +70,14 @@ 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/tenant-crd.md --template docs/template/reference-cr.tmpl
+ $(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)
@@ -88,8 +86,15 @@ 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: docker
- @docker run -v "$(SRC_ROOT):/workdir" --entrypoint /bin/sh quay.io/helmpack/chart-testing:v3.3.1 -c "cd /workdir && ct lint --config .github/configs/ct.yaml --lint-conf .github/configs/lintconf.yaml --all --debug"
+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 || {\
@@ -134,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}/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="[\
@@ -147,8 +155,17 @@ dev-setup:
{'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) \
@@ -164,7 +181,7 @@ 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.
@@ -172,7 +189,15 @@ apidocs-gen: ## Download crdoc locally if necessary.
GINKGO = $(shell pwd)/bin/ginkgo
ginkgo: ## Download ginkgo locally if necessary.
- $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/ginkgo@v1.16.5)
+ $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo@v2.9.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.
@@ -213,18 +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.51.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
- $(MAKE) e2e-build/$* && $(MAKE) e2e-exec || $(MAKE) e2e-destroy
+ $(MAKE) e2e-build/$* && $(MAKE) e2e-exec && $(MAKE) e2e-destroy
e2e-build/%:
- kind create cluster --name capsule --image=kindest/node:$*
+ 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 \
@@ -237,10 +266,11 @@ e2e-build/%:
--set "manager.image.tag=$(VERSION)" \
--set 'manager.livenessProbe.failureThreshold=10' \
--set 'manager.readinessProbe.failureThreshold=10' \
+ --set 'podSecurityContext.seccompProfile=null' \
capsule \
./charts/capsule
-e2e-exec:
+e2e-exec: ginkgo
$(GINKGO) -v -tags e2e ./e2e
e2e-destroy:
diff --git a/PROJECT b/PROJECT
index 78f90d36..ff22733d 100644
--- a/PROJECT
+++ b/PROJECT
@@ -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"
diff --git a/README.md b/README.md
index 79467d4a..d7827fdd 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
+
diff --git a/api/v1alpha1/additional_metadata.go b/api/v1alpha1/additional_metadata.go
index 0f032997..377688df 100644
--- a/api/v1alpha1/additional_metadata.go
+++ b/api/v1alpha1/additional_metadata.go
@@ -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"`
}
diff --git a/api/v1alpha1/allowed_list.go b/api/v1alpha1/allowed_list.go
deleted file mode 100644
index eac46577..00000000
--- a/api/v1alpha1/allowed_list.go
+++ /dev/null
@@ -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
-}
diff --git a/api/v1alpha1/allowed_list_test.go b/api/v1alpha1/allowed_list_test.go
deleted file mode 100644
index 7aef5b60..00000000
--- a/api/v1alpha1/allowed_list_test.go
+++ /dev/null
@@ -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))
- }
- }
-}
diff --git a/api/v1alpha1/capsuleconfiguration_annotations.go b/api/v1alpha1/capsuleconfiguration_annotations.go
index 83ce062e..9fb42c54 100644
--- a/api/v1alpha1/capsuleconfiguration_annotations.go
+++ b/api/v1alpha1/capsuleconfiguration_annotations.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package v1alpha1
const (
diff --git a/api/v1alpha1/capsuleconfiguration_types.go b/api/v1alpha1/capsuleconfiguration_types.go
index 21b6e9df..ac404a25 100644
--- a/api/v1alpha1/capsuleconfiguration_types.go
+++ b/api/v1alpha1/capsuleconfiguration_types.go
@@ -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.
diff --git a/api/v1alpha1/capsuleconfiguration_webhook.go b/api/v1alpha1/capsuleconfiguration_webhook.go
new file mode 100644
index 00000000..45a4221d
--- /dev/null
+++ b/api/v1alpha1/capsuleconfiguration_webhook.go
@@ -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()
+}
diff --git a/api/v1alpha1/conversion_hub.go b/api/v1alpha1/conversion_hub.go
index 00beee62..1332ff07 100644
--- a/api/v1alpha1/conversion_hub.go
+++ b/api/v1alpha1/conversion_hub.go
@@ -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 {
@@ -132,151 +133,135 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
return owners
}
-// nolint:gocognit,gocyclo,cyclop,maintidx
-func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
+//nolint:gocognit,gocyclo,cyclop,maintidx
+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{}
}
- }
- if t.Spec.StorageClasses != nil {
- dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{
- Exact: t.Spec.StorageClasses.Exact,
- Regex: t.Spec.StorageClasses.Regex,
+ dst.Spec.ServiceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{
+ Labels: in.Spec.ServicesMetadata.Labels,
+ Annotations: in.Spec.ServicesMetadata.Annotations,
}
}
- if v, annotationOk := t.Annotations[ingressHostnameCollisionScope]; annotationOk {
+ if in.Spec.StorageClasses != nil {
+ dst.Spec.StorageClasses = in.Spec.StorageClasses
+ }
+
+ 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.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{
- Allowed: make([]capsulev1beta1.AllowedIP, len(t.Spec.ExternalServiceIPs.Allowed)),
+ dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
- 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,59 +283,59 @@ 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)
+ dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.Bool(val)
}
enableExternalName, ok := annotations[enableExternalNameAnnotation]
if ok {
val, err := strconv.ParseBool(enableExternalName)
if err != nil {
- return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, t.GetName()))
+ 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)
+ dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.Bool(val)
}
loadBalancerService, ok := annotations[enableLoadBalancerAnnotation]
if ok {
val, err := strconv.ParseBool(loadBalancerService)
if err != nil {
- return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, t.GetName()))
+ 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)
+ dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.Bool(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)
@@ -380,8 +365,8 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
return nil
}
-// nolint:gocognit,gocyclo,cyclop
-func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
+//nolint:gocognit,gocyclo,cyclop
+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,
}
diff --git a/api/v1alpha1/conversion_hub_test.go b/api/v1alpha1/conversion_hub_test.go
index 5c6f9311..64a2e369 100644
--- a/api/v1alpha1/conversion_hub_test.go
+++ b/api/v1alpha1/conversion_hub_test.go
@@ -16,28 +16,29 @@ import (
"k8s.io/utils/pointer"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/pkg/api"
)
-// nolint:maintidx
+//nolint:maintidx
func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
var namespaceQuota int32 = 5
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{
- NodePort: pointer.BoolPtr(false),
- ExternalName: pointer.BoolPtr(false),
- LoadBalancer: pointer.BoolPtr(false),
+ AllowedServices: &api.AllowedServices{
+ NodePort: pointer.Bool(false),
+ ExternalName: pointer.Bool(false),
+ LoadBalancer: pointer.Bool(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{
diff --git a/api/v1alpha1/tenant_func.go b/api/v1alpha1/tenant_func.go
index 07a3d7b5..b00147b7 100644
--- a/api/v1alpha1/tenant_func.go
+++ b/api/v1alpha1/tenant_func.go
@@ -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))
}
diff --git a/api/v1alpha1/tenant_types.go b/api/v1alpha1/tenant_types.go
index b9f0f786..e0b8c884 100644
--- a/api/v1alpha1/tenant_types.go
+++ b/api/v1alpha1/tenant_types.go
@@ -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 {
diff --git a/api/v1alpha1/tenant_webhook.go b/api/v1alpha1/tenant_webhook.go
index 13443adb..0df967e9 100644
--- a/api/v1alpha1/tenant_webhook.go
+++ b/api/v1alpha1/tenant_webhook.go
@@ -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()
}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index efca1518..db654794 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -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)
}
}
diff --git a/api/v1beta1/allowed_list.go b/api/v1beta1/allowed_list.go
deleted file mode 100644
index b5fd1aff..00000000
--- a/api/v1beta1/allowed_list.go
+++ /dev/null
@@ -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
-}
diff --git a/api/v1beta1/deny_wildcard.go b/api/v1beta1/deny_wildcard.go
index b6084977..76460942 100644
--- a/api/v1beta1/deny_wildcard.go
+++ b/api/v1beta1/deny_wildcard.go
@@ -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
}
diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go
index 341689ec..4a99993c 100644
--- a/api/v1beta1/groupversion_info.go
+++ b/api/v1beta1/groupversion_info.go
@@ -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 (
diff --git a/api/v1beta1/ingress_options.go b/api/v1beta1/ingress_options.go
index d748e472..ab4baad7 100644
--- a/api/v1beta1/ingress_options.go
+++ b/api/v1beta1/ingress_options.go
@@ -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"`
}
diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go
index cf35753b..c4a2aed5 100644
--- a/api/v1beta1/namespace_options.go
+++ b/api/v1beta1/namespace_options.go
@@ -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],
}
}
diff --git a/api/v1beta1/owner_list.go b/api/v1beta1/owner_list.go
index 355ab7e1..b3f9d728 100644
--- a/api/v1beta1/owner_list.go
+++ b/api/v1beta1/owner_list.go
@@ -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]
}
diff --git a/api/v1beta1/owner_list_test.go b/api/v1beta1/owner_list_test.go
index 6877478a..c8b62a03 100644
--- a/api/v1beta1/owner_list_test.go
+++ b/api/v1beta1/owner_list_test.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package v1beta1
import (
diff --git a/api/v1beta1/owner_role.go b/api/v1beta1/owner_role.go
index dc73309d..27123c30 100644
--- a/api/v1beta1/owner_role.go
+++ b/api/v1beta1/owner_role.go
@@ -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__": "@",
}
diff --git a/api/v1beta1/service_allowed_ips.go b/api/v1beta1/service_allowed_ips.go
deleted file mode 100644
index 5dd65ba0..00000000
--- a/api/v1beta1/service_allowed_ips.go
+++ /dev/null
@@ -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"`
-}
diff --git a/api/v1beta1/service_allowed_types.go b/api/v1beta1/service_allowed_types.go
index 38e692b5..3f57c648 100644
--- a/api/v1beta1/service_allowed_types.go
+++ b/api/v1beta1/service_allowed_types.go
@@ -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"`
}
diff --git a/api/v1beta1/service_options.go b/api/v1beta1/service_options.go
index dfdcc265..636a2f7f 100644
--- a/api/v1beta1/service_options.go
+++ b/api/v1beta1/service_options.go
@@ -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"`
}
diff --git a/api/v1beta1/tenant_annotations.go b/api/v1beta1/tenant_annotations.go
deleted file mode 100644
index 9c7f3ad7..00000000
--- a/api/v1beta1/tenant_annotations.go
+++ /dev/null
@@ -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(), "/", "_")
-}
diff --git a/api/v1beta1/tenant_func.go b/api/v1beta1/tenant_func.go
index 2bb43969..8111c028 100644
--- a/api/v1beta1/tenant_func.go
+++ b/api/v1beta1/tenant_func.go
@@ -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
}
diff --git a/api/v1beta1/tenant_status.go b/api/v1beta1/tenant_status.go
index ef248080..8122633d 100644
--- a/api/v1beta1/tenant_status.go
+++ b/api/v1beta1/tenant_status.go
@@ -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.
diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go
index 740c6f5a..5d809b24 100644
--- a/api/v1beta1/tenant_types.go
+++ b/api/v1beta1/tenant_types.go
@@ -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,11 @@ type TenantList struct {
func init() {
SchemeBuilder.Register(&Tenant{}, &TenantList{})
}
+
+func (in *Tenant) GetNamespaces() (res []string) {
+ res = make([]string, 0, len(in.Status.Namespaces))
+
+ res = append(res, in.Status.Namespaces...)
+
+ return
+}
diff --git a/api/v1beta1/tenant_webhook.go b/api/v1beta1/tenant_webhook.go
new file mode 100644
index 00000000..58ffec85
--- /dev/null
+++ b/api/v1beta1/tenant_webhook.go
@@ -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()
+}
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index f410020d..5ae9834b 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -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)
}
}
diff --git a/api/v1beta1/additional_role_bindings.go b/api/v1beta2/additional_role_bindings.go
similarity index 94%
rename from api/v1beta1/additional_role_bindings.go
rename to api/v1beta2/additional_role_bindings.go
index f71e3cec..298aa8bd 100644
--- a/api/v1beta1/additional_role_bindings.go
+++ b/api/v1beta2/additional_role_bindings.go
@@ -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"
diff --git a/api/v1beta2/capsuleconfiguration_convertion_hub.go b/api/v1beta2/capsuleconfiguration_convertion_hub.go
new file mode 100644
index 00000000..6f4c02b5
--- /dev/null
+++ b/api/v1beta2/capsuleconfiguration_convertion_hub.go
@@ -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
+}
diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go
new file mode 100644
index 00000000..a5854b51
--- /dev/null
+++ b/api/v1beta2/capsuleconfiguration_types.go
@@ -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{})
+}
diff --git a/api/v1beta2/custom_resource_quota.go b/api/v1beta2/custom_resource_quota.go
new file mode 100644
index 00000000..42ccb730
--- /dev/null
+++ b/api/v1beta2/custom_resource_quota.go
@@ -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
+}
diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go
new file mode 100644
index 00000000..37920ac4
--- /dev/null
+++ b/api/v1beta2/groupversion_info.go
@@ -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
+)
diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go
new file mode 100644
index 00000000..740b1503
--- /dev/null
+++ b/api/v1beta2/ingress_options.go
@@ -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"`
+}
diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go
new file mode 100644
index 00000000..0b71d73f
--- /dev/null
+++ b/api/v1beta2/namespace_options.go
@@ -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"`
+}
diff --git a/api/v1beta2/owner.go b/api/v1beta2/owner.go
new file mode 100644
index 00000000..c4753c80
--- /dev/null
+++ b/api/v1beta2/owner.go
@@ -0,0 +1,59 @@
+// 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;RuntimeClasses;PersistentVolumes
+type ProxyServiceKind string
+
+func (p ProxyServiceKind) String() string {
+ return string(p)
+}
+
+const (
+ NodesProxy ProxyServiceKind = "Nodes"
+ StorageClassesProxy ProxyServiceKind = "StorageClasses"
+ IngressClassesProxy ProxyServiceKind = "IngressClasses"
+ PriorityClassesProxy ProxyServiceKind = "PriorityClasses"
+ RuntimeClassesProxy ProxyServiceKind = "RuntimeClasses"
+ PersistentVolumesProxy ProxyServiceKind = "PersistentVolumes"
+
+ ListOperation ProxyOperation = "List"
+ UpdateOperation ProxyOperation = "Update"
+ DeleteOperation ProxyOperation = "Delete"
+
+ UserOwner OwnerKind = "User"
+ GroupOwner OwnerKind = "Group"
+ ServiceAccountOwner OwnerKind = "ServiceAccount"
+)
diff --git a/api/v1beta2/owner_list.go b/api/v1beta2/owner_list.go
new file mode 100644
index 00000000..3677acc6
--- /dev/null
+++ b/api/v1beta2/owner_list.go
@@ -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]
+}
diff --git a/api/v1beta2/owner_list_test.go b/api/v1beta2/owner_list_test.go
new file mode 100644
index 00000000..93697b93
--- /dev/null
+++ b/api/v1beta2/owner_list_test.go
@@ -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{})
+}
diff --git a/api/v1beta2/tenant_annotations.go b/api/v1beta2/tenant_annotations.go
new file mode 100644
index 00000000..4f02bdf6
--- /dev/null
+++ b/api/v1beta2/tenant_annotations.go
@@ -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(), "/", "_")
+}
diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go
new file mode 100644
index 00000000..aa144bb5
--- /dev/null
+++ b/api/v1beta2/tenant_conversion_hub.go
@@ -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
+}
diff --git a/api/v1beta2/tenant_func.go b/api/v1beta2/tenant_func.go
new file mode 100644
index 00000000..166956aa
--- /dev/null
+++ b/api/v1beta2/tenant_func.go
@@ -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
+}
diff --git a/api/v1alpha1/tenant_labels.go b/api/v1beta2/tenant_labels.go
similarity index 84%
rename from api/v1alpha1/tenant_labels.go
rename to api/v1beta2/tenant_labels.go
index 7c49b68a..7bd2a922 100644
--- a/api/v1alpha1/tenant_labels.go
+++ b/api/v1beta2/tenant_labels.go
@@ -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
diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go
new file mode 100644
index 00000000..e3204ed1
--- /dev/null
+++ b/api/v1beta2/tenant_status.go
@@ -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"`
+}
diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go
new file mode 100644
index 00000000..60edd2fe
--- /dev/null
+++ b/api/v1beta2/tenant_types.go
@@ -0,0 +1,95 @@
+// 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))
+
+ res = append(res, in.Status.Namespaces...)
+
+ 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{})
+}
diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go
new file mode 100644
index 00000000..9108e533
--- /dev/null
+++ b/api/v1beta2/tenantresource_global.go
@@ -0,0 +1,62 @@
+// 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.Set[string] {
+ set := sets.New[string]()
+
+ 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{})
+}
diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go
new file mode 100644
index 00000000..ac6c764c
--- /dev/null
+++ b/api/v1beta2/tenantresource_namespaced.go
@@ -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{})
+}
diff --git a/api/v1beta2/tenantresource_types.go b/api/v1beta2/tenantresource_types.go
new file mode 100644
index 00000000..8f701b22
--- /dev/null
+++ b/api/v1beta2/tenantresource_types.go
@@ -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
+}
diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
new file mode 100644
index 00000000..141f2fe9
--- /dev/null
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -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
+}
diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml
index 56757dd6..27e1c9cb 100644
--- a/charts/capsule/Chart.yaml
+++ b/charts/capsule/Chart.yaml
@@ -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.12
+version: 0.4.5
# 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.3
+appVersion: 0.3.3
diff --git a/charts/capsule/README.md b/charts/capsule/README.md
index a4772e96..d7e0040d 100644
--- a/charts/capsule/README.md
+++ b/charts/capsule/README.md
@@ -66,15 +66,18 @@ Here the values you can override:
| 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 |
+| imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. |
| 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 |
| podAnnotations | object | `{}` | Annotations to add to the capsule pod. |
+| podSecurityContext | object | `{"runAsGroup":1002,"runAsNonRoot":true,"runAsUser":1002,"seccompProfile":{"type":"RuntimeDefault"}}` | Set the securityContext for the Capsule pod |
| podSecurityPolicy.enabled | bool | `false` | Specify if a Pod Security Policy must be created |
| priorityClassName | string | `""` | Set the priority class name of the Capsule pod |
| replicaCount | int | `1` | Set the replica count for capsule pod |
+| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true}` | Set the securityContext for the Capsule container |
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account. |
| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. |
| serviceAccount.name | string | `"capsule"` | The name of the service account to use. If not set and `serviceAccount.create=true`, a name is generated using the fullname template |
@@ -92,19 +95,20 @@ Here the values you can override:
| manager.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy. |
| manager.image.repository | string | `"clastix/capsule"` | Set the image repository of the capsule. |
| manager.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
-| manager.imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. |
| manager.kind | string | `"Deployment"` | Set the controller deployment mode as `Deployment` or `DaemonSet`. |
| manager.livenessProbe | object | `{"httpGet":{"path":"/healthz","port":10080}}` | Configure the liveness probe using Deployment probe spec |
| manager.options.capsuleUserGroups | list | `["capsule.clastix.io"]` | Override the Capsule user groups |
| manager.options.forceTenantPrefix | bool | `false` | Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash |
| manager.options.generateCertificates | bool | `true` | Specifies whether capsule webhooks certificates should be generated by capsule operator |
| manager.options.logLevel | string | `"4"` | Set the log verbosity of the capsule with a value from 1 to 10 |
+| manager.options.nodeMetadata | object | `{"forbiddenAnnotations":{"denied":[],"deniedRegex":""},"forbiddenLabels":{"denied":[],"deniedRegex":""}}` | Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant |
| manager.options.protectedNamespaceRegex | string | `""` | If specified, disallows creation of namespaces matching the passed regexp |
| manager.readinessProbe | object | `{"httpGet":{"path":"/readyz","port":10080}}` | Configure the readiness probe using Deployment probe spec |
| manager.resources.limits.cpu | string | `"200m"` | |
| manager.resources.limits.memory | string | `"128Mi"` | |
| manager.resources.requests.cpu | string | `"200m"` | |
| manager.resources.requests.memory | string | `"128Mi"` | |
+| manager.webhookPort | int | `9443` | Set an alternative to the default container port. Useful for use in some kubernetes clusters (such as GKE Private) with aggregator routing turned on, because pod ports have to be opened manually on the firewall side |
### ServiceMonitor Parameters
@@ -119,8 +123,6 @@ Here the values you can override:
| serviceMonitor.labels | object | `{}` | Assign additional labels according to Prometheus' serviceMonitorSelector matching labels |
| serviceMonitor.matchLabels | object | `{}` | Change matching labels |
| serviceMonitor.namespace | string | `""` | Install the ServiceMonitor into a different Namespace, as the monitoring stack one (default: the release one) |
-| serviceMonitor.serviceAccount.name | string | `"capsule"` | ServiceAccount for Metrics RBAC |
-| serviceMonitor.serviceAccount.namespace | string | `"capsule-system"` | ServiceAccount Namespace for Metrics RBAC |
| serviceMonitor.targetLabels | list | `[]` | Set targetLabels for the serviceMonitor |
### Webhook Parameters
@@ -130,6 +132,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"` | |
@@ -148,6 +159,7 @@ Here the values you can override:
| webhooks.services.failurePolicy | string | `"Fail"` | |
| webhooks.services.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.services.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
+| webhooks.tenantResourceObjects.failurePolicy | string | `"Fail"` | |
| webhooks.tenants.failurePolicy | string | `"Fail"` | |
## Created resources
diff --git a/charts/capsule/ci/test-values.yaml b/charts/capsule/ci/test-values.yaml
new file mode 100644
index 00000000..54daa068
--- /dev/null
+++ b/charts/capsule/ci/test-values.yaml
@@ -0,0 +1,9 @@
+fullnameOverride: capsule
+manager:
+ resources:
+ limits:
+ cpu: 500m
+ memory: 512Mi
+ requests:
+ cpu: 200m
+ memory: 128Mi
diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml
index 7665e040..78c7945e 100644
--- a/charts/capsule/crds/capsuleconfiguration-crd.yaml
+++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml
@@ -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: []
\ No newline at end of file
diff --git a/charts/capsule/crds/globaltenantresources-crd.yaml b/charts/capsule/crds/globaltenantresources-crd.yaml
new file mode 100644
index 00000000..5519a4b6
--- /dev/null
+++ b/charts/capsule/crds/globaltenantresources-crd.yaml
@@ -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: {}
diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml
index 538006a9..314016fb 100644
--- a/charts/capsule/crds/tenant-crd.yaml
+++ b/charts/capsule/crds/tenant-crd.yaml
@@ -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,9 @@ 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:
@@ -70,6 +73,7 @@ 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:
@@ -95,6 +99,7 @@ spec:
- kind
- name
type: object
+ x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
@@ -102,6 +107,7 @@ spec:
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:
@@ -110,452 +116,1779 @@ spec:
allowedRegex:
type: string
type: object
- externalServiceIPs:
- 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
- ingressClasses:
- properties:
- allowed:
- items:
- type: string
- type: array
- allowedRegex:
- type: string
- type: object
- ingressHostnames:
- properties:
- allowed:
- items:
- type: string
- type: array
- allowedRegex:
- type: string
- type: object
- limitRanges:
+ 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:
- 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
+ enum:
+ - Always
+ - Never
+ - IfNotPresent
+ type: string
type: array
- namespaceQuota:
- format: int32
- minimum: 1
- type: integer
- namespacesMetadata:
+ ingressOptions:
+ description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
properties:
- additionalAnnotations:
- additionalProperties:
- type: string
+ 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
- additionalLabels:
- additionalProperties:
- type: string
+ 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
- networkPolicies:
- 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:
+ 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
- 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.
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
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
- 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
- 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
- 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
- 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:
+ description: Default resource requirement limit value by resource name if resource limit is omitted.
+ type: object
+ defaultRequest:
+ additionalProperties:
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.
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
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
+ 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
- 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.
+ 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:
- matchExpressions:
- description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ egress:
+ description: egress is a 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: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+ 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:
- 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.
+ ports:
+ description: ports is a 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:
- 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.
+ description: NetworkPolicyPort describes a port to allow traffic on
+ properties:
+ endPort:
+ description: endPort indicates that the range of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents 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: to is a 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 IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ items:
+ type: string
+ type: array
+ required:
+ - cidr
+ type: object
+ namespaceSelector:
+ description: "namespaceSelector 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: "podSelector 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: ingress is a 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: from is a 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 IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ items:
+ type: string
+ type: array
+ required:
+ - cidr
+ type: object
+ namespaceSelector:
+ description: "namespaceSelector 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: "podSelector 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: ports is a 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: endPort indicates that the range of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents 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: podSelector 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: policyTypes is a 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
+ - RuntimeClasses
+ - PersistentVolumes
+ 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: 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: v1beta1
+ 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
+ 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:
+ 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.
+ Optional.
+ properties:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
+ type: string
+ type: object
+ 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
+ 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:
+ 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
+ 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. Optional.
+ properties:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
+ type: string
+ type: object
+ 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
- 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
- nodeSelector:
- additionalProperties:
- type: string
- type: object
- owner:
- description: OwnerSpec defines tenant owner name and kind.
- properties:
- kind:
+ 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:
- - User
- - Group
- type: string
- name:
+ - Tenant
+ - Namespace
type: string
- required:
- - kind
- - name
type: object
- resourceQuotas:
- 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
- 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
- servicesMetadata:
+ serviceOptions:
+ description: Specifies options for the Service, such as additional
+ metadata or block of certain type of Services. Optional.
properties:
- additionalAnnotations:
- additionalProperties:
- type: string
+ 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
- additionalLabels:
- additionalProperties:
- type: string
+ 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.
+ Optional.
properties:
allowed:
items:
@@ -565,19 +1898,30 @@ spec:
type: string
type: object
required:
- - owner
+ - owners
type: object
status:
- description: TenantStatus defines the observed state of Tenant.
+ 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
@@ -605,16 +1949,20 @@ spec:
jsonPath: .metadata.creationTimestamp
name: Age
type: date
- name: v1beta1
+ 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'
+ 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
@@ -622,7 +1970,9 @@ 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.
+ 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:
@@ -630,24 +1980,37 @@ spec:
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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -655,7 +2018,9 @@ spec:
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.
+ 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:
@@ -664,8 +2029,14 @@ spec:
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.
+ 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
@@ -674,10 +2045,19 @@ spec:
type: string
type: array
ingressOptions:
- description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+ 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. Optional.
+ 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:
@@ -685,9 +2065,55 @@ spec:
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.
+ 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:
@@ -698,7 +2124,15 @@ spec:
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."
+ 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
@@ -707,16 +2141,21 @@ spec:
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.
+ 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.
+ 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.
+ 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.
+ description: LimitRangeItem defines a min/max usage limit
+ for any resource that matches on kind.
properties:
default:
additionalProperties:
@@ -725,7 +2164,8 @@ spec:
- 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.
+ description: Default resource requirement limit value
+ by resource name if resource limit is omitted.
type: object
defaultRequest:
additionalProperties:
@@ -734,7 +2174,9 @@ spec:
- 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.
+ description: DefaultRequest is the default resource
+ requirement request value by resource name if resource
+ request is omitted.
type: object
max:
additionalProperties:
@@ -743,7 +2185,8 @@ spec:
- 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.
+ description: Max usage constraints on this kind by
+ resource name.
type: object
maxLimitRequestRatio:
additionalProperties:
@@ -752,7 +2195,11 @@ spec:
- 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.
+ 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:
@@ -761,10 +2208,12 @@ spec:
- 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.
+ description: Min usage constraints on this kind by
+ resource name.
type: object
type:
- description: Type of resource that this limit applies to.
+ description: Type of resource that this limit applies
+ to.
type: string
required:
- type
@@ -776,10 +2225,14 @@ spec:
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.
+ 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.
+ description: Specifies additional labels and annotations the Capsule
+ operator places on any Namespace resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
@@ -790,58 +2243,139 @@ spec:
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.
+ 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.
+ 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
+ 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
+ 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
+ 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.
+ 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
+ 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".
+ 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.
+ 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.
+ 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.
+ 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
+ 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.
+ 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"
+ 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
+ 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
@@ -849,21 +2383,45 @@ spec:
- 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."
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -875,25 +2433,55 @@ spec:
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.
+ 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."
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -905,31 +2493,67 @@ spec:
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.
+ 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)
+ 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.
+ 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.
+ 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
+ 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.
+ 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"
+ 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
+ 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
@@ -937,21 +2561,45 @@ spec:
- 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."
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -963,25 +2611,55 @@ spec:
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.
+ 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."
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -993,50 +2671,96 @@ spec:
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.
+ 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.
+ 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
+ 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".
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -1048,13 +2772,32 @@ spec:
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.
+ 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
+ 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
+ description: PolicyType string describes the NetworkPolicy
+ type This type is beta-level in 1.8
type: string
type: array
required:
@@ -1065,14 +2808,28 @@ spec:
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.
+ 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"
+ description: Kind of tenant owner. Possible values are "User",
+ "Group", and "ServiceAccount"
enum:
- User
- Group
@@ -1091,6 +2848,8 @@ spec:
- StorageClasses
- IngressClasses
- PriorityClasses
+ - RuntimeClasses
+ - PersistentVolumes
type: string
operations:
items:
@@ -1110,8 +2869,16 @@ spec:
- 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. Optional.
+ 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:
@@ -1119,13 +2886,62 @@ spec:
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.
+ 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.
+ description: ResourceQuotaSpec defines the desired hard limits
+ to enforce for Quota.
properties:
hard:
additionalProperties:
@@ -1134,24 +2950,40 @@ spec:
- 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/'
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -1161,27 +2993,88 @@ 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.
+ 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
+ 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
+ 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.
+ 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.
+ description: Specifies additional labels and annotations the Capsule
+ operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
@@ -1197,19 +3090,24 @@ spec:
properties:
externalName:
default: true
- description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
+ 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.
+ 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.
+ 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.
+ 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:
@@ -1221,7 +3119,11 @@ spec:
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. Optional.
+ 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:
@@ -1229,7 +3131,50 @@ spec:
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
@@ -1246,7 +3191,8 @@ spec:
type: integer
state:
default: Active
- description: The operational state of the Tenant. Possible values are "Active", "Cordoned".
+ description: The operational state of the Tenant. Possible values
+ are "Active", "Cordoned".
enum:
- Cordoned
- Active
@@ -1260,9 +3206,3 @@ spec:
storage: true
subresources:
status: {}
-status:
- acceptedNames:
- kind: ""
- plural: ""
- conditions: []
- storedVersions: []
diff --git a/charts/capsule/crds/tenantresources-crd.yaml b/charts/capsule/crds/tenantresources-crd.yaml
new file mode 100644
index 00000000..c1d2a4c7
--- /dev/null
+++ b/charts/capsule/crds/tenantresources-crd.yaml
@@ -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: {}
diff --git a/charts/capsule/templates/configuration-default.yaml b/charts/capsule/templates/configuration-default.yaml
index 3cb897a1..bd5b6513 100644
--- a/charts/capsule/templates/configuration-default.yaml
+++ b/charts/capsule/templates/configuration-default.yaml
@@ -1,21 +1,26 @@
-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 }}
- {{ . }}
{{- end}}
protectedNamespaceRegex: {{ .Values.manager.options.protectedNamespaceRegex | quote }}
+ {{- with .Values.manager.options.nodeMetadata }}
+ nodeMetadata:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
diff --git a/charts/capsule/templates/daemonset.yaml b/charts/capsule/templates/daemonset.yaml
index 293442b8..9266070e 100644
--- a/charts/capsule/templates/daemonset.yaml
+++ b/charts/capsule/templates/daemonset.yaml
@@ -29,6 +29,10 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
+ {{- with .Values.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
{{- if .Values.manager.hostNetwork }}
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
@@ -56,6 +60,7 @@ spec:
command:
- /manager
args:
+ - --webhook-port={{ .Values.manager.webhookPort }}
- --enable-leader-election
- --zap-log-level={{ default 4 .Values.manager.options.logLevel }}
- --configuration-name=default
@@ -68,7 +73,7 @@ spec:
fieldPath: metadata.namespace
ports:
- name: webhook-server
- containerPort: 9443
+ containerPort: {{ .Values.manager.webhookPort }}
protocol: TCP
- name: metrics
containerPort: 8080
@@ -84,5 +89,5 @@ spec:
resources:
{{- toYaml .Values.manager.resources | nindent 12 }}
securityContext:
- allowPrivilegeEscalation: false
+ {{- toYaml .Values.securityContext | nindent 12 }}
{{- end }}
diff --git a/charts/capsule/templates/deployment.yaml b/charts/capsule/templates/deployment.yaml
index 07f592e1..d9d3e444 100644
--- a/charts/capsule/templates/deployment.yaml
+++ b/charts/capsule/templates/deployment.yaml
@@ -28,6 +28,10 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
+ {{- with .Values.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
{{- if .Values.manager.hostNetwork }}
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
@@ -55,6 +59,7 @@ spec:
command:
- /manager
args:
+ - --webhook-port={{ .Values.manager.webhookPort }}
- --enable-leader-election
- --zap-log-level={{ default 4 .Values.manager.options.logLevel }}
- --configuration-name=default
@@ -71,7 +76,7 @@ spec:
fieldPath: spec.serviceAccountName
ports:
- name: webhook-server
- containerPort: 9443
+ containerPort: {{ .Values.manager.webhookPort }}
protocol: TCP
- name: metrics
containerPort: 8080
@@ -87,5 +92,5 @@ spec:
resources:
{{- toYaml .Values.manager.resources | nindent 12 }}
securityContext:
- allowPrivilegeEscalation: false
+ {{- toYaml .Values.securityContext | nindent 12 }}
{{- end }}
diff --git a/charts/capsule/templates/metrics-rbac.yaml b/charts/capsule/templates/metrics-rbac.yaml
deleted file mode 100644
index 3e694358..00000000
--- a/charts/capsule/templates/metrics-rbac.yaml
+++ /dev/null
@@ -1,46 +0,0 @@
-{{- if .Values.serviceMonitor.enabled }}
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
- labels:
- {{- include "capsule.labels" . | nindent 4 }}
- {{- if .Values.serviceMonitor.labels }}
- {{- toYaml .Values.serviceMonitor.labels | nindent 4 }}
- {{- end }}
- {{- with .Values.customAnnotations }}
- annotations:
- {{- toYaml . | nindent 4 }}
- {{- end }}
- name: {{ include "capsule.fullname" . }}-metrics-role
- namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
-rules:
-- apiGroups:
- - ""
- resources:
- - services
- - endpoints
- - pods
- verbs:
- - get
- - list
- - watch
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
- labels:
- {{- include "capsule.labels" . | nindent 4 }}
- {{- if .Values.serviceMonitor.labels }}
- {{- toYaml .Values.serviceMonitor.labels | nindent 4 }}
- {{- end }}
- name: {{ include "capsule.fullname" . }}-metrics-rolebinding
- namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: Role
- name: {{ include "capsule.fullname" . }}-metrics-role
-subjects:
-- kind: ServiceAccount
- name: {{ .Values.serviceMonitor.serviceAccount.name }}
- namespace: {{ .Values.serviceMonitor.serviceAccount.namespace | default .Release.Namespace }}
-{{- end }}
diff --git a/charts/capsule/templates/mutatingwebhookconfiguration.yaml b/charts/capsule/templates/mutatingwebhookconfiguration.yaml
index 9997e702..0d396ee8 100644
--- a/charts/capsule/templates/mutatingwebhookconfiguration.yaml
+++ b/charts/capsule/templates/mutatingwebhookconfiguration.yaml
@@ -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
diff --git a/charts/capsule/templates/post-install-job.yaml b/charts/capsule/templates/post-install-job.yaml
index 1e043a8e..58bb8786 100644
--- a/charts/capsule/templates/post-install-job.yaml
+++ b/charts/capsule/templates/post-install-job.yaml
@@ -45,5 +45,11 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
+ {{- with .Values.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
{{- end }}
diff --git a/charts/capsule/templates/pre-delete-job.yaml b/charts/capsule/templates/pre-delete-job.yaml
index 0897bb1a..fe3a9075 100644
--- a/charts/capsule/templates/pre-delete-job.yaml
+++ b/charts/capsule/templates/pre-delete-job.yaml
@@ -47,4 +47,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
+ {{- with .Values.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
diff --git a/charts/capsule/templates/validatingwebhookconfiguration.yaml b/charts/capsule/templates/validatingwebhookconfiguration.yaml
index e20ae09c..aa4101a9 100644
--- a/charts/capsule/templates/validatingwebhookconfiguration.yaml
+++ b/charts/capsule/templates/validatingwebhookconfiguration.yaml
@@ -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
@@ -230,35 +259,36 @@ webhooks:
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- - v1beta1
clientConfig:
{{- if not .Values.certManager.generateCertificates }}
caBundle: Cg==
{{- end }}
service:
- name: {{ include "capsule.fullname" . }}-webhook-service
+ name: capsule-webhook-service
namespace: {{ .Release.Namespace }}
- path: /tenants
- port: 443
- failurePolicy: {{ .Values.webhooks.tenants.failurePolicy }}
- matchPolicy: Exact
- name: tenants.capsule.clastix.io
- namespaceSelector: {}
- objectSelector: {}
+ path: /tenantresource-objects
+ failurePolicy: {{ .Values.webhooks.tenantResourceObjects.failurePolicy }}
+ name: resource-objects.tenant.capsule.clastix.io
+ namespaceSelector:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+ objectSelector:
+ matchExpressions:
+ - key: capsule.clastix.io/resources
+ operator: Exists
rules:
- apiGroups:
- - capsule.clastix.io
+ - '*'
apiVersions:
- - v1beta1
+ - '*'
operations:
- - CREATE
- UPDATE
- DELETE
resources:
- - tenants
- scope: '*'
+ - '*'
+ scope: Namespaced
sideEffects: None
- timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
- admissionReviewVersions:
- v1
- v1beta1
@@ -269,21 +299,24 @@ webhooks:
service:
name: {{ include "capsule.fullname" . }}-webhook-service
namespace: {{ .Release.Namespace }}
- path: /nodes
+ path: /tenants
port: 443
- failurePolicy: {{ .Values.webhooks.nodes.failurePolicy }}
- name: nodes.capsule.clastix.io
+ failurePolicy: {{ .Values.webhooks.tenants.failurePolicy }}
matchPolicy: Exact
+ name: tenants.capsule.clastix.io
namespaceSelector: {}
objectSelector: {}
rules:
- apiGroups:
- - ""
+ - capsule.clastix.io
apiVersions:
- - v1
+ - v1beta2
operations:
+ - CREATE
- UPDATE
+ - DELETE
resources:
- - nodes
+ - tenants
+ scope: '*'
sideEffects: None
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
diff --git a/charts/capsule/templates/webhook-service.yaml b/charts/capsule/templates/webhook-service.yaml
index 3fceef91..c170e1cf 100644
--- a/charts/capsule/templates/webhook-service.yaml
+++ b/charts/capsule/templates/webhook-service.yaml
@@ -13,7 +13,7 @@ spec:
- port: 443
name: https
protocol: TCP
- targetPort: 9443
+ targetPort: {{ .Values.manager.webhookPort }}
selector:
{{- include "capsule.selectorLabels" . | nindent 4 }}
sessionAffinity: None
diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml
index 8885801a..215a3426 100644
--- a/charts/capsule/values.yaml
+++ b/charts/capsule/values.yaml
@@ -25,9 +25,6 @@ manager:
# -- Overrides the image tag whose default is the chart appVersion.
tag: ''
- # -- Configuration for `imagePullSecrets` so that you can use a private images registry.
- imagePullSecrets: []
-
# -- Specifies if the container should be started in hostNetwork mode.
#
# Required for use in some managed kubernetes clusters (such as AWS EKS) with custom
@@ -35,6 +32,13 @@ manager:
# with pods' IP CIDR and admission webhooks are not working
hostNetwork: false
+ # -- Set an alternative to the default container port.
+ #
+ # Useful for use in some kubernetes clusters (such as GKE Private) with
+ # aggregator routing turned on, because pod ports have to be opened manually
+ # on the firewall side
+ webhookPort: 9443
+
# Additional Capsule Controller Options
options:
# -- Set the log verbosity of the capsule with a value from 1 to 10
@@ -47,6 +51,14 @@ manager:
protectedNamespaceRegex: ""
# -- Specifies whether capsule webhooks certificates should be generated by capsule operator
generateCertificates: true
+ # -- Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant
+ nodeMetadata:
+ forbiddenLabels:
+ denied: []
+ deniedRegex: ""
+ forbiddenAnnotations:
+ denied: []
+ deniedRegex: ""
# -- Configure the liveness probe using Deployment probe spec
livenessProbe:
@@ -68,6 +80,9 @@ manager:
cpu: 200m
memory: 128Mi
+# -- Configuration for `imagePullSecrets` so that you can use a private images registry.
+imagePullSecrets: []
+
# -- Annotations to add to the capsule pod.
podAnnotations: {}
# The following annotations guarantee scheduling for critical add-on pods
@@ -77,6 +92,23 @@ podAnnotations: {}
# -- Set the priority class name of the Capsule pod
priorityClassName: '' # system-cluster-critical
+# -- Set the securityContext for the Capsule pod
+podSecurityContext:
+ seccompProfile:
+ type: "RuntimeDefault"
+ runAsGroup: 1002
+ runAsNonRoot: true
+ runAsUser: 1002
+
+
+# -- Set the securityContext for the Capsule container
+securityContext:
+ capabilities:
+ drop:
+ - ALL
+ allowPrivilegeEscalation: false
+ readOnlyRootFilesystem: true
+
# -- Set the node selector for the Capsule pod
nodeSelector: {}
# node-role.kubernetes.io/master: ""
@@ -101,7 +133,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
@@ -164,6 +196,8 @@ webhooks:
operator: Exists
tenants:
failurePolicy: Fail
+ tenantResourceObjects:
+ failurePolicy: Fail
services:
failurePolicy: Fail
namespaceSelector:
@@ -172,6 +206,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
@@ -192,11 +246,6 @@ serviceMonitor:
matchLabels: {}
# -- Set targetLabels for the serviceMonitor
targetLabels: []
- serviceAccount:
- # -- ServiceAccount for Metrics RBAC
- name: capsule
- # -- ServiceAccount Namespace for Metrics RBAC
- namespace: capsule-system
endpoint:
# -- Set the scrape interval for the endpoint of the serviceMonitor
interval: "15s"
diff --git a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml
index 5ce5bdfe..f2b41739 100644
--- a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml
+++ b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml
@@ -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: []
diff --git a/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml b/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml
new file mode 100644
index 00000000..c6b3869a
--- /dev/null
+++ b/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml
@@ -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: {}
diff --git a/config/crd/bases/capsule.clastix.io_tenantresources.yaml b/config/crd/bases/capsule.clastix.io_tenantresources.yaml
new file mode 100644
index 00000000..bd2a3681
--- /dev/null
+++ b/config/crd/bases/capsule.clastix.io_tenantresources.yaml
@@ -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: {}
diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml
index 0298fb28..75c54723 100644
--- a/config/crd/bases/capsule.clastix.io_tenants.yaml
+++ b/config/crd/bases/capsule.clastix.io_tenants.yaml
@@ -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: tenants.capsule.clastix.io
spec:
@@ -43,16 +42,23 @@ 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:
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'
+ 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
@@ -67,24 +73,37 @@ spec:
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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -130,12 +149,15 @@ spec:
type: object
limitRanges:
items:
- description: LimitRangeSpec defines a min/max usage limit for resources that match on kind.
+ 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.
+ 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.
+ description: LimitRangeItem defines a min/max usage limit
+ for any resource that matches on kind.
properties:
default:
additionalProperties:
@@ -144,7 +166,8 @@ spec:
- 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.
+ description: Default resource requirement limit value
+ by resource name if resource limit is omitted.
type: object
defaultRequest:
additionalProperties:
@@ -153,7 +176,9 @@ spec:
- 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.
+ description: DefaultRequest is the default resource requirement
+ request value by resource name if resource request is
+ omitted.
type: object
max:
additionalProperties:
@@ -162,7 +187,8 @@ spec:
- 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.
+ description: Max usage constraints on this kind by resource
+ name.
type: object
maxLimitRequestRatio:
additionalProperties:
@@ -171,7 +197,11 @@ spec:
- 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.
+ 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:
@@ -180,10 +210,12 @@ spec:
- 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.
+ description: Min usage constraints on this kind by resource
+ name.
type: object
type:
- description: Type of resource that this limit applies to.
+ description: Type of resource that this limit applies
+ to.
type: string
required:
- type
@@ -213,44 +245,92 @@ spec:
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
+ description: egress is a 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
+ 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.
+ description: ports is a 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
+ 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".
+ description: endPort indicates that the range of
+ ports from port to endPort if set, 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.
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.
+ description: port represents 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.
+ description: protocol represents 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.
+ description: to is a 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
+ 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.
+ 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"
+ description: cidr is a string representing the
+ IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/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
+ description: except is a slice of CIDRs that
+ should not be included within an IPBlock Valid
+ examples are "192.168.1.0/24" or "2001:db8::/64"
+ Except values will be rejected if they are
+ outside the cidr range
items:
type: string
type: array
@@ -258,21 +338,43 @@ spec:
- 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."
+ description: "namespaceSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -284,25 +386,54 @@ spec:
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.
+ 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."
+ description: "podSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -314,31 +445,65 @@ spec:
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.
+ 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)
+ description: ingress is a 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.
+ 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.
+ description: from is a 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
+ 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.
+ 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"
+ description: cidr is a string representing the
+ IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/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
+ description: except is a slice of CIDRs that
+ should not be included within an IPBlock Valid
+ examples are "192.168.1.0/24" or "2001:db8::/64"
+ Except values will be rejected if they are
+ outside the cidr range
items:
type: string
type: array
@@ -346,21 +511,43 @@ spec:
- 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."
+ description: "namespaceSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -372,25 +559,54 @@ spec:
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.
+ 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."
+ description: "podSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -402,50 +618,93 @@ spec:
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.
+ 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.
+ description: ports is a 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
+ 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".
+ description: endPort indicates that the range of
+ ports from port to endPort if set, 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.
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.
+ description: port represents 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.
+ description: protocol represents 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.
+ description: podSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -457,13 +716,31 @@ spec:
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.
+ 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
+ description: policyTypes is a 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
+ description: PolicyType string describes the NetworkPolicy
+ type This type is beta-level in 1.8
type: string
type: array
required:
@@ -490,7 +767,8 @@ spec:
type: object
resourceQuotas:
items:
- description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
+ description: ResourceQuotaSpec defines the desired hard limits to
+ enforce for Quota.
properties:
hard:
additionalProperties:
@@ -499,24 +777,39 @@ spec:
- 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/'
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -526,10 +819,14 @@ 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.
+ 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
+ description: A ResourceQuotaScope defines a filter that must
+ match each object tracked by a quota
type: string
type: array
type: object
@@ -601,10 +898,14 @@ spec:
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'
+ 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
@@ -612,7 +913,9 @@ 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.
+ 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:
@@ -620,24 +923,37 @@ spec:
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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -645,7 +961,9 @@ spec:
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.
+ 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:
@@ -655,7 +973,9 @@ spec:
type: string
type: object
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.
+ 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
@@ -664,10 +984,14 @@ spec:
type: string
type: array
ingressOptions:
- description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+ description: Specifies options for the Ingress resources, such as
+ allowed hostnames and IngressClass. Optional.
properties:
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. Optional.
+ 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.
+ Optional.
properties:
allowed:
items:
@@ -677,7 +1001,10 @@ spec:
type: string
type: object
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.
+ 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:
@@ -688,7 +1015,15 @@ spec:
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."
+ 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
@@ -697,16 +1032,21 @@ spec:
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.
+ 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.
+ 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.
+ 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.
+ description: LimitRangeItem defines a min/max usage limit
+ for any resource that matches on kind.
properties:
default:
additionalProperties:
@@ -715,7 +1055,8 @@ spec:
- 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.
+ description: Default resource requirement limit value
+ by resource name if resource limit is omitted.
type: object
defaultRequest:
additionalProperties:
@@ -724,7 +1065,9 @@ spec:
- 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.
+ description: DefaultRequest is the default resource
+ requirement request value by resource name if resource
+ request is omitted.
type: object
max:
additionalProperties:
@@ -733,7 +1076,8 @@ spec:
- 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.
+ description: Max usage constraints on this kind by
+ resource name.
type: object
maxLimitRequestRatio:
additionalProperties:
@@ -742,7 +1086,11 @@ spec:
- 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.
+ 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:
@@ -751,10 +1099,12 @@ spec:
- 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.
+ description: Min usage constraints on this kind by
+ resource name.
type: object
type:
- description: Type of resource that this limit applies to.
+ description: Type of resource that this limit applies
+ to.
type: string
required:
- type
@@ -766,10 +1116,14 @@ spec:
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.
+ 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.
+ description: Specifies additional labels and annotations the Capsule
+ operator places on any Namespace resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
@@ -781,57 +1135,115 @@ spec:
type: object
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.
+ 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.
+ 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
+ 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
+ description: egress is a 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
+ 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.
+ description: ports is a 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
+ 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".
+ description: endPort indicates that the range
+ of ports from port to endPort if set, 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.
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.
+ description: port represents 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.
+ description: protocol represents 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.
+ description: to is a 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
+ 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.
+ 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"
+ description: cidr is a string representing
+ the IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/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
+ description: except is a slice of CIDRs
+ that should not be included within an
+ IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/64" Except values will
+ be rejected if they are outside the cidr
+ range
items:
type: string
type: array
@@ -839,21 +1251,45 @@ spec:
- 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."
+ description: "namespaceSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -865,25 +1301,55 @@ spec:
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.
+ 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."
+ description: "podSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -895,31 +1361,68 @@ spec:
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.
+ 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)
+ description: ingress is a 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.
+ 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.
+ description: from is a 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
+ 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.
+ 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"
+ description: cidr is a string representing
+ the IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/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
+ description: except is a slice of CIDRs
+ that should not be included within an
+ IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/64" Except values will
+ be rejected if they are outside the cidr
+ range
items:
type: string
type: array
@@ -927,21 +1430,45 @@ spec:
- 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."
+ description: "namespaceSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -953,25 +1480,55 @@ spec:
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.
+ 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."
+ description: "podSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -983,50 +1540,96 @@ spec:
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.
+ 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.
+ description: ports is a 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
+ 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".
+ description: endPort indicates that the range
+ of ports from port to endPort if set, 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.
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.
+ description: port represents 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.
+ description: protocol represents 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.
+ description: podSelector 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -1038,13 +1641,32 @@ spec:
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.
+ 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
+ description: policyTypes is a 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
+ description: PolicyType string describes the NetworkPolicy
+ type This type is beta-level in 1.8
type: string
type: array
required:
@@ -1055,14 +1677,19 @@ spec:
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.
+ 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:
kind:
- description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
+ description: Kind of tenant owner. Possible values are "User",
+ "Group", and "ServiceAccount"
enum:
- User
- Group
@@ -1101,7 +1728,9 @@ spec:
type: object
type: array
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. Optional.
+ 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. Optional.
properties:
allowed:
items:
@@ -1111,11 +1740,17 @@ spec:
type: string
type: object
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.
+ 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.
+ description: ResourceQuotaSpec defines the desired hard limits
+ to enforce for Quota.
properties:
hard:
additionalProperties:
@@ -1124,24 +1759,40 @@ spec:
- 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/'
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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
@@ -1151,27 +1802,35 @@ 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.
+ 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
+ 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
+ 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
serviceOptions:
- description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+ 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.
+ description: Specifies additional labels and annotations the Capsule
+ operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
@@ -1187,19 +1846,24 @@ spec:
properties:
externalName:
default: true
- description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
+ 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.
+ 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.
+ 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.
+ 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:
@@ -1211,7 +1875,10 @@ spec:
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. Optional.
+ 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.
+ Optional.
properties:
allowed:
items:
@@ -1236,7 +1903,1286 @@ spec:
type: integer
state:
default: Active
- description: The operational state of the Tenant. Possible values are "Active", "Cordoned".
+ 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: 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: egress is a 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: ports is a 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: endPort indicates that the range
+ of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents 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: to is a 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 IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs
+ that should not be included within an
+ IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/64" Except values will
+ be rejected if they are outside the cidr
+ range
+ items:
+ type: string
+ type: array
+ required:
+ - cidr
+ type: object
+ namespaceSelector:
+ description: "namespaceSelector 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: "podSelector 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: ingress is a 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: from is a 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 IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs
+ that should not be included within an
+ IPBlock Valid examples are "192.168.1.0/24"
+ or "2001:db8::/64" Except values will
+ be rejected if they are outside the cidr
+ range
+ items:
+ type: string
+ type: array
+ required:
+ - cidr
+ type: object
+ namespaceSelector:
+ description: "namespaceSelector 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: "podSelector 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: ports is a 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: endPort indicates that the range
+ of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents 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: podSelector 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: policyTypes is a 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
+ - RuntimeClasses
+ - PersistentVolumes
+ 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
@@ -1250,9 +3196,3 @@ spec:
storage: true
subresources:
status: {}
-status:
- acceptedNames:
- kind: ""
- plural: ""
- conditions: []
- storedVersions: []
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index 1f37ca41..07d5edd3 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -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
diff --git a/config/crd/patches/cainjection_in_globaltenantresources.yaml b/config/crd/patches/cainjection_in_globaltenantresources.yaml
new file mode 100644
index 00000000..fdf9c307
--- /dev/null
+++ b/config/crd/patches/cainjection_in_globaltenantresources.yaml
@@ -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
diff --git a/config/crd/patches/cainjection_in_tenantresources.yaml b/config/crd/patches/cainjection_in_tenantresources.yaml
new file mode 100644
index 00000000..e7158ad9
--- /dev/null
+++ b/config/crd/patches/cainjection_in_tenantresources.yaml
@@ -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
diff --git a/config/crd/patches/webhook_in_capsuleconfiguration.yaml b/config/crd/patches/webhook_in_capsuleconfiguration.yaml
new file mode 100644
index 00000000..a23cd9e4
--- /dev/null
+++ b/config/crd/patches/webhook_in_capsuleconfiguration.yaml
@@ -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
diff --git a/config/crd/patches/webhook_in_globaltenantresources.yaml b/config/crd/patches/webhook_in_globaltenantresources.yaml
new file mode 100644
index 00000000..12cfc50a
--- /dev/null
+++ b/config/crd/patches/webhook_in_globaltenantresources.yaml
@@ -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
diff --git a/config/crd/patches/webhook_in_tenantresources.yaml b/config/crd/patches/webhook_in_tenantresources.yaml
new file mode 100644
index 00000000..827ccf34
--- /dev/null
+++ b/config/crd/patches/webhook_in_tenantresources.yaml
@@ -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
diff --git a/config/crd/patches/webhook_in_tenants.yaml b/config/crd/patches/webhook_in_tenants.yaml
index 342c408c..bc8182da 100644
--- a/config/crd/patches/webhook_in_tenants.yaml
+++ b/config/crd/patches/webhook_in_tenants.yaml
@@ -15,3 +15,4 @@ spec:
conversionReviewVersions:
- v1alpha1
- v1beta1
+ - v1beta2
diff --git a/config/install.yaml b/config/install.yaml
index 36f80d93..dc3feea3 100644
--- a/config/install.yaml
+++ b/config/install.yaml
@@ -9,10 +9,21 @@ 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
@@ -54,71 +65,119 @@ 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: []
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- controller-gen.kubebuilder.io/version: v0.5.0
- name: tenants.capsule.clastix.io
+ controller-gen.kubebuilder.io/version: v0.10.0
+ creationTimestamp: null
+ name: globaltenantresources.capsule.clastix.io
spec:
- conversion:
- strategy: Webhook
- webhook:
- clientConfig:
- service:
- name: capsule-webhook-service
- namespace: capsule-system
- path: /convert
- conversionReviewVersions:
- - v1alpha1
- - v1beta1
group: capsule.clastix.io
names:
- kind: Tenant
- listKind: TenantList
- plural: tenants
- shortNames:
- - tnt
- singular: tenant
+ kind: GlobalTenantResource
+ listKind: GlobalTenantResourceList
+ plural: globaltenantresources
+ singular: globaltenantresource
scope: Cluster
versions:
- - additionalPrinterColumns:
- - description: The max amount of Namespaces can be created
- jsonPath: .spec.namespaceQuota
- name: Namespace quota
- type: integer
- - description: The total amount of Namespaces in use
- jsonPath: .status.size
- name: Namespace count
- type: integer
- - description: The assigned Tenant owner
- jsonPath: .spec.owner.name
- name: Owner name
- type: string
- - description: The assigned Tenant owner kind
- jsonPath: .spec.owner.kind
- name: Owner kind
- type: string
- - description: Node Selector applied to Pods
- jsonPath: .spec.nodeSelector
- name: Node selector
- type: string
- - description: Age
- jsonPath: .metadata.creationTimestamp
- name: Age
- type: date
- name: v1alpha1
+ - name: v1beta2
schema:
openAPIV3Schema:
- description: Tenant is the Schema for the tenants API.
+ 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'
@@ -129,495 +188,1620 @@ spec:
metadata:
type: object
spec:
- description: TenantSpec defines the desired state of Tenant.
+ description: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
properties:
- additionalRoleBindings:
+ 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:
- clusterRoleName:
- type: string
- subjects:
- description: kubebuilder:validation:Minimum=1
+ 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:
- 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.
+ apiVersion:
+ description: API version of the referent.
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.
+ 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 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.
+ 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
- - name
+ - namespace
+ - selector
type: object
type: array
- required:
- - clusterRoleName
- - subjects
+ 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
- containerRegistries:
+ 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:
- allowed:
+ matchExpressions:
+ description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
- type: string
+ 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
- allowedRegex:
- type: string
- type: object
- externalServiceIPs:
- properties:
- allowed:
- items:
- pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$
+ matchLabels:
+ additionalProperties:
type: string
- type: array
- required:
- - allowed
+ 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
- ingressClasses:
- properties:
- allowed:
- items:
+ 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
- type: array
- allowedRegex:
- type: string
- type: object
- ingressHostnames:
- properties:
- allowed:
- items:
+ kind:
+ description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
- type: array
- allowedRegex:
- type: string
- type: object
- limitRanges:
+ 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:
- 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: string
type: array
- namespaceQuota:
- format: int32
- minimum: 1
- type: integer
- namespacesMetadata:
- properties:
- additionalAnnotations:
- additionalProperties:
- type: string
- type: object
- additionalLabels:
- additionalProperties:
- type: string
- type: object
- type: object
- networkPolicies:
+ required:
+ - processedItems
+ - selectedTenants
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+---
+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:
- 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
+ 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:
- 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.
+ 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:
- cidr:
- description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+ key:
+ description: key is the label key that the selector applies to.
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
+ 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:
- - 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
- 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
+ - key
+ - operator
type: object
- type: object
- type: array
+ 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
- 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)
+ rawItems:
+ description: List of raw resources that must be replicated.
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
+ 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: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.10.0
+ name: tenants.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: Tenant
+ listKind: TenantList
+ plural: tenants
+ shortNames:
+ - tnt
+ singular: tenant
+ scope: Cluster
+ versions:
+ - additionalPrinterColumns:
+ - description: The max amount of Namespaces can be created
+ jsonPath: .spec.namespaceQuota
+ name: Namespace quota
+ type: integer
+ - description: The total amount of Namespaces in use
+ jsonPath: .status.size
+ name: Namespace count
+ type: integer
+ - description: The assigned Tenant owner
+ jsonPath: .spec.owner.name
+ name: Owner name
+ type: string
+ - description: The assigned Tenant owner kind
+ jsonPath: .spec.owner.kind
+ name: Owner kind
+ type: string
+ - description: Node Selector applied to Pods
+ jsonPath: .spec.nodeSelector
+ name: Node selector
+ type: string
+ - description: Age
+ 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:
+ 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:
+ 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:
+ properties:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
+ type: string
+ type: object
+ externalServiceIPs:
+ 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
+ ingressClasses:
+ properties:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
+ type: string
+ type: object
+ ingressHostnames:
+ properties:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
+ type: string
+ type: object
+ limitRanges:
+ 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
+ namespaceQuota:
+ format: int32
+ minimum: 1
+ type: integer
+ namespacesMetadata:
+ properties:
+ additionalAnnotations:
+ additionalProperties:
+ type: string
+ type: object
+ additionalLabels:
+ additionalProperties:
+ type: string
+ type: object
+ type: object
+ networkPolicies:
+ items:
+ description: NetworkPolicySpec provides the specification of a NetworkPolicy
+ properties:
+ egress:
+ description: egress is a 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: ports is a 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: endPort indicates that the range of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents 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: to is a 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 IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ items:
+ type: string
+ type: array
+ required:
+ - cidr
+ type: object
+ namespaceSelector:
+ description: "namespaceSelector 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: "podSelector 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: ingress is a 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: from is a 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 IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/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."
+ description: "namespaceSelector 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: "podSelector 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: ports is a 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: endPort indicates that the range of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents 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: podSelector 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: policyTypes is a 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
+ nodeSelector:
+ additionalProperties:
+ type: string
+ type: object
+ owner:
+ description: OwnerSpec defines tenant owner name and kind.
+ properties:
+ kind:
+ enum:
+ - User
+ - Group
+ type: string
+ name:
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ resourceQuotas:
+ 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
+ servicesMetadata:
+ properties:
+ additionalAnnotations:
+ additionalProperties:
+ type: string
+ type: object
+ additionalLabels:
+ additionalProperties:
+ type: string
+ type: object
+ type: object
+ storageClasses:
+ properties:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
+ type: string
+ type: object
+ required:
+ - owner
+ type: object
+ status:
+ description: TenantStatus defines the observed state of Tenant.
+ properties:
+ namespaces:
+ items:
+ type: string
+ type: array
+ size:
+ type: integer
+ required:
+ - size
+ 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: v1beta1
+ 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
+ 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:
+ 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. Optional.
+ properties:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
+ type: string
+ type: object
+ 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
+ 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: egress is a 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: ports is a 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:
- 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.
+ endPort:
+ description: endPort indicates that the range of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents 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: to is a 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 IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ items:
type: string
- operator:
- description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ type: array
+ required:
+ - cidr
+ type: object
+ namespaceSelector:
+ description: "namespaceSelector 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
- 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.
+ 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: "podSelector 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
- 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."
+ type: array
+ type: object
+ type: array
+ ingress:
+ description: ingress is a 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: from is a 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:
- 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.
+ 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 IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ type: string
+ except:
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ items:
type: string
- operator:
- description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ type: array
+ required:
+ - cidr
+ type: object
+ namespaceSelector:
+ description: "namespaceSelector 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
- 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.
+ 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: "podSelector 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: 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.
+ type: array
+ ports:
+ description: ports is a 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:
- type: string
+ description: NetworkPolicyPort describes a port to allow traffic on
+ properties:
+ endPort:
+ description: endPort indicates that the range of ports from port to endPort if set, 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.
+ format: int32
+ type: integer
+ port:
+ anyOf:
+ - type: integer
+ - type: string
+ description: port represents 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: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+ type: string
+ type: object
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.
+ podSelector:
+ description: podSelector 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: policyTypes is a 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
- 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
+ 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:
+ 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:
- description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8
- type: string
+ 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:
- - podSelector
+ - kind
+ - name
type: object
type: array
- nodeSelector:
- additionalProperties:
- type: string
- type: object
- owner:
- description: OwnerSpec defines tenant owner name and kind.
+ 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. Optional.
properties:
- kind:
- enum:
- - User
- - Group
- type: string
- name:
+ allowed:
+ items:
+ type: string
+ type: array
+ allowedRegex:
type: string
- required:
- - kind
- - name
type: object
resourceQuotas:
- 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.
+ 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:
- matchExpressions:
- description: A list of scope selector requirements by scope of the resources.
+ 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 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
+ description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota
+ type: string
type: array
type: object
- 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
- servicesMetadata:
+ 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
+ serviceOptions:
+ description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
properties:
- additionalAnnotations:
- additionalProperties:
- type: string
+ 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
- additionalLabels:
- additionalProperties:
- type: string
+ 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. Optional.
properties:
allowed:
items:
@@ -627,19 +1811,29 @@ spec:
type: string
type: object
required:
- - owner
+ - owners
type: object
status:
- description: TenantStatus defines the observed state of Tenant.
+ 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
@@ -667,7 +1861,7 @@ spec:
jsonPath: .metadata.creationTimestamp
name: Age
type: date
- name: v1beta1
+ name: v1beta2
schema:
openAPIV3Schema:
description: Tenant is the Schema for the tenants API.
@@ -710,6 +1904,7 @@ spec:
- kind
- name
type: object
+ x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
@@ -726,6 +1921,9 @@ spec:
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:
@@ -738,8 +1936,11 @@ spec:
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. Optional.
+ 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:
@@ -747,7 +1948,36 @@ spec:
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:
@@ -852,6 +2082,26 @@ spec:
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
@@ -866,44 +2116,44 @@ spec:
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
+ description: egress is a 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.
+ description: ports is a 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".
+ description: endPort indicates that the range of ports from port to endPort if set, 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.
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.
+ description: port represents 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.
+ description: protocol represents 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.
+ description: to is a 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.
+ 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"
+ description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/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
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
items:
type: string
type: array
@@ -911,7 +2161,7 @@ spec:
- 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."
+ description: "namespaceSelector 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.
@@ -940,8 +2190,9 @@ 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."
+ description: "podSelector 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.
@@ -970,28 +2221,29 @@ 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
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)
+ description: ingress is a 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.
+ description: from is a 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.
+ 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"
+ description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/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
+ description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
items:
type: string
type: array
@@ -999,7 +2251,7 @@ spec:
- 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."
+ description: "namespaceSelector 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.
@@ -1028,8 +2280,9 @@ 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."
+ description: "podSelector 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.
@@ -1058,33 +2311,34 @@ 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:
- 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.
+ description: ports is a 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".
+ description: endPort indicates that the range of ports from port to endPort if set, 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.
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.
+ description: port represents 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.
+ description: protocol represents 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.
+ description: podSelector 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.
@@ -1113,8 +2367,9 @@ 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
+ description: policyTypes is a 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
@@ -1133,6 +2388,14 @@ spec:
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:
@@ -1153,6 +2416,8 @@ spec:
- StorageClasses
- IngressClasses
- PriorityClasses
+ - RuntimeClasses
+ - PersistentVolumes
type: string
operations:
items:
@@ -1172,8 +2437,11 @@ spec:
- 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. Optional.
+ 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:
@@ -1181,7 +2449,36 @@ spec:
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:
@@ -1223,6 +2520,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:
@@ -1239,6 +2537,43 @@ spec:
- 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:
@@ -1283,7 +2618,7 @@ spec:
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. Optional.
+ 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:
@@ -1291,7 +2626,36 @@ spec:
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
@@ -1322,12 +2686,6 @@ spec:
storage: true
subresources:
status: {}
-status:
- acceptedNames:
- kind: ""
- plural: ""
- conditions: []
- storedVersions: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
@@ -1411,7 +2769,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- image: clastix/capsule:v0.1.3
+ image: clastix/capsule:v0.3.3
imagePullPolicy: IfNotPresent
name: manager
ports:
@@ -1439,12 +2797,13 @@ spec:
defaultMode: 420
secretName: capsule-tls
---
-apiVersion: capsule.clastix.io/v1alpha1
+apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: capsule-default
namespace: capsule-system
spec:
+ enableTLSReconciler: true
forceTenantPrefix: false
protectedNamespaceRegex: ""
userGroups:
@@ -1456,6 +2815,80 @@ metadata:
creationTimestamp: null
name: capsule-mutating-webhook-configuration
webhooks:
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: capsule-webhook-service
+ namespace: capsule-system
+ path: /defaults
+ failurePolicy: Fail
+ name: pod.defaults.capsule.clastix.io
+ namespaceSelector:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+ rules:
+ - apiGroups:
+ - ""
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ resources:
+ - pods
+ scope: Namespaced
+ sideEffects: None
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: capsule-webhook-service
+ namespace: capsule-system
+ path: /defaults
+ failurePolicy: Fail
+ name: storage.defaults.capsule.clastix.io
+ namespaceSelector:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+ rules:
+ - apiGroups:
+ - ""
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ resources:
+ - persistentvolumeclaims
+ scope: Namespaced
+ sideEffects: None
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: capsule-webhook-service
+ namespace: capsule-system
+ path: /defaults
+ failurePolicy: Fail
+ name: ingress.defaults.capsule.clastix.io
+ namespaceSelector:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+ rules:
+ - apiGroups:
+ - networking.k8s.io
+ apiVersions:
+ - v1beta1
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - ingresses
+ scope: Namespaced
+ sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
@@ -1625,6 +3058,7 @@ webhooks:
- v1
operations:
- CREATE
+ - UPDATE
resources:
- pods
scope: Namespaced
@@ -1678,6 +3112,35 @@ webhooks:
- services
scope: Namespaced
sideEffects: None
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: capsule-webhook-service
+ namespace: capsule-system
+ path: /tenantresource-objects
+ failurePolicy: Fail
+ name: resource-objects.tenant.capsule.clastix.io
+ namespaceSelector:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+ objectSelector:
+ matchExpressions:
+ - key: capsule.clastix.io/resources
+ operator: Exists
+ rules:
+ - apiGroups:
+ - '*'
+ apiVersions:
+ - '*'
+ operations:
+ - UPDATE
+ - DELETE
+ resources:
+ - '*'
+ scope: Namespaced
+ sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
@@ -1691,7 +3154,7 @@ webhooks:
- apiGroups:
- capsule.clastix.io
apiVersions:
- - v1beta1
+ - v1beta2
operations:
- CREATE
- UPDATE
diff --git a/config/manager/configuration.yaml b/config/manager/configuration.yaml
index 950a5d46..c5adef5c 100644
--- a/config/manager/configuration.yaml
+++ b/config/manager/configuration.yaml
@@ -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
diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml
index 55f23e74..5274894d 100644
--- a/config/manager/kustomization.yaml
+++ b/config/manager/kustomization.yaml
@@ -7,4 +7,4 @@ kind: Kustomization
images:
- name: controller
newName: clastix/capsule
- newTag: v0.1.3
+ newTag: v0.3.3
diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml
index 4e44d90b..ed137168 100644
--- a/config/prometheus/kustomization.yaml
+++ b/config/prometheus/kustomization.yaml
@@ -1,4 +1,2 @@
resources:
- monitor.yaml
-- role.yaml
-- rolebinding.yaml
diff --git a/config/prometheus/role.yaml b/config/prometheus/role.yaml
deleted file mode 100644
index cc789363..00000000
--- a/config/prometheus/role.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
- labels:
- control-plane: controller-manager
- name: capsule-metrics-role
- namespace: capsule-system
-rules:
-- apiGroups:
- - ""
- resources:
- - services
- - endpoints
- - pods
- verbs:
- - get
- - list
- - watch
diff --git a/config/prometheus/rolebinding.yaml b/config/prometheus/rolebinding.yaml
deleted file mode 100644
index b5c57a56..00000000
--- a/config/prometheus/rolebinding.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
- labels:
- control-plane: controller-manager
- name: capsule-metrics-rolebinding
- namespace: system
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: Role
- name: capsule-metrics-role
-subjects:
-- kind: ServiceAccount
- name: capsule
- namespace: capsule-system
diff --git a/config/rbac/globaltenantresource_editor_role.yaml b/config/rbac/globaltenantresource_editor_role.yaml
new file mode 100644
index 00000000..60d87ab1
--- /dev/null
+++ b/config/rbac/globaltenantresource_editor_role.yaml
@@ -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
diff --git a/config/rbac/globaltenantresource_viewer_role.yaml b/config/rbac/globaltenantresource_viewer_role.yaml
new file mode 100644
index 00000000..d535a89b
--- /dev/null
+++ b/config/rbac/globaltenantresource_viewer_role.yaml
@@ -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
diff --git a/config/rbac/tenantresource_editor_role.yaml b/config/rbac/tenantresource_editor_role.yaml
new file mode 100644
index 00000000..2812649e
--- /dev/null
+++ b/config/rbac/tenantresource_editor_role.yaml
@@ -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
diff --git a/config/rbac/tenantresource_viewer_role.yaml b/config/rbac/tenantresource_viewer_role.yaml
new file mode 100644
index 00000000..b37ea627
--- /dev/null
+++ b/config/rbac/tenantresource_viewer_role.yaml
@@ -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
diff --git a/config/samples/capsule_v1beta2_capsuleconfiguration.yaml b/config/samples/capsule_v1beta2_capsuleconfiguration.yaml
new file mode 100644
index 00000000..71b2fce1
--- /dev/null
+++ b/config/samples/capsule_v1beta2_capsuleconfiguration.yaml
@@ -0,0 +1,11 @@
+---
+apiVersion: capsule.clastix.io/v1beta2
+kind: CapsuleConfiguration
+metadata:
+ name: default
+spec:
+ userGroups: ["capsule.clastix.io"]
+ forceTenantPrefix: false
+ protectedNamespaceRegex: ""
+ enableTLSReconciler: true
+
diff --git a/config/samples/capsule_v1beta2_globaltenantresource.yaml b/config/samples/capsule_v1beta2_globaltenantresource.yaml
new file mode 100644
index 00000000..3d2853ce
--- /dev/null
+++ b/config/samples/capsule_v1beta2_globaltenantresource.yaml
@@ -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
\ No newline at end of file
diff --git a/config/samples/capsule_v1beta2_tenantresource.yaml b/config/samples/capsule_v1beta2_tenantresource.yaml
new file mode 100644
index 00000000..db255f5d
--- /dev/null
+++ b/config/samples/capsule_v1beta2_tenantresource.yaml
@@ -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
\ No newline at end of file
diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml
index 8b7eb535..8ff7dd79 100644
--- a/config/webhook/kustomization.yaml
+++ b/config/webhook/kustomization.yaml
@@ -8,7 +8,14 @@ patchesJson6902:
kind: ValidatingWebhookConfiguration
name: validating-webhook-configuration
version: v1
- path: patch_ns_selector.yaml
+ path: patch_validating_ns_selector.yaml
+- target:
+ group: admissionregistration.k8s.io
+ kind: MutatingWebhookConfiguration
+ name: mutating-webhook-configuration
+ version: v1
+ path: patch_mutating_ns_selector.yaml
+
configurations:
- kustomizeconfig.yaml
diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml
index f76c3196..294c5fc2 100644
--- a/config/webhook/manifests.yaml
+++ b/config/webhook/manifests.yaml
@@ -1,4 +1,3 @@
-
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
@@ -6,6 +5,65 @@ metadata:
creationTimestamp: null
name: mutating-webhook-configuration
webhooks:
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: webhook-service
+ namespace: system
+ path: /defaults
+ failurePolicy: Fail
+ name: pod.defaults.capsule.clastix.io
+ rules:
+ - apiGroups:
+ - ""
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ resources:
+ - pods
+ sideEffects: None
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: webhook-service
+ namespace: system
+ path: /defaults
+ failurePolicy: Fail
+ name: storage.defaults.capsule.clastix.io
+ rules:
+ - apiGroups:
+ - ""
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ resources:
+ - persistentvolumeclaims
+ sideEffects: None
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: webhook-service
+ namespace: system
+ path: /defaults
+ failurePolicy: Fail
+ name: ingress.defaults.capsule.clastix.io
+ rules:
+ - apiGroups:
+ - networking.k8s.io
+ apiVersions:
+ - v1beta1
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - ingresses
+ sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
@@ -26,7 +84,6 @@ webhooks:
resources:
- namespaces
sideEffects: None
-
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
@@ -153,6 +210,7 @@ webhooks:
- v1
operations:
- CREATE
+ - UPDATE
resources:
- pods
sideEffects: None
@@ -195,6 +253,26 @@ webhooks:
resources:
- services
sideEffects: None
+- admissionReviewVersions:
+ - v1
+ clientConfig:
+ service:
+ name: webhook-service
+ namespace: system
+ path: /tenantresource-objects
+ failurePolicy: Fail
+ name: resource-objects.tenant.capsule.clastix.io
+ rules:
+ - apiGroups:
+ - '*'
+ apiVersions:
+ - '*'
+ operations:
+ - UPDATE
+ - DELETE
+ resources:
+ - '*'
+ sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
@@ -208,7 +286,7 @@ webhooks:
- apiGroups:
- capsule.clastix.io
apiVersions:
- - v1beta1
+ - v1beta2
operations:
- CREATE
- UPDATE
diff --git a/config/webhook/patch_mutating_ns_selector.yaml b/config/webhook/patch_mutating_ns_selector.yaml
new file mode 100644
index 00000000..790b06e1
--- /dev/null
+++ b/config/webhook/patch_mutating_ns_selector.yaml
@@ -0,0 +1,27 @@
+- op: add
+ path: /webhooks/0/namespaceSelector
+ value:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+- op: add
+ path: /webhooks/1/namespaceSelector
+ value:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+- op: add
+ path: /webhooks/2/namespaceSelector
+ value:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+- op: add
+ path: /webhooks/0/rules/0/scope
+ value: Namespaced
+- op: add
+ path: /webhooks/1/rules/0/scope
+ value: Namespaced
+- op: add
+ path: /webhooks/2/rules/0/scope
+ value: Namespaced
diff --git a/config/webhook/patch_ns_selector.yaml b/config/webhook/patch_validating_ns_selector.yaml
similarity index 79%
rename from config/webhook/patch_ns_selector.yaml
rename to config/webhook/patch_validating_ns_selector.yaml
index 523f7a0d..2e39ab83 100644
--- a/config/webhook/patch_ns_selector.yaml
+++ b/config/webhook/patch_validating_ns_selector.yaml
@@ -40,6 +40,18 @@
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
+- op: add
+ path: /webhooks/8/namespaceSelector
+ value:
+ matchExpressions:
+ - key: capsule.clastix.io/tenant
+ operator: Exists
+- op: add
+ path: /webhooks/8/objectSelector
+ value:
+ matchExpressions:
+ - key: capsule.clastix.io/resources
+ operator: Exists
- op: add
path: /webhooks/0/rules/0/scope
value: Namespaced
@@ -58,3 +70,6 @@
- op: add
path: /webhooks/7/rules/0/scope
value: Namespaced
+- op: add
+ path: /webhooks/8/rules/0/scope
+ value: Namespaced
diff --git a/controllers/config/manager.go b/controllers/config/manager.go
index 7be53be8..129733d6 100644
--- a/controllers/config/manager.go
+++ b/controllers/config/manager.go
@@ -12,33 +12,29 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/controllers/utils"
"github.com/clastix/capsule/pkg/configuration"
)
type Manager struct {
- Log logr.Logger
- Client client.Client
-}
-
-// InjectClient injects the Client interface, required by the Runnable interface.
-func (c *Manager) InjectClient(client client.Client) error {
- c.Client = client
+ client client.Client
- return nil
+ Log logr.Logger
}
func (c *Manager) SetupWithManager(mgr ctrl.Manager, configurationName string) error {
+ c.client = mgr.GetClient()
+
return ctrl.NewControllerManagedBy(mgr).
- For(&capsulev1alpha1.CapsuleConfiguration{}, utils.NamesMatchingPredicate(configurationName)).
+ For(&capsulev1beta2.CapsuleConfiguration{}, utils.NamesMatchingPredicate(configurationName)).
Complete(c)
}
func (c *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res reconcile.Result, err error) {
c.Log.Info("CapsuleConfiguration reconciliation started", "request.name", request.Name)
- cfg := configuration.NewCapsuleConfiguration(ctx, c.Client, request.Name)
+ cfg := configuration.NewCapsuleConfiguration(ctx, c.client, request.Name)
// Validating the Capsule Configuration options
if _, err = cfg.ProtectedNamespaceRegexp(); err != nil {
panic(errors.Wrap(err, "Invalid configuration for protected Namespace regex"))
diff --git a/controllers/pv/controller.go b/controllers/pv/controller.go
new file mode 100644
index 00000000..3ef3ebae
--- /dev/null
+++ b/controllers/pv/controller.go
@@ -0,0 +1,117 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package pv
+
+import (
+ "context"
+
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/client-go/util/retry"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/builder"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ log2 "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ capsuleutils "github.com/clastix/capsule/pkg/utils"
+ webhookutils "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+type Controller struct {
+ client client.Client
+ label string
+}
+
+func (c *Controller) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
+ log := log2.FromContext(ctx)
+
+ persistentVolume := corev1.PersistentVolume{}
+ if err := c.client.Get(ctx, request.NamespacedName, &persistentVolume); err != nil {
+ if errors.IsNotFound(err) {
+ log.Info("skipping reconciliation, resource may have been deleted")
+
+ return reconcile.Result{}, nil
+ }
+
+ log.Error(err, "cannot retrieve corev1.PersistentVolume")
+
+ return reconcile.Result{}, err
+ }
+
+ if persistentVolume.Spec.ClaimRef == nil {
+ log.Info("skipping reconciliation, missing claimRef")
+
+ return reconcile.Result{}, nil
+ }
+
+ tnt, err := webhookutils.TenantByStatusNamespace(ctx, c.client, persistentVolume.Spec.ClaimRef.Namespace)
+ if err != nil {
+ log.Error(err, "unable to retrieve Tenant from the claimRef")
+
+ return reconcile.Result{}, err
+ }
+
+ if tnt == nil {
+ log.Info("skipping reconciliation, PV is claimed by a PVC not managed in a Tenant")
+
+ return reconcile.Result{}, nil
+ }
+
+ retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
+ pv := persistentVolume
+
+ if err = c.client.Get(ctx, request.NamespacedName, &pv); err != nil {
+ return err
+ }
+
+ labels := pv.GetLabels()
+ if labels == nil {
+ labels = map[string]string{}
+ }
+
+ labels[c.label] = tnt.GetName()
+
+ pv.SetLabels(labels)
+
+ return c.client.Update(ctx, &pv)
+ })
+ if retryErr != nil {
+ log.Error(retryErr, "unable to update PersistentVolume with Capsule label")
+
+ return reconcile.Result{}, retryErr
+ }
+
+ return reconcile.Result{}, nil
+}
+
+func (c *Controller) SetupWithManager(mgr ctrl.Manager) error {
+ label, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{})
+ if err != nil {
+ return err
+ }
+
+ c.client = mgr.GetClient()
+ c.label = label
+
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&corev1.PersistentVolume{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ pv, ok := object.(*corev1.PersistentVolume)
+ if !ok {
+ return false
+ }
+
+ if pv.Spec.ClaimRef == nil {
+ return false
+ }
+
+ labels := object.GetLabels()
+ _, ok = labels[c.label]
+
+ return !ok
+ }))).
+ Complete(c)
+}
diff --git a/controllers/rbac/manager.go b/controllers/rbac/manager.go
index 58cf6159..a5a8c086 100644
--- a/controllers/rbac/manager.go
+++ b/controllers/rbac/manager.go
@@ -19,9 +19,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
- "sigs.k8s.io/controller-runtime/pkg/source"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/controllers/utils"
"github.com/clastix/capsule/pkg/configuration"
)
@@ -32,13 +31,6 @@ type Manager struct {
Configuration configuration.Configuration
}
-// InjectClient injects the Client interface, required by the Runnable interface.
-func (r *Manager) InjectClient(c client.Client) error {
- r.Client = c
-
- return nil
-}
-
func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, configurationName string) (err error) {
namesPredicate := utils.NamesMatchingPredicate(ProvisionerRoleName, DeleterRoleName)
@@ -51,8 +43,8 @@ func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, config
crbErr := ctrl.NewControllerManagedBy(mgr).
For(&rbacv1.ClusterRoleBinding{}, namesPredicate).
- Watches(source.NewKindWithCache(&capsulev1alpha1.CapsuleConfiguration{}, mgr.GetCache()), handler.Funcs{
- UpdateFunc: func(updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) {
+ Watches(&capsulev1beta2.CapsuleConfiguration{}, handler.Funcs{
+ UpdateFunc: func(ctx context.Context, updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) {
if updateEvent.ObjectNew.GetName() == configurationName {
if crbErr := r.EnsureClusterRoleBindings(ctx); crbErr != nil {
r.Log.Error(err, "cannot update ClusterRoleBinding upon CapsuleConfiguration update")
diff --git a/controllers/resources/global.go b/controllers/resources/global.go
new file mode 100644
index 00000000..6bcaaa93
--- /dev/null
+++ b/controllers/resources/global.go
@@ -0,0 +1,206 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+ "context"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/pkg/errors"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/sets"
+ "sigs.k8s.io/cluster-api/util/patch"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+type Global struct {
+ client client.Client
+ processor Processor
+}
+
+func (r *Global) enqueueRequestFromTenant(ctx context.Context, object client.Object) (reqs []reconcile.Request) {
+ tnt := object.(*capsulev1beta2.Tenant) //nolint:forcetypeassert
+
+ resList := capsulev1beta2.GlobalTenantResourceList{}
+ if err := r.client.List(ctx, &resList); err != nil {
+ return nil
+ }
+
+ set := sets.NewString()
+
+ for _, res := range resList.Items {
+ selector, err := metav1.LabelSelectorAsSelector(&res.Spec.TenantSelector)
+ if err != nil {
+ continue
+ }
+
+ if selector.Matches(labels.Set(tnt.GetLabels())) {
+ set.Insert(res.GetName())
+ }
+ }
+ // No need of ordered value here
+ for res := range set {
+ reqs = append(reqs, reconcile.Request{
+ NamespacedName: types.NamespacedName{
+ Name: res,
+ },
+ })
+ }
+
+ return reqs
+}
+
+func (r *Global) SetupWithManager(mgr ctrl.Manager) error {
+ r.client = mgr.GetClient()
+ r.processor = Processor{
+ client: mgr.GetClient(),
+ }
+
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&capsulev1beta2.GlobalTenantResource{}).
+ Watches(&capsulev1beta2.Tenant{}, handler.EnqueueRequestsFromMapFunc(r.enqueueRequestFromTenant)).
+ Complete(r)
+}
+
+//nolint:dupl
+func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
+ log := ctrllog.FromContext(ctx)
+
+ log.Info("start processing")
+ // Retrieving the GlobalTenantResource
+ tntResource := &capsulev1beta2.GlobalTenantResource{}
+ if err := r.client.Get(ctx, request.NamespacedName, tntResource); err != nil {
+ if apierrors.IsNotFound(err) {
+ log.Info("Request object not found, could have been deleted after reconcile request")
+
+ return reconcile.Result{}, nil
+ }
+
+ return reconcile.Result{}, err
+ }
+
+ patchHelper, err := patch.NewHelper(tntResource, r.client)
+ if err != nil {
+ return reconcile.Result{}, errors.Wrap(err, "failed to init patch helper")
+ }
+
+ defer func() {
+ if e := patchHelper.Patch(ctx, tntResource); e != nil {
+ if err == nil {
+ err = errors.Wrap(e, "failed to patch GlobalTenantResource")
+ }
+ }
+ }()
+
+ // Handle deleted GlobalTenantResource
+ if !tntResource.DeletionTimestamp.IsZero() {
+ return r.reconcileDelete(ctx, tntResource)
+ }
+
+ // Handle non-deleted GlobalTenantResource
+ return r.reconcileNormal(ctx, tntResource)
+}
+
+func (r *Global) reconcileNormal(ctx context.Context, tntResource *capsulev1beta2.GlobalTenantResource) (reconcile.Result, error) {
+ log := ctrllog.FromContext(ctx)
+
+ if *tntResource.Spec.PruningOnDelete {
+ controllerutil.AddFinalizer(tntResource, finalizer)
+ }
+
+ if tntResource.Status.ProcessedItems == nil {
+ tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0)
+ }
+
+ // Retrieving the list of the Tenants up to the selector provided by the GlobalTenantResource resource.
+ tntSelector, err := metav1.LabelSelectorAsSelector(&tntResource.Spec.TenantSelector)
+ if err != nil {
+ log.Error(err, "cannot create MatchingLabelsSelector for Global filtering")
+
+ return reconcile.Result{}, err
+ }
+
+ tntList := capsulev1beta2.TenantList{}
+ if err = r.client.List(ctx, &tntList, &client.MatchingLabelsSelector{Selector: tntSelector}); err != nil {
+ log.Error(err, "cannot list Tenants matching the provided selector")
+
+ return reconcile.Result{}, err
+ }
+ // This is the list of newer Tenants that are matching the provided GlobalTenantResource Selector:
+ // upon replication and pruning, this will be updated in the status of the resource.
+ tntSet := sets.NewString()
+
+ err = new(multierror.Error)
+ // A TenantResource is made of several Resource sections, each one with specific options:
+ // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status.
+ processedItems := sets.NewString()
+
+ for index, resource := range tntResource.Spec.Resources {
+ tenantLabel, labelErr := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{})
+ if labelErr != nil {
+ log.Error(labelErr, "expected label for selection")
+
+ return reconcile.Result{}, labelErr
+ }
+
+ for _, tnt := range tntList.Items {
+ tntSet.Insert(tnt.GetName())
+
+ items, sectionErr := r.processor.HandleSection(ctx, tnt, true, tenantLabel, index, resource)
+ if sectionErr != nil {
+ // Upon a process error storing the last error occurred and continuing to iterate,
+ // avoid to block the whole processing.
+ err = multierror.Append(err, sectionErr)
+ } else {
+ processedItems.Insert(items...)
+ }
+ }
+ }
+
+ if err.(*multierror.Error).ErrorOrNil() != nil { //nolint:errorlint,forcetypeassert
+ log.Error(err, "unable to replicate the requested resources")
+
+ return reconcile.Result{}, err
+ }
+
+ if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), sets.Set[string](processedItems)) {
+ tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems))
+
+ for _, item := range processedItems.List() {
+ if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil {
+ tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or)
+ }
+ }
+ }
+
+ tntResource.Status.SelectedTenants = tntSet.List()
+
+ log.Info("processing completed")
+
+ return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil
+}
+
+func (r *Global) reconcileDelete(ctx context.Context, tntResource *capsulev1beta2.GlobalTenantResource) (reconcile.Result, error) {
+ log := ctrllog.FromContext(ctx)
+
+ if *tntResource.Spec.PruningOnDelete {
+ r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), nil)
+
+ controllerutil.RemoveFinalizer(tntResource, finalizer)
+ }
+
+ log.Info("processing completed")
+
+ return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil
+}
diff --git a/controllers/resources/namespaced.go b/controllers/resources/namespaced.go
new file mode 100644
index 00000000..e9b0ae80
--- /dev/null
+++ b/controllers/resources/namespaced.go
@@ -0,0 +1,162 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+ "context"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/pkg/errors"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/fields"
+ "k8s.io/apimachinery/pkg/util/sets"
+ "sigs.k8s.io/cluster-api/util/patch"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+ ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+type Namespaced struct {
+ client client.Client
+ processor Processor
+}
+
+func (r *Namespaced) SetupWithManager(mgr ctrl.Manager) error {
+ r.client = mgr.GetClient()
+ r.processor = Processor{
+ client: mgr.GetClient(),
+ }
+
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&capsulev1beta2.TenantResource{}).
+ Complete(r)
+}
+
+//nolint:dupl
+func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
+ log := ctrllog.FromContext(ctx)
+
+ log.Info("start processing")
+ // Retrieving the TenantResource
+ tntResource := &capsulev1beta2.TenantResource{}
+ if err := r.client.Get(ctx, request.NamespacedName, tntResource); err != nil {
+ if apierrors.IsNotFound(err) {
+ log.Info("Request object not found, could have been deleted after reconcile request")
+
+ return reconcile.Result{}, nil
+ }
+
+ return reconcile.Result{}, err
+ }
+
+ patchHelper, err := patch.NewHelper(tntResource, r.client)
+ if err != nil {
+ return reconcile.Result{}, errors.Wrap(err, "failed to init patch helper")
+ }
+
+ defer func() {
+ if e := patchHelper.Patch(ctx, tntResource); e != nil {
+ if err == nil {
+ err = errors.Wrap(e, "failed to patch TenantResource")
+ }
+ }
+ }()
+
+ // Handle deleted TenantResource
+ if !tntResource.DeletionTimestamp.IsZero() {
+ return r.reconcileDelete(ctx, tntResource)
+ }
+
+ // Handle non-deleted TenantResource
+ return r.reconcileNormal(ctx, tntResource)
+}
+
+func (r *Namespaced) reconcileNormal(ctx context.Context, tntResource *capsulev1beta2.TenantResource) (reconcile.Result, error) {
+ log := ctrllog.FromContext(ctx)
+
+ if *tntResource.Spec.PruningOnDelete {
+ controllerutil.AddFinalizer(tntResource, finalizer)
+ }
+
+ // Adding the default value for the status
+ if tntResource.Status.ProcessedItems == nil {
+ tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0)
+ }
+
+ // Retrieving the parent of the Tenant Resource:
+ // can be owned, or being deployed in one of its Namespace.
+ tl := &capsulev1beta2.TenantList{}
+ if err := r.client.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", tntResource.GetNamespace())}); err != nil {
+ log.Error(err, "unable to detect the Tenant for the given TenantResource")
+
+ return reconcile.Result{}, err
+ }
+
+ if len(tl.Items) == 0 {
+ log.Info("skipping sync, the current Namespace is not belonging to any Global")
+
+ return reconcile.Result{}, nil
+ }
+
+ err := new(multierror.Error)
+ // A TenantResource is made of several Resource sections, each one with specific options:
+ // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status.
+ processedItems := sets.NewString()
+
+ tenantLabel, labelErr := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{})
+ if labelErr != nil {
+ log.Error(labelErr, "expected label for selection")
+
+ return reconcile.Result{}, labelErr
+ }
+
+ for index, resource := range tntResource.Spec.Resources {
+ items, sectionErr := r.processor.HandleSection(ctx, tl.Items[0], false, tenantLabel, index, resource)
+ if sectionErr != nil {
+ // Upon a process error storing the last error occurred and continuing to iterate,
+ // avoid to block the whole processing.
+ err = multierror.Append(err, sectionErr)
+ } else {
+ processedItems.Insert(items...)
+ }
+ }
+
+ if err.ErrorOrNil() != nil {
+ log.Error(err, "unable to replicate the requested resources")
+
+ return reconcile.Result{}, err
+ }
+
+ if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), sets.Set[string](processedItems)) {
+ tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems))
+
+ for _, item := range processedItems.List() {
+ if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil {
+ tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or)
+ }
+ }
+ }
+
+ log.Info("processing completed")
+
+ return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil
+}
+
+func (r *Namespaced) reconcileDelete(ctx context.Context, tntResource *capsulev1beta2.TenantResource) (reconcile.Result, error) {
+ log := ctrllog.FromContext(ctx)
+
+ if *tntResource.Spec.PruningOnDelete {
+ r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), nil)
+ }
+
+ controllerutil.RemoveFinalizer(tntResource, finalizer)
+
+ log.Info("processing completed")
+
+ return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil
+}
diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go
new file mode 100644
index 00000000..20075bb9
--- /dev/null
+++ b/controllers/resources/processor.go
@@ -0,0 +1,275 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/valyala/fasttemplate"
+ corev1 "k8s.io/api/core/v1"
+ apierr "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/runtime/serializer"
+ "k8s.io/apimachinery/pkg/selection"
+ "k8s.io/apimachinery/pkg/util/sets"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+ ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+const (
+ Label = "capsule.clastix.io/resources"
+ finalizer = "capsule.clastix.io/resources"
+)
+
+type Processor struct {
+ client client.Client
+}
+
+func (r *Processor) HandlePruning(ctx context.Context, current, desired sets.Set[string]) (updateStatus bool) {
+ log := ctrllog.FromContext(ctx)
+
+ diff := current.Difference(desired)
+ // We don't want to trigger a reconciliation of the Status every time,
+ // rather, only in case of a difference between the processed and the actual status.
+ // This can happen upon the first reconciliation, or a removal, or a change, of a resource.
+ updateStatus = diff.Len() > 0 || current.Len() != desired.Len()
+
+ if diff.Len() > 0 {
+ log.Info("starting processing pruning", "length", diff.Len())
+ }
+
+ // The outer resources must be removed, iterating over these to clean-up
+ for item := range diff {
+ or := capsulev1beta2.ObjectReferenceStatus{}
+ if err := or.ParseFromString(item); err != nil {
+ log.Error(err, "unable to parse resource to prune", "resource", item)
+
+ continue
+ }
+
+ obj := unstructured.Unstructured{}
+ obj.SetNamespace(or.Namespace)
+ obj.SetName(or.Name)
+ obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(or.APIVersion, or.Kind))
+
+ if err := r.client.Delete(ctx, &obj); err != nil {
+ if apierr.IsNotFound(err) {
+ // Object may have been already deleted, we can ignore this error
+ continue
+ }
+
+ log.Error(err, "unable to prune resource", "resource", item)
+
+ continue
+ }
+
+ log.Info("resource has been pruned", "resource", item)
+ }
+
+ return updateStatus
+}
+
+func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant, allowCrossNamespaceSelection bool, tenantLabel string, resourceIndex int, spec capsulev1beta2.ResourceSpec) ([]string, error) {
+ log := ctrllog.FromContext(ctx)
+
+ var err error
+ // Creating Namespace selector
+ var selector labels.Selector
+
+ if spec.NamespaceSelector != nil {
+ selector, err = metav1.LabelSelectorAsSelector(spec.NamespaceSelector)
+ if err != nil {
+ log.Error(err, "cannot create Namespace selector for Namespace filtering and resource replication", "index", resourceIndex)
+
+ return nil, err
+ }
+ } else {
+ selector = labels.NewSelector()
+ }
+ // Resources can be replicated only on Namespaces belonging to the same Global:
+ // preventing a boundary cross by enforcing the selection.
+ tntRequirement, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tnt.GetName()})
+ if err != nil {
+ log.Error(err, "unable to create requirement for Namespace filtering and resource replication", "index", resourceIndex)
+
+ return nil, err
+ }
+
+ selector = selector.Add(*tntRequirement)
+ // Selecting the targeted Namespace according to the TenantResource specification.
+ namespaces := corev1.NamespaceList{}
+ if err = r.client.List(ctx, &namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil {
+ log.Error(err, "cannot retrieve Namespaces for resource", "index", resourceIndex)
+
+ return nil, err
+ }
+ // Generating additional metadata
+ objAnnotations, objLabels := map[string]string{}, map[string]string{}
+
+ if spec.AdditionalMetadata != nil {
+ objAnnotations = spec.AdditionalMetadata.Annotations
+ objLabels = spec.AdditionalMetadata.Labels
+ }
+
+ objAnnotations[tenantLabel] = tnt.GetName()
+
+ objLabels[Label] = fmt.Sprintf("%d", resourceIndex)
+ objLabels[tenantLabel] = tnt.GetName()
+ // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones:
+ // these are required to perform a final pruning once the replication has been occurred.
+ processed := sets.NewString()
+
+ tntNamespaces := sets.NewString(tnt.Status.Namespaces...)
+
+ syncErr := new(multierror.Error)
+
+ codecFactory := serializer.NewCodecFactory(r.client.Scheme())
+
+ for _, ns := range namespaces.Items {
+ for nsIndex, item := range spec.NamespacedItems {
+ keysAndValues := []any{"index", nsIndex, "namespace", item.Namespace}
+ // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned
+ // Namespace: this must be blocked by checking it this is the case.
+ if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) {
+ log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Tenant", keysAndValues...)
+
+ continue
+ }
+ // Namespaced Items are relying on selecting resources, rather than specifying a specific name:
+ // creating it to get used by the client List action.
+ itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector)
+ if selectorErr != nil {
+ log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...)
+
+ syncErr = multierror.Append(syncErr, selectorErr)
+
+ continue
+ }
+
+ objs := unstructured.UnstructuredList{}
+ objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind)))
+
+ if clientErr := r.client.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil {
+ log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...)
+
+ syncErr = multierror.Append(syncErr, clientErr)
+
+ continue
+ }
+
+ multiErr := new(multierror.Group)
+ // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces:
+ // in case of error during the create or update function, this will be appended to the list of errors.
+ for _, o := range objs.Items {
+ obj := o
+ obj.SetNamespace(ns.Name)
+ obj.SetOwnerReferences(nil)
+
+ multiErr.Go(func() error {
+ kv := keysAndValues
+ kv = append(kv, "resource", fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetNamespace()))
+
+ if opErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); opErr != nil {
+ log.Error(opErr, "unable to sync namespacedItems", kv...)
+
+ return opErr
+ }
+
+ log.Info("resource has been replicated", kv...)
+
+ replicatedItem := &capsulev1beta2.ObjectReferenceStatus{}
+ replicatedItem.Name = obj.GetName()
+ replicatedItem.Kind = obj.GetKind()
+ replicatedItem.Namespace = ns.Name
+ replicatedItem.APIVersion = obj.GetAPIVersion()
+
+ processed.Insert(replicatedItem.String())
+
+ return nil
+ })
+ }
+
+ if objsErr := multiErr.Wait(); objsErr != nil {
+ syncErr = multierror.Append(syncErr, objsErr)
+ }
+ }
+
+ for rawIndex, item := range spec.RawItems {
+ template := string(item.Raw)
+
+ t := fasttemplate.New(template, "{{ ", " }}")
+
+ tmplString := t.ExecuteString(map[string]interface{}{
+ "tenant.name": tnt.Name,
+ "namespace": ns.Name,
+ })
+
+ obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex}
+
+ if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode([]byte(tmplString), nil, &obj); decodeErr != nil {
+ log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...)
+
+ syncErr = multierror.Append(syncErr, decodeErr)
+
+ continue
+ }
+
+ obj.SetNamespace(ns.Name)
+
+ if rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); rawErr != nil {
+ log.Info("unable to sync rawItem", keysAndValues...)
+ // In case of error processing an item in one of any selected Namespaces, storing it to report it lately
+ // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation.
+ syncErr = multierror.Append(syncErr, rawErr)
+ } else {
+ log.Info("resource has been replicated", keysAndValues...)
+
+ replicatedItem := &capsulev1beta2.ObjectReferenceStatus{}
+ replicatedItem.Name = obj.GetName()
+ replicatedItem.Kind = obj.GetKind()
+ replicatedItem.Namespace = ns.Name
+ replicatedItem.APIVersion = obj.GetAPIVersion()
+
+ processed.Insert(replicatedItem.String())
+ }
+ }
+ }
+
+ return processed.List(), syncErr.ErrorOrNil()
+}
+
+// createOrUpdate replicates the provided unstructured object to all the provided Namespaces:
+// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated,
+// along adding the additional metadata, if required.
+func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string) (err error) {
+ actual, desired := &unstructured.Unstructured{}, obj.DeepCopy()
+
+ actual.SetAPIVersion(desired.GetAPIVersion())
+ actual.SetKind(desired.GetKind())
+ actual.SetNamespace(desired.GetNamespace())
+ actual.SetName(desired.GetName())
+
+ _, err = controllerutil.CreateOrUpdate(ctx, r.client, actual, func() error {
+ UID := actual.GetUID()
+ rv := actual.GetResourceVersion()
+
+ actual.SetUnstructuredContent(desired.Object)
+ actual.SetLabels(labels)
+ actual.SetAnnotations(annotations)
+ actual.SetResourceVersion(rv)
+ actual.SetUID(UID)
+
+ return nil
+ })
+
+ return err
+}
diff --git a/controllers/servicelabels/abstract.go b/controllers/servicelabels/abstract.go
index 90b22884..19674a01 100644
--- a/controllers/servicelabels/abstract.go
+++ b/controllers/servicelabels/abstract.go
@@ -20,7 +20,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/utils"
)
type abstractServiceLabelsReconciler struct {
@@ -29,12 +30,6 @@ type abstractServiceLabelsReconciler struct {
log logr.Logger
}
-func (r *abstractServiceLabelsReconciler) InjectClient(c client.Client) error {
- r.client = c
-
- return nil
-}
-
func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
tenant, err := r.getTenant(ctx, request.NamespacedName, r.client)
if err != nil {
@@ -69,15 +64,15 @@ func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request
return reconcile.Result{}, err
}
-func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*capsulev1beta1.Tenant, error) {
+func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*capsulev1beta2.Tenant, error) {
ns := &corev1.Namespace{}
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := client.Get(ctx, types.NamespacedName{Name: namespacedName.Namespace}, ns); err != nil {
return nil, err
}
- capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
+ capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
if _, ok := ns.GetLabels()[capsuleLabel]; !ok {
return nil, NewNonTenantObject(namespacedName.Name)
}
@@ -116,7 +111,7 @@ func (r *abstractServiceLabelsReconciler) forOptionPerInstanceName(ctx context.C
}
func (r *abstractServiceLabelsReconciler) IsNamespaceInTenant(ctx context.Context, namespace string) bool {
- tl := &capsulev1beta1.TenantList{}
+ tl := &capsulev1beta2.TenantList{}
if err := r.client.List(ctx, tl, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", namespace),
}); err != nil {
diff --git a/controllers/servicelabels/endpoint.go b/controllers/servicelabels/endpoint.go
index d9eb80bc..26b0d182 100644
--- a/controllers/servicelabels/endpoint.go
+++ b/controllers/servicelabels/endpoint.go
@@ -19,8 +19,9 @@ type EndpointsLabelsReconciler struct {
func (r *EndpointsLabelsReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
- obj: &corev1.Endpoints{},
- log: r.Log,
+ obj: &corev1.Endpoints{},
+ client: mgr.GetClient(),
+ log: r.Log,
}
return ctrl.NewControllerManagedBy(mgr).
diff --git a/controllers/servicelabels/endpoint_slices.go b/controllers/servicelabels/endpoint_slices.go
index c73ff6cf..efcee1f7 100644
--- a/controllers/servicelabels/endpoint_slices.go
+++ b/controllers/servicelabels/endpoint_slices.go
@@ -22,18 +22,19 @@ type EndpointSlicesLabelsReconciler struct {
func (r *EndpointSlicesLabelsReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
- log: r.Log,
+ client: mgr.GetClient(),
+ log: r.Log,
}
switch {
- case r.VersionMajor == 1 && r.VersionMinor <= 16:
+ case r.VersionMajor == 1 && r.VersionMinor < 16:
r.Log.Info("Skipping controller setup, as EndpointSlices are not supported on current kubernetes version", "VersionMajor", r.VersionMajor, "VersionMinor", r.VersionMinor)
return nil
- case r.VersionMajor == 1 && r.VersionMinor >= 21:
- r.abstractServiceLabelsReconciler.obj = &discoveryv1.EndpointSlice{}
- default:
+ case r.VersionMajor == 1 && r.VersionMinor < 25:
r.abstractServiceLabelsReconciler.obj = &discoveryv1beta1.EndpointSlice{}
+ default:
+ r.abstractServiceLabelsReconciler.obj = &discoveryv1.EndpointSlice{}
}
return ctrl.NewControllerManagedBy(mgr).
diff --git a/controllers/servicelabels/service.go b/controllers/servicelabels/service.go
index 9c28d5b1..505f00f3 100644
--- a/controllers/servicelabels/service.go
+++ b/controllers/servicelabels/service.go
@@ -19,8 +19,9 @@ type ServicesLabelsReconciler struct {
func (r *ServicesLabelsReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
- obj: &corev1.Service{},
- log: r.Log,
+ obj: &corev1.Service{},
+ client: mgr.GetClient(),
+ log: r.Log,
}
return ctrl.NewControllerManagedBy(mgr).
diff --git a/api/v1alpha1/tenant_annotations.go b/controllers/tenant/annotations.go
similarity index 69%
rename from api/v1alpha1/tenant_annotations.go
rename to controllers/tenant/annotations.go
index bb3d4877..94711e92 100644
--- a/api/v1alpha1/tenant_annotations.go
+++ b/controllers/tenant/annotations.go
@@ -1,11 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1alpha1
-
-import (
- "fmt"
-)
+package tenant
const (
AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes"
@@ -15,11 +11,3 @@ const (
AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries"
AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp"
)
-
-func UsedQuotaFor(resource fmt.Stringer) string {
- return "quota.capsule.clastix.io/used-" + resource.String()
-}
-
-func HardQuotaFor(resource fmt.Stringer) string {
- return "quota.capsule.clastix.io/hard-" + resource.String()
-}
diff --git a/controllers/tenant/limitranges.go b/controllers/tenant/limitranges.go
index 8561c11f..99a03576 100644
--- a/controllers/tenant/limitranges.go
+++ b/controllers/tenant/limitranges.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package tenant
import (
@@ -10,12 +13,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/utils"
)
-// nolint:dupl
// Ensuring all the LimitRange are applied to each Namespace handled by the Tenant.
-func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta1.Tenant) error {
+func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta2.Tenant) error { //nolint:dupl
// getting requested LimitRange keys
keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items))
@@ -36,15 +39,15 @@ func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta1.Te
return group.Wait()
}
-func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta1.Tenant, namespace string, keys []string) (err error) {
+func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta2.Tenant, namespace string, keys []string) (err error) {
// getting LimitRange labels for the mutateFn
var tenantLabel, limitRangeLabel string
- if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
+ if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}
- if limitRangeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.LimitRange{}); err != nil {
+ if limitRangeLabel, err = utils.GetTypeLabel(&corev1.LimitRange{}); err != nil {
return err
}
diff --git a/controllers/tenant/manager.go b/controllers/tenant/manager.go
index fe42c948..c89c6cf9 100644
--- a/controllers/tenant/manager.go
+++ b/controllers/tenant/manager.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package tenant
import (
@@ -15,7 +18,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
type Manager struct {
@@ -27,7 +30,7 @@ type Manager struct {
func (r *Manager) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
- For(&capsulev1beta1.Tenant{}).
+ For(&capsulev1beta2.Tenant{}).
Owns(&corev1.Namespace{}).
Owns(&networkingv1.NetworkPolicy{}).
Owns(&corev1.LimitRange{}).
@@ -39,7 +42,7 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager) error {
func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ctrl.Result, err error) {
r.Log = r.Log.WithValues("Request.Name", request.Name)
// Fetch the Tenant instance
- instance := &capsulev1beta1.Tenant{}
+ instance := &capsulev1beta2.Tenant{}
if err = r.Get(ctx, request.NamespacedName, instance); err != nil {
if apierrors.IsNotFound(err) {
r.Log.Info("Request object not found, could have been deleted after reconcile request")
@@ -127,12 +130,12 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct
return ctrl.Result{}, err
}
-func (r *Manager) updateTenantStatus(ctx context.Context, tnt *capsulev1beta1.Tenant) error {
+func (r *Manager) updateTenantStatus(ctx context.Context, tnt *capsulev1beta2.Tenant) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
- if tnt.IsCordoned() {
- tnt.Status.State = capsulev1beta1.TenantStateCordoned
+ if tnt.Spec.Cordoned {
+ tnt.Status.State = capsulev1beta2.TenantStateCordoned
} else {
- tnt.Status.State = capsulev1beta1.TenantStateActive
+ tnt.Status.State = capsulev1beta2.TenantStateActive
}
return r.Client.Status().Update(ctx, tnt)
diff --git a/controllers/tenant/namespaces.go b/controllers/tenant/namespaces.go
index 73e2db38..3262511c 100644
--- a/controllers/tenant/namespaces.go
+++ b/controllers/tenant/namespaces.go
@@ -16,12 +16,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
"github.com/clastix/capsule/pkg/utils"
)
// Ensuring all annotations are applied to each Namespace handled by the Tenant.
-func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) {
+func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) {
group := new(errgroup.Group)
for _, item := range tenant.Status.Namespaces {
@@ -41,8 +42,8 @@ func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta1.Ten
return
}
-// nolint:gocognit
-func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, tnt *capsulev1beta1.Tenant) (err error) {
+//nolint:gocognit
+func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, tnt *capsulev1beta2.Tenant) (err error) {
var res controllerutil.OperationResult
err = retry.RetryOnConflict(retry.DefaultBackoff, func() (conflictErr error) {
@@ -51,7 +52,7 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
return
}
- capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
+ capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error {
annotations := make(map[string]string)
@@ -78,45 +79,45 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
if tnt.Spec.IngressOptions.AllowedClasses != nil {
if len(tnt.Spec.IngressOptions.AllowedClasses.Exact) > 0 {
- annotations[capsulev1beta1.AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",")
+ annotations[AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",")
}
if len(tnt.Spec.IngressOptions.AllowedClasses.Regex) > 0 {
- annotations[capsulev1beta1.AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex
+ annotations[AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex
}
}
if tnt.Spec.StorageClasses != nil {
if len(tnt.Spec.StorageClasses.Exact) > 0 {
- annotations[capsulev1beta1.AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",")
+ annotations[AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",")
}
if len(tnt.Spec.StorageClasses.Regex) > 0 {
- annotations[capsulev1beta1.AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex
+ annotations[AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex
}
}
if tnt.Spec.ContainerRegistries != nil {
if len(tnt.Spec.ContainerRegistries.Exact) > 0 {
- annotations[capsulev1beta1.AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",")
+ annotations[AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",")
}
if len(tnt.Spec.ContainerRegistries.Regex) > 0 {
- annotations[capsulev1beta1.AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex
+ annotations[AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex
}
}
- if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; ok {
- annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = value
+ if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok {
+ annotations[api.ForbiddenNamespaceLabelsAnnotation] = value
}
- if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
- annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = value
+ if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
+ annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = value
}
- if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; ok {
- annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = value
+ if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok {
+ annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = value
}
- if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
- annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = value
+ if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
+ annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = value
}
if ns.Annotations == nil {
@@ -146,22 +147,22 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
return err
}
-func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1beta1.Tenant) error {
+func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1beta2.Tenant) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
tenant.Status.Size = uint(len(tenant.Status.Namespaces))
- found := &capsulev1beta1.Tenant{}
+ found := &capsulev1beta2.Tenant{}
if err := r.Client.Get(ctx, types.NamespacedName{Name: tenant.GetName()}, found); err != nil {
return err
}
found.Status.Size = tenant.Status.Size
- return r.Client.Status().Update(ctx, found, &client.UpdateOptions{})
+ return r.Client.Status().Update(ctx, found, &client.SubResourceUpdateOptions{})
})
}
-func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta1.Tenant) error {
+func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta2.Tenant) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
list := &corev1.NamespaceList{}
err = r.Client.List(ctx, list, client.MatchingFieldsSelector{
@@ -175,7 +176,7 @@ func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta1.
_, err = controllerutil.CreateOrUpdate(ctx, r.Client, tenant.DeepCopy(), func() error {
tenant.AssignNamespaces(list.Items)
- return r.Client.Status().Update(ctx, tenant, &client.UpdateOptions{})
+ return r.Client.Status().Update(ctx, tenant, &client.SubResourceUpdateOptions{})
})
return
diff --git a/controllers/tenant/networkpolicies.go b/controllers/tenant/networkpolicies.go
index 34d35226..914897ee 100644
--- a/controllers/tenant/networkpolicies.go
+++ b/controllers/tenant/networkpolicies.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package tenant
import (
@@ -10,12 +13,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/utils"
)
-// nolint:dupl
// Ensuring all the NetworkPolicies are applied to each Namespace handled by the Tenant.
-func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta1.Tenant) error {
+func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta2.Tenant) error { //nolint:dupl
// getting requested NetworkPolicy keys
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items))
@@ -36,18 +39,18 @@ func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta
return group.Wait()
}
-func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta1.Tenant, namespace string, keys []string) (err error) {
+func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta2.Tenant, namespace string, keys []string) (err error) {
if err = r.pruningResources(ctx, namespace, keys, &networkingv1.NetworkPolicy{}); err != nil {
return err
}
// getting NetworkPolicy labels for the mutateFn
var tenantLabel, networkPolicyLabel string
- if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
+ if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}
- if networkPolicyLabel, err = capsulev1beta1.GetTypeLabel(&networkingv1.NetworkPolicy{}); err != nil {
+ if networkPolicyLabel, err = utils.GetTypeLabel(&networkingv1.NetworkPolicy{}); err != nil {
return err
}
diff --git a/controllers/tenant/resourcequotas.go b/controllers/tenant/resourcequotas.go
index 88793529..23886322 100644
--- a/controllers/tenant/resourcequotas.go
+++ b/controllers/tenant/resourcequotas.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package tenant
import (
@@ -16,7 +19,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
// When the Resource Budget assigned to a Tenant is Tenant-scoped we have to rely on the ResourceQuota resources to
@@ -31,20 +36,19 @@ import (
// the mutateFn along with the CreateOrUpdate to don't perform the update since resources are identical.
//
// In case of Namespace-scoped Resource Budget, we're just replicating the resources across all registered Namespaces.
-// nolint:gocognit
-func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) {
+func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) { //nolint:gocognit
// getting ResourceQuota labels for the mutateFn
var tenantLabel, typeLabel string
- if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
+ if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}
- if typeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.ResourceQuota{}); err != nil {
+ if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil {
return err
}
- // nolint:nestif
- if tenant.Spec.ResourceQuota.Scope == capsulev1beta1.ResourceQuotaScopeTenant {
+ //nolint:nestif
+ if tenant.Spec.ResourceQuota.Scope == api.ResourceQuotaScopeTenant {
group := new(errgroup.Group)
for i, q := range tenant.Spec.ResourceQuota.Items {
@@ -153,15 +157,15 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta1
return group.Wait()
}
-func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta1.Tenant, namespace string, keys []string) (err error) {
+func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta2.Tenant, namespace string, keys []string) (err error) {
// getting ResourceQuota labels for the mutateFn
var tenantLabel, typeLabel string
- if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
+ if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}
- if typeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.ResourceQuota{}); err != nil {
+ if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil {
return err
}
// Pruning resource of non-requested resources
@@ -188,7 +192,7 @@ func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta1.
target.Spec.Scopes = resQuota.Scopes
target.Spec.ScopeSelector = resQuota.ScopeSelector
// In case of Namespace scope for the ResourceQuota we can easily apply the bare specification
- if tenant.Spec.ResourceQuota.Scope == capsulev1beta1.ResourceQuotaScopeNamespace {
+ if tenant.Spec.ResourceQuota.Scope == api.ResourceQuotaScopeNamespace {
target.Spec.Hard = resQuota.Hard
}
@@ -233,8 +237,8 @@ func (r *Manager) resourceQuotasUpdate(ctx context.Context, resourceName corev1.
found.Annotations = make(map[string]string)
}
found.Labels = rq.Labels
- found.Annotations[capsulev1beta1.UsedQuotaFor(resourceName)] = actual.String()
- found.Annotations[capsulev1beta1.HardQuotaFor(resourceName)] = limit.String()
+ found.Annotations[capsulev1beta2.UsedQuotaFor(resourceName)] = actual.String()
+ found.Annotations[capsulev1beta2.HardQuotaFor(resourceName)] = limit.String()
// Updating the Resource according to the actual.Cmp result
found.Spec.Hard = rq.Spec.Hard
diff --git a/controllers/tenant/resourcequotas_quota.go b/controllers/tenant/resourcequotas_quota.go
index e0f4151d..55e42749 100644
--- a/controllers/tenant/resourcequotas_quota.go
+++ b/controllers/tenant/resourcequotas_quota.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package tenant
import (
@@ -13,20 +16,20 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/util/retry"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
-func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *capsulev1beta1.Tenant) error {
+func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *capsulev1beta2.Tenant) error {
type resource struct {
kind string
group string
version string
}
- // nolint:prealloc
+ //nolint:prealloc
var resourceList []resource
for k := range tenant.GetAnnotations() {
- if !strings.HasPrefix(k, capsulev1beta1.ResourceQuotaAnnotationPrefix) {
+ if !strings.HasPrefix(k, capsulev1beta2.ResourceQuotaAnnotationPrefix) {
continue
}
@@ -66,7 +69,7 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap
defer func() {
for gvk, used := range usedMap {
err := retry.RetryOnConflict(retry.DefaultBackoff, func() (retryErr error) {
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
if retryErr = r.Client.Get(ctx, types.NamespacedName{Name: tenant.GetName()}, tnt); retryErr != nil {
return
}
@@ -75,7 +78,7 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap
tnt.Annotations = make(map[string]string)
}
- tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(gvk)] = fmt.Sprintf("%d", used)
+ tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(gvk)] = fmt.Sprintf("%d", used)
return r.Client.Update(ctx, tnt)
})
diff --git a/controllers/tenant/rolebindings.go b/controllers/tenant/rolebindings.go
index 96682047..236bfec7 100644
--- a/controllers/tenant/rolebindings.go
+++ b/controllers/tenant/rolebindings.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package tenant
import (
@@ -11,12 +14,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
// ownerClusterRoleBindings generates a Capsule AdditionalRoleBinding object for the Owner dynamic clusterrole in order
// to take advantage of the additional role binding feature.
-func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clusterRole string) capsulev1beta1.AdditionalRoleBindingsSpec {
+func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta2.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec {
var subject rbacv1.Subject
if owner.Kind == "ServiceAccount" {
@@ -35,7 +40,7 @@ func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clust
}
}
- return capsulev1beta1.AdditionalRoleBindingsSpec{
+ return api.AdditionalRoleBindingsSpec{
ClusterRoleName: clusterRole,
Subjects: []rbacv1.Subject{
subject,
@@ -45,9 +50,9 @@ func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clust
// Sync the dynamic Tenant Owner specific cluster-roles and additional Role Bindings, which can be used in many ways:
// applying Pod Security Policies or giving access to CRDs or specific API groups.
-func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) {
+func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) {
// hashing the RoleBinding name due to DNS RFC-1123 applied to Kubernetes labels
- hashFn := func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string {
+ hashFn := func(binding api.AdditionalRoleBindingsSpec) string {
h := fnv.New64a()
_, _ = h.Write([]byte(binding.ClusterRoleName))
@@ -61,8 +66,8 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.T
// getting requested Role Binding keys
keys := make([]string, 0, len(tenant.Spec.Owners))
// Generating for dynamic tenant owners cluster roles
- for index, owner := range tenant.Spec.Owners {
- for _, clusterRoleName := range owner.GetRoles(*tenant, index) {
+ for _, owner := range tenant.Spec.Owners {
+ for _, clusterRoleName := range owner.ClusterRoles {
cr := r.ownerClusterRoleBindings(owner, clusterRoleName)
keys = append(keys, hashFn(cr))
@@ -86,14 +91,14 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.T
return group.Wait()
}
-func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta1.Tenant, ns string, keys []string, hashFn func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string) (err error) {
+func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta2.Tenant, ns string, keys []string, hashFn func(binding api.AdditionalRoleBindingsSpec) string) (err error) {
var tenantLabel, roleBindingLabel string
- if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
+ if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return
}
- if roleBindingLabel, err = capsulev1beta1.GetTypeLabel(&rbacv1.RoleBinding{}); err != nil {
+ if roleBindingLabel, err = utils.GetTypeLabel(&rbacv1.RoleBinding{}); err != nil {
return
}
@@ -101,10 +106,10 @@ func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsule
return
}
- var roleBindings []capsulev1beta1.AdditionalRoleBindingsSpec
+ var roleBindings []api.AdditionalRoleBindingsSpec
- for index, owner := range tenant.Spec.Owners {
- for _, clusterRoleName := range owner.GetRoles(*tenant, index) {
+ for _, owner := range tenant.Spec.Owners {
+ for _, clusterRoleName := range owner.ClusterRoles {
roleBindings = append(roleBindings, r.ownerClusterRoleBindings(owner, clusterRoleName))
}
}
diff --git a/controllers/tenant/utils.go b/controllers/tenant/utils.go
index 00b47518..5adf7ac4 100644
--- a/controllers/tenant/utils.go
+++ b/controllers/tenant/utils.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package tenant
import (
@@ -11,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/pkg/utils"
)
// pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or
@@ -19,7 +22,7 @@ import (
func (r *Manager) pruningResources(ctx context.Context, ns string, keys []string, obj client.Object) (err error) {
var capsuleLabel string
- if capsuleLabel, err = capsulev1beta1.GetTypeLabel(obj); err != nil {
+ if capsuleLabel, err = capsulev1beta2.GetTypeLabel(obj); err != nil {
return
}
diff --git a/controllers/tls/manager.go b/controllers/tls/manager.go
index d67e3a61..f788e360 100644
--- a/controllers/tls/manager.go
+++ b/controllers/tls/manager.go
@@ -27,7 +27,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
- "sigs.k8s.io/controller-runtime/pkg/source"
"github.com/clastix/capsule/controllers/utils"
"github.com/clastix/capsule/pkg/cert"
@@ -49,7 +48,7 @@ type Reconciler struct {
}
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
- enqueueFn := handler.EnqueueRequestsFromMapFunc(func(client.Object) []reconcile.Request {
+ enqueueFn := handler.EnqueueRequestsFromMapFunc(func(context.Context, client.Object) []reconcile.Request {
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
@@ -62,13 +61,13 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Secret{}, utils.NamesMatchingPredicate(r.Configuration.TLSSecretName())).
- Watches(source.NewKindWithCache(&admissionregistrationv1.ValidatingWebhookConfiguration{}, mgr.GetCache()), enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ Watches(&admissionregistrationv1.ValidatingWebhookConfiguration{}, enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == r.Configuration.ValidatingWebhookConfigurationName()
}))).
- Watches(source.NewKindWithCache(&admissionregistrationv1.MutatingWebhookConfiguration{}, mgr.GetCache()), enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ Watches(&admissionregistrationv1.MutatingWebhookConfiguration{}, enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == r.Configuration.MutatingWebhookConfigurationName()
}))).
- Watches(source.NewKindWithCache(&apiextensionsv1.CustomResourceDefinition{}, mgr.GetCache()), enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
+ Watches(&apiextensionsv1.CustomResourceDefinition{}, enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == r.Configuration.TenantCRDName()
}))).
Complete(r)
@@ -132,7 +131,10 @@ func (r Reconciler) ReconcileCertificates(ctx context.Context, certSecret *corev
return r.updateValidatingWebhookConfiguration(ctx, caBundle)
})
group.Go(func() error {
- return r.updateCustomResourceDefinition(ctx, caBundle)
+ return r.updateTenantCustomResourceDefinition(ctx, "tenants.capsule.clastix.io", caBundle)
+ })
+ group.Go(func() error {
+ return r.updateTenantCustomResourceDefinition(ctx, "capsuleconfigurations.capsule.clastix.io", caBundle)
})
operatorPods, err := r.getOperatorPods(ctx)
@@ -214,10 +216,10 @@ func (r Reconciler) shouldUpdateCertificate(secret *corev1.Secret) bool {
// By default helm doesn't allow to use templates in CRD (https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you).
// In order to overcome this, we are setting conversion strategy in helm chart to None, and then update it with CA and namespace information.
-func (r *Reconciler) updateCustomResourceDefinition(ctx context.Context, caBundle []byte) error {
+func (r *Reconciler) updateTenantCustomResourceDefinition(ctx context.Context, name string, caBundle []byte) error {
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
crd := &apiextensionsv1.CustomResourceDefinition{}
- err = r.Get(ctx, types.NamespacedName{Name: "tenants.capsule.clastix.io"}, crd)
+ err = r.Get(ctx, types.NamespacedName{Name: name}, crd)
if err != nil {
r.Log.Error(err, "cannot retrieve CustomResourceDefinition")
@@ -232,12 +234,12 @@ func (r *Reconciler) updateCustomResourceDefinition(ctx context.Context, caBundl
Service: &apiextensionsv1.ServiceReference{
Namespace: r.Namespace,
Name: "capsule-webhook-service",
- Path: pointer.StringPtr("/convert"),
- Port: pointer.Int32Ptr(443),
+ Path: pointer.String("/convert"),
+ Port: pointer.Int32(443),
},
CABundle: caBundle,
},
- ConversionReviewVersions: []string{"v1alpha1", "v1beta1"},
+ ConversionReviewVersions: []string{"v1alpha1", "v1beta1", "v1beta2"},
},
}
diff --git a/controllers/utils/name_matching.go b/controllers/utils/name_matching.go
index 7b1c540c..ddc3dbdf 100644
--- a/controllers/utils/name_matching.go
+++ b/controllers/utils/name_matching.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package utils
import (
diff --git a/docs/content/contributing/development.md b/docs/content/contributing/development.md
index 0f0c83b2..c0a9c926 100644
--- a/docs/content/contributing/development.md
+++ b/docs/content/contributing/development.md
@@ -4,7 +4,7 @@
Make sure you have these tools installed:
-- [Go 1.18+](https://golang.org/dl/)
+- [Go 1.19+](https://golang.org/dl/)
- [Operator SDK 1.7.2+](https://github.com/operator-framework/operator-sdk), or [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)
- [KinD](https://github.com/kubernetes-sigs/kind) or [k3d](https://k3d.io/), with `kubectl`
- [ngrok](https://ngrok.com/) (if you want to run locally with remote Kubernetes)
@@ -152,7 +152,7 @@ $ kubectl -n capsule-system logs --all-containers -l control-plane=controller-ma
# You may have a try to deploy a Tenant too to make sure it works end to end
$ kubectl apply -f - <
+ table {
+ border: solid;
+ padding: 15px;
+ text-align: left;
+ }
+ th, td {
+ border-bottom: 1px solid #ddd;
+ padding: 10px;
+ border: solid;
+ }
+ tr:hover {background-color: coral;}
+ sup {
+ font-size: 15px;
+ }
+
+
+# API Reference
+
+Packages:
+
+- [capsule.clastix.io/v1alpha1](#capsuleclastixiov1alpha1)
+- [capsule.clastix.io/v1beta2](#capsuleclastixiov1beta2)
+- [capsule.clastix.io/v1beta1](#capsuleclastixiov1beta1)
+
+# capsule.clastix.io/v1alpha1
+
+Resource Types:
+
+- [CapsuleConfiguration](#capsuleconfiguration)
+
+- [Tenant](#tenant)
+
+
+
+
+## CapsuleConfiguration
+
+
+
+
+
+
+CapsuleConfiguration is the Schema for the Capsule configuration API.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ apiVersion |
+ string |
+ capsule.clastix.io/v1alpha1 |
+ true |
+
+
+ kind |
+ string |
+ CapsuleConfiguration |
+ true |
+
+
+ metadata |
+ object |
+ Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
+ true |
+
+ spec |
+ object |
+
+ CapsuleConfigurationSpec defines the Capsule configuration.
+ |
+ false |
+
+
+
+
+### CapsuleConfiguration.spec
+
+
+
+CapsuleConfigurationSpec defines the Capsule configuration.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ forceTenantPrefix |
+ boolean |
+
+ 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.
+
+ Default: false
+ |
+ false |
+
+ protectedNamespaceRegex |
+ string |
+
+ Disallow creation of namespaces, whose name matches this regexp
+ |
+ false |
+
+ userGroups |
+ []string |
+
+ Names of the groups for Capsule users.
+
+ Default: [capsule.clastix.io]
+ |
+ false |
+
+
+
+## Tenant
+
+
+
+
+
+
+Tenant is the Schema for the tenants API.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ apiVersion |
+ string |
+ capsule.clastix.io/v1alpha1 |
+ true |
+
+
+ kind |
+ string |
+ Tenant |
+ true |
+
+
+ metadata |
+ object |
+ Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
+ true |
+
+ spec |
+ object |
+
+ TenantSpec defines the desired state of Tenant.
+ |
+ false |
+
+ status |
+ object |
+
+ TenantStatus defines the observed state of Tenant.
+ |
+ false |
+
+
+
+
+### Tenant.spec
+
+
+
+TenantSpec defines the desired state of Tenant.
+
+
+
+
+### Tenant.spec.owner
+
+
+
+OwnerSpec defines tenant owner name and kind.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ enum |
+
+
+
+ Enum: User, Group
+ |
+ true |
+
+ name |
+ string |
+
+
+ |
+ true |
+
+
+
+
+### Tenant.spec.additionalRoleBindings[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ clusterRoleName |
+ string |
+
+
+ |
+ true |
+
+ subjects |
+ []object |
+
+ kubebuilder:validation:Minimum=1
+ |
+ true |
+
+
+
+
+### Tenant.spec.additionalRoleBindings[index].subjects[index]
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ string |
+
+ 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.
+ |
+ true |
+
+ name |
+ string |
+
+ Name of the object being referenced.
+ |
+ true |
+
+ apiGroup |
+ string |
+
+ 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.
+ |
+ false |
+
+ namespace |
+ string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.containerRegistries
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.externalServiceIPs
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ true |
+
+
+
+
+### Tenant.spec.ingressClasses
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressHostnames
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.limitRanges[index]
+
+
+
+LimitRangeSpec defines a min/max usage limit for resources that match on kind.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ limits |
+ []object |
+
+ Limits is the list of LimitRangeItem objects that are enforced.
+ |
+ true |
+
+
+
+
+### Tenant.spec.limitRanges[index].limits[index]
+
+
+
+LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ type |
+ string |
+
+ Type of resource that this limit applies to.
+ |
+ true |
+
+ default |
+ map[string]int or string |
+
+ Default resource requirement limit value by resource name if resource limit is omitted.
+ |
+ false |
+
+ defaultRequest |
+ map[string]int or string |
+
+ DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
+ |
+ false |
+
+ max |
+ map[string]int or string |
+
+ Max usage constraints on this kind by resource name.
+ |
+ false |
+
+ maxLimitRequestRatio |
+ map[string]int or string |
+
+ 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.
+ |
+ false |
+
+ min |
+ map[string]int or string |
+
+ Min usage constraints on this kind by resource name.
+ |
+ false |
+
+
+
+
+### Tenant.spec.namespacesMetadata
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalAnnotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ additionalLabels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index]
+
+
+
+NetworkPolicySpec provides the specification of a NetworkPolicy
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ podSelector |
+ object |
+
+ podSelector 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.
+ |
+ true |
+
+ egress |
+ []object |
+
+ egress is a 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
+ |
+ false |
+
+ ingress |
+ []object |
+
+ ingress is a 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)
+ |
+ false |
+
+ policyTypes |
+ []string |
+
+ policyTypes is a 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
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].podSelector
+
+
+
+podSelector 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index]
+
+
+
+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
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ports |
+ []object |
+
+ ports is a 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.
+ |
+ false |
+
+ to |
+ []object |
+
+ to is a 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index].ports[index]
+
+
+
+NetworkPolicyPort describes a port to allow traffic on
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ endPort |
+ integer |
+
+ endPort indicates that the range of ports from port to endPort if set, 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.
+
+ Format: int32
+ |
+ false |
+
+ port |
+ int or string |
+
+ port represents 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.
+ |
+ false |
+
+ protocol |
+ string |
+
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index].to[index]
+
+
+
+NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ipBlock |
+ object |
+
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+ |
+ false |
+
+ podSelector |
+ object |
+
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index].to[index].ipBlock
+
+
+
+ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ cidr |
+ string |
+
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ |
+ true |
+
+ except |
+ []string |
+
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index].to[index].namespaceSelector
+
+
+
+namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index].to[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index].to[index].podSelector
+
+
+
+podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].egress[index].to[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index]
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ from |
+ []object |
+
+ from is a 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.
+ |
+ false |
+
+ ports |
+ []object |
+
+ ports is a 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index].from[index]
+
+
+
+NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ipBlock |
+ object |
+
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+ |
+ false |
+
+ podSelector |
+ object |
+
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index].from[index].ipBlock
+
+
+
+ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ cidr |
+ string |
+
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ |
+ true |
+
+ except |
+ []string |
+
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index].from[index].namespaceSelector
+
+
+
+namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index].from[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index].from[index].podSelector
+
+
+
+podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index].from[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies[index].ingress[index].ports[index]
+
+
+
+NetworkPolicyPort describes a port to allow traffic on
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ endPort |
+ integer |
+
+ endPort indicates that the range of ports from port to endPort if set, 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.
+
+ Format: int32
+ |
+ false |
+
+ port |
+ int or string |
+
+ port represents 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.
+ |
+ false |
+
+ protocol |
+ string |
+
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas[index]
+
+
+
+ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ hard |
+ map[string]int or string |
+
+ hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/
+ |
+ false |
+
+ scopeSelector |
+ object |
+
+ 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.
+ |
+ false |
+
+ scopes |
+ []string |
+
+ A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas[index].scopeSelector
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ A list of scope selector requirements by scope of the resources.
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas[index].scopeSelector.matchExpressions[index]
+
+
+
+A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ operator |
+ string |
+
+ Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
+ |
+ true |
+
+ scopeName |
+ string |
+
+ The name of the scope that the selector applies to.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.servicesMetadata
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalAnnotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ additionalLabels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.storageClasses
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.status
+
+
+
+TenantStatus defines the observed state of Tenant.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ size |
+ integer |
+
+
+ |
+ true |
+
+ namespaces |
+ []string |
+
+
+ |
+ false |
+
+
+
+# capsule.clastix.io/v1beta2
+
+Resource Types:
+
+- [CapsuleConfiguration](#capsuleconfiguration)
+
+- [GlobalTenantResource](#globaltenantresource)
+
+- [TenantResource](#tenantresource)
+
+- [Tenant](#tenant)
+
+
+
+
+## CapsuleConfiguration
+
+
+
+
+
+
+CapsuleConfiguration is the Schema for the Capsule configuration API.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ apiVersion |
+ string |
+ capsule.clastix.io/v1beta2 |
+ true |
+
+
+ kind |
+ string |
+ CapsuleConfiguration |
+ true |
+
+
+ metadata |
+ object |
+ Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
+ true |
+
+ spec |
+ object |
+
+ CapsuleConfigurationSpec defines the Capsule configuration.
+ |
+ false |
+
+
+
+
+### CapsuleConfiguration.spec
+
+
+
+CapsuleConfigurationSpec defines the Capsule configuration.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ enableTLSReconciler |
+ boolean |
+
+ 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.
+
+ Default: true
+ |
+ true |
+
+ forceTenantPrefix |
+ boolean |
+
+ 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.
+
+ Default: false
+ |
+ false |
+
+ nodeMetadata |
+ object |
+
+ 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.
+ |
+ false |
+
+ overrides |
+ object |
+
+ Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.
+
+ Default: map[TLSSecretName:capsule-tls mutatingWebhookConfigurationName:capsule-mutating-webhook-configuration validatingWebhookConfigurationName:capsule-validating-webhook-configuration]
+ |
+ false |
+
+ protectedNamespaceRegex |
+ string |
+
+ Disallow creation of namespaces, whose name matches this regexp
+ |
+ false |
+
+ userGroups |
+ []string |
+
+ Names of the groups for Capsule users.
+
+ Default: [capsule.clastix.io]
+ |
+ false |
+
+
+
+
+### CapsuleConfiguration.spec.nodeMetadata
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ forbiddenAnnotations |
+ object |
+
+ Define the annotations that a Tenant Owner cannot set for their nodes.
+ |
+ true |
+
+ forbiddenLabels |
+ object |
+
+ Define the labels that a Tenant Owner cannot set for their nodes.
+ |
+ true |
+
+
+
+
+### CapsuleConfiguration.spec.nodeMetadata.forbiddenAnnotations
+
+
+
+Define the annotations that a Tenant Owner cannot set for their nodes.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ denied |
+ []string |
+
+
+ |
+ false |
+
+ deniedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### CapsuleConfiguration.spec.nodeMetadata.forbiddenLabels
+
+
+
+Define the labels that a Tenant Owner cannot set for their nodes.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ denied |
+ []string |
+
+
+ |
+ false |
+
+ deniedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### CapsuleConfiguration.spec.overrides
+
+
+
+Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ TLSSecretName |
+ string |
+
+ Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed.
+
+ Default: capsule-tls
+ |
+ true |
+
+ mutatingWebhookConfigurationName |
+ string |
+
+ Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
+
+ Default: capsule-mutating-webhook-configuration
+ |
+ true |
+
+ validatingWebhookConfigurationName |
+ string |
+
+ Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
+
+ Default: capsule-validating-webhook-configuration
+ |
+ true |
+
+
+
+## GlobalTenantResource
+
+
+
+
+
+
+GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ apiVersion |
+ string |
+ capsule.clastix.io/v1beta2 |
+ true |
+
+
+ kind |
+ string |
+ GlobalTenantResource |
+ true |
+
+
+ metadata |
+ object |
+ Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
+ true |
+
+ spec |
+ object |
+
+ GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
+ |
+ false |
+
+ status |
+ object |
+
+ GlobalTenantResourceStatus defines the observed state of GlobalTenantResource.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec
+
+
+
+GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ resources |
+ []object |
+
+ Defines the rules to select targeting Namespace, along with the objects that must be replicated.
+ |
+ true |
+
+ resyncPeriod |
+ string |
+
+ 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.
+
+ Default: 60s
+ |
+ true |
+
+ pruningOnDelete |
+ boolean |
+
+ 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.
+
+ Default: true
+ |
+ false |
+
+ tenantSelector |
+ object |
+
+ Defines the Tenant selector used target the tenants on which resources must be propagated.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.resources[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalMetadata |
+ object |
+
+ Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ 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.
+ |
+ false |
+
+ namespacedItems |
+ []object |
+
+ List of the resources already existing in other Namespaces that must be replicated.
+ |
+ false |
+
+ rawItems |
+ []RawExtension |
+
+ List of raw resources that must be replicated.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.resources[index].additionalMetadata
+
+
+
+Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ annotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ labels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.resources[index].namespaceSelector
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.resources[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.resources[index].namespacedItems[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ string |
+
+ Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ |
+ true |
+
+ namespace |
+ string |
+
+ Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+ |
+ true |
+
+ selector |
+ object |
+
+ Label selector used to select the given resources in the given Namespace.
+ |
+ true |
+
+ apiVersion |
+ string |
+
+ API version of the referent.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.resources[index].namespacedItems[index].selector
+
+
+
+Label selector used to select the given resources in the given Namespace.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.resources[index].namespacedItems[index].selector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.tenantSelector
+
+
+
+Defines the Tenant selector used target the tenants on which resources must be propagated.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.spec.tenantSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### GlobalTenantResource.status
+
+
+
+GlobalTenantResourceStatus defines the observed state of GlobalTenantResource.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ processedItems |
+ []object |
+
+ List of the replicated resources for the given TenantResource.
+ |
+ true |
+
+ selectedTenants |
+ []string |
+
+ List of Tenants addressed by the GlobalTenantResource.
+ |
+ true |
+
+
+
+
+### GlobalTenantResource.status.processedItems[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ string |
+
+ Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ |
+ true |
+
+ name |
+ string |
+
+ Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ |
+ true |
+
+ namespace |
+ string |
+
+ Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+ |
+ true |
+
+ apiVersion |
+ string |
+
+ API version of the referent.
+ |
+ false |
+
+
+
+## TenantResource
+
+
+
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ apiVersion |
+ string |
+ capsule.clastix.io/v1beta2 |
+ true |
+
+
+ kind |
+ string |
+ TenantResource |
+ true |
+
+
+ metadata |
+ object |
+ Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
+ true |
+
+ spec |
+ object |
+
+ TenantResourceSpec defines the desired state of TenantResource.
+ |
+ false |
+
+ status |
+ object |
+
+ TenantResourceStatus defines the observed state of TenantResource.
+ |
+ false |
+
+
+
+
+### TenantResource.spec
+
+
+
+TenantResourceSpec defines the desired state of TenantResource.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ resources |
+ []object |
+
+ Defines the rules to select targeting Namespace, along with the objects that must be replicated.
+ |
+ true |
+
+ resyncPeriod |
+ string |
+
+ 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.
+
+ Default: 60s
+ |
+ true |
+
+ pruningOnDelete |
+ boolean |
+
+ 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.
+
+ Default: true
+ |
+ false |
+
+
+
+
+### TenantResource.spec.resources[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalMetadata |
+ object |
+
+ Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ 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.
+ |
+ false |
+
+ namespacedItems |
+ []object |
+
+ List of the resources already existing in other Namespaces that must be replicated.
+ |
+ false |
+
+ rawItems |
+ []RawExtension |
+
+ List of raw resources that must be replicated.
+ |
+ false |
+
+
+
+
+### TenantResource.spec.resources[index].additionalMetadata
+
+
+
+Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ annotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ labels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### TenantResource.spec.resources[index].namespaceSelector
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### TenantResource.spec.resources[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### TenantResource.spec.resources[index].namespacedItems[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ string |
+
+ Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ |
+ true |
+
+ namespace |
+ string |
+
+ Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+ |
+ true |
+
+ selector |
+ object |
+
+ Label selector used to select the given resources in the given Namespace.
+ |
+ true |
+
+ apiVersion |
+ string |
+
+ API version of the referent.
+ |
+ false |
+
+
+
+
+### TenantResource.spec.resources[index].namespacedItems[index].selector
+
+
+
+Label selector used to select the given resources in the given Namespace.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### TenantResource.spec.resources[index].namespacedItems[index].selector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### TenantResource.status
+
+
+
+TenantResourceStatus defines the observed state of TenantResource.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ processedItems |
+ []object |
+
+ List of the replicated resources for the given TenantResource.
+ |
+ true |
+
+
+
+
+### TenantResource.status.processedItems[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ string |
+
+ Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ |
+ true |
+
+ name |
+ string |
+
+ Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ |
+ true |
+
+ namespace |
+ string |
+
+ Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+ |
+ true |
+
+ apiVersion |
+ string |
+
+ API version of the referent.
+ |
+ false |
+
+
+
+## Tenant
+
+
+
+
+
+
+Tenant is the Schema for the tenants API.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ apiVersion |
+ string |
+ capsule.clastix.io/v1beta2 |
+ true |
+
+
+ kind |
+ string |
+ Tenant |
+ true |
+
+
+ metadata |
+ object |
+ Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
+ true |
+
+ spec |
+ object |
+
+ TenantSpec defines the desired state of Tenant.
+ |
+ false |
+
+ status |
+ object |
+
+ Returns the observed state of the Tenant.
+ |
+ false |
+
+
+
+
+### Tenant.spec
+
+
+
+TenantSpec defines the desired state of Tenant.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ owners |
+ []object |
+
+ Specifies the owners of the Tenant. Mandatory.
+ |
+ true |
+
+ additionalRoleBindings |
+ []object |
+
+ 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.
+ |
+ false |
+
+ containerRegistries |
+ object |
+
+ 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.
+ |
+ false |
+
+ cordoned |
+ boolean |
+
+ Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
+ |
+ false |
+
+ imagePullPolicies |
+ []enum |
+
+ 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.
+ |
+ false |
+
+ ingressOptions |
+ object |
+
+ Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+ |
+ false |
+
+ limitRanges |
+ object |
+
+ Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+ |
+ false |
+
+ namespaceOptions |
+ object |
+
+ 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.
+ |
+ false |
+
+ networkPolicies |
+ object |
+
+ Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+ |
+ false |
+
+ nodeSelector |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+ preventDeletion |
+ boolean |
+
+ Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
+ |
+ false |
+
+ priorityClasses |
+ object |
+
+ 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.
+ |
+ false |
+
+ resourceQuotas |
+ object |
+
+ 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.
+ |
+ false |
+
+ runtimeClasses |
+ object |
+
+ 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.
+ |
+ false |
+
+ serviceOptions |
+ object |
+
+ Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+ |
+ false |
+
+ storageClasses |
+ object |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.owners[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ enum |
+
+ Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
+
+ Enum: User, Group, ServiceAccount
+ |
+ true |
+
+ name |
+ string |
+
+ Name of tenant owner.
+ |
+ true |
+
+ clusterRoles |
+ []string |
+
+ Defines additional cluster-roles for the specific Owner.
+
+ Default: [admin capsule-namespace-deleter]
+ |
+ false |
+
+ proxySettings |
+ []object |
+
+ Proxy settings for tenant owner.
+ |
+ false |
+
+
+
+
+### Tenant.spec.owners[index].proxySettings[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ enum |
+
+
+
+ Enum: Nodes, StorageClasses, IngressClasses, PriorityClasses, RuntimeClasses, PersistentVolumes
+ |
+ true |
+
+ operations |
+ []enum |
+
+
+ |
+ true |
+
+
+
+
+### Tenant.spec.additionalRoleBindings[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ clusterRoleName |
+ string |
+
+
+ |
+ true |
+
+ subjects |
+ []object |
+
+ kubebuilder:validation:Minimum=1
+ |
+ true |
+
+
+
+
+### Tenant.spec.additionalRoleBindings[index].subjects[index]
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ string |
+
+ 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.
+ |
+ true |
+
+ name |
+ string |
+
+ Name of the object being referenced.
+ |
+ true |
+
+ apiGroup |
+ string |
+
+ 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.
+ |
+ false |
+
+ namespace |
+ string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.containerRegistries
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressOptions
+
+
+
+Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowWildcardHostnames |
+ boolean |
+
+ Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.
+ |
+ false |
+
+ allowedClasses |
+ object |
+
+ 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.
+ |
+ false |
+
+ allowedHostnames |
+ object |
+
+ 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.
+ |
+ false |
+
+ hostnameCollisionScope |
+ enum |
+
+ 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.
+
+ Enum: Cluster, Tenant, Namespace, Disabled
+ Default: Disabled
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressOptions.allowedClasses
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+ default |
+ string |
+
+
+ |
+ false |
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressOptions.allowedClasses.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressOptions.allowedHostnames
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.limitRanges
+
+
+
+Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ items |
+ []object |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.limitRanges.items[index]
+
+
+
+LimitRangeSpec defines a min/max usage limit for resources that match on kind.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ limits |
+ []object |
+
+ Limits is the list of LimitRangeItem objects that are enforced.
+ |
+ true |
+
+
+
+
+### Tenant.spec.limitRanges.items[index].limits[index]
+
+
+
+LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ type |
+ string |
+
+ Type of resource that this limit applies to.
+ |
+ true |
+
+ default |
+ map[string]int or string |
+
+ Default resource requirement limit value by resource name if resource limit is omitted.
+ |
+ false |
+
+ defaultRequest |
+ map[string]int or string |
+
+ DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
+ |
+ false |
+
+ max |
+ map[string]int or string |
+
+ Max usage constraints on this kind by resource name.
+ |
+ false |
+
+ maxLimitRequestRatio |
+ map[string]int or string |
+
+ 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.
+ |
+ false |
+
+ min |
+ map[string]int or string |
+
+ Min usage constraints on this kind by resource name.
+ |
+ false |
+
+
+
+
+### Tenant.spec.namespaceOptions
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalMetadata |
+ object |
+
+ Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
+ |
+ false |
+
+ forbiddenAnnotations |
+ object |
+
+ Define the annotations that a Tenant Owner cannot set for their Namespace resources.
+ |
+ false |
+
+ forbiddenLabels |
+ object |
+
+ Define the labels that a Tenant Owner cannot set for their Namespace resources.
+ |
+ false |
+
+ quota |
+ integer |
+
+ 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
+ |
+ false |
+
+
+
+
+### Tenant.spec.namespaceOptions.additionalMetadata
+
+
+
+Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ annotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ labels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.namespaceOptions.forbiddenAnnotations
+
+
+
+Define the annotations that a Tenant Owner cannot set for their Namespace resources.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ denied |
+ []string |
+
+
+ |
+ false |
+
+ deniedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.namespaceOptions.forbiddenLabels
+
+
+
+Define the labels that a Tenant Owner cannot set for their Namespace resources.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ denied |
+ []string |
+
+
+ |
+ false |
+
+ deniedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies
+
+
+
+Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ items |
+ []object |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index]
+
+
+
+NetworkPolicySpec provides the specification of a NetworkPolicy
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ podSelector |
+ object |
+
+ podSelector 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.
+ |
+ true |
+
+ egress |
+ []object |
+
+ egress is a 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
+ |
+ false |
+
+ ingress |
+ []object |
+
+ ingress is a 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)
+ |
+ false |
+
+ policyTypes |
+ []string |
+
+ policyTypes is a 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
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].podSelector
+
+
+
+podSelector 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index]
+
+
+
+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
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ports |
+ []object |
+
+ ports is a 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.
+ |
+ false |
+
+ to |
+ []object |
+
+ to is a 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].ports[index]
+
+
+
+NetworkPolicyPort describes a port to allow traffic on
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ endPort |
+ integer |
+
+ endPort indicates that the range of ports from port to endPort if set, 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.
+
+ Format: int32
+ |
+ false |
+
+ port |
+ int or string |
+
+ port represents 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.
+ |
+ false |
+
+ protocol |
+ string |
+
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index]
+
+
+
+NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ipBlock |
+ object |
+
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+ |
+ false |
+
+ podSelector |
+ object |
+
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].ipBlock
+
+
+
+ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ cidr |
+ string |
+
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ |
+ true |
+
+ except |
+ []string |
+
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector
+
+
+
+namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector
+
+
+
+podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index]
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ from |
+ []object |
+
+ from is a 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.
+ |
+ false |
+
+ ports |
+ []object |
+
+ ports is a 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index]
+
+
+
+NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ipBlock |
+ object |
+
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+ |
+ false |
+
+ podSelector |
+ object |
+
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].ipBlock
+
+
+
+ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ cidr |
+ string |
+
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ |
+ true |
+
+ except |
+ []string |
+
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector
+
+
+
+namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector
+
+
+
+podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].ports[index]
+
+
+
+NetworkPolicyPort describes a port to allow traffic on
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ endPort |
+ integer |
+
+ endPort indicates that the range of ports from port to endPort if set, 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.
+
+ Format: int32
+ |
+ false |
+
+ port |
+ int or string |
+
+ port represents 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.
+ |
+ false |
+
+ protocol |
+ string |
+
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+ |
+ false |
+
+
+
+
+### Tenant.spec.priorityClasses
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+ default |
+ string |
+
+
+ |
+ false |
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.priorityClasses.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ items |
+ []object |
+
+
+ |
+ false |
+
+ scope |
+ enum |
+
+ Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
+
+ Enum: Tenant, Namespace
+ Default: Tenant
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas.items[index]
+
+
+
+ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ hard |
+ map[string]int or string |
+
+ hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/
+ |
+ false |
+
+ scopeSelector |
+ object |
+
+ 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.
+ |
+ false |
+
+ scopes |
+ []string |
+
+ A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas.items[index].scopeSelector
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ A list of scope selector requirements by scope of the resources.
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas.items[index].scopeSelector.matchExpressions[index]
+
+
+
+A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ operator |
+ string |
+
+ Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
+ |
+ true |
+
+ scopeName |
+ string |
+
+ The name of the scope that the selector applies to.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.runtimeClasses
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.runtimeClasses.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions
+
+
+
+Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalMetadata |
+ object |
+
+ Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
+ |
+ false |
+
+ allowedServices |
+ object |
+
+ Block or deny certain type of Services. Optional.
+ |
+ false |
+
+ externalIPs |
+ object |
+
+ Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions.additionalMetadata
+
+
+
+Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ annotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ labels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions.allowedServices
+
+
+
+Block or deny certain type of Services. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ externalName |
+ boolean |
+
+ Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+ |
+ false |
+
+ loadBalancer |
+ boolean |
+
+ Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+ |
+ false |
+
+ nodePort |
+ boolean |
+
+ Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions.externalIPs
+
+
+
+Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ true |
+
+
+
+
+### Tenant.spec.storageClasses
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+ default |
+ string |
+
+
+ |
+ false |
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.storageClasses.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.status
+
+
+
+Returns the observed state of the Tenant.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ size |
+ integer |
+
+ How many namespaces are assigned to the Tenant.
+ |
+ true |
+
+ state |
+ enum |
+
+ The operational state of the Tenant. Possible values are "Active", "Cordoned".
+
+ Enum: Cordoned, Active
+ Default: Active
+ |
+ true |
+
+ namespaces |
+ []string |
+
+ List of namespaces assigned to the Tenant.
+ |
+ false |
+
+
+
+# capsule.clastix.io/v1beta1
+
+Resource Types:
+
+- [Tenant](#tenant)
+
+
+
+
+## Tenant
+
+
+
+
+
+
+Tenant is the Schema for the tenants API.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ apiVersion |
+ string |
+ capsule.clastix.io/v1beta1 |
+ true |
+
+
+ kind |
+ string |
+ Tenant |
+ true |
+
+
+ metadata |
+ object |
+ Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
+ true |
+
+ spec |
+ object |
+
+ TenantSpec defines the desired state of Tenant.
+ |
+ false |
+
+ status |
+ object |
+
+ Returns the observed state of the Tenant.
+ |
+ false |
+
+
+
+
+### Tenant.spec
+
+
+
+TenantSpec defines the desired state of Tenant.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ owners |
+ []object |
+
+ Specifies the owners of the Tenant. Mandatory.
+ |
+ true |
+
+ additionalRoleBindings |
+ []object |
+
+ 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.
+ |
+ false |
+
+ containerRegistries |
+ object |
+
+ 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.
+ |
+ false |
+
+ imagePullPolicies |
+ []enum |
+
+ 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.
+ |
+ false |
+
+ ingressOptions |
+ object |
+
+ Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+ |
+ false |
+
+ limitRanges |
+ object |
+
+ Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+ |
+ false |
+
+ namespaceOptions |
+ object |
+
+ 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.
+ |
+ false |
+
+ networkPolicies |
+ object |
+
+ Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+ |
+ false |
+
+ nodeSelector |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+ priorityClasses |
+ object |
+
+ 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.
+ |
+ false |
+
+ resourceQuotas |
+ object |
+
+ 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.
+ |
+ false |
+
+ serviceOptions |
+ object |
+
+ Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+ |
+ false |
+
+ storageClasses |
+ object |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.owners[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ enum |
+
+ Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
+
+ Enum: User, Group, ServiceAccount
+ |
+ true |
+
+ name |
+ string |
+
+ Name of tenant owner.
+ |
+ true |
+
+ proxySettings |
+ []object |
+
+ Proxy settings for tenant owner.
+ |
+ false |
+
+
+
+
+### Tenant.spec.owners[index].proxySettings[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ enum |
+
+
+
+ Enum: Nodes, StorageClasses, IngressClasses, PriorityClasses
+ |
+ true |
+
+ operations |
+ []enum |
+
+
+ |
+ true |
+
+
+
+
+### Tenant.spec.additionalRoleBindings[index]
+
+
+
+
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ clusterRoleName |
+ string |
+
+
+ |
+ true |
+
+ subjects |
+ []object |
+
+ kubebuilder:validation:Minimum=1
+ |
+ true |
+
+
+
+
+### Tenant.spec.additionalRoleBindings[index].subjects[index]
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ kind |
+ string |
+
+ 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.
+ |
+ true |
+
+ name |
+ string |
+
+ Name of the object being referenced.
+ |
+ true |
+
+ apiGroup |
+ string |
+
+ 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.
+ |
+ false |
+
+ namespace |
+ string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.containerRegistries
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressOptions
+
+
+
+Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowedClasses |
+ object |
+
+ 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.
+ |
+ false |
+
+ allowedHostnames |
+ object |
+
+ 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.
+ |
+ false |
+
+ hostnameCollisionScope |
+ enum |
+
+ 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.
+
+ Enum: Cluster, Tenant, Namespace, Disabled
+ Default: Disabled
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressOptions.allowedClasses
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.ingressOptions.allowedHostnames
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.limitRanges
+
+
+
+Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ items |
+ []object |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.limitRanges.items[index]
+
+
+
+LimitRangeSpec defines a min/max usage limit for resources that match on kind.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ limits |
+ []object |
+
+ Limits is the list of LimitRangeItem objects that are enforced.
+ |
+ true |
+
+
+
+
+### Tenant.spec.limitRanges.items[index].limits[index]
+
+
+
+LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ type |
+ string |
+
+ Type of resource that this limit applies to.
+ |
+ true |
+
+ default |
+ map[string]int or string |
+
+ Default resource requirement limit value by resource name if resource limit is omitted.
+ |
+ false |
+
+ defaultRequest |
+ map[string]int or string |
+
+ DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
+ |
+ false |
+
+ max |
+ map[string]int or string |
+
+ Max usage constraints on this kind by resource name.
+ |
+ false |
+
+ maxLimitRequestRatio |
+ map[string]int or string |
+
+ 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.
+ |
+ false |
+
+ min |
+ map[string]int or string |
+
+ Min usage constraints on this kind by resource name.
+ |
+ false |
+
+
+
+
+### Tenant.spec.namespaceOptions
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalMetadata |
+ object |
+
+ Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
+ |
+ false |
+
+ quota |
+ integer |
+
+ 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
+ |
+ false |
+
+
+
+
+### Tenant.spec.namespaceOptions.additionalMetadata
+
+
+
+Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ annotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ labels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies
+
+
+
+Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ items |
+ []object |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index]
+
+
+
+NetworkPolicySpec provides the specification of a NetworkPolicy
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ podSelector |
+ object |
+
+ podSelector 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.
+ |
+ true |
+
+ egress |
+ []object |
+
+ egress is a 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
+ |
+ false |
+
+ ingress |
+ []object |
+
+ ingress is a 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)
+ |
+ false |
+
+ policyTypes |
+ []string |
+
+ policyTypes is a 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
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].podSelector
+
+
+
+podSelector 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index]
+
+
+
+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
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ports |
+ []object |
+
+ ports is a 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.
+ |
+ false |
+
+ to |
+ []object |
+
+ to is a 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].ports[index]
+
+
+
+NetworkPolicyPort describes a port to allow traffic on
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ endPort |
+ integer |
+
+ endPort indicates that the range of ports from port to endPort if set, 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.
+
+ Format: int32
+ |
+ false |
+
+ port |
+ int or string |
+
+ port represents 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.
+ |
+ false |
+
+ protocol |
+ string |
+
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index]
+
+
+
+NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ipBlock |
+ object |
+
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+ |
+ false |
+
+ podSelector |
+ object |
+
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].ipBlock
+
+
+
+ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ cidr |
+ string |
+
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ |
+ true |
+
+ except |
+ []string |
+
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector
+
+
+
+namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector
+
+
+
+podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index]
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ from |
+ []object |
+
+ from is a 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.
+ |
+ false |
+
+ ports |
+ []object |
+
+ ports is a 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index]
+
+
+
+NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ ipBlock |
+ object |
+
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ |
+ false |
+
+ namespaceSelector |
+ object |
+
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+ |
+ false |
+
+ podSelector |
+ object |
+
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].ipBlock
+
+
+
+ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ cidr |
+ string |
+
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
+ |
+ true |
+
+ except |
+ []string |
+
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector
+
+
+
+namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector
+
+
+
+podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
+ 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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ |
+ false |
+
+ matchLabels |
+ map[string]string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector.matchExpressions[index]
+
+
+
+A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ key |
+ string |
+
+ key is the label key that the selector applies to.
+ |
+ true |
+
+ operator |
+ string |
+
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.networkPolicies.items[index].ingress[index].ports[index]
+
+
+
+NetworkPolicyPort describes a port to allow traffic on
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ endPort |
+ integer |
+
+ endPort indicates that the range of ports from port to endPort if set, 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.
+
+ Format: int32
+ |
+ false |
+
+ port |
+ int or string |
+
+ port represents 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.
+ |
+ false |
+
+ protocol |
+ string |
+
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+ |
+ false |
+
+
+
+
+### Tenant.spec.priorityClasses
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ items |
+ []object |
+
+
+ |
+ false |
+
+ scope |
+ enum |
+
+ Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
+
+ Enum: Tenant, Namespace
+ Default: Tenant
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas.items[index]
+
+
+
+ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ hard |
+ map[string]int or string |
+
+ hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/
+ |
+ false |
+
+ scopeSelector |
+ object |
+
+ 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.
+ |
+ false |
+
+ scopes |
+ []string |
+
+ A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas.items[index].scopeSelector
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ matchExpressions |
+ []object |
+
+ A list of scope selector requirements by scope of the resources.
+ |
+ false |
+
+
+
+
+### Tenant.spec.resourceQuotas.items[index].scopeSelector.matchExpressions[index]
+
+
+
+A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ operator |
+ string |
+
+ Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
+ |
+ true |
+
+ scopeName |
+ string |
+
+ The name of the scope that the selector applies to.
+ |
+ true |
+
+ values |
+ []string |
+
+ 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.
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions
+
+
+
+Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ additionalMetadata |
+ object |
+
+ Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
+ |
+ false |
+
+ allowedServices |
+ object |
+
+ Block or deny certain type of Services. Optional.
+ |
+ false |
+
+ externalIPs |
+ object |
+
+ Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions.additionalMetadata
+
+
+
+Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ annotations |
+ map[string]string |
+
+
+ |
+ false |
+
+ labels |
+ map[string]string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions.allowedServices
+
+
+
+Block or deny certain type of Services. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ externalName |
+ boolean |
+
+ Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+ |
+ false |
+
+ loadBalancer |
+ boolean |
+
+ Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+ |
+ false |
+
+ nodePort |
+ boolean |
+
+ Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+ |
+ false |
+
+
+
+
+### Tenant.spec.serviceOptions.externalIPs
+
+
+
+Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ true |
+
+
+
+
+### Tenant.spec.storageClasses
+
+
+
+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.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ allowed |
+ []string |
+
+
+ |
+ false |
+
+ allowedRegex |
+ string |
+
+
+ |
+ false |
+
+
+
+
+### Tenant.status
+
+
+
+Returns the observed state of the Tenant.
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Required |
+
+
+
+ size |
+ integer |
+
+ How many namespaces are assigned to the Tenant.
+ |
+ true |
+
+ state |
+ enum |
+
+ The operational state of the Tenant. Possible values are "Active", "Cordoned".
+
+ Enum: Cordoned, Active
+ Default: Active
+ |
+ true |
+
+ namespaces |
+ []string |
+
+ List of namespaces assigned to the Tenant.
+ |
+ false |
+
+
\ No newline at end of file
diff --git a/docs/content/general/getting-started.md b/docs/content/general/getting-started.md
index cddfa1b3..5eed9121 100644
--- a/docs/content/general/getting-started.md
+++ b/docs/content/general/getting-started.md
@@ -35,7 +35,7 @@ Create the tenant as cluster admin:
```yaml
kubectl create -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
diff --git a/docs/content/general/mtb.md b/docs/content/general/mtb.md
index 390e839e..2d491b57 100644
--- a/docs/content/general/mtb.md
+++ b/docs/content/general/mtb.md
@@ -51,7 +51,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <
+gold gold 21h capsule.clastix.io/qos=gold
+silver silver 21h capsule.clastix.io/qos=silver
+```
+
+The expected output using `capsule-proxy` is the retrieval of the `bronze` and `silver` ones.
+
+```bash
+$ kubectl --context alice-oidc@mycluster get runtimeclasses.node.k8s.io
+NAME HANDLER AGE
+bronze bronze 21h
+silver silver 21h
+```
+
+> `RuntimeClass` is one of the latest implementations in Capsule Proxy and is adhering to the new selection strategy based on labels selector, rather than exact match and regex ones.
+>
+> The latter ones are going to be deprecated in the upcoming releases of Capsule.
+
+### Persistent Volumes
+
+A Tenant can request persistent volumes through the `PersistentVolumeClaim` API, and get a volume from it.
+
+Starting from release v0.2.0, all the `PersistentVolumes` are labelled with the Capsule label that is used by the Capsule Proxy to allow the retrieval.
+
+```yaml
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ annotations:
+ finalizers:
+ - kubernetes.io/pv-protection
+ labels:
+ capsule.clastix.io/tenant: oil
+ name: data-01
+spec:
+ accessModes:
+ - ReadWriteOnce
+ capacity:
+ storage: 10Gi
+ hostPath:
+ path: /mnt/data
+ type: ""
+ persistentVolumeReclaimPolicy: Retain
+ storageClassName: manual
+ volumeMode: Filesystem
+```
+
+> Please, notice the label `capsule.clastix.io/tenant` matching the Tenant name.
+
+With that said, a multi-tenant cluster can be made of several volumes, each one for different tenants.
+
+```bash
+$ kubectl --context admin@mycluster get persistentvolumes --show-labels
+NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE LABELS
+data-01 10Gi RWO Retain Available manual 17h capsule.clastix.io/tenant=oil
+data-02 10Gi RWO Retain Available manual 17h capsule.clastix.io/tenant=gas
+
+```
+
+For the `oil` Tenant, Alice has the required permission to list Volumes.
+
+```yaml
+apiVersion: capsule.clastix.io/v1beta2
+kind: Tenant
+metadata:
+ name: oil
+spec:
+ owners:
+ - kind: User
+ name: alice
+ proxySettings:
+ - kind: PersistentVolumes
+ operations:
+ - List
+```
+
+The expected output using `capsule-proxy` is the retrieval of the PVs used currently, or in the past, by the PVCs in their Tenants.
+
+```bash
+$ kubectl --context alice-oidc@mycluster get persistentvolumes
+NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
+data-01 10Gi RWO Retain Available manual 17h
+```
+
### ProxySetting Use Case
-Consider a scenario, where a cluster admin creates a tenant and assign ownership of the tenant to a user, so called tenant owner. Afterwards, tenant owner would in turn like to provide access to their cluster-scoped resources to a set of users (e.g. non-owners or tenant users), groups and service accounts, who doesn't require tenant owner level permissions.
+Consider a scenario, where a cluster admin creates a tenant and assigns ownership of the tenant to a user, the so-called tenant owner. Afterwards, tenant owner would in turn like to provide access to their cluster-scoped resources to a set of users (e.g. non-owners or tenant users), groups and service accounts, who doesn't require tenant-owner-level permissions.
-Tenant Owner can provide access to following cluster-scoped resources to their tenant users, groups and service account by creating `ProxySetting` resource
+Tenant Owner can provide access to the following cluster-scoped resources to their tenant users, groups and service account by creating `ProxySetting` resource
- `Nodes`
- `StorageClasses`
- `IngressClasses`
- `PriorityClasses`
+- `RuntimeClasses`
+- `PersistentVolumes`
-Each Resource kind can be granted with following verbs, such as:
+Each Resource kind can be granted with the following verbs, such as:
- `List`
- `Update`
- `Delete`
These tenant users, groups and services accounts have less privileged access than tenant owners.
-As a Tenant Owner `alice`, you can create a `ProxySetting` resources to allow `bob` to list nodes, storage classes, ingress classes and priority classes
+As a Tenant Owner `alice`, you can create a `ProxySetting` resource to allow `bob` to list nodes, storage classes, ingress classes and priority classes
```yaml
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: ProxySetting
metadata:
name: sre-readers
@@ -439,7 +569,7 @@ $ kubectl auth can-i --context bob-oidc@mycluster get priorityclasses
yes
```
## HTTP support
-Capsule proxy supports `https` and `http`, although the latter is not recommended, we understand that it can be useful for some use cases (i.e. development, working behind a TLS-terminated reverse proxy and so on). As the default behaviour is to work with `https`, we need to use the flag `--enable-ssl=false` if we really want to work under `http`.
+Capsule proxy supports `https` and `http`, although the latter is not recommended, we understand that it can be useful for some use cases (i.e. development, working behind a TLS-terminated reverse proxy and so on). As the default behaviour is to work with `https`, we need to use the flag `--enable-ssl=false` if we want to work under `http`.
After having the `capsule-proxy` working under `http`, requests must provide authentication using an allowed Bearer Token.
@@ -456,16 +586,16 @@ $ curl -H "Authorization: Bearer $TOKEN" http://localhost:9001/api/v1/namespaces
Starting from the v0.3.0 release, Capsule Proxy exposes Prometheus metrics available at `http://0.0.0.0:8080/metrics`.
-The offered metrics are related to the internal `controller-manager` code base, such as work-queue and REST client requests, and the Go runtime ones.
+The offered metrics are related to the internal `controller-manager` code base, such as work queue and REST client requests, and the Go runtime ones.
Along with these, metrics `capsule_proxy_response_time_seconds` and `capsule_proxy_requests_total` have been introduced and are specific to the Capsule Proxy code-base and functionalities.
`capsule_proxy_response_time_seconds` offers a bucket representation of the HTTP request duration.
-The available variables for this metrics are the following ones:
-- `path`: the HTTP path of each single request that Capsule Proxy passes to the upstream
+The available variables for these metrics are the following ones:
+- `path`: the HTTP path of every single request that Capsule Proxy passes to the upstream
`capsule_proxy_requests_total` counts the global requests that Capsule Proxy is passing to the upstream with the following labels.
-- `path`: the HTTP path of each single request that Capsule Proxy passes to the upstream
+- `path`: the HTTP path of every single request that Capsule Proxy passes to the upstream
- `status`: the HTTP status code of the request
> Example output of the metrics:
@@ -493,6 +623,6 @@ The available variables for this metrics are the following ones:
## Contributing
-`capsule-proxy` is an open-source software released with Apache2 [license](https://github.com/clastix/capsule-proxy/blob/master/LICENSE).
+`capsule-proxy` is open-source software released with Apache2 [license](https://github.com/clastix/capsule-proxy/blob/master/LICENSE).
Contributing guidelines are available [here](https://github.com/clastix/capsule-proxy/blob/master/CONTRIBUTING.md).
diff --git a/docs/content/general/references.md b/docs/content/general/references.md
index bc91c311..8b959c7c 100644
--- a/docs/content/general/references.md
+++ b/docs/content/general/references.md
@@ -6,7 +6,7 @@ Reference document for Capsule Operator configuration
Capsule operator uses a Custom Resources Definition (CRD) for _Tenants_.
Tenants are cluster wide resources, so you need cluster level permissions to work with tenants.
-You can learn about tenant CRDs in the following [section](./tenant-crd)
+You can learn about tenant CRDs in the following [section](./crds-apis)
## Capsule Configuration
diff --git a/docs/content/general/tenant-crd.md b/docs/content/general/tenant-crd.md
deleted file mode 100644
index 13d8cb3f..00000000
--- a/docs/content/general/tenant-crd.md
+++ /dev/null
@@ -1,3378 +0,0 @@
-
-
-# API Reference
-
-Packages:
-
-- [capsule.clastix.io/v1alpha1](#capsuleclastixiov1alpha1)
-- [capsule.clastix.io/v1beta1](#capsuleclastixiov1beta1)
-
-# capsule.clastix.io/v1alpha1
-
-Resource Types:
-
-- [CapsuleConfiguration](#capsuleconfiguration)
-
-- [Tenant](#tenant)
-
-
-
-
-## CapsuleConfiguration
-
-
-
-
-
-
-CapsuleConfiguration is the Schema for the Capsule configuration API.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- apiVersion |
- string |
- capsule.clastix.io/v1alpha1 |
- true |
-
-
- kind |
- string |
- CapsuleConfiguration |
- true |
-
-
- metadata |
- object |
- Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
- true |
-
- spec |
- object |
-
- CapsuleConfigurationSpec defines the Capsule configuration.
- |
- false |
-
-
-
-
-### CapsuleConfiguration.spec
-
-
-
-CapsuleConfigurationSpec defines the Capsule configuration.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- forceTenantPrefix |
- boolean |
-
- 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.
-
- Default: false
- |
- false |
-
- protectedNamespaceRegex |
- string |
-
- Disallow creation of namespaces, whose name matches this regexp
- |
- false |
-
- userGroups |
- []string |
-
- Names of the groups for Capsule users.
-
- Default: [capsule.clastix.io]
- |
- false |
-
-
-
-## Tenant
-
-
-
-
-
-
-Tenant is the Schema for the tenants API.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- apiVersion |
- string |
- capsule.clastix.io/v1alpha1 |
- true |
-
-
- kind |
- string |
- Tenant |
- true |
-
-
- metadata |
- object |
- Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
- true |
-
- spec |
- object |
-
- TenantSpec defines the desired state of Tenant.
- |
- false |
-
- status |
- object |
-
- TenantStatus defines the observed state of Tenant.
- |
- false |
-
-
-
-
-### Tenant.spec
-
-
-
-TenantSpec defines the desired state of Tenant.
-
-
-
-
-### Tenant.spec.owner
-
-
-
-OwnerSpec defines tenant owner name and kind.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- kind |
- enum |
-
-
-
- Enum: User, Group
- |
- true |
-
- name |
- string |
-
-
- |
- true |
-
-
-
-
-### Tenant.spec.additionalRoleBindings[index]
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- clusterRoleName |
- string |
-
-
- |
- true |
-
- subjects |
- []object |
-
- kubebuilder:validation:Minimum=1
- |
- true |
-
-
-
-
-### Tenant.spec.additionalRoleBindings[index].subjects[index]
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- kind |
- string |
-
- 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.
- |
- true |
-
- name |
- string |
-
- Name of the object being referenced.
- |
- true |
-
- apiGroup |
- string |
-
- 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.
- |
- false |
-
- namespace |
- string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.containerRegistries
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.externalServiceIPs
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- true |
-
-
-
-
-### Tenant.spec.ingressClasses
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.ingressHostnames
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.limitRanges[index]
-
-
-
-LimitRangeSpec defines a min/max usage limit for resources that match on kind.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- limits |
- []object |
-
- Limits is the list of LimitRangeItem objects that are enforced.
- |
- true |
-
-
-
-
-### Tenant.spec.limitRanges[index].limits[index]
-
-
-
-LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- type |
- string |
-
- Type of resource that this limit applies to.
- |
- true |
-
- default |
- map[string]int or string |
-
- Default resource requirement limit value by resource name if resource limit is omitted.
- |
- false |
-
- defaultRequest |
- map[string]int or string |
-
- DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
- |
- false |
-
- max |
- map[string]int or string |
-
- Max usage constraints on this kind by resource name.
- |
- false |
-
- maxLimitRequestRatio |
- map[string]int or string |
-
- 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.
- |
- false |
-
- min |
- map[string]int or string |
-
- Min usage constraints on this kind by resource name.
- |
- false |
-
-
-
-
-### Tenant.spec.namespacesMetadata
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- additionalAnnotations |
- map[string]string |
-
-
- |
- false |
-
- additionalLabels |
- map[string]string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index]
-
-
-
-NetworkPolicySpec provides the specification of a NetworkPolicy
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- podSelector |
- object |
-
- 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.
- |
- true |
-
- egress |
- []object |
-
- 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
- |
- false |
-
- ingress |
- []object |
-
- 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)
- |
- false |
-
- policyTypes |
- []string |
-
- 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
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].podSelector
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].podSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index]
-
-
-
-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
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- ports |
- []object |
-
- 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.
- |
- false |
-
- to |
- []object |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index].ports[index]
-
-
-
-NetworkPolicyPort describes a port to allow traffic on
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- endPort |
- integer |
-
- 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
- |
- false |
-
- port |
- int or string |
-
- 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.
- |
- false |
-
- protocol |
- string |
-
- The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
-
- Default: TCP
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index].to[index]
-
-
-
-NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- ipBlock |
- object |
-
- IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
- |
- false |
-
- namespaceSelector |
- object |
-
- Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
- |
- false |
-
- podSelector |
- object |
-
- This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index].to[index].ipBlock
-
-
-
-IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- cidr |
- string |
-
- CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
- |
- true |
-
- except |
- []string |
-
- 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
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index].to[index].namespaceSelector
-
-
-
-Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index].to[index].namespaceSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index].to[index].podSelector
-
-
-
-This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].egress[index].to[index].podSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index]
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- from |
- []object |
-
- 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.
- |
- false |
-
- ports |
- []object |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index].from[index]
-
-
-
-NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- ipBlock |
- object |
-
- IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
- |
- false |
-
- namespaceSelector |
- object |
-
- Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
- |
- false |
-
- podSelector |
- object |
-
- This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index].from[index].ipBlock
-
-
-
-IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- cidr |
- string |
-
- CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
- |
- true |
-
- except |
- []string |
-
- 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
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index].from[index].namespaceSelector
-
-
-
-Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index].from[index].namespaceSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index].from[index].podSelector
-
-
-
-This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index].from[index].podSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies[index].ingress[index].ports[index]
-
-
-
-NetworkPolicyPort describes a port to allow traffic on
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- endPort |
- integer |
-
- 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
- |
- false |
-
- port |
- int or string |
-
- 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.
- |
- false |
-
- protocol |
- string |
-
- The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
-
- Default: TCP
- |
- false |
-
-
-
-
-### Tenant.spec.resourceQuotas[index]
-
-
-
-ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- hard |
- map[string]int or string |
-
- hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/
- |
- false |
-
- scopeSelector |
- object |
-
- 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.
- |
- false |
-
- scopes |
- []string |
-
- A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
- |
- false |
-
-
-
-
-### Tenant.spec.resourceQuotas[index].scopeSelector
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- A list of scope selector requirements by scope of the resources.
- |
- false |
-
-
-
-
-### Tenant.spec.resourceQuotas[index].scopeSelector.matchExpressions[index]
-
-
-
-A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- operator |
- string |
-
- Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
- |
- true |
-
- scopeName |
- string |
-
- The name of the scope that the selector applies to.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.servicesMetadata
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- additionalAnnotations |
- map[string]string |
-
-
- |
- false |
-
- additionalLabels |
- map[string]string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.storageClasses
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.status
-
-
-
-TenantStatus defines the observed state of Tenant.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- size |
- integer |
-
-
- |
- true |
-
- namespaces |
- []string |
-
-
- |
- false |
-
-
-
-# capsule.clastix.io/v1beta1
-
-Resource Types:
-
-- [Tenant](#tenant)
-
-
-
-
-## Tenant
-
-
-
-
-
-
-Tenant is the Schema for the tenants API.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- apiVersion |
- string |
- capsule.clastix.io/v1beta1 |
- true |
-
-
- kind |
- string |
- Tenant |
- true |
-
-
- metadata |
- object |
- Refer to the Kubernetes API documentation for the fields of the `metadata` field. |
- true |
-
- spec |
- object |
-
- TenantSpec defines the desired state of Tenant.
- |
- false |
-
- status |
- object |
-
- Returns the observed state of the Tenant.
- |
- false |
-
-
-
-
-### Tenant.spec
-
-
-
-TenantSpec defines the desired state of Tenant.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- owners |
- []object |
-
- Specifies the owners of the Tenant. Mandatory.
- |
- true |
-
- additionalRoleBindings |
- []object |
-
- 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.
- |
- false |
-
- containerRegistries |
- object |
-
- 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.
- |
- false |
-
- imagePullPolicies |
- []enum |
-
- 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.
- |
- false |
-
- ingressOptions |
- object |
-
- Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
- |
- false |
-
- limitRanges |
- object |
-
- Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
- |
- false |
-
- namespaceOptions |
- object |
-
- 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.
- |
- false |
-
- networkPolicies |
- object |
-
- Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
- |
- false |
-
- nodeSelector |
- map[string]string |
-
- 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.
- |
- false |
-
- priorityClasses |
- object |
-
- 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.
- |
- false |
-
- resourceQuotas |
- object |
-
- 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.
- |
- false |
-
- serviceOptions |
- object |
-
- Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
- |
- false |
-
- storageClasses |
- object |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.owners[index]
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- kind |
- enum |
-
- Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
-
- Enum: User, Group, ServiceAccount
- |
- true |
-
- name |
- string |
-
- Name of tenant owner.
- |
- true |
-
- proxySettings |
- []object |
-
- Proxy settings for tenant owner.
- |
- false |
-
-
-
-
-### Tenant.spec.owners[index].proxySettings[index]
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- kind |
- enum |
-
-
-
- Enum: Nodes, StorageClasses, IngressClasses, PriorityClasses
- |
- true |
-
- operations |
- []enum |
-
-
- |
- true |
-
-
-
-
-### Tenant.spec.additionalRoleBindings[index]
-
-
-
-
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- clusterRoleName |
- string |
-
-
- |
- true |
-
- subjects |
- []object |
-
- kubebuilder:validation:Minimum=1
- |
- true |
-
-
-
-
-### Tenant.spec.additionalRoleBindings[index].subjects[index]
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- kind |
- string |
-
- 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.
- |
- true |
-
- name |
- string |
-
- Name of the object being referenced.
- |
- true |
-
- apiGroup |
- string |
-
- 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.
- |
- false |
-
- namespace |
- string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.containerRegistries
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.ingressOptions
-
-
-
-Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowedClasses |
- object |
-
- 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.
- |
- false |
-
- allowedHostnames |
- object |
-
- 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.
- |
- false |
-
- hostnameCollisionScope |
- enum |
-
- 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.
-
- Enum: Cluster, Tenant, Namespace, Disabled
- Default: Disabled
- |
- false |
-
-
-
-
-### Tenant.spec.ingressOptions.allowedClasses
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.ingressOptions.allowedHostnames
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.limitRanges
-
-
-
-Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- items |
- []object |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.limitRanges.items[index]
-
-
-
-LimitRangeSpec defines a min/max usage limit for resources that match on kind.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- limits |
- []object |
-
- Limits is the list of LimitRangeItem objects that are enforced.
- |
- true |
-
-
-
-
-### Tenant.spec.limitRanges.items[index].limits[index]
-
-
-
-LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- type |
- string |
-
- Type of resource that this limit applies to.
- |
- true |
-
- default |
- map[string]int or string |
-
- Default resource requirement limit value by resource name if resource limit is omitted.
- |
- false |
-
- defaultRequest |
- map[string]int or string |
-
- DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
- |
- false |
-
- max |
- map[string]int or string |
-
- Max usage constraints on this kind by resource name.
- |
- false |
-
- maxLimitRequestRatio |
- map[string]int or string |
-
- 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.
- |
- false |
-
- min |
- map[string]int or string |
-
- Min usage constraints on this kind by resource name.
- |
- false |
-
-
-
-
-### Tenant.spec.namespaceOptions
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- additionalMetadata |
- object |
-
- Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
- |
- false |
-
- quota |
- integer |
-
- 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
- |
- false |
-
-
-
-
-### Tenant.spec.namespaceOptions.additionalMetadata
-
-
-
-Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- annotations |
- map[string]string |
-
-
- |
- false |
-
- labels |
- map[string]string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies
-
-
-
-Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- items |
- []object |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index]
-
-
-
-NetworkPolicySpec provides the specification of a NetworkPolicy
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- podSelector |
- object |
-
- 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.
- |
- true |
-
- egress |
- []object |
-
- 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
- |
- false |
-
- ingress |
- []object |
-
- 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)
- |
- false |
-
- policyTypes |
- []string |
-
- 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
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].podSelector
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].podSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index]
-
-
-
-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
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- ports |
- []object |
-
- 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.
- |
- false |
-
- to |
- []object |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index].ports[index]
-
-
-
-NetworkPolicyPort describes a port to allow traffic on
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- endPort |
- integer |
-
- 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
- |
- false |
-
- port |
- int or string |
-
- 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.
- |
- false |
-
- protocol |
- string |
-
- The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
-
- Default: TCP
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index].to[index]
-
-
-
-NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- ipBlock |
- object |
-
- IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
- |
- false |
-
- namespaceSelector |
- object |
-
- Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
- |
- false |
-
- podSelector |
- object |
-
- This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index].to[index].ipBlock
-
-
-
-IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- cidr |
- string |
-
- CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
- |
- true |
-
- except |
- []string |
-
- 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
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector
-
-
-
-Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector
-
-
-
-This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index]
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- from |
- []object |
-
- 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.
- |
- false |
-
- ports |
- []object |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index].from[index]
-
-
-
-NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- ipBlock |
- object |
-
- IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
- |
- false |
-
- namespaceSelector |
- object |
-
- Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
- |
- false |
-
- podSelector |
- object |
-
- This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].ipBlock
-
-
-
-IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- cidr |
- string |
-
- CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
- |
- true |
-
- except |
- []string |
-
- 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
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector
-
-
-
-Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector
-
-
-
-This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods.
- 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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- matchExpressions is a list of label selector requirements. The requirements are ANDed.
- |
- false |
-
- matchLabels |
- map[string]string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector.matchExpressions[index]
-
-
-
-A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- key |
- string |
-
- key is the label key that the selector applies to.
- |
- true |
-
- operator |
- string |
-
- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.networkPolicies.items[index].ingress[index].ports[index]
-
-
-
-NetworkPolicyPort describes a port to allow traffic on
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- endPort |
- integer |
-
- 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
- |
- false |
-
- port |
- int or string |
-
- 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.
- |
- false |
-
- protocol |
- string |
-
- The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
-
- Default: TCP
- |
- false |
-
-
-
-
-### Tenant.spec.priorityClasses
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.resourceQuotas
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- items |
- []object |
-
-
- |
- false |
-
- scope |
- enum |
-
- Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
-
- Enum: Tenant, Namespace
- Default: Tenant
- |
- false |
-
-
-
-
-### Tenant.spec.resourceQuotas.items[index]
-
-
-
-ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- hard |
- map[string]int or string |
-
- hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/
- |
- false |
-
- scopeSelector |
- object |
-
- 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.
- |
- false |
-
- scopes |
- []string |
-
- A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
- |
- false |
-
-
-
-
-### Tenant.spec.resourceQuotas.items[index].scopeSelector
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- matchExpressions |
- []object |
-
- A list of scope selector requirements by scope of the resources.
- |
- false |
-
-
-
-
-### Tenant.spec.resourceQuotas.items[index].scopeSelector.matchExpressions[index]
-
-
-
-A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- operator |
- string |
-
- Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
- |
- true |
-
- scopeName |
- string |
-
- The name of the scope that the selector applies to.
- |
- true |
-
- values |
- []string |
-
- 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.
- |
- false |
-
-
-
-
-### Tenant.spec.serviceOptions
-
-
-
-Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- additionalMetadata |
- object |
-
- Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
- |
- false |
-
- allowedServices |
- object |
-
- Block or deny certain type of Services. Optional.
- |
- false |
-
- externalIPs |
- object |
-
- Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
- |
- false |
-
-
-
-
-### Tenant.spec.serviceOptions.additionalMetadata
-
-
-
-Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- annotations |
- map[string]string |
-
-
- |
- false |
-
- labels |
- map[string]string |
-
-
- |
- false |
-
-
-
-
-### Tenant.spec.serviceOptions.allowedServices
-
-
-
-Block or deny certain type of Services. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- externalName |
- boolean |
-
- Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
-
- Default: true
- |
- false |
-
- loadBalancer |
- boolean |
-
- Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
-
- Default: true
- |
- false |
-
- nodePort |
- boolean |
-
- Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
-
- Default: true
- |
- false |
-
-
-
-
-### Tenant.spec.serviceOptions.externalIPs
-
-
-
-Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- true |
-
-
-
-
-### Tenant.spec.storageClasses
-
-
-
-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.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- allowed |
- []string |
-
-
- |
- false |
-
- allowedRegex |
- string |
-
-
- |
- false |
-
-
-
-
-### Tenant.status
-
-
-
-Returns the observed state of the Tenant.
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- size |
- integer |
-
- How many namespaces are assigned to the Tenant.
- |
- true |
-
- state |
- enum |
-
- The operational state of the Tenant. Possible values are "Active", "Cordoned".
-
- Enum: Cordoned, Active
- Default: Active
- |
- true |
-
- namespaces |
- []string |
-
- List of namespaces assigned to the Tenant.
- |
- false |
-
-
\ No newline at end of file
diff --git a/docs/content/general/tutorial.md b/docs/content/general/tutorial.md
index 4392cae7..ab877672 100644
--- a/docs/content/general/tutorial.md
+++ b/docs/content/general/tutorial.md
@@ -24,7 +24,7 @@ Bill creates a new tenant `oil` in the CaaS management portal according to the t
```yaml
kubectl create -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -88,7 +88,7 @@ In the example above, Bill assigned the ownership of `oil` tenant to `alice` use
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -107,7 +107,7 @@ The tenant manifest is modified as in the following:
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -133,7 +133,7 @@ The tenant manifest is modified as in the following:
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -154,7 +154,7 @@ yes
The service account has to be part of Capsule group, so Bill has to set in the `CapsuleConfiguration`
```yaml
-apiVersion: capsule.clastix.io/v1alpha1
+apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: default
@@ -242,18 +242,18 @@ For example, assign user `Joe` the tenant ownership with only [view](https://kub
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
- annotations:
- clusterrolenames.capsule.clastix.io/user.joe: view
spec:
owners:
- name: alice
kind: User
- name: joe
kind: User
+ clusterRoles:
+ - view
EOF
```
@@ -262,9 +262,9 @@ you'll see the new Role Bindings assigned to Joe:
```
kubectl -n oil-production get rolebindings
NAME ROLE AGE
-capsule-oil-0-admin ClusterRole/admin 8d
-capsule-oil-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 8d
-capsule-oil-2-view ClusterRole/edit 5s
+capsule-oil-0-admin ClusterRole/admin 3s
+capsule-oil-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 3s
+capsule-oil-2-view ClusterRole/view 3s
```
so that Joe can only view resources in the tenant namespaces:
@@ -274,7 +274,8 @@ kubectl --as joe --as-group capsule.clastix.io auth can-i delete pods -n oil-mar
no
```
-> Please, note that, despite created with more restricted permissions, a tenant owner can still create namespaces in the tenant because he belongs to the `capsule.clastix.io` group. If you want a user not acting as tenant owner, but still operating in the tenant, you can assign additional `RoleBindings` without assigning him the tenant ownership.
+> Please, note that, despite created with more restricted permissions, a tenant owner can still create namespaces in the tenant because he belongs to the `capsule.clastix.io` group.
+> If you want a user not acting as tenant owner, but still operating in the tenant, you can assign additional `RoleBindings` without assigning him the tenant ownership.
Custom ClusterRoles are also supported. Assuming the cluster admin creates:
@@ -295,18 +296,19 @@ These permissions can be granted to Joe
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
- annotations:
- clusterrolenames.capsule.clastix.io/user.joe: view,prometheus-servicemonitors-viewer
spec:
owners:
- name: alice
kind: User
- name: joe
kind: User
+ clusterRoles:
+ - view
+ - prometheus-servicemonitors-viewer
EOF
```
@@ -315,51 +317,12 @@ For the given configuration, the resulting RoleBinding resources are the followi
```
kubectl -n oil-production get rolebindings
NAME ROLE AGE
-capsule-oil-0-admin ClusterRole/admin 8d
-capsule-oil-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 8d
-capsule-oil-2-view ClusterRole/view 11m
-capsule-oil-3-prometheus-servicemonitors-viewer ClusterRole/prometheus-servicemonitors-viewer 18s
+capsule-oil-0-admin ClusterRole/admin 90s
+capsule-oil-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 90s
+capsule-oil-2-view ClusterRole/view 90s
+capsule-oil-3-prometheus-servicemonitors-viewer ClusterRole/prometheus-servicemonitors-viewer 25s
```
-> The pattern for the annotation is `clusterrolenames.capsule.clastix.io/${KIND}.${NAME}`.
-> The placeholders `${KIND}` and `${NAME}` are referring to the Tenant Owner specification fields, both lower-cased.
->
-> In the case of users that are identified using their email address, the symbol `@` wouldn't be supported by the RFC 1123.
-> For such cases, the `@` symbol can be replaced with the placeholder `__AT__`.
->
-> ```yaml
-> apiVersion: capsule.clastix.io/v1beta1
-> kind: Tenant
-> metadata:
-> annotations:
-> clusterrolenames.capsule.clastix.io/alice__AT__clastix.io: editor,manager
-> spec:
-> owners:
-> - kind: User
-> name: alice@org.tld
-> - kind: User
-> name: alice@clastix.io
-> ```
->
-> Instead, with the resulting annotation key exceeding 63 characters length, the zero-based index of the owner can be specified as follows:
->
-> ```yaml
-> apiVersion: capsule.clastix.io/v1beta1
-> kind: Tenant
-> metadata:
-> annotations:
-> clusterrolenames.capsule.clastix.io/1: editor,manager
-> spec:
-> owners:
-> - kind: User
-> name: alice@org.tld
-> - kind: User
-> name: very-long-user-name-that-breaks-rfc-1123@org.tld
-> ```
->
-> This latter example will assign the roles `editor` and `manager`, assigned to the user `very-long-user-name-that-breaks-rfc-1123@org.tld`.
-
-
### Assign additional Role Bindings
The tenant owner acts as admin of tenant namespaces. Other users can operate inside the tenant namespaces with different levels of permissions and authorizations.
@@ -382,7 +345,7 @@ These permissions can be granted to a user without giving the role of tenant own
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -421,7 +384,7 @@ The cluster admin, can control how many namespaces Alice, creates by setting a q
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -454,7 +417,8 @@ status:
oil-development
oil-production
oil-test
- size: 3 # current namespace count
+ Size: 3 # current namespace count
+ State: Active
...
```
@@ -476,7 +440,7 @@ Bill, the cluster admin, creates multiple tenants having `alice` as owner:
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -491,7 +455,7 @@ and
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: gas
@@ -506,7 +470,7 @@ Alternatively, the ownership can be assigned to a group called `oil-and-gas`:
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -521,7 +485,7 @@ and
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: gas
@@ -564,7 +528,7 @@ Set resources quota for each namespace in the Alice's tenant by defining them in
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -716,7 +680,7 @@ By setting enforcement at the namespace level, i.e. `spec.resourceQuotas.scope=N
Bill, the cluster admin, can also set Limit Ranges for each namespace in Alice's tenant by defining limits for pods and containers in the tenant spec:
```yaml
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -724,70 +688,87 @@ spec:
...
limitRanges:
items:
- - type: Pod
- min:
- cpu: "50m"
- memory: "5Mi"
- max:
- cpu: "1"
- memory: "1Gi"
- - type: Container
- defaultRequest:
- cpu: "100m"
- memory: "10Mi"
- default:
- cpu: "200m"
- memory: "100Mi"
- min:
- cpu: "50m"
- memory: "5Mi"
- max:
- cpu: "1"
- memory: "1Gi"
- - type: PersistentVolumeClaim
- min:
- storage: "1Gi"
- max:
- storage: "10Gi"
+ - limits:
+ - type: Pod
+ min:
+ cpu: "50m"
+ memory: "5Mi"
+ max:
+ cpu: "1"
+ memory: "1Gi"
+ - limits:
+ - type: Container
+ defaultRequest:
+ cpu: "100m"
+ memory: "10Mi"
+ default:
+ cpu: "200m"
+ memory: "100Mi"
+ min:
+ cpu: "50m"
+ memory: "5Mi"
+ max:
+ cpu: "1"
+ memory: "1Gi"
+ - limits:
+ - type: PersistentVolumeClaim
+ min:
+ storage: "1Gi"
+ max:
+ storage: "10Gi"
```
Limits will be inherited by all the namespaces created by Alice. In our case, when Alice creates the namespace `oil-production`, Capsule creates the following:
```yaml
+apiVersion: v1
kind: LimitRange
+metadata:
+ name: capsule-oil-0
+ namespace: oil-production
+spec:
+ limits:
+ - max:
+ cpu: "1"
+ memory: 1Gi
+ min:
+ cpu: 50m
+ memory: 5Mi
+ type: Pod
+---
apiVersion: v1
+kind: LimitRange
metadata:
- name: limits
+ name: capsule-oil-1
+ namespace: oil-production
+spec:
+ limits:
+ - default:
+ cpu: 200m
+ memory: 100Mi
+ defaultRequest:
+ cpu: 100m
+ memory: 10Mi
+ max:
+ cpu: "1"
+ memory: 1Gi
+ min:
+ cpu: 50m
+ memory: 5Mi
+ type: Container
+---
+apiVersion: v1
+kind: LimitRange
+metadata:
+ name: capsule-oil-2
namespace: oil-production
- labels:
- tenant: oil
spec:
limits:
- - type: Pod
- min:
- cpu: "50m"
- memory: "5Mi"
- max:
- cpu: "1"
- memory: "1Gi"
- - type: Container
- defaultRequest:
- cpu: "100m"
- memory: "10Mi"
- default:
- cpu: "200m"
- memory: "100Mi"
- min:
- cpu: "50m"
- memory: "5Mi"
- max:
- cpu: "1"
- memory: "1Gi"
- - type: PersistentVolumeClaim
- min:
- storage: "1Gi"
- max:
- storage: "10Gi"
+ - max:
+ storage: 10Gi
+ min:
+ storage: 1Gi
+ type: PersistentVolumeClaim
```
> Note: being the limit range specific of single resources, there is no aggregate to count.
@@ -816,7 +797,7 @@ To prevent misuses of Pod Priority Class, Bill, the cluster admin, can enforce t
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -826,18 +807,100 @@ spec:
kind: User
priorityClasses:
allowed:
- - default
+ - custom
allowedRegex: "^tier-.*$"
+ matchLabels:
+ env: "production"
EOF
```
With the said Tenant specification, Alice can create a Pod resource if `spec.priorityClassName` equals to:
-- `default`
+- `custom`
- `tier-gold`, `tier-silver`, or `tier-bronze`, since these compile the allowed regex.
+- Any PriorityClass which has the label `env` with the value `production`
If a Pod is going to use a non-allowed _Priority Class_, it will be rejected by the Validation Webhook enforcing it.
+### Assign Pod Priority Class as tenant default
+
+It's possible to assign each tenant a PriorityClass which will be used, if no PriorityClass is set on pod basis:
+
+```yaml
+kubectl apply -f - << EOF
+apiVersion: capsule.clastix.io/v1beta2
+kind: Tenant
+metadata:
+ name: oil
+spec:
+ owners:
+ - name: alice
+ kind: User
+ priorityClasses:
+ allowed:
+ - custom
+ default: "tenant-default"
+ allowedRegex: "^tier-.*$"
+ matchLabels:
+ env: "production"
+EOF
+```
+
+Here's how the new PriorityClass could look like
+
+```yaml
+kubectl apply -f - << EOF
+apiVersion: scheduling.k8s.io/v1
+kind: PriorityClass
+metadata:
+ name: tenant-default
+value: 1313
+preemptionPolicy: Never
+globalDefault: false
+description: "This is the default PriorityClass for the oil-tenant"
+EOF
+```
+
+If a Pod has no value for `spec.priorityClassName`, the default value for PriorityClass (`tenant-default`) will be used.
+
+> This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (`globalDefault=true`) that acts only at the cluster level.
+
+**Note**: This feature supports type `PriorityClass` only on API version `scheduling.k8s.io/v1`
+
+## Assign Pod Runtime Classes
+
+Pods can be assigned different runtime classes. With the assigned runtime you can control Container Runtime Interface (CRI) is used for each pod.
+See [Kubernetes documentation](https://kubernetes.io/docs/concepts/containers/runtime-class/) for more information.
+
+To prevent misuses of Pod Runtime Classes, Bill, the cluster admin, can enforce the allowed Pod Runtime Class at tenant level:
+
+```yaml
+kubectl apply -f - << EOF
+apiVersion: capsule.clastix.io/v1beta2
+kind: Tenant
+metadata:
+ name: oil
+spec:
+ owners:
+ - name: alice
+ kind: User
+ runtimeClasses:
+ allowed:
+ - legacy
+ allowedRegex: "^hardened-.*$"
+ matchLabels:
+ env: "production"
+EOF
+```
+
+With the said Tenant specification, Alice can create a Pod resource if `spec.runtimeClassName` equals to:
+
+- `legacy`
+- e.g.: `hardened-crio` or `hardened-containerd`, since these compile the allowed regex (`^hardened-.*$"`).
+- any RuntimeClass which has the label `env` with the value `production`
+
+If a Pod is going to use a non-allowed _Runtime Class_, it will be rejected by the Validation Webhook enforcing it.
+
## Assign Nodes Pool
Bill, the cluster admin, can dedicate a pool of worker nodes to the `oil` tenant, to isolate the tenant applications from other noisy neighbors.
@@ -857,7 +920,7 @@ The label `pool=oil` is defined as node selector in the tenant manifest:
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -876,7 +939,7 @@ The Capsule controller makes sure that any namespace created in the tenant has t
Multiple node selector labels can be defined as in the following snippet:
```yaml
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -907,7 +970,7 @@ Bill can assign a set of dedicated Ingress Classes to the `oil` tenant to force
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -918,12 +981,20 @@ spec:
ingressOptions:
allowedClasses:
allowed:
- - default
+ - legacy
allowedRegex: ^\w+-lb$
+ matchLabels:
+ env: "production"
EOF
```
-Capsule assures that all Ingresses created in the tenant can use only one of the valid Ingress Classes.
+With the said Tenant specification, Alice can create a Ingress resource if `spec.ingressClassName` or `metadata.annotations."kubernetes.io/ingress.class"` equals to:
+
+- `legacy`
+- eg. `haproxy-lb` or `nginx-lb`, since these compile the allowed regex (`^\w+-lb$`).
+- Any IngressClass which has the label `env` with the value `production`
+
+If an Ingress is going to use a non-allowed _IngressClass_, it will be rejected by the Validation Webhook enforcing it.
Alice can create an Ingress using only an allowed Ingress Class:
@@ -935,27 +1006,81 @@ metadata:
name: nginx
namespace: oil-production
annotations:
- kubernetes.io/ingress.class: default
+ kubernetes.io/ingress.class: legacy
spec:
rules:
- host: oil.acmecorp.com
http:
paths:
- backend:
- serviceName: nginx
- servicePort: 80
+ service:
+ name: nginx
+ port:
+ number: 80
path: /
+ pathType: ImplementationSpecific
EOF
```
Any attempt of Alice to use a non-valid Ingress Class, or missing it, is denied by the Validation Webhook enforcing it.
+### Assign Ingress Class as tenant default
+
+It's possible to assign each tenant an Ingress Class which will be used, if a class is not set on ingress basis:
+
+```yaml
+kubectl apply -f - << EOF
+apiVersion: capsule.clastix.io/v1beta2
+kind: Tenant
+metadata:
+ name: oil
+spec:
+ owners:
+ - name: alice
+ kind: User
+ ingressOptions:
+ allowedClasses:
+ allowed:
+ - legacy
+ default: "tenant-default"
+ allowedRegex: ^\w+-lb$
+ matchLabels:
+ env: "production"
+EOF
+```
+
+Here's how the Tenant default IngressClass could look like:
+
+```yaml
+kubectl apply -f - << EOF
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+ labels:
+ app.kubernetes.io/component: controller
+ name: tenant-default
+ annotations:
+ ingressclass.kubernetes.io/is-default-class: "false"
+spec:
+ controller: k8s.io/customer-nginx
+EOF
+```
+
+If an Ingress has no value for `spec.ingressClassName` or `metadata.annotations."kubernetes.io/ingress.class"`, the `tenant-default` IngressClass is automatically applied to the Ingress resource.
+
+> This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (with the annotation `metadata.annotations.ingressclass.kubernetes.io/is-default-class=true`) that acts only at the cluster level.
+>
+> More information: [Default IngressClass](https://kubernetes.io/docs/concepts/services-networking/ingress/#default-ingress-class)
+
+**Note**: This feature is offered only by API type `IngressClass` in group `networking.k8s.io` version `v1`.
+However, resource `Ingress` is supported in `networking.k8s.io/v1` and `networking.k8s.io/v1beta1`
+
## Assign Ingress Hostnames
Bill can control ingress hostnames in the `oil` tenant to force the applications to be published only using the given hostname or set of hostnames:
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1011,7 +1136,7 @@ In a multi-tenant environment, as more and more ingresses are defined, there is
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1085,7 +1210,7 @@ Persistent storage infrastructure is provided to tenants. Different types of sto
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1098,9 +1223,17 @@ spec:
- ceph-rbd
- ceph-nfs
allowedRegex: "^ceph-.*$"
+ matchLabels:
+ env: "production"
EOF
```
+With the said Tenant specification, Alice can create a Persistent Volume Claims if `spec.storageClassName` equals to:
+
+- `ceph-rbd` or `ceph-nfs`
+- eg. `ceph-hdd` or `ceph-ssd`, since these compile the allowed regex (`^ceph-.*$`).
+- Any IngressClass which has the label `env` with the value `production`
+
Capsule assures that all Persistent Volume Claims created by Alice will use only one of the valid storage classes:
```yaml
@@ -1120,7 +1253,55 @@ spec:
EOF
```
-Any attempt of Alice to use a non-valid Storage Class, or missing it, is denied by the Validation Webhook enforcing it.
+If a Persistent Volume Claim is going to use a non-allowed _Storage Class_, it will be rejected by the Validation Webhook enforcing it.
+
+### Assign Storage Class as tenant default
+
+It's possible to assign each tenant a StorageClass which will be used, if no value is set on Persistent Volume Claim basis:
+
+```yaml
+kubectl apply -f - << EOF
+apiVersion: capsule.clastix.io/v1beta2
+kind: Tenant
+metadata:
+ name: oil
+spec:
+ owners:
+ - name: alice
+ kind: User
+ storageClasses:
+ default: "tenant-default"
+ allowed:
+ - ceph-rbd
+ - ceph-nfs
+ allowedRegex: "^ceph-.*$"
+ matchLabels:
+ env: "production"
+EOF
+```
+
+Here's how the new Storage Class could look like
+
+```yaml
+kubectl apply -f - << EOF
+apiVersion: storage.k8s.io/v1
+kind: StorageClass
+metadata:
+ name: tenant-default
+ annotations:
+ storageclass.kubernetes.io/is-default-class: "false"
+provisioner: kubernetes.io/no-provisioner
+volumeBindingMode: WaitForFirstConsumer
+EOF
+```
+
+If a Persistent Volume Claim has no value for `spec.storageClassName` the `tenant-default` value will be used on new Persistent Volume Claim resources.
+
+> This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (`.metadata.annotations.storageclass.kubernetes.io/is-default-class=true`) that acts only at the cluster level.
+>
+> See the [Default Storage Class](https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/) section on Kubernetes documentation.
+
+**Note**: This feature supports type `StorageClass` only on API version `storage.k8s.io/v1`
## Assign Network Policies
Kubernetes network policies control network traffic between namespaces and between pods in the same namespace. Bill, the cluster admin, can enforce network traffic isolation between different tenants while leaving to Alice, the tenant owner, the freedom to set isolation between namespaces in the same tenant or even between pods in the same namespace.
@@ -1133,7 +1314,7 @@ Bill can set network policies in the tenant manifest, according to the requireme
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1234,7 +1415,7 @@ To avoid this kind of attack, Bill, the cluster admin, can force Alice, the tena
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1260,7 +1441,7 @@ The spec `containerRegistries` addresses this task and can provide a combination
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1316,7 +1497,7 @@ Bill can assign this role to any namespace in the Alice's tenant by setting it i
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1371,7 +1552,7 @@ Starting from Capsule **v0.1.1**, this can be done using a special annotation in
Imagine the case where a Custom Resource named `MySQL` in the API group `databases.acme.corp/v1` usage must be limited in the Tenant `oil`: this can be done as follows.
```yaml
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1398,7 +1579,7 @@ spec:
When `alice` will create a `MySQL` instance in one of their Tenant Namespace, the Cluster Administrator can easily retrieve the overall usage.
```yaml
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1420,7 +1601,7 @@ Assigns additional labels and annotations to all namespaces created in the `oil`
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1452,7 +1633,7 @@ metadata:
capsule.clastix.io/backup: "true"
name: oil-production
ownerReferences:
- - apiVersion: capsule.clastix.io/v1beta1
+ - apiVersion: capsule.clastix.io/v1beta2
blockOwnerDeletion: true
controller: true
kind: Tenant
@@ -1470,7 +1651,7 @@ Assigns additional labels and annotations to all services created in the `oil` t
```yaml
kubectl apply -f - << EOF
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1516,20 +1697,36 @@ Bill needs to cordon a Tenant and its Namespaces for several reasons:
With this said, the Tenant Owner and the related Service Account living into managed Namespaces, cannot proceed to any update, create or delete action.
-This is possible just labeling the Tenant as follows:
+This is possible by just toggling the specific Tenant specification:
```shell
-kubectl label tenant oil capsule.clastix.io/cordon=enabled
-tenant oil labeled
+apiVersion: capsule.clastix.io/v1beta2
+kind: Tenant
+metadata:
+ name: oil
+spec:
+ cordoned: true
+ owners:
+ - kind: User
+ name: alice
```
Any operation performed by Alice, the Tenant Owner, will be rejected by the Admission controller.
-Uncordoning can be done by removing the said label:
+Uncordoning can be done by removing the said specification key:
```shell
-$ kubectl label tenant oil capsule.clastix.io/cordon-
-tenant.capsule.clastix.io/oil labeled
+$ cat < A generic example could be the container registry secrets, especially in the context where the Tenants can just use a specific registry.
+
+Starting from Capsule v0.2.0, a new set of Custom Resource Definitions have been introduced, such as the `GlobalTenantResource`, let's start with a potential use-case using the personas described at the beginning of this document.
+
+**Bill** created the Tenants for **Alice** using the `Tenant` CRD, and labels these resources using the following command:
+
+```
+$: kubectl label tnt/oil energy=fossil
+tenant oil labeled
+
+$: kubectl label tnt/gas energy=fossil
+tenant oil labeled
+```
+
+In the said scenario, these Tenants must use container images from a trusted registry, and that would require the usage of specific credentials for the image pull.
+
+The said container registry is deployed in the cluster in the namespace `harbor-system`, and this Namespace contains all image pull secret for each Tenant, e.g.: a secret named `harbor-system/fossil-pull-secret` as follows.
+
+```
+$: kubectl -n harbor-system get secret --show-labels
+NAME TYPE DATA AGE LABELS
+fossil-pull-secret Opaque 1 28s tenant=fossil
+```
+
+These credentials would be distributed to the Tenant owners manually, or vice-versa, the owners would require those.
+Such a scenario would be against the concept of the self-service solution offered by Capsule, and **Bill** can solve this by creating the `GlobalTenantResource` as follows.
+
+```yaml
+apiVersion: capsule.clastix.io/v1beta2
+kind: GlobalTenantResource
+metadata:
+ name: fossil-pull-secrets
+spec:
+ tenantSelector:
+ matchLabels:
+ energy: fossil
+ resyncPeriod: 60s
+ resources:
+ - namespacedItems:
+ - apiVersion: v1
+ kind: Secret
+ namespace: harbor-system
+ selector:
+ matchLabels:
+ tenant: fossil
+```
+
+A full reference of the API is available in the [CRDs API section](/docs/general/crds-apis), just explaining the expected behaviour and the resulting outcome:
+
+> Capsule will select all the Tenant resources according to the key `tenantSelector`.
+> Each object defined in the `namespacedItems` and matching the provided `selector` will be replicated into each Namespace bounded to the selected Tenants.
+> Capsule will check every 60 seconds if the resources are replicated and in sync, as defined in the key `resyncPeriod`.
+
+The `GlobalTenantResource` is a cluster-scoped resource, thus it has been designed for cluster administrators and cannot be used by Tenant owners: for that purpose, the `TenantResource` one can help.
+
+## Replicating resources across Namespaces of a Tenant
+
+Although Capsule is supporting a few amounts of personas, it can be used to allow building an Internal Developer Platform used barely by Tenant owners, or users created by these thanks to Service Account.
+
+In a such scenario, a Tenant Owner would like to distribute resources across all the Namespace of their Tenant, without the need to establish a manual procedure, or the need for writing a custom automation.
+
+The Namespaced-scope API `TenantResource` allows to replicate resources across the Tenant's Namespace.
+
+> The Tenant owners must have proper RBAC configured in order to create, get, update, and delete their `TenantResource` CRD instances.
+> This can be achieved using the Tenant key `additionalRoleBindings` or a custom Tenant owner role, compared to the default one (`admin`).
+
+For our example, **Alice**, the project lead for the `solar` tenant, wants to provision automatically a **DataBase** resource for each Namespace of their Tenant: these are the Namespace list.
+
+```
+$: kubectl get namespaces -l capsule.clastix.io/tenant=solar --show-labels
+NAME STATUS AGE LABELS
+solar-1 Active 59s capsule.clastix.io/tenant=solar,environment=production,kubernetes.io/metadata.name=solar-1,name=solar-1
+solar-2 Active 58s capsule.clastix.io/tenant=solar,environment=production,kubernetes.io/metadata.name=solar-2,name=solar-2
+solar-system Active 62s capsule.clastix.io/tenant=solar,kubernetes.io/metadata.name=solar-system,name=solar-system
+```
+
+**Alice** creates a `TenantResource` in the Tenant namespace `solar-system` as follows.
+
+```yaml
+apiVersion: capsule.clastix.io/v1beta2
+kind: TenantResource
+metadata:
+ name: solar-db
+ namespace: solar-system
+spec:
+ resyncPeriod: 60s
+ resources:
+ - namespaceSelector:
+ matchLabels:
+ environment: production
+ rawItems:
+ - apiVersion: postgresql.cnpg.io/v1
+ kind: Cluster
+ metadata:
+ name: postgresql
+ spec:
+ description: PostgreSQL cluster for the Solar project
+ instances: 3
+ postgresql:
+ pg_hba:
+ - hostssl app all all cert
+ primaryUpdateStrategy: unsupervised
+ storage:
+ size: 1Gi
+```
+
+The expected result will be the object `Cluster` for the API version `postgresql.cnpg.io/v1` to get created in all the Solar tenant namespaces matching the label selector declared by the key `namespaceSelector`.
+
+```
+$: kubectl get clusters.postgresql.cnpg.io -A
+NAMESPACE NAME AGE INSTANCES READY STATUS PRIMARY
+solar-1 postgresql 80s 3 3 Cluster in healthy state postgresql-1
+solar-2 postgresql 80s 3 3 Cluster in healthy state postgresql-1
+```
+
+The `TenantResource` object has been created in the namespace `solar-system` that doesn't satisfy the Namespace selector. Furthermore, Capsule will automatically inject the required labels to avoid a `TenantResource` could start polluting other Namespaces.
+
+Eventually, using the key `namespacedItem`, it is possible to reference existing objects to get propagated across the other Tenant namespaces: in this case, a Tenant Owner can just refer to objects in their Namespaces, preventing a possible escalation referring to non owned objects.
+
+As with `GlobalTenantResource`, the full reference of the API is available in the [CRDs API section](/docs/general/crds-apis).
+
+## Preventing PersistentVolume cross mounting across Tenants
+
+Any Tenant owner is able to create a `PersistentVolumeClaim` that, backed by a given _StorageClass_, will provide volumes for their applications.
+
+In most cases, once a `PersistentVolumeClaim` is deleted, the bounded `PersistentVolume` will be recycled due.
+
+However, in some scenarios, the `StorageClass` or the provisioned `PersistentVolume` itself could change the retention policy of the volume, keeping it available for recycling and being consumable for another Pod.
+
+In such a scenario, Capsule enforces the Volume mount only to the Namespaces belonging to the Tenant on which it's been consumed, by adding a label to the Volume as follows.
+
+```yaml
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ annotations:
+ pv.kubernetes.io/provisioned-by: rancher.io/local-path
+ creationTimestamp: "2022-12-22T09:54:46Z"
+ finalizers:
+ - kubernetes.io/pv-protection
+ labels:
+ capsule.clastix.io/tenant: atreides
+ name: pvc-1b3aa814-3b0c-4912-9bd9-112820da38fe
+ resourceVersion: "2743059"
+ uid: 9836ae3e-4adb-41d2-a416-0c45c2da41ff
+spec:
+ accessModes:
+ - ReadWriteOnce
+ capacity:
+ storage: 10Gi
+ claimRef:
+ apiVersion: v1
+ kind: PersistentVolumeClaim
+ name: melange
+ namespace: caladan
+ resourceVersion: "2743014"
+ uid: 1b3aa814-3b0c-4912-9bd9-112820da38fe
+```
+
+Once the `PeristentVolume` become available again, it can be referenced by any `PersistentVolumeClaim` in the `atreides` Tenant Namespace resources.
+
+If another Tenant, like `harkonnen`, tries to use it, it will get an error:
+
+```
+$: k describe pv pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d
+Name: pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d
+Labels: capsule.clastix.io/tenant=atreides
+Annotations: pv.kubernetes.io/provisioned-by: rancher.io/local-path
+Finalizers: [kubernetes.io/pv-protection]
+StorageClass: standard
+Status: Available
+...
+
+$: cat /tmp/pvc.yaml
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: melange
+ namespace: harkonnen
+spec:
+ storageClassName: standard
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 3Gi
+ volumeName: pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d
+
+$: k apply -f /tmp/pvc.yaml
+Error from server: error when creating "/tmp/pvc.yaml": admission webhook "pvc.capsule.clastix.io" denied the request: PeristentVolume pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d cannot be used by the following Tenant, preventing a cross-tenant mount
+```
+
---
This ends our tutorial on how to implement complex multi-tenancy and policy-driven scenarios with Capsule. As we improve it, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future.
diff --git a/docs/content/guides/assets/proxy-kubernetes-dashboard.png b/docs/content/guides/assets/proxy-kubernetes-dashboard.png
new file mode 100644
index 00000000..58e45a05
Binary files /dev/null and b/docs/content/guides/assets/proxy-kubernetes-dashboard.png differ
diff --git a/docs/content/guides/flux2-capsule.md b/docs/content/guides/flux2-capsule.md
index 78e0cd63..c06567ca 100644
--- a/docs/content/guides/flux2-capsule.md
+++ b/docs/content/guides/flux2-capsule.md
@@ -77,7 +77,7 @@ metadata:
name: gitops-reconciler
namespace: my-tenant
---
-apiVersion: capsule.clastix.io/v1beta1
+apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: my-tenant
@@ -93,7 +93,7 @@ From now on, we'll refer to it as the **Tenant GitOps Reconciler**.
We also need to state that Capsule should enforce tenant access control for requests coming from tenants, and we can do that by specifying one of the `Group`s bound by default by Kubernetes to the Tenant GitOps Reconciler `ServiceAccount` in the `CapsuleConfiguration`:
```yaml
-apiVersion: capsule.clastix.io/v1alpha1
+apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: default
@@ -238,7 +238,7 @@ this is the required set of resources to setup a Tenant:
- Additional binding to *cluster-admin* `ClusterRole` for the Tenant's `Namespace`s and `Namespace` of the Tenant GitOps Reconciler' `ServiceAccount`.
By default Capsule binds only `admin` ClusterRole, which has no privileges over Custom Resources, but *cluster-admin* has. This is needed to operate on Flux CRs:
```yaml
- apiVersion: capsule.clastix.io/v1beta1
+ apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: my-tenant
diff --git a/docs/content/guides/kubernetes-dashboard.md b/docs/content/guides/kubernetes-dashboard.md
new file mode 100644
index 00000000..567b109a
--- /dev/null
+++ b/docs/content/guides/kubernetes-dashboard.md
@@ -0,0 +1,145 @@
+# Kubernetes Dashboard
+
+This guide describes how to integrate the [Kubernetes Dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/) and [Capsule Proxy](https://capsule.clastix.io/docs/general/proxy/) with OIDC authorization.
+
+In this guide, we will use [Keycloak](https://www.keycloak.org) as the Identity Provider.
+
+![Kubernetes Dashboard](./assets/proxy-kubernetes-dashboard.png)
+
+## Configuring oauth2-proxy
+
+To enable the proxy authorization from the Kubernetes dashboard to Keycloak, we need to use an OAuth proxy.
+In this article, we will use [oauth2-proxy](https://oauth2-proxy.github.io/oauth2-proxy/) and install it as a pod in the Kubernetes Dashboard namespace.
+Alternatively, we can install `oauth2-proxy` in a different namespace or use it as a sidecar container in the Kubernetes Dashboard deployment.
+
+Prepare the values for oauth2-proxy:
+```bash
+cat > values-oauth2-proxy.yaml < Values used for the config:
+>
+> - **OIDC_CLIENT_ID**: the keycloak client ID (name) which user in Kubernetes API Server for authorization
+> - **OIDC_CLIENT_SECRET**: secret for the client (`OIDC_CLIENT_ID`). You can see it from the Keycloack UI -> Clients -> `OIDC_CLIENT_ID` -> Credentials
+> - **DASHBOARD_URL**: the Kubernetes Dashboard URL
+> - **KEYCLOAK_URL**: the Keycloak URL
+
+More information about the `keycloak-oidc` provider can be found on the [oauth2-proxy documentation](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#keycloak-oidc-auth-provider).
+
+We're ready to install the `oauth2-proxy`:
+
+```bash
+helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests
+helm install oauth2-proxy oauth2-proxy/oauth2-proxy -n ${KUBERNETES_DASHBOARD_NAMESPACE} -f values-oauth2-proxy.yaml
+```
+
+## Configuring Keycloak
+
+The Kubernetes cluster must be configured with a valid OIDC provider: for our guide, we're giving for granted that Keycloak is used, if you need more info please follow the [OIDC Authentication](/docs/guides/oidc-auth) section.
+
+In a such scenario, you should have in the `kube-apiserver.yaml` manifest the following content:
+```yaml
+spec:
+ containers:
+ - command:
+ - kube-apiserver
+ ...
+ - --oidc-issuer-url=https://${OIDC_ISSUER}
+ - --oidc-ca-file=/etc/kubernetes/oidc/ca.crt
+ - --oidc-client-id=${OIDC_CLIENT_ID}
+ - --oidc-username-claim=preferred_username
+ - --oidc-groups-claim=groups
+ - --oidc-username-prefix=-
+```
+
+Where `${OIDC_CLIENT_ID}` refers to the client ID that all tokens must be issued.
+
+For this client we need:
+1. Check `Valid Redirect URIs`: in the `oauth2-proxy` configuration we set `redirect-url: "https://${DASHBOARD_URL}/oauth2/callback"`, it needs to add this path to the `Valid Redirect URIs`
+2. Create a mapper with Mapper Type 'Group Membership' and Token Claim Name 'groups'.
+3. Create a mapper with Mapper Type 'Audience' and Included Client Audience and Included Custom Audience set to your client name(OIDC_CLIENT_ID).
+
+## Configuring Kubernetes Dashboard
+
+If your Capsule Proxy uses HTTPS and the CA certificate is not the Kubernetes CA, you need to add a secret with the CA for the Capsule Proxy URL.
+```bash
+cat > ca.crt<< EOF
+-----BEGIN CERTIFICATE-----
+...
+...
+...
+-----END CERTIFICATE-----
+EOF
+
+kubectl create secret generic certificate --from-file=ca.crt=ca.crt -n ${KUBERNETES_DASHBOARD_NAMESPACE}
+```
+
+Prepare the values for the Kubernetes Dashboard:
+```bash
+cat > values-kubernetes-dashboard.yaml <=0.4.0 is expected for Capsule >=v0.3.0, you can fetch the full list of available charts with the following command.
+
+```
+helm search repo -l clastix/capsule
+```
+
+Since the Tenant custom resource definition has been patched with new fields, we can install back Capsule using the provided Helm chart.
+
+```
+helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace --version 0.4.0
+```
+
+This will start the Operator with the latest changes, and perform the required sync operations like:
+
+1. Ensuring the CA is still valid
+2. Ensuring a TLS certificate is valid for the local webhook server
+3. If not using the cert-manager integration, patching the Validating and Mutating Webhook Configuration resources with the Capsule CA
+4. If not using the cert-manager integration, patching the Capsule's Custom Resource Definitions conversion webhook fields with the Capsule CA
+
+# Upgrading from v0.1.3 to v0.2.x
+
+## Scale down the Capsule controller
+
+Using the `kubectl` or Helm, scale down the Capsule controller manager: this is required to avoid the old Capsule version from processing objects that aren't yet installed as a CRD.
+
+```
+helm upgrade -n capsule-system capsule --set "replicaCount=0"
+```
+
+> Ensure that all the Pods have been removed correctly.
+
+## Migrate manually the `CapsuleConfiguration` to the latest API version
+
+With the v0.2.x release of Capsule and the new features introduced, the resource `CapsuleConfiguration` is offering a new API version, bumped to `v1beta1` from `v1alpha1`.
+
+Essentially, the `CapsuleConfiguration` is storing configuration flags that allow Capsule to be configured on the fly without requiring the operator to reload.
+This resource is read at the operator init-time when the conversion webhook offered by Capsule is not yet ready to serve any request.
+
+Migrating from v0.1.3 to v0.2.x requires a manual conversion of your `CapsuleConfiguration` according to the latest version (currently, `v1beta2`).
+You can find further information about it at the section `CRDs APIs`.
+
+The deletion of the `CapsuleConfiguration` resource is required, along with the update of the related CRD.
+
+```
+kubectl delete capsuleconfiguration default
+kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/capsuleconfiguration-crd.yaml
+```
+
+During the Helm upgrade, a new `CapsuleConfiguration` will be created: please, refer to the Helm Chart values to pick up your desired settings.
+
+## Patch the Tenant custom resource definition
+
+Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, additional details can be found [here](https://github.com/helm/community/blob/f9e06c16d89ccea1bea77c01a6a96ae3b309f823/architecture/crds.md).
+
+This process must be executed manually as follows:
+
+```
+kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/globaltenantresources-crd.yaml
+kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/tenant-crd.yaml
+kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/tenantresources-crd.yaml
+```
+
+> We're giving for granted that Capsule is installed in the `capsule-system` Namespace.
+> According to your needs you can change the Namespace at your wish, e.g.:
+>
+> ```bash
+> CUSTOM_NS="tenancy-operations"
+>
+> for CR in capsuleconfigurations.capsule.clastix.io globaltenantresources.capsule.clastix.io tenantresources.capsule.clastix.io tenants.capsule.clastix.io; do
+> kubectl patch crd capsuleconfigurations.capsule.clastix.io --type='json' -p=" [{'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig/service/namespace', 'value': "${CUSTOM_NS}"}]"
+> done
+> ```
+
+## Update your Capsule Helm chart
+
+Ensure to update the Capsule repository to fetch the latest changes.
+
+```
+helm repo update
+```
+
+The latest Chart must be used, at the current time, >0.3.0 is expected for Capsule >v0.2.0, you can fetch the full list of available charts with the following command.
+
+```
+helm search repo -l clastix/capsule
+```
+
+Since the Tenant custom resource definition has been patched with new fields, we can install back Capsule using the provided Helm chart.
+
+```
+helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace --version 0.3.0
+```
+
+This will start the Operator with the latest changes, and perform the required sync operations like:
+
+1. Ensuring the CA is still valid
+2. Ensuring a TLS certificate is valid for the local webhook server
+3. If not using the cert-manager integration, patching the Validating and Mutating Webhook Configuration resources with the Capsule CA
+4. If not using the cert-manager integration, patching the Capsule's Custom Resource Definitions conversion webhook fields with the Capsule CA
+
+## Ensure the conversion webhook is working
+
+Kubernetes Custom Resource definitions provide a conversion webhook that is used by an Operator to perform a seamless conversion between resources with different versioning.
+
+With the fresh new installation, Capsule patches all the required moving parts to ensure this conversion is put in place and uses the latest version (actually, `v1beta2`) for presenting the Tenant resources.
+
+You can check this behaviour by issuing the following command:
+
+```
+$: kubectl get tenants.v1beta2.capsule.clastix.io
+NAME NAMESPACE QUOTA NAMESPACE COUNT OWNER NAME OWNER KIND NODE SELECTOR AGE
+oil 3 0 alice User {"kubernetes.io/os":"linux"} 3m43s
+```
+
+You should see all the previous Tenant resources converted in the new format and structure.
+
+```
+$: kubectl get tenants.v1beta2.capsule.clastix.io
+NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE
+oil Active 3 0 {"kubernetes.io/os":"linux"} 3m38s
+```
+
+> Resources are still persisted in etcd using the previous Tenant version (`v1beta1`) and the conversion is executed on-the-fly thanks to the conversion webhook.
+> If you'd like to decrease the pressure on Capsule due to the conversion webhook, we suggest performing a resource patching using the command `kubectl replace`:
+> in this way, the API Server will update the etcd key with the specification according to the new versioning, allowing to skip the conversion.
+>
+> The `kubectl replace` command must be triggered when the Capsule webhook is up and running to allow the conversion between versions.
+
+# Upgrading from < v0.1.0 up to v0.1.3
## Uninstall the old Capsule release
-If you're using Helm as package manager, all the Operator resources such as Deployment, Service, Role Binding, and etc. must be deleted.
+If you're using Helm as package manager, all the Operator resources such as Deployment, Service, Role Binding, etc. must be deleted.
```
helm uninstall -n capsule-system capsule
@@ -21,12 +187,12 @@ Ensure that everything has been removed correctly, especially the Secret resourc
## Patch the Tenant custom resource definition
-Helm doesn't manage the lifecycle of Custom Resource Definitions, additional details can be found [here](https://github.com/helm/community/blob/f9e06c16d89ccea1bea77c01a6a96ae3b309f823/architecture/crds.md).
+Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, additional details can be found [here](https://github.com/helm/community/blob/f9e06c16d89ccea1bea77c01a6a96ae3b309f823/architecture/crds.md).
This process must be executed manually as follows:
```
-kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.1.0/config/crd/bases/capsule.clastix.io_tenants.yaml
+kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.1.0/charts/capsule/crds/tenant-crd.yaml
```
> Please note the Capsule version in the said URL, your mileage may vary according to the desired upgrading version.
@@ -36,23 +202,27 @@ kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.1.0/config
Since the Tenant custom resource definition has been patched with new fields, we can install back Capsule using the provided Helm chart.
```
-helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace
+helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace --version=DESIRED_VERSION
```
-This will start the Operator that will perform several required actions, such as:
+> Please, note the `DESIRED_VERSION`: you have to pick the Helm chart version according to the Capsule version you'd like to upgrade to.
+>
+> You can retrieve it by browsing the GitHub source code picking the Capsule tag as ref and inspecting the file `Chart.yaml` available in the folder `charts/capsule`.
+
+This will start the operator that will perform several required actions, such as:
-1. Generating a new CA
-2. Generating new TLS certificates for the local webhook server
-3. Patching the Validating and Mutating Webhook Configuration resources with the fresh new CA
+1. Generating a new CA
+2. Generating new TLS certificates for the local webhook server
+3. Patching the Validating and Mutating Webhook Configuration resources with the fresh new CA
4. Patching the Custom Resource Definition tenant conversion webhook CA
## Ensure the conversion webhook is working
-Kubernetes Custom Resource definitions provide a conversion webhook that is used by an Operator to perform seamless conversion between resources with different versioning.
+Kubernetes Custom Resource definitions provide a conversion webhook that is used by an Operator to perform a seamless conversion between resources with different versioning.
-With the fresh new installation, Capsule patched all the required moving parts to ensure this conversion is put in place, and using the latest version (actually, `v1beta1`) for presenting the Tenant resources.
+With the fresh new installation, Capsule patched all the required moving parts to ensure this conversion is put in place and using the latest version (actually, `v1beta1`) for presenting the Tenant resources.
-You can check this behavior by issuing the following command:
+You can check this behaviour by issuing the following command:
```
$: kubectl get tenants.v1beta1.capsule.clastix.io
@@ -60,7 +230,7 @@ NAME NAMESPACE QUOTA NAMESPACE COUNT OWNER NAME OWNER KIND NODE SELECT
oil 3 0 alice User {"kubernetes.io/os":"linux"} 3m43s
```
-You should see all the previous Tenant resources converted in the new format and structure.
+You should see all the previous Tenant resources converted into the new format and structure.
```
$: kubectl get tenants.v1beta1.capsule.clastix.io
@@ -68,6 +238,5 @@ NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR
oil Active 3 0 {"kubernetes.io/os":"linux"} 3m38s
```
-> Resources are still persisted in etcd using the `v1alpha1` specification and the conversion is executed on-the-fly thanks to the conversion webhook.
-> If you'd like to decrease the pressure on Capsule due to the conversion webhook, we suggest performing a resource patching using the command `kubectl replace`:
-> in this way, the API Server will update the etcd key with the specification according to the new versioning, allowing to skip the conversion.
+> Resources are still persisted in etcd using the v1alpha1 specification and the conversion is executed on-the-fly thanks to the conversion webhook.
+> If you'd like to decrease the pressure on Capsule due to the conversion webhook, we suggest performing a resource patching using the command kubectl replace: in this way, the API Server will update the etcd key with the specification according to the new versioning, allowing to skip the conversion.
diff --git a/docs/gridsome.server.js b/docs/gridsome.server.js
index f85a7649..ad2ccbb8 100644
--- a/docs/gridsome.server.js
+++ b/docs/gridsome.server.js
@@ -39,7 +39,7 @@ module.exports = function (api) {
},
{
label: 'CRDs APIs',
- path: '/docs/general/tenant-crd'
+ path: '/docs/general/crds-apis'
},
{
label: 'Multi-Tenant Benchmark',
@@ -66,12 +66,16 @@ module.exports = function (api) {
label: 'Monitoring Capsule',
path: '/docs/guides/monitoring'
},
+ {
+ label: 'Kubernetes Dashboard',
+ path: '/docs/guides/kubernetes-dashboard'
+ },
{
label: 'Backup & Restore with Velero',
path: '/docs/guides/velero'
},
{
- label: 'Upgrading Tenant version',
+ label: 'Upgrading Capsule',
path: '/docs/guides/upgrading'
},
{
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 7c15871f..b99a761d 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -2810,38 +2810,6 @@
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
},
- "body-parser": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
- "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
- "requires": {
- "bytes": "3.1.0",
- "content-type": "~1.0.4",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "on-finished": "~2.3.0",
- "qs": "6.7.0",
- "raw-body": "2.4.0",
- "type-is": "~1.6.17"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- }
- }
- },
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -3734,11 +3702,6 @@
}
}
},
- "cookie": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
- "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
- },
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -4269,9 +4232,9 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"decode-uri-component": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
},
"decompress": {
"version": "4.2.1",
@@ -4501,11 +4464,6 @@
"minimalistic-assert": "^1.0.0"
}
},
- "destroy": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
- "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
- },
"detab": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz",
@@ -5124,42 +5082,89 @@
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"express": {
- "version": "4.17.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
- "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+ "version": "4.18.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+ "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"requires": {
- "accepts": "~1.3.7",
+ "accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.19.0",
- "content-disposition": "0.5.3",
+ "body-parser": "1.20.1",
+ "content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.4.0",
+ "cookie": "0.5.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
- "depd": "~1.1.2",
+ "depd": "2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "~1.1.2",
+ "finalhandler": "1.2.0",
"fresh": "0.5.2",
+ "http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
- "on-finished": "~2.3.0",
+ "on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.5",
- "qs": "6.7.0",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.11.0",
"range-parser": "~1.2.1",
- "safe-buffer": "5.1.2",
- "send": "0.17.1",
- "serve-static": "1.14.1",
- "setprototypeof": "1.1.1",
- "statuses": "~1.5.0",
+ "safe-buffer": "5.2.1",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"dependencies": {
+ "accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "requires": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ }
+ },
+ "body-parser": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+ "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "requires": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.1",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ }
+ },
+ "bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
+ },
+ "content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "requires": {
+ "safe-buffer": "5.2.1"
+ }
+ },
+ "cookie": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -5168,15 +5173,154 @@
"ms": "2.0.0"
}
},
+ "depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+ },
+ "destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
+ },
+ "finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ }
+ },
+ "http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "requires": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ }
+ },
+ "mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+ },
+ "mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "requires": {
+ "mime-db": "1.52.0"
+ }
+ },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+ },
+ "on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "requires": {
+ "ee-first": "1.1.1"
+ }
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
- "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
+ "qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
+ },
+ "raw-body": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+ "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "requires": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
+ },
+ "toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
}
}
},
@@ -5447,35 +5591,6 @@
"to-regex-range": "^5.0.1"
}
},
- "finalhandler": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
- "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
- "requires": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "statuses": "~1.5.0",
- "unpipe": "~1.0.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- }
- }
- },
"find-cache-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
@@ -6619,25 +6734,6 @@
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
},
- "http-errors": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
- "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
- "requires": {
- "depd": "~1.1.2",
- "inherits": "2.0.3",
- "setprototypeof": "1.1.1",
- "statuses": ">= 1.5.0 < 2",
- "toidentifier": "1.0.0"
- },
- "dependencies": {
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
- }
- }
- },
"http-parser-js": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz",
@@ -8616,14 +8712,6 @@
"es-abstract": "^1.19.1"
}
},
- "on-finished": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
- "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
- "requires": {
- "ee-first": "1.1.1"
- }
- },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -10859,11 +10947,6 @@
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
- "qs": {
- "version": "6.7.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
- },
"query-string": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
@@ -10922,17 +11005,6 @@
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
- "raw-body": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
- "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
- "requires": {
- "bytes": "3.1.0",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
- }
- },
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -11476,9 +11548,9 @@
},
"dependencies": {
"qs": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
}
}
},
@@ -11858,48 +11930,6 @@
}
}
},
- "send": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
- "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
- "requires": {
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "destroy": "~1.0.4",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "~1.7.2",
- "mime": "1.6.0",
- "ms": "2.1.1",
- "on-finished": "~2.3.0",
- "range-parser": "~1.2.1",
- "statuses": "~1.5.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- },
- "dependencies": {
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- }
- }
- },
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
- }
- }
- },
"serialize-javascript": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
@@ -11908,17 +11938,6 @@
"randombytes": "^2.1.0"
}
},
- "serve-static": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
- "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
- "requires": {
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.17.1"
- }
- },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -11950,11 +11969,6 @@
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
},
- "setprototypeof": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
- },
"sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
@@ -12846,9 +12860,9 @@
}
},
"anymatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
- "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
@@ -12928,9 +12942,9 @@
}
},
"cosmiconfig": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
- "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
"dev": true,
"requires": {
"@types/parse-json": "^4.0.0",
diff --git a/docs/yarn.lock b/docs/yarn.lock
index 3fec7974..cdf3b549 100644
--- a/docs/yarn.lock
+++ b/docs/yarn.lock
@@ -1455,13 +1455,13 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
-accepts@^1.3.7, accepts@~1.3.7:
- version "1.3.7"
- resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
- integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
+accepts@^1.3.7, accepts@~1.3.8:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
- mime-types "~2.1.24"
- negotiator "0.6.2"
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
acorn-node@^1.6.1:
version "1.8.2"
@@ -1654,7 +1654,7 @@ array-find-index@^1.0.1:
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
- integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
+ integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
array-union@^1.0.1, array-union@^1.0.2:
version "1.0.2"
@@ -1953,21 +1953,23 @@ bn.js@^5.0.0, bn.js@^5.1.1:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
-body-parser@1.19.0:
- version "1.19.0"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
- integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
+body-parser@1.20.1:
+ version "1.20.1"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
+ integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
dependencies:
- bytes "3.1.0"
+ bytes "3.1.2"
content-type "~1.0.4"
debug "2.6.9"
- depd "~1.1.2"
- http-errors "1.7.2"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
iconv-lite "0.4.24"
- on-finished "~2.3.0"
- qs "6.7.0"
- raw-body "2.4.0"
- type-is "~1.6.17"
+ on-finished "2.4.1"
+ qs "6.11.0"
+ raw-body "2.5.1"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
boolbase@^1.0.0, boolbase@~1.0.0:
version "1.0.0"
@@ -2156,6 +2158,11 @@ bytes@3.1.0, bytes@^3.0.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
cacache@^12.0.2:
version "12.0.4"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c"
@@ -2698,12 +2705,12 @@ constants-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
-content-disposition@0.5.3, content-disposition@^0.5.2:
- version "0.5.3"
- resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
- integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
+content-disposition@0.5.4, content-disposition@^0.5.2:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+ integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
dependencies:
- safe-buffer "5.1.2"
+ safe-buffer "5.2.1"
content-type@^1.0.4, content-type@~1.0.4:
version "1.0.4"
@@ -2720,12 +2727,12 @@ convert-source-map@^1.7.0:
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
- integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
+ integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
-cookie@0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
- integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
+cookie@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
+ integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
copy-concurrently@^1.0.0:
version "1.0.5"
@@ -3135,9 +3142,9 @@ decamelize@^1.1.2:
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decode-uri-component@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
- integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
+ integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
decompress-response@^3.2.0, decompress-response@^3.3.0:
version "3.3.0"
@@ -3284,6 +3291,11 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@@ -3297,10 +3309,10 @@ des.js@^1.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
-destroy@~1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
- integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
detab@^2.0.0:
version "2.0.4"
@@ -3517,7 +3529,7 @@ ecc-jsbn@~0.1.1:
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
- integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.3.867:
version "1.3.867"
@@ -3555,7 +3567,7 @@ emojis-list@^3.0.0:
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
- integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
@@ -3697,7 +3709,7 @@ esutils@^2.0.2:
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
- integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
eventemitter3@^3.1.0:
version "3.1.2"
@@ -3810,37 +3822,38 @@ express-graphql@^0.9.0:
raw-body "^2.4.1"
express@^4.16.4:
- version "4.17.1"
- resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
- integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
+ version "4.18.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
+ integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
dependencies:
- accepts "~1.3.7"
+ accepts "~1.3.8"
array-flatten "1.1.1"
- body-parser "1.19.0"
- content-disposition "0.5.3"
+ body-parser "1.20.1"
+ content-disposition "0.5.4"
content-type "~1.0.4"
- cookie "0.4.0"
+ cookie "0.5.0"
cookie-signature "1.0.6"
debug "2.6.9"
- depd "~1.1.2"
+ depd "2.0.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
- finalhandler "~1.1.2"
+ finalhandler "1.2.0"
fresh "0.5.2"
+ http-errors "2.0.0"
merge-descriptors "1.0.1"
methods "~1.1.2"
- on-finished "~2.3.0"
+ on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
- proxy-addr "~2.0.5"
- qs "6.7.0"
+ proxy-addr "~2.0.7"
+ qs "6.11.0"
range-parser "~1.2.1"
- safe-buffer "5.1.2"
- send "0.17.1"
- serve-static "1.14.1"
- setprototypeof "1.1.1"
- statuses "~1.5.0"
+ safe-buffer "5.2.1"
+ send "0.18.0"
+ serve-static "1.15.0"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
@@ -4052,17 +4065,17 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
-finalhandler@~1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
- integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
+finalhandler@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
+ integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
escape-html "~1.0.3"
- on-finished "~2.3.0"
+ on-finished "2.4.1"
parseurl "~1.3.3"
- statuses "~1.5.0"
+ statuses "2.0.1"
unpipe "~1.0.0"
find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
@@ -4157,7 +4170,7 @@ fragment-cache@^0.2.1:
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
- integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+ integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
friendly-errors-webpack-plugin@^1.7.0:
version "1.7.0"
@@ -4272,7 +4285,16 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
+get-intrinsic@^1.0.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
+ integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.3"
+
+get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
@@ -4692,6 +4714,11 @@ has-symbols@^1.0.1, has-symbols@^1.0.2:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
has-to-string-tag-x@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
@@ -4955,18 +4982,7 @@ http-cache-semantics@^4.0.0:
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
-http-errors@1.7.2:
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
- integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
- dependencies:
- depd "~1.1.2"
- inherits "2.0.3"
- setprototypeof "1.1.1"
- statuses ">= 1.5.0 < 2"
- toidentifier "1.0.0"
-
-http-errors@1.7.3, http-errors@~1.7.2:
+http-errors@1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
@@ -4977,6 +4993,17 @@ http-errors@1.7.3, http-errors@~1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
http-errors@^1.7.3:
version "1.8.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507"
@@ -6203,7 +6230,7 @@ mdurl@^1.0.1:
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
- integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
memory-fs@^0.4.1:
version "0.4.1"
@@ -6240,7 +6267,7 @@ meow@^3.3.0:
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
- integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+ integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
merge-source-map@^1.1.0:
version "1.1.0"
@@ -6264,7 +6291,7 @@ merge2@^1.2.3, merge2@^1.3.0:
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
- integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
@@ -6306,13 +6333,25 @@ mime-db@1.50.0, mime-db@^1.28.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f"
integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==
-mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.19, mime-types@~2.1.24:
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.19:
version "2.1.33"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb"
integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==
dependencies:
mime-db "1.50.0"
+mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@@ -6469,19 +6508,14 @@ mozjpeg@^6.0.0:
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
- integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
-ms@2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
- integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-ms@^2.1.1:
+ms@2.1.3, ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -6518,10 +6552,10 @@ napi-build-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
-negotiator@0.6.2:
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
- integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2:
version "2.6.2"
@@ -6722,11 +6756,16 @@ object-hash@^2.2.0:
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
-object-inspect@^1.11.0, object-inspect@^1.9.0:
+object-inspect@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
+object-inspect@^1.9.0:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
+ integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
+
object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -6779,10 +6818,10 @@ object.values@^1.1.0:
define-properties "^1.1.3"
es-abstract "^1.19.1"
-on-finished@~2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
- integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
dependencies:
ee-first "1.1.1"
@@ -7091,7 +7130,7 @@ path-parse@^1.0.6:
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
- integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+ integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path-to-regexp@^2.2.1:
version "2.4.0"
@@ -8014,7 +8053,7 @@ proto-list@~1.2.1:
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
-proxy-addr@~2.0.5:
+proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
@@ -8111,15 +8150,17 @@ q@^1.1.2:
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
-qs@6.7.0:
- version "6.7.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
- integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
+qs@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
+ integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
+ dependencies:
+ side-channel "^1.0.4"
qs@~6.5.2:
- version "6.5.2"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
- integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
+ version "6.5.3"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
+ integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
query-string@^5.0.1:
version "5.1.1"
@@ -8180,13 +8221,13 @@ range-parser@^1.2.1, range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
-raw-body@2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
- integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
+raw-body@2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
+ integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
dependencies:
- bytes "3.1.0"
- http-errors "1.7.2"
+ bytes "3.1.2"
+ http-errors "2.0.0"
iconv-lite "0.4.24"
unpipe "1.0.0"
@@ -8615,16 +8656,16 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -8744,24 +8785,24 @@ semver@^7.3.2:
dependencies:
lru-cache "^6.0.0"
-send@0.17.1:
- version "0.17.1"
- resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
- integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
+send@0.18.0:
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
+ integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
dependencies:
debug "2.6.9"
- depd "~1.1.2"
- destroy "~1.0.4"
+ depd "2.0.0"
+ destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
- http-errors "~1.7.2"
+ http-errors "2.0.0"
mime "1.6.0"
- ms "2.1.1"
- on-finished "~2.3.0"
+ ms "2.1.3"
+ on-finished "2.4.1"
range-parser "~1.2.1"
- statuses "~1.5.0"
+ statuses "2.0.1"
serialize-javascript@^3.1.0:
version "3.1.0"
@@ -8777,15 +8818,15 @@ serialize-javascript@^4.0.0:
dependencies:
randombytes "^2.1.0"
-serve-static@1.14.1:
- version "1.14.1"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
- integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
+serve-static@1.15.0:
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
+ integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
- send "0.17.1"
+ send "0.18.0"
set-blocking@~2.0.0:
version "2.0.0"
@@ -9151,7 +9192,12 @@ static-extend@^0.1.1:
define-property "^0.2.5"
object-copy "^0.1.0"
-"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+"statuses@>= 1.5.0 < 2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -9640,6 +9686,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
toposort@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
@@ -9707,7 +9758,7 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
-type-is@~1.6.17, type-is@~1.6.18:
+type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
@@ -9953,7 +10004,7 @@ universalify@^2.0.0:
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
- integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
unquote@~1.1.1:
version "1.1.1"
@@ -10114,7 +10165,7 @@ utila@~0.4:
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
- integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
+ integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0:
version "3.4.0"
@@ -10132,7 +10183,7 @@ validate-npm-package-license@^3.0.1:
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
- integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
vendors@^1.0.0:
version "1.0.4"
diff --git a/e2e/additional_role_bindings_test.go b/e2e/additional_role_bindings_test.go
index 29a7136d..9bca98ba 100644
--- a/e2e/additional_role_bindings_test.go
+++ b/e2e/additional_role_bindings_test.go
@@ -9,27 +9,28 @@ import (
"context"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating a Namespace with an additional Role Binding", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "additional-role-binding",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "dale",
Kind: "User",
},
},
- AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{
+ AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{
{
ClusterRoleName: "crds-rolebinding",
Subjects: []rbacv1.Subject{
diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go
index f63c1d64..d5b0757e 100644
--- a/e2e/allowed_external_ips_test.go
+++ b/e2e/allowed_external_ips_test.go
@@ -8,30 +8,31 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("enforcing an allowed set of Service external IPs", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "allowed-external-ip",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "google",
Kind: "User",
},
},
- ServiceOptions: &capsulev1beta1.ServiceOptions{
- ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{
- Allowed: []capsulev1beta1.AllowedIP{
+ ServiceOptions: &api.ServiceOptions{
+ ExternalServiceIPs: &api.ExternalServiceIPsSpec{
+ Allowed: []api.AllowedIP{
"10.20.0.0/16",
"192.168.1.2/32",
},
@@ -51,7 +52,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
})
It("should fail creating an evil service", func() {
- ns := NewNamespace("evil-service")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
svc := &corev1.Service{
@@ -84,7 +85,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
})
It("should allow the first CIDR block", func() {
- ns := NewNamespace("allowed-service-cidr")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
svc := &corev1.Service{
@@ -117,7 +118,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
})
It("should allow the /32 CIDR block", func() {
- ns := NewNamespace("allowed-service-strict")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
svc := &corev1.Service{
diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go
index 7162daa7..9b499f24 100644
--- a/e2e/container_registry_test.go
+++ b/e2e/container_registry_test.go
@@ -7,29 +7,37 @@ package e2e
import (
"context"
+ "encoding/json"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
+type Patch struct {
+ Op string `json:"op"`
+ Path string `json:"path"`
+ Value string `json:"value"`
+}
+
var _ = Describe("enforcing a Container Registry", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "container-registry",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "matt",
Kind: "User",
},
},
- ContainerRegistries: &capsulev1beta1.AllowedListSpec{
+ ContainerRegistries: &api.AllowedListSpec{
Exact: []string{"docker.io", "myregistry.azurecr.io"},
Regex: `quay\.\w+`,
},
@@ -47,7 +55,7 @@ var _ = Describe("enforcing a Container Registry", func() {
})
It("should add labels to Namespace", func() {
- ns := NewNamespace("registry-labels")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.Name}, ns)).Should(Succeed())
@@ -64,7 +72,7 @@ var _ = Describe("enforcing a Container Registry", func() {
})
It("should deny running a gcr.io container", func() {
- ns := NewNamespace("registry-deny")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
pod := &corev1.Pod{
@@ -86,7 +94,7 @@ var _ = Describe("enforcing a Container Registry", func() {
})
It("should allow using a registry only match", func() {
- ns := NewNamespace("registry-only")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
pod := &corev1.Pod{
@@ -110,8 +118,192 @@ var _ = Describe("enforcing a Container Registry", func() {
}).Should(Succeed())
})
+ It("should deny patching a not matching registry after applying with a matching (Container)", func() {
+ ns := NewNamespace("")
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "container",
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "myregistry.azurecr.io/myapp:latest",
+ },
+ },
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
+
+ return err
+ }).Should(Succeed())
+
+ Eventually(func() error {
+ payload := []Patch{{
+ Op: "replace",
+ Path: "/spec/containers/0/image",
+ Value: "attacker/google-containers/pause-amd64:3.0",
+ }}
+ payloadBytes, _ := json.Marshal(payload)
+ _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{})
+ if err != nil {
+ return err
+ }
+ return nil
+ }).ShouldNot(Succeed())
+ })
+
+ It("should deny patching a not matching registry after applying with a matching (initContainer)", func() {
+ ns := NewNamespace("")
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "container",
+ },
+ Spec: corev1.PodSpec{
+ InitContainers: []corev1.Container{
+ {
+ Name: "init",
+ Image: "myregistry.azurecr.io/myapp:latest",
+ },
+ },
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "myregistry.azurecr.io/myapp:latest",
+ },
+ },
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
+
+ return err
+ }).Should(Succeed())
+
+ Eventually(func() error {
+ payload := []Patch{{
+ Op: "replace",
+ Path: "/spec/initContainers/0/image",
+ Value: "attacker/google-containers/pause-amd64:3.0",
+ }}
+ payloadBytes, _ := json.Marshal(payload)
+ _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{})
+ if err != nil {
+ return err
+ }
+ return nil
+ }).ShouldNot(Succeed())
+ })
+
+ It("should allow patching a matching registry after applying with a matching (Container)", func() {
+ ns := NewNamespace("")
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "container",
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "docker.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
+
+ return err
+ }).Should(Succeed())
+
+ Eventually(func() error {
+ payload := []Patch{{
+ Op: "replace",
+ Path: "/spec/containers/0/image",
+ Value: "myregistry.azurecr.io/google-containers/pause-amd64:3.1",
+ }}
+ payloadBytes, _ := json.Marshal(payload)
+ _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{})
+ if err != nil {
+ return err
+ }
+ return nil
+ }).Should(Succeed())
+ })
+
+ It("should allow patching a matching registry after applying with a matching (initContainer)", func() {
+ ns := NewNamespace("")
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "container",
+ },
+ Spec: corev1.PodSpec{
+ InitContainers: []corev1.Container{
+ {
+ Name: "init",
+ Image: "myregistry.azurecr.io/myapp:latest",
+ },
+ },
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "docker.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
+
+ return err
+ }).Should(Succeed())
+
+ Eventually(func() error {
+ payload := []Patch{{
+ Op: "replace",
+ Path: "/spec/initContainers/0/image",
+ Value: "myregistry.azurecr.io/google-containers/pause-amd64:3.1",
+ }}
+ payloadBytes, _ := json.Marshal(payload)
+ _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{})
+ if err != nil {
+ return err
+ }
+ return nil
+ }).Should(Succeed())
+ })
+
It("should allow using an exact match", func() {
- ns := NewNamespace("registry-list")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
pod := &corev1.Pod{
@@ -136,7 +328,7 @@ var _ = Describe("enforcing a Container Registry", func() {
})
It("should allow using a regex match", func() {
- ns := NewNamespace("registry-regex")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
pod := &corev1.Pod{
diff --git a/e2e/custom_capsule_group_test.go b/e2e/custom_capsule_group_test.go
index 64a20807..750ece2f 100644
--- a/e2e/custom_capsule_group_test.go
+++ b/e2e/custom_capsule_group_test.go
@@ -8,22 +8,20 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
-
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-group", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-assigned-custom-group",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "alice",
Kind: "User",
@@ -43,31 +41,31 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
})
It("should fail using a User non matching the capsule-user-group flag", func() {
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
configuration.Spec.UserGroups = []string{"test"}
})
- ns := NewNamespace("cg-namespace-fail")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
It("should succeed and be available in Tenant namespaces list with multiple groups", func() {
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
configuration.Spec.UserGroups = []string{"test", "alice"}
})
- ns := NewNamespace("cg-namespace-1")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
})
It("should succeed and be available in Tenant namespaces list with default single group", func() {
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
configuration.Spec.UserGroups = []string{"capsule.clastix.io"}
})
- ns := NewNamespace("cg-namespace-2")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
diff --git a/e2e/custom_resource_quota_test.go b/e2e/custom_resource_quota_test.go
index 30c0b14c..dd056d0d 100644
--- a/e2e/custom_resource_quota_test.go
+++ b/e2e/custom_resource_quota_test.go
@@ -9,7 +9,7 @@ import (
"context"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -20,19 +20,19 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes/scheme"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("when Tenant limits custom Resource Quota", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "limiting-resources",
Annotations: map[string]string{
"quota.resources.capsule.clastix.io/foos.test.clastix.io_v1": "3",
},
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "resource",
Kind: "User",
diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go
index 39f54b44..819bc0af 100644
--- a/e2e/disable_externalname_test.go
+++ b/e2e/disable_externalname_test.go
@@ -8,31 +8,32 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating an ExternalName service when it is disabled for Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "disable-external-service",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "google",
Kind: "User",
},
},
- ServiceOptions: &capsulev1beta1.ServiceOptions{
- AllowedServices: &capsulev1beta1.AllowedServices{
- ExternalName: pointer.BoolPtr(false),
+ ServiceOptions: &api.ServiceOptions{
+ AllowedServices: &api.AllowedServices{
+ ExternalName: pointer.Bool(false),
},
},
},
@@ -50,7 +51,7 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan
})
It("should fail creating a service with ExternalService type", func() {
- ns := NewNamespace("disable-external-service")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
EventuallyCreation(func() error {
diff --git a/e2e/disable_ingress_wildcard_test.go b/e2e/disable_ingress_wildcard_test.go
index 4e848e99..38c6a7b6 100644
--- a/e2e/disable_ingress_wildcard_test.go
+++ b/e2e/disable_ingress_wildcard_test.go
@@ -7,31 +7,30 @@ package e2e
import (
"context"
- "errors"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("creating an Ingress with a wildcard when it is denied for the Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "denied-ingress-wildcard",
Annotations: map[string]string{
"capsule.clastix.io/deny-wildcard": "true",
},
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "scott",
Kind: "User",
@@ -54,13 +53,12 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the
It("should fail creating an extensions/v1beta1 Ingress with a wildcard hostname", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("extensions-v1beta1")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -135,13 +133,12 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the
It("should fail creating an networking.k8s.io/v1beta1 Ingress with a wildcard hostname", func() {
if err := k8sClient.List(context.Background(), &networkingv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("networking-v1beta1")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -216,13 +213,12 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the
It("should fail creating an networking.k8s.io/v1 Ingress with a wildcard hostname", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("networking-v1")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go
index 76a050da..c4f52c78 100644
--- a/e2e/disable_loadbalancer_test.go
+++ b/e2e/disable_loadbalancer_test.go
@@ -8,31 +8,32 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "disable-loadbalancer-service",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "amazon",
Kind: "User",
},
},
- ServiceOptions: &capsulev1beta1.ServiceOptions{
- AllowedServices: &capsulev1beta1.AllowedServices{
- LoadBalancer: pointer.BoolPtr(false),
+ ServiceOptions: &api.ServiceOptions{
+ AllowedServices: &api.AllowedServices{
+ LoadBalancer: pointer.Bool(false),
},
},
},
@@ -49,7 +50,7 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant
})
It("should fail creating a service with LoadBalancer type", func() {
- ns := NewNamespace("disable-loadbalancer-service")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go
index d2cd2be7..2e08dced 100644
--- a/e2e/disable_node_ports_test.go
+++ b/e2e/disable_node_ports_test.go
@@ -8,31 +8,32 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating a nodePort service when it is disabled for Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "disable-node-ports",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "google",
Kind: "User",
},
},
- ServiceOptions: &capsulev1beta1.ServiceOptions{
- AllowedServices: &capsulev1beta1.AllowedServices{
- NodePort: pointer.BoolPtr(false),
+ ServiceOptions: &api.ServiceOptions{
+ AllowedServices: &api.AllowedServices{
+ NodePort: pointer.Bool(false),
},
},
},
@@ -49,7 +50,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f
})
It("should fail creating a service with NodePort type", func() {
- ns := NewNamespace("disable-node-ports")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
svc := &corev1.Service{
diff --git a/e2e/dynamic_tenant_owner_clusterroles_test.go b/e2e/dynamic_tenant_owner_clusterroles_test.go
index b7338875..36221053 100644
--- a/e2e/dynamic_tenant_owner_clusterroles_test.go
+++ b/e2e/dynamic_tenant_owner_clusterroles_test.go
@@ -9,31 +9,29 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("defining dynamic Tenant Owner Cluster Roles", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "dynamic-tenant-owner-clusterroles",
- Annotations: map[string]string{
- "clusterrolenames.capsule.clastix.io/user.michonne": "editor,manager",
- "clusterrolenames.capsule.clastix.io/group.kingdom": "readonly",
- },
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
- Name: "michonne",
- Kind: "User",
+ Kind: "User",
+ Name: "michonne",
+ ClusterRoles: []string{"editor", "manager"},
},
{
- Name: "kingdom",
- Kind: "Group",
+ Name: "kingdom",
+ Kind: "Group",
+ ClusterRoles: []string{"readonly"},
},
},
},
diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go
index a2262703..f9ac51b4 100644
--- a/e2e/enable_loadbalancer_test.go
+++ b/e2e/enable_loadbalancer_test.go
@@ -8,31 +8,32 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "enable-loadbalancer-service",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "netflix",
Kind: "User",
},
},
- ServiceOptions: &capsulev1beta1.ServiceOptions{
- AllowedServices: &capsulev1beta1.AllowedServices{
- LoadBalancer: pointer.BoolPtr(true),
+ ServiceOptions: &api.ServiceOptions{
+ AllowedServices: &api.AllowedServices{
+ LoadBalancer: pointer.Bool(true),
},
},
},
@@ -49,7 +50,7 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant"
})
It("should succeed creating a service with LoadBalancer type", func() {
- ns := NewNamespace("enable-loadbalancer-service")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
diff --git a/e2e/enable_node_ports_test.go b/e2e/enable_node_ports_test.go
index b0a27ba7..764b0665 100644
--- a/e2e/enable_node_ports_test.go
+++ b/e2e/enable_node_ports_test.go
@@ -8,22 +8,22 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a nodePort service when it is enabled for Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "enable-node-ports",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "google",
Kind: "User",
@@ -43,7 +43,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", fu
})
It("should allow creating a service with NodePort type", func() {
- ns := NewNamespace("enable-node-ports")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
svc := &corev1.Service{
diff --git a/e2e/forbidden_annotations_regex_test.go b/e2e/forbidden_annotations_regex_test.go
index 74e93041..f2797a84 100644
--- a/e2e/forbidden_annotations_regex_test.go
+++ b/e2e/forbidden_annotations_regex_test.go
@@ -8,76 +8,98 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating a tenant with various forbidden regexes", func() {
- tnt := &capsulev1beta1.Tenant{
- ObjectMeta: metav1.ObjectMeta{
- Annotations: nil,
- Name: "namespace",
- },
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
- {
- Name: "alice",
- Kind: "User",
- },
- },
- },
- }
-
- It("should succeed when there are no annotations", func() {
- EventuallyCreation(func() error {
- tnt.ObjectMeta.Annotations = nil
- tnt.ResourceVersion = ""
- return k8sClient.Create(context.TODO(), tnt)
- }).Should(Succeed())
- Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
- })
-
- annotationsToCheck := []string{
- capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation,
- capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation,
- }
-
- errorRegexes := []string{
- "(.*gitops|.*nsm).[k8s.io/((?!(resource)).*|trusted)](http://k8s.io/((?!(resource)).*%7Ctrusted))",
- }
-
- for _, annotation := range annotationsToCheck {
- for _, annotationValue := range errorRegexes {
- It("should fail using a non-valid the regex on the annotation "+annotation, func() {
- EventuallyCreation(func() error {
- tnt.ResourceVersion = ""
- tnt.ObjectMeta.Annotations = make(map[string]string)
- tnt.ObjectMeta.Annotations[annotation] = annotationValue
- return k8sClient.Create(context.TODO(), tnt)
- }).ShouldNot(Succeed())
- })
- }
- }
+ //errorRegexes := []string{
+ // "(.*gitops|.*nsm).[k8s.io/((?!(resource)).*|trusted)](http://k8s.io/((?!(resource)).*%7Ctrusted))",
+ //}
+ //
+ //for _, annotationValue := range errorRegexes {
+ // It("should fail using a non-valid the regex on the annotation", func() {
+ // tnt := &capsulev1beta2.Tenant{
+ // ObjectMeta: metav1.ObjectMeta{
+ // Name: "namespace",
+ // },
+ // Spec: capsulev1beta2.TenantSpec{
+ // Owners: capsulev1beta2.OwnerListSpec{
+ // {
+ // Name: "alice",
+ // Kind: "User",
+ // },
+ // },
+ // },
+ // }
+ //
+ // EventuallyCreation(func() error {
+ // tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
+ // ForbiddenLabels: api.ForbiddenListSpec{
+ // Regex: annotationValue,
+ // },
+ // }
+ // return k8sClient.Create(context.TODO(), tnt)
+ // }).ShouldNot(Succeed())
+ //
+ // EventuallyCreation(func() error {
+ // tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
+ // ForbiddenAnnotations: api.ForbiddenListSpec{
+ // Regex: annotationValue,
+ // },
+ // }
+ // return k8sClient.Create(context.TODO(), tnt)
+ // }).ShouldNot(Succeed())
+ // })
+ //}
successRegexes := []string{
"",
"(.*gitops|.*nsm)",
}
- for _, annotation := range annotationsToCheck {
- for _, annotationValue := range successRegexes {
- It("should succeed using a valid regex on the annotation "+annotation, func() {
- EventuallyCreation(func() error {
- tnt.ResourceVersion = ""
- tnt.ObjectMeta.Annotations = make(map[string]string)
- tnt.ObjectMeta.Annotations[annotation] = annotationValue
- return k8sClient.Create(context.TODO(), tnt)
- }).Should(Succeed())
- Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
- })
- }
- }
+ for _, annotationValue := range successRegexes {
+ It("should succeed using a valid regex on the annotation", func() {
+ tnt := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "namespace",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "alice",
+ Kind: "User",
+ },
+ },
+ },
+ }
+ EventuallyCreation(func() error {
+ tnt.SetResourceVersion("")
+
+ tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
+ ForbiddenLabels: api.ForbiddenListSpec{
+ Regex: annotationValue,
+ },
+ }
+ return k8sClient.Create(context.TODO(), tnt)
+ }).Should(Succeed())
+ Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+
+ EventuallyCreation(func() error {
+ tnt.SetResourceVersion("")
+
+ tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
+ ForbiddenAnnotations: api.ForbiddenListSpec{
+ Regex: annotationValue,
+ },
+ }
+ return k8sClient.Create(context.TODO(), tnt)
+ }).Should(Succeed())
+ Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ })
+ }
})
diff --git a/e2e/force_tenant_prefix_test.go b/e2e/force_tenant_prefix_test.go
index 6ac706e9..ef763eb4 100644
--- a/e2e/force_tenant_prefix_test.go
+++ b/e2e/force_tenant_prefix_test.go
@@ -8,22 +8,20 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
-
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace with Tenant name prefix enforcement", func() {
- t1 := &capsulev1beta1.Tenant{
+ t1 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "awesome",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
@@ -31,12 +29,12 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun
},
},
}
- t2 := &capsulev1beta1.Tenant{
+ t2 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "awesome-tenant",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
@@ -55,7 +53,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun
return k8sClient.Create(context.TODO(), t2)
}).Should(Succeed())
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
configuration.Spec.ForceTenantPrefix = true
})
})
@@ -63,7 +61,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun
Expect(k8sClient.Delete(context.TODO(), t1)).Should(Succeed())
Expect(k8sClient.Delete(context.TODO(), t2)).Should(Succeed())
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
configuration.Spec.ForceTenantPrefix = false
})
})
diff --git a/e2e/globaltenantresource_test.go b/e2e/globaltenantresource_test.go
new file mode 100644
index 00000000..2a3beff4
--- /dev/null
+++ b/e2e/globaltenantresource_test.go
@@ -0,0 +1,299 @@
+//go:build e2e
+
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package e2e
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/selection"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/utils/pointer"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+)
+
+var _ = Describe("Creating a GlobalTenantResource object", func() {
+ solar := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "energy-solar",
+ Labels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "solar-user",
+ Kind: "User",
+ },
+ },
+ },
+ }
+
+ wind := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "energy-wind",
+ Labels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "wind-user",
+ Kind: "User",
+ },
+ },
+ },
+ }
+
+ namespacedItem := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "dummy-secret",
+ Namespace: "default",
+ Labels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ Type: corev1.SecretTypeOpaque,
+ }
+
+ gtr := &capsulev1beta2.GlobalTenantResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "replicate-energies",
+ },
+ Spec: capsulev1beta2.GlobalTenantResourceSpec{
+ TenantSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ TenantResourceSpec: capsulev1beta2.TenantResourceSpec{
+ ResyncPeriod: metav1.Duration{Duration: time.Minute},
+ PruningOnDelete: pointer.Bool(true),
+ Resources: []capsulev1beta2.ResourceSpec{
+ {
+ NamespacedItems: []capsulev1beta2.ObjectReference{
+ {
+ ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{
+ Kind: "Secret",
+ Namespace: "default",
+ APIVersion: "v1",
+ },
+ Selector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ },
+ },
+ RawItems: []capsulev1beta2.RawExtension{
+ {
+ RawExtension: runtime.RawExtension{
+ Object: &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "raw-secret-1",
+ },
+ Type: corev1.SecretTypeOpaque,
+ },
+ },
+ },
+ {
+ RawExtension: runtime.RawExtension{
+ Object: &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "raw-secret-2",
+ },
+ Type: corev1.SecretTypeOpaque,
+ },
+ },
+ },
+
+ {
+ RawExtension: runtime.RawExtension{
+ Object: &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "raw-secret-3",
+ },
+ Type: corev1.SecretTypeOpaque,
+ },
+ },
+ },
+ },
+ AdditionalMetadata: &api.AdditionalMetadataSpec{
+ Labels: map[string]string{
+ "labels.energy.io": "replicate",
+ },
+ Annotations: map[string]string{
+ "annotations.energy.io": "replicate",
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ JustBeforeEach(func() {
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), solar)
+ }).Should(Succeed())
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), wind)
+ }).Should(Succeed())
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), gtr)
+ }).Should(Succeed())
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), namespacedItem)
+ }).Should(Succeed())
+ })
+
+ JustAfterEach(func() {
+ Expect(k8sClient.Delete(context.TODO(), solar)).Should(Succeed())
+ Expect(k8sClient.Delete(context.TODO(), wind)).Should(Succeed())
+ Expect(k8sClient.Delete(context.TODO(), gtr)).Should(Succeed())
+ Expect(k8sClient.Delete(context.TODO(), namespacedItem)).Should(Succeed())
+ })
+
+ It("should replicate resources to all Tenants", func() {
+ solarNs, windNs := []string{"solar-one", "solar-two", "solar-three"}, []string{"wind-one", "wind-two", "wind-three"}
+
+ By("creating solar Namespaces", func() {
+ for _, ns := range solarNs {
+ NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ }
+ })
+
+ By("creating wind Namespaces", func() {
+ for _, ns := range windNs {
+ NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, wind.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ }
+ })
+
+ for _, ns := range append(solarNs, windNs...) {
+ By(fmt.Sprintf("waiting for replicated resources in %s Namespace", ns), func() {
+ Eventually(func() []corev1.Secret {
+ r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"})
+ if err != nil {
+ return nil
+ }
+
+ secrets := corev1.SecretList{}
+ err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns})
+ if err != nil {
+ return nil
+ }
+
+ return secrets.Items
+ }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4))
+ })
+ }
+
+ By("removing a Namespace from labels", func() {
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: wind.GetName()}, wind)).ToNot(HaveOccurred())
+
+ wind.SetLabels(nil)
+ Expect(k8sClient.Update(context.TODO(), wind)).ToNot(HaveOccurred())
+
+ By("expecting no more items in the wind Tenant namespaces due to label update", func() {
+ for _, ns := range windNs {
+ Eventually(func() []corev1.Secret {
+ r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"})
+ if err != nil {
+ return nil
+ }
+
+ secrets := corev1.SecretList{}
+ err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns})
+ if err != nil {
+ return nil
+ }
+
+ return secrets.Items
+ }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0))
+ }
+ })
+ })
+
+ By("using a Namespace selector", func() {
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: gtr.GetName()}, gtr)).ToNot(HaveOccurred())
+
+ gtr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "name": "solar-three",
+ },
+ }
+
+ Expect(k8sClient.Update(context.TODO(), gtr)).ToNot(HaveOccurred())
+
+ checkFn := func(ns string) func() []corev1.Secret {
+ return func() []corev1.Secret {
+ r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"})
+ if err != nil {
+ return nil
+ }
+
+ secrets := corev1.SecretList{}
+ err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns})
+ if err != nil {
+ return nil
+ }
+
+ return secrets.Items
+ }
+ }
+
+ for _, ns := range []string{"solar-one", "solar-two"} {
+ Eventually(checkFn(ns), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0))
+ }
+
+ Eventually(checkFn("solar-three"), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4))
+ })
+
+ By("checking if replicated object have annotations and labels", func() {
+ for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} {
+ secret := corev1.Secret{}
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: "solar-three"}, &secret)).ToNot(HaveOccurred())
+
+ for k, v := range gtr.Spec.Resources[0].AdditionalMetadata.Labels {
+ _, err := HaveKeyWithValue(k, v).Match(secret.GetLabels())
+ Expect(err).ToNot(HaveOccurred())
+ }
+
+ for k, v := range gtr.Spec.Resources[0].AdditionalMetadata.Annotations {
+ _, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations())
+ Expect(err).ToNot(HaveOccurred())
+ }
+ }
+ })
+ })
+})
diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go
index af4528a4..65fdc18f 100644
--- a/e2e/imagepullpolicy_multiple_test.go
+++ b/e2e/imagepullpolicy_multiple_test.go
@@ -8,27 +8,28 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("enforcing some defined ImagePullPolicy", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "image-pull-policies",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "alex",
Kind: "User",
},
},
- ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"},
+ ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"},
},
}
@@ -44,7 +45,7 @@ var _ = Describe("enforcing some defined ImagePullPolicy", func() {
})
It("should just allow the defined policies", func() {
- ns := NewNamespace("allow-policy")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
cs := ownerClient(tnt.Spec.Owners[0])
diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go
index a93d9a05..6143bf77 100644
--- a/e2e/imagepullpolicy_single_test.go
+++ b/e2e/imagepullpolicy_single_test.go
@@ -8,27 +8,28 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("enforcing a defined ImagePullPolicy", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "image-pull-policy",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "axel",
Kind: "User",
},
},
- ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always"},
+ ImagePullPolicies: []api.ImagePullPolicySpec{"Always"},
},
}
@@ -44,7 +45,7 @@ var _ = Describe("enforcing a defined ImagePullPolicy", func() {
})
It("should just allow the defined policy", func() {
- ns := NewNamespace("allow-policies")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
cs := ownerClient(tnt.Spec.Owners[0])
diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go
index b3775b00..7171fc14 100644
--- a/e2e/ingress_class_extensions_test.go
+++ b/e2e/ingress_class_extensions_test.go
@@ -7,39 +7,46 @@ package e2e
import (
"context"
- "errors"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "ingress-class-extensions-v1beta1",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "ingress",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- AllowedClasses: &capsulev1beta1.AllowedListSpec{
- Exact: []string{
- "nginx",
- "haproxy",
+ IngressOptions: capsulev1beta2.IngressOptions{
+ AllowedClasses: &api.DefaultAllowedListSpec{
+ Default: "tenant-default",
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ AllowedListSpec: api.AllowedListSpec{
+ Exact: []string{"nginx", "haproxy"},
+ Regex: "^oil-.*$",
+ },
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "env": "customers",
+ },
+ },
},
- Regex: "^oil-.*$",
},
},
},
@@ -56,7 +63,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
})
It("should block a non allowed class for extensions/v1beta1", func() {
- ns := NewNamespace("ingress-class-disallowed-extensions-v1beta1")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -64,8 +71,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
By("non-specifying at all", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -88,8 +94,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
})
By("defining as deprecated annotation", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -115,8 +120,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
})
By("using the ingressClassName", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -127,7 +131,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
Name: "denied-ingress",
},
Spec: extensionsv1beta1.IngressSpec{
- IngressClassName: pointer.StringPtr("the-worst-ingress-available"),
+ IngressClassName: pointer.String("the-worst-ingress-available"),
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
@@ -141,7 +145,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
})
It("should allow enabled class using the deprecated annotation", func() {
- ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -150,8 +154,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact {
Eventually(func() (err error) {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -178,8 +181,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
It("should allow enabled class using the ingressClassName field", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -188,7 +190,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
Skip("Running test on Kubernetes " + version.String() + ", doesn't provide .spec.ingressClassName")
}
- ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -215,7 +217,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
})
It("should allow enabled Ingress by regex using the deprecated annotation", func() {
- ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
ingressClass := "oil-ingress"
@@ -224,8 +226,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
Eventually(func() (err error) {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -250,7 +251,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
})
It("should allow enabled Ingress by regex using the ingressClassName field", func() {
- ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
ingressClass := "oil-haproxy"
@@ -259,8 +260,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
Eventually(func() (err error) {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go
index abc04a80..881a4b7d 100644
--- a/e2e/ingress_class_networking_test.go
+++ b/e2e/ingress_class_networking_test.go
@@ -7,66 +7,166 @@ package e2e
import (
"context"
- "errors"
"fmt"
+ "strconv"
+ "strings"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
networkingv1 "k8s.io/api/networking/v1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/selection"
+ "k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
+ "sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1", func() {
- tnt := &capsulev1beta1.Tenant{
+ tntNoDefault := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
- Name: "ingress-class-networking-v1",
+ Name: "ic-selector-networking-v1",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: []capsulev1beta1.OwnerSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: []capsulev1beta2.OwnerSpec{
{
- Name: "ingress",
+ Name: "ingress-selector",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- AllowedClasses: &capsulev1beta1.AllowedListSpec{
- Exact: []string{
- "nginx",
- "haproxy",
+ IngressOptions: capsulev1beta2.IngressOptions{
+ AllowedClasses: &api.DefaultAllowedListSpec{
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ AllowedListSpec: api.AllowedListSpec{
+ Exact: []string{"nginx", "haproxy"},
+ Regex: "^oil-.*$",
+ },
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "env": "customers",
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ tntWithDefault := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ic-default-networking-v1",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: []capsulev1beta2.OwnerSpec{
+ {
+ Name: "ingress-default",
+ Kind: "User",
+ },
+ },
+ IngressOptions: capsulev1beta2.IngressOptions{
+ AllowedClasses: &api.DefaultAllowedListSpec{
+ Default: "tenant-default",
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "name": "tenant-default",
+ },
+ },
},
- Regex: "^oil-.*$",
},
},
},
}
+ tenantDefault := networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tenant-default",
+ Labels: map[string]string{
+ "name": "tenant-default",
+ "env": "e2e",
+ },
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "k8s.io/ingress-nginx",
+ },
+ }
+
+ globalDefault := networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "global-default",
+ Labels: map[string]string{
+ "name": "global-default",
+ "env": "customers",
+ },
+ Annotations: map[string]string{
+ "ingressclass.kubernetes.io/is-default-class": "true",
+ },
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "k8s.io/ingress-nginx",
+ },
+ }
+
+ disallowedGlobalDefault := networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "disallowed",
+ Labels: map[string]string{
+ "name": "disallowed-global-default",
+ "env": "e2e",
+ },
+ Annotations: map[string]string{
+ "ingressclass.kubernetes.io/is-default-class": "true",
+ },
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "k8s.io/ingress-nginx",
+ },
+ }
+
JustBeforeEach(func() {
- EventuallyCreation(func() error {
- tnt.ResourceVersion = ""
- return k8sClient.Create(context.TODO(), tnt)
- }).Should(Succeed())
+ for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefault, tntNoDefault} {
+ EventuallyCreation(func() error {
+ tnt.ResourceVersion = ""
+
+ return k8sClient.Create(context.TODO(), tnt)
+ }).Should(Succeed())
+ }
})
+
JustAfterEach(func() {
- Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefault, tntNoDefault} {
+ Eventually(func() error {
+ return k8sClient.Delete(context.TODO(), tnt)
+ }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
+ }
+
+ Eventually(func() (err error) {
+ req, _ := labels.NewRequirement("env", selection.Exists, nil)
+
+ return k8sClient.DeleteAllOf(context.TODO(), &networkingv1.IngressClass{}, &client.DeleteAllOfOptions{
+ ListOptions: client.ListOptions{
+ LabelSelector: labels.NewSelector().Add(*req),
+ },
+ })
+ }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
It("should block a non allowed class", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("ingress-class-disallowed-networking-v1")
- cs := ownerClient(tnt.Spec.Owners[0])
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefault.Spec.Owners[0])
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
- TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+ NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
By("non-specifying at all", func() {
Eventually(func() (err error) {
@@ -120,7 +220,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
Name: "denied-ingress",
},
Spec: networkingv1.IngressSpec{
- IngressClassName: pointer.StringPtr("the-worst-ingress-available"),
+ IngressClassName: pointer.String("the-worst-ingress-available"),
DefaultBackend: &networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "foo",
@@ -139,19 +239,18 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
It("should allow enabled class using the deprecated annotation for networking.k8s.io/v1", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("ingress-class-allowed-annotation-networking-v1")
- cs := ownerClient(tnt.Spec.Owners[0])
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefault.Spec.Owners[0])
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
- TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+ NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
- for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact {
+ for _, c := range tntNoDefault.Spec.IngressOptions.AllowedClasses.Exact {
Eventually(func() (err error) {
i := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
@@ -179,19 +278,18 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
It("should allow enabled class using the ingressClassName field", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("ingress-class-allowed-annotation-networking-v1")
- cs := ownerClient(tnt.Spec.Owners[0])
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefault.Spec.Owners[0])
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
- TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+ NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
- for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact {
+ for _, c := range tntNoDefault.Spec.IngressOptions.AllowedClasses.Exact {
Eventually(func() (err error) {
i := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
@@ -217,18 +315,17 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
It("should allow enabled Ingress by regex using the deprecated annotation", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("ingress-class-allowed-annotation-networking-v1")
- cs := ownerClient(tnt.Spec.Owners[0])
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefault.Spec.Owners[0])
ingressClass := "oil-ingress"
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
- TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+ NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
Eventually(func() (err error) {
i := &networkingv1.Ingress{
@@ -256,18 +353,17 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
It("should allow enabled Ingress by regex using the ingressClassName field", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("ingress-class-allowed-annotation-networking-v1")
- cs := ownerClient(tnt.Spec.Owners[0])
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefault.Spec.Owners[0])
ingressClass := "oil-haproxy"
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
- TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+ NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
Eventually(func() (err error) {
i := &networkingv1.Ingress{
@@ -290,4 +386,271 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
return
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
+
+ It("should allow enabled Ingress by selector using the deprecated annotation", func() {
+ if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
+ if utils.IsUnsupportedAPI(err) {
+ Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
+ }
+ }
+
+ for i, sc := range []string{"customer-nginx", "customer-haproxy"} {
+ ingressClass := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "")
+ class := &networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ingressClass,
+ Labels: map[string]string{
+ "name": ingressClass,
+ "env": "customers",
+ },
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "k8s.io/ingress-nginx",
+ },
+ }
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+
+ i := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: fmt.Sprintf("allowed-%s", ingressClass),
+ Annotations: map[string]string{
+ "kubernetes.io/ingress.class": ingressClass,
+ },
+ },
+ Spec: networkingv1.IngressSpec{
+ DefaultBackend: &networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: "foo",
+ Port: networkingv1.ServiceBackendPort{
+ Number: 8080,
+ },
+ },
+ },
+ },
+ }
+
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefault.Spec.Owners[0])
+
+ NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), i, metav1.CreateOptions{})
+ return err
+ }).Should(Succeed())
+ }
+ })
+
+ It("should allow enabled Ingress by selector using the ingressClassName field", func() {
+ if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
+ if utils.IsUnsupportedAPI(err) {
+ Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
+ }
+ }
+
+ for i, sc := range []string{"customer-nginx", "customer-haproxy"} {
+ ingressClass := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "")
+ class := &networkingv1.IngressClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: ingressClass,
+ Labels: map[string]string{
+ "name": ingressClass,
+ "env": "customers",
+ },
+ },
+ Spec: networkingv1.IngressClassSpec{
+ Controller: "k8s.io/ingress-nginx",
+ },
+ }
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+
+ i := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: fmt.Sprintf("allowed-%s", ingressClass),
+ },
+ Spec: networkingv1.IngressSpec{
+ IngressClassName: &ingressClass,
+ DefaultBackend: &networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: "foo",
+ Port: networkingv1.ServiceBackendPort{
+ Number: 8080,
+ },
+ },
+ },
+ },
+ }
+
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefault.Spec.Owners[0])
+
+ NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), i, metav1.CreateOptions{})
+ return err
+ }).Should(Succeed())
+ }
+ })
+
+ It("should mutate to default tenant IngressClass (class not does not exist)", func() {
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ i := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "e2e-default-ingress",
+ Namespace: ns.GetName(),
+ },
+ Spec: networkingv1.IngressSpec{
+ DefaultBackend: &networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: "foo",
+ Port: networkingv1.ServiceBackendPort{
+ Number: 8080,
+ },
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), i)
+ }).Should(Succeed())
+
+ Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i))
+ Expect(*i.Spec.IngressClassName).To(Equal("tenant-default"))
+ })
+
+ It("should mutate to default tenant IngressClass (class exists)", func() {
+ if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
+ if utils.IsUnsupportedAPI(err) {
+ Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
+ }
+ }
+
+ class := tenantDefault
+ Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ i := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "e2e-default-ingress",
+ Namespace: ns.GetName(),
+ },
+ Spec: networkingv1.IngressSpec{
+ DefaultBackend: &networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: "foo",
+ Port: networkingv1.ServiceBackendPort{
+ Number: 8080,
+ },
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), i)
+ }).Should(Succeed())
+
+ Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i))
+ Expect(*i.Spec.IngressClassName).To(Equal(class.GetName()))
+ })
+
+ It("shoult mutate to default tenant IngressClass although the cluster global one is not allowed", func() {
+ if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
+ if utils.IsUnsupportedAPI(err) {
+ Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
+ }
+ }
+
+ class := tenantDefault
+ global := disallowedGlobalDefault
+
+ Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed())
+ Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ i := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "e2e-default-global-ingress",
+ Namespace: ns.GetName(),
+ },
+ Spec: networkingv1.IngressSpec{
+ DefaultBackend: &networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: "foo",
+ Port: networkingv1.ServiceBackendPort{
+ Number: 8080,
+ },
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), i)
+ }).Should(Succeed())
+ Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i))
+ Expect(*i.Spec.IngressClassName).To(Equal(class.GetName()))
+ // Run Patch To verify same happens on Update
+ i.Spec.IngressClassName = nil
+ Expect(k8sClient.Update(context.Background(), i)).Should(Succeed())
+ Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i))
+ Expect(*i.Spec.IngressClassName).To(Equal(class.GetName()))
+ })
+
+ It("should mutate to default tenant IngressClass although the cluster global one is allowed", func() {
+ if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
+ if utils.IsUnsupportedAPI(err) {
+ Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
+ }
+ }
+
+ class := tenantDefault
+ global := globalDefault
+
+ Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed())
+ Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ i := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "e2e-default-global-ingress",
+ Namespace: ns.GetName(),
+ },
+ Spec: networkingv1.IngressSpec{
+ DefaultBackend: &networkingv1.IngressBackend{
+ Service: &networkingv1.IngressServiceBackend{
+ Name: "foo",
+ Port: networkingv1.ServiceBackendPort{
+ Number: 8080,
+ },
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), i)
+ }).Should(Succeed())
+ Expect(*i.Spec.IngressClassName).To(Equal(class.GetName()))
+ // Run Patch To verify same happens on Update
+ i.Spec.IngressClassName = nil
+ Expect(k8sClient.Update(context.Background(), i)).Should(Succeed())
+ Expect(*i.Spec.IngressClassName).To(Equal(class.GetName()))
+ })
})
diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go
index 64204bcb..14d895c7 100644
--- a/e2e/ingress_hostnames_collision_cluster_scope_test.go
+++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go
@@ -7,50 +7,50 @@ package e2e
import (
"context"
- "errors"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("when handling Cluster scoped Ingress hostnames collision", func() {
- tnt1 := &capsulev1beta1.Tenant{
+ tnt1 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-cluster-one",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "ingress-tenant-one",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster,
+ IngressOptions: capsulev1beta2.IngressOptions{
+ HostnameCollisionScope: api.HostnameCollisionScopeCluster,
},
},
}
- tnt2 := &capsulev1beta1.Tenant{
+ tnt2 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-cluster-two",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "ingress-tenant-two",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster,
+ IngressOptions: capsulev1beta2.IngressOptions{
+ HostnameCollisionScope: api.HostnameCollisionScopeCluster,
},
},
}
@@ -143,20 +143,19 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun
})
It("should ensure Cluster scope for Ingress hostname and path collision", func() {
- ns1 := NewNamespace("tenant-one-ns")
+ ns1 := NewNamespace("")
cs1 := ownerClient(tnt1.Spec.Owners[0])
NamespaceCreation(ns1, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns1.GetName()))
- ns2 := NewNamespace("tenant-two-ns")
+ ns2 := NewNamespace("")
cs2 := ownerClient(tnt2.Spec.Owners[0])
NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.GetName()))
By("testing networking.k8s.io", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -190,8 +189,7 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun
By("testing extensions", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go
index b20f681e..7b18ec4f 100644
--- a/e2e/ingress_hostnames_collision_disabled_test.go
+++ b/e2e/ingress_hostnames_collision_disabled_test.go
@@ -7,34 +7,34 @@ package e2e
import (
"context"
- "errors"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("when disabling Ingress hostnames collision", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-disabled",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "ingress-disabled",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled,
+ IngressOptions: capsulev1beta2.IngressOptions{
+ HostnameCollisionScope: api.HostnameCollisionScopeDisabled,
},
},
}
@@ -119,8 +119,8 @@ var _ = Describe("when disabling Ingress hostnames collision", func() {
})
It("should not check any kind of collision", func() {
- ns1 := NewNamespace("namespace-collision-one")
- ns2 := NewNamespace("namespace-collision-two")
+ ns1 := NewNamespace("")
+ ns2 := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -129,8 +129,7 @@ var _ = Describe("when disabling Ingress hostnames collision", func() {
By("testing networking.k8s.io", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -170,8 +169,7 @@ var _ = Describe("when disabling Ingress hostnames collision", func() {
By("testing extensions", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go
index 6e7fe6a7..317f1ce8 100644
--- a/e2e/ingress_hostnames_collision_namespace_scope_test.go
+++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go
@@ -7,34 +7,34 @@ package e2e
import (
"context"
- "errors"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("when handling Namespace scoped Ingress hostnames collision", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-namespace",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "ingress-namespace",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeNamespace,
+ IngressOptions: capsulev1beta2.IngressOptions{
+ HostnameCollisionScope: api.HostnameCollisionScopeNamespace,
},
},
}
@@ -119,8 +119,8 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f
})
It("should ensure Namespace scope for Ingress hostname and path collision", func() {
- ns1 := NewNamespace("namespace-collision-one")
- ns2 := NewNamespace("namespace-collision-two")
+ ns1 := NewNamespace("")
+ ns2 := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -129,8 +129,7 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f
By("testing networking.k8s.io", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -171,8 +170,7 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f
By("testing extensions", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go
index 4873f899..9e460494 100644
--- a/e2e/ingress_hostnames_collision_tenant_scope_test.go
+++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go
@@ -7,34 +7,34 @@ package e2e
import (
"context"
- "errors"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-tenant",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "ingress-tenant",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeTenant,
+ IngressOptions: capsulev1beta2.IngressOptions{
+ HostnameCollisionScope: api.HostnameCollisionScopeTenant,
},
},
}
@@ -119,9 +119,9 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func
It("should ensure Tenant scope for Ingress hostname and path collision", func() {
- ns1 := NewNamespace("cluster-collision-one")
+ ns1 := NewNamespace("")
- ns2 := NewNamespace("cluster-collision-two")
+ ns2 := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
@@ -132,8 +132,7 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func
By("testing networking.k8s.io", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -167,8 +166,7 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func
By("testing extensions", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go
index 2d27478d..0a115365 100644
--- a/e2e/ingress_hostnames_test.go
+++ b/e2e/ingress_hostnames_test.go
@@ -7,34 +7,34 @@ package e2e
import (
"context"
- "errors"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("when Tenant handles Ingress hostnames", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "ingress-hostnames",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "hostname",
Kind: "User",
},
},
- IngressOptions: capsulev1beta1.IngressOptions{
- AllowedHostnames: &capsulev1beta1.AllowedListSpec{
+ IngressOptions: capsulev1beta2.IngressOptions{
+ AllowedHostnames: &api.AllowedListSpec{
Exact: []string{"sigs.k8s.io", "operator.sdk", "domain.tld"},
Regex: `.*\.clastix\.io`,
},
@@ -120,7 +120,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
})
It("should block a non allowed Hostname", func() {
- ns := NewNamespace("disallowed-hostname-networking")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -128,8 +128,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
By("testing networking.k8s.io", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -143,7 +142,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
})
It("should block a non allowed Hostname", func() {
- ns := NewNamespace("disallowed-hostname-extensions")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -151,8 +150,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
By("testing extensions", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -166,7 +164,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
})
It("should allow Hostnames in list", func() {
- ns := NewNamespace("allowed-hostname-list-networking")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -174,8 +172,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
By("testing networking.k8s.io", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -191,7 +188,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
})
It("should allow Hostnames in list", func() {
- ns := NewNamespace("allowed-hostname-list-extensions")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -199,8 +196,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
By("testing extensions", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -216,7 +212,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
})
It("should allow Hostnames in regex", func() {
- ns := NewNamespace("allowed-hostname-regex-networking")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -224,8 +220,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
By("testing networking.k8s.io", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
@@ -241,7 +236,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
})
It("should allow Hostnames in regex", func() {
- ns := NewNamespace("allowed-hostname-regex-extensions")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
@@ -249,8 +244,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
By("testing extensions", func() {
if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
diff --git a/e2e/missing_tenant_test.go b/e2e/missing_tenant_test.go
index dd78b24d..fdc98d41 100644
--- a/e2e/missing_tenant_test.go
+++ b/e2e/missing_tenant_test.go
@@ -8,18 +8,18 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace creation with no Tenant assigned", func() {
It("should fail", func() {
- tnt := &capsulev1beta1.Tenant{
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ tnt := &capsulev1beta2.Tenant{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "missing",
Kind: "User",
@@ -27,7 +27,7 @@ var _ = Describe("creating a Namespace creation with no Tenant assigned", func()
},
},
}
- ns := NewNamespace("no-namespace")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
_, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
Expect(err).ShouldNot(Succeed())
diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go
index 413178da..4bac88bf 100644
--- a/e2e/namespace_additional_metadata_test.go
+++ b/e2e/namespace_additional_metadata_test.go
@@ -8,28 +8,29 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating a Namespace for a Tenant with additional metadata", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-metadata",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "gatsby",
Kind: "User",
},
},
- NamespaceOptions: &capsulev1beta1.NamespaceOptions{
- AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{
+ NamespaceOptions: &capsulev1beta2.NamespaceOptions{
+ AdditionalMetadata: &api.AdditionalMetadataSpec{
Labels: map[string]string{
"k8s.io/custom-label": "foo",
"clastix.io/custom-label": "bar",
@@ -53,7 +54,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
})
It("should contain additional Namespace metadata", func() {
- ns := NewNamespace("namespace-metadata")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
diff --git a/e2e/namespace_capsule_label_test.go b/e2e/namespace_capsule_label_test.go
index 49777ca3..aa858dfd 100644
--- a/e2e/namespace_capsule_label_test.go
+++ b/e2e/namespace_capsule_label_test.go
@@ -8,22 +8,22 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating several Namespaces for a Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "capsule-labels",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "charlie",
Kind: "User",
@@ -44,9 +44,9 @@ var _ = Describe("creating several Namespaces for a Tenant", func() {
It("should contains the default Capsule label", func() {
namespaces := []*v1.Namespace{
- NewNamespace("first-capsule-ns"),
- NewNamespace("second-capsule-ns"),
- NewNamespace("third-capsule-ns"),
+ NewNamespace(""),
+ NewNamespace(""),
+ NewNamespace(""),
}
for _, ns := range namespaces {
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
diff --git a/e2e/namespace_user_metadata_test.go b/e2e/namespace_user_metadata_test.go
index 2abcea68..7f7d5773 100644
--- a/e2e/namespace_user_metadata_test.go
+++ b/e2e/namespace_user_metadata_test.go
@@ -7,26 +7,35 @@ package e2e
import (
"context"
+ "time"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("creating a Namespace with user-specified labels and annotations", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-user-metadata-forbidden",
- Annotations: map[string]string{
- capsulev1beta1.ForbiddenNamespaceLabelsAnnotation: "foo,bar",
- capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation: "^gatsby-.*$",
- capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation: "foo,bar",
- capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation: "^gatsby-.*$",
- },
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ NamespaceOptions: &capsulev1beta2.NamespaceOptions{
+ ForbiddenLabels: api.ForbiddenListSpec{
+ Exact: []string{"foo", "bar"},
+ Regex: "^gatsby-.*$",
+ },
+ ForbiddenAnnotations: api.ForbiddenListSpec{
+ Exact: []string{"foo", "bar"},
+ Regex: "^gatsby-.*$",
+ },
+ },
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "gatsby",
Kind: "User",
@@ -47,37 +56,151 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation
It("should allow", func() {
By("specifying non-forbidden labels", func() {
- ns := NewNamespace("namespace-user-metadata-allowed-labels")
+ ns := NewNamespace("")
ns.SetLabels(map[string]string{"bim": "baz"})
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
})
By("specifying non-forbidden annotations", func() {
- ns := NewNamespace("namespace-user-metadata-allowed-annotations")
+ ns := NewNamespace("")
ns.SetAnnotations(map[string]string{"bim": "baz"})
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
})
})
- It("should fail", func() {
+ It("should fail when creating a Namespace", func() {
By("specifying forbidden labels using exact match", func() {
- ns := NewNamespace("namespace-user-metadata-forbidden-labels")
+ ns := NewNamespace("")
ns.SetLabels(map[string]string{"foo": "bar"})
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden labels using regex match", func() {
- ns := NewNamespace("namespace-user-metadata-forbidden-labels")
+ ns := NewNamespace("")
ns.SetLabels(map[string]string{"gatsby-foo": "bar"})
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden annotations using exact match", func() {
- ns := NewNamespace("namespace-user-metadata-forbidden-labels")
+ ns := NewNamespace("")
ns.SetAnnotations(map[string]string{"foo": "bar"})
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden annotations using regex match", func() {
- ns := NewNamespace("namespace-user-metadata-forbidden-labels")
+ ns := NewNamespace("")
ns.SetAnnotations(map[string]string{"gatsby-foo": "bar"})
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
})
+
+ It("should fail when updating a Namespace", func() {
+ role := &rbacv1.Role{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ns-patch",
+ },
+ Rules: []rbacv1.PolicyRule{
+ {
+ Verbs: []string{"patch", "update"},
+ APIGroups: []string{""},
+ Resources: []string{"namespaces"},
+ },
+ },
+ }
+
+ roleBinding := &rbacv1.RoleBinding{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "ns-patch",
+ },
+ Subjects: []rbacv1.Subject{
+ {
+ APIGroup: "rbac.authorization.k8s.io",
+ Kind: tnt.Spec.Owners[0].Kind.String(),
+ Name: tnt.Spec.Owners[0].Name,
+ },
+ },
+ RoleRef: rbacv1.RoleRef{
+ APIGroup: "rbac.authorization.k8s.io",
+ Kind: "Role",
+ Name: role.GetName(),
+ },
+ }
+
+ rbacPatch := func(ns string) {
+ role := role.DeepCopy()
+ role.SetNamespace(ns)
+ Expect(k8sClient.Create(context.Background(), role)).To(Succeed())
+
+ roleBinding := roleBinding.DeepCopy()
+ roleBinding.SetNamespace(ns)
+ Expect(k8sClient.Create(context.Background(), roleBinding)).To(Succeed())
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+
+ By("specifying forbidden labels using exact match", func() {
+ ns := NewNamespace("forbidden-labels-exact-match")
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ rbacPatch(ns.GetName())
+ Consistently(func() error {
+ if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil {
+ return nil
+ }
+
+ ns.SetLabels(map[string]string{"foo": "bar"})
+
+ _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
+
+ return err
+ }, 10*time.Second, time.Second).ShouldNot(Succeed())
+ })
+ By("specifying forbidden labels using regex match", func() {
+ ns := NewNamespace("forbidden-labels-regex-match")
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ rbacPatch(ns.GetName())
+ Consistently(func() error {
+ if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil {
+ return nil
+ }
+
+ ns.SetLabels(map[string]string{"gatsby-foo": "bar"})
+
+ _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
+
+ return err
+ }, 3*time.Second, time.Second).ShouldNot(Succeed())
+ })
+ By("specifying forbidden annotations using exact match", func() {
+ ns := NewNamespace("forbidden-annotations-exact-match")
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ rbacPatch(ns.GetName())
+ Consistently(func() error {
+ if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil {
+ return nil
+ }
+
+ ns.SetAnnotations(map[string]string{"foo": "bar"})
+
+ _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
+
+ return err
+ }, 10*time.Second, time.Second).ShouldNot(Succeed())
+ })
+ By("specifying forbidden annotations using regex match", func() {
+ ns := NewNamespace("forbidden-annotations-regex-match")
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ rbacPatch(ns.GetName())
+ Consistently(func() error {
+ if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil {
+ return nil
+ }
+
+ ns.SetAnnotations(map[string]string{"gatsby-foo": "bar"})
+
+ _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{})
+
+ return err
+ }, 10*time.Second, time.Second).ShouldNot(Succeed())
+ })
+ })
})
diff --git a/e2e/new_namespace_test.go b/e2e/new_namespace_test.go
index 440821b4..21df0812 100644
--- a/e2e/new_namespace_test.go
+++ b/e2e/new_namespace_test.go
@@ -7,20 +7,21 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespaces as different type of Tenant owners", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-assigned",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "alice",
Kind: "User",
@@ -48,7 +49,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun
})
It("should be available in Tenant namespaces list and RoleBindings should be present when created", func() {
- ns := NewNamespace("new-namespace-user")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName()))
@@ -57,7 +58,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun
}
})
It("should be available in Tenant namespaces list and RoleBindings should present when created as Group", func() {
- ns := NewNamespace("new-namespace-group")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[1], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName()))
@@ -66,7 +67,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun
}
})
It("should be available in Tenant namespaces list and RoleBindings should present when created as ServiceAccount", func() {
- ns := NewNamespace("new-namespace-sa")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[2], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName()))
diff --git a/e2e/node_user_metadata_test.go b/e2e/node_user_metadata_test.go
index f898147a..2a02ba18 100644
--- a/e2e/node_user_metadata_test.go
+++ b/e2e/node_user_metadata_test.go
@@ -7,25 +7,26 @@ package e2e
import (
"context"
-
"fmt"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
- "github.com/clastix/capsule/pkg/webhook/utils"
- . "github.com/onsi/ginkgo"
+
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/webhook/utils"
)
var _ = Describe("modifying node labels and annotations", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-node-user-metadata-forbidden",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "gatsby",
Kind: "User",
@@ -90,17 +91,41 @@ var _ = Describe("modifying node labels and annotations", func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
Expect(k8sClient.Delete(context.TODO(), crb)).Should(Succeed())
Expect(k8sClient.Delete(context.TODO(), cr)).Should(Succeed())
+ EventuallyCreation(func() error {
+ return ModifyNode(func(node *corev1.Node) error {
+ annotations := node.GetAnnotations()
+
+ delete(annotations, "bim")
+ delete(annotations, "foo")
+ delete(annotations, "gatsby-foo")
+
+ node.SetAnnotations(annotations)
+
+ labels := node.GetLabels()
+
+ delete(labels, "bim")
+ delete(labels, "foo")
+ delete(labels, "gatsby-foo")
+
+ node.SetLabels(labels)
+
+ return k8sClient.Update(context.Background(), node)
+ })
+ }).Should(Succeed())
})
It("should allow", func() {
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
- protected := map[string]string{
- capsulev1alpha1.ForbiddenNodeLabelsAnnotation: "foo,bar",
- capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation: "^gatsby-.*$",
- capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation: "foo,bar",
- capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation: "^gatsby-.*$",
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
+ configuration.Spec.NodeMetadata = &capsulev1beta2.NodeMetadata{
+ ForbiddenLabels: api.ForbiddenListSpec{
+ Exact: []string{"foo", "bar"},
+ Regex: "^gatsby-.*$",
+ },
+ ForbiddenAnnotations: api.ForbiddenListSpec{
+ Exact: []string{"foo", "bar"},
+ Regex: "^gatsby-.*$",
+ },
}
- configuration.SetAnnotations(protected)
})
By("adding non-forbidden labels", func() {
EventuallyCreation(func() error {
diff --git a/e2e/overquota_namespace_test.go b/e2e/overquota_namespace_test.go
index aec9e7e7..9587baa5 100644
--- a/e2e/overquota_namespace_test.go
+++ b/e2e/overquota_namespace_test.go
@@ -8,28 +8,28 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace in over-quota of three", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "over-quota-tenant",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "bob",
Kind: "User",
},
},
- NamespaceOptions: &capsulev1beta1.NamespaceOptions{
- Quota: pointer.Int32Ptr(3),
+ NamespaceOptions: &capsulev1beta2.NamespaceOptions{
+ Quota: pointer.Int32(3),
},
},
}
@@ -52,7 +52,7 @@ var _ = Describe("creating a Namespace in over-quota of three", func() {
}
})
- ns := NewNamespace("bob-fail")
+ ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
_, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
Expect(err).ShouldNot(Succeed())
diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go
index 71ab1d81..bc1f8f85 100644
--- a/e2e/owner_webhooks_test.go
+++ b/e2e/owner_webhooks_test.go
@@ -9,7 +9,7 @@ import (
"context"
"fmt"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
@@ -17,28 +17,33 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("when Tenant owner interacts with the webhooks", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-owner",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "ruby",
Kind: "User",
},
},
- StorageClasses: &capsulev1beta1.AllowedListSpec{
- Exact: []string{
- "cephfs",
- "glusterfs",
+ StorageClasses: &api.DefaultAllowedListSpec{
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ AllowedListSpec: api.AllowedListSpec{
+ Exact: []string{
+ "cephfs",
+ "glusterfs",
+ },
+ },
},
},
- LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
+ LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
{
Limits: []corev1.LimitRangeItem{
{
@@ -56,7 +61,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
},
},
},
- NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{
+ NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{
{
Egress: []networkingv1.NetworkPolicyEgressRule{
{
@@ -77,7 +82,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
},
},
},
- ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
+ ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
{
Hard: map[corev1.ResourceName]resource.Quantity{
corev1.ResourcePods: resource.MustParse("10"),
@@ -99,7 +104,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
It("should disallow deletions", func() {
By("blocking Capsule Limit ranges", func() {
- ns := NewNamespace("limit-range-disallow")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -113,7 +118,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
Expect(cs.CoreV1().LimitRanges(ns.GetName()).Delete(context.TODO(), lr.Name, metav1.DeleteOptions{})).ShouldNot(Succeed())
})
By("blocking Capsule Network Policy", func() {
- ns := NewNamespace("network-policy-disallow")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -127,7 +132,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
Expect(cs.NetworkingV1().NetworkPolicies(ns.GetName()).Delete(context.TODO(), np.Name, metav1.DeleteOptions{})).ShouldNot(Succeed())
})
By("blocking Capsule Resource Quota", func() {
- ns := NewNamespace("resource-quota-disallow")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -144,7 +149,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
It("should allow", func() {
By("listing Limit Range", func() {
- ns := NewNamespace("limit-range-list")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -155,7 +160,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("listing Network Policy", func() {
- ns := NewNamespace("network-policy-list")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -166,7 +171,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
By("listing Resource Quota", func() {
- ns := NewNamespace("resource-quota-list")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -179,7 +184,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
})
It("should allow all actions to Tenant owner Network Policy", func() {
- ns := NewNamespace("network-policy-allow")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go
index a8d1ac32..0e96c555 100644
--- a/e2e/pod_priority_class_test.go
+++ b/e2e/pod_priority_class_test.go
@@ -7,48 +7,141 @@ package e2e
import (
"context"
+ "strconv"
+ "strings"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
- v1 "k8s.io/api/scheduling/v1"
+ schedulingv1 "k8s.io/api/scheduling/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/selection"
+ "sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("enforcing a Priority Class", func() {
- tnt := &capsulev1beta1.Tenant{
+ tntWithDefaults := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
- Name: "priority-class",
+ Name: "priority-class-defaults",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "paul",
+ Kind: "User",
+ },
+ },
+ PriorityClasses: &api.DefaultAllowedListSpec{
+ Default: "tenant-default",
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "env": "customer",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ tntNoDefaults := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "priority-class-no-defaults",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "george",
Kind: "User",
},
},
- PriorityClasses: &capsulev1beta1.AllowedListSpec{
- Exact: []string{"gold"},
- Regex: "pc\\-\\w+",
+ PriorityClasses: &api.DefaultAllowedListSpec{
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ AllowedListSpec: api.AllowedListSpec{
+ Exact: []string{"gold"},
+ Regex: "pc\\-\\w+",
+ },
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "env": "customer",
+ },
+ },
+ },
},
},
}
+ pcTenantPreemption := corev1.PreemptionPolicy("PreemptLowerPriority")
+ tenantDefault := schedulingv1.PriorityClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tenant-default",
+ Labels: map[string]string{
+ "env": "e2e",
+ },
+ },
+ Description: "tenant default priorityclass",
+ Value: 1212,
+ PreemptionPolicy: &pcTenantPreemption,
+ GlobalDefault: false,
+ }
+
+ globalDefault := schedulingv1.PriorityClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "global-default",
+ Labels: map[string]string{
+ "env": "customer",
+ },
+ },
+ Description: "global default priorityclass",
+ Value: 100000,
+ GlobalDefault: true,
+ }
+
+ disallowedGlobalDefault := schedulingv1.PriorityClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "disallowed-global-default",
+ Labels: map[string]string{
+ "env": "e2e",
+ },
+ },
+ Description: "global default priorityclass",
+ Value: 100000,
+ GlobalDefault: true,
+ }
+
JustBeforeEach(func() {
- EventuallyCreation(func() error {
- tnt.ResourceVersion = ""
- return k8sClient.Create(context.TODO(), tnt)
- }).Should(Succeed())
+ for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefaults, tntNoDefaults} {
+ EventuallyCreation(func() error {
+ tnt.ResourceVersion = ""
+
+ return k8sClient.Create(context.TODO(), tnt)
+ }).Should(Succeed())
+ }
})
+
JustAfterEach(func() {
- Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefaults, tntNoDefaults} {
+ Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ }
+
+ Eventually(func() (err error) {
+ req, _ := labels.NewRequirement("env", selection.Exists, nil)
+
+ return k8sClient.DeleteAllOf(context.TODO(), &schedulingv1.PriorityClass{}, &client.DeleteAllOfOptions{
+ ListOptions: client.ListOptions{
+ LabelSelector: labels.NewSelector().Add(*req),
+ },
+ })
+ }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
It("should block non allowed Priority Class", func() {
- ns := NewNamespace("system-node-critical")
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -65,29 +158,71 @@ var _ = Describe("enforcing a Priority Class", func() {
},
}
- cs := ownerClient(tnt.Spec.Owners[0])
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
EventuallyCreation(func() error {
_, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
return err
}).ShouldNot(Succeed())
})
+ It("should block non matching selector match", func() {
+ for i, pc := range []string{"internal-bronze", "internal-silver", "internal-gold"} {
+ priorityName := strings.Join([]string{pc, "-", strconv.Itoa(i)}, "")
+ class := &schedulingv1.PriorityClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: priorityName,
+ Labels: map[string]string{
+ "env": "internal",
+ },
+ },
+ Description: "fake PriorityClass for e2e",
+ Value: int32(10000 * (i + 2)),
+ }
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: pc,
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ PriorityClassName: class.GetName(),
+ },
+ }
+
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
+
+ NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
+ return err
+ }).ShouldNot(Succeed())
+ }
+ })
+
It("should allow exact match", func() {
- pc := &v1.PriorityClass{
+ pc := &schedulingv1.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: "gold",
+ Labels: map[string]string{
+ "env": "e2e",
+ },
},
Description: "fake PriorityClass for e2e",
Value: 10000,
}
Expect(k8sClient.Create(context.TODO(), pc)).Should(Succeed())
- defer func() {
- Expect(k8sClient.Delete(context.TODO(), pc)).Should(Succeed())
- }()
-
- ns := NewNamespace("pc-exact-match")
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -104,7 +239,7 @@ var _ = Describe("enforcing a Priority Class", func() {
},
}
- cs := ownerClient(tnt.Spec.Owners[0])
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
EventuallyCreation(func() error {
_, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
return err
@@ -112,14 +247,16 @@ var _ = Describe("enforcing a Priority Class", func() {
})
It("should allow regex match", func() {
- ns := NewNamespace("pc-regex-match")
-
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
for i, pc := range []string{"pc-bronze", "pc-silver", "pc-gold"} {
- class := &v1.PriorityClass{
+ class := &schedulingv1.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: pc,
+ Labels: map[string]string{
+ "env": "e2e",
+ },
},
Description: "fake PriorityClass for e2e",
Value: int32(10000 * (i + 2)),
@@ -127,6 +264,43 @@ var _ = Describe("enforcing a Priority Class", func() {
Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+ EventuallyCreation(func() error {
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: pc,
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ PriorityClassName: class.GetName(),
+ },
+ }
+
+ return k8sClient.Create(context.Background(), pod)
+ }).Should(Succeed())
+ }
+ })
+
+ It("should allow selector match", func() {
+ for i, pc := range []string{"customer-bronze", "customer-silver", "customer-gold"} {
+ priorityName := strings.Join([]string{pc, "-", strconv.Itoa(i)}, "")
+ class := &schedulingv1.PriorityClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: priorityName,
+ Labels: map[string]string{
+ "env": "customer",
+ },
+ },
+ Description: "fake PriorityClass for e2e",
+ Value: int32(10000 * (i + 2)),
+ }
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: pc,
@@ -142,14 +316,153 @@ var _ = Describe("enforcing a Priority Class", func() {
},
}
- cs := ownerClient(tnt.Spec.Owners[0])
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
+
+ NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
EventuallyCreation(func() error {
_, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
return err
}).Should(Succeed())
+ }
+ })
+
+ It("fail if default tenant PriorityClass is absent", func() {
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tenant-default",
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ },
+ }
+
+ ns := NewNamespace("")
+ cs := ownerClient(tntWithDefaults.Spec.Owners[0])
+
+ NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
+
+ return err
+ }).ShouldNot(Succeed())
+
+ Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed())
+ })
+
+ It("should mutate to default tenant PriorityClass", func() {
+ class := tenantDefault.DeepCopy()
+ class.SetResourceVersion("")
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ pod := corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tenant-default-present",
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), &pod)
+ }).Should(Succeed())
+ // Check if correct mutated
+ Expect(pod.Spec.PriorityClassName).To(Equal(class.GetName()))
+ Expect(pod.Spec.Priority).To(Equal(&class.Value))
+ Expect(pod.Spec.PreemptionPolicy).To(Equal(class.PreemptionPolicy))
+ })
+
+ It("should mutate to default tenant PriorityClass although the cluster global one is not allowed", func() {
+ class := tenantDefault.DeepCopy()
+ class.SetResourceVersion("")
+
+ global := disallowedGlobalDefault.DeepCopy()
+ global.SetResourceVersion("")
+
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+ Expect(k8sClient.Create(context.TODO(), global)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tenant-default-global-default",
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), pod)
+ }).Should(Succeed())
+ // Check if correct applied
+ Expect(pod.Spec.PriorityClassName).To(Equal(class.GetName()))
+ Expect(pod.Spec.Priority).To(Equal(&class.Value))
+ Expect(pod.Spec.PreemptionPolicy).To(Equal(class.PreemptionPolicy))
+ })
+
+ It("should mutate to default tenant PriorityClass although the cluster global one is allowed", func() {
+ class := tenantDefault.DeepCopy()
+ class.SetResourceVersion("")
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
- Expect(k8sClient.Delete(context.TODO(), class)).Should(Succeed())
+ global := globalDefault.DeepCopy()
+ global.SetResourceVersion("")
+ Expect(k8sClient.Create(context.TODO(), global)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tenant-default-allowed",
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ },
}
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), pod)
+ }).Should(Succeed())
+ // Check if correctly applied
+ Expect(pod.Spec.PriorityClassName).To(Equal(class.GetName()))
+ Expect(*pod.Spec.Priority).To(Equal(class.Value))
})
})
diff --git a/e2e/pod_runtime_class_test.go b/e2e/pod_runtime_class_test.go
new file mode 100644
index 00000000..58c5339a
--- /dev/null
+++ b/e2e/pod_runtime_class_test.go
@@ -0,0 +1,224 @@
+//go:build e2e
+
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package e2e
+
+import (
+ "context"
+ "strconv"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
+ nodev1 "k8s.io/api/node/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/clastix/capsule/pkg/api"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+var _ = Describe("enforcing a Runtime Class", func() {
+ tnt := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "runtime-class",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "george",
+ Kind: "User",
+ },
+ },
+ RuntimeClasses: &api.SelectorAllowedListSpec{
+ AllowedListSpec: api.AllowedListSpec{
+ Exact: []string{"legacy"},
+ Regex: "^hardened-.*$",
+ },
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "env": "customers",
+ },
+ },
+ },
+ },
+ }
+
+ JustBeforeEach(func() {
+ EventuallyCreation(func() error {
+ tnt.ResourceVersion = ""
+ return k8sClient.Create(context.TODO(), tnt)
+ }).Should(Succeed())
+ })
+ JustAfterEach(func() {
+ Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ })
+
+ It("should block non allowed Runtime Class", func() {
+ runtime := &nodev1.RuntimeClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "disallowed",
+ },
+ Handler: "custom-handler",
+ }
+ Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed())
+
+ defer func() {
+ Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed())
+ }()
+
+ ns := NewNamespace("rt-disallow")
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ runtimeName := "disallowed"
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "container",
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ RuntimeClassName: &runtimeName,
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
+ return err
+ }).ShouldNot(Succeed())
+ })
+
+ It("should allow exact match", func() {
+ runtime := &nodev1.RuntimeClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "legacy",
+ },
+ Handler: "custom-handler",
+ }
+ Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed())
+
+ defer func() {
+ Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed())
+ }()
+
+ ns := NewNamespace("rt-exact-match")
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ runtimeName := "legacy"
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "container",
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ RuntimeClassName: &runtimeName,
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
+ return err
+ }).Should(Succeed())
+ })
+
+ It("should allow regex match", func() {
+ ns := NewNamespace("rc-regex-match")
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+
+ for i, rt := range []string{"hardened-crio", "hardened-containerd", "hardened-dockerd"} {
+ runtimeName := strings.Join([]string{rt, "-", strconv.Itoa(i)}, "")
+ runtime := &nodev1.RuntimeClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: runtimeName,
+ },
+ Handler: "custom-handler",
+ }
+
+ Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed())
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: rt,
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ RuntimeClassName: &runtimeName,
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
+ return err
+ }).Should(Succeed())
+
+ Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed())
+ }
+ })
+
+ It("should allow selector match", func() {
+ ns := NewNamespace("rc-selector-match")
+
+ NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+
+ for i, rt := range []string{"customer-containerd", "customer-crio", "customer-dockerd"} {
+ runtimeName := strings.Join([]string{rt, "-", strconv.Itoa(i)}, "")
+ runtime := &nodev1.RuntimeClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: runtimeName,
+ Labels: map[string]string{
+ "name": runtimeName,
+ "env": "customers",
+ },
+ },
+ Handler: "custom-handler",
+ }
+
+ Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed())
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: rt,
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "quay.io/google-containers/pause-amd64:3.0",
+ },
+ },
+ RuntimeClassName: &runtimeName,
+ },
+ }
+
+ cs := ownerClient(tnt.Spec.Owners[0])
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
+ return err
+ }).Should(Succeed())
+
+ Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed())
+ }
+ })
+
+})
diff --git a/e2e/preventing_pv_cross_tenant_mount_test.go b/e2e/preventing_pv_cross_tenant_mount_test.go
new file mode 100644
index 00000000..0618bb58
--- /dev/null
+++ b/e2e/preventing_pv_cross_tenant_mount_test.go
@@ -0,0 +1,199 @@
+//go:build e2e
+
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package e2e
+
+import (
+ "context"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/utils/pointer"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+var _ = Describe("preventing PersistentVolume cross-tenant mount", func() {
+ tnt1 := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pv-one",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "jessica",
+ Kind: "User",
+ },
+ },
+ },
+ }
+
+ tnt2 := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pv-two",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "leto",
+ Kind: "User",
+ },
+ },
+ },
+ }
+
+ JustBeforeEach(func() {
+ for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} {
+ EventuallyCreation(func() error {
+ tnt.ResourceVersion = ""
+
+ return k8sClient.Create(context.TODO(), tnt)
+ }).Should(Succeed())
+ }
+ })
+
+ JustAfterEach(func() {
+ for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} {
+ Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ }
+ })
+
+ It("should add labels to PersistentVolume and prevent cross-Tenant mount", func() {
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns.Name))
+
+ pvc := corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "arrakis",
+ Namespace: ns.Name,
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{
+ corev1.ReadWriteOnce,
+ },
+ Resources: corev1.ResourceRequirements{
+ Requests: corev1.ResourceList{
+ corev1.ResourceStorage: resource.MustParse("1Gi"),
+ },
+ },
+ StorageClassName: pointer.String("standard"),
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), &pvc)
+ }).Should(Succeed())
+
+ pod := corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "arrakis-pod",
+ Namespace: ns.Name,
+ },
+ Spec: corev1.PodSpec{
+ Containers: []corev1.Container{
+ {
+ Name: "container",
+ Image: "gcr.io/google_containers/pause-amd64:3.0",
+ ImagePullPolicy: corev1.PullAlways,
+ VolumeMounts: []corev1.VolumeMount{
+ {
+ Name: "data",
+ MountPath: "/tmp",
+ },
+ },
+ },
+ },
+ Volumes: []corev1.Volume{
+ {
+ Name: "data",
+ VolumeSource: corev1.VolumeSource{
+ PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
+ ClaimName: pvc.Name,
+ },
+ },
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), &pod)
+ }).Should(Succeed())
+
+ Eventually(func() int {
+ nsName := types.NamespacedName{Name: pvc.Name, Namespace: pvc.Namespace}
+
+ if err := k8sClient.Get(context.Background(), nsName, &pvc); err != nil {
+ return 0
+ }
+
+ return len(pvc.Spec.VolumeName)
+ }, defaultTimeoutInterval, defaultPollInterval).Should(BeNumerically(">", 0))
+
+ pv := corev1.PersistentVolume{}
+ defer func() {
+ _ = k8sClient.Delete(context.Background(), &pv)
+ }()
+
+ Eventually(func() string {
+ if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: pvc.Spec.VolumeName}, &pv); err != nil {
+ return "not-found"
+ }
+
+ if pv.GetLabels() == nil {
+ return "no-labels"
+ }
+
+ return pv.GetLabels()["capsule.clastix.io/tenant"]
+ }, defaultTimeoutInterval, defaultPollInterval).Should(Equal(tnt1.Name))
+
+ Eventually(func() error {
+ nsName := types.NamespacedName{Name: pv.Name}
+
+ if err := k8sClient.Get(context.Background(), nsName, &pv); err != nil {
+ return err
+ }
+
+ pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRecycle
+
+ return k8sClient.Update(context.Background(), &pv)
+ }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
+
+ Expect(k8sClient.Delete(context.Background(), &pod, &client.DeleteOptions{GracePeriodSeconds: pointer.Int64(0)})).ToNot(HaveOccurred())
+
+ ns2 := NewNamespace("")
+ NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.Name))
+
+ Consistently(func() error {
+ pvc := corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "caladan",
+ Namespace: ns2.Name,
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{
+ corev1.ReadWriteOnce,
+ },
+ Resources: corev1.ResourceRequirements{
+ Requests: corev1.ResourceList{
+ corev1.ResourceStorage: resource.MustParse("1Gi"),
+ },
+ },
+ StorageClassName: pointer.String("standard"),
+ VolumeName: pv.Name,
+ },
+ }
+
+ return k8sClient.Create(context.Background(), &pvc)
+ }, defaultTimeoutInterval, defaultPollInterval).Should(HaveOccurred())
+ })
+})
diff --git a/e2e/protected_namespace_regex_test.go b/e2e/protected_namespace_regex_test.go
index 7dadb2a4..3b4279b9 100644
--- a/e2e/protected_namespace_regex_test.go
+++ b/e2e/protected_namespace_regex_test.go
@@ -8,22 +8,20 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
-
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace with a protected Namespace regex enabled", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-protected-namespace",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "alice",
Kind: "User",
@@ -43,7 +41,7 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled"
})
It("should succeed and be available in Tenant namespaces list", func() {
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
configuration.Spec.ProtectedNamespaceRegexpString = `^.*[-.]system$`
})
@@ -57,7 +55,7 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled"
ns := NewNamespace("test-system")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
- ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
+ ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
configuration.Spec.ProtectedNamespaceRegexpString = ""
})
})
diff --git a/e2e/resource_quota_exceeded_test.go b/e2e/resource_quota_exceeded_test.go
index fc8aa4df..86813518 100644
--- a/e2e/resource_quota_exceeded_test.go
+++ b/e2e/resource_quota_exceeded_test.go
@@ -9,7 +9,9 @@ import (
"context"
"fmt"
- . "github.com/onsi/ginkgo"
+ "github.com/clastix/capsule/pkg/api"
+
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
@@ -18,22 +20,22 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("exceeding a Tenant resource quota", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-resources-changes",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "bobby",
Kind: "User",
},
},
- LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
+ LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
{
Limits: []corev1.LimitRangeItem{
{
@@ -79,7 +81,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() {
},
},
},
- ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
+ ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
{
Hard: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceLimitsCPU: resource.MustParse("8"),
@@ -133,7 +135,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() {
Name: "my-pause",
},
Spec: appsv1.DeploymentSpec{
- Replicas: pointer.Int32Ptr(5),
+ Replicas: pointer.Int32(5),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "pause",
diff --git a/e2e/sa_prevent_privilege_escalation_test.go b/e2e/sa_prevent_privilege_escalation_test.go
index 59649e58..b31a4a86 100644
--- a/e2e/sa_prevent_privilege_escalation_test.go
+++ b/e2e/sa_prevent_privilege_escalation_test.go
@@ -10,7 +10,7 @@ import (
"fmt"
"time"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -19,16 +19,16 @@ import (
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("trying to escalate from a Tenant Namespace ServiceAccount", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "sa-privilege-escalation",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "mario",
Kind: "User",
diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go
index 2aa62593..8734dbd5 100644
--- a/e2e/selecting_non_owned_tenant_test.go
+++ b/e2e/selecting_non_owned_tenant_test.go
@@ -8,21 +8,23 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ "github.com/clastix/capsule/pkg/utils"
+
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace trying to select a third Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-non-owned",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "undefined",
Kind: "User",
@@ -44,16 +46,16 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func()
var ns *corev1.Namespace
By("assigning to the Namespace the Capsule Tenant label", func() {
- l, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
+ l, err := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
Expect(err).ToNot(HaveOccurred())
- ns := NewNamespace("tenant-non-owned-ns")
+ ns := NewNamespace("")
ns.SetLabels(map[string]string{
l: tnt.Name,
})
})
- cs := ownerClient(capsulev1beta1.OwnerSpec{Name: "dale", Kind: "User"})
+ cs := ownerClient(capsulev1beta2.OwnerSpec{Name: "dale", Kind: "User"})
_, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
Expect(err).To(HaveOccurred())
})
diff --git a/e2e/selecting_tenant_fail_test.go b/e2e/selecting_tenant_fail_test.go
index bfa4150f..95253c45 100644
--- a/e2e/selecting_tenant_fail_test.go
+++ b/e2e/selecting_tenant_fail_test.go
@@ -8,20 +8,20 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace without a Tenant selector when user owns multiple Tenants", func() {
- t1 := &capsulev1beta1.Tenant{
+ t1 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-one",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
@@ -29,12 +29,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
},
},
}
- t2 := &capsulev1beta1.Tenant{
+ t2 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-two",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
@@ -42,12 +42,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
},
},
}
- t3 := &capsulev1beta1.Tenant{
+ t3 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-three",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "Group",
@@ -55,12 +55,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
},
},
}
- t4 := &capsulev1beta1.Tenant{
+ t4 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-four",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "Group",
@@ -70,7 +70,7 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
}
It("should fail", func() {
- ns := NewNamespace("fail-ns")
+ ns := NewNamespace("")
By("user owns 2 tenants", func() {
EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t1) }).Should(Succeed())
EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t2) }).Should(Succeed())
diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go
index ed1459b2..093a2712 100644
--- a/e2e/selecting_tenant_with_label_test.go
+++ b/e2e/selecting_tenant_with_label_test.go
@@ -8,20 +8,22 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ "github.com/clastix/capsule/pkg/utils"
+
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Namespace with Tenant selector when user owns multiple tenants", func() {
- t1 := &capsulev1beta1.Tenant{
+ t1 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-one",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
@@ -29,12 +31,12 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi
},
},
}
- t2 := &capsulev1beta1.Tenant{
+ t2 := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-two",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
@@ -57,9 +59,9 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi
})
It("should be assigned to the selected Tenant", func() {
- ns := NewNamespace("tenant-2-ns")
+ ns := NewNamespace("")
By("assigning to the Namespace the Capsule Tenant label", func() {
- l, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
+ l, err := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
Expect(err).ToNot(HaveOccurred())
ns.Labels = map[string]string{
l: t2.Name,
diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go
index 22f02df8..b27524fc 100644
--- a/e2e/service_metadata_test.go
+++ b/e2e/service_metadata_test.go
@@ -7,36 +7,39 @@ package e2e
import (
"context"
- "errors"
"fmt"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
+ discoveryv1 "k8s.io/api/discovery/v1"
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
- "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/utils"
)
var _ = Describe("adding metadata to Service objects", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "service-metadata",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "gatsby",
Kind: "User",
},
},
- ServiceOptions: &capsulev1beta1.ServiceOptions{
- AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{
+ ServiceOptions: &api.ServiceOptions{
+ AdditionalMetadata: &api.AdditionalMetadataSpec{
Labels: map[string]string{
"k8s.io/custom-label": "foo",
"clastix.io/custom-label": "bar",
@@ -63,7 +66,7 @@ var _ = Describe("adding metadata to Service objects", func() {
})
It("should apply them to Service", func() {
- ns := NewNamespace("service-metadata")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -140,7 +143,7 @@ var _ = Describe("adding metadata to Service objects", func() {
})
It("should apply them to Endpoints", func() {
- ns := NewNamespace("endpoints-metadata")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
@@ -216,36 +219,16 @@ var _ = Describe("adding metadata to Service objects", func() {
})
})
- It("should apply them to EndpointSlice", func() {
+ It("should apply them to EndpointSlice in v1", func() {
if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
}
}
- ns := NewNamespace("endpointslice-metadata")
+ ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
-
- eps := &discoveryv1beta1.EndpointSlice{
- ObjectMeta: metav1.ObjectMeta{
- Name: "endpointslice-metadata",
- Namespace: ns.GetName(),
- },
- AddressType: discoveryv1beta1.AddressTypeIPv4,
- Endpoints: []discoveryv1beta1.Endpoint{
- {
- Addresses: []string{"10.10.1.1"},
- },
- },
- Ports: []discoveryv1beta1.EndpointPort{
- {
- Name: pointer.StringPtr("foo"),
- Port: pointer.Int32Ptr(9999),
- },
- },
- }
// Waiting for the reconciliation of required RBAC
EventuallyCreation(func() (err error) {
pod := &corev1.Pod{
@@ -266,6 +249,52 @@ var _ = Describe("adding metadata to Service objects", func() {
return
}).Should(Succeed())
+ var eps client.Object
+
+ if err := k8sClient.List(context.Background(), &discoveryv1.EndpointSliceList{}); err != nil {
+ if utils.IsUnsupportedAPI(err) {
+ Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
+ }
+
+ eps = &discoveryv1beta1.EndpointSlice{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "endpointslice-metadata",
+ Namespace: ns.GetName(),
+ },
+ AddressType: discoveryv1beta1.AddressTypeIPv4,
+ Endpoints: []discoveryv1beta1.Endpoint{
+ {
+ Addresses: []string{"10.10.1.1"},
+ },
+ },
+ Ports: []discoveryv1beta1.EndpointPort{
+ {
+ Name: pointer.String("foo"),
+ Port: pointer.Int32(9999),
+ },
+ },
+ }
+ } else {
+ eps = &discoveryv1.EndpointSlice{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "endpointslice-metadata",
+ Namespace: ns.GetName(),
+ },
+ AddressType: discoveryv1.AddressTypeIPv4,
+ Endpoints: []discoveryv1.Endpoint{
+ {
+ Addresses: []string{"10.10.1.1"},
+ },
+ },
+ Ports: []discoveryv1.EndpointPort{
+ {
+ Name: pointer.String("foo"),
+ Port: pointer.Int32(9999),
+ },
+ },
+ }
+ }
+
EventuallyCreation(func() (err error) {
return k8sClient.Create(context.TODO(), eps)
}).Should(Succeed())
@@ -274,7 +303,7 @@ var _ = Describe("adding metadata to Service objects", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.Annotations {
- ok, _ = HaveKeyWithValue(k, v).Match(eps.Annotations)
+ ok, _ = HaveKeyWithValue(k, v).Match(eps.GetAnnotations())
if !ok {
return false
}
@@ -287,7 +316,7 @@ var _ = Describe("adding metadata to Service objects", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.Labels {
- ok, _ = HaveKeyWithValue(k, v).Match(eps.Labels)
+ ok, _ = HaveKeyWithValue(k, v).Match(eps.GetLabels())
if !ok {
return false
}
diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go
index 5f600677..c182c4ea 100644
--- a/e2e/storage_class_test.go
+++ b/e2e/storage_class_test.go
@@ -3,61 +3,148 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
+// "sigs.k8s.io/controller-runtime/pkg/client"
+
package e2e
import (
"context"
+ "fmt"
+ "strconv"
+ "strings"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
+ storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/selection"
"k8s.io/utils/pointer"
+ "sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
)
var _ = Describe("when Tenant handles Storage classes", func() {
- tnt := &capsulev1beta1.Tenant{
+ tntNoDefaults := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
- Name: "storage-class",
+ Name: "storage-class-selector",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
- Name: "storage",
+ Name: "selector",
Kind: "User",
},
},
- StorageClasses: &capsulev1beta1.AllowedListSpec{
- Exact: []string{
- "cephfs",
- "glusterfs",
+ StorageClasses: &api.DefaultAllowedListSpec{
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ AllowedListSpec: api.AllowedListSpec{
+ Exact: []string{"cephfs", "glusterfs"},
+ Regex: "^oil-.*$",
+ },
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "env": "customer",
+ },
+ },
},
- Regex: "^oil-.*$",
},
},
}
+ tntWithDefault := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "storage-class-default",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "default",
+ Kind: "User",
+ },
+ },
+ StorageClasses: &api.DefaultAllowedListSpec{
+ Default: "tenant-default",
+ SelectorAllowedListSpec: api.SelectorAllowedListSpec{
+ LabelSelector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "name": "tenant-default",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ tenantDefault := storagev1.StorageClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "tenant-default",
+ Labels: map[string]string{
+ "name": "tenant-default",
+ "env": "e2e",
+ },
+ },
+ Provisioner: "kubernetes.io/no-provisioner",
+ }
+ globalDefault := storagev1.StorageClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "global-default",
+ Labels: map[string]string{
+ "env": "customer",
+ },
+ },
+ Provisioner: "kubernetes.io/no-provisioner",
+ }
+ disallowedGlobalDefault := storagev1.StorageClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "disallowed-global-default",
+ Labels: map[string]string{
+ "name": "disallowed-global-default",
+ "env": "e2e",
+ },
+ },
+ Provisioner: "kubernetes.io/no-provisioner",
+ }
+
JustBeforeEach(func() {
- EventuallyCreation(func() error {
- tnt.ResourceVersion = ""
- return k8sClient.Create(context.TODO(), tnt)
- }).Should(Succeed())
+ for _, tnt := range []*capsulev1beta2.Tenant{tntNoDefaults, tntWithDefault} {
+ EventuallyCreation(func() error {
+ tnt.ResourceVersion = ""
+
+ return k8sClient.Create(context.TODO(), tnt)
+ }).Should(Succeed())
+ }
})
JustAfterEach(func() {
- Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ for _, tnt := range []*capsulev1beta2.Tenant{tntNoDefaults, tntWithDefault} {
+ Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
+ }
+
+ Eventually(func() (err error) {
+ req, _ := labels.NewRequirement("env", selection.Exists, nil)
+
+ return k8sClient.DeleteAllOf(context.TODO(), &storagev1.StorageClass{}, &client.DeleteAllOfOptions{
+ ListOptions: client.ListOptions{
+ LabelSelector: labels.NewSelector().Add(*req),
+ },
+ })
+ }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
- It("should fails", func() {
- ns := NewNamespace("storage-class-disallowed")
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
- TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+ It("should fail", func() {
+ k8sClient.Create(context.TODO(), tntNoDefaults)
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
By("non-specifying it", func() {
Eventually(func() (err error) {
- cs := ownerClient(tnt.Spec.Owners[0])
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
p := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "denied-pvc",
@@ -77,7 +164,7 @@ var _ = Describe("when Tenant handles Storage classes", func() {
})
By("specifying a forbidden one", func() {
Eventually(func() (err error) {
- cs := ownerClient(tnt.Spec.Owners[0])
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
p := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "mighty-storage",
@@ -95,23 +182,61 @@ var _ = Describe("when Tenant handles Storage classes", func() {
return
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
})
+ By("specifying with not matching label", func() {
+ for i, sc := range []string{"internal-hdd", "internal-ssd"} {
+ storageName := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "")
+ class := &storagev1.StorageClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: fmt.Sprintf("sc-%s", storageName),
+ Labels: map[string]string{
+ "env": "internal",
+ },
+ },
+ Provisioner: "kubernetes.io/no-provisioner",
+ }
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+
+ p := &corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: storageName,
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ StorageClassName: &storageName,
+ AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
+ Resources: corev1.ResourceRequirements{
+ Requests: map[corev1.ResourceName]resource.Quantity{
+ corev1.ResourceStorage: resource.MustParse("3Gi"),
+ },
+ },
+ },
+ }
+
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
+
+ EventuallyCreation(func() error {
+ _, err := cs.CoreV1().PersistentVolumeClaims(ns.GetName()).Create(context.Background(), p, metav1.CreateOptions{})
+ return err
+ }).ShouldNot(Succeed())
+ }
+ })
+
})
It("should allow", func() {
- ns := NewNamespace("storage-class-allowed")
- cs := ownerClient(tnt.Spec.Owners[0])
+ ns := NewNamespace("")
+ cs := ownerClient(tntNoDefaults.Spec.Owners[0])
- NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
- TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+ NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
By("using exact matches", func() {
- for _, c := range tnt.Spec.StorageClasses.Exact {
+ for _, c := range tntNoDefaults.Spec.StorageClasses.Exact {
Eventually(func() (err error) {
p := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: c,
},
Spec: corev1.PersistentVolumeClaimSpec{
- StorageClassName: pointer.StringPtr(c),
+ StorageClassName: pointer.String(c),
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
@@ -146,5 +271,156 @@ var _ = Describe("when Tenant handles Storage classes", func() {
return
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
})
+ By("using a selector match", func() {
+ for i, sc := range []string{"customer-hdd", "customer-ssd"} {
+ storageName := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "")
+ class := &storagev1.StorageClass{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: storageName,
+ Labels: map[string]string{
+ "env": "customer",
+ },
+ },
+ Provisioner: "kubernetes.io/no-provisioner",
+ }
+ Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed())
+
+ EventuallyCreation(func() error {
+ p := &corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: storageName,
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ StorageClassName: &storageName,
+ AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
+ Resources: corev1.ResourceRequirements{
+ Requests: map[corev1.ResourceName]resource.Quantity{
+ corev1.ResourceStorage: resource.MustParse("3Gi"),
+ },
+ },
+ },
+ }
+
+ return k8sClient.Create(context.Background(), p)
+ }).Should(Succeed())
+ }
+ })
+ })
+
+ It("should mutate to default tenant StorageClass (class does not exists)", func() {
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ p := &corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pvc-default-sc",
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
+ Resources: corev1.ResourceRequirements{
+ Requests: map[corev1.ResourceName]resource.Quantity{
+ corev1.ResourceStorage: resource.MustParse("3Gi"),
+ },
+ },
+ },
+ }
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), p)
+ }).Should(Succeed())
+ Expect(*p.Spec.StorageClassName).To(Equal("tenant-default"))
+ })
+
+ It("should mutate to default tenant StorageClass (class exists)", func() {
+ class := tenantDefault
+ Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ p := &corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pvc-default-sc-present",
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
+ Resources: corev1.ResourceRequirements{
+ Requests: map[corev1.ResourceName]resource.Quantity{
+ corev1.ResourceStorage: resource.MustParse("3Gi"),
+ },
+ },
+ },
+ }
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), p)
+ }).Should(Succeed())
+ Expect(*p.Spec.StorageClassName).To(Equal(class.GetName()))
+ })
+
+ It("should mutate to default tenant StorageClass although cluster global ons is not allowed", func() {
+ class := tenantDefault
+ global := disallowedGlobalDefault
+
+ Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed())
+ Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ p := &corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pvc-default-sc-present",
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
+ Resources: corev1.ResourceRequirements{
+ Requests: map[corev1.ResourceName]resource.Quantity{
+ corev1.ResourceStorage: resource.MustParse("3Gi"),
+ },
+ },
+ },
+ }
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), p)
+ }).Should(Succeed())
+ Expect(*p.Spec.StorageClassName).To(Equal(class.GetName()))
+ })
+
+ It("should mutate to default tenant StorageClass although cluster global ons is allowed", func() {
+ class := tenantDefault
+ global := globalDefault
+
+ Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed())
+ Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed())
+
+ ns := NewNamespace("")
+ NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
+
+ p := &corev1.PersistentVolumeClaim{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pvc-default-sc-present",
+ Namespace: ns.GetName(),
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
+ Resources: corev1.ResourceRequirements{
+ Requests: map[corev1.ResourceName]resource.Quantity{
+ corev1.ResourceStorage: resource.MustParse("3Gi"),
+ },
+ },
+ },
+ }
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.Background(), p)
+ }).Should(Succeed())
+ Expect(*p.Spec.StorageClassName).To(Equal(class.GetName()))
})
})
diff --git a/e2e/suite_test.go b/e2e/suite_test.go
index f3af00af..55a6e370 100644
--- a/e2e/suite_test.go
+++ b/e2e/suite_test.go
@@ -8,7 +8,7 @@ package e2e
import (
"testing"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
@@ -16,33 +16,28 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/envtest"
- "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var (
- cfg *rest.Config
- k8sClient client.Client
- testEnv *envtest.Environment
- tenantRoleBindingNames = []string{"namespace:admin", "namespace-deleter"}
+ cfg *rest.Config
+ k8sClient client.Client
+ testEnv *envtest.Environment
)
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
- RunSpecsWithDefaultAndCustomReporters(t,
- "Controller Suite",
- []Reporter{printer.NewlineReporter{}})
+ RunSpecs(t, "Controller Suite")
}
-var _ = BeforeSuite(func(done Done) {
+var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter)))
By("bootstrapping test environment")
@@ -57,28 +52,22 @@ var _ = BeforeSuite(func(done Done) {
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
- err = capsulev1beta1.AddToScheme(scheme.Scheme)
- Expect(err).NotTo(HaveOccurred())
-
- err = capsulev1alpha1.AddToScheme(scheme.Scheme)
- Expect(err).NotTo(HaveOccurred())
+ Expect(capsulev1beta2.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
-
- close(done)
-}, 60)
+})
var _ = AfterSuite(func() {
By("tearing down the test environment")
Expect(testEnv.Stop()).ToNot(HaveOccurred())
})
-func ownerClient(owner capsulev1beta1.OwnerSpec) (cs kubernetes.Interface) {
+func ownerClient(owner capsulev1beta2.OwnerSpec) (cs kubernetes.Interface) {
c, err := config.GetConfig()
Expect(err).ToNot(HaveOccurred())
- c.Impersonate.Groups = []string{capsulev1beta1.GroupVersion.Group, owner.Name}
+ c.Impersonate.Groups = []string{capsulev1beta2.GroupVersion.Group, owner.Name}
c.Impersonate.UserName = owner.Name
cs, err = kubernetes.NewForConfig(c)
Expect(err).ToNot(HaveOccurred())
diff --git a/e2e/tenant_cordoning_test.go b/e2e/tenant_cordoning_test.go
index fa12d391..95a77228 100644
--- a/e2e/tenant_cordoning_test.go
+++ b/e2e/tenant_cordoning_test.go
@@ -9,22 +9,22 @@ import (
"context"
"time"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("cordoning a Tenant", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-cordoning",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "jim",
Kind: "User",
@@ -46,7 +46,7 @@ var _ = Describe("cordoning a Tenant", func() {
It("should block or allow operations", func() {
cs := ownerClient(tnt.Spec.Owners[0])
- ns := NewNamespace("cordoned-namespace")
+ ns := NewNamespace("")
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -75,9 +75,7 @@ var _ = Describe("cordoning a Tenant", func() {
By("cordoning the Tenant deletion must be blocked", func() {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tnt)).Should(Succeed())
- tnt.Labels = map[string]string{
- "capsule.clastix.io/cordon": "enabled",
- }
+ tnt.Spec.Cordoned = true
Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed())
@@ -89,7 +87,7 @@ var _ = Describe("cordoning a Tenant", func() {
By("uncordoning the Tenant deletion must be allowed", func() {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tnt)).Should(Succeed())
- tnt.Labels = map[string]string{}
+ tnt.Spec.Cordoned = false
Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed())
diff --git a/e2e/tenant_name_webhook_test.go b/e2e/tenant_name_webhook_test.go
index d6d2ae80..91abe378 100644
--- a/e2e/tenant_name_webhook_test.go
+++ b/e2e/tenant_name_webhook_test.go
@@ -8,20 +8,20 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating a Tenant with wrong name", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "non_rfc_dns_1123",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
diff --git a/e2e/tenant_protected_webhook_test.go b/e2e/tenant_protected_webhook_test.go
index 108f35e5..f41175a3 100644
--- a/e2e/tenant_protected_webhook_test.go
+++ b/e2e/tenant_protected_webhook_test.go
@@ -8,24 +8,22 @@ package e2e
import (
"context"
- . "github.com/onsi/ginkgo"
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("Deleting a tenant with protected annotation", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "protected-tenant",
- Annotations: map[string]string{
- capsulev1beta1.ProtectedTenantAnnotation: "",
- },
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ PreventDeletion: true,
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
@@ -36,7 +34,7 @@ var _ = Describe("Deleting a tenant with protected annotation", func() {
JustAfterEach(func() {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, tnt)).Should(Succeed())
- tnt.SetAnnotations(map[string]string{})
+ tnt.Spec.PreventDeletion = false
Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed())
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
diff --git a/e2e/tenant_resources_changes_test.go b/e2e/tenant_resources_changes_test.go
index eacbaf4c..17a7e713 100644
--- a/e2e/tenant_resources_changes_test.go
+++ b/e2e/tenant_resources_changes_test.go
@@ -9,7 +9,9 @@ import (
"context"
"fmt"
- . "github.com/onsi/ginkgo"
+ "github.com/clastix/capsule/pkg/api"
+
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
@@ -18,22 +20,22 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("changing Tenant managed Kubernetes resources", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-resources-changes",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "laura",
Kind: "User",
},
},
- LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
+ LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
{
Limits: []corev1.LimitRangeItem{
{
@@ -79,7 +81,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() {
},
},
},
- NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{
+ NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{
{
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
@@ -127,7 +129,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() {
NodeSelector: map[string]string{
"kubernetes.io/os": "linux",
},
- ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
+ ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
{
Hard: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceLimitsCPU: resource.MustParse("8"),
diff --git a/e2e/tenant_resources_test.go b/e2e/tenant_resources_test.go
index ca740e55..5337775e 100644
--- a/e2e/tenant_resources_test.go
+++ b/e2e/tenant_resources_test.go
@@ -10,7 +10,9 @@ import (
"fmt"
"strings"
- . "github.com/onsi/ginkgo"
+ "github.com/clastix/capsule/pkg/api"
+
+ . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
@@ -18,22 +20,22 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
var _ = Describe("creating namespaces within a Tenant with resources", func() {
- tnt := &capsulev1beta1.Tenant{
+ tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-resources",
},
- Spec: capsulev1beta1.TenantSpec{
- Owners: capsulev1beta1.OwnerListSpec{
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
{
Name: "john",
Kind: "User",
},
},
- LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
+ LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{
{
Limits: []corev1.LimitRangeItem{
{
@@ -79,7 +81,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() {
},
},
},
- NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{
+ NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{
{
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
@@ -127,7 +129,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() {
NodeSelector: map[string]string{
"kubernetes.io/os": "linux",
},
- ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
+ ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{
{
Hard: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceLimitsCPU: resource.MustParse("8"),
diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go
new file mode 100644
index 00000000..478db0f8
--- /dev/null
+++ b/e2e/tenantresource_test.go
@@ -0,0 +1,377 @@
+//go:build e2e
+
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package e2e
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/selection"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/utils/pointer"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
+)
+
+var _ = Describe("Creating a TenantResource object", func() {
+ solar := &capsulev1beta2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "energy-solar",
+ },
+ Spec: capsulev1beta2.TenantSpec{
+ Owners: capsulev1beta2.OwnerListSpec{
+ {
+ Name: "solar-user",
+ Kind: "User",
+ },
+ },
+ },
+ }
+
+ tntItem := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "dummy-secret",
+ Namespace: "solar-system",
+ Labels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ Type: corev1.SecretTypeOpaque,
+ }
+
+ crossNamespaceItem := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "cross-reference-secret",
+ Namespace: "default",
+ Labels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ Type: corev1.SecretTypeOpaque,
+ }
+
+ tr := &capsulev1beta2.TenantResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "replicate-energies",
+ Namespace: "solar-system",
+ },
+ Spec: capsulev1beta2.TenantResourceSpec{
+ ResyncPeriod: metav1.Duration{Duration: time.Minute},
+ PruningOnDelete: pointer.Bool(true),
+ Resources: []capsulev1beta2.ResourceSpec{
+ {
+ NamespaceSelector: &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ NamespacedItems: []capsulev1beta2.ObjectReference{
+ {
+ ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{
+ Kind: "Secret",
+ Namespace: "solar-system",
+ APIVersion: "v1",
+ },
+ Selector: metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "replicate": "true",
+ },
+ },
+ },
+ },
+ RawItems: []capsulev1beta2.RawExtension{
+ {
+ RawExtension: runtime.RawExtension{
+ Object: &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "raw-secret-1",
+ },
+ Type: corev1.SecretTypeOpaque,
+ Data: map[string][]byte{
+ "{{ tenant.name }}": []byte("Cg=="),
+ "{{ namespace }}": []byte("Cg=="),
+ },
+ },
+ },
+ },
+ {
+ RawExtension: runtime.RawExtension{
+ Object: &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "raw-secret-2",
+ },
+ Type: corev1.SecretTypeOpaque,
+ Data: map[string][]byte{
+ "{{ tenant.name }}": []byte("Cg=="),
+ "{{ namespace }}": []byte("Cg=="),
+ },
+ },
+ },
+ },
+ {
+ RawExtension: runtime.RawExtension{
+ Object: &corev1.Secret{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "raw-secret-3",
+ },
+ Type: corev1.SecretTypeOpaque,
+ Data: map[string][]byte{
+ "{{ tenant.name }}": []byte("Cg=="),
+ "{{ namespace }}": []byte("Cg=="),
+ },
+ },
+ },
+ },
+ },
+ AdditionalMetadata: &api.AdditionalMetadataSpec{
+ Labels: map[string]string{
+ "labels.energy.io": "replicate",
+ },
+ Annotations: map[string]string{
+ "annotations.energy.io": "replicate",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ JustBeforeEach(func() {
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), solar)
+ }).Should(Succeed())
+
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), crossNamespaceItem)
+ }).Should(Succeed())
+ })
+
+ JustAfterEach(func() {
+ Expect(k8sClient.Delete(context.TODO(), crossNamespaceItem)).Should(Succeed())
+ _ = k8sClient.Delete(context.TODO(), solar)
+ })
+
+ It("should replicate resources to all Tenant Namespaces", func() {
+ solarNs := []string{"solar-one", "solar-two", "solar-three"}
+
+ By("creating solar Namespaces", func() {
+ for _, ns := range append(solarNs, "solar-system") {
+ NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
+ }
+ })
+
+ By("labelling Namespaces", func() {
+ for _, name := range []string{"solar-one", "solar-two", "solar-three"} {
+ EventuallyWithOffset(1, func() error {
+ ns := corev1.Namespace{}
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name}, &ns)).Should(Succeed())
+
+ labels := ns.GetLabels()
+ if labels == nil {
+ return fmt.Errorf("missing labels")
+ }
+ labels["replicate"] = "true"
+ ns.SetLabels(labels)
+
+ return k8sClient.Update(context.TODO(), &ns)
+ }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
+ }
+ })
+
+ By("creating the namespaced item", func() {
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), tntItem)
+ }).Should(Succeed())
+ })
+
+ By("creating the TenantResource", func() {
+ EventuallyCreation(func() error {
+ return k8sClient.Create(context.TODO(), tr)
+ }).Should(Succeed())
+ })
+
+ for _, ns := range solarNs {
+ By(fmt.Sprintf("waiting for replicated resources in %s Namespace", ns), func() {
+ Eventually(func() []corev1.Secret {
+ r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"})
+ if err != nil {
+ return nil
+ }
+
+ secrets := corev1.SecretList{}
+ err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns})
+ if err != nil {
+ return nil
+ }
+
+ return secrets.Items
+ }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4))
+ })
+
+ By(fmt.Sprintf("ensuring raw items are templated in %s Namespace", ns), func() {
+ for _, name := range []string{"raw-secret-1", "raw-secret-2", "raw-secret-3"} {
+ secret := corev1.Secret{}
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: ns}, &secret)).ToNot(HaveOccurred())
+
+ Expect(secret.Data).To(HaveKey(solar.Name))
+ Expect(secret.Data).To(HaveKey(ns))
+ }
+ })
+ }
+
+ By("using a Namespace selector", func() {
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred())
+
+ tr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "name": "solar-three",
+ },
+ }
+
+ Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred())
+
+ checkFn := func(ns string) func() []corev1.Secret {
+ return func() []corev1.Secret {
+ r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"})
+ if err != nil {
+ return nil
+ }
+
+ secrets := corev1.SecretList{}
+ err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns})
+ if err != nil {
+ return nil
+ }
+
+ return secrets.Items
+ }
+ }
+
+ for _, ns := range []string{"solar-one", "solar-two"} {
+ Eventually(checkFn(ns), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0))
+ }
+
+ Eventually(checkFn("solar-three"), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4))
+ })
+
+ By("checking if replicated object have annotations and labels", func() {
+ for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} {
+ secret := corev1.Secret{}
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: "solar-three"}, &secret)).ToNot(HaveOccurred())
+
+ for k, v := range tr.Spec.Resources[0].AdditionalMetadata.Labels {
+ _, err := HaveKeyWithValue(k, v).Match(secret.GetLabels())
+ Expect(err).ToNot(HaveOccurred())
+ }
+
+ for k, v := range tr.Spec.Resources[0].AdditionalMetadata.Annotations {
+ _, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations())
+ Expect(err).ToNot(HaveOccurred())
+ }
+ }
+ })
+
+ By("checking replicated object cannot be deleted by a Tenant Owner", func() {
+ for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} {
+ cs := ownerClient(solar.Spec.Owners[0])
+
+ Consistently(func() error {
+ return cs.CoreV1().Secrets("solar-three").Delete(context.TODO(), name, metav1.DeleteOptions{})
+ }, 10*time.Second, time.Second).Should(HaveOccurred())
+ }
+ })
+
+ By("checking replicated object cannot be update by a Tenant Owner", func() {
+ for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} {
+ cs := ownerClient(solar.Spec.Owners[0])
+
+ Consistently(func() error {
+ secret, err := cs.CoreV1().Secrets("solar-three").Get(context.TODO(), name, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+
+ secret.SetLabels(nil)
+ secret.SetAnnotations(nil)
+
+ _, err = cs.CoreV1().Secrets("solar-three").Update(context.TODO(), secret, metav1.UpdateOptions{})
+
+ return err
+ }, 10*time.Second, time.Second).Should(HaveOccurred())
+ }
+ })
+
+ By("checking that cross-namespace objects are not replicated", func() {
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred())
+ tr.Spec.Resources[0].NamespacedItems = append(tr.Spec.Resources[0].NamespacedItems, capsulev1beta2.ObjectReference{
+ ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{
+ Kind: crossNamespaceItem.Kind,
+ Namespace: crossNamespaceItem.GetName(),
+ APIVersion: crossNamespaceItem.APIVersion,
+ },
+ Selector: metav1.LabelSelector{
+ MatchLabels: crossNamespaceItem.GetLabels(),
+ },
+ })
+
+ Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred())
+ // Ensuring that although the deletion of TenantResource object,
+ // the replicated objects are not deleted.
+ Consistently(func() error {
+ return k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: solarNs[rand.Intn(len(solarNs))], Name: crossNamespaceItem.GetName()}, &corev1.Secret{})
+ }, 10*time.Second, time.Second).Should(HaveOccurred())
+ })
+
+ By("checking pruning is deleted", func() {
+ Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred())
+ Expect(*tr.Spec.PruningOnDelete).Should(BeTrue())
+
+ tr.Spec.PruningOnDelete = pointer.Bool(false)
+
+ Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred())
+
+ By("deleting the TenantResource", func() {
+ // Ensuring that although the deletion of TenantResource object,
+ // the replicated objects are not deleted.
+ Expect(k8sClient.Delete(context.TODO(), tr)).Should(Succeed())
+
+ r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"})
+ Expect(err).ToNot(HaveOccurred())
+
+ Consistently(func() []corev1.Secret {
+ secrets := corev1.SecretList{}
+
+ err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: "solar-three"})
+ Expect(err).ToNot(HaveOccurred())
+
+ return secrets.Items
+ }, 10*time.Second, time.Second).Should(HaveLen(4))
+ })
+ })
+ })
+})
diff --git a/e2e/utils_test.go b/e2e/utils_test.go
index 56eb9052..46ec50d1 100644
--- a/e2e/utils_test.go
+++ b/e2e/utils_test.go
@@ -8,10 +8,12 @@ package e2e
import (
"context"
"fmt"
- "sigs.k8s.io/controller-runtime/pkg/client"
"strings"
"time"
+ "k8s.io/apimachinery/pkg/util/rand"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -21,8 +23,7 @@ import (
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
const (
@@ -31,6 +32,10 @@ const (
)
func NewNamespace(name string) *corev1.Namespace {
+ if len(name) == 0 {
+ name = rand.String(10)
+ }
+
return &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -38,7 +43,7 @@ func NewNamespace(name string) *corev1.Namespace {
}
}
-func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta1.OwnerSpec, timeout time.Duration) AsyncAssertion {
+func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, timeout time.Duration) AsyncAssertion {
cs := ownerClient(owner)
return Eventually(func() (err error) {
_, err = cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
@@ -46,7 +51,7 @@ func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta1.OwnerSpec, tim
}, timeout, defaultPollInterval)
}
-func TenantNamespaceList(t *capsulev1beta1.Tenant, timeout time.Duration) AsyncAssertion {
+func TenantNamespaceList(t *capsulev1beta2.Tenant, timeout time.Duration) AsyncAssertion {
return Eventually(func() []string {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: t.GetName()}, t)).Should(Succeed())
return t.Status.Namespaces
@@ -65,8 +70,8 @@ func EventuallyCreation(f interface{}) AsyncAssertion {
return Eventually(f, defaultTimeoutInterval, defaultPollInterval)
}
-func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1alpha1.CapsuleConfiguration)) {
- config := &capsulev1alpha1.CapsuleConfiguration{}
+func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1beta2.CapsuleConfiguration)) {
+ config := &capsulev1beta2.CapsuleConfiguration{}
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "default"}, config)).ToNot(HaveOccurred())
fn(config)
@@ -76,7 +81,7 @@ func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1alpha1.Capsu
time.Sleep(1 * time.Second)
}
-func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta1.OwnerSpec, roles map[string]bool) func() error {
+func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, roles map[string]bool) func() error {
if roles == nil {
roles = map[string]bool{
"admin": false,
@@ -93,7 +98,7 @@ func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta1.OwnerS
var ownerName string
- if owner.Kind == capsulev1beta1.ServiceAccountOwner {
+ if owner.Kind == capsulev1beta2.ServiceAccountOwner {
parts := strings.Split(owner.Name, ":")
ownerName = parts[3]
diff --git a/go.mod b/go.mod
index 6414e366..cd57e86f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,71 +1,84 @@
module github.com/clastix/capsule
-go 1.18
+go 1.19
require (
- github.com/go-logr/logr v0.4.0
+ github.com/go-logr/logr v1.2.4
github.com/hashicorp/go-multierror v1.1.0
- github.com/onsi/ginkgo v1.16.5
- github.com/onsi/gomega v1.14.0
+ github.com/onsi/ginkgo/v2 v2.9.5
+ github.com/onsi/gomega v1.27.7
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.1 // indirect
github.com/spf13/pflag v1.0.5
- github.com/stretchr/testify v1.7.0
- go.uber.org/zap v1.18.1
- golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
- k8s.io/api v0.22.0
- k8s.io/apiextensions-apiserver v0.22.0
- k8s.io/apimachinery v0.22.0
- k8s.io/client-go v0.22.0
- k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471
- sigs.k8s.io/controller-runtime v0.9.5
+ github.com/stretchr/testify v1.8.1
+ github.com/valyala/fasttemplate v1.2.2
+ go.uber.org/zap v1.24.0
+ golang.org/x/sync v0.2.0
+ k8s.io/api v0.27.2
+ k8s.io/apiextensions-apiserver v0.27.2
+ k8s.io/apimachinery v0.27.2
+ k8s.io/client-go v0.27.2
+ k8s.io/utils v0.0.0-20230209194617-a36077c30491
+ sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f
+ sigs.k8s.io/controller-runtime v0.15.0
)
require (
- cloud.google.com/go v0.81.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/cespare/xxhash/v2 v2.1.1 // indirect
+ github.com/blang/semver v3.5.1+incompatible // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/evanphx/json-patch v4.11.0+incompatible // indirect
- github.com/fsnotify/fsnotify v1.4.9 // indirect
- github.com/go-logr/zapr v0.4.0 // indirect
+ github.com/emicklei/go-restful/v3 v3.9.0 // indirect
+ github.com/evanphx/json-patch/v5 v5.6.0 // indirect
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/go-logr/zapr v1.2.4 // indirect
+ github.com/go-openapi/jsonpointer v0.19.6 // indirect
+ github.com/go-openapi/jsonreference v0.20.1 // indirect
+ github.com/go-openapi/swag v0.22.3 // indirect
+ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+ github.com/gobuffalo/flect v1.0.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
- github.com/golang/protobuf v1.5.2 // indirect
- github.com/google/go-cmp v0.5.5 // indirect
- github.com/google/gofuzz v1.1.0 // indirect
- github.com/google/uuid v1.1.2 // indirect
- github.com/googleapis/gnostic v0.5.5 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/gnostic v0.6.9 // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
+ github.com/google/gofuzz v1.2.0 // indirect
+ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
+ github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
- github.com/imdario/mergo v0.3.12 // indirect
- github.com/json-iterator/go v1.1.11 // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
+ github.com/imdario/mergo v0.3.13 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
- github.com/modern-go/reflect2 v1.0.1 // indirect
- github.com/nxadm/tail v1.4.8 // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/client_golang v1.11.0 // indirect
- github.com/prometheus/client_model v0.2.0 // indirect
- github.com/prometheus/common v0.26.0 // indirect
- github.com/prometheus/procfs v0.6.0 // indirect
- go.uber.org/atomic v1.7.0 // indirect
- go.uber.org/multierr v1.6.0 // indirect
- golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect
- golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
- golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
- golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
- golang.org/x/text v0.3.7 // indirect
- golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
- gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
+ github.com/prometheus/client_golang v1.15.1 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.9.0 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ go.uber.org/multierr v1.8.0 // indirect
+ golang.org/x/net v0.10.0 // indirect
+ golang.org/x/oauth2 v0.8.0 // indirect
+ golang.org/x/sys v0.8.0 // indirect
+ golang.org/x/term v0.8.0 // indirect
+ golang.org/x/text v0.9.0 // indirect
+ golang.org/x/time v0.3.0 // indirect
+ golang.org/x/tools v0.9.1 // indirect
+ gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/protobuf v1.26.0 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
- gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
- k8s.io/component-base v0.22.0 // indirect
- k8s.io/klog/v2 v2.9.0 // indirect
- k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
- sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
- sigs.k8s.io/yaml v1.2.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/component-base v0.27.2 // indirect
+ k8s.io/klog/v2 v2.90.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
+ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
)
diff --git a/go.sum b/go.sum
index cfc7b590..02e269f4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,201 +1,81 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
-cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
-cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
-cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
-cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
-github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
-github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
-github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
-github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
-github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
-github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
-github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
-github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
+github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
+github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
-github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
-github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
-github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0=
+github.com/coredns/corefile-migration v1.0.20 h1:MdOkT6F3ehju/n9tgxlGct8XAajOX2vN+wG7To4BWSI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
-github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
+github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
-github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
-github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
+github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
+github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
+github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
+github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
-github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM=
-github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
-github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
-github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
-github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
-github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
-github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
-github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
+github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
+github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
+github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA=
+github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -205,681 +85,226 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M=
+github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
+github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
-github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
-github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
-github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
-github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
-github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
-github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
-github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
-github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
-github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
-github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
-github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
-github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
-github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
+github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
+github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
+github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
-github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
+github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
-github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
-github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
+github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
-go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
-go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
-go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
-go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
-go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
-go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
-go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
-go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
-go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
-go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
-go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
-go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
-go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
-go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
-go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
-go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
-go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4=
-go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
-golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc=
-golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
-golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
-golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
-golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
+golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
-gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
-google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
-google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
-google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
-google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=
+gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
-google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -888,94 +313,57 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
-gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=
-k8s.io/api v0.22.0 h1:elCpMZ9UE8dLdYxr55E06TmSeji9I3KH494qH70/y+c=
-k8s.io/api v0.22.0/go.mod h1:0AoXXqst47OI/L0oGKq9DG61dvGRPXs7X4/B7KyjBCU=
-k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE=
-k8s.io/apiextensions-apiserver v0.22.0 h1:QTuZIQggaE7N8FTjur+1zxLmEPziphK7nNm8t+VNO3g=
-k8s.io/apiextensions-apiserver v0.22.0/go.mod h1:+9w/QQC/lwH2qTbpqndXXjwBgidlSmytvIUww16UACE=
-k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=
-k8s.io/apimachinery v0.22.0 h1:CqH/BdNAzZl+sr3tc0D3VsK3u6ARVSo3GWyLmfIjbP0=
-k8s.io/apimachinery v0.22.0/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
-k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU=
-k8s.io/apiserver v0.22.0/go.mod h1:04kaIEzIQrTGJ5syLppQWvpkLJXQtJECHmae+ZGc/nc=
-k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU=
-k8s.io/client-go v0.22.0 h1:sD6o9O6tCwUKCENw8v+HFsuAbq2jCu8cWC61/ydwA50=
-k8s.io/client-go v0.22.0/go.mod h1:GUjIuXR5PiEv/RVK5OODUsm6eZk7wtSWZSaSJbpFdGg=
-k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo=
-k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
-k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ=
-k8s.io/component-base v0.22.0 h1:ZTmX8hUqH9T9gc0mM42O+KDgtwTYbVTt2MwmLP0eK8A=
-k8s.io/component-base v0.22.0/go.mod h1:SXj6Z+V6P6GsBhHZVbWCw9hFjUdUYnJerlhhPnYCBCg=
-k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
-k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
-k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM=
-k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
-k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
-k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
-k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
-k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI=
-k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/controller-runtime v0.9.5 h1:WThcFE6cqctTn2jCZprLICO6BaKZfhsT37uAapTNfxc=
-sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
-sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
-sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo=
+k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4=
+k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo=
+k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ=
+k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg=
+k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
+k8s.io/apiserver v0.27.2 h1:p+tjwrcQEZDrEorCZV2/qE8osGTINPuS5ZNqWAvKm5E=
+k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE=
+k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ=
+k8s.io/cluster-bootstrap v0.27.2 h1:OL3onrOwrUD7NQxBUqQwTl1Uu2GQKCkw9BMHpc4PbiA=
+k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo=
+k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo=
+k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
+k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
+k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=
+k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
+k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f h1:mqGSAkdrKHfeUB4A4mD55NfHXfQe+FqaB5tUFSCSptY=
+sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f/go.mod h1:VgMs4bjc3P0igCtHAbG9+jix2gyYtECQhKfNfRedStc=
+sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU=
+sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/hack/local-test-with-kind.sh b/hack/local-test-with-kind.sh
new file mode 100755
index 00000000..30d6f2fb
--- /dev/null
+++ b/hack/local-test-with-kind.sh
@@ -0,0 +1,145 @@
+#!/usr/bin/env bash
+
+# This script test capsule with kind
+# Good to use it before pull request
+
+USER=alice
+TENANT=oil
+GROUP=capsule.clastix.io
+KIND_CLUSTER_NAME=capsule-local-test
+
+function error_action() {
+ cleanup_action
+ exit 1
+}
+
+function cleanup_action() {
+ kind delete cluster --name=${KIND_CLUSTER_NAME}
+ rm -f ./tenant-test.yaml
+ rm -f ${USER}-${TENANT}.crt
+ rm -f ${USER}-${TENANT}.key
+ rm -f ${USER}-${TENANT}.kubeconfig
+}
+
+function check_command() {
+ local command=$1
+
+ if ! command -v $command &> /dev/null; then
+ echo "Error: ${command} not found"
+ exit 1
+ fi
+}
+
+check_command kind
+check_command kubectl
+
+### Prepare Kind cluster
+
+echo `date`": INFO: Create Kind Cluster"
+error_create_kind=$(kind create cluster --name=${KIND_CLUSTER_NAME} 2>&1)
+if [ $? -ne 0 ]; then
+ echo `date`": $error_create_kind"
+ exit 1
+fi
+
+echo `date`": INFO: Wait then Kind cluster be ready. Wait only 30 seconds"
+counter=0
+while true
+do
+ if [ $counter == 30 ]; then
+ echo `date`": ERROR: Kind cluster not ready for too long"
+ error_action
+ fi
+
+ kubectl get nodes | grep " Ready " &>/dev/null
+ if [ $? == 0 ]; then
+ break
+ fi
+
+ ((counter++))
+ sleep 1
+done
+
+echo `date`": INFO: Kind cluster ready"
+
+### Install helm capsule to Kind
+
+echo `date`": INFO: Install helm capsule"
+error_install_helm=$(helm install capsule ./charts/capsule/ -n capsule-system --create-namespace 2>&1)
+if [ $? -ne 0 ]; then
+ echo `date`": $error_install_helm"
+ exit 1
+fi
+
+echo `date`": INFO: Wait then capsule POD be ready. Wait only 30 seconds"
+counter=0
+while true
+do
+ if [ $counter == 30 ]; then
+ echo `date`": ERROR: Kind cluster not ready for too long"
+ error_action
+ fi
+
+ kubectl get pod -n capsule-system | grep " Running " &>/dev/null
+ if [ $? == 0 ]; then
+ break
+ fi
+
+ ((counter++))
+ sleep 1
+done
+sleep 5
+
+echo `date`": INFO: Capsule ready"
+
+### Tests
+
+echo `date`": INFO: Create tenant"
+cat >>./tenant-test.yaml<&1)
+if [ $? -ne 0 ]; then
+ echo `date`": $error_create_tenant"
+ error_action
+fi
+
+echo `date`": INFO: Check tenant exist"
+error_check_tenant=$(kubectl get tenant ${TENANT} 2>&1)
+if [ $? -ne 0 ]; then
+ echo `date`": ERROR: $error_check_tenant"
+ error_action
+fi
+
+echo `date`": INFO: Create user ${USER} for tenant ${TENANT}"
+error_create_user=$(./hack/create-user.sh ${USER} ${TENANT} 2>&1)
+if [ $? -ne 0 ]; then
+ echo `date`": ERROR: $error_create_user"
+ error_action
+fi
+
+echo `date`": INFO: Create namespace from tenant user"
+error_create_namespace=$(kubectl --kubeconfig=${USER}-${TENANT}.kubeconfig create ns ${TENANT}-test 2>&1)
+if [ $? -ne 0 ]; then
+ echo `date`": ERROR: $error_create_namespace"
+ error_action
+fi
+
+echo `date`": INFO: Check namespace exist in tenant"
+error_tenant=$(kubectl get tenant ${TENANT} -o yaml | grep namespaces -A1 | grep ${TENANT}-test 2>&1)
+if [ $? -ne 0 ]; then
+ echo `date`": ERROR: $error_tenant"
+ error_action
+fi
+
+echo `date`": INFO: All ok"
+
+cleanup_action
\ No newline at end of file
diff --git a/main.go b/main.go
index 945f158b..76a09c7e 100644
--- a/main.go
+++ b/main.go
@@ -20,21 +20,27 @@ import (
utilVersion "k8s.io/apimachinery/pkg/util/version"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
+ "k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
+ ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook"
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
configcontroller "github.com/clastix/capsule/controllers/config"
+ "github.com/clastix/capsule/controllers/pv"
rbaccontroller "github.com/clastix/capsule/controllers/rbac"
+ "github.com/clastix/capsule/controllers/resources"
servicelabelscontroller "github.com/clastix/capsule/controllers/servicelabels"
tenantcontroller "github.com/clastix/capsule/controllers/tenant"
tlscontroller "github.com/clastix/capsule/controllers/tls"
"github.com/clastix/capsule/pkg/configuration"
"github.com/clastix/capsule/pkg/indexer"
"github.com/clastix/capsule/pkg/webhook"
+ "github.com/clastix/capsule/pkg/webhook/defaults"
"github.com/clastix/capsule/pkg/webhook/ingress"
namespacewebhook "github.com/clastix/capsule/pkg/webhook/namespace"
"github.com/clastix/capsule/pkg/webhook/networkpolicy"
@@ -45,6 +51,7 @@ import (
"github.com/clastix/capsule/pkg/webhook/route"
"github.com/clastix/capsule/pkg/webhook/service"
"github.com/clastix/capsule/pkg/webhook/tenant"
+ tntresource "github.com/clastix/capsule/pkg/webhook/tenantresource"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -58,6 +65,7 @@ func init() {
utilruntime.Must(capsulev1alpha1.AddToScheme(scheme))
utilruntime.Must(capsulev1beta1.AddToScheme(scheme))
+ utilruntime.Must(capsulev1beta2.AddToScheme(scheme))
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
}
@@ -69,14 +77,17 @@ func printVersion() {
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
}
-// nolint:maintidx
+//nolint:maintidx,cyclop
func main() {
var enableLeaderElection, version bool
var metricsAddr, namespace, serviceAccountName, capsuleUserName, configurationName string
+ var webhookPort int
+
var goFlagSet goflag.FlagSet
+ flag.IntVar(&webhookPort, "webhook-port", 9443, "The port the webhook server binds to.")
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. "+
@@ -120,12 +131,19 @@ func main() {
}
manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
- Scheme: scheme,
- MetricsBindAddress: metricsAddr,
- Port: 9443,
+ Scheme: scheme,
+ MetricsBindAddress: metricsAddr,
+ WebhookServer: ctrlwebhook.NewServer(ctrlwebhook.Options{
+ Port: webhookPort,
+ }),
LeaderElection: enableLeaderElection,
LeaderElectionID: "42c733ea.clastix.capsule.io",
HealthProbeBindAddress: ":10080",
+ NewClient: func(config *rest.Config, options client.Options) (client.Client, error) {
+ options.Cache.Unstructured = true
+
+ return client.New(config, options)
+ },
})
if err != nil {
setupLog.Error(err, "unable to start manager")
@@ -187,7 +205,17 @@ func main() {
}
if err = (&capsulev1alpha1.Tenant{}).SetupWebhookWithManager(manager); err != nil {
- setupLog.Error(err, "unable to create conversion webhook", "webhook", "Tenant")
+ setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1alpha1.Tenant")
+ os.Exit(1)
+ }
+
+ if err = (&capsulev1alpha1.CapsuleConfiguration{}).SetupWebhookWithManager(manager); err != nil {
+ setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1alpha1.CapsuleConfiguration")
+ os.Exit(1)
+ }
+
+ if err = (&capsulev1beta1.Tenant{}).SetupWebhookWithManager(manager); err != nil {
+ setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1beta1.Tenant")
os.Exit(1)
}
@@ -206,16 +234,18 @@ func main() {
// webhooks: the order matters, don't change it and just append
webhooksList := append(
make([]webhook.Webhook, 0),
- route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(), pod.PriorityClass()),
+ route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(), pod.PriorityClass(), pod.RuntimeClass()),
route.Namespace(utils.InCapsuleGroups(cfg, namespacewebhook.PatchHandler(), namespacewebhook.QuotaHandler(), namespacewebhook.FreezeHandler(cfg), namespacewebhook.PrefixHandler(cfg), namespacewebhook.UserMetadataHandler())),
- route.Ingress(ingress.Class(cfg), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()),
- route.PVC(pvc.Handler()),
+ route.Ingress(ingress.Class(cfg, kubeVersion), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()),
+ route.PVC(pvc.Validating(), pvc.PersistentVolumeReuse()),
route.Service(service.Handler()),
+ route.TenantResourceObjects(utils.InCapsuleGroups(cfg, tntresource.WriteOpsHandler())),
route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())),
route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler()),
route.OwnerReference(utils.InCapsuleGroups(cfg, namespacewebhook.OwnerReferenceHandler(), ownerreference.Handler(cfg, capsuleUserName))),
- route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler()),
+ route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler(manager.GetClient())),
route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))),
+ route.Defaults(defaults.Handler(cfg, kubeVersion)),
)
nodeWebhookSupported, _ := utils.NodeWebhookSupported(kubeVersion)
@@ -230,6 +260,7 @@ func main() {
rbacManager := &rbaccontroller.Manager{
Log: ctrl.Log.WithName("controllers").WithName("Rbac"),
+ Client: manager.GetClient(),
Configuration: cfg,
}
@@ -265,6 +296,11 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "EndpointSliceLabels")
}
+ if err = (&pv.Controller{}).SetupWithManager(manager); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "PersistentVolume")
+ os.Exit(1)
+ }
+
if err = (&configcontroller.Manager{
Log: ctrl.Log.WithName("controllers").WithName("CapsuleConfiguration"),
}).SetupWithManager(manager, configurationName); err != nil {
@@ -272,6 +308,16 @@ func main() {
os.Exit(1)
}
+ if err = (&resources.Global{}).SetupWithManager(manager); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "resources.Global")
+ os.Exit(1)
+ }
+
+ if err = (&resources.Namespaced{}).SetupWithManager(manager); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "resources.Namespaced")
+ os.Exit(1)
+ }
+
setupLog.Info("starting manager")
if err = manager.Start(ctx); err != nil {
diff --git a/api/v1beta1/additional_metadata.go b/pkg/api/additional_metadata.go
similarity index 82%
rename from api/v1beta1/additional_metadata.go
rename to pkg/api/additional_metadata.go
index 3cf7f5e1..7e8ef104 100644
--- a/api/v1beta1/additional_metadata.go
+++ b/pkg/api/additional_metadata.go
@@ -1,7 +1,9 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1beta1
+package api
+
+// +kubebuilder:object:generate=true
type AdditionalMetadataSpec struct {
Labels map[string]string `json:"labels,omitempty"`
diff --git a/api/v1alpha1/additional_role_bindings.go b/pkg/api/additional_role_bindings.go
similarity index 85%
rename from api/v1alpha1/additional_role_bindings.go
rename to pkg/api/additional_role_bindings.go
index ecbe686b..e7c7e7f3 100644
--- a/api/v1alpha1/additional_role_bindings.go
+++ b/pkg/api/additional_role_bindings.go
@@ -1,10 +1,12 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1alpha1
+package api
import rbacv1 "k8s.io/api/rbac/v1"
+// +kubebuilder:object:generate=true
+
type AdditionalRoleBindingsSpec struct {
ClusterRoleName string `json:"clusterRoleName"`
// kubebuilder:validation:Minimum=1
diff --git a/pkg/api/allowed_list.go b/pkg/api/allowed_list.go
new file mode 100644
index 00000000..3d2ef867
--- /dev/null
+++ b/pkg/api/allowed_list.go
@@ -0,0 +1,90 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package api
+
+import (
+ "regexp"
+ "sort"
+ "strings"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// +kubebuilder:object:generate=true
+
+type DefaultAllowedListSpec struct {
+ SelectorAllowedListSpec `json:",inline"`
+ Default string `json:"default,omitempty"`
+}
+
+func (in *DefaultAllowedListSpec) MatchDefault(value string) bool {
+ return in.Default == value
+}
+
+// +kubebuilder:object:generate=true
+
+type SelectorAllowedListSpec struct {
+ AllowedListSpec `json:",inline"`
+ metav1.LabelSelector `json:",inline"`
+}
+
+func (in *SelectorAllowedListSpec) MatchSelectByName(obj client.Object) bool {
+ if obj != nil {
+ return in.AllowedListSpec.Match(obj.GetName()) || in.SelectorMatch(obj)
+ }
+
+ return false
+}
+
+func (in *SelectorAllowedListSpec) SelectorMatch(obj client.Object) bool {
+ if obj != nil {
+ selector, err := metav1.LabelSelectorAsSelector(&in.LabelSelector)
+ if err != nil {
+ return false
+ }
+
+ return selector.Matches(labels.Set(obj.GetLabels()))
+ }
+
+ return false
+}
+
+// +kubebuilder:object:generate=true
+
+type AllowedListSpec struct {
+ Exact []string `json:"allowed,omitempty"`
+ Regex string `json:"allowedRegex,omitempty"`
+}
+
+func (in *AllowedListSpec) Match(value string) (ok bool) {
+ if in.ExactMatch(value) || in.RegexMatch(value) {
+ return true
+ }
+
+ return false
+}
+
+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
+}
diff --git a/api/v1beta1/allowed_list_test.go b/pkg/api/allowed_list_test.go
similarity index 98%
rename from api/v1beta1/allowed_list_test.go
rename to pkg/api/allowed_list_test.go
index 20364bf6..67fc247b 100644
--- a/api/v1beta1/allowed_list_test.go
+++ b/pkg/api/allowed_list_test.go
@@ -1,7 +1,8 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
+
//nolint:dupl
-package v1beta1
+package api
import (
"testing"
diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go
new file mode 100644
index 00000000..37753567
--- /dev/null
+++ b/pkg/api/annotations.go
@@ -0,0 +1,12 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package api
+
+const (
+ 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"
+)
diff --git a/api/v1alpha1/external_service_ips.go b/pkg/api/external_service_ips.go
similarity index 84%
rename from api/v1alpha1/external_service_ips.go
rename to pkg/api/external_service_ips.go
index 2c1112d9..4bd1c9b6 100644
--- a/api/v1alpha1/external_service_ips.go
+++ b/pkg/api/external_service_ips.go
@@ -1,11 +1,13 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1alpha1
+package api
// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
type AllowedIP string
+// +kubebuilder:object:generate=true
+
type ExternalServiceIPsSpec struct {
Allowed []AllowedIP `json:"allowed"`
}
diff --git a/api/v1beta1/forbidden_list.go b/pkg/api/forbidden_list.go
similarity index 85%
rename from api/v1beta1/forbidden_list.go
rename to pkg/api/forbidden_list.go
index 0b02d75d..acabb1ed 100644
--- a/api/v1beta1/forbidden_list.go
+++ b/pkg/api/forbidden_list.go
@@ -1,7 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-//nolint:dupl
-package v1beta1
+
+package api
import (
"regexp"
@@ -9,12 +9,14 @@ import (
"strings"
)
+// +kubebuilder:object:generate=true
+
type ForbiddenListSpec struct {
Exact []string `json:"denied,omitempty"`
Regex string `json:"deniedRegex,omitempty"`
}
-func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) {
+func (in ForbiddenListSpec) ExactMatch(value string) (ok bool) {
if len(in.Exact) > 0 {
sort.SliceStable(in.Exact, func(i, j int) bool {
return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j])
diff --git a/api/v1beta1/forbidden_list_test.go b/pkg/api/forbidden_list_test.go
similarity index 98%
rename from api/v1beta1/forbidden_list_test.go
rename to pkg/api/forbidden_list_test.go
index ef80d762..48c0e676 100644
--- a/api/v1beta1/forbidden_list_test.go
+++ b/pkg/api/forbidden_list_test.go
@@ -1,7 +1,8 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
+//
//nolint:dupl
-package v1beta1
+package api
import (
"testing"
diff --git a/api/v1beta1/hostname_collision_scope.go b/pkg/api/hostname_collision_scope.go
similarity index 96%
rename from api/v1beta1/hostname_collision_scope.go
rename to pkg/api/hostname_collision_scope.go
index 6bed62b9..31a09bde 100644
--- a/api/v1beta1/hostname_collision_scope.go
+++ b/pkg/api/hostname_collision_scope.go
@@ -1,7 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1beta1
+package api
const (
HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster"
diff --git a/api/v1beta1/image_pull_policy.go b/pkg/api/image_pull_policy.go
similarity index 93%
rename from api/v1beta1/image_pull_policy.go
rename to pkg/api/image_pull_policy.go
index 35076840..3535f259 100644
--- a/api/v1beta1/image_pull_policy.go
+++ b/pkg/api/image_pull_policy.go
@@ -1,7 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1beta1
+package api
// +kubebuilder:validation:Enum=Always;Never;IfNotPresent
type ImagePullPolicySpec string
diff --git a/api/v1beta1/limit_ranges.go b/pkg/api/limit_ranges.go
similarity index 80%
rename from api/v1beta1/limit_ranges.go
rename to pkg/api/limit_ranges.go
index 81d0e431..649099ad 100644
--- a/api/v1beta1/limit_ranges.go
+++ b/pkg/api/limit_ranges.go
@@ -1,10 +1,12 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1beta1
+package api
import corev1 "k8s.io/api/core/v1"
+// +kubebuilder:object:generate=true
+
type LimitRangesSpec struct {
Items []corev1.LimitRangeSpec `json:"items,omitempty"`
}
diff --git a/api/v1beta1/network_policy.go b/pkg/api/network_policy.go
similarity index 82%
rename from api/v1beta1/network_policy.go
rename to pkg/api/network_policy.go
index 18c96489..b9789eb2 100644
--- a/api/v1beta1/network_policy.go
+++ b/pkg/api/network_policy.go
@@ -1,12 +1,14 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1beta1
+package api
import (
networkingv1 "k8s.io/api/networking/v1"
)
+// +kubebuilder:object:generate=true
+
type NetworkPolicySpec struct {
Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"`
}
diff --git a/api/v1beta1/resource_quota.go b/pkg/api/resource_quota.go
similarity index 92%
rename from api/v1beta1/resource_quota.go
rename to pkg/api/resource_quota.go
index 4f0a48a6..1eb5ae48 100644
--- a/api/v1beta1/resource_quota.go
+++ b/pkg/api/resource_quota.go
@@ -1,7 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1beta1
+package api
import corev1 "k8s.io/api/core/v1"
@@ -13,6 +13,8 @@ const (
ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace"
)
+// +kubebuilder:object:generate=true
+
type ResourceQuotaSpec struct {
// +kubebuilder:default=Tenant
// Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
diff --git a/pkg/api/service_allowed_types.go b/pkg/api/service_allowed_types.go
new file mode 100644
index 00000000..e1e604e2
--- /dev/null
+++ b/pkg/api/service_allowed_types.go
@@ -0,0 +1,18 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package api
+
+// +kubebuilder:object:generate=true
+
+type AllowedServices struct {
+ // +kubebuilder:default=true
+ // Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
+ NodePort *bool `json:"nodePort,omitempty"`
+ // +kubebuilder:default=true
+ // Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
+ ExternalName *bool `json:"externalName,omitempty"`
+ // +kubebuilder:default=true
+ // Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
+ LoadBalancer *bool `json:"loadBalancer,omitempty"`
+}
diff --git a/pkg/api/service_options.go b/pkg/api/service_options.go
new file mode 100644
index 00000000..22168d4c
--- /dev/null
+++ b/pkg/api/service_options.go
@@ -0,0 +1,15 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package api
+
+// +kubebuilder:object:generate=true
+
+type ServiceOptions struct {
+ // Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
+ AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
+ // Block or deny certain type of Services. Optional.
+ AllowedServices *AllowedServices `json:"allowedServices,omitempty"`
+ // Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
+ ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"`
+}
diff --git a/pkg/api/status_namespaces.go b/pkg/api/status_namespaces.go
new file mode 100644
index 00000000..016ff841
--- /dev/null
+++ b/pkg/api/status_namespaces.go
@@ -0,0 +1,8 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package api
+
+type Tenant interface {
+ GetNamespaces() []string
+}
diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go
new file mode 100644
index 00000000..93b61c23
--- /dev/null
+++ b/pkg/api/zz_generated.deepcopy.go
@@ -0,0 +1,283 @@
+//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 api
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ networkingv1 "k8s.io/api/networking/v1"
+ "k8s.io/api/rbac/v1"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
+ *out = *in
+ if in.Labels != nil {
+ in, out := &in.Labels, &out.Labels
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+ if in.Annotations != nil {
+ in, out := &in.Annotations, &out.Annotations
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec.
+func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(AdditionalMetadataSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) {
+ *out = *in
+ if in.Subjects != nil {
+ in, out := &in.Subjects, &out.Subjects
+ *out = make([]v1.Subject, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec.
+func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(AdditionalRoleBindingsSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) {
+ *out = *in
+ if in.Exact != nil {
+ in, out := &in.Exact, &out.Exact
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec.
+func (in *AllowedListSpec) DeepCopy() *AllowedListSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(AllowedListSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AllowedServices) DeepCopyInto(out *AllowedServices) {
+ *out = *in
+ if in.NodePort != nil {
+ in, out := &in.NodePort, &out.NodePort
+ *out = new(bool)
+ **out = **in
+ }
+ if in.ExternalName != nil {
+ in, out := &in.ExternalName, &out.ExternalName
+ *out = new(bool)
+ **out = **in
+ }
+ if in.LoadBalancer != nil {
+ in, out := &in.LoadBalancer, &out.LoadBalancer
+ *out = new(bool)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices.
+func (in *AllowedServices) DeepCopy() *AllowedServices {
+ if in == nil {
+ return nil
+ }
+ out := new(AllowedServices)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DefaultAllowedListSpec) DeepCopyInto(out *DefaultAllowedListSpec) {
+ *out = *in
+ in.SelectorAllowedListSpec.DeepCopyInto(&out.SelectorAllowedListSpec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultAllowedListSpec.
+func (in *DefaultAllowedListSpec) DeepCopy() *DefaultAllowedListSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(DefaultAllowedListSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) {
+ *out = *in
+ if in.Allowed != nil {
+ in, out := &in.Allowed, &out.Allowed
+ *out = make([]AllowedIP, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec.
+func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ExternalServiceIPsSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) {
+ *out = *in
+ if in.Exact != nil {
+ in, out := &in.Exact, &out.Exact
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec.
+func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ForbiddenListSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *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 *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 *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 *SelectorAllowedListSpec) DeepCopyInto(out *SelectorAllowedListSpec) {
+ *out = *in
+ in.AllowedListSpec.DeepCopyInto(&out.AllowedListSpec)
+ in.LabelSelector.DeepCopyInto(&out.LabelSelector)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectorAllowedListSpec.
+func (in *SelectorAllowedListSpec) DeepCopy() *SelectorAllowedListSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(SelectorAllowedListSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) {
+ *out = *in
+ if in.AdditionalMetadata != nil {
+ in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
+ *out = new(AdditionalMetadataSpec)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.AllowedServices != nil {
+ in, out := &in.AllowedServices, &out.AllowedServices
+ *out = new(AllowedServices)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.ExternalServiceIPs != nil {
+ in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs
+ *out = new(ExternalServiceIPsSpec)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions.
+func (in *ServiceOptions) DeepCopy() *ServiceOptions {
+ if in == nil {
+ return nil
+ }
+ out := new(ServiceOptions)
+ in.DeepCopyInto(out)
+ return out
+}
diff --git a/pkg/cert/ca.go b/pkg/cert/ca.go
index cd676e83..d09fcf17 100644
--- a/pkg/cert/ca.go
+++ b/pkg/cert/ca.go
@@ -144,7 +144,7 @@ func NewCertificateAuthorityFromBytes(certBytes, keyBytes []byte) (*CapsuleCA, e
}, nil
}
-// nolint:nakedret
+//nolint:nakedret
func (c *CapsuleCA) GenerateCertificate(opts CertificateOptions) (certificatePem *bytes.Buffer, certificateKey *bytes.Buffer, err error) {
var certPrivKey *rsa.PrivateKey
certPrivKey, err = rsa.GenerateKey(rand.Reader, 4096)
diff --git a/pkg/configuration/client.go b/pkg/configuration/client.go
index 3a0dd009..c50d1b20 100644
--- a/pkg/configuration/client.go
+++ b/pkg/configuration/client.go
@@ -6,32 +6,30 @@ package configuration
import (
"context"
"regexp"
- "strconv"
- "strings"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ capsuleapi "github.com/clastix/capsule/pkg/api"
)
// capsuleConfiguration is the Capsule Configuration retrieval mode
// using a closure that provides the desired configuration.
type capsuleConfiguration struct {
- retrievalFn func() *capsulev1alpha1.CapsuleConfiguration
+ retrievalFn func() *capsulev1beta2.CapsuleConfiguration
}
func NewCapsuleConfiguration(ctx context.Context, client client.Client, name string) Configuration {
- return &capsuleConfiguration{retrievalFn: func() *capsulev1alpha1.CapsuleConfiguration {
- config := &capsulev1alpha1.CapsuleConfiguration{}
+ return &capsuleConfiguration{retrievalFn: func() *capsulev1beta2.CapsuleConfiguration {
+ config := &capsulev1beta2.CapsuleConfiguration{}
if err := client.Get(ctx, types.NamespacedName{Name: name}, config); err != nil {
if apierrors.IsNotFound(err) {
- return &capsulev1alpha1.CapsuleConfiguration{
- Spec: capsulev1alpha1.CapsuleConfigurationSpec{
+ return &capsulev1beta2.CapsuleConfiguration{
+ Spec: capsulev1beta2.CapsuleConfigurationSpec{
UserGroups: []string{"capsule.clastix.io"},
ForceTenantPrefix: false,
ProtectedNamespaceRegexpString: "",
@@ -45,10 +43,10 @@ func NewCapsuleConfiguration(ctx context.Context, client client.Client, name str
}}
}
-func (c capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error) {
+func (c *capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error) {
expr := c.retrievalFn().Spec.ProtectedNamespaceRegexpString
if len(expr) == 0 {
- return nil, nil // nolint:nilnil
+ return nil, nil //nolint:nilnil
}
r, err := regexp.Compile(expr)
@@ -59,120 +57,46 @@ func (c capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error)
return r, nil
}
-func (c capsuleConfiguration) ForceTenantPrefix() bool {
+func (c *capsuleConfiguration) ForceTenantPrefix() bool {
return c.retrievalFn().Spec.ForceTenantPrefix
}
-func (c capsuleConfiguration) TLSSecretName() (name string) {
- name = TLSSecretName
-
- if c.retrievalFn().Annotations == nil {
- return
- }
-
- v, ok := c.retrievalFn().Annotations[capsulev1alpha1.TLSSecretNameAnnotation]
- if ok {
- return v
- }
-
- return
+func (c *capsuleConfiguration) TLSSecretName() (name string) {
+ return c.retrievalFn().Spec.CapsuleResources.TLSSecretName
}
-func (c capsuleConfiguration) EnableTLSConfiguration() bool {
- annotationValue, ok := c.retrievalFn().Annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName]
-
- if ok {
- value, err := strconv.ParseBool(annotationValue)
- if err != nil {
- return false
- }
-
- return value
- }
-
- return false
+func (c *capsuleConfiguration) EnableTLSConfiguration() bool {
+ return c.retrievalFn().Spec.EnableTLSReconciler
}
-func (c capsuleConfiguration) MutatingWebhookConfigurationName() (name string) {
- name = MutatingWebhookConfigurationName
-
- if c.retrievalFn().Annotations == nil {
- return
- }
-
- v, ok := c.retrievalFn().Annotations[capsulev1alpha1.MutatingWebhookConfigurationName]
- if ok {
- return v
- }
-
- return
+func (c *capsuleConfiguration) MutatingWebhookConfigurationName() (name string) {
+ return c.retrievalFn().Spec.CapsuleResources.MutatingWebhookConfigurationName
}
-func (c capsuleConfiguration) TenantCRDName() string {
+func (c *capsuleConfiguration) TenantCRDName() string {
return TenantCRDName
}
-func (c capsuleConfiguration) ValidatingWebhookConfigurationName() (name string) {
- name = ValidatingWebhookConfigurationName
-
- if c.retrievalFn().Annotations == nil {
- return
- }
-
- v, ok := c.retrievalFn().Annotations[capsulev1alpha1.ValidatingWebhookConfigurationName]
- if ok {
- return v
- }
-
- return
+func (c *capsuleConfiguration) ValidatingWebhookConfigurationName() (name string) {
+ return c.retrievalFn().Spec.CapsuleResources.ValidatingWebhookConfigurationName
}
-func (c capsuleConfiguration) UserGroups() []string {
+func (c *capsuleConfiguration) UserGroups() []string {
return c.retrievalFn().Spec.UserGroups
}
-func (c capsuleConfiguration) hasForbiddenNodeLabelsAnnotations() bool {
- if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation]; ok {
- return true
- }
-
- if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation]; ok {
- return true
- }
-
- return false
-}
-
-func (c capsuleConfiguration) hasForbiddenNodeAnnotationsAnnotations() bool {
- if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation]; ok {
- return true
- }
-
- if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation]; ok {
- return true
- }
-
- return false
-}
-
-func (c *capsuleConfiguration) ForbiddenUserNodeLabels() *capsulev1beta1.ForbiddenListSpec {
- if !c.hasForbiddenNodeLabelsAnnotations() {
+func (c *capsuleConfiguration) ForbiddenUserNodeLabels() *capsuleapi.ForbiddenListSpec {
+ if c.retrievalFn().Spec.NodeMetadata == nil {
return nil
}
- return &capsulev1beta1.ForbiddenListSpec{
- Exact: strings.Split(c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation], ","),
- Regex: c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation],
- }
+ return &c.retrievalFn().Spec.NodeMetadata.ForbiddenLabels
}
-func (c *capsuleConfiguration) ForbiddenUserNodeAnnotations() *capsulev1beta1.ForbiddenListSpec {
- if !c.hasForbiddenNodeAnnotationsAnnotations() {
+func (c *capsuleConfiguration) ForbiddenUserNodeAnnotations() *capsuleapi.ForbiddenListSpec {
+ if c.retrievalFn().Spec.NodeMetadata == nil {
return nil
}
- return &capsulev1beta1.ForbiddenListSpec{
- Exact: strings.Split(c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation], ","),
- Regex: c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation],
- }
+ return &c.retrievalFn().Spec.NodeMetadata.ForbiddenAnnotations
}
diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go
index 5036ac62..68ad6416 100644
--- a/pkg/configuration/configuration.go
+++ b/pkg/configuration/configuration.go
@@ -6,14 +6,11 @@ package configuration
import (
"regexp"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsuleapi "github.com/clastix/capsule/pkg/api"
)
const (
- TLSSecretName = "capsule-tls"
- MutatingWebhookConfigurationName = "capsule-mutating-webhook-configuration"
- ValidatingWebhookConfigurationName = "capsule-validating-webhook-configuration"
- TenantCRDName = "tenants.capsule.clastix.io"
+ TenantCRDName = "tenants.capsule.clastix.io"
)
type Configuration interface {
@@ -27,6 +24,6 @@ type Configuration interface {
ValidatingWebhookConfigurationName() string
TenantCRDName() string
UserGroups() []string
- ForbiddenUserNodeLabels() *capsulev1beta1.ForbiddenListSpec
- ForbiddenUserNodeAnnotations() *capsulev1beta1.ForbiddenListSpec
+ ForbiddenUserNodeLabels() *capsuleapi.ForbiddenListSpec
+ ForbiddenUserNodeAnnotations() *capsuleapi.ForbiddenListSpec
}
diff --git a/pkg/indexer/indexer.go b/pkg/indexer/indexer.go
index 2e3e88f2..e726ca8b 100644
--- a/pkg/indexer/indexer.go
+++ b/pkg/indexer/indexer.go
@@ -8,17 +8,18 @@ import (
"fmt"
"github.com/go-logr/logr"
- "github.com/pkg/errors"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
- "k8s.io/apimachinery/pkg/api/meta"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/indexer/ingress"
"github.com/clastix/capsule/pkg/indexer/namespace"
"github.com/clastix/capsule/pkg/indexer/tenant"
+ "github.com/clastix/capsule/pkg/indexer/tenantresource"
+ "github.com/clastix/capsule/pkg/utils"
)
type CustomIndexer interface {
@@ -29,18 +30,19 @@ type CustomIndexer interface {
func AddToManager(ctx context.Context, log logr.Logger, mgr manager.Manager) error {
indexers := []CustomIndexer{
- tenant.NamespacesReference{},
+ tenant.NamespacesReference{Obj: &capsulev1beta2.Tenant{}},
tenant.OwnerReference{},
namespace.OwnerReference{},
ingress.HostnamePath{Obj: &extensionsv1beta1.Ingress{}},
ingress.HostnamePath{Obj: &networkingv1beta1.Ingress{}},
ingress.HostnamePath{Obj: &networkingv1.Ingress{}},
+ tenantresource.GlobalProcessedItems{},
+ tenantresource.LocalProcessedItems{},
}
for _, f := range indexers {
if err := mgr.GetFieldIndexer().IndexField(ctx, f.Object(), f.Field(), f.Func()); err != nil {
- missingAPIError := &meta.NoKindMatchError{}
- if errors.As(err, &missingAPIError) {
+ if utils.IsUnsupportedAPI(err) {
log.Info(fmt.Sprintf("skipping setup of Indexer %T for object %T", f, f.Object()), "error", err.Error())
continue
diff --git a/pkg/indexer/ingress/hostname_path.go b/pkg/indexer/ingress/hostname_path.go
index 459bfc26..e717d0ea 100644
--- a/pkg/indexer/ingress/hostname_path.go
+++ b/pkg/indexer/ingress/hostname_path.go
@@ -22,7 +22,7 @@ type HostnamePath struct {
Obj metav1.Object
}
-// nolint:forcetypeassert
+//nolint:forcetypeassert
func (s HostnamePath) Object() client.Object {
return s.Obj.(client.Object)
}
@@ -33,7 +33,7 @@ func (s HostnamePath) Field() string {
func (s HostnamePath) Func() client.IndexerFunc {
return func(object client.Object) (entries []string) {
- hostPathMap := make(map[string]sets.String)
+ hostPathMap := make(map[string]sets.Set[string])
switch ing := object.(type) {
case *networkingv1.Ingress:
diff --git a/pkg/indexer/ingress/utils.go b/pkg/indexer/ingress/utils.go
index fa008f92..a4c893e3 100644
--- a/pkg/indexer/ingress/utils.go
+++ b/pkg/indexer/ingress/utils.go
@@ -10,8 +10,8 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
)
-func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string]sets.String {
- hostPathMap := make(map[string]sets.String)
+func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string]sets.Set[string] {
+ hostPathMap := make(map[string]sets.Set[string])
for _, r := range ing.Spec.Rules {
if r.HTTP == nil {
@@ -19,7 +19,7 @@ func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string]
}
if _, ok := hostPathMap[r.Host]; !ok {
- hostPathMap[r.Host] = sets.NewString()
+ hostPathMap[r.Host] = sets.New[string]()
}
for _, path := range r.HTTP.Paths {
@@ -30,8 +30,8 @@ func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string]
return hostPathMap
}
-func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string]sets.String {
- hostPathMap := make(map[string]sets.String)
+func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string]sets.Set[string] {
+ hostPathMap := make(map[string]sets.Set[string])
for _, r := range ing.Spec.Rules {
if r.HTTP == nil {
@@ -39,7 +39,7 @@ func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string]
}
if _, ok := hostPathMap[r.Host]; !ok {
- hostPathMap[r.Host] = sets.NewString()
+ hostPathMap[r.Host] = sets.New[string]()
}
for _, path := range r.HTTP.Paths {
@@ -50,8 +50,8 @@ func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string]
return hostPathMap
}
-func hostPathMapForNetworkingV1(ing *networkingv1.Ingress) map[string]sets.String {
- hostPathMap := make(map[string]sets.String)
+func hostPathMapForNetworkingV1(ing *networkingv1.Ingress) map[string]sets.Set[string] {
+ hostPathMap := make(map[string]sets.Set[string])
for _, r := range ing.Spec.Rules {
if r.HTTP == nil {
@@ -59,7 +59,7 @@ func hostPathMapForNetworkingV1(ing *networkingv1.Ingress) map[string]sets.Strin
}
if _, ok := hostPathMap[r.Host]; !ok {
- hostPathMap[r.Host] = sets.NewString()
+ hostPathMap[r.Host] = sets.New[string]()
}
for _, path := range r.HTTP.Paths {
diff --git a/pkg/indexer/namespace/namespaces.go b/pkg/indexer/namespace/namespaces.go
index 52f9aa4d..87ea803b 100644
--- a/pkg/indexer/namespace/namespaces.go
+++ b/pkg/indexer/namespace/namespaces.go
@@ -9,7 +9,7 @@ import (
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
type OwnerReference struct{}
@@ -32,7 +32,7 @@ func (o OwnerReference) Func() client.IndexerFunc {
}
for _, or := range ns.OwnerReferences {
- if or.APIVersion == capsulev1beta1.GroupVersion.String() {
+ if or.APIVersion == capsulev1beta2.GroupVersion.String() {
res = append(res, or.Name)
}
}
diff --git a/pkg/indexer/tenant/namespaces.go b/pkg/indexer/tenant/namespaces.go
index f292d07b..ab9d8008 100644
--- a/pkg/indexer/tenant/namespaces.go
+++ b/pkg/indexer/tenant/namespaces.go
@@ -6,28 +6,24 @@ package tenant
import (
"sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/pkg/api"
)
-type NamespacesReference struct{}
+type NamespacesReference struct {
+ Obj client.Object
+}
func (o NamespacesReference) Object() client.Object {
- return &capsulev1beta1.Tenant{}
+ return o.Obj
}
func (o NamespacesReference) Field() string {
return ".status.namespaces"
}
-// nolint:forcetypeassert
+//nolint:forcetypeassert
func (o NamespacesReference) Func() client.IndexerFunc {
return func(object client.Object) []string {
- namespaces := object.(*capsulev1beta1.Tenant).DeepCopy().Status.Namespaces
-
- if namespaces == nil {
- return []string{}
- }
-
- return namespaces
+ return object.(api.Tenant).GetNamespaces()
}
}
diff --git a/pkg/indexer/tenant/owner.go b/pkg/indexer/tenant/owner.go
index f947d66d..46dc3c69 100644
--- a/pkg/indexer/tenant/owner.go
+++ b/pkg/indexer/tenant/owner.go
@@ -8,14 +8,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/utils"
)
type OwnerReference struct{}
func (o OwnerReference) Object() client.Object {
- return &capsulev1beta1.Tenant{}
+ return &capsulev1beta2.Tenant{}
}
func (o OwnerReference) Field() string {
@@ -24,9 +24,9 @@ func (o OwnerReference) Field() string {
func (o OwnerReference) Func() client.IndexerFunc {
return func(object client.Object) []string {
- tenant, ok := object.(*capsulev1beta1.Tenant)
+ tenant, ok := object.(*capsulev1beta2.Tenant)
if !ok {
- panic(fmt.Errorf("expected type *capsulev1beta1.Tenant, got %T", tenant))
+ panic(fmt.Errorf("expected type *capsulev1beta2.Tenant, got %T", tenant))
}
return utils.GetOwnersWithKinds(tenant)
diff --git a/pkg/indexer/tenantresource/constants.go b/pkg/indexer/tenantresource/constants.go
new file mode 100644
index 00000000..254622f9
--- /dev/null
+++ b/pkg/indexer/tenantresource/constants.go
@@ -0,0 +1,8 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package tenantresource
+
+const (
+ IndexerFieldName = "status.processedItems"
+)
diff --git a/pkg/indexer/tenantresource/global.go b/pkg/indexer/tenantresource/global.go
new file mode 100644
index 00000000..380d9eb8
--- /dev/null
+++ b/pkg/indexer/tenantresource/global.go
@@ -0,0 +1,34 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+//nolint:dupl
+package tenantresource
+
+import (
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+type GlobalProcessedItems struct{}
+
+func (g GlobalProcessedItems) Object() client.Object {
+ return &capsulev1beta2.GlobalTenantResource{}
+}
+
+func (g GlobalProcessedItems) Field() string {
+ return IndexerFieldName
+}
+
+func (g GlobalProcessedItems) Func() client.IndexerFunc {
+ return func(object client.Object) []string {
+ tgr := object.(*capsulev1beta2.GlobalTenantResource) //nolint:forcetypeassert
+
+ out := make([]string, 0, len(tgr.Status.ProcessedItems))
+ for _, pi := range tgr.Status.ProcessedItems {
+ out = append(out, pi.String())
+ }
+
+ return out
+ }
+}
diff --git a/pkg/indexer/tenantresource/local.go b/pkg/indexer/tenantresource/local.go
new file mode 100644
index 00000000..9413400c
--- /dev/null
+++ b/pkg/indexer/tenantresource/local.go
@@ -0,0 +1,34 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+//nolint:dupl
+package tenantresource
+
+import (
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+type LocalProcessedItems struct{}
+
+func (g LocalProcessedItems) Object() client.Object {
+ return &capsulev1beta2.TenantResource{}
+}
+
+func (g LocalProcessedItems) Field() string {
+ return IndexerFieldName
+}
+
+func (g LocalProcessedItems) Func() client.IndexerFunc {
+ return func(object client.Object) []string {
+ tgr := object.(*capsulev1beta2.TenantResource) //nolint:forcetypeassert
+
+ out := make([]string, 0, len(tgr.Status.ProcessedItems))
+ for _, pi := range tgr.Status.ProcessedItems {
+ out = append(out, pi.String())
+ }
+
+ return out
+ }
+}
diff --git a/pkg/utils/errors.go b/pkg/utils/errors.go
new file mode 100644
index 00000000..cc552035
--- /dev/null
+++ b/pkg/utils/errors.go
@@ -0,0 +1,16 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package utils
+
+import (
+ "github.com/pkg/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ "k8s.io/client-go/discovery"
+)
+
+func IsUnsupportedAPI(err error) bool {
+ missingAPIError, discoveryError := &meta.NoKindMatchError{}, &discovery.ErrGroupDiscoveryFailed{}
+
+ return errors.As(err, &missingAPIError) || errors.As(err, &discoveryError)
+}
diff --git a/pkg/utils/node_selector.go b/pkg/utils/node_selector.go
index d7160e87..94f3b785 100644
--- a/pkg/utils/node_selector.go
+++ b/pkg/utils/node_selector.go
@@ -8,14 +8,14 @@ import (
"sort"
"strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
const (
NodeSelectorAnnotation = "scheduler.alpha.kubernetes.io/node-selector"
)
-func BuildNodeSelector(tnt *capsulev1beta1.Tenant, nsAnnotations map[string]string) map[string]string {
+func BuildNodeSelector(tnt *capsulev1beta2.Tenant, nsAnnotations map[string]string) map[string]string {
if nsAnnotations == nil {
nsAnnotations = make(map[string]string)
}
diff --git a/pkg/utils/owner.go b/pkg/utils/owner.go
index 6ec97f90..fad2c695 100644
--- a/pkg/utils/owner.go
+++ b/pkg/utils/owner.go
@@ -6,10 +6,10 @@ package utils
import (
"fmt"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
-func GetOwnersWithKinds(tenant *capsulev1beta1.Tenant) (owners []string) {
+func GetOwnersWithKinds(tenant *capsulev1beta2.Tenant) (owners []string) {
for _, owner := range tenant.Spec.Owners {
owners = append(owners, fmt.Sprintf("%s:%s", owner.Kind.String(), owner.Name))
}
diff --git a/api/v1beta1/tenant_labels.go b/pkg/utils/tenant_labels.go
similarity index 79%
rename from api/v1beta1/tenant_labels.go
rename to pkg/utils/tenant_labels.go
index 836e6810..db97f7ae 100644
--- a/api/v1beta1/tenant_labels.go
+++ b/pkg/utils/tenant_labels.go
@@ -1,7 +1,7 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
-package v1beta1
+package utils
import (
"fmt"
@@ -10,11 +10,15 @@ import (
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
+
+ "github.com/clastix/capsule/api/v1alpha1"
+ "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/api/v1beta2"
)
func GetTypeLabel(t runtime.Object) (label string, err error) {
switch v := t.(type) {
- case *Tenant:
+ case *v1alpha1.Tenant, *v1beta1.Tenant, *v1beta2.Tenant:
return "capsule.clastix.io/tenant", nil
case *corev1.LimitRange:
return "capsule.clastix.io/limit-range", nil
diff --git a/pkg/utils/user_group.go b/pkg/utils/user_group.go
index 5aae517b..2db7e1e6 100644
--- a/pkg/utils/user_group.go
+++ b/pkg/utils/user_group.go
@@ -15,9 +15,7 @@ type userGroupList []string
func NewUserGroupList(groups []string) UserGroupList {
list := make(userGroupList, len(groups))
- for k, v := range groups {
- list[k] = v
- }
+ copy(list, groups)
sort.SliceStable(list, func(i, j int) bool {
return list[i] < list[j]
diff --git a/pkg/webhook/defaults/errors.go b/pkg/webhook/defaults/errors.go
new file mode 100644
index 00000000..85202fb2
--- /dev/null
+++ b/pkg/webhook/defaults/errors.go
@@ -0,0 +1,56 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package defaults
+
+import (
+ "fmt"
+)
+
+type StorageClassError struct {
+ storageClass string
+ msg error
+}
+
+func NewStorageClassError(class string, msg error) error {
+ return &StorageClassError{
+ storageClass: class,
+ msg: msg,
+ }
+}
+
+func (e StorageClassError) Error() string {
+ return fmt.Sprintf("Failed to resolve Storage Class %s: %s", e.storageClass, e.msg)
+}
+
+type IngressClassError struct {
+ ingressClass string
+ msg error
+}
+
+func NewIngressClassError(class string, msg error) error {
+ return &IngressClassError{
+ ingressClass: class,
+ msg: msg,
+ }
+}
+
+func (e IngressClassError) Error() string {
+ return fmt.Sprintf("Failed to resolve Ingress Class %s: %s", e.ingressClass, e.msg)
+}
+
+type PriorityClassError struct {
+ priorityClass string
+ msg error
+}
+
+func NewPriorityClassError(class string, msg error) error {
+ return &PriorityClassError{
+ priorityClass: class,
+ msg: msg,
+ }
+}
+
+func (e PriorityClassError) Error() string {
+ return fmt.Sprintf("Failed to resolve Priority Class %s: %s", e.priorityClass, e.msg)
+}
diff --git a/pkg/webhook/defaults/handler.go b/pkg/webhook/defaults/handler.go
new file mode 100644
index 00000000..57b38492
--- /dev/null
+++ b/pkg/webhook/defaults/handler.go
@@ -0,0 +1,68 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package defaults
+
+import (
+ "context"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/version"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ "github.com/clastix/capsule/pkg/configuration"
+ capsulewebhook "github.com/clastix/capsule/pkg/webhook"
+)
+
+type handler struct {
+ cfg configuration.Configuration
+ version *version.Version
+}
+
+func Handler(cfg configuration.Configuration, version *version.Version) capsulewebhook.Handler {
+ return &handler{
+ cfg: cfg,
+ version: version,
+ }
+}
+
+func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return h.mutate(ctx, req, client, decoder, recorder)
+ }
+}
+
+func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
+
+func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return h.mutate(ctx, req, client, decoder, recorder)
+ }
+}
+
+func (h *handler) mutate(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response {
+ var response *admission.Response
+
+ switch {
+ case req.Resource == (metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}):
+ response = mutatePodDefaults(ctx, req, c, decoder, recorder, req.Namespace)
+ case req.Resource == (metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"}):
+ response = mutatePVCDefaults(ctx, req, c, decoder, recorder, req.Namespace)
+ case req.Resource == (metav1.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingresses"}) || req.Resource == (metav1.GroupVersionResource{Group: "networking.k8s.io", Version: "v1beta1", Resource: "ingresses"}):
+ response = mutateIngressDefaults(ctx, req, h.version, c, decoder, recorder, req.Namespace)
+ }
+
+ if response == nil {
+ skip := admission.Allowed("Skipping Mutation")
+
+ response = &skip
+ }
+
+ return response
+}
diff --git a/pkg/webhook/defaults/ingress.go b/pkg/webhook/defaults/ingress.go
new file mode 100644
index 00000000..7038245c
--- /dev/null
+++ b/pkg/webhook/defaults/ingress.go
@@ -0,0 +1,80 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package defaults
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ corev1 "k8s.io/api/core/v1"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/util/version"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ capsuleingress "github.com/clastix/capsule/pkg/webhook/ingress"
+ "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+func mutateIngressDefaults(ctx context.Context, req admission.Request, version *version.Version, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response {
+ ingress, err := capsuleingress.FromRequest(req, decoder)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ ingress.SetNamespace(namespace)
+
+ var tnt *capsulev1beta2.Tenant
+
+ tnt, err = capsuleingress.TenantFromIngress(ctx, c, ingress)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ if tnt == nil {
+ return nil
+ }
+ // Validate Default Ingress
+ allowed := tnt.Spec.IngressOptions.AllowedClasses
+
+ if allowed == nil || allowed.Default == "" {
+ return nil
+ }
+
+ var mutate bool
+
+ var ingressClass client.Object
+
+ if ingressClassName := ingress.IngressClass(); ingressClassName != nil && *ingressClassName != allowed.Default {
+ if ingressClass, err = utils.GetIngressClassByName(ctx, version, c, ingressClassName); err != nil && !k8serrors.IsNotFound(err) {
+ response := admission.Denied(NewIngressClassError(*ingressClassName, err).Error())
+
+ return &response
+ }
+ } else {
+ mutate = true
+ }
+
+ if mutate = mutate || (utils.IsDefaultIngressClass(ingressClass) && ingressClass.GetName() != allowed.Default); !mutate {
+ return nil
+ }
+
+ ingress.SetIngressClass(allowed.Default)
+ // Marshal Manifest
+ marshaled, err := json.Marshal(ingress)
+ if err != nil {
+ response := admission.Errored(http.StatusInternalServerError, err)
+
+ return &response
+ }
+
+ recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Ingress Class %s to %s/%s", allowed.Default, ingress.Name(), ingress.Namespace())
+
+ response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
+
+ return &response
+}
diff --git a/pkg/webhook/defaults/pods.go b/pkg/webhook/defaults/pods.go
new file mode 100644
index 00000000..3e5ff455
--- /dev/null
+++ b/pkg/webhook/defaults/pods.go
@@ -0,0 +1,89 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package defaults
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ corev1 "k8s.io/api/core/v1"
+ schedulev1 "k8s.io/api/scheduling/v1"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response {
+ var err error
+
+ pod := &corev1.Pod{}
+ if err = decoder.Decode(req, pod); err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ pod.SetNamespace(namespace)
+
+ var tnt *capsulev1beta2.Tenant
+
+ tnt, err = utils.TenantByStatusNamespace(ctx, c, pod.Namespace)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ if tnt == nil {
+ return nil
+ }
+
+ allowed := tnt.Spec.PriorityClasses
+
+ if allowed == nil || allowed.Default == "" {
+ return nil
+ }
+
+ priorityClassPod := pod.Spec.PriorityClassName
+
+ var mutate bool
+
+ var cpc *schedulev1.PriorityClass
+ // PriorityClass name is empty, if no GlobalDefault is set and no PriorityClass was given on pod
+ if len(priorityClassPod) > 0 && priorityClassPod != allowed.Default {
+ cpc, err = utils.GetPriorityClassByName(ctx, c, priorityClassPod)
+ // Should not happen, since API already checks if PC present
+ if err != nil {
+ response := admission.Denied(NewPriorityClassError(priorityClassPod, err).Error())
+
+ return &response
+ }
+ } else {
+ mutate = true
+ }
+
+ if mutate = mutate || (utils.IsDefaultPriorityClass(cpc) && cpc.GetName() != allowed.Default); !mutate {
+ return nil
+ }
+
+ pc, err := utils.GetPriorityClassByName(ctx, c, allowed.Default)
+ if err != nil {
+ return utils.ErroredResponse(fmt.Errorf("failed to assign tenant default Priority Class: %w", err))
+ }
+
+ pod.Spec.PreemptionPolicy = pc.PreemptionPolicy
+ pod.Spec.Priority = &pc.Value
+ pod.Spec.PriorityClassName = pc.Name
+ // Marshal Pod
+ marshaled, err := json.Marshal(pod)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Priority Class %s to %s/%s", allowed.Default, pod.Namespace, pod.Name)
+
+ response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
+
+ return &response
+}
diff --git a/pkg/webhook/defaults/storage.go b/pkg/webhook/defaults/storage.go
new file mode 100644
index 00000000..2513524b
--- /dev/null
+++ b/pkg/webhook/defaults/storage.go
@@ -0,0 +1,79 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package defaults
+
+import (
+ "context"
+ "encoding/json"
+
+ corev1 "k8s.io/api/core/v1"
+ storagev1 "k8s.io/api/storage/v1"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+func mutatePVCDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response {
+ var err error
+
+ pvc := &corev1.PersistentVolumeClaim{}
+ if err = decoder.Decode(req, pvc); err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ pvc.SetNamespace(namespace)
+
+ var tnt *capsulev1beta2.Tenant
+
+ tnt, err = utils.TenantByStatusNamespace(ctx, c, pvc.Namespace)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ if tnt == nil {
+ return nil
+ }
+
+ allowed := tnt.Spec.StorageClasses
+
+ if allowed == nil || allowed.Default == "" {
+ return nil
+ }
+
+ var mutate bool
+
+ var csc *storagev1.StorageClass
+
+ if storageClassName := pvc.Spec.StorageClassName; storageClassName != nil && *storageClassName != allowed.Default {
+ csc, err = utils.GetStorageClassByName(ctx, c, *storageClassName)
+ if err != nil && !k8serrors.IsNotFound(err) {
+ response := admission.Denied(NewStorageClassError(*storageClassName, err).Error())
+
+ return &response
+ }
+ } else {
+ mutate = true
+ }
+
+ if mutate = mutate || (utils.IsDefaultStorageClass(csc) && csc.GetName() != allowed.Default); !mutate {
+ return nil
+ }
+
+ pvc.Spec.StorageClassName = &tnt.Spec.StorageClasses.Default
+ // Marshal Manifest
+ marshaled, err := json.Marshal(pvc)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Storage Class %s to %s/%s", allowed.Default, pvc.Namespace, pvc.Name)
+
+ response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
+
+ return &response
+}
diff --git a/pkg/webhook/ingress/errors.go b/pkg/webhook/ingress/errors.go
index cd0abc36..80d1a622 100644
--- a/pkg/webhook/ingress/errors.go
+++ b/pkg/webhook/ingress/errors.go
@@ -7,29 +7,32 @@ import (
"fmt"
"strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/webhook/utils"
)
type ingressClassForbiddenError struct {
- className string
- spec capsulev1beta1.AllowedListSpec
+ ingressClassName string
+ spec api.DefaultAllowedListSpec
}
-func NewIngressClassForbidden(className string, spec capsulev1beta1.AllowedListSpec) error {
+func NewIngressClassForbidden(class string, spec api.DefaultAllowedListSpec) error {
return &ingressClassForbiddenError{
- className: className,
- spec: spec,
+ ingressClassName: class,
+ spec: spec,
}
}
func (i ingressClassForbiddenError) Error() string {
- return fmt.Sprintf("Ingress Class %s is forbidden for the current Tenant%s", i.className, appendClassError(i.spec))
+ err := fmt.Sprintf("Ingress Class %s is forbidden for the current Tenant: ", i.ingressClassName)
+
+ return utils.DefaultAllowedValuesErrorMessage(i.spec, err)
}
type ingressHostnameNotValidError struct {
invalidHostnames []string
notMatchingHostnames []string
- spec capsulev1beta1.AllowedListSpec
+ spec api.AllowedListSpec
}
type ingressHostnameCollisionError struct {
@@ -44,7 +47,7 @@ func NewIngressHostnameCollision(hostname string) error {
return &ingressHostnameCollisionError{hostname: hostname}
}
-func NewIngressHostnamesNotValid(invalidHostnames []string, notMatchingHostnames []string, spec capsulev1beta1.AllowedListSpec) error {
+func NewIngressHostnamesNotValid(invalidHostnames []string, notMatchingHostnames []string, spec api.AllowedListSpec) error {
return &ingressHostnameNotValidError{invalidHostnames: invalidHostnames, notMatchingHostnames: notMatchingHostnames, spec: spec}
}
@@ -53,35 +56,40 @@ func (i ingressHostnameNotValidError) Error() string {
i.invalidHostnames, i.notMatchingHostnames, appendHostnameError(i.spec))
}
-type ingressClassNotValidError struct {
- spec capsulev1beta1.AllowedListSpec
+type ingressClassUndefinedError struct {
+ spec api.DefaultAllowedListSpec
}
-func NewIngressClassNotValid(spec capsulev1beta1.AllowedListSpec) error {
- return &ingressClassNotValidError{
+func NewIngressClassUndefined(spec api.DefaultAllowedListSpec) error {
+ return &ingressClassUndefinedError{
spec: spec,
}
}
-func (i ingressClassNotValidError) Error() string {
- return "A valid Ingress Class must be used" + appendClassError(i.spec)
+func (i ingressClassUndefinedError) Error() string {
+ return utils.DefaultAllowedValuesErrorMessage(i.spec, "No Ingress Class is forbidden for the current Tenant. Specify a Ingress Class which is allowed within the Tenant: ")
}
-// nolint:predeclared
-func appendClassError(spec capsulev1beta1.AllowedListSpec) (append string) {
- if len(spec.Exact) > 0 {
- append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", "))
- }
+type ingressClassNotValidError struct {
+ ingressClassName string
+ spec api.DefaultAllowedListSpec
+}
- if len(spec.Regex) > 0 {
- append += fmt.Sprintf(", or matching the regex %s", spec.Regex)
+func NewIngressClassNotValid(class string, spec api.DefaultAllowedListSpec) error {
+ return &ingressClassNotValidError{
+ ingressClassName: class,
+ spec: spec,
}
+}
- return
+func (i ingressClassNotValidError) Error() string {
+ err := fmt.Sprintf("Ingress Class %s is forbidden for the current Tenant: ", i.ingressClassName)
+
+ return utils.DefaultAllowedValuesErrorMessage(i.spec, err)
}
-// nolint:predeclared
-func appendHostnameError(spec capsulev1beta1.AllowedListSpec) (append string) {
+//nolint:predeclared
+func appendHostnameError(spec api.AllowedListSpec) (append string) {
if len(spec.Exact) > 0 {
append = fmt.Sprintf(", specify one of the following (%s)", strings.Join(spec.Exact, ", "))
}
diff --git a/pkg/webhook/ingress/types.go b/pkg/webhook/ingress/types.go
index 99bab5c3..63857491 100644
--- a/pkg/webhook/ingress/types.go
+++ b/pkg/webhook/ingress/types.go
@@ -20,7 +20,9 @@ type Ingress interface {
IngressClass() *string
Namespace() string
Name() string
- HostnamePathsPairs() map[string]sets.String
+ HostnamePathsPairs() map[string]sets.Set[string]
+ SetIngressClass(string)
+ SetNamespace(string)
}
type NetworkingV1 struct {
@@ -44,19 +46,37 @@ func (n NetworkingV1) IngressClass() (res *string) {
return
}
+func (n NetworkingV1) SetIngressClass(ingressClassName string) {
+ if n.Spec.IngressClassName == nil {
+ if a := n.GetAnnotations(); a != nil {
+ if _, ok := a[annotationName]; ok {
+ a[annotationName] = ingressClassName
+
+ return
+ }
+ }
+ }
+ // Assign in case the IngressClassName property was not set
+ n.Spec.IngressClassName = &ingressClassName
+}
+
func (n NetworkingV1) Namespace() string {
return n.GetNamespace()
}
-// nolint:dupl
-func (n NetworkingV1) HostnamePathsPairs() (pairs map[string]sets.String) {
- pairs = make(map[string]sets.String)
+func (n NetworkingV1) SetNamespace(ns string) {
+ n.Ingress.SetNamespace(ns)
+}
+
+//nolint:dupl
+func (n NetworkingV1) HostnamePathsPairs() (pairs map[string]sets.Set[string]) {
+ pairs = make(map[string]sets.Set[string])
for _, rule := range n.Spec.Rules {
host := rule.Host
if _, ok := pairs[host]; !ok {
- pairs[host] = sets.NewString()
+ pairs[host] = sets.New[string]()
}
if http := rule.IngressRuleValue.HTTP; http != nil {
@@ -96,19 +116,37 @@ func (n NetworkingV1Beta1) IngressClass() (res *string) {
return
}
+func (n NetworkingV1Beta1) SetIngressClass(ingressClassName string) {
+ if n.Spec.IngressClassName == nil {
+ if a := n.GetAnnotations(); a != nil {
+ if _, ok := a[annotationName]; ok {
+ a[annotationName] = ingressClassName
+
+ return
+ }
+ }
+ }
+ // Assign in case the IngressClassName property was not set
+ n.Annotations[annotationName] = ingressClassName
+}
+
func (n NetworkingV1Beta1) Namespace() string {
return n.GetNamespace()
}
-// nolint:dupl
-func (n NetworkingV1Beta1) HostnamePathsPairs() (pairs map[string]sets.String) {
- pairs = make(map[string]sets.String)
+func (n NetworkingV1Beta1) SetNamespace(ns string) {
+ n.Ingress.SetNamespace(ns)
+}
+
+//nolint:dupl
+func (n NetworkingV1Beta1) HostnamePathsPairs() (pairs map[string]sets.Set[string]) {
+ pairs = make(map[string]sets.Set[string])
for _, rule := range n.Spec.Rules {
host := rule.Host
if _, ok := pairs[host]; !ok {
- pairs[host] = sets.NewString()
+ pairs[host] = sets.New[string]()
}
if http := rule.IngressRuleValue.HTTP; http != nil {
@@ -135,6 +173,10 @@ func (e Extension) Name() string {
return e.GetName()
}
+func (e Extension) SetNamespace(ns string) {
+ e.Ingress.SetNamespace(ns)
+}
+
func (e Extension) IngressClass() (res *string) {
res = e.Spec.IngressClassName
if res == nil {
@@ -148,19 +190,31 @@ func (e Extension) IngressClass() (res *string) {
return
}
+func (e Extension) SetIngressClass(ingressClassName string) {
+ if a := e.GetAnnotations(); a != nil {
+ if _, ok := a[annotationName]; ok {
+ a[annotationName] = ingressClassName
+
+ return
+ }
+ }
+ // Assign in case the IngressClassName property was not set
+ e.Annotations[annotationName] = ingressClassName
+}
+
func (e Extension) Namespace() string {
return e.GetNamespace()
}
-// nolint:dupl
-func (e Extension) HostnamePathsPairs() (pairs map[string]sets.String) {
- pairs = make(map[string]sets.String)
+//nolint:dupl
+func (e Extension) HostnamePathsPairs() (pairs map[string]sets.Set[string]) {
+ pairs = make(map[string]sets.Set[string])
for _, rule := range e.Spec.Rules {
host := rule.Host
if _, ok := pairs[host]; !ok {
- pairs[host] = sets.NewString()
+ pairs[host] = sets.New[string]()
}
if http := rule.IngressRuleValue.HTTP; http != nil {
diff --git a/pkg/webhook/ingress/utils.go b/pkg/webhook/ingress/utils.go
index 52850a81..0fe7f99a 100644
--- a/pkg/webhook/ingress/utils.go
+++ b/pkg/webhook/ingress/utils.go
@@ -14,11 +14,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
-func tenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (*capsulev1beta1.Tenant, error) {
- tenantList := &capsulev1beta1.TenantList{}
+func TenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (*capsulev1beta2.Tenant, error) {
+ tenantList := &capsulev1beta2.TenantList{}
if err := c.List(ctx, tenantList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", ingress.Namespace()),
}); err != nil {
@@ -26,14 +26,14 @@ func tenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (*
}
if len(tenantList.Items) == 0 {
- return nil, nil // nolint:nilnil
+ return nil, nil //nolint:nilnil
}
return &tenantList.Items[0], nil
}
-// nolint:nakedret
-func ingressFromRequest(req admission.Request, decoder *admission.Decoder) (ingress Ingress, err error) {
+//nolint:nakedret
+func FromRequest(req admission.Request, decoder *admission.Decoder) (ingress Ingress, err error) {
switch req.Kind.Group {
case "networking.k8s.io":
if req.Kind.Version == "v1" {
diff --git a/pkg/webhook/ingress/validate_class.go b/pkg/webhook/ingress/validate_class.go
index c7f67c68..9f2018d2 100644
--- a/pkg/webhook/ingress/validate_class.go
+++ b/pkg/webhook/ingress/validate_class.go
@@ -5,14 +5,16 @@ package ingress
import (
"context"
+ "net/http"
- "github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/util/version"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/configuration"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
@@ -20,120 +22,94 @@ import (
type class struct {
configuration configuration.Configuration
+ version *version.Version
}
-func Class(configuration configuration.Configuration) capsulewebhook.Handler {
- return &class{configuration: configuration}
+func Class(configuration configuration.Configuration, version *version.Version) capsulewebhook.Handler {
+ return &class{
+ configuration: configuration,
+ version: version,
+ }
}
-// nolint:dupl
func (r *class) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- ingress, err := ingressFromRequest(req, decoder)
- if err != nil {
- return utils.ErroredResponse(err)
- }
-
- var tenant *capsulev1beta1.Tenant
-
- tenant, err = tenantFromIngress(ctx, client, ingress)
- if err != nil {
- return utils.ErroredResponse(err)
- }
+ return r.validate(ctx, r.version, client, req, decoder, recorder)
+ }
+}
- if tenant == nil {
- return nil
- }
+func (r *class) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return r.validate(ctx, r.version, client, req, decoder, recorder)
+ }
+}
- if err = r.validateClass(*tenant, ingress.IngressClass()); err == nil {
- return nil
- }
+func (r *class) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
- var forbiddenErr *ingressClassForbiddenError
+func (r *class) validate(ctx context.Context, version *version.Version, client client.Client, req admission.Request, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response {
+ ingress, err := FromRequest(req, decoder)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
- if errors.As(err, &forbiddenErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassForbidden", "Ingress %s/%s class is forbidden", ingress.Namespace(), ingress.Name())
- }
+ var tnt *capsulev1beta2.Tenant
- var invalidErr *ingressClassNotValidError
+ tnt, err = TenantFromIngress(ctx, client, ingress)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
- if errors.As(err, &invalidErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassNotValid", "Ingress %s/%s class is invalid", ingress.Namespace(), ingress.Name())
- }
+ if tnt == nil {
+ return nil
+ }
- response := admission.Denied(err.Error())
+ allowed := tnt.Spec.IngressOptions.AllowedClasses
- return &response
+ if allowed == nil {
+ return nil
}
-}
-// nolint:dupl
-func (r *class) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
- return func(ctx context.Context, req admission.Request) *admission.Response {
- ingress, err := ingressFromRequest(req, decoder)
- if err != nil {
- return utils.ErroredResponse(err)
- }
+ ingressClass := ingress.IngressClass()
- var tenant *capsulev1beta1.Tenant
+ if ingressClass == nil {
+ recorder.Eventf(tnt, corev1.EventTypeWarning, "MissingIngressClass", "Ingress %s/%s is missing IngressClass", req.Namespace, req.Name)
- tenant, err = tenantFromIngress(ctx, client, ingress)
- if err != nil {
- return utils.ErroredResponse(err)
- }
+ response := admission.Denied(NewIngressClassUndefined(*allowed).Error())
- if tenant == nil {
- return nil
- }
+ return &response
+ }
- if err = r.validateClass(*tenant, ingress.IngressClass()); err == nil {
- return nil
- }
+ selector := false
- var forbiddenErr *ingressClassForbiddenError
+ // Verify if the IngressClass exists and matches the label selector/expression
+ if len(allowed.MatchExpressions) > 0 || len(allowed.MatchLabels) > 0 {
+ ingressClassObj, err := utils.GetIngressClassByName(ctx, version, client, ingressClass)
+ if err != nil && !k8serrors.IsNotFound(err) {
+ response := admission.Errored(http.StatusInternalServerError, err)
- if errors.As(err, &forbiddenErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassForbidden", "Ingress %s/%s class is forbidden", ingress.Namespace(), ingress.Name())
+ return &response
}
- var invalidErr *ingressClassNotValidError
-
- if errors.As(err, &invalidErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassNotValid", "Ingress %s/%s class is invalid", ingress.Namespace(), ingress.Name())
+ // Ingress Class is present, check if it matches the selector
+ if ingressClassObj != nil {
+ selector = allowed.SelectorMatch(ingressClassObj)
}
-
- response := admission.Denied(err.Error())
-
- return &response
}
-}
-func (r *class) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
- return func(ctx context.Context, req admission.Request) *admission.Response {
+ switch {
+ case allowed.MatchDefault(*ingressClass):
return nil
- }
-}
-
-func (r *class) validateClass(tenant capsulev1beta1.Tenant, ingressClass *string) error {
- if tenant.Spec.IngressOptions.AllowedClasses == nil {
+ case allowed.Match(*ingressClass) || selector:
return nil
- }
+ default:
+ recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenIngressClass", "Ingress %s/%s IngressClass %s is forbidden for the current Tenant", req.Namespace, req.Name, &ingressClass)
- if ingressClass == nil {
- return NewIngressClassNotValid(*tenant.Spec.IngressOptions.AllowedClasses)
- }
+ response := admission.Denied(NewIngressClassForbidden(*ingressClass, *allowed).Error())
- var valid, matched bool
-
- if len(tenant.Spec.IngressOptions.AllowedClasses.Exact) > 0 {
- valid = tenant.Spec.IngressOptions.AllowedClasses.ExactMatch(*ingressClass)
- }
-
- matched = tenant.Spec.IngressOptions.AllowedClasses.RegexMatch(*ingressClass)
-
- if !valid && !matched {
- return NewIngressClassForbidden(*ingressClass, *tenant.Spec.IngressOptions.AllowedClasses)
+ return &response
}
-
- return nil
}
diff --git a/pkg/webhook/ingress/validate_collision.go b/pkg/webhook/ingress/validate_collision.go
index 62b2a33d..e552be81 100644
--- a/pkg/webhook/ingress/validate_collision.go
+++ b/pkg/webhook/ingress/validate_collision.go
@@ -18,7 +18,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/api"
"github.com/clastix/capsule/pkg/configuration"
"github.com/clastix/capsule/pkg/indexer/ingress"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
@@ -33,84 +34,58 @@ func Collision(configuration configuration.Configuration) capsulewebhook.Handler
return &collision{configuration: configuration}
}
-// nolint:dupl
func (r *collision) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- ing, err := ingressFromRequest(req, decoder)
- if err != nil {
- return utils.ErroredResponse(err)
- }
-
- var tenant *capsulev1beta1.Tenant
-
- tenant, err = tenantFromIngress(ctx, client, ing)
- if err != nil {
- return utils.ErroredResponse(err)
- }
-
- if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == capsulev1beta1.HostnameCollisionScopeDisabled {
- return nil
- }
-
- if err = r.validateCollision(ctx, client, ing, tenant.Spec.IngressOptions.HostnameCollisionScope); err == nil {
- return nil
- }
-
- var collisionErr *ingressHostnameCollisionError
-
- if errors.As(err, &collisionErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameCollision", "Ingress %s/%s hostname is colliding", ing.Namespace(), ing.Name())
- }
-
- response := admission.Denied(err.Error())
-
- return &response
+ return r.validate(ctx, client, req, decoder, recorder)
}
}
-// nolint:dupl
func (r *collision) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- ing, err := ingressFromRequest(req, decoder)
- if err != nil {
- return utils.ErroredResponse(err)
- }
+ return r.validate(ctx, client, req, decoder, recorder)
+ }
+}
- var tenant *capsulev1beta1.Tenant
+func (r *collision) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
- tenant, err = tenantFromIngress(ctx, client, ing)
- if err != nil {
- return utils.ErroredResponse(err)
- }
+func (r *collision) validate(ctx context.Context, client client.Client, req admission.Request, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response {
+ ing, err := FromRequest(req, decoder)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
- if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == capsulev1beta1.HostnameCollisionScopeDisabled {
- return nil
- }
+ var tenant *capsulev1beta2.Tenant
- if err = r.validateCollision(ctx, client, ing, tenant.Spec.IngressOptions.HostnameCollisionScope); err == nil {
- return nil
- }
+ tenant, err = TenantFromIngress(ctx, client, ing)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
- var collisionErr *ingressHostnameCollisionError
+ if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled {
+ return nil
+ }
- if errors.As(err, &collisionErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameCollision", "Ingress %s/%s hostname is colliding", ing.Namespace(), ing.Name())
- }
+ if err = r.validateCollision(ctx, client, ing, tenant.Spec.IngressOptions.HostnameCollisionScope); err == nil {
+ return nil
+ }
- response := admission.Denied(err.Error())
+ var collisionErr *ingressHostnameCollisionError
- return &response
+ if errors.As(err, &collisionErr) {
+ recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameCollision", "Ingress %s/%s hostname is colliding", ing.Namespace(), ing.Name())
}
-}
-func (r *collision) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
- return func(ctx context.Context, req admission.Request) *admission.Response {
- return nil
- }
+ response := admission.Denied(err.Error())
+
+ return &response
}
-// nolint:gocognit,gocyclo,cyclop
-func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope capsulev1beta1.HostnameCollisionScope) error {
+//nolint:gocognit,gocyclo,cyclop
+func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope api.HostnameCollisionScope) error {
for hostname, paths := range ing.HostnamePathsPairs() {
for path := range paths {
var ingressObjList client.ObjectList
@@ -125,10 +100,10 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in
}
namespaces := sets.NewString()
- // nolint:exhaustive
+ //nolint:exhaustive
switch scope {
- case capsulev1beta1.HostnameCollisionScopeCluster:
- tenantList := &capsulev1beta1.TenantList{}
+ case api.HostnameCollisionScopeCluster:
+ tenantList := &capsulev1beta2.TenantList{}
if err := clt.List(ctx, tenantList); err != nil {
return err
}
@@ -136,10 +111,10 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in
for _, tenant := range tenantList.Items {
namespaces.Insert(tenant.Status.Namespaces...)
}
- case capsulev1beta1.HostnameCollisionScopeTenant:
+ case api.HostnameCollisionScopeTenant:
selector := client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", ing.Namespace())}
- tenantList := &capsulev1beta1.TenantList{}
+ tenantList := &capsulev1beta2.TenantList{}
if err := clt.List(ctx, tenantList, selector); err != nil {
return err
}
@@ -147,7 +122,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in
for _, tenant := range tenantList.Items {
namespaces.Insert(tenant.Status.Namespaces...)
}
- case capsulev1beta1.HostnameCollisionScopeNamespace:
+ case api.HostnameCollisionScopeNamespace:
namespaces.Insert(ing.Namespace())
}
diff --git a/pkg/webhook/ingress/validate_hostnames.go b/pkg/webhook/ingress/validate_hostnames.go
index 77df4865..9975f3b1 100644
--- a/pkg/webhook/ingress/validate_hostnames.go
+++ b/pkg/webhook/ingress/validate_hostnames.go
@@ -14,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/configuration"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
@@ -30,106 +30,75 @@ func Hostnames(configuration configuration.Configuration) capsulewebhook.Handler
func (r *hostnames) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- ingress, err := ingressFromRequest(req, decoder)
- if err != nil {
- return utils.ErroredResponse(err)
- }
-
- var tenant *capsulev1beta1.Tenant
-
- tenant, err = tenantFromIngress(ctx, c, ingress)
- if err != nil {
- return utils.ErroredResponse(err)
- }
-
- if tenant == nil || tenant.Spec.IngressOptions.AllowedHostnames == nil {
- return nil
- }
-
- hostnameList := sets.NewString()
- for hostname := range ingress.HostnamePathsPairs() {
- hostnameList.Insert(hostname)
- }
-
- if err = r.validateHostnames(*tenant, hostnameList); err == nil {
- return nil
- }
-
- var hostnameNotValidErr *ingressHostnameNotValidError
-
- if errors.As(err, &hostnameNotValidErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameNotValid", "Ingress %s/%s hostname is not valid", ingress.Namespace(), ingress.Name())
-
- response := admission.Denied(err.Error())
-
- return &response
- }
-
- return utils.ErroredResponse(err)
+ return r.validate(ctx, c, req, decoder, recorder)
}
}
func (r *hostnames) OnUpdate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- ingress, err := ingressFromRequest(req, decoder)
- if err != nil {
- return utils.ErroredResponse(err)
- }
+ return r.validate(ctx, c, req, decoder, recorder)
+ }
+}
+
+func (r *hostnames) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
- var tenant *capsulev1beta1.Tenant
+func (r *hostnames) validate(ctx context.Context, client client.Client, req admission.Request, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response {
+ ingress, err := FromRequest(req, decoder)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
- tenant, err = tenantFromIngress(ctx, c, ingress)
- if err != nil {
- return utils.ErroredResponse(err)
- }
+ var tenant *capsulev1beta2.Tenant
- if tenant == nil {
- return nil
- }
+ tenant, err = TenantFromIngress(ctx, client, ingress)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
- hostnameSet := sets.NewString()
- for hostname := range ingress.HostnamePathsPairs() {
- hostnameSet.Insert(hostname)
- }
+ if tenant == nil || tenant.Spec.IngressOptions.AllowedHostnames == nil {
+ return nil
+ }
- if err = r.validateHostnames(*tenant, hostnameSet); err == nil {
- return nil
- }
+ hostnameList := sets.New[string]()
+ for hostname := range ingress.HostnamePathsPairs() {
+ hostnameList.Insert(hostname)
+ }
- var hostnameNotValidErr *ingressHostnameNotValidError
+ if err = r.validateHostnames(*tenant, hostnameList); err == nil {
+ return nil
+ }
- if errors.As(err, &hostnameNotValidErr) {
- recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameNotValid", "Ingress %s/%s hostname is not valid", ingress.Namespace(), ingress.Name())
+ var hostnameNotValidErr *ingressHostnameNotValidError
- response := admission.Denied(err.Error())
+ if errors.As(err, &hostnameNotValidErr) {
+ recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameNotValid", "Ingress %s/%s hostname is not valid", ingress.Namespace(), ingress.Name())
- return &response
- }
+ response := admission.Denied(err.Error())
- return utils.ErroredResponse(err)
+ return &response
}
-}
-func (r *hostnames) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
- return func(ctx context.Context, req admission.Request) *admission.Response {
- return nil
- }
+ return utils.ErroredResponse(err)
}
-func (r *hostnames) validateHostnames(tenant capsulev1beta1.Tenant, hostnames sets.String) error {
+func (r *hostnames) validateHostnames(tenant capsulev1beta2.Tenant, hostnames sets.Set[string]) error {
if tenant.Spec.IngressOptions.AllowedHostnames == nil {
return nil
}
var valid, matched bool
- tenantHostnameSet := sets.NewString(tenant.Spec.IngressOptions.AllowedHostnames.Exact...)
+ tenantHostnameSet := sets.New[string](tenant.Spec.IngressOptions.AllowedHostnames.Exact...)
var invalidHostnames []string
if len(hostnames) > 0 {
if diff := hostnames.Difference(tenantHostnameSet); len(diff) > 0 {
- invalidHostnames = append(invalidHostnames, diff.List()...)
+ invalidHostnames = append(invalidHostnames, diff.UnsortedList()...)
}
if len(invalidHostnames) == 0 {
diff --git a/pkg/webhook/ingress/validate_wildcard.go b/pkg/webhook/ingress/validate_wildcard.go
index 62f9e214..c4c8a132 100644
--- a/pkg/webhook/ingress/validate_wildcard.go
+++ b/pkg/webhook/ingress/validate_wildcard.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package ingress
import (
@@ -11,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,7 +27,7 @@ func Wildcard() capsulewebhook.Handler {
func (h *wildcard) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- return h.wildcardHandler(ctx, client, req, recorder, decoder)
+ return h.validate(ctx, client, req, recorder, decoder)
}
}
@@ -36,12 +39,12 @@ func (h *wildcard) OnDelete(client client.Client, decoder *admission.Decoder, re
func (h *wildcard) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- return h.wildcardHandler(ctx, client, req, recorder, decoder)
+ return h.validate(ctx, client, req, recorder, decoder)
}
}
-func (h *wildcard) wildcardHandler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder, decoder *admission.Decoder) *admission.Response {
- tntList := &capsulev1beta1.TenantList{}
+func (h *wildcard) validate(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder, decoder *admission.Decoder) *admission.Response {
+ tntList := &capsulev1beta2.TenantList{}
if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace),
@@ -56,10 +59,9 @@ func (h *wildcard) wildcardHandler(ctx context.Context, clt client.Client, req a
tnt := tntList.Items[0]
- // Check if Annotation in manifest has value "capsule.clastix.io/deny-wildcard" set to "true".
- if tnt.IsWildcardDenied() {
+ if !tnt.Spec.IngressOptions.AllowWildcardHostnames {
// Retrieve ingress resource from request.
- ingress, err := ingressFromRequest(req, decoder)
+ ingress, err := FromRequest(req, decoder)
if err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/namespace/errors.go b/pkg/webhook/namespace/errors.go
index 6cdf732f..8b0ec4f2 100644
--- a/pkg/webhook/namespace/errors.go
+++ b/pkg/webhook/namespace/errors.go
@@ -7,11 +7,11 @@ import (
"fmt"
"strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsuleapi "github.com/clastix/capsule/pkg/api"
)
-// nolint:predeclared
-func appendForbiddenError(spec *capsulev1beta1.ForbiddenListSpec) (append string) {
+//nolint:predeclared
+func appendForbiddenError(spec *capsuleapi.ForbiddenListSpec) (append string) {
append += "Forbidden are "
if len(spec.Exact) > 0 {
append += fmt.Sprintf("one of the following (%s)", strings.Join(spec.Exact, ", "))
@@ -39,10 +39,10 @@ func (namespaceQuotaExceededError) Error() string {
type namespaceLabelForbiddenError struct {
label string
- spec *capsulev1beta1.ForbiddenListSpec
+ spec *capsuleapi.ForbiddenListSpec
}
-func NewNamespaceLabelForbiddenError(label string, forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error {
+func NewNamespaceLabelForbiddenError(label string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error {
return &namespaceLabelForbiddenError{
label: label,
spec: forbiddenSpec,
@@ -55,10 +55,10 @@ func (f namespaceLabelForbiddenError) Error() string {
type namespaceAnnotationForbiddenError struct {
annotation string
- spec *capsulev1beta1.ForbiddenListSpec
+ spec *capsuleapi.ForbiddenListSpec
}
-func NewNamespaceAnnotationForbiddenError(annotation string, forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error {
+func NewNamespaceAnnotationForbiddenError(annotation string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error {
return &namespaceAnnotationForbiddenError{
annotation: annotation,
spec: forbiddenSpec,
diff --git a/pkg/webhook/namespace/freezed.go b/pkg/webhook/namespace/freezed.go
index 290217a4..a9a1234d 100644
--- a/pkg/webhook/namespace/freezed.go
+++ b/pkg/webhook/namespace/freezed.go
@@ -13,7 +13,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/configuration"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
@@ -36,12 +36,12 @@ func (r *freezedHandler) OnCreate(client client.Client, decoder *admission.Decod
for _, objectRef := range ns.ObjectMeta.OwnerReferences {
// retrieving the selected Tenant
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil {
return utils.ErroredResponse(err)
}
- if tnt.IsCordoned() {
+ if tnt.Spec.Cordoned {
recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be attached, the current Tenant is freezed", ns.GetName())
response := admission.Denied("the selected Tenant is freezed")
@@ -56,7 +56,7 @@ func (r *freezedHandler) OnCreate(client client.Client, decoder *admission.Decod
func (r *freezedHandler) OnDelete(c client.Client, _ *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- tntList := &capsulev1beta1.TenantList{}
+ tntList := &capsulev1beta2.TenantList{}
if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", req.Name),
}); err != nil {
@@ -69,7 +69,7 @@ func (r *freezedHandler) OnDelete(c client.Client, _ *admission.Decoder, recorde
tnt := tntList.Items[0]
- if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) {
+ if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) {
recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be deleted, the current Tenant is freezed", req.Name)
response := admission.Denied("the selected Tenant is freezed")
@@ -88,7 +88,7 @@ func (r *freezedHandler) OnUpdate(c client.Client, decoder *admission.Decoder, r
return utils.ErroredResponse(err)
}
- tntList := &capsulev1beta1.TenantList{}
+ tntList := &capsulev1beta2.TenantList{}
if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", ns.Name),
}); err != nil {
@@ -101,7 +101,7 @@ func (r *freezedHandler) OnUpdate(c client.Client, decoder *admission.Decoder, r
tnt := tntList.Items[0]
- if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) {
+ if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) {
recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be updated, the current Tenant is freezed", ns.GetName())
response := admission.Denied("the selected Tenant is freezed")
diff --git a/pkg/webhook/namespace/owner_reference.go b/pkg/webhook/namespace/owner_reference.go
index b38dd40c..7f4e2960 100644
--- a/pkg/webhook/namespace/owner_reference.go
+++ b/pkg/webhook/namespace/owner_reference.go
@@ -42,6 +42,10 @@ func (r *ownerReferenceHandler) OnUpdate(_ client.Client, decoder *admission.Dec
return utils.ErroredResponse(err)
}
+ if len(oldNs.OwnerReferences) == 0 {
+ return nil
+ }
+
newNs := &corev1.Namespace{}
if err := decoder.Decode(req, newNs); err != nil {
return utils.ErroredResponse(err)
diff --git a/pkg/webhook/namespace/patch.go b/pkg/webhook/namespace/patch.go
index 13a289e5..1f3fb18e 100644
--- a/pkg/webhook/namespace/patch.go
+++ b/pkg/webhook/namespace/patch.go
@@ -14,7 +14,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ capsuleutils "github.com/clastix/capsule/pkg/utils"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -46,7 +47,7 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec
}
// Get Tenant Label
- ln, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
+ ln, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{})
if err != nil {
response := admission.Errored(http.StatusBadRequest, err)
@@ -58,7 +59,7 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec
if label, ok := ns.ObjectMeta.Labels[ln]; ok {
// retrieving the selected Tenant
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
if err = c.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil {
response := admission.Errored(http.StatusBadRequest, err)
@@ -71,11 +72,6 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec
return &response
}
- } else {
- recorder.Eventf(ns, corev1.EventTypeWarning, "NamespacePatch", e)
- response := admission.Denied(e)
-
- return &response
}
return nil
diff --git a/pkg/webhook/namespace/prefix.go b/pkg/webhook/namespace/prefix.go
index 14cf74d2..7c998ba6 100644
--- a/pkg/webhook/namespace/prefix.go
+++ b/pkg/webhook/namespace/prefix.go
@@ -14,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/configuration"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
@@ -46,7 +46,7 @@ func (r *prefixHandler) OnCreate(clt client.Client, decoder *admission.Decoder,
}
if r.configuration.ForceTenantPrefix() {
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
for _, or := range ns.ObjectMeta.OwnerReferences {
// retrieving the selected Tenant
diff --git a/pkg/webhook/namespace/quota.go b/pkg/webhook/namespace/quota.go
index 0517c7e9..86010e86 100644
--- a/pkg/webhook/namespace/quota.go
+++ b/pkg/webhook/namespace/quota.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -32,7 +32,7 @@ func (r *quotaHandler) OnCreate(client client.Client, decoder *admission.Decoder
for _, objectRef := range ns.ObjectMeta.OwnerReferences {
// retrieving the selected Tenant
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/namespace/user_metadata.go b/pkg/webhook/namespace/user_metadata.go
index ac34be46..6d42f408 100644
--- a/pkg/webhook/namespace/user_metadata.go
+++ b/pkg/webhook/namespace/user_metadata.go
@@ -13,7 +13,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,9 +24,9 @@ func UserMetadataHandler() capsulewebhook.Handler {
return &userMetadataHandler{}
}
-func (r *userMetadataHandler) validateUserMetadata(tnt *capsulev1beta1.Tenant, recorder record.EventRecorder, labels map[string]string, annotations map[string]string) *admission.Response {
- if tnt.ForbiddenUserNamespaceLabels() != nil {
- forbiddenLabels := tnt.ForbiddenUserNamespaceLabels()
+func (r *userMetadataHandler) validateUserMetadata(tnt *capsulev1beta2.Tenant, recorder record.EventRecorder, labels map[string]string, annotations map[string]string) *admission.Response {
+ if tnt.Spec.NamespaceOptions != nil {
+ forbiddenLabels := tnt.Spec.NamespaceOptions.ForbiddenLabels
for label := range labels {
var forbidden, matched bool
@@ -36,28 +36,30 @@ func (r *userMetadataHandler) validateUserMetadata(tnt *capsulev1beta1.Tenant, r
if forbidden || matched {
recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceLabel", fmt.Sprintf("Label %s is forbidden for a namespaces of the current Tenant ", label))
- response := admission.Denied(NewNamespaceLabelForbiddenError(label, forbiddenLabels).Error())
+ response := admission.Denied(NewNamespaceLabelForbiddenError(label, &forbiddenLabels).Error())
return &response
}
}
}
- if tnt.ForbiddenUserNamespaceAnnotations() != nil {
- forbiddenAnnotations := tnt.ForbiddenUserNamespaceLabels()
+ if tnt.Spec.NamespaceOptions == nil {
+ return nil
+ }
- for annotation := range annotations {
- var forbidden, matched bool
- forbidden = forbiddenAnnotations.ExactMatch(annotation)
- matched = forbiddenAnnotations.RegexMatch(annotation)
+ forbiddenAnnotations := tnt.Spec.NamespaceOptions.ForbiddenLabels
- if forbidden || matched {
- recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceAnnotation", fmt.Sprintf("Annotation %s is forbidden for a namespaces of the current Tenant ", annotation))
+ for annotation := range annotations {
+ var forbidden, matched bool
+ forbidden = forbiddenAnnotations.ExactMatch(annotation)
+ matched = forbiddenAnnotations.RegexMatch(annotation)
- response := admission.Denied(NewNamespaceAnnotationForbiddenError(annotation, forbiddenAnnotations).Error())
+ if forbidden || matched {
+ recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceAnnotation", fmt.Sprintf("Annotation %s is forbidden for a namespaces of the current Tenant ", annotation))
- return &response
- }
+ response := admission.Denied(NewNamespaceAnnotationForbiddenError(annotation, &forbiddenAnnotations).Error())
+
+ return &response
}
}
@@ -71,7 +73,7 @@ func (r *userMetadataHandler) OnCreate(client client.Client, decoder *admission.
return utils.ErroredResponse(err)
}
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
for _, objectRef := range ns.ObjectMeta.OwnerReferences {
// retrieving the selected Tenant
if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil {
@@ -104,7 +106,7 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission.
return utils.ErroredResponse(err)
}
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
for _, objectRef := range newNs.ObjectMeta.OwnerReferences {
// retrieving the selected Tenant
if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil {
@@ -123,7 +125,7 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission.
}
if v != oldNs.GetAnnotations()["scheduler.alpha.kubernetes.io/node-selector"] {
- response := admission.Denied("the the node-selector annotation is enforced, cannot be updated")
+ response := admission.Denied("the node-selector annotation is enforced, cannot be updated")
recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNodeSelectorUpdate", string(response.Result.Reason))
@@ -131,26 +133,44 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission.
}
}
- var labels, annotations map[string]string
+ labels, annotations := oldNs.GetLabels(), oldNs.GetAnnotations()
- for key, value := range newNs.GetLabels() {
- if _, ok := oldNs.GetLabels()[key]; ok {
- if labels == nil {
- labels = make(map[string]string)
- }
+ if labels == nil {
+ labels = make(map[string]string)
+ }
+ if annotations == nil {
+ annotations = make(map[string]string)
+ }
+
+ for key, value := range newNs.GetLabels() {
+ v, ok := labels[key]
+ if !ok {
labels[key] = value
+
+ continue
+ }
+
+ if v != value {
+ continue
}
+
+ delete(labels, key)
}
for key, value := range newNs.GetAnnotations() {
- if _, ok := oldNs.GetAnnotations()[key]; ok {
- if annotations == nil {
- annotations = make(map[string]string)
- }
-
+ v, ok := annotations[key]
+ if !ok {
annotations[key] = value
+
+ continue
}
+
+ if v != value {
+ continue
+ }
+
+ delete(annotations, key)
}
return r.validateUserMetadata(tnt, recorder, labels, annotations)
diff --git a/pkg/webhook/networkpolicy/validating.go b/pkg/webhook/networkpolicy/validating.go
index 0ac22113..f5cc6b5a 100644
--- a/pkg/webhook/networkpolicy/validating.go
+++ b/pkg/webhook/networkpolicy/validating.go
@@ -13,7 +13,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ capsuleutils "github.com/clastix/capsule/pkg/utils"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -30,7 +31,7 @@ func (r *handler) OnCreate(client.Client, *admission.Decoder, record.EventRecord
}
}
-func (r *handler) generic(ctx context.Context, req admission.Request, client client.Client, _ *admission.Decoder) (*capsulev1beta1.Tenant, error) {
+func (r *handler) generic(ctx context.Context, req admission.Request, client client.Client, _ *admission.Decoder) (*capsulev1beta2.Tenant, error) {
var err error
np := &networkingv1.NetworkPolicy{}
@@ -38,9 +39,9 @@ func (r *handler) generic(ctx context.Context, req admission.Request, client cli
return nil, err
}
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
- l, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
+ l, _ := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{})
if v, ok := np.GetLabels()[l]; ok {
if err = client.Get(ctx, types.NamespacedName{Name: v}, tnt); err != nil {
return nil, err
@@ -49,7 +50,7 @@ func (r *handler) generic(ctx context.Context, req admission.Request, client cli
return tnt, nil
}
- return nil, nil // nolint:nilnil
+ return nil, nil //nolint:nilnil
}
//nolint:dupl
diff --git a/pkg/webhook/node/errors.go b/pkg/webhook/node/errors.go
index 355c74d0..1b7a5f37 100644
--- a/pkg/webhook/node/errors.go
+++ b/pkg/webhook/node/errors.go
@@ -7,11 +7,11 @@ import (
"fmt"
"strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/pkg/api"
)
-// nolint:predeclared
-func appendForbiddenError(spec *capsulev1beta1.ForbiddenListSpec) (append string) {
+//nolint:predeclared
+func appendForbiddenError(spec *capsulev1beta2.ForbiddenListSpec) (append string) {
append += "Forbidden are "
if len(spec.Exact) > 0 {
append += fmt.Sprintf("one of the following (%s)", strings.Join(spec.Exact, ", "))
@@ -28,10 +28,10 @@ func appendForbiddenError(spec *capsulev1beta1.ForbiddenListSpec) (append string
}
type nodeLabelForbiddenError struct {
- spec *capsulev1beta1.ForbiddenListSpec
+ spec *capsulev1beta2.ForbiddenListSpec
}
-func NewNodeLabelForbiddenError(forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error {
+func NewNodeLabelForbiddenError(forbiddenSpec *capsulev1beta2.ForbiddenListSpec) error {
return &nodeLabelForbiddenError{
spec: forbiddenSpec,
}
@@ -42,10 +42,10 @@ func (f nodeLabelForbiddenError) Error() string {
}
type nodeAnnotationForbiddenError struct {
- spec *capsulev1beta1.ForbiddenListSpec
+ spec *capsulev1beta2.ForbiddenListSpec
}
-func NewNodeAnnotationForbiddenError(forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error {
+func NewNodeAnnotationForbiddenError(forbiddenSpec *capsulev1beta2.ForbiddenListSpec) error {
return &nodeAnnotationForbiddenError{
spec: forbiddenSpec,
}
diff --git a/pkg/webhook/ownerreference/patching.go b/pkg/webhook/ownerreference/patching.go
index 5633f9d7..374fa2bd 100644
--- a/pkg/webhook/ownerreference/patching.go
+++ b/pkg/webhook/ownerreference/patching.go
@@ -19,8 +19,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/configuration"
+ capsuleutils "github.com/clastix/capsule/pkg/utils"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -63,7 +64,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client
return &response
}
- ln, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
+ ln, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{})
if err != nil {
response := admission.Errored(http.StatusBadRequest, err)
@@ -72,7 +73,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client
// If we already had TenantName label on NS -> assign to it
if label, ok := ns.ObjectMeta.Labels[ln]; ok {
// retrieving the selected Tenant
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
if err = client.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil {
response := admission.Errored(http.StatusBadRequest, err)
@@ -97,7 +98,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client
// Find tenants belonging to user (it can be regular user or ServiceAccount)
if strings.HasPrefix(req.UserInfo.Username, "system:serviceaccount:") {
- var tntList *capsulev1beta1.TenantList
+ var tntList *capsulev1beta2.TenantList
if tntList, err = h.listTenantsForOwnerKind(ctx, "ServiceAccount", req.UserInfo.Username, client); err != nil {
response := admission.Errored(http.StatusBadRequest, err)
@@ -109,7 +110,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client
tenants = append(tenants, tnt)
}
} else {
- var tntList *capsulev1beta1.TenantList
+ var tntList *capsulev1beta2.TenantList
if tntList, err = h.listTenantsForOwnerKind(ctx, "User", req.UserInfo.Username, client); err != nil {
response := admission.Errored(http.StatusBadRequest, err)
@@ -172,9 +173,9 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client
return &response
}
-func (h *handler) patchResponseForOwnerRef(tenant *capsulev1beta1.Tenant, ns *corev1.Namespace, recorder record.EventRecorder) admission.Response {
+func (h *handler) patchResponseForOwnerRef(tenant *capsulev1beta2.Tenant, ns *corev1.Namespace, recorder record.EventRecorder) admission.Response {
scheme := runtime.NewScheme()
- _ = capsulev1beta1.AddToScheme(scheme)
+ _ = capsulev1beta2.AddToScheme(scheme)
_ = corev1.AddToScheme(scheme)
o, err := json.Marshal(ns.DeepCopy())
@@ -198,8 +199,8 @@ func (h *handler) patchResponseForOwnerRef(tenant *capsulev1beta1.Tenant, ns *co
return admission.PatchResponseFromRaw(o, c)
}
-func (h *handler) listTenantsForOwnerKind(ctx context.Context, ownerKind string, ownerName string, clt client.Client) (*capsulev1beta1.TenantList, error) {
- tntList := &capsulev1beta1.TenantList{}
+func (h *handler) listTenantsForOwnerKind(ctx context.Context, ownerKind string, ownerName string, clt client.Client) (*capsulev1beta2.TenantList, error) {
+ tntList := &capsulev1beta2.TenantList{}
fields := client.MatchingFields{
".spec.owner.ownerkind": fmt.Sprintf("%s:%s", ownerKind, ownerName),
}
@@ -208,7 +209,7 @@ func (h *handler) listTenantsForOwnerKind(ctx context.Context, ownerKind string,
return tntList, err
}
-type sortedTenants []capsulev1beta1.Tenant
+type sortedTenants []capsulev1beta2.Tenant
func (s sortedTenants) Len() int {
return len(s)
diff --git a/pkg/webhook/pod/containerregistry.go b/pkg/webhook/pod/containerregistry.go
index fbb11af8..c21e4cf0 100644
--- a/pkg/webhook/pod/containerregistry.go
+++ b/pkg/webhook/pod/containerregistry.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -25,64 +25,85 @@ func ContainerRegistry() capsulewebhook.Handler {
func (h *containerRegistryHandler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- pod := &corev1.Pod{}
- if err := decoder.Decode(req, pod); err != nil {
- return utils.ErroredResponse(err)
- }
+ return h.validate(ctx, c, decoder, recorder, req)
+ }
+}
- tntList := &capsulev1beta1.TenantList{}
- if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
- Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace),
- }); err != nil {
- return utils.ErroredResponse(err)
- }
+func (h *containerRegistryHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
- if len(tntList.Items) == 0 {
- return nil
- }
+// ust be validate on update events since updates to pods on spec.containers[*].image and spec.initContainers[*].image are allowed.
+func (h *containerRegistryHandler) OnUpdate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return h.validate(ctx, c, decoder, recorder, req)
+ }
+}
- tnt := tntList.Items[0]
+func (h *containerRegistryHandler) validate(ctx context.Context, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, req admission.Request) *admission.Response {
+ pod := &corev1.Pod{}
+ if err := decoder.Decode(req, pod); err != nil {
+ return utils.ErroredResponse(err)
+ }
- if tnt.Spec.ContainerRegistries != nil {
- var valid, matched bool
+ tntList := &capsulev1beta2.TenantList{}
+ if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
+ Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace),
+ }); err != nil {
+ return utils.ErroredResponse(err)
+ }
- for _, container := range pod.Spec.Containers {
- reg := NewRegistry(container.Image)
+ if len(tntList.Items) == 0 {
+ return nil
+ }
- if len(reg.Registry()) == 0 {
- recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry())
+ tnt := tntList.Items[0]
- response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error())
+ if tnt.Spec.ContainerRegistries != nil {
+ // Evaluate init containers
+ for _, container := range pod.Spec.InitContainers {
+ if response := h.VerifyContainerRegistry(recorder, req, container, tnt); response != nil {
+ return response
+ }
+ }
- return &response
- }
+ // Evaluate containers
+ for _, container := range pod.Spec.Containers {
+ if response := h.VerifyContainerRegistry(recorder, req, container, tnt); response != nil {
+ return response
+ }
+ }
+ }
- valid = tnt.Spec.ContainerRegistries.ExactMatch(reg.Registry())
+ return nil
+}
- matched = tnt.Spec.ContainerRegistries.RegexMatch(reg.Registry())
+func (h *containerRegistryHandler) VerifyContainerRegistry(recorder record.EventRecorder, req admission.Request, container corev1.Container, tnt capsulev1beta2.Tenant) *admission.Response {
+ var valid, matched bool
- if !valid && !matched {
- recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenContainerRegistry", "Pod %s/%s is using a container hosted on registry %s that is forbidden for the current Tenant", req.Namespace, req.Name, reg.Registry())
+ reg := NewRegistry(container.Image)
- response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error())
+ if len(reg.Registry()) == 0 {
+ recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry())
- return &response
- }
- }
- }
+ response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error())
- return nil
+ return &response
}
-}
-func (h *containerRegistryHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
- return func(ctx context.Context, req admission.Request) *admission.Response {
- return nil
- }
-}
+ valid = tnt.Spec.ContainerRegistries.ExactMatch(reg.Registry())
-func (h *containerRegistryHandler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
- return func(ctx context.Context, req admission.Request) *admission.Response {
- return nil
+ matched = tnt.Spec.ContainerRegistries.RegexMatch(reg.Registry())
+
+ if !valid && !matched {
+ recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenContainerRegistry", "Pod %s/%s is using a container hosted on registry %s that is forbidden for the current Tenant", req.Namespace, req.Name, reg.Registry())
+
+ response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error())
+
+ return &response
}
+
+ return nil
}
diff --git a/pkg/webhook/pod/containerregistry_errors.go b/pkg/webhook/pod/containerregistry_errors.go
index 76454d52..776594a4 100644
--- a/pkg/webhook/pod/containerregistry_errors.go
+++ b/pkg/webhook/pod/containerregistry_errors.go
@@ -7,7 +7,7 @@ import (
"fmt"
"strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/pkg/api"
)
type missingContainerRegistryError struct {
@@ -24,10 +24,10 @@ func NewMissingContainerRegistryError(image string) error {
type registryClassForbiddenError struct {
fqci string
- spec capsulev1beta1.AllowedListSpec
+ spec api.AllowedListSpec
}
-func NewContainerRegistryForbidden(image string, spec capsulev1beta1.AllowedListSpec) error {
+func NewContainerRegistryForbidden(image string, spec api.AllowedListSpec) error {
return ®istryClassForbiddenError{
fqci: image,
spec: spec,
diff --git a/pkg/webhook/pod/imagepullpolicy.go b/pkg/webhook/pod/imagepullpolicy.go
index f1ed80d4..3b160b20 100644
--- a/pkg/webhook/pod/imagepullpolicy.go
+++ b/pkg/webhook/pod/imagepullpolicy.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -30,7 +30,7 @@ func (r *imagePullPolicy) OnCreate(c client.Client, decoder *admission.Decoder,
return utils.ErroredResponse(err)
}
- tntList := &capsulev1beta1.TenantList{}
+ tntList := &capsulev1beta2.TenantList{}
if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace),
}); err != nil {
diff --git a/pkg/webhook/pod/imagepullpolicy_pullpolicy.go b/pkg/webhook/pod/imagepullpolicy_pullpolicy.go
index 884c250f..ccafe549 100644
--- a/pkg/webhook/pod/imagepullpolicy_pullpolicy.go
+++ b/pkg/webhook/pod/imagepullpolicy_pullpolicy.go
@@ -6,7 +6,7 @@ package pod
import (
"strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
type PullPolicy interface {
@@ -32,7 +32,7 @@ func (i imagePullPolicyValidator) AllowedPullPolicies() []string {
return i.allowedPolicies
}
-func NewPullPolicy(tenant *capsulev1beta1.Tenant) PullPolicy {
+func NewPullPolicy(tenant *capsulev1beta2.Tenant) PullPolicy {
// the Tenant doesn't enforce the allowed image pull policy, returning nil
if len(tenant.Spec.ImagePullPolicies) == 0 {
return nil
diff --git a/pkg/webhook/pod/priorityclass.go b/pkg/webhook/pod/priorityclass.go
index 3bd7bd5e..81d02eec 100644
--- a/pkg/webhook/pod/priorityclass.go
+++ b/pkg/webhook/pod/priorityclass.go
@@ -5,14 +5,13 @@ package pod
import (
"context"
+ "net/http"
corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -30,37 +29,57 @@ func (h *priorityClass) OnCreate(c client.Client, decoder *admission.Decoder, re
return utils.ErroredResponse(err)
}
- tntList := &capsulev1beta1.TenantList{}
-
- if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
- Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace),
- }); err != nil {
+ tnt, err := utils.TenantByStatusNamespace(ctx, c, pod.Namespace)
+ if err != nil {
return utils.ErroredResponse(err)
}
- if len(tntList.Items) == 0 {
+ if tnt == nil {
return nil
}
- allowed := tntList.Items[0].Spec.PriorityClasses
+ allowed := tnt.Spec.PriorityClasses
+
+ if allowed == nil {
+ return nil
+ }
priorityClassName := pod.Spec.PriorityClassName
+ if len(priorityClassName) == 0 {
+ // We don't have to force Pod to specify a Priority Class
+ return nil
+ }
+
+ selector := false
+
+ // Verify if the StorageClass exists and matches the label selector/expression
+ if len(allowed.MatchExpressions) > 0 || len(allowed.MatchLabels) > 0 {
+ priorityClassObj, err := utils.GetPriorityClassByName(ctx, c, priorityClassName)
+ if err != nil {
+ response := admission.Errored(http.StatusInternalServerError, err)
+
+ return &response
+ }
+
+ // Storage Class is present, check if it matches the selector
+ if priorityClassObj != nil {
+ selector = allowed.SelectorMatch(priorityClassObj)
+ }
+ }
+
switch {
- case allowed == nil:
- // Enforcement is not in place, skipping it at all
+ case allowed.MatchDefault(priorityClassName):
+ // Allow if given Priority Class is equal tenant default (eventough it's not allowed by selector)
return nil
- case len(priorityClassName) == 0:
- // We don't have to force Pod to specify a Priority Class
+ case allowed.Match(priorityClassName) || selector:
return nil
- case !allowed.ExactMatch(priorityClassName) && !allowed.RegexMatch(priorityClassName):
- recorder.Eventf(&tntList.Items[0], corev1.EventTypeWarning, "ForbiddenPriorityClass", "Pod %s/%s is using Priority Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, priorityClassName)
+ default:
+ recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenPriorityClass", "Pod %s/%s is using Priority Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, priorityClassName)
response := admission.Denied(NewPodPriorityClassForbidden(priorityClassName, *allowed).Error())
return &response
- default:
- return nil
}
}
}
diff --git a/pkg/webhook/pod/priorityclass_errors.go b/pkg/webhook/pod/priorityclass_errors.go
index ba7c9919..9198d598 100644
--- a/pkg/webhook/pod/priorityclass_errors.go
+++ b/pkg/webhook/pod/priorityclass_errors.go
@@ -5,17 +5,17 @@ package pod
import (
"fmt"
- "strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/webhook/utils"
)
type podPriorityClassForbiddenError struct {
priorityClassName string
- spec capsulev1beta1.AllowedListSpec
+ spec api.DefaultAllowedListSpec
}
-func NewPodPriorityClassForbidden(priorityClassName string, spec capsulev1beta1.AllowedListSpec) error {
+func NewPodPriorityClassForbidden(priorityClassName string, spec api.DefaultAllowedListSpec) error {
return &podPriorityClassForbiddenError{
priorityClassName: priorityClassName,
spec: spec,
@@ -23,19 +23,7 @@ func NewPodPriorityClassForbidden(priorityClassName string, spec capsulev1beta1.
}
func (f podPriorityClassForbiddenError) Error() (err string) {
- err = fmt.Sprintf("Pod Priorioty Class %s is forbidden for the current Tenant: ", f.priorityClassName)
+ msg := fmt.Sprintf("Pod Priority Class %s is forbidden for the current Tenant: ", f.priorityClassName)
- var extra []string
-
- if len(f.spec.Exact) > 0 {
- extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(f.spec.Exact, ", ")))
- }
-
- if len(f.spec.Regex) > 0 {
- extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", f.spec.Regex))
- }
-
- err += strings.Join(extra, " or ")
-
- return
+ return utils.DefaultAllowedValuesErrorMessage(f.spec, msg)
}
diff --git a/pkg/webhook/pod/runtimeclass.go b/pkg/webhook/pod/runtimeclass.go
new file mode 100644
index 00000000..08d539c0
--- /dev/null
+++ b/pkg/webhook/pod/runtimeclass.go
@@ -0,0 +1,103 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package pod
+
+import (
+ "context"
+ "net/http"
+
+ corev1 "k8s.io/api/core/v1"
+ nodev1 "k8s.io/api/node/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ capsulewebhook "github.com/clastix/capsule/pkg/webhook"
+ "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+type runtimeClass struct{}
+
+func RuntimeClass() capsulewebhook.Handler {
+ return &runtimeClass{}
+}
+
+func (h *runtimeClass) class(ctx context.Context, c client.Client, name string) (client.Object, error) {
+ if len(name) == 0 {
+ return nil, nil
+ }
+
+ obj := &nodev1.RuntimeClass{}
+ if err := c.Get(ctx, types.NamespacedName{Name: name}, obj); err != nil {
+ return nil, err
+ }
+
+ return obj, nil
+}
+
+func (h *runtimeClass) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return h.validate(ctx, c, decoder, recorder, req)
+ }
+}
+
+func (h *runtimeClass) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
+
+func (h *runtimeClass) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
+
+func (h *runtimeClass) validate(ctx context.Context, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, req admission.Request) *admission.Response {
+ pod := &corev1.Pod{}
+ if err := decoder.Decode(req, pod); err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ tnt, err := utils.TenantByStatusNamespace(ctx, c, pod.Namespace)
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ if tnt == nil {
+ return nil
+ }
+
+ allowed := tnt.Spec.RuntimeClasses
+
+ runtimeClassName := ""
+ if pod.Spec.RuntimeClassName != nil {
+ runtimeClassName = *pod.Spec.RuntimeClassName
+ }
+
+ class, err := h.class(ctx, c, runtimeClassName)
+ if err != nil {
+ response := admission.Errored(http.StatusInternalServerError, err)
+
+ return &response
+ }
+
+ switch {
+ case allowed == nil:
+ // Enforcement is not in place, skipping it at all
+ return nil
+ case len(runtimeClassName) == 0:
+ // We don't have to force Pod to specify a RuntimeClass
+ return nil
+ case !allowed.MatchSelectByName(class):
+ recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenRuntimeClass", "Pod %s/%s is using Runtime Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, runtimeClassName)
+
+ response := admission.Denied(NewPodRuntimeClassForbidden(runtimeClassName, *allowed).Error())
+
+ return &response
+ default:
+ return nil
+ }
+}
diff --git a/pkg/webhook/pod/runtimeclass_errors.go b/pkg/webhook/pod/runtimeclass_errors.go
new file mode 100644
index 00000000..0a1d9c73
--- /dev/null
+++ b/pkg/webhook/pod/runtimeclass_errors.go
@@ -0,0 +1,29 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package pod
+
+import (
+ "fmt"
+
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+type podRuntimeClassForbiddenError struct {
+ runtimeClassName string
+ spec api.SelectorAllowedListSpec
+}
+
+func NewPodRuntimeClassForbidden(runtimeClassName string, spec api.SelectorAllowedListSpec) error {
+ return &podRuntimeClassForbiddenError{
+ runtimeClassName: runtimeClassName,
+ spec: spec,
+ }
+}
+
+func (f podRuntimeClassForbiddenError) Error() (err string) {
+ err = fmt.Sprintf("Pod Runtime Class %s is forbidden for the current Tenant: ", f.runtimeClassName)
+
+ return utils.AllowedValuesErrorMessage(f.spec, err)
+}
diff --git a/pkg/webhook/pvc/errors.go b/pkg/webhook/pvc/errors.go
index 2774f835..11337172 100644
--- a/pkg/webhook/pvc/errors.go
+++ b/pkg/webhook/pvc/errors.go
@@ -5,44 +5,33 @@ package pvc
import (
"fmt"
- "strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/pkg/api"
+ "github.com/clastix/capsule/pkg/webhook/utils"
)
type storageClassNotValidError struct {
- spec capsulev1beta1.AllowedListSpec
+ spec api.DefaultAllowedListSpec
}
-func NewStorageClassNotValid(storageClasses capsulev1beta1.AllowedListSpec) error {
+func NewStorageClassNotValid(storageClasses api.DefaultAllowedListSpec) error {
return &storageClassNotValidError{
spec: storageClasses,
}
}
-// nolint:predeclared
-func appendError(spec capsulev1beta1.AllowedListSpec) (append string) {
- if len(spec.Exact) > 0 {
- append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", "))
- }
-
- if len(spec.Regex) > 0 {
- append += fmt.Sprintf(", or matching the regex %s", spec.Regex)
- }
-
- return
-}
-
func (s storageClassNotValidError) Error() (err string) {
- return "A valid Storage Class must be used" + appendError(s.spec)
+ msg := "A valid Storage Class must be used: "
+
+ return utils.DefaultAllowedValuesErrorMessage(s.spec, msg)
}
type storageClassForbiddenError struct {
className string
- spec capsulev1beta1.AllowedListSpec
+ spec api.DefaultAllowedListSpec
}
-func NewStorageClassForbidden(className string, storageClasses capsulev1beta1.AllowedListSpec) error {
+func NewStorageClassForbidden(className string, storageClasses api.DefaultAllowedListSpec) error {
return &storageClassForbiddenError{
className: className,
spec: storageClasses,
@@ -50,5 +39,55 @@ func NewStorageClassForbidden(className string, storageClasses capsulev1beta1.Al
}
func (f storageClassForbiddenError) Error() string {
- return fmt.Sprintf("Storage Class %s is forbidden for the current Tenant%s", f.className, appendError(f.spec))
+ msg := fmt.Sprintf("Storage Class %s is forbidden for the current Tenant ", f.className)
+
+ return utils.DefaultAllowedValuesErrorMessage(f.spec, msg)
+}
+
+type missingPVLabelsError struct {
+ name string
+}
+
+func NewMissingPVLabelsError(name string) error {
+ return &missingPVLabelsError{name: name}
+}
+
+func (m missingPVLabelsError) Error() string {
+ return fmt.Sprintf("PeristentVolume %s is missing any label, please, ask the Cluster Administrator to label it", m.name)
+}
+
+type missingPVTenantLabelsError struct {
+ name string
+}
+
+func NewMissingTenantPVLabelsError(name string) error {
+ return &missingPVTenantLabelsError{name: name}
+}
+
+func (m missingPVTenantLabelsError) Error() string {
+ return fmt.Sprintf("PeristentVolume %s is missing the Capsule Tenant label, preventing a potential cross-tenant mount", m.name)
+}
+
+type crossTenantPVMountError struct {
+ name string
+}
+
+func NewCrossTenantPVMountError(name string) error {
+ return &crossTenantPVMountError{
+ name: name,
+ }
+}
+
+func (m crossTenantPVMountError) Error() string {
+ return fmt.Sprintf("PeristentVolume %s cannot be used by the following Tenant, preventing a cross-tenant mount", m.name)
+}
+
+type pvSelectorError struct{}
+
+func NewPVSelectorError() error {
+ return &pvSelectorError{}
+}
+
+func (m pvSelectorError) Error() string {
+ return "PersistentVolume selectors are not allowed since unable to prevent cross-tenant mount"
}
diff --git a/pkg/webhook/pvc/pv.go b/pkg/webhook/pvc/pv.go
new file mode 100644
index 00000000..6f004d25
--- /dev/null
+++ b/pkg/webhook/pvc/pv.go
@@ -0,0 +1,98 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package pvc
+
+import (
+ "context"
+ "fmt"
+
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ capsulewebhook "github.com/clastix/capsule/pkg/webhook"
+ "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+type PV struct {
+ capsuleLabel string
+}
+
+func PersistentVolumeReuse() capsulewebhook.Handler {
+ value, err := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{})
+ if err != nil {
+ panic(fmt.Sprintf("this shouldn't happen: %s", err.Error()))
+ }
+
+ return &PV{
+ capsuleLabel: value,
+ }
+}
+
+func (p PV) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ pvc := corev1.PersistentVolumeClaim{}
+ if err := decoder.Decode(req, &pvc); err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ tnt, err := utils.TenantByStatusNamespace(ctx, client, pvc.GetNamespace())
+ if err != nil {
+ return utils.ErroredResponse(err)
+ }
+ // PVC is not in a Tenant Namespace, skipping
+ if tnt == nil {
+ return nil
+ }
+ // A PersistentVolume selector cannot help in preventing a cross-tenant mount:
+ // thus, disallowing that in first place.
+ if pvc.Spec.Selector != nil {
+ return utils.ErroredResponse(NewPVSelectorError())
+ }
+ // The PVC hasn't any volumeName pre-claimed, it can be skipped
+ if len(pvc.Spec.VolumeName) == 0 {
+ return nil
+ }
+ // Checking if the PV is labelled with the Tenant name
+ pv := corev1.PersistentVolume{}
+ if err = client.Get(ctx, types.NamespacedName{Name: pvc.Spec.VolumeName}, &pv); err != nil {
+ if errors.IsNotFound(err) {
+ err = fmt.Errorf("cannot create a PVC referring to a not yet existing PV")
+ }
+
+ return utils.ErroredResponse(err)
+ }
+
+ if pv.GetLabels() == nil {
+ return utils.ErroredResponse(NewMissingPVLabelsError(pv.GetName()))
+ }
+
+ value, ok := pv.GetLabels()[p.capsuleLabel]
+ if !ok {
+ return utils.ErroredResponse(NewMissingTenantPVLabelsError(pv.GetName()))
+ }
+
+ if value != tnt.Name {
+ return utils.ErroredResponse(NewCrossTenantPVMountError(pv.GetName()))
+ }
+
+ return nil
+ }
+}
+
+func (p PV) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(context.Context, admission.Request) *admission.Response {
+ return nil
+ }
+}
+
+func (p PV) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(context.Context, admission.Request) *admission.Response {
+ return nil
+ }
+}
diff --git a/pkg/webhook/pvc/validating.go b/pkg/webhook/pvc/validating.go
index 579a47f8..03b6fb2c 100644
--- a/pkg/webhook/pvc/validating.go
+++ b/pkg/webhook/pvc/validating.go
@@ -5,78 +5,95 @@ package pvc
import (
"context"
+ "net/http"
corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/fields"
+ "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
-type handler struct{}
+type validating struct{}
-func Handler() capsulewebhook.Handler {
- return &handler{}
+func Validating() capsulewebhook.Handler {
+ return &validating{}
}
-func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+func (h *validating) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- var valid, matched bool
-
pvc := &corev1.PersistentVolumeClaim{}
if err := decoder.Decode(req, pvc); err != nil {
return utils.ErroredResponse(err)
}
- tntList := &capsulev1beta1.TenantList{}
- if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
- Selector: fields.OneTermEqualSelector(".status.namespaces", pvc.Namespace),
- }); err != nil {
+ tnt, err := utils.TenantByStatusNamespace(ctx, c, pvc.Namespace)
+ if err != nil {
return utils.ErroredResponse(err)
}
- if len(tntList.Items) == 0 {
+ if tnt == nil {
return nil
}
- tnt := tntList.Items[0]
+ allowed := tnt.Spec.StorageClasses
- if tnt.Spec.StorageClasses == nil {
+ if allowed == nil {
return nil
}
- if pvc.Spec.StorageClassName == nil {
- recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingStorageClass", "PersistentVolumeClaim %s/%s is missing StorageClass", req.Namespace, req.Name)
+ storageClass := pvc.Spec.StorageClassName
+
+ if storageClass == nil {
+ recorder.Eventf(tnt, corev1.EventTypeWarning, "MissingStorageClass", "PersistentVolumeClaim %s/%s is missing StorageClass", req.Namespace, req.Name)
- response := admission.Denied(NewStorageClassNotValid(*tntList.Items[0].Spec.StorageClasses).Error())
+ response := admission.Denied(NewStorageClassNotValid(*tnt.Spec.StorageClasses).Error())
return &response
}
- sc := *pvc.Spec.StorageClassName
- if valid, matched = tnt.Spec.StorageClasses.ExactMatch(sc), tnt.Spec.StorageClasses.RegexMatch(sc); !valid && !matched {
- recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenStorageClass", "PersistentVolumeClaim %s/%s StorageClass %s is forbidden for the current Tenant", req.Namespace, req.Name, sc)
+ selector := false
+
+ // Verify if the StorageClass exists and matches the label selector/expression
+ if len(allowed.MatchExpressions) > 0 || len(allowed.MatchLabels) > 0 {
+ storageClassObj, err := utils.GetStorageClassByName(ctx, c, *storageClass)
+ if err != nil && !errors.IsNotFound(err) {
+ response := admission.Errored(http.StatusInternalServerError, err)
+
+ return &response
+ }
+
+ // Storage Class is present, check if it matches the selector
+ if storageClassObj != nil {
+ selector = allowed.SelectorMatch(storageClassObj)
+ }
+ }
+
+ switch {
+ case allowed.MatchDefault(*storageClass):
+ return nil
+ case allowed.Match(*storageClass) || selector:
+ return nil
+ default:
+ recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenStorageClass", "PersistentVolumeClaim %s/%s StorageClass %s is forbidden for the current Tenant", req.Namespace, req.Name, *storageClass)
response := admission.Denied(NewStorageClassForbidden(*pvc.Spec.StorageClassName, *tnt.Spec.StorageClasses).Error())
return &response
}
-
- return nil
}
}
-func (h *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+func (h *validating) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
return nil
}
}
-func (h *handler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+func (h *validating) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
return nil
}
diff --git a/pkg/webhook/route/defaults.go b/pkg/webhook/route/defaults.go
new file mode 100644
index 00000000..b42ca895
--- /dev/null
+++ b/pkg/webhook/route/defaults.go
@@ -0,0 +1,28 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package route
+
+import (
+ capsulewebhook "github.com/clastix/capsule/pkg/webhook"
+)
+
+// +kubebuilder:webhook:path=/defaults,mutating=true,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=pods,verbs=create,versions=v1,name=pod.defaults.capsule.clastix.io
+// +kubebuilder:webhook:path=/defaults,mutating=true,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=persistentvolumeclaims,verbs=create,versions=v1,name=storage.defaults.capsule.clastix.io
+// +kubebuilder:webhook:path=/defaults,mutating=true,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups=networking.k8s.io,resources=ingresses,verbs=create;update,versions=v1beta1;v1,name=ingress.defaults.capsule.clastix.io
+
+type defaults struct {
+ handlers []capsulewebhook.Handler
+}
+
+func Defaults(handler ...capsulewebhook.Handler) capsulewebhook.Webhook {
+ return &defaults{handlers: handler}
+}
+
+func (w *defaults) GetHandlers() []capsulewebhook.Handler {
+ return w.handlers
+}
+
+func (w *defaults) GetPath() string {
+ return "/defaults"
+}
diff --git a/pkg/webhook/route/ownerreference.go b/pkg/webhook/route/ownerreference.go
index d4e3a098..9fb903a2 100644
--- a/pkg/webhook/route/ownerreference.go
+++ b/pkg/webhook/route/ownerreference.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package route
import (
diff --git a/pkg/webhook/route/pods.go b/pkg/webhook/route/pods.go
index 577d54de..e704567d 100644
--- a/pkg/webhook/route/pods.go
+++ b/pkg/webhook/route/pods.go
@@ -7,7 +7,7 @@ import (
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
)
-// +kubebuilder:webhook:path=/pods,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=pods,verbs=create,versions=v1,name=pods.capsule.clastix.io
+// +kubebuilder:webhook:path=/pods,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=pods.capsule.clastix.io
type pod struct {
handlers []capsulewebhook.Handler
diff --git a/pkg/webhook/route/tenantresource_objs.go b/pkg/webhook/route/tenantresource_objs.go
new file mode 100644
index 00000000..b1e8e3cc
--- /dev/null
+++ b/pkg/webhook/route/tenantresource_objs.go
@@ -0,0 +1,26 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package route
+
+import (
+ capsulewebhook "github.com/clastix/capsule/pkg/webhook"
+)
+
+// +kubebuilder:webhook:path=/tenantresource-objects,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="*",resources="*",verbs=update;delete,versions="*",name=resource-objects.tenant.capsule.clastix.io
+
+type tntResourceObjs struct {
+ handlers []capsulewebhook.Handler
+}
+
+func TenantResourceObjects(handlers ...capsulewebhook.Handler) capsulewebhook.Webhook {
+ return &tntResourceObjs{handlers: handlers}
+}
+
+func (t tntResourceObjs) GetPath() string {
+ return "/tenantresource-objects"
+}
+
+func (t tntResourceObjs) GetHandlers() []capsulewebhook.Handler {
+ return t.handlers
+}
diff --git a/pkg/webhook/route/tenants.go b/pkg/webhook/route/tenants.go
index 63552ffe..8059c8f7 100644
--- a/pkg/webhook/route/tenants.go
+++ b/pkg/webhook/route/tenants.go
@@ -7,7 +7,7 @@ import (
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
)
-// +kubebuilder:webhook:path=/tenants,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="capsule.clastix.io",resources=tenants,verbs=create;update;delete,versions=v1beta1,name=tenants.capsule.clastix.io
+// +kubebuilder:webhook:path=/tenants,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="capsule.clastix.io",resources=tenants,verbs=create;update;delete,versions=v1beta2,name=tenants.capsule.clastix.io
type tenant struct {
handlers []capsulewebhook.Handler
diff --git a/pkg/webhook/router.go b/pkg/webhook/router.go
index 3f2ab521..3c765fa1 100644
--- a/pkg/webhook/router.go
+++ b/pkg/webhook/router.go
@@ -22,6 +22,8 @@ func Register(manager controllerruntime.Manager, webhookList ...Webhook) error {
for _, wh := range webhookList {
server.Register(wh.GetPath(), &webhook.Admission{
Handler: &handlerRouter{
+ client: manager.GetClient(),
+ decoder: admission.NewDecoder(manager.GetScheme()),
recorder: recorder,
handlers: wh.GetHandlers(),
},
@@ -65,15 +67,3 @@ func (r *handlerRouter) Handle(ctx context.Context, req admission.Request) admis
return admission.Allowed("")
}
-
-func (r *handlerRouter) InjectClient(c client.Client) error {
- r.client = c
-
- return nil
-}
-
-func (r *handlerRouter) InjectDecoder(d *admission.Decoder) error {
- r.decoder = d
-
- return nil
-}
diff --git a/pkg/webhook/service/errors.go b/pkg/webhook/service/errors.go
index 6802fda8..c85cb65b 100644
--- a/pkg/webhook/service/errors.go
+++ b/pkg/webhook/service/errors.go
@@ -7,14 +7,14 @@ import (
"fmt"
"strings"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ "github.com/clastix/capsule/pkg/api"
)
type externalServiceIPForbiddenError struct {
cidr []string
}
-func NewExternalServiceIPForbidden(allowedIps []capsulev1beta1.AllowedIP) error {
+func NewExternalServiceIPForbidden(allowedIps []api.AllowedIP) error {
cidr := make([]string, 0, len(allowedIps))
for _, i := range allowedIps {
diff --git a/pkg/webhook/service/validating.go b/pkg/webhook/service/validating.go
index 1de66e2e..3dda98bb 100644
--- a/pkg/webhook/service/validating.go
+++ b/pkg/webhook/service/validating.go
@@ -14,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -31,7 +31,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
return utils.ErroredResponse(err)
}
- tntList := &capsulev1beta1.TenantList{}
+ tntList := &capsulev1beta2.TenantList{}
if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", svc.GetNamespace()),
}); err != nil {
diff --git a/pkg/webhook/tenant/containerregistry_regex.go b/pkg/webhook/tenant/containerregistry_regex.go
index 41e490e7..2445e896 100644
--- a/pkg/webhook/tenant/containerregistry_regex.go
+++ b/pkg/webhook/tenant/containerregistry_regex.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,7 +24,7 @@ func ContainerRegistryRegexHandler() capsulewebhook.Handler {
}
func (h *containerRegistryRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/tenant/cordoning.go b/pkg/webhook/tenant/cordoning.go
index cc024ac7..13b6a3f8 100644
--- a/pkg/webhook/tenant/cordoning.go
+++ b/pkg/webhook/tenant/cordoning.go
@@ -14,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/configuration"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
@@ -31,7 +31,7 @@ func CordoningHandler(configuration configuration.Configuration) capsulewebhook.
}
func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder) *admission.Response {
- tntList := &capsulev1beta1.TenantList{}
+ tntList := &capsulev1beta2.TenantList{}
if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace),
@@ -44,7 +44,7 @@ func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client,
}
tnt := tntList.Items[0]
- if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, clt, h.configuration.UserGroups()) {
+ if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, clt, h.configuration.UserGroups()) {
recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "%s %s/%s cannot be %sd, current Tenant is freezed", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation)))
response := admission.Denied(fmt.Sprintf("tenant %s is freezed: please, reach out to the system administrator", tnt.GetName()))
diff --git a/pkg/webhook/tenant/custom_resource_quota.go b/pkg/webhook/tenant/custom_resource_quota.go
index e997e1de..e18ccbb9 100644
--- a/pkg/webhook/tenant/custom_resource_quota.go
+++ b/pkg/webhook/tenant/custom_resource_quota.go
@@ -16,7 +16,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -25,18 +25,14 @@ type resourceCounterHandler struct {
client client.Client
}
-func (r *resourceCounterHandler) InjectClient(c client.Client) error {
- r.client = c
-
- return nil
-}
-
-func ResourceCounterHandler() capsulewebhook.Handler {
- return &resourceCounterHandler{}
+func ResourceCounterHandler(client client.Client) capsulewebhook.Handler {
+ return &resourceCounterHandler{
+ client: client,
+ }
}
func (r *resourceCounterHandler) getTenantName(ctx context.Context, clt client.Client, req admission.Request) (string, error) {
- tntList := &capsulev1beta1.TenantList{}
+ tntList := &capsulev1beta2.TenantList{}
if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace),
@@ -67,7 +63,7 @@ func (r *resourceCounterHandler) OnCreate(clt client.Client, decoder *admission.
kgv := fmt.Sprintf("%s.%s_%s", req.Resource.Resource, req.Resource.Group, req.Resource.Version)
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
var limit int64
@@ -76,20 +72,20 @@ func (r *resourceCounterHandler) OnCreate(clt client.Client, decoder *admission.
return retryErr
}
- if limit, retryErr = capsulev1beta1.GetLimitResourceFromTenant(*tnt, kgv); retryErr != nil {
- if errors.As(err, &capsulev1beta1.NonLimitedResourceError{}) {
+ if limit, retryErr = capsulev1beta2.GetLimitResourceFromTenant(*tnt, kgv); retryErr != nil {
+ if errors.As(err, &capsulev1beta2.NonLimitedResourceError{}) {
return nil
}
return err
}
- used, _ := capsulev1beta1.GetUsedResourceFromTenant(*tnt, kgv)
+ used, _ := capsulev1beta2.GetUsedResourceFromTenant(*tnt, kgv)
if used >= limit {
return NewCustomResourceQuotaError(kgv, limit)
}
- tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used+1)
+ tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used+1)
return clt.Update(ctx, tnt)
})
@@ -122,7 +118,7 @@ func (r *resourceCounterHandler) OnDelete(clt client.Client, decoder *admission.
kgv := fmt.Sprintf("%s.%s_%s", req.Resource.Resource, req.Resource.Group, req.Resource.Version)
err = retry.RetryOnConflict(retry.DefaultRetry, func() (retryErr error) {
- tnt := &capsulev1beta1.Tenant{}
+ tnt := &capsulev1beta2.Tenant{}
if retryErr = clt.Get(ctx, types.NamespacedName{Name: tntName}, tnt); err != nil {
return
}
@@ -131,13 +127,13 @@ func (r *resourceCounterHandler) OnDelete(clt client.Client, decoder *admission.
return
}
- if _, ok := tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(kgv)]; !ok {
+ if _, ok := tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(kgv)]; !ok {
return
}
- used, _ := capsulev1beta1.GetUsedResourceFromTenant(*tnt, kgv)
+ used, _ := capsulev1beta2.GetUsedResourceFromTenant(*tnt, kgv)
- tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used-1)
+ tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used-1)
return clt.Update(ctx, tnt)
})
diff --git a/pkg/webhook/tenant/forbidden_annotations_regex.go b/pkg/webhook/tenant/forbidden_annotations_regex.go
index 61f47c2e..3cfb79f2 100644
--- a/pkg/webhook/tenant/forbidden_annotations_regex.go
+++ b/pkg/webhook/tenant/forbidden_annotations_regex.go
@@ -5,13 +5,14 @@ package tenant
import (
"context"
+ "fmt"
"regexp"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -23,23 +24,23 @@ func ForbiddenAnnotationsRegexHandler() capsulewebhook.Handler {
}
func (h *forbiddenAnnotationsRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
- if tenant.Annotations == nil {
+ if tenant.Spec.NamespaceOptions == nil {
return nil
}
- annotationsToCheck := []string{
- capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation,
- capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation,
+ annotationsToCheck := map[string]string{
+ "labels": tenant.Spec.NamespaceOptions.ForbiddenLabels.Regex,
+ "annotations": tenant.Spec.NamespaceOptions.ForbiddenAnnotations.Regex,
}
- for _, annotation := range annotationsToCheck {
- if _, err := regexp.Compile(tenant.Annotations[annotation]); err != nil {
- response := admission.Denied("unable to compile " + annotation + " regex annotation")
+ for scope, annotation := range annotationsToCheck {
+ if _, err := regexp.Compile(tenant.Spec.NamespaceOptions.ForbiddenLabels.Regex); err != nil {
+ response := admission.Denied(fmt.Sprintf("unable to compile %s regex for forbidden %s", annotation, scope))
return &response
}
diff --git a/pkg/webhook/tenant/freezed_emitter.go b/pkg/webhook/tenant/freezed_emitter.go
index 7a7a627e..a4c2d228 100644
--- a/pkg/webhook/tenant/freezed_emitter.go
+++ b/pkg/webhook/tenant/freezed_emitter.go
@@ -11,7 +11,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -36,20 +36,20 @@ func (h *freezedEmitterHandler) OnDelete(client.Client, *admission.Decoder, reco
func (h *freezedEmitterHandler) OnUpdate(_ client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- oldTnt := &capsulev1beta1.Tenant{}
+ oldTnt := &capsulev1beta2.Tenant{}
if err := decoder.DecodeRaw(req.OldObject, oldTnt); err != nil {
return utils.ErroredResponse(err)
}
- newTnt := &capsulev1beta1.Tenant{}
+ newTnt := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, newTnt); err != nil {
return utils.ErroredResponse(err)
}
switch {
- case !oldTnt.IsCordoned() && newTnt.IsCordoned():
+ case !oldTnt.Spec.Cordoned && newTnt.Spec.Cordoned:
recorder.Eventf(newTnt, corev1.EventTypeNormal, "TenantCordoned", "Tenant has been cordoned")
- case oldTnt.IsCordoned() && !newTnt.IsCordoned():
+ case oldTnt.Spec.Cordoned && !newTnt.Spec.Cordoned:
recorder.Eventf(newTnt, corev1.EventTypeNormal, "TenantUncordoned", "Tenant has been uncordoned")
}
diff --git a/pkg/webhook/tenant/hostname_regex.go b/pkg/webhook/tenant/hostname_regex.go
index f1184bf3..23c68dc5 100644
--- a/pkg/webhook/tenant/hostname_regex.go
+++ b/pkg/webhook/tenant/hostname_regex.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,7 +24,7 @@ func HostnameRegexHandler() capsulewebhook.Handler {
}
func (h *hostnameRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/tenant/ingressclass_regex.go b/pkg/webhook/tenant/ingressclass_regex.go
index c9e91523..e3349338 100644
--- a/pkg/webhook/tenant/ingressclass_regex.go
+++ b/pkg/webhook/tenant/ingressclass_regex.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,7 +24,7 @@ func IngressClassRegexHandler() capsulewebhook.Handler {
}
func (h *ingressClassRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/tenant/name.go b/pkg/webhook/tenant/name.go
index 0e44a4bb..96ab98b3 100644
--- a/pkg/webhook/tenant/name.go
+++ b/pkg/webhook/tenant/name.go
@@ -11,7 +11,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,7 +24,7 @@ func NameHandler() capsulewebhook.Handler {
func (h *nameHandler) OnCreate(_ client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/tenant/protected.go b/pkg/webhook/tenant/protected.go
index e5381bfa..72d3612f 100644
--- a/pkg/webhook/tenant/protected.go
+++ b/pkg/webhook/tenant/protected.go
@@ -1,18 +1,17 @@
-// Copyright 2020-2022 Clastix Labs
+// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package tenant
import (
"context"
- "fmt"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -31,14 +30,14 @@ func (h *protectedHandler) OnCreate(client.Client, *admission.Decoder, record.Ev
func (h *protectedHandler) OnDelete(clt client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := clt.Get(ctx, types.NamespacedName{Name: req.AdmissionRequest.Name}, tenant); err != nil {
return utils.ErroredResponse(err)
}
- if _, protected := tenant.Annotations[capsulev1beta1.ProtectedTenantAnnotation]; protected {
- response := admission.Denied(fmt.Sprintf("tenant is protected and cannot be deleted, remove %s annotation before proceeding", capsulev1beta1.ProtectedTenantAnnotation))
+ if tenant.Spec.PreventDeletion {
+ response := admission.Denied("tenant is protected and cannot be deleted")
return &response
}
diff --git a/pkg/webhook/tenant/rolebindings_regex.go b/pkg/webhook/tenant/rolebindings_regex.go
index 63ba86df..cb7a655c 100644
--- a/pkg/webhook/tenant/rolebindings_regex.go
+++ b/pkg/webhook/tenant/rolebindings_regex.go
@@ -14,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -26,7 +26,7 @@ func RoleBindingRegexHandler() capsulewebhook.Handler {
}
func (h *rbRegexHandler) validate(req admission.Request, decoder *admission.Decoder) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/tenant/serviceaccount_format.go b/pkg/webhook/tenant/serviceaccount_format.go
index 96da0066..6a394b15 100644
--- a/pkg/webhook/tenant/serviceaccount_format.go
+++ b/pkg/webhook/tenant/serviceaccount_format.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,7 +24,7 @@ func ServiceAccountNameHandler() capsulewebhook.Handler {
}
func (h *saNameHandler) validateServiceAccountName(req admission.Request, decoder *admission.Decoder) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/tenant/storageclass_regex.go b/pkg/webhook/tenant/storageclass_regex.go
index 93ac9f31..9719ea6a 100644
--- a/pkg/webhook/tenant/storageclass_regex.go
+++ b/pkg/webhook/tenant/storageclass_regex.go
@@ -12,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
@@ -24,7 +24,7 @@ func StorageClassRegexHandler() capsulewebhook.Handler {
}
func (h *storageClassRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response {
- tenant := &capsulev1beta1.Tenant{}
+ tenant := &capsulev1beta2.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
diff --git a/pkg/webhook/tenantresource/objects.go b/pkg/webhook/tenantresource/objects.go
new file mode 100644
index 00000000..6398d921
--- /dev/null
+++ b/pkg/webhook/tenantresource/objects.go
@@ -0,0 +1,89 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package tenant
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/fields"
+ "k8s.io/client-go/tools/record"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+ "github.com/clastix/capsule/pkg/indexer/tenantresource"
+ capsulewebhook "github.com/clastix/capsule/pkg/webhook"
+ "github.com/clastix/capsule/pkg/webhook/utils"
+)
+
+type cordoningHandler struct{}
+
+func WriteOpsHandler() capsulewebhook.Handler {
+ return &cordoningHandler{}
+}
+
+func (h *cordoningHandler) handler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder) *admission.Response {
+ tntList := &capsulev1beta2.TenantList{}
+
+ if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace)}); err != nil {
+ return utils.ErroredResponse(err)
+ }
+ // resource is not inside a Tenant namespace:
+ // we can avoid any kind of extra check.
+ if len(tntList.Items) == 0 {
+ return nil
+ }
+ // Checking if the object is managed by a TenantResource, local or global
+ ors := capsulev1beta2.ObjectReferenceStatus{
+ ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{
+ Kind: req.Kind.Kind,
+ Namespace: req.Namespace,
+ APIVersion: req.Kind.Version,
+ },
+ Name: req.Name,
+ }
+
+ global, local := &capsulev1beta2.GlobalTenantResourceList{}, &capsulev1beta2.TenantResourceList{}
+
+ if err := clt.List(ctx, global, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(tenantresource.IndexerFieldName, ors.String())}); err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ if err := clt.List(ctx, local, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(tenantresource.IndexerFieldName, ors.String())}); err != nil {
+ return utils.ErroredResponse(err)
+ }
+
+ if len(local.Items) > 0 || len(global.Items) > 0 {
+ tnt := tntList.Items[0]
+
+ recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantResourceWriteOp", "%s %s/%s cannot be %sd, resource is managed by the Tenant", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation)))
+
+ response := admission.Denied(fmt.Sprintf("resource %s is managed at the Tenant level", req.Name))
+
+ return &response
+ }
+
+ return nil
+}
+
+func (h *cordoningHandler) OnCreate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return nil
+ }
+}
+
+func (h *cordoningHandler) OnDelete(client client.Client, _ *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return h.handler(ctx, client, req, recorder)
+ }
+}
+
+func (h *cordoningHandler) OnUpdate(client client.Client, _ *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
+ return func(ctx context.Context, req admission.Request) *admission.Response {
+ return h.handler(ctx, client, req, recorder)
+ }
+}
diff --git a/pkg/webhook/utils/error.go b/pkg/webhook/utils/error.go
index b4b95f17..fe137ee9 100644
--- a/pkg/webhook/utils/error.go
+++ b/pkg/webhook/utils/error.go
@@ -4,9 +4,13 @@
package utils
import (
+ "fmt"
"net/http"
+ "strings"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+ "github.com/clastix/capsule/pkg/api"
)
func ErroredResponse(err error) *admission.Response {
@@ -14,3 +18,26 @@ func ErroredResponse(err error) *admission.Response {
return &response
}
+
+func DefaultAllowedValuesErrorMessage(allowed api.DefaultAllowedListSpec, err string) string {
+ return AllowedValuesErrorMessage(allowed.SelectorAllowedListSpec, err)
+}
+
+func AllowedValuesErrorMessage(allowed api.SelectorAllowedListSpec, err string) string {
+ var extra []string
+ if len(allowed.Exact) > 0 {
+ extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(allowed.Exact, ", ")))
+ }
+
+ if len(allowed.Regex) > 0 {
+ extra = append(extra, fmt.Sprintf("use one matching the following regex (%s)", allowed.Regex))
+ }
+
+ if len(allowed.MatchLabels) > 0 || len(allowed.MatchExpressions) > 0 {
+ extra = append(extra, "matching the label selector defined in the Tenant")
+ }
+
+ err += strings.Join(extra, " or ")
+
+ return err
+}
diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/webhook/utils/is_capsule_user.go
index 5c163073..bcd607c4 100644
--- a/pkg/webhook/utils/is_capsule_user.go
+++ b/pkg/webhook/utils/is_capsule_user.go
@@ -1,3 +1,6 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package utils
import (
@@ -9,7 +12,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
"github.com/clastix/capsule/pkg/utils"
)
@@ -21,14 +24,14 @@ func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client
if groupList.Find("system:serviceaccounts:kube-system") {
return false
}
- // nolint:nestif
+ //nolint:nestif
if sets.NewString(req.UserInfo.Groups...).Has("system:serviceaccounts") {
parts := strings.Split(req.UserInfo.Username, ":")
- targetNamespace := parts[2]
+ if len(parts) == 4 {
+ targetNamespace := parts[2]
- if len(targetNamespace) > 0 {
- tl := &capsulev1beta1.TenantList{}
+ tl := &capsulev1beta2.TenantList{}
if err := clt.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", targetNamespace)}); err != nil {
return false
}
diff --git a/pkg/webhook/utils/is_tenant_owner.go b/pkg/webhook/utils/is_tenant_owner.go
index 34207f9f..bd14f9a4 100644
--- a/pkg/webhook/utils/is_tenant_owner.go
+++ b/pkg/webhook/utils/is_tenant_owner.go
@@ -1,19 +1,22 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
package utils
import (
authenticationv1 "k8s.io/api/authentication/v1"
- capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
)
-func IsTenantOwner(owners capsulev1beta1.OwnerListSpec, userInfo authenticationv1.UserInfo) bool {
+func IsTenantOwner(owners capsulev1beta2.OwnerListSpec, userInfo authenticationv1.UserInfo) bool {
for _, owner := range owners {
switch owner.Kind {
- case capsulev1beta1.UserOwner, capsulev1beta1.ServiceAccountOwner:
+ case capsulev1beta2.UserOwner, capsulev1beta2.ServiceAccountOwner:
if userInfo.Username == owner.Name {
return true
}
- case capsulev1beta1.GroupOwner:
+ case capsulev1beta2.GroupOwner:
for _, group := range userInfo.Groups {
if group == owner.Name {
return true
diff --git a/pkg/webhook/utils/resources.go b/pkg/webhook/utils/resources.go
new file mode 100644
index 00000000..d729a0e9
--- /dev/null
+++ b/pkg/webhook/utils/resources.go
@@ -0,0 +1,100 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package utils
+
+import (
+ "context"
+
+ networkingv1 "k8s.io/api/networking/v1"
+ networkingv1beta1 "k8s.io/api/networking/v1beta1"
+ schedulev1 "k8s.io/api/scheduling/v1"
+ storagev1 "k8s.io/api/storage/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/version"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const TRUE string = "true"
+
+// Get PriorityClass by name (Does not return error if not found).
+func GetPriorityClassByName(ctx context.Context, c client.Client, name string) (*schedulev1.PriorityClass, error) {
+ class := &schedulev1.PriorityClass{}
+ if err := c.Get(ctx, types.NamespacedName{Name: name}, class); err != nil {
+ return nil, err
+ }
+
+ return class, nil
+}
+
+// Get StorageClass by name (Does not return error if not found).
+func GetStorageClassByName(ctx context.Context, c client.Client, name string) (*storagev1.StorageClass, error) {
+ class := &storagev1.StorageClass{}
+ if err := c.Get(ctx, types.NamespacedName{Name: name}, class); err != nil {
+ return nil, err
+ }
+
+ return class, nil
+}
+
+// Get IngressClass by name (Does not return error if not found).
+func GetIngressClassByName(ctx context.Context, version *version.Version, c client.Client, ingressClassName *string) (client.Object, error) {
+ if ingressClassName == nil {
+ return nil, nil
+ }
+
+ var obj client.Object
+
+ switch {
+ case version == nil:
+ obj = &networkingv1.IngressClass{}
+ case version.Minor() < 18:
+ return nil, nil
+ case version.Minor() < 19:
+ obj = &networkingv1beta1.IngressClass{}
+ default:
+ obj = &networkingv1.IngressClass{}
+ }
+
+ if err := c.Get(ctx, types.NamespacedName{Name: *ingressClassName}, obj); err != nil {
+ return nil, err
+ }
+
+ return obj, nil
+}
+
+// IsDefaultPriorityClass checks if the given PriorityClass is cluster default.
+func IsDefaultPriorityClass(class *schedulev1.PriorityClass) bool {
+ if class != nil {
+ return class.GlobalDefault
+ }
+
+ return false
+}
+
+func IsDefaultIngressClass(class client.Object) bool {
+ annotation := "ingressclass.kubernetes.io/is-default-class"
+
+ if class != nil {
+ annotations := class.GetAnnotations()
+ if v, ok := annotations[annotation]; ok && v == TRUE {
+ return true
+ }
+ }
+
+ return false
+}
+
+// IsDefaultStorageClass checks if the given StorageClass is cluster default.
+func IsDefaultStorageClass(class client.Object) bool {
+ annotation := "storageclass.kubernetes.io/is-default-class"
+
+ if class != nil {
+ annotations := class.GetAnnotations()
+ if v, ok := annotations[annotation]; ok && v == TRUE {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/pkg/webhook/utils/tenant_by_field.go b/pkg/webhook/utils/tenant_by_field.go
new file mode 100644
index 00000000..3c9af276
--- /dev/null
+++ b/pkg/webhook/utils/tenant_by_field.go
@@ -0,0 +1,32 @@
+// Copyright 2020-2021 Clastix Labs
+// SPDX-License-Identifier: Apache-2.0
+
+package utils
+
+import (
+ "context"
+
+ "k8s.io/apimachinery/pkg/fields"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ capsulev1beta2 "github.com/clastix/capsule/api/v1beta2"
+)
+
+func TenantByStatusNamespace(ctx context.Context, c client.Client, namespace string) (*capsulev1beta2.Tenant, error) {
+ tntList := &capsulev1beta2.TenantList{}
+ tnt := &capsulev1beta2.Tenant{}
+
+ if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
+ Selector: fields.OneTermEqualSelector(".status.namespaces", namespace),
+ }); err != nil {
+ return nil, err
+ }
+
+ if len(tntList.Items) == 0 {
+ return tnt, nil
+ }
+
+ *tnt = tntList.Items[0]
+
+ return tnt, nil
+}