mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-19 20:39:51 +00:00
Compare commits
52 Commits
helm-v0.2.
...
v0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d92f1e7825 | ||
|
|
ee813c5343 | ||
|
|
0fbf43ba0f | ||
|
|
7ec7f3c69c | ||
|
|
de587919f8 | ||
|
|
a334e68256 | ||
|
|
36f0281355 | ||
|
|
6a5fff01cd | ||
|
|
f8e212c291 | ||
|
|
91a979edc4 | ||
|
|
1b51a4777d | ||
|
|
8e827b2f5b | ||
|
|
1f0ac0724b | ||
|
|
18a9e77a8a | ||
|
|
ea88b102e5 | ||
|
|
b58cb19ea1 | ||
|
|
25095bcf2d | ||
|
|
ea71dd7e65 | ||
|
|
68c86a6509 | ||
|
|
7784e8df50 | ||
|
|
8f933cd2b3 | ||
|
|
a1b624f239 | ||
|
|
ab0fe91c58 | ||
|
|
fbea737a51 | ||
|
|
51288f30c5 | ||
|
|
f73a5b17f4 | ||
|
|
f6c1ad68da | ||
|
|
9f10923d21 | ||
|
|
7591140fc7 | ||
|
|
34cfb16ea3 | ||
|
|
f5cd194c05 | ||
|
|
628efbb30f | ||
|
|
79391f863a | ||
|
|
ee0fdc9efa | ||
|
|
e964f34086 | ||
|
|
0d9054db34 | ||
|
|
7c4b46eeb1 | ||
|
|
dbbd9e64c0 | ||
|
|
462d7213b9 | ||
|
|
93fbca9b18 | ||
|
|
289b079530 | ||
|
|
5af3c9828e | ||
|
|
77a8c9ab62 | ||
|
|
791dde5bf6 | ||
|
|
43bd2491ae | ||
|
|
2cb37abc51 | ||
|
|
0b057d63b6 | ||
|
|
0f580ef379 | ||
|
|
34a948da81 | ||
|
|
e14e12c287 | ||
|
|
9d6f766cc1 | ||
|
|
ea01b9d1f9 |
38
.github/workflows/helm.yml
vendored
38
.github/workflows/helm.yml
vendored
@@ -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
4
.gitignore
vendored
@@ -29,6 +29,4 @@ bin
|
||||
**/*.key
|
||||
.DS_Store
|
||||
*.tgz
|
||||
|
||||
capsule
|
||||
|
||||
kind.yaml
|
||||
|
||||
@@ -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/)
|
||||

|
||||
|
||||
### [Wargaming.net](https://www.wargaming.net/)
|
||||

|
||||
|
||||
38
Makefile
38
Makefile
@@ -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
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ type CapsuleConfigurationSpec struct {
|
||||
ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
|
||||
|
||||
21
api/v1alpha1/capsuleconfiguration_webhook.go
Normal file
21
api/v1alpha1/capsuleconfiguration_webhook.go
Normal 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()
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
21
api/v1beta1/tenant_webhook.go
Normal file
21
api/v1beta1/tenant_webhook.go
Normal 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()
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
17
api/v1beta2/tenant_annotations.go
Normal file
17
api/v1beta2/tenant_annotations.go
Normal 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(), "/", "_")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"` | |
|
||||
|
||||
9
charts/capsule/ci/test-values.yaml
Normal file
9
charts/capsule/ci/test-values.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
fullnameOverride: capsule
|
||||
manager:
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
@@ -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
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,4 +7,4 @@ kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: clastix/capsule
|
||||
newTag: v0.1.3
|
||||
newTag: v0.2.1
|
||||
|
||||
11
config/samples/capsule_v1beta2_capsuleconfiguration.yaml
Normal file
11
config/samples/capsule_v1beta2_capsuleconfiguration.yaml
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
27
config/webhook/patch_mutating_ns_selector.yaml
Normal file
27
config/webhook/patch_mutating_ns_selector.yaml
Normal 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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
117
controllers/pv/controller.go
Normal file
117
controllers/pv/controller.go
Normal 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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -71,7 +71,7 @@ module.exports = function (api) {
|
||||
path: '/docs/guides/velero'
|
||||
},
|
||||
{
|
||||
label: 'Upgrading Tenant version',
|
||||
label: 'Upgrading Capsule',
|
||||
path: '/docs/guides/upgrading'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"}
|
||||
})
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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-.*$",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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()))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
|
||||
224
e2e/pod_runtime_class_test.go
Normal file
224
e2e/pod_runtime_class_test.go
Normal 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())
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
199
e2e/preventing_pv_cross_tenant_mount_test.go
Normal file
199
e2e/preventing_pv_cross_tenant_mount_test.go
Normal 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())
|
||||
})
|
||||
})
|
||||
@@ -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 = ""
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user