Compare commits

..

52 Commits

Author SHA1 Message Date
Dario Tranchitella
d92f1e7825 chore(helm): bumping up to v0.2.1 2023-02-01 09:21:09 +01:00
Dario Tranchitella
ee813c5343 chore(kustomize): bumping up to v0.2.1 2023-02-01 09:21:09 +01:00
Dario Tranchitella
0fbf43ba0f docs: bumping up to v0.2.1 2023-02-01 09:21:09 +01:00
Dario Tranchitella
7ec7f3c69c fix: converting the status between v1beta1 and v1beta2 2023-01-31 19:54:14 +01:00
Dario Tranchitella
de587919f8 fix(tenantresources): using actual resourceversion during createorupdate 2023-01-31 16:57:07 +01:00
Dario Tranchitella
a334e68256 chore(helm): release v0.3.0 2023-01-28 11:47:00 +01:00
Dario Tranchitella
36f0281355 chore(kustomize): bumping up to Capsule v0.2.0 2023-01-28 11:47:00 +01:00
Dario Tranchitella
6a5fff01cd docs: migrating guidelines for v1beta2 api version 2023-01-28 11:42:34 +01:00
Dario Tranchitella
f8e212c291 docs: refactoring for v1beta2 2023-01-28 11:42:34 +01:00
Dario Tranchitella
91a979edc4 chore(helm): adding required omitempty for ux 2023-01-28 11:42:34 +01:00
Dario Tranchitella
1b51a4777d chore(kustomize): adding required omitempty for ux 2023-01-28 11:42:34 +01:00
Dario Tranchitella
8e827b2f5b fix(api): adding required omitempty for ux 2023-01-28 11:42:34 +01:00
Dario Tranchitella
1f0ac0724b docs: preventing persistentvolume cross tenant mount 2023-01-26 09:31:16 +01:00
Dario Tranchitella
18a9e77a8a test(e2e): preventing persistentvolume cross tenant mount 2023-01-26 09:31:16 +01:00
Dario Tranchitella
ea88b102e5 feat: pv labelling and preventing cross-tenant mount 2023-01-26 09:31:16 +01:00
Oliver Bähler
b58cb19ea1 chore(gitignore): add kind.yaml
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
25095bcf2d chore(makefile): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
ea71dd7e65 fix(e2e): correct e2e test for container patches
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
68c86a6509 test(e2e): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
7784e8df50 docs: add defaults handler for storage, ingress, and priority classes
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
8f933cd2b3 chore(helm): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
a1b624f239 chore(kustomize): add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Oliver Bähler
ab0fe91c58 feat: add defaults handler
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-01-14 15:51:01 +01:00
Max Fedotov
fbea737a51 feat: add Wargaming to adopters
Co-authored-by: Maksim Fedotov <m_fedotov@wargaming.net>
2023-01-11 15:45:55 +01:00
Dario Tranchitella
51288f30c5 fix(docs): limit range spec for pods and containers limits 2023-01-02 17:43:25 +01:00
Dario Tranchitella
f73a5b17f4 fix: using embedded struct for selector 2022-12-29 17:49:45 +01:00
Oliver Bähler
f6c1ad68da chore(makefile): add crds to dev-setup
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
9f10923d21 fix: use v1beta2 for capsuleconfiguration kind
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
7591140fc7 test(e2e): validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
34cfb16ea3 chore(helm): validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
f5cd194c05 chore(kustomize): validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
628efbb30f fix: validate pods on update
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-29 17:49:45 +01:00
Oliver Bähler
79391f863a feat: add runtimeclass control
Signed-off-by: Oliver Baehler <oliver.baehler@hotmail.com>
2022-12-28 15:01:28 +01:00
Oliver Bähler
ee0fdc9efa feat: ct install performs locally & ci loads current capsule build
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2022-12-27 17:53:17 +01:00
Dario Tranchitella
e964f34086 fix: avoiding nil pointer when empty map for labels and annotations 2022-12-27 17:53:17 +01:00
Dario Tranchitella
0d9054db34 docs(api): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
7c4b46eeb1 chore(helm): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
dbbd9e64c0 chore(kustomize): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
462d7213b9 refactor(e2e): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
93fbca9b18 feat(api): label selector for storage, ingress, podpriority classes 2022-12-27 17:53:17 +01:00
Dario Tranchitella
289b079530 docs(api): aligning to latest changes for capsule configuration 2022-12-27 17:53:17 +01:00
Dario Tranchitella
5af3c9828e chore(helm): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
77a8c9ab62 chore(kustomize): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
791dde5bf6 refactor(e2e): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
43bd2491ae refactor(api): switching to v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
2cb37abc51 chore(helm): promoting v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
0b057d63b6 chore(kustomize): promoting v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
0f580ef379 feat(api): promoting v1beta2 as storage version 2022-12-27 17:53:17 +01:00
Dario Tranchitella
34a948da81 chore(helm)!: release of v0.2.0 2022-12-27 17:53:17 +01:00
Dario Tranchitella
e14e12c287 refactor: abstracting types used by several api versions 2022-12-27 17:53:17 +01:00
Dario Tranchitella
9d6f766cc1 fix(makefile): avoid race condition for local kind cluster 2022-12-26 14:27:26 +01:00
Dario Tranchitella
ea01b9d1f9 test: support for endpointslice/v1 for k8s v1.25 2022-12-26 14:27:26 +01:00
174 changed files with 5834 additions and 1664 deletions

View File

@@ -42,23 +42,31 @@ jobs:
else
echo -e '\033[0;32mDocumentation up to date\033[0m ✔'
fi
# Create KIND Cluster
- name: Create kind cluster
uses: helm/kind-action@v1.2.0
if: steps.list-changed.outputs.changed == 'true'
# Install Required Operators/CRDs
- name: Prepare Cluster Operators/CRDs
run: |
# Cert-Manager CRDs
kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml
# Prometheus CRDs
kubectl create -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.58.0/bundle.yaml
if: steps.list-changed.outputs.changed == 'true'
# Install Charts
# ATTENTION: This is a workaround for the upcoming ApiVersion Conversions for the capsule CRDs
# With this workflow the current docker image is build and loaded into kind, otherwise the install fails
# In the future this must be removed and the chart-testing-action must be used
- name: Run chart-testing (install)
run: ct install --debug --config ./.github/configs/ct.yaml
run: make helm-test
if: steps.list-changed.outputs.changed == 'true'
## Create KIND Cluster
#- name: Create kind cluster
# uses: helm/kind-action@v1.2.0
# if: steps.list-changed.outputs.changed == 'true'
## Install Required Operators/CRDs
#- name: Prepare Cluster Operators/CRDs
# run: |
# # Cert-Manager CRDs
# kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml
#
# # Prometheus CRDs
# kubectl create -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.58.0/bundle.yaml
# if: steps.list-changed.outputs.changed == 'true'
## Install Charts
#- name: Run chart-testing (install)
# run: ct install --debug --config ./.github/configs/ct.yaml
# if: steps.list-changed.outputs.changed == 'true'
release:
if: startsWith(github.ref, 'refs/tags/helm-v')
runs-on: ubuntu-latest

4
.gitignore vendored
View File

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

View File

@@ -6,3 +6,6 @@ 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)
### [Wargaming.net](https://www.wargaming.net/)
![Wargaming.net](https://static-cspbe-eu.wargaming.net/images/logo@2x.png)

View File

@@ -86,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 || {\
@@ -132,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="[\
@@ -145,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) \
@@ -172,6 +191,14 @@ GINKGO = $(shell pwd)/bin/ginkgo
ginkgo: ## Download ginkgo locally if necessary.
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/ginkgo@v1.16.5)
CT = $(shell pwd)/bin/ct
ct: ## Download ct locally if necessary.
$(call go-install-tool,$(CT),github.com/helm/chart-testing/v3/ct@v3.7.1)
KIND = $(shell pwd)/bin/kind
kind: ## Download kind locally if necessary.
$(call go-install-tool,$(KIND),sigs.k8s.io/kind/cmd/kind@v0.17.0)
KUSTOMIZE = $(shell pwd)/bin/kustomize
kustomize: ## Download kustomize locally if necessary.
$(call install-kustomize,$(KUSTOMIZE),3.8.7)
@@ -226,7 +253,7 @@ e2e/%: ginkgo
$(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 \
@@ -242,9 +269,8 @@ e2e-build/%:
capsule \
./charts/capsule
e2e-exec:
e2e-exec: ginkgo
$(GINKGO) -v -tags e2e ./e2e
e2e-destroy:
kind delete cluster --name capsule

View File

@@ -20,7 +20,6 @@ type CapsuleConfigurationSpec struct {
ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"`
}
// +kubebuilder:storageversion
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster

View File

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

View File

@@ -61,9 +61,11 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
Allowed: []api.AllowedIP{"192.168.0.1"},
},
}
v1beta1AllowedListSpec := &api.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
v1beta2AllowedListSpec := &api.SelectorAllowedListSpec{
AllowedListSpec: api.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
},
}
networkPolicies := []networkingv1.NetworkPolicySpec{
{
@@ -235,13 +237,13 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
},
NamespaceOptions: v1beta1NamespaceOptions,
ServiceOptions: v1beta1ServiceOptions,
StorageClasses: v1beta1AllowedListSpec,
StorageClasses: &v1beta2AllowedListSpec.AllowedListSpec,
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: api.HostnameCollisionScopeDisabled,
AllowedClasses: v1beta1AllowedListSpec,
AllowedHostnames: v1beta1AllowedListSpec,
AllowedClasses: &v1beta2AllowedListSpec.AllowedListSpec,
AllowedHostnames: &v1beta2AllowedListSpec.AllowedListSpec,
},
ContainerRegistries: v1beta1AllowedListSpec,
ContainerRegistries: &v1beta2AllowedListSpec.AllowedListSpec,
NodeSelector: nodeSelector,
NetworkPolicies: api.NetworkPolicySpec{
Items: networkPolicies,

View File

@@ -9,14 +9,6 @@ import (
corev1 "k8s.io/api/core/v1"
)
func (in *Tenant) IsCordoned() bool {
if v, ok := in.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" {
return true
}
return false
}
func (in *Tenant) IsFull() bool {
// we don't have limits on assigned Namespaces
if in.Spec.NamespaceQuota == nil {

View File

@@ -18,11 +18,11 @@ type NamespaceOptions struct {
}
func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool {
if _, ok := in.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok {
if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok {
return true
}
if _, ok := in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
return true
}
@@ -30,11 +30,11 @@ func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool {
}
func (in *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool {
if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok {
if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok {
return true
}
if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
return true
}
@@ -47,8 +47,8 @@ func (in *Tenant) ForbiddenUserNamespaceLabels() *api.ForbiddenListSpec {
}
return &api.ForbiddenListSpec{
Exact: strings.Split(in.Annotations[ForbiddenNamespaceLabelsAnnotation], ","),
Regex: in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation],
Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceLabelsAnnotation], ","),
Regex: in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation],
}
}
@@ -58,7 +58,7 @@ func (in *Tenant) ForbiddenUserNamespaceAnnotations() *api.ForbiddenListSpec {
}
return &api.ForbiddenListSpec{
Exact: strings.Split(in.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","),
Regex: in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation],
Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation], ","),
Regex: in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation],
}
}

View File

@@ -41,7 +41,6 @@ type TenantSpec struct {
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:storageversion
// +kubebuilder:resource:scope=Cluster,shortName=tnt
// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant"
// +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created"

View File

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

View File

@@ -22,10 +22,11 @@ type CapsuleConfigurationSpec struct {
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.
CapsuleResources CapsuleResources `json:"overrides"`
// +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"`
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
@@ -54,6 +55,7 @@ type CapsuleResources struct {
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:storageversion
// CapsuleConfiguration is the Schema for the Capsule configuration API.
type CapsuleConfiguration struct {

View File

@@ -8,8 +8,11 @@ import (
)
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 *api.AllowedListSpec `json:"allowedClasses,omitempty"`
// 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.
//
//
@@ -26,5 +29,5 @@ type IngressOptions struct {
// 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"`
AllowWildcardHostnames bool `json:"allowWildcardHostnames,omitempty"`
}

View File

@@ -14,7 +14,7 @@ type NamespaceOptions struct {
// 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"`
ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitempty"`
// Define the annotations that a Tenant Owner cannot set for their Namespace resources.
ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"`
ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations,omitempty"`
}

View File

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

View File

@@ -11,6 +11,7 @@ import (
"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 {
@@ -58,33 +59,39 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error {
in.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata
if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found {
if value, found := annotations[api.ForbiddenNamespaceLabelsAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",")
delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation)
delete(annotations, api.ForbiddenNamespaceLabelsAnnotation)
}
if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found {
if value, found := annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value
delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation)
delete(annotations, api.ForbiddenNamespaceLabelsRegexpAnnotation)
}
if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found {
if value, found := annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",")
delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation)
delete(annotations, api.ForbiddenNamespaceAnnotationsAnnotation)
}
if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found {
if value, found := annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found {
in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value
delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation)
delete(annotations, api.ForbiddenNamespaceAnnotationsRegexpAnnotation)
}
}
in.Spec.ServiceOptions = src.Spec.ServiceOptions
in.Spec.StorageClasses = src.Spec.StorageClasses
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
@@ -101,7 +108,11 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error {
}
if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil {
in.Spec.IngressOptions.AllowedClasses = ingressClass
in.Spec.IngressOptions.AllowedClasses = &api.DefaultAllowedListSpec{
SelectorAllowedListSpec: api.SelectorAllowedListSpec{
AllowedListSpec: *ingressClass,
},
}
}
if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil {
@@ -115,7 +126,14 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error {
in.Spec.ResourceQuota = src.Spec.ResourceQuota
in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings
in.Spec.ImagePullPolicies = src.Spec.ImagePullPolicies
in.Spec.PriorityClasses = src.Spec.PriorityClasses
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)
@@ -126,17 +144,25 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error {
in.Spec.Cordoned = value
}
if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found {
if _, found := annotations[api.ProtectedTenantAnnotation]; found {
in.Spec.PreventDeletion = true
delete(annotations, capsulev1beta1.ProtectedTenantAnnotation)
delete(annotations, api.ProtectedTenantAnnotation)
}
in.SetAnnotations(annotations)
in.Status.Namespaces = src.Status.Namespaces
in.Status.Size = src.Status.Size
in.Status.State = tenantState(src.Status.State)
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
}
@@ -189,29 +215,31 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error {
dst.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata
if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 {
annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",")
annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",")
}
if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 {
annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex
annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex
}
if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 {
annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",")
annotations[api.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",")
}
if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 {
annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex
annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = regex
}
}
dst.Spec.ServiceOptions = in.Spec.ServiceOptions
dst.Spec.StorageClasses = in.Spec.StorageClasses
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
dst.Spec.IngressOptions.AllowedClasses = &allowed.AllowedListSpec
}
if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil {
@@ -230,10 +258,13 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error {
dst.Spec.ResourceQuota = in.Spec.ResourceQuota
dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings
dst.Spec.ImagePullPolicies = in.Spec.ImagePullPolicies
dst.Spec.PriorityClasses = in.Spec.PriorityClasses
if in.Spec.PriorityClasses != nil {
dst.Spec.PriorityClasses = &in.Spec.PriorityClasses.AllowedListSpec
}
if in.Spec.PreventDeletion {
annotations[capsulev1beta1.ProtectedTenantAnnotation] = "true" //nolint:goconst
annotations[api.ProtectedTenantAnnotation] = "true" //nolint:goconst
}
if in.Spec.Cordoned {
@@ -242,5 +273,17 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error {
dst.SetAnnotations(annotations)
dst.Status.Size = in.Status.Size
dst.Status.Namespaces = in.Status.Namespaces
switch in.Status.State {
case TenantStateActive:
dst.Status.State = capsulev1beta1.TenantStateActive
case TenantStateCordoned:
dst.Status.State = capsulev1beta1.TenantStateCordoned
default:
dst.Status.State = capsulev1beta1.TenantStateActive
}
return nil
}

View File

@@ -17,8 +17,11 @@ type TenantSpec struct {
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. Optional.
StorageClasses *api.AllowedListSpec `json:"storageClasses,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.
@@ -35,8 +38,15 @@ type TenantSpec struct {
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 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 *api.AllowedListSpec `json:"priorityClasses,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.
@@ -45,6 +55,7 @@ type TenantSpec struct {
}
// +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"

View File

@@ -261,7 +261,7 @@ func (in *IngressOptions) DeepCopyInto(out *IngressOptions) {
*out = *in
if in.AllowedClasses != nil {
in, out := &in.AllowedClasses, &out.AllowedClasses
*out = new(api.AllowedListSpec)
*out = new(api.DefaultAllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.AllowedHostnames != nil {
@@ -718,7 +718,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
}
if in.StorageClasses != nil {
in, out := &in.StorageClasses, &out.StorageClasses
*out = new(api.AllowedListSpec)
*out = new(api.DefaultAllowedListSpec)
(*in).DeepCopyInto(*out)
}
in.IngressOptions.DeepCopyInto(&out.IngressOptions)
@@ -749,9 +749,14 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
*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.AllowedListSpec)
*out = new(api.DefaultAllowedListSpec)
(*in).DeepCopyInto(*out)
}
}

View File

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

View File

@@ -67,7 +67,7 @@ Here the values you can override:
| customAnnotations | object | `{}` | Additional annotations which will be added to all resources created by Capsule helm chart |
| customLabels | object | `{}` | Additional labels which will be added to all resources created by Capsule helm chart |
| jobs.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy of the helm chart job |
| jobs.image.repository | string | `"quay.io/clastix/kubectl"` | Set the image repository of the helm chart job |
| jobs.image.repository | string | `"clastix/kubectl"` | Set the image repository of the helm chart job |
| jobs.image.tag | string | `""` | Set the image tag of the helm chart job |
| mutatingWebhooksTimeoutSeconds | int | `30` | Timeout in seconds for mutating webhooks |
| nodeSelector | object | `{}` | Set the node selector for the Capsule pod |
@@ -130,6 +130,15 @@ Here the values you can override:
| webhooks.cordoning.failurePolicy | string | `"Fail"` | |
| webhooks.cordoning.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.cordoning.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.defaults.ingress.failurePolicy | string | `"Fail"` | |
| webhooks.defaults.ingress.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.defaults.ingress.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.defaults.pods.failurePolicy | string | `"Fail"` | |
| webhooks.defaults.pods.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.defaults.pods.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.defaults.pvc.failurePolicy | string | `"Fail"` | |
| webhooks.defaults.pvc.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.defaults.pvc.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |
| webhooks.ingresses.failurePolicy | string | `"Fail"` | |
| webhooks.ingresses.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | |
| webhooks.ingresses.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | |

View File

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

View File

@@ -60,7 +60,7 @@ spec:
type: object
type: object
served: true
storage: true
storage: false
- name: v1beta2
schema:
openAPIV3Schema:
@@ -113,6 +113,10 @@ spec:
- 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:
@@ -144,9 +148,7 @@ spec:
type: array
required:
- enableTLSReconciler
- nodeMetadata
- overrides
type: object
type: object
served: true
storage: false
storage: true

View File

@@ -1,4 +1,3 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
@@ -1274,7 +1273,7 @@ spec:
type: object
type: object
served: true
storage: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
@@ -1377,7 +1376,7 @@ spec:
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:
@@ -1385,7 +1384,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:
@@ -1405,8 +1433,6 @@ spec:
- Namespace
- Disabled
type: string
required:
- allowWildcardHostnames
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.
@@ -1517,9 +1543,6 @@ spec:
format: int32
minimum: 1
type: integer
required:
- forbiddenAnnotations
- forbiddenLabels
type: object
networkPolicies:
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
@@ -1852,7 +1875,7 @@ spec:
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:
@@ -1860,7 +1883,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:
@@ -1919,6 +1971,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:
@@ -1963,7 +2052,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:
@@ -1971,7 +2060,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
@@ -1999,6 +2117,6 @@ spec:
type: object
type: object
served: true
storage: false
storage: true
subresources:
status: {}

View File

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

View File

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

View File

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

View File

@@ -101,7 +101,7 @@ podSecurityPolicy:
jobs:
image:
# -- Set the image repository of the helm chart job
repository: quay.io/clastix/kubectl
repository: clastix/kubectl
# -- Set the image pull policy of the helm chart job
pullPolicy: IfNotPresent
# -- Set the image tag of the helm chart job
@@ -172,6 +172,26 @@ webhooks:
operator: Exists
nodes:
failurePolicy: Fail
defaults:
ingress:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
pvc:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
pods:
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: capsule.clastix.io/tenant
operator: Exists
# -- Timeout in seconds for mutating webhooks
mutatingWebhooksTimeoutSeconds: 30

View File

@@ -57,7 +57,7 @@ spec:
type: object
type: object
served: true
storage: true
storage: false
- name: v1beta2
schema:
openAPIV3Schema:
@@ -126,6 +126,10 @@ spec:
- 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.
@@ -164,9 +168,7 @@ spec:
type: array
required:
- enableTLSReconciler
- nodeMetadata
- overrides
type: object
type: object
served: true
storage: false
storage: true

View File

@@ -1917,7 +1917,7 @@ spec:
type: object
type: object
served: true
storage: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
@@ -2048,7 +2048,8 @@ spec:
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.
A default value can be specified, and all the Ingress resources
created will inherit the declared class. Optional.
properties:
allowed:
items:
@@ -2056,7 +2057,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
allowedHostnames:
description: Specifies the allowed hostnames in Ingresses for
the given Tenant. Capsule assures that all Ingress resources
@@ -2087,8 +2131,6 @@ spec:
- Namespace
- Disabled
type: string
required:
- allowWildcardHostnames
type: object
limitRanges:
description: Specifies the resource min/max usage restrictions to
@@ -2223,9 +2265,6 @@ spec:
format: int32
minimum: 1
type: integer
required:
- forbiddenAnnotations
- forbiddenLabels
type: object
networkPolicies:
description: Specifies the NetworkPolicies assigned to the Tenant.
@@ -2827,7 +2866,9 @@ spec:
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.
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:
@@ -2835,7 +2876,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
resourceQuotas:
description: Specifies a list of ResourceQuota resources assigned
to the Tenant. The assigned values are inherited by any namespace
@@ -2921,6 +3005,59 @@ 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.
@@ -2975,7 +3112,8 @@ spec:
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.
A default value can be specified, and all the PersistentVolumeClaim
resources created will inherit the declared class. Optional.
properties:
allowed:
items:
@@ -2983,7 +3121,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
@@ -3012,6 +3193,6 @@ spec:
type: object
type: object
served: true
storage: false
storage: true
subresources:
status: {}

View File

@@ -65,7 +65,7 @@ spec:
type: object
type: object
served: true
storage: true
storage: false
- name: v1beta2
schema:
openAPIV3Schema:
@@ -118,6 +118,10 @@ spec:
- 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:
@@ -149,12 +153,10 @@ spec:
type: array
required:
- enableTLSReconciler
- nodeMetadata
- overrides
type: object
type: object
served: true
storage: false
storage: true
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
@@ -1835,7 +1837,7 @@ spec:
type: object
type: object
served: true
storage: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
@@ -1938,7 +1940,7 @@ spec:
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:
@@ -1946,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:
@@ -1966,8 +1997,6 @@ spec:
- Namespace
- Disabled
type: string
required:
- allowWildcardHostnames
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.
@@ -2078,9 +2107,6 @@ spec:
format: int32
minimum: 1
type: integer
required:
- forbiddenAnnotations
- forbiddenLabels
type: object
networkPolicies:
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
@@ -2413,7 +2439,7 @@ spec:
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:
@@ -2421,7 +2447,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:
@@ -2480,6 +2535,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:
@@ -2524,7 +2616,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:
@@ -2532,7 +2624,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
@@ -2560,7 +2681,7 @@ spec:
type: object
type: object
served: true
storage: false
storage: true
subresources:
status: {}
---
@@ -2646,7 +2767,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: clastix/capsule:v0.1.3
image: clastix/capsule:v0.2.1
imagePullPolicy: IfNotPresent
name: manager
ports:
@@ -2674,12 +2795,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:
@@ -2691,6 +2813,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:
@@ -2860,6 +3056,7 @@ webhooks:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods
scope: Namespaced
@@ -2926,7 +3123,7 @@ webhooks:
- apiGroups:
- capsule.clastix.io
apiVersions:
- v1beta1
- v1beta2
operations:
- CREATE
- UPDATE

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,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:
@@ -151,6 +210,7 @@ webhooks:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods
sideEffects: None
@@ -206,7 +266,7 @@ webhooks:
- apiGroups:
- capsule.clastix.io
apiVersions:
- v1beta1
- v1beta2
operations:
- CREATE
- UPDATE

View File

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

View File

@@ -12,7 +12,7 @@ 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"
)
@@ -31,7 +31,7 @@ func (c *Manager) InjectClient(client client.Client) error {
func (c *Manager) SetupWithManager(mgr ctrl.Manager, configurationName string) error {
return ctrl.NewControllerManagedBy(mgr).
For(&capsulev1alpha1.CapsuleConfiguration{}, utils.NamesMatchingPredicate(configurationName)).
For(&capsulev1beta2.CapsuleConfiguration{}, utils.NamesMatchingPredicate(configurationName)).
Complete(c)
}

View File

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

View File

@@ -21,7 +21,7 @@ import (
"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"
)
@@ -51,7 +51,7 @@ 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{
Watches(source.NewKindWithCache(&capsulev1beta2.CapsuleConfiguration{}, mgr.GetCache()), handler.Funcs{
UpdateFunc: func(updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) {
if updateEvent.ObjectNew.GetName() == configurationName {
if crbErr := r.EnsureClusterRoleBindings(ctx); crbErr != nil {

View File

@@ -252,12 +252,13 @@ func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstru
_, err = controllerutil.CreateOrUpdate(ctx, r.client, actual, func() error {
UID := actual.GetUID()
rv := actual.GetResourceVersion()
actual.SetUnstructuredContent(desired.Object)
actual.SetNamespace(ns)
actual.SetLabels(labels)
actual.SetAnnotations(annotations)
actual.SetResourceVersion("")
actual.SetResourceVersion(rv)
actual.SetUID(UID)
return nil

View File

@@ -20,7 +20,7 @@ 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"
)
@@ -70,15 +70,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, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{})
capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
if _, ok := ns.GetLabels()[capsuleLabel]; !ok {
return nil, NewNonTenantObject(namespacedName.Name)
}
@@ -117,7 +117,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 {

View File

@@ -13,13 +13,13 @@ 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 {
// getting requested LimitRange keys
keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items))
@@ -40,11 +40,11 @@ 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 = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}

View File

@@ -18,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 {
@@ -30,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{}).
@@ -42,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")
@@ -130,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)

View File

@@ -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 {
@@ -42,7 +43,7 @@ func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta1.Ten
}
// nolint:gocognit
func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, tnt *capsulev1beta1.Tenant) (err error) {
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, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{})
capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error {
annotations := make(map[string]string)
@@ -103,20 +104,20 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
}
}
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,11 +147,11 @@ 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
}
@@ -161,7 +162,7 @@ func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1bet
})
}
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{

View File

@@ -13,13 +13,13 @@ 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 {
// getting requested NetworkPolicy keys
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items))
@@ -40,14 +40,14 @@ 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 = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}

View File

@@ -19,7 +19,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/api/v1beta2"
"github.com/clastix/capsule/pkg/api"
"github.com/clastix/capsule/pkg/utils"
)
@@ -37,11 +37,11 @@ import (
//
// 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) {
// getting ResourceQuota labels for the mutateFn
var tenantLabel, typeLabel string
if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}
@@ -158,11 +158,11 @@ 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 = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return err
}
@@ -238,8 +238,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

View File

@@ -16,10 +16,10 @@ 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
@@ -29,7 +29,7 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap
var resourceList []resource
for k := range tenant.GetAnnotations() {
if !strings.HasPrefix(k, capsulev1beta1.ResourceQuotaAnnotationPrefix) {
if !strings.HasPrefix(k, capsulev1beta2.ResourceQuotaAnnotationPrefix) {
continue
}
@@ -69,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
}
@@ -78,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)
})

View File

@@ -14,14 +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) api.AdditionalRoleBindingsSpec {
func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta2.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec {
var subject rbacv1.Subject
if owner.Kind == "ServiceAccount" {
@@ -50,7 +50,7 @@ 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 api.AdditionalRoleBindingsSpec) string {
h := fnv.New64a()
@@ -66,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))
@@ -91,10 +91,10 @@ 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 api.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 = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil {
return
}
@@ -108,8 +108,8 @@ func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsule
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))
}
}

View File

@@ -14,7 +14,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
capsulev1beta1 "github.com/clastix/capsule/pkg/utils"
capsulev1beta2 "github.com/clastix/capsule/pkg/utils"
)
// pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or
@@ -22,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
}

View File

@@ -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 - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -307,7 +307,7 @@ To verify that, we can open a new console and create a new Tenant:
```shell
$ kubectl apply -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: gas

View File

@@ -1665,20 +1665,6 @@ CapsuleConfigurationSpec defines the Capsule configuration.
<i>Default</i>: true<br/>
</td>
<td>true</td>
</tr><tr>
<td><b><a href="#capsuleconfigurationspecnodemetadata">nodeMetadata</a></b></td>
<td>object</td>
<td>
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.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b><a href="#capsuleconfigurationspecoverrides">overrides</a></b></td>
<td>object</td>
<td>
Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>forceTenantPrefix</b></td>
<td>boolean</td>
@@ -1688,6 +1674,22 @@ CapsuleConfigurationSpec defines the Capsule configuration.
<i>Default</i>: false<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#capsuleconfigurationspecnodemetadata">nodeMetadata</a></b></td>
<td>object</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#capsuleconfigurationspecoverrides">overrides</a></b></td>
<td>object</td>
<td>
Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.<br/>
<br/>
<i>Default</i>: map[TLSSecretName:capsule-tls mutatingWebhookConfigurationName:capsule-mutating-webhook-configuration validatingWebhookConfigurationName:capsule-validating-webhook-configuration]<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>protectedNamespaceRegex</b></td>
<td>string</td>
@@ -2972,7 +2974,7 @@ TenantSpec defines the desired state of Tenant.
<td><b><a href="#tenantspecpriorityclasses-1">priorityClasses</a></b></td>
<td>object</td>
<td>
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.<br/>
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.<br/>
</td>
<td>false</td>
</tr><tr>
@@ -2982,6 +2984,13 @@ TenantSpec defines the desired state of Tenant.
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.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecruntimeclasses">runtimeClasses</a></b></td>
<td>object</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecserviceoptions-1">serviceOptions</a></b></td>
<td>object</td>
@@ -2993,7 +3002,7 @@ TenantSpec defines the desired state of Tenant.
<td><b><a href="#tenantspecstorageclasses-1">storageClasses</a></b></td>
<td>object</td>
<td>
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.<br/>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
@@ -3220,12 +3229,12 @@ Specifies options for the Ingress resources, such as allowed hostnames and Ingre
<td>
Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.<br/>
</td>
<td>true</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecingressoptionsallowedclasses-1">allowedClasses</a></b></td>
<td>object</td>
<td>
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.<br/>
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.<br/>
</td>
<td>false</td>
</tr><tr>
@@ -3257,7 +3266,7 @@ Specifies options for the Ingress resources, such as allowed hostnames and Ingre
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.
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.
<table>
<thead>
@@ -3282,6 +3291,67 @@ Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures tha
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>default</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecingressoptionsallowedclassesmatchexpressionsindex">matchExpressions</a></b></td>
<td>[]object</td>
<td>
matchExpressions is a list of label selector requirements. The requirements are ANDed.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>matchLabels</b></td>
<td>map[string]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### 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.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>key</b></td>
<td>string</td>
<td>
key is the label key that the selector applies to.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>operator</b></td>
<td>string</td>
<td>
operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>values</b></td>
<td>[]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
@@ -3448,25 +3518,25 @@ Specifies options for the Namespaces, such as additional metadata or maximum num
</tr>
</thead>
<tbody><tr>
<td><b><a href="#tenantspecnamespaceoptionsadditionalmetadata-1">additionalMetadata</a></b></td>
<td>object</td>
<td>
Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecnamespaceoptionsforbiddenannotations">forbiddenAnnotations</a></b></td>
<td>object</td>
<td>
Define the annotations that a Tenant Owner cannot set for their Namespace resources.<br/>
</td>
<td>true</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecnamespaceoptionsforbiddenlabels">forbiddenLabels</a></b></td>
<td>object</td>
<td>
Define the labels that a Tenant Owner cannot set for their Namespace resources.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b><a href="#tenantspecnamespaceoptionsadditionalmetadata-1">additionalMetadata</a></b></td>
<td>object</td>
<td>
Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>quota</b></td>
@@ -3482,6 +3552,39 @@ Specifies options for the Namespaces, such as additional metadata or maximum num
</table>
### Tenant.spec.namespaceOptions.additionalMetadata
Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>annotations</b></td>
<td>map[string]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>labels</b></td>
<td>map[string]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### Tenant.spec.namespaceOptions.forbiddenAnnotations
@@ -3548,39 +3651,6 @@ Define the labels that a Tenant Owner cannot set for their Namespace resources.
</table>
### Tenant.spec.namespaceOptions.additionalMetadata
Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>annotations</b></td>
<td>map[string]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>labels</b></td>
<td>map[string]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### Tenant.spec.networkPolicies
@@ -4331,7 +4401,7 @@ NetworkPolicyPort describes a port to allow traffic on
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.
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.
<table>
<thead>
@@ -4356,6 +4426,67 @@ Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures th
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>default</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecpriorityclassesmatchexpressionsindex">matchExpressions</a></b></td>
<td>[]object</td>
<td>
matchExpressions is a list of label selector requirements. The requirements are ANDed.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>matchLabels</b></td>
<td>map[string]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### 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.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>key</b></td>
<td>string</td>
<td>
key is the label key that the selector applies to.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>operator</b></td>
<td>string</td>
<td>
operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>values</b></td>
<td>[]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
@@ -4502,6 +4633,93 @@ A scoped-resource selector requirement is a selector that contains values, a sco
</table>
### 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.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>allowed</b></td>
<td>[]string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>allowedRegex</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecruntimeclassesmatchexpressionsindex">matchExpressions</a></b></td>
<td>[]object</td>
<td>
matchExpressions is a list of label selector requirements. The requirements are ANDed.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>matchLabels</b></td>
<td>map[string]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### 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.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>key</b></td>
<td>string</td>
<td>
key is the label key that the selector applies to.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>operator</b></td>
<td>string</td>
<td>
operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>values</b></td>
<td>[]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### Tenant.spec.serviceOptions
@@ -4651,7 +4869,7 @@ Specifies the external IPs that can be used in Services with type ClusterIP. An
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.
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.
<table>
<thead>
@@ -4676,6 +4894,67 @@ Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures tha
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>default</b></td>
<td>string</td>
<td>
<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#tenantspecstorageclassesmatchexpressionsindex">matchExpressions</a></b></td>
<td>[]object</td>
<td>
matchExpressions is a list of label selector requirements. The requirements are ANDed.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>matchLabels</b></td>
<td>map[string]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
### 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.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>key</b></td>
<td>string</td>
<td>
key is the label key that the selector applies to.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>operator</b></td>
<td>string</td>
<td>
operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.<br/>
</td>
<td>true</td>
</tr><tr>
<td><b>values</b></td>
<td>[]string</td>
<td>
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.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>

View File

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

View File

@@ -51,7 +51,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -129,7 +129,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -188,7 +188,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -247,7 +247,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -361,7 +361,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -517,7 +517,7 @@ As cluster admin, create a couple of tenants
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -535,7 +535,7 @@ and
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: gas
@@ -659,7 +659,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -737,7 +737,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -807,7 +807,7 @@ As cluster admin, create a couple of tenants
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -834,7 +834,7 @@ and
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: gas
@@ -956,7 +956,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1072,7 +1072,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1148,7 +1148,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1231,7 +1231,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1348,7 +1348,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1489,7 +1489,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1613,7 +1613,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1687,12 +1687,14 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
spec:
enableNodePorts: false
serviceOptions:
allowedServices:
nodePort: false
owners:
- kind: User
name: alice
@@ -1763,7 +1765,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1830,7 +1832,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - <<EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -1896,7 +1898,7 @@ As cluster admin, create a tenant
```yaml
kubectl create -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -2011,7 +2013,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -2106,7 +2108,7 @@ As cluster admin, create a tenant and assign the above Storage Class
```yaml
kubectl create -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -2229,7 +2231,7 @@ And assign it to the tenant
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil

View File

@@ -104,7 +104,7 @@ For a web-based dashboard, like the [Kubernetes Dashboard](https://github.com/ku
Each Tenant owner can have their capabilities managed pretty similar to a standard Kubernetes RBAC.
```yaml
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: my-tenant
@@ -171,7 +171,7 @@ namespace/solar-development created
The Capsule Proxy gives the owners the ability to access the nodes matching the `.spec.nodeSelector` in the Tenant manifest:
```yaml
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -209,7 +209,7 @@ These are mandatory in order to retrieve the list of the running Pods on the req
A Tenant may be limited to use a set of allowed Storage Class resources, as follows.
```yaml
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -267,7 +267,7 @@ provisioner: cephfs
As for Storage Class, also Ingress Class can be enforced.
```yaml
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -330,7 +330,7 @@ spec:
Allowed PriorityClasses assigned to a Tenant Owner can be enforced as follows:
```yaml
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -403,7 +403,7 @@ These tenant users, groups and services accounts have less privileged access tha
As a Tenant Owner `alice`, you can create a `ProxySetting` resources 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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -47,7 +47,7 @@ He can assign this role to all namespaces in a tenant by setting the tenant mani
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -171,7 +171,7 @@ As cluster admin, create a tenant with additional labels:
```yaml
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: oil
@@ -203,7 +203,7 @@ metadata:
pod-security.kubernetes.io/audit: restricted
name: oil-development
ownerReferences:
- apiVersion: capsule.clastix.io/v1beta1
- apiVersion: capsule.clastix.io/v1beta2
blockOwnerDeletion: true
controller: true
kind: Tenant

View File

@@ -1,17 +1,133 @@
# Upgrading Tenant resource from v1alpha1 to v1beta1 version
# Capsule upgrading guide
With [Capsule v0.1.0](https://github.com/clastix/capsule/releases/tag/v0.1.0), the Tenant custom resource has been bumped to `v1beta1` from `v1alpha1` with additional fields addressing the new features implemented so far.
List of Tenant API changes:
- [Capsule v0.1.0](https://github.com/clastix/capsule/releases/tag/v0.1.0) bump to `v1beta1` from `v1alpha1`.
- [Capsule v0.2.0](https://github.com/clastix/capsule/releases/tag/v0.1.0) bump to `v1beta2` from `v1beta1`, deprecating `v1alpha1`.
This document aims to provide support and a guide on how to perform a clean upgrade to the latest API version in order to avoid service disruption and data loss.
## Backup your cluster
As an installation method, Helm is given for granted, YMMV using the `kustomize` manifest.
## Considerations
We strongly suggest performing a full backup of your Kubernetes cluster, such as storage and etcd.
Use your favorite tool according to your needs.
Use your favourite tool according to your needs.
# 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/config/crd/bases/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/config/crd/bases/globaltenantresources-crd.yaml
kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/tenant-crd.yaml
kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/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,7 +137,7 @@ 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:
@@ -36,23 +152,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`.
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
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
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 +180,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 +188,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.

View File

@@ -71,7 +71,7 @@ module.exports = function (api) {
path: '/docs/guides/velero'
},
{
label: 'Upgrading Tenant version',
label: 'Upgrading Capsule',
path: '/docs/guides/upgrading'
},
{

View File

@@ -14,17 +14,17 @@ import (
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",

View File

@@ -14,17 +14,17 @@ import (
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",

View File

@@ -7,6 +7,7 @@ package e2e
import (
"context"
"encoding/json"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -14,17 +15,23 @@ 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"
)
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",
@@ -111,6 +118,190 @@ 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("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())

View File

@@ -12,18 +12,16 @@ import (
. "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,7 +41,7 @@ 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"}
})
@@ -52,7 +50,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
})
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"}
})
@@ -63,7 +61,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
})
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"}
})

View File

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

View File

@@ -15,17 +15,17 @@ import (
"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",

View File

@@ -19,19 +19,19 @@ import (
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 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",

View File

@@ -15,17 +15,17 @@ import (
"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",

View File

@@ -15,17 +15,17 @@ import (
"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",

View File

@@ -13,27 +13,25 @@ import (
. "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"},
},
},
},

View File

@@ -15,17 +15,17 @@ import (
"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",

View File

@@ -14,16 +14,16 @@ import (
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",

View File

@@ -12,72 +12,94 @@ import (
. "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())
})
}
})

View File

@@ -12,18 +12,16 @@ import (
. "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
})
})

View File

@@ -21,21 +21,20 @@ import (
"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("Creating a GlobalTenantResource object", func() {
solar := &capsulev1beta1.Tenant{
solar := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "energy-solar",
Labels: map[string]string{
"replicate": "true",
},
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
Spec: capsulev1beta2.TenantSpec{
Owners: capsulev1beta2.OwnerListSpec{
{
Name: "solar-user",
Kind: "User",
@@ -44,15 +43,15 @@ var _ = Describe("Creating a GlobalTenantResource object", func() {
},
}
wind := &capsulev1beta1.Tenant{
wind := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "energy-wind",
Labels: map[string]string{
"replicate": "true",
},
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
Spec: capsulev1beta2.TenantSpec{
Owners: capsulev1beta2.OwnerListSpec{
{
Name: "wind-user",
Kind: "User",

View File

@@ -13,17 +13,17 @@ import (
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",

View File

@@ -13,17 +13,17 @@ import (
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",

View File

@@ -18,29 +18,36 @@ import (
"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("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: &api.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-.*$",
},
},
},

View File

@@ -9,50 +9,151 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"
. "github.com/onsi/ginkgo"
. "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"
)
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: &api.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",
},
},
},
Regex: "^oil-.*$",
},
},
},
}
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",
},
},
},
},
},
},
}
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() {
@@ -64,10 +165,10 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
}
ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
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) {
@@ -147,12 +248,12 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
}
ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
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{
@@ -187,12 +288,12 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
}
ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
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{
@@ -225,11 +326,11 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
}
ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
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{
@@ -264,11 +365,11 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
}
ns := NewNamespace("")
cs := ownerClient(tnt.Spec.Owners[0])
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{
@@ -291,4 +392,276 @@ 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 {
missingAPIError := &meta.NoKindMatchError{}
if errors.As(err, &missingAPIError) {
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 {
missingAPIError := &meta.NoKindMatchError{}
if errors.As(err, &missingAPIError) {
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 {
missingAPIError := &meta.NoKindMatchError{}
if errors.As(err, &missingAPIError) {
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 {
missingAPIError := &meta.NoKindMatchError{}
if errors.As(err, &missingAPIError) {
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 {
missingAPIError := &meta.NoKindMatchError{}
if errors.As(err, &missingAPIError) {
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()))
})
})

View File

@@ -18,39 +18,39 @@ import (
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("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{
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{
IngressOptions: capsulev1beta2.IngressOptions{
HostnameCollisionScope: api.HostnameCollisionScopeCluster,
},
},

View File

@@ -18,23 +18,23 @@ import (
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("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{
IngressOptions: capsulev1beta2.IngressOptions{
HostnameCollisionScope: api.HostnameCollisionScopeDisabled,
},
},

View File

@@ -18,23 +18,23 @@ import (
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("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{
IngressOptions: capsulev1beta2.IngressOptions{
HostnameCollisionScope: api.HostnameCollisionScopeNamespace,
},
},

View File

@@ -18,23 +18,23 @@ import (
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("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{
IngressOptions: capsulev1beta2.IngressOptions{
HostnameCollisionScope: api.HostnameCollisionScopeTenant,
},
},

View File

@@ -18,23 +18,23 @@ import (
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("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{
IngressOptions: capsulev1beta2.IngressOptions{
AllowedHostnames: &api.AllowedListSpec{
Exact: []string{"sigs.k8s.io", "operator.sdk", "domain.tld"},
Regex: `.*\.clastix\.io`,

View File

@@ -12,14 +12,14 @@ import (
. "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",

View File

@@ -13,23 +13,23 @@ 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("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{
NamespaceOptions: &capsulev1beta2.NamespaceOptions{
AdditionalMetadata: &api.AdditionalMetadataSpec{
Labels: map[string]string{
"k8s.io/custom-label": "foo",

View File

@@ -14,16 +14,16 @@ 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 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",

View File

@@ -15,22 +15,27 @@ 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("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",

View File

@@ -12,16 +12,16 @@ import (
. "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",

View File

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

View File

@@ -13,22 +13,22 @@ import (
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{
NamespaceOptions: &capsulev1beta2.NamespaceOptions{
Quota: pointer.Int32Ptr(3),
},
},

View File

@@ -17,26 +17,30 @@ 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: &api.AllowedListSpec{
Exact: []string{
"cephfs",
"glusterfs",
StorageClasses: &api.DefaultAllowedListSpec{
SelectorAllowedListSpec: api.SelectorAllowedListSpec{
AllowedListSpec: api.AllowedListSpec{
Exact: []string{
"cephfs",
"glusterfs",
},
},
},
},
LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{

View File

@@ -7,49 +7,141 @@ package e2e
import (
"context"
"strconv"
"strings"
. "github.com/onsi/ginkgo"
. "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: &api.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("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -66,66 +158,26 @@ 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 allow exact match", func() {
pc := &v1.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: "gold",
},
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("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
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",
},
},
PriorityClassName: "gold",
},
}
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("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
for i, pc := range []string{"pc-bronze", "pc-silver", "pc-gold"} {
class := &v1.PriorityClass{
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: pc,
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{
@@ -143,14 +195,274 @@ 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
}).ShouldNot(Succeed())
}
})
It("should allow exact match", func() {
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())
ns := NewNamespace("")
NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
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",
},
},
PriorityClassName: "gold",
},
}
cs := ownerClient(tntNoDefaults.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("")
NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
for i, pc := range []string{"pc-bronze", "pc-silver", "pc-gold"} {
class := &schedulingv1.PriorityClass{
ObjectMeta: metav1.ObjectMeta{
Name: pc,
Labels: map[string]string{
"env": "e2e",
},
},
Description: "fake PriorityClass for e2e",
Value: int32(10000 * (i + 2)),
}
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,
},
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
}).Should(Succeed())
Expect(k8sClient.Delete(context.TODO(), class)).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())
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))
})
})

View File

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

View File

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

View File

@@ -12,18 +12,16 @@ import (
. "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 = ""
})
})

View File

@@ -19,16 +19,16 @@ 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",

View File

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

View File

@@ -14,16 +14,16 @@ import (
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",
@@ -45,7 +45,7 @@ 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 := utils.GetTypeLabel(&capsulev1beta1.Tenant{})
l, err := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
Expect(err).ToNot(HaveOccurred())
ns := NewNamespace("")
@@ -54,7 +54,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func()
})
})
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())
})

View File

@@ -12,16 +12,16 @@ import (
. "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",

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