mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-03-02 01:30:17 +00:00
Compare commits
88 Commits
v0.1.0-rc1
...
v0.1.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb19a7a89f | ||
|
|
db8b8ac1d9 | ||
|
|
663ce93a3e | ||
|
|
a6408f26b0 | ||
|
|
1aa026c977 | ||
|
|
6008373960 | ||
|
|
414c03a874 | ||
|
|
4d34a9e3d7 | ||
|
|
cb9b560926 | ||
|
|
ef75d0496a | ||
|
|
e1e75a093b | ||
|
|
80143ffd50 | ||
|
|
3d54810f19 | ||
|
|
09dfe33a6a | ||
|
|
01ea36b462 | ||
|
|
bd448d8c29 | ||
|
|
b58ca3a7d7 | ||
|
|
52fb0948cb | ||
|
|
1b0fa587eb | ||
|
|
92655f1872 | ||
|
|
44bf846260 | ||
|
|
e6b433dcd7 | ||
|
|
3e0882dbc8 | ||
|
|
416609362d | ||
|
|
3d714dc124 | ||
|
|
bd01881dd3 | ||
|
|
ac6af13b07 | ||
|
|
8fb4b7d4a1 | ||
|
|
d4280b8d7e | ||
|
|
6e39b17e7c | ||
|
|
b1a9603faa | ||
|
|
0d4201a6c2 | ||
|
|
1734c906a9 | ||
|
|
184f054f2f | ||
|
|
126449b796 | ||
|
|
284e7da66f | ||
|
|
99e1589828 | ||
|
|
7cc2c3f4e9 | ||
|
|
ba07f99c6e | ||
|
|
d79972691e | ||
|
|
25f021e151 | ||
|
|
320353e561 | ||
|
|
5bd2af89e4 | ||
|
|
61d394f844 | ||
|
|
fc8f5a08fe | ||
|
|
ffcb7c4408 | ||
|
|
8d1a109f1c | ||
|
|
a19045419d | ||
|
|
7574335a8a | ||
|
|
72e97b9960 | ||
|
|
b3c6082a1e | ||
|
|
9a940096c9 | ||
|
|
f9becf39e5 | ||
|
|
e1160b8862 | ||
|
|
6472b221ed | ||
|
|
a2e5bbf26d | ||
|
|
8804496bb2 | ||
|
|
5de0a6d712 | ||
|
|
531cc4cf14 | ||
|
|
3e33290c4c | ||
|
|
824442b9ee | ||
|
|
34583352e5 | ||
|
|
5681228789 | ||
|
|
7237972b80 | ||
|
|
46fc65a988 | ||
|
|
44acfaed86 | ||
|
|
7ca087cac5 | ||
|
|
b2b640dc96 | ||
|
|
5b35e0b0d5 | ||
|
|
accd9ca038 | ||
|
|
e7b33bda26 | ||
|
|
08fbd26ec8 | ||
|
|
006b0c80cf | ||
|
|
b6f3fccbea | ||
|
|
bf79c25a8a | ||
|
|
630e802708 | ||
|
|
e5a1861cac | ||
|
|
246c1a3c2c | ||
|
|
a06e68945c | ||
|
|
61c9bc647c | ||
|
|
9c8b0377dc | ||
|
|
dfe0f5ea49 | ||
|
|
a1a2e5e00c | ||
|
|
20aa7657e4 | ||
|
|
7c1592e739 | ||
|
|
f60f2b1542 | ||
|
|
53377e994c | ||
|
|
d0893a5aa9 |
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [prometherion]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
73
.github/workflows/docker-ci.yml
vendored
Normal file
73
.github/workflows/docker-ci.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: docker-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
docker-ci:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
quay.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=semver,pattern={{raw}}
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm64,arm
|
||||
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
id: buildx
|
||||
with:
|
||||
install: true
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
-
|
||||
name: Inspect builder
|
||||
run: |
|
||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
||||
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
|
||||
echo "Status: ${{ steps.buildx.outputs.status }}"
|
||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
|
||||
-
|
||||
name: Login to quay.io Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ github.repository_owner }}+github
|
||||
password: ${{ secrets.BOT_QUAY_IO }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
id: build-release
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
-
|
||||
name: Image digest
|
||||
run: echo ${{ steps.build-release.outputs.digest }}
|
||||
@@ -1,6 +1,7 @@
|
||||
# Build the manager binary
|
||||
FROM golang:1.16 as builder
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG GIT_HEAD_COMMIT
|
||||
ARG GIT_TAG_COMMIT
|
||||
ARG GIT_LAST_TAG
|
||||
@@ -24,7 +25,7 @@ COPY controllers/ controllers/
|
||||
COPY pkg/ pkg/
|
||||
|
||||
# Build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build \
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH GO111MODULE=on go build \
|
||||
-gcflags "-N -l" \
|
||||
-ldflags "-X main.GitRepo=$GIT_REPO -X main.GitTag=$GIT_LAST_TAG -X main.GitCommit=$GIT_HEAD_COMMIT -X main.GitDirty=$GIT_MODIFIED -X main.BuildTime=$BUILD_DATE" \
|
||||
-o manager
|
||||
|
||||
52
Makefile
52
Makefile
@@ -15,7 +15,7 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= quay.io/clastix/capsule:$(VERSION)
|
||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||
CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false"
|
||||
CRD_OPTIONS ?= "crd:preserveUnknownFields=false"
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
@@ -87,37 +87,27 @@ docker-build: test
|
||||
docker-push:
|
||||
docker push ${IMG}
|
||||
|
||||
# find or download controller-gen
|
||||
# download controller-gen if necessary
|
||||
controller-gen:
|
||||
ifeq (, $(shell which controller-gen))
|
||||
@{ \
|
||||
set -e ;\
|
||||
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0 ;\
|
||||
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
}
|
||||
CONTROLLER_GEN=$(GOBIN)/controller-gen
|
||||
else
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
endif
|
||||
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
|
||||
controller-gen: ## Download controller-gen locally if necessary.
|
||||
$(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0)
|
||||
|
||||
kustomize:
|
||||
ifeq (, $(shell which kustomize))
|
||||
@{ \
|
||||
set -e ;\
|
||||
KUSTOMIZE_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$KUSTOMIZE_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/kustomize/kustomize/v3@v3.5.4 ;\
|
||||
rm -rf $$KUSTOMIZE_GEN_TMP_DIR ;\
|
||||
}
|
||||
KUSTOMIZE=$(GOBIN)/kustomize
|
||||
else
|
||||
KUSTOMIZE=$(shell which kustomize)
|
||||
endif
|
||||
KUSTOMIZE = $(shell pwd)/bin/kustomize
|
||||
kustomize: ## Download kustomize locally if necessary.
|
||||
$(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7)
|
||||
|
||||
# go-get-tool will 'go get' any package $2 and install it to $1.
|
||||
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
define go-get-tool
|
||||
@[ -f $(1) ] || { \
|
||||
set -e ;\
|
||||
TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
echo "Downloading $(2)" ;\
|
||||
GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\
|
||||
rm -rf $$TMP_DIR ;\
|
||||
}
|
||||
endef
|
||||
|
||||
# Generate bundle manifests and metadata, then validate generated files.
|
||||
bundle: manifests
|
||||
|
||||
40
PROJECT
40
PROJECT
@@ -1,25 +1,39 @@
|
||||
domain: github.com/clastix/capsule
|
||||
layout: go.kubebuilder.io/v3
|
||||
domain: clastix.io
|
||||
layout:
|
||||
- go.kubebuilder.io/v3
|
||||
plugins:
|
||||
manifests.sdk.operatorframework.io/v2: {}
|
||||
scorecard.sdk.operatorframework.io/v2: {}
|
||||
projectName: capsule
|
||||
repo: github.com/clastix/capsule
|
||||
resources:
|
||||
- api:
|
||||
crdVersion: v1
|
||||
controller: false
|
||||
domain: github.com/clastix/capsule
|
||||
group: capsule.clastix.io
|
||||
namespaced: false
|
||||
controller: true
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: Tenant
|
||||
path: github.com/clastix/capsule/api/v1alpha1
|
||||
version: v1alpha1
|
||||
webhooks:
|
||||
conversion: true
|
||||
webhookVersion: v1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: false
|
||||
controller: true
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: CapsuleConfiguration
|
||||
path: github.com/clastix/capsule/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
controller: true
|
||||
domain: github.com/clastix/capsule
|
||||
group: capsule.clastix.io
|
||||
namespaced: false
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: Tenant
|
||||
path: github.com/clastix/capsule/api/v1alpha1
|
||||
version: v1alpha1
|
||||
path: github.com/clastix/capsule/api/v1beta1
|
||||
version: v1beta1
|
||||
version: "3"
|
||||
plugins:
|
||||
manifests.sdk.operatorframework.io/v2: {}
|
||||
scorecard.sdk.operatorframework.io/v2: {}
|
||||
9
api/v1alpha1/additional_metadata.go
Normal file
9
api/v1alpha1/additional_metadata.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
type AdditionalMetadataSpec struct {
|
||||
AdditionalLabels map[string]string `json:"additionalLabels,omitempty"`
|
||||
AdditionalAnnotations map[string]string `json:"additionalAnnotations,omitempty"`
|
||||
}
|
||||
12
api/v1alpha1/additional_role_bindings.go
Normal file
12
api/v1alpha1/additional_role_bindings.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import rbacv1 "k8s.io/api/rbac/v1"
|
||||
|
||||
type AdditionalRoleBindingsSpec struct {
|
||||
ClusterRoleName string `json:"clusterRoleName"`
|
||||
// kubebuilder:validation:Minimum=1
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
}
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
|
||||
458
api/v1alpha1/conversion_hub.go
Normal file
458
api/v1alpha1/conversion_hub.go
Normal file
@@ -0,0 +1,458 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/conversion"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
const (
|
||||
podAllowedImagePullPolicyAnnotation = "capsule.clastix.io/allowed-image-pull-policy"
|
||||
|
||||
podPriorityAllowedAnnotation = "priorityclass.capsule.clastix.io/allowed"
|
||||
podPriorityAllowedRegexAnnotation = "priorityclass.capsule.clastix.io/allowed-regex"
|
||||
|
||||
enableNodePortsAnnotation = "capsule.clastix.io/enable-node-ports"
|
||||
|
||||
ownerGroupsAnnotation = "owners.capsule.clastix.io/group"
|
||||
ownerUsersAnnotation = "owners.capsule.clastix.io/user"
|
||||
ownerServiceAccountAnnotation = "owners.capsule.clastix.io/serviceaccount"
|
||||
|
||||
enableNodeListingAnnotation = "capsule.clastix.io/enable-node-listing"
|
||||
enableNodeUpdateAnnotation = "capsule.clastix.io/enable-node-update"
|
||||
enableNodeDeletionAnnotation = "capsule.clastix.io/enable-node-deletion"
|
||||
enableStorageClassListingAnnotation = "capsule.clastix.io/enable-storageclass-listing"
|
||||
enableStorageClassUpdateAnnotation = "capsule.clastix.io/enable-storageclass-update"
|
||||
enableStorageClassDeletionAnnotation = "capsule.clastix.io/enable-storageclass-deletion"
|
||||
enableIngressClassListingAnnotation = "capsule.clastix.io/enable-ingressclass-listing"
|
||||
enableIngressClassUpdateAnnotation = "capsule.clastix.io/enable-ingressclass-update"
|
||||
enableIngressClassDeletionAnnotation = "capsule.clastix.io/enable-ingressclass-deletion"
|
||||
|
||||
listOperation = "List"
|
||||
updateOperation = "Update"
|
||||
deleteOperation = "Delete"
|
||||
|
||||
nodesServiceKind = "Nodes"
|
||||
storageClassesServiceKind = "StorageClasses"
|
||||
ingressClassesServiceKind = "IngressClasses"
|
||||
)
|
||||
|
||||
func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() []capsulev1beta1.OwnerSpec {
|
||||
var serviceKindToAnnotationMap = map[capsulev1beta1.ProxyServiceKind][]string{
|
||||
nodesServiceKind: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
|
||||
storageClassesServiceKind: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
|
||||
ingressClassesServiceKind: {enableIngressClassListingAnnotation, enableIngressClassUpdateAnnotation, enableIngressClassDeletionAnnotation},
|
||||
}
|
||||
var annotationToOperationMap = map[string]capsulev1beta1.ProxyOperation{
|
||||
enableNodeListingAnnotation: listOperation,
|
||||
enableNodeUpdateAnnotation: updateOperation,
|
||||
enableNodeDeletionAnnotation: deleteOperation,
|
||||
enableStorageClassListingAnnotation: listOperation,
|
||||
enableStorageClassUpdateAnnotation: updateOperation,
|
||||
enableStorageClassDeletionAnnotation: deleteOperation,
|
||||
enableIngressClassListingAnnotation: listOperation,
|
||||
enableIngressClassUpdateAnnotation: updateOperation,
|
||||
enableIngressClassDeletionAnnotation: deleteOperation,
|
||||
}
|
||||
var annotationToOwnerKindMap = map[string]capsulev1beta1.OwnerKind{
|
||||
ownerUsersAnnotation: "User",
|
||||
ownerGroupsAnnotation: "Group",
|
||||
ownerServiceAccountAnnotation: "ServiceAccount",
|
||||
}
|
||||
annotations := t.GetAnnotations()
|
||||
|
||||
var operations = make(map[string]map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation)
|
||||
|
||||
for serviceKind, operationAnnotations := range serviceKindToAnnotationMap {
|
||||
for _, operationAnnotation := range operationAnnotations {
|
||||
val, ok := annotations[operationAnnotation]
|
||||
if ok {
|
||||
for _, owner := range strings.Split(val, ",") {
|
||||
if _, exists := operations[owner]; !exists {
|
||||
operations[owner] = make(map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation)
|
||||
}
|
||||
operations[owner][serviceKind] = append(operations[owner][serviceKind], annotationToOperationMap[operationAnnotation])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var owners []capsulev1beta1.OwnerSpec
|
||||
|
||||
var getProxySettingsForOwner = func(ownerName string) (settings []capsulev1beta1.ProxySettings) {
|
||||
ownerOperations, ok := operations[ownerName]
|
||||
if ok {
|
||||
for k, v := range ownerOperations {
|
||||
settings = append(settings, capsulev1beta1.ProxySettings{
|
||||
Kind: k,
|
||||
Operations: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
owners = append(owners, capsulev1beta1.OwnerSpec{
|
||||
Kind: capsulev1beta1.OwnerKind(t.Spec.Owner.Kind),
|
||||
Name: t.Spec.Owner.Name,
|
||||
ProxyOperations: getProxySettingsForOwner(t.Spec.Owner.Name),
|
||||
})
|
||||
|
||||
for ownerAnnotation, ownerKind := range annotationToOwnerKindMap {
|
||||
val, ok := annotations[ownerAnnotation]
|
||||
if ok {
|
||||
for _, owner := range strings.Split(val, ",") {
|
||||
owners = append(owners, capsulev1beta1.OwnerSpec{
|
||||
Kind: ownerKind,
|
||||
Name: owner,
|
||||
ProxyOperations: getProxySettingsForOwner(owner),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return owners
|
||||
}
|
||||
|
||||
func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
dst := dstRaw.(*capsulev1beta1.Tenant)
|
||||
annotations := t.GetAnnotations()
|
||||
|
||||
// ObjectMeta
|
||||
dst.ObjectMeta = t.ObjectMeta
|
||||
|
||||
// Spec
|
||||
dst.Spec.NamespaceQuota = t.Spec.NamespaceQuota
|
||||
dst.Spec.NodeSelector = t.Spec.NodeSelector
|
||||
|
||||
dst.Spec.Owners = t.convertV1Alpha1OwnerToV1Beta1()
|
||||
|
||||
if t.Spec.NamespacesMetadata != nil {
|
||||
dst.Spec.NamespacesMetadata = &capsulev1beta1.AdditionalMetadataSpec{
|
||||
AdditionalLabels: t.Spec.NamespacesMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: t.Spec.NamespacesMetadata.AdditionalAnnotations,
|
||||
}
|
||||
}
|
||||
if t.Spec.ServicesMetadata != nil {
|
||||
dst.Spec.ServicesMetadata = &capsulev1beta1.AdditionalMetadataSpec{
|
||||
AdditionalLabels: t.Spec.ServicesMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: t.Spec.ServicesMetadata.AdditionalAnnotations,
|
||||
}
|
||||
}
|
||||
if t.Spec.StorageClasses != nil {
|
||||
dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.StorageClasses.Exact,
|
||||
Regex: t.Spec.StorageClasses.Regex,
|
||||
}
|
||||
}
|
||||
if t.Spec.IngressClasses != nil {
|
||||
dst.Spec.IngressClasses = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.IngressClasses.Exact,
|
||||
Regex: t.Spec.IngressClasses.Regex,
|
||||
}
|
||||
}
|
||||
if t.Spec.IngressHostnames != nil {
|
||||
dst.Spec.IngressHostnames = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.IngressHostnames.Exact,
|
||||
Regex: t.Spec.IngressHostnames.Regex,
|
||||
}
|
||||
}
|
||||
if t.Spec.ContainerRegistries != nil {
|
||||
dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: t.Spec.ContainerRegistries.Exact,
|
||||
Regex: t.Spec.ContainerRegistries.Regex,
|
||||
}
|
||||
}
|
||||
if len(t.Spec.NetworkPolicies) > 0 {
|
||||
dst.Spec.NetworkPolicies = &capsulev1beta1.NetworkPolicySpec{
|
||||
Items: t.Spec.NetworkPolicies,
|
||||
}
|
||||
}
|
||||
if len(t.Spec.LimitRanges) > 0 {
|
||||
dst.Spec.LimitRanges = &capsulev1beta1.LimitRangesSpec{
|
||||
Items: t.Spec.LimitRanges,
|
||||
}
|
||||
}
|
||||
if len(t.Spec.ResourceQuota) > 0 {
|
||||
dst.Spec.ResourceQuota = &capsulev1beta1.ResourceQuotaSpec{
|
||||
Items: t.Spec.ResourceQuota,
|
||||
}
|
||||
}
|
||||
if len(t.Spec.AdditionalRoleBindings) > 0 {
|
||||
for _, rb := range t.Spec.AdditionalRoleBindings {
|
||||
dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{
|
||||
ClusterRoleName: rb.ClusterRoleName,
|
||||
Subjects: rb.Subjects,
|
||||
})
|
||||
}
|
||||
}
|
||||
if t.Spec.ExternalServiceIPs != nil {
|
||||
var allowedIPs []capsulev1beta1.AllowedIP
|
||||
for _, IP := range t.Spec.ExternalServiceIPs.Allowed {
|
||||
allowedIPs = append(allowedIPs, capsulev1beta1.AllowedIP(IP))
|
||||
}
|
||||
|
||||
dst.Spec.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{
|
||||
Allowed: allowedIPs,
|
||||
}
|
||||
}
|
||||
|
||||
pullPolicies, ok := annotations[podAllowedImagePullPolicyAnnotation]
|
||||
if ok {
|
||||
for _, policy := range strings.Split(pullPolicies, ",") {
|
||||
dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(policy))
|
||||
}
|
||||
}
|
||||
|
||||
priorityClasses := capsulev1beta1.AllowedListSpec{}
|
||||
|
||||
priorityClassAllowed, ok := annotations[podPriorityAllowedAnnotation]
|
||||
if ok {
|
||||
priorityClasses.Exact = strings.Split(priorityClassAllowed, ",")
|
||||
}
|
||||
priorityClassesRegexp, ok := annotations[podPriorityAllowedRegexAnnotation]
|
||||
if ok {
|
||||
priorityClasses.Regex = priorityClassesRegexp
|
||||
}
|
||||
|
||||
if !reflect.ValueOf(priorityClasses).IsZero() {
|
||||
dst.Spec.PriorityClasses = &priorityClasses
|
||||
}
|
||||
|
||||
enableNodePorts, ok := annotations[enableNodePortsAnnotation]
|
||||
if ok {
|
||||
val, err := strconv.ParseBool(enableNodePorts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, t.GetName()))
|
||||
}
|
||||
dst.Spec.EnableNodePorts = val
|
||||
}
|
||||
|
||||
// Status
|
||||
dst.Status = capsulev1beta1.TenantStatus{
|
||||
Size: t.Status.Size,
|
||||
Namespaces: t.Status.Namespaces,
|
||||
}
|
||||
|
||||
// Remove unneeded annotations
|
||||
delete(dst.ObjectMeta.Annotations, podAllowedImagePullPolicyAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, podPriorityAllowedAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, podPriorityAllowedRegexAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodePortsAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerGroupsAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerUsersAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerServiceAccountAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodeListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodeUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodeDeletionAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableStorageClassListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableStorageClassUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableStorageClassDeletionAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassDeletionAnnotation)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
|
||||
var ownersAnnotations = map[string][]string{
|
||||
ownerGroupsAnnotation: nil,
|
||||
ownerUsersAnnotation: nil,
|
||||
ownerServiceAccountAnnotation: nil,
|
||||
}
|
||||
|
||||
var proxyAnnotations = map[string][]string{
|
||||
enableNodeListingAnnotation: nil,
|
||||
enableNodeUpdateAnnotation: nil,
|
||||
enableNodeDeletionAnnotation: nil,
|
||||
enableStorageClassListingAnnotation: nil,
|
||||
enableStorageClassUpdateAnnotation: nil,
|
||||
enableStorageClassDeletionAnnotation: nil,
|
||||
enableIngressClassListingAnnotation: nil,
|
||||
enableIngressClassUpdateAnnotation: nil,
|
||||
enableIngressClassDeletionAnnotation: nil,
|
||||
}
|
||||
|
||||
for i, owner := range src.Spec.Owners {
|
||||
if i == 0 {
|
||||
t.Spec.Owner = OwnerSpec{
|
||||
Name: owner.Name,
|
||||
Kind: Kind(owner.Kind),
|
||||
}
|
||||
} else {
|
||||
switch owner.Kind {
|
||||
case "User":
|
||||
ownersAnnotations[ownerUsersAnnotation] = append(ownersAnnotations[ownerUsersAnnotation], owner.Name)
|
||||
case "Group":
|
||||
ownersAnnotations[ownerGroupsAnnotation] = append(ownersAnnotations[ownerGroupsAnnotation], owner.Name)
|
||||
case "ServiceAccount":
|
||||
ownersAnnotations[ownerServiceAccountAnnotation] = append(ownersAnnotations[ownerServiceAccountAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
for _, setting := range owner.ProxyOperations {
|
||||
switch setting.Kind {
|
||||
case nodesServiceKind:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case listOperation:
|
||||
proxyAnnotations[enableNodeListingAnnotation] = append(proxyAnnotations[enableNodeListingAnnotation], owner.Name)
|
||||
case updateOperation:
|
||||
proxyAnnotations[enableNodeUpdateAnnotation] = append(proxyAnnotations[enableNodeUpdateAnnotation], owner.Name)
|
||||
case deleteOperation:
|
||||
proxyAnnotations[enableNodeDeletionAnnotation] = append(proxyAnnotations[enableNodeDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case storageClassesServiceKind:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case listOperation:
|
||||
proxyAnnotations[enableStorageClassListingAnnotation] = append(proxyAnnotations[enableStorageClassListingAnnotation], owner.Name)
|
||||
case updateOperation:
|
||||
proxyAnnotations[enableStorageClassUpdateAnnotation] = append(proxyAnnotations[enableStorageClassUpdateAnnotation], owner.Name)
|
||||
case deleteOperation:
|
||||
proxyAnnotations[enableStorageClassDeletionAnnotation] = append(proxyAnnotations[enableStorageClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case ingressClassesServiceKind:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case listOperation:
|
||||
proxyAnnotations[enableIngressClassListingAnnotation] = append(proxyAnnotations[enableIngressClassListingAnnotation], owner.Name)
|
||||
case updateOperation:
|
||||
proxyAnnotations[enableIngressClassUpdateAnnotation] = append(proxyAnnotations[enableIngressClassUpdateAnnotation], owner.Name)
|
||||
case deleteOperation:
|
||||
proxyAnnotations[enableIngressClassDeletionAnnotation] = append(proxyAnnotations[enableIngressClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range ownersAnnotations {
|
||||
if len(v) > 0 {
|
||||
t.Annotations[k] = strings.Join(v, ",")
|
||||
}
|
||||
}
|
||||
for k, v := range proxyAnnotations {
|
||||
if len(v) > 0 {
|
||||
t.Annotations[k] = strings.Join(v, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
|
||||
src := srcRaw.(*capsulev1beta1.Tenant)
|
||||
|
||||
// ObjectMeta
|
||||
t.ObjectMeta = src.ObjectMeta
|
||||
|
||||
// Spec
|
||||
t.Spec.NamespaceQuota = src.Spec.NamespaceQuota
|
||||
t.Spec.NodeSelector = src.Spec.NodeSelector
|
||||
|
||||
if t.Annotations == nil {
|
||||
t.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
t.convertV1Beta1OwnerToV1Alpha1(src)
|
||||
|
||||
if src.Spec.NamespacesMetadata != nil {
|
||||
t.Spec.NamespacesMetadata = &AdditionalMetadataSpec{
|
||||
AdditionalLabels: src.Spec.NamespacesMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: src.Spec.NamespacesMetadata.AdditionalAnnotations,
|
||||
}
|
||||
}
|
||||
if src.Spec.ServicesMetadata != nil {
|
||||
t.Spec.ServicesMetadata = &AdditionalMetadataSpec{
|
||||
AdditionalLabels: src.Spec.ServicesMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: src.Spec.ServicesMetadata.AdditionalAnnotations,
|
||||
}
|
||||
}
|
||||
if src.Spec.StorageClasses != nil {
|
||||
t.Spec.StorageClasses = &AllowedListSpec{
|
||||
Exact: src.Spec.StorageClasses.Exact,
|
||||
Regex: src.Spec.StorageClasses.Regex,
|
||||
}
|
||||
}
|
||||
if src.Spec.IngressClasses != nil {
|
||||
t.Spec.IngressClasses = &AllowedListSpec{
|
||||
Exact: src.Spec.IngressClasses.Exact,
|
||||
Regex: src.Spec.IngressClasses.Regex,
|
||||
}
|
||||
}
|
||||
if src.Spec.IngressHostnames != nil {
|
||||
t.Spec.IngressHostnames = &AllowedListSpec{
|
||||
Exact: src.Spec.IngressHostnames.Exact,
|
||||
Regex: src.Spec.IngressHostnames.Regex,
|
||||
}
|
||||
}
|
||||
if src.Spec.ContainerRegistries != nil {
|
||||
t.Spec.ContainerRegistries = &AllowedListSpec{
|
||||
Exact: src.Spec.ContainerRegistries.Exact,
|
||||
Regex: src.Spec.ContainerRegistries.Regex,
|
||||
}
|
||||
}
|
||||
if src.Spec.NetworkPolicies != nil {
|
||||
t.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items
|
||||
}
|
||||
if src.Spec.LimitRanges != nil {
|
||||
t.Spec.LimitRanges = src.Spec.LimitRanges.Items
|
||||
}
|
||||
if src.Spec.ResourceQuota != nil {
|
||||
t.Spec.ResourceQuota = src.Spec.ResourceQuota.Items
|
||||
}
|
||||
if len(src.Spec.AdditionalRoleBindings) > 0 {
|
||||
for _, rb := range src.Spec.AdditionalRoleBindings {
|
||||
t.Spec.AdditionalRoleBindings = append(t.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{
|
||||
ClusterRoleName: rb.ClusterRoleName,
|
||||
Subjects: rb.Subjects,
|
||||
})
|
||||
}
|
||||
}
|
||||
if src.Spec.ExternalServiceIPs != nil {
|
||||
var allowedIPs []AllowedIP
|
||||
for _, IP := range src.Spec.ExternalServiceIPs.Allowed {
|
||||
allowedIPs = append(allowedIPs, AllowedIP(IP))
|
||||
}
|
||||
|
||||
t.Spec.ExternalServiceIPs = &ExternalServiceIPsSpec{
|
||||
Allowed: allowedIPs,
|
||||
}
|
||||
}
|
||||
if len(src.Spec.ImagePullPolicies) != 0 {
|
||||
var pullPolicies []string
|
||||
for _, policy := range src.Spec.ImagePullPolicies {
|
||||
pullPolicies = append(pullPolicies, string(policy))
|
||||
}
|
||||
t.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",")
|
||||
}
|
||||
|
||||
if src.Spec.PriorityClasses != nil {
|
||||
if len(src.Spec.PriorityClasses.Exact) != 0 {
|
||||
t.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",")
|
||||
}
|
||||
if src.Spec.PriorityClasses.Regex != "" {
|
||||
t.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex
|
||||
}
|
||||
}
|
||||
|
||||
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(src.Spec.EnableNodePorts)
|
||||
|
||||
// Status
|
||||
t.Status = TenantStatus{
|
||||
Size: src.Status.Size,
|
||||
Namespaces: src.Status.Namespaces,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
360
api/v1alpha1/conversion_hub_test.go
Normal file
360
api/v1alpha1/conversion_hub_test.go
Normal file
@@ -0,0 +1,360 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
var namespaceQuota int32 = 5
|
||||
var nodeSelector = map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
var v1alpha1AdditionalMetadataSpec = &AdditionalMetadataSpec{
|
||||
AdditionalLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
AdditionalAnnotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
var v1alpha1AllowedListSpec = &AllowedListSpec{
|
||||
Exact: []string{"foo", "bar"},
|
||||
Regex: "^foo*",
|
||||
}
|
||||
var v1beta1AdditionalMetadataSpec = &capsulev1beta1.AdditionalMetadataSpec{
|
||||
AdditionalLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
AdditionalAnnotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
var v1beta1AllowedListSpec = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: []string{"foo", "bar"},
|
||||
Regex: "^foo*",
|
||||
}
|
||||
var networkPolicies = []networkingv1.NetworkPolicySpec{
|
||||
{
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{
|
||||
{
|
||||
From: []networkingv1.NetworkPolicyPeer{
|
||||
{
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "tenant-resources",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PodSelector: &metav1.LabelSelector{},
|
||||
},
|
||||
{
|
||||
IPBlock: &networkingv1.IPBlock{
|
||||
CIDR: "192.168.0.0/12",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
var limitRanges = []corev1.LimitRangeSpec{
|
||||
{
|
||||
Limits: []corev1.LimitRangeItem{
|
||||
{
|
||||
Type: corev1.LimitTypePod,
|
||||
Min: map[corev1.ResourceName]resource.Quantity{
|
||||
corev1.ResourceCPU: resource.MustParse("50m"),
|
||||
corev1.ResourceMemory: resource.MustParse("5Mi"),
|
||||
},
|
||||
Max: map[corev1.ResourceName]resource.Quantity{
|
||||
corev1.ResourceCPU: resource.MustParse("1"),
|
||||
corev1.ResourceMemory: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
var resourceQuotas = []corev1.ResourceQuotaSpec{
|
||||
{
|
||||
Hard: map[corev1.ResourceName]resource.Quantity{
|
||||
corev1.ResourceLimitsCPU: resource.MustParse("8"),
|
||||
corev1.ResourceLimitsMemory: resource.MustParse("16Gi"),
|
||||
corev1.ResourceRequestsCPU: resource.MustParse("8"),
|
||||
corev1.ResourceRequestsMemory: resource.MustParse("16Gi"),
|
||||
},
|
||||
Scopes: []corev1.ResourceQuotaScope{
|
||||
corev1.ResourceQuotaScopeNotTerminating,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var v1beta1Tnt = capsulev1beta1.Tenant{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "alice",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "alice",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List", "Update", "Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "Nodes",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update", "Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update", "Delete"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "bob",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "jack",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "Nodes",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: "owner-foo",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: "owner-bar",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "IngressClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "system:serviceaccount:oil-production:default",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "Nodes",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "system:serviceaccount:gas-production:gas",
|
||||
ProxyOperations: []capsulev1beta1.ProxySettings{
|
||||
{
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"Update"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceQuota: &namespaceQuota,
|
||||
NamespacesMetadata: v1beta1AdditionalMetadataSpec,
|
||||
ServicesMetadata: v1beta1AdditionalMetadataSpec,
|
||||
StorageClasses: v1beta1AllowedListSpec,
|
||||
IngressClasses: v1beta1AllowedListSpec,
|
||||
IngressHostnames: v1beta1AllowedListSpec,
|
||||
ContainerRegistries: v1beta1AllowedListSpec,
|
||||
NodeSelector: nodeSelector,
|
||||
NetworkPolicies: &capsulev1beta1.NetworkPolicySpec{
|
||||
Items: networkPolicies,
|
||||
},
|
||||
LimitRanges: &capsulev1beta1.LimitRangesSpec{
|
||||
Items: limitRanges,
|
||||
},
|
||||
ResourceQuota: &capsulev1beta1.ResourceQuotaSpec{
|
||||
Items: resourceQuotas,
|
||||
},
|
||||
AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{
|
||||
{
|
||||
ClusterRoleName: "crds-rolebinding",
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "Group",
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: "system:authenticated",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{
|
||||
Allowed: []capsulev1beta1.AllowedIP{"192.168.0.1"},
|
||||
},
|
||||
ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"},
|
||||
PriorityClasses: &capsulev1beta1.AllowedListSpec{
|
||||
Exact: []string{"default"},
|
||||
Regex: "^tier-.*$",
|
||||
},
|
||||
EnableNodePorts: false,
|
||||
},
|
||||
Status: capsulev1beta1.TenantStatus{
|
||||
Size: 1,
|
||||
Namespaces: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
var v1alpha1Tnt = Tenant{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "alice",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
podAllowedImagePullPolicyAnnotation: "Always,IfNotPresent",
|
||||
enableNodePortsAnnotation: "false",
|
||||
podPriorityAllowedAnnotation: "default",
|
||||
podPriorityAllowedRegexAnnotation: "^tier-.*$",
|
||||
ownerGroupsAnnotation: "owner-foo,owner-bar",
|
||||
ownerUsersAnnotation: "bob,jack",
|
||||
ownerServiceAccountAnnotation: "system:serviceaccount:oil-production:default,system:serviceaccount:gas-production:gas",
|
||||
enableNodeUpdateAnnotation: "alice,system:serviceaccount:oil-production:default",
|
||||
enableNodeDeletionAnnotation: "alice,jack",
|
||||
enableStorageClassListingAnnotation: "bob,jack",
|
||||
enableStorageClassUpdateAnnotation: "alice,system:serviceaccount:gas-production:gas",
|
||||
enableStorageClassDeletionAnnotation: "alice,owner-bar",
|
||||
enableIngressClassListingAnnotation: "alice,owner-foo,owner-bar",
|
||||
enableIngressClassUpdateAnnotation: "alice,bob",
|
||||
enableIngressClassDeletionAnnotation: "alice,jack",
|
||||
},
|
||||
},
|
||||
Spec: TenantSpec{
|
||||
Owner: OwnerSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
NamespaceQuota: &namespaceQuota,
|
||||
NamespacesMetadata: v1alpha1AdditionalMetadataSpec,
|
||||
ServicesMetadata: v1alpha1AdditionalMetadataSpec,
|
||||
StorageClasses: v1alpha1AllowedListSpec,
|
||||
IngressClasses: v1alpha1AllowedListSpec,
|
||||
IngressHostnames: v1alpha1AllowedListSpec,
|
||||
ContainerRegistries: v1alpha1AllowedListSpec,
|
||||
NodeSelector: nodeSelector,
|
||||
NetworkPolicies: networkPolicies,
|
||||
LimitRanges: limitRanges,
|
||||
ResourceQuota: resourceQuotas,
|
||||
AdditionalRoleBindings: []AdditionalRoleBindingsSpec{
|
||||
{
|
||||
ClusterRoleName: "crds-rolebinding",
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "Group",
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: "system:authenticated",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExternalServiceIPs: &ExternalServiceIPsSpec{
|
||||
Allowed: []AllowedIP{"192.168.0.1"},
|
||||
},
|
||||
},
|
||||
Status: TenantStatus{
|
||||
Size: 1,
|
||||
Namespaces: []string{"foo", "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
return v1alpha1Tnt, v1beta1Tnt
|
||||
}
|
||||
|
||||
func TestConversionHub_ConvertTo(t *testing.T) {
|
||||
var v1beta1ConvertedTnt = capsulev1beta1.Tenant{}
|
||||
|
||||
v1alpha1Tnt, v1beta1tnt := generateTenantsSpecs()
|
||||
err := v1alpha1Tnt.ConvertTo(&v1beta1ConvertedTnt)
|
||||
if assert.NoError(t, err) {
|
||||
sort.Slice(v1beta1tnt.Spec.Owners, func(i, j int) bool {
|
||||
return v1beta1tnt.Spec.Owners[i].Name < v1beta1tnt.Spec.Owners[j].Name
|
||||
})
|
||||
sort.Slice(v1beta1ConvertedTnt.Spec.Owners, func(i, j int) bool {
|
||||
return v1beta1ConvertedTnt.Spec.Owners[i].Name < v1beta1ConvertedTnt.Spec.Owners[j].Name
|
||||
})
|
||||
|
||||
for _, owner := range v1beta1tnt.Spec.Owners {
|
||||
sort.Slice(owner.ProxyOperations, func(i, j int) bool {
|
||||
return owner.ProxyOperations[i].Kind < owner.ProxyOperations[j].Kind
|
||||
})
|
||||
}
|
||||
for _, owner := range v1beta1ConvertedTnt.Spec.Owners {
|
||||
sort.Slice(owner.ProxyOperations, func(i, j int) bool {
|
||||
return owner.ProxyOperations[i].Kind < owner.ProxyOperations[j].Kind
|
||||
})
|
||||
}
|
||||
assert.Equal(t, v1beta1tnt, v1beta1ConvertedTnt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConversionHub_ConvertFrom(t *testing.T) {
|
||||
var v1alpha1ConvertedTnt = Tenant{}
|
||||
v1alpha1Tnt, v1beta1tnt := generateTenantsSpecs()
|
||||
|
||||
err := v1alpha1ConvertedTnt.ConvertFrom(&v1beta1tnt)
|
||||
if assert.NoError(t, err) {
|
||||
assert.EqualValues(t, v1alpha1Tnt, v1alpha1ConvertedTnt)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package domain
|
||||
|
||||
type AllowedList interface {
|
||||
ExactMatch(value string) bool
|
||||
RegexMatch(value string) bool
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package domain
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
podPriorityAllowedAnnotation = "priorityclass.capsule.clastix.io/allowed"
|
||||
podPriorityAllowedRegexAnnotation = "priorityclass.capsule.clastix.io/allowed-regex"
|
||||
)
|
||||
|
||||
func NewPodPriority(object metav1.Object) (allowed *v1alpha1.AllowedListSpec) {
|
||||
annotations := object.GetAnnotations()
|
||||
|
||||
if v, ok := annotations[podPriorityAllowedAnnotation]; ok {
|
||||
allowed = &v1alpha1.AllowedListSpec{}
|
||||
allowed.Exact = strings.Split(v, ",")
|
||||
}
|
||||
|
||||
if v, ok := annotations[podPriorityAllowedRegexAnnotation]; ok {
|
||||
if _, err := regexp.Compile(v); err == nil {
|
||||
if allowed == nil {
|
||||
allowed = &v1alpha1.AllowedListSpec{}
|
||||
}
|
||||
allowed.Regex = v
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewRegistry(t *testing.T) {
|
||||
type tc struct {
|
||||
registry string
|
||||
repo string
|
||||
image string
|
||||
tag string
|
||||
}
|
||||
for name, tc := range map[string]tc{
|
||||
"docker.io/my-org/my-repo:v0.0.1": {
|
||||
registry: "docker.io",
|
||||
repo: "my-org",
|
||||
image: "my-repo",
|
||||
tag: "v0.0.1",
|
||||
},
|
||||
"unnamed/repository:1.2.3": {
|
||||
registry: "docker.io",
|
||||
repo: "unnamed",
|
||||
image: "repository",
|
||||
tag: "1.2.3",
|
||||
},
|
||||
"quay.io/clastix/capsule:v1.0.0": {
|
||||
registry: "quay.io",
|
||||
repo: "clastix",
|
||||
image: "capsule",
|
||||
tag: "v1.0.0",
|
||||
},
|
||||
"docker.io/redis:alpine": {
|
||||
registry: "docker.io",
|
||||
repo: "",
|
||||
image: "redis",
|
||||
tag: "alpine",
|
||||
},
|
||||
"nginx:alpine": {
|
||||
registry: "docker.io",
|
||||
repo: "",
|
||||
image: "nginx",
|
||||
tag: "alpine",
|
||||
},
|
||||
"nginx": {
|
||||
registry: "docker.io",
|
||||
repo: "",
|
||||
image: "nginx",
|
||||
tag: "latest",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewRegistry(name)
|
||||
assert.Equal(t, tc.registry, r.Registry())
|
||||
assert.Equal(t, tc.repo, r.Repository())
|
||||
assert.Equal(t, tc.image, r.Image())
|
||||
assert.Equal(t, tc.tag, r.Tag())
|
||||
})
|
||||
}
|
||||
}
|
||||
11
api/v1alpha1/external_service_ips.go
Normal file
11
api/v1alpha1/external_service_ips.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||
type AllowedIP string
|
||||
|
||||
type ExternalServiceIPsSpec struct {
|
||||
Allowed []AllowedIP `json:"allowed"`
|
||||
}
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the capsule.clastix.io v1alpha1 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
type IngressHostnamesList []string
|
||||
|
||||
func (hostnames IngressHostnamesList) Len() int {
|
||||
return len(hostnames)
|
||||
}
|
||||
|
||||
func (hostnames IngressHostnamesList) Swap(i, j int) {
|
||||
hostnames[i], hostnames[j] = hostnames[j], hostnames[i]
|
||||
}
|
||||
|
||||
func (hostnames IngressHostnamesList) Less(i, j int) bool {
|
||||
return hostnames[i] < hostnames[j]
|
||||
}
|
||||
|
||||
func (hostnames IngressHostnamesList) IsStringInList(value string) (ok bool) {
|
||||
sort.Sort(hostnames)
|
||||
i := sort.SearchStrings(hostnames, value)
|
||||
ok = i < hostnames.Len() && hostnames[i] == value
|
||||
return
|
||||
}
|
||||
17
api/v1alpha1/owner.go
Normal file
17
api/v1alpha1/owner.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// OwnerSpec defines tenant owner name and kind
|
||||
type OwnerSpec struct {
|
||||
Name string `json:"name"`
|
||||
Kind Kind `json:"kind"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=User;Group
|
||||
type Kind string
|
||||
|
||||
func (k Kind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
@@ -22,6 +9,13 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func (t *Tenant) IsCordoned() bool {
|
||||
if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tenant) IsFull() bool {
|
||||
// we don't have limits on assigned Namespaces
|
||||
if t.Spec.NamespaceQuota == nil {
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
|
||||
@@ -1,53 +1,22 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type AdditionalMetadata struct {
|
||||
AdditionalLabels map[string]string `json:"additionalLabels,omitempty"`
|
||||
AdditionalAnnotations map[string]string `json:"additionalAnnotations,omitempty"`
|
||||
}
|
||||
|
||||
type IngressHostnamesSpec struct {
|
||||
Allowed IngressHostnamesList `json:"allowed"`
|
||||
AllowedRegex string `json:"allowedRegex"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||
type AllowedIP string
|
||||
|
||||
type ExternalServiceIPs struct {
|
||||
Allowed []AllowedIP `json:"allowed"`
|
||||
}
|
||||
|
||||
// TenantSpec defines the desired state of Tenant
|
||||
type TenantSpec struct {
|
||||
Owner OwnerSpec `json:"owner"`
|
||||
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
NamespaceQuota *int32 `json:"namespaceQuota,omitempty"`
|
||||
NamespacesMetadata AdditionalMetadata `json:"namespacesMetadata,omitempty"`
|
||||
ServicesMetadata AdditionalMetadata `json:"servicesMetadata,omitempty"`
|
||||
NamespacesMetadata *AdditionalMetadataSpec `json:"namespacesMetadata,omitempty"`
|
||||
ServicesMetadata *AdditionalMetadataSpec `json:"servicesMetadata,omitempty"`
|
||||
StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"`
|
||||
IngressClasses *AllowedListSpec `json:"ingressClasses,omitempty"`
|
||||
IngressHostnames *AllowedListSpec `json:"ingressHostnames,omitempty"`
|
||||
@@ -56,27 +25,8 @@ type TenantSpec struct {
|
||||
NetworkPolicies []networkingv1.NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
LimitRanges []corev1.LimitRangeSpec `json:"limitRanges,omitempty"`
|
||||
ResourceQuota []corev1.ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
AdditionalRoleBindings []AdditionalRoleBindings `json:"additionalRoleBindings,omitempty"`
|
||||
ExternalServiceIPs *ExternalServiceIPs `json:"externalServiceIPs,omitempty"`
|
||||
}
|
||||
|
||||
type AdditionalRoleBindings struct {
|
||||
ClusterRoleName string `json:"clusterRoleName"`
|
||||
// kubebuilder:validation:Minimum=1
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
}
|
||||
|
||||
// OwnerSpec defines tenant owner name and kind
|
||||
type OwnerSpec struct {
|
||||
Name string `json:"name"`
|
||||
Kind Kind `json:"kind"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=User;Group
|
||||
type Kind string
|
||||
|
||||
func (k Kind) String() string {
|
||||
return string(k)
|
||||
AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
|
||||
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"`
|
||||
}
|
||||
|
||||
// TenantStatus defines the observed state of Tenant
|
||||
|
||||
21
api/v1alpha1/tenant_webhook.go
Normal file
21
api/v1alpha1/tenant_webhook.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
)
|
||||
|
||||
func (t *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error {
|
||||
certData, _ := ioutil.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt")
|
||||
if len(certData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ctrl.NewWebhookManagedBy(mgr).
|
||||
For(t).
|
||||
Complete()
|
||||
}
|
||||
@@ -1,20 +1,7 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
@@ -22,13 +9,13 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) {
|
||||
func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
|
||||
*out = *in
|
||||
if in.AdditionalLabels != nil {
|
||||
in, out := &in.AdditionalLabels, &out.AdditionalLabels
|
||||
@@ -46,32 +33,32 @@ func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) {
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadata.
|
||||
func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec.
|
||||
func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalMetadata)
|
||||
out := new(AdditionalMetadataSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalRoleBindings) DeepCopyInto(out *AdditionalRoleBindings) {
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) {
|
||||
*out = *in
|
||||
if in.Subjects != nil {
|
||||
in, out := &in.Subjects, &out.Subjects
|
||||
*out = make([]rbacv1.Subject, len(*in))
|
||||
*out = make([]v1.Subject, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindings.
|
||||
func (in *AdditionalRoleBindings) DeepCopy() *AdditionalRoleBindings {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec.
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalRoleBindings)
|
||||
out := new(AdditionalRoleBindingsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -175,7 +162,7 @@ func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec {
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalServiceIPs) DeepCopyInto(out *ExternalServiceIPs) {
|
||||
func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) {
|
||||
*out = *in
|
||||
if in.Allowed != nil {
|
||||
in, out := &in.Allowed, &out.Allowed
|
||||
@@ -184,51 +171,12 @@ func (in *ExternalServiceIPs) DeepCopyInto(out *ExternalServiceIPs) {
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPs.
|
||||
func (in *ExternalServiceIPs) DeepCopy() *ExternalServiceIPs {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExternalServiceIPs)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in IngressHostnamesList) DeepCopyInto(out *IngressHostnamesList) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(IngressHostnamesList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressHostnamesList.
|
||||
func (in IngressHostnamesList) DeepCopy() IngressHostnamesList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IngressHostnamesList)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressHostnamesSpec) DeepCopyInto(out *IngressHostnamesSpec) {
|
||||
*out = *in
|
||||
if in.Allowed != nil {
|
||||
in, out := &in.Allowed, &out.Allowed
|
||||
*out = make(IngressHostnamesList, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressHostnamesSpec.
|
||||
func (in *IngressHostnamesSpec) DeepCopy() *IngressHostnamesSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IngressHostnamesSpec)
|
||||
out := new(ExternalServiceIPsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -316,8 +264,16 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
in.NamespacesMetadata.DeepCopyInto(&out.NamespacesMetadata)
|
||||
in.ServicesMetadata.DeepCopyInto(&out.ServicesMetadata)
|
||||
if in.NamespacesMetadata != nil {
|
||||
in, out := &in.NamespacesMetadata, &out.NamespacesMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServicesMetadata != nil {
|
||||
in, out := &in.ServicesMetadata, &out.ServicesMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.StorageClasses != nil {
|
||||
in, out := &in.StorageClasses, &out.StorageClasses
|
||||
*out = new(AllowedListSpec)
|
||||
@@ -347,7 +303,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
}
|
||||
if in.NetworkPolicies != nil {
|
||||
in, out := &in.NetworkPolicies, &out.NetworkPolicies
|
||||
*out = make([]v1.NetworkPolicySpec, len(*in))
|
||||
*out = make([]networkingv1.NetworkPolicySpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -368,14 +324,14 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
}
|
||||
if in.AdditionalRoleBindings != nil {
|
||||
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
|
||||
*out = make([]AdditionalRoleBindings, len(*in))
|
||||
*out = make([]AdditionalRoleBindingsSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ExternalServiceIPs != nil {
|
||||
in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs
|
||||
*out = new(ExternalServiceIPs)
|
||||
*out = new(ExternalServiceIPsSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
9
api/v1beta1/additional_metadata.go
Normal file
9
api/v1beta1/additional_metadata.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
type AdditionalMetadataSpec struct {
|
||||
AdditionalLabels map[string]string `json:"additionalLabels,omitempty"`
|
||||
AdditionalAnnotations map[string]string `json:"additionalAnnotations,omitempty"`
|
||||
}
|
||||
12
api/v1beta1/additional_role_bindings.go
Normal file
12
api/v1beta1/additional_role_bindings.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import rbacv1 "k8s.io/api/rbac/v1"
|
||||
|
||||
type AdditionalRoleBindingsSpec struct {
|
||||
ClusterRoleName string `json:"clusterRoleName"`
|
||||
// kubebuilder:validation:Minimum=1
|
||||
Subjects []rbacv1.Subject `json:"subjects"`
|
||||
}
|
||||
33
api/v1beta1/allowed_list.go
Normal file
33
api/v1beta1/allowed_list.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AllowedListSpec struct {
|
||||
Exact []string `json:"allowed,omitempty"`
|
||||
Regex string `json:"allowedRegex,omitempty"`
|
||||
}
|
||||
|
||||
func (in *AllowedListSpec) ExactMatch(value string) (ok bool) {
|
||||
if len(in.Exact) > 0 {
|
||||
sort.SliceStable(in.Exact, func(i, j int) bool {
|
||||
return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j])
|
||||
})
|
||||
i := sort.SearchStrings(in.Exact, value)
|
||||
ok = i < len(in.Exact) && in.Exact[i] == value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (in AllowedListSpec) RegexMatch(value string) (ok bool) {
|
||||
if len(in.Regex) > 0 {
|
||||
ok = regexp.MustCompile(in.Regex).MatchString(value)
|
||||
}
|
||||
return
|
||||
}
|
||||
67
api/v1beta1/allowed_list_test.go
Normal file
67
api/v1beta1/allowed_list_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAllowedListSpec_ExactMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
In []string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
for _, tc := range []tc{
|
||||
{
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"foo", "bar", "bizz", "buzz"},
|
||||
[]string{"bing", "bong"},
|
||||
},
|
||||
{
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"one", "two", "three"},
|
||||
[]string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
nil,
|
||||
nil,
|
||||
[]string{"any", "value"},
|
||||
},
|
||||
} {
|
||||
a := AllowedListSpec{
|
||||
Exact: tc.In,
|
||||
}
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.ExactMatch(ok))
|
||||
}
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.ExactMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedListSpec_RegexMatch(t *testing.T) {
|
||||
type tc struct {
|
||||
Regex string
|
||||
True []string
|
||||
False []string
|
||||
}
|
||||
for _, tc := range []tc{
|
||||
{`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}},
|
||||
{``, nil, []string{"any", "value"}},
|
||||
} {
|
||||
a := AllowedListSpec{
|
||||
Regex: tc.Regex,
|
||||
}
|
||||
for _, ok := range tc.True {
|
||||
assert.True(t, a.RegexMatch(ok))
|
||||
}
|
||||
for _, ko := range tc.False {
|
||||
assert.False(t, a.RegexMatch(ko))
|
||||
}
|
||||
}
|
||||
}
|
||||
11
api/v1beta1/external_service_ips.go
Normal file
11
api/v1beta1/external_service_ips.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||
type AllowedIP string
|
||||
|
||||
type ExternalServiceIPsSpec struct {
|
||||
Allowed []AllowedIP `json:"allowed"`
|
||||
}
|
||||
23
api/v1beta1/groupversion_info.go
Normal file
23
api/v1beta1/groupversion_info.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package v1beta1 contains API Schema definitions for the capsule v1beta1 API group
|
||||
//+kubebuilder:object:generate=true
|
||||
//+groupName=capsule.clastix.io
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects
|
||||
GroupVersion = schema.GroupVersion{Group: "capsule.clastix.io", Version: "v1beta1"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
11
api/v1beta1/image_pull_policy.go
Normal file
11
api/v1beta1/image_pull_policy.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
// +kubebuilder:validation:Enum=Always;Never;IfNotPresent
|
||||
type ImagePullPolicySpec string
|
||||
|
||||
func (i ImagePullPolicySpec) String() string {
|
||||
return string(i)
|
||||
}
|
||||
10
api/v1beta1/limit_ranges.go
Normal file
10
api/v1beta1/limit_ranges.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
type LimitRangesSpec struct {
|
||||
Items []corev1.LimitRangeSpec `json:"items,omitempty"`
|
||||
}
|
||||
12
api/v1beta1/network_policy.go
Normal file
12
api/v1beta1/network_policy.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
)
|
||||
|
||||
type NetworkPolicySpec struct {
|
||||
Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"`
|
||||
}
|
||||
37
api/v1beta1/owner.go
Normal file
37
api/v1beta1/owner.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
// OwnerSpec defines tenant owner name and kind
|
||||
type OwnerSpec struct {
|
||||
Kind OwnerKind `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
ProxyOperations []ProxySettings `json:"proxySettings,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=User;Group;ServiceAccount
|
||||
type OwnerKind string
|
||||
|
||||
func (k OwnerKind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
|
||||
type ProxySettings struct {
|
||||
Kind ProxyServiceKind `json:"kind"`
|
||||
Operations []ProxyOperation `json:"operations"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=List;Update;Delete
|
||||
type ProxyOperation string
|
||||
|
||||
func (p ProxyOperation) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses
|
||||
type ProxyServiceKind string
|
||||
|
||||
func (p ProxyServiceKind) String() string {
|
||||
return string(p)
|
||||
}
|
||||
10
api/v1beta1/resource_quota.go
Normal file
10
api/v1beta1/resource_quota.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
type ResourceQuotaSpec struct {
|
||||
Items []corev1.ResourceQuotaSpec `json:"items,omitempty"`
|
||||
}
|
||||
25
api/v1beta1/tenant_annotations.go
Normal file
25
api/v1beta1/tenant_annotations.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes"
|
||||
AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp"
|
||||
AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes"
|
||||
AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp"
|
||||
AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries"
|
||||
AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp"
|
||||
)
|
||||
|
||||
func UsedQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/used-" + resource.String()
|
||||
}
|
||||
|
||||
func HardQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/hard-" + resource.String()
|
||||
}
|
||||
38
api/v1beta1/tenant_func.go
Normal file
38
api/v1beta1/tenant_func.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func (t *Tenant) IsCordoned() bool {
|
||||
if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tenant) IsFull() bool {
|
||||
// we don't have limits on assigned Namespaces
|
||||
if t.Spec.NamespaceQuota == nil {
|
||||
return false
|
||||
}
|
||||
return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceQuota)
|
||||
}
|
||||
|
||||
func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
|
||||
var l []string
|
||||
for _, ns := range namespaces {
|
||||
if ns.Status.Phase == corev1.NamespaceActive {
|
||||
l = append(l, ns.GetName())
|
||||
}
|
||||
}
|
||||
sort.Strings(l)
|
||||
|
||||
t.Status.Namespaces = l
|
||||
t.Status.Size = uint(len(l))
|
||||
}
|
||||
31
api/v1beta1/tenant_labels.go
Normal file
31
api/v1beta1/tenant_labels.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func GetTypeLabel(t runtime.Object) (label string, err error) {
|
||||
switch v := t.(type) {
|
||||
case *Tenant:
|
||||
return "capsule.clastix.io/tenant", nil
|
||||
case *corev1.LimitRange:
|
||||
return "capsule.clastix.io/limit-range", nil
|
||||
case *networkingv1.NetworkPolicy:
|
||||
return "capsule.clastix.io/network-policy", nil
|
||||
case *corev1.ResourceQuota:
|
||||
return "capsule.clastix.io/resource-quota", nil
|
||||
case *rbacv1.RoleBinding:
|
||||
return "capsule.clastix.io/role-binding", nil
|
||||
default:
|
||||
err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v)
|
||||
}
|
||||
return
|
||||
}
|
||||
20
api/v1beta1/tenant_status.go
Normal file
20
api/v1beta1/tenant_status.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
// +kubebuilder:validation:Enum=cordoned;active
|
||||
type tenantState string
|
||||
|
||||
const (
|
||||
TenantStateActive tenantState = "active"
|
||||
TenantStateCordoned tenantState = "cordoned"
|
||||
)
|
||||
|
||||
// TenantStatus defines the observed state of Tenant
|
||||
type TenantStatus struct {
|
||||
//+kubebuilder:default=active
|
||||
State tenantState `json:"state"`
|
||||
Size uint `json:"size"`
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
}
|
||||
67
api/v1beta1/tenant_types.go
Normal file
67
api/v1beta1/tenant_types.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// TenantSpec defines the desired state of Tenant
|
||||
type TenantSpec struct {
|
||||
Owners []OwnerSpec `json:"owners"`
|
||||
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
NamespaceQuota *int32 `json:"namespaceQuota,omitempty"`
|
||||
NamespacesMetadata *AdditionalMetadataSpec `json:"namespacesMetadata,omitempty"`
|
||||
ServicesMetadata *AdditionalMetadataSpec `json:"servicesMetadata,omitempty"`
|
||||
StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"`
|
||||
IngressClasses *AllowedListSpec `json:"ingressClasses,omitempty"`
|
||||
IngressHostnames *AllowedListSpec `json:"ingressHostnames,omitempty"`
|
||||
ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"`
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
NetworkPolicies *NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
LimitRanges *LimitRangesSpec `json:"limitRanges,omitempty"`
|
||||
ResourceQuota *ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
|
||||
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"`
|
||||
ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"`
|
||||
PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"`
|
||||
|
||||
//+kubebuilder:default=true
|
||||
EnableNodePorts bool `json:"enableNodePorts,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
//+kubebuilder:storageversion
|
||||
// +kubebuilder:resource:scope=Cluster,shortName=tnt
|
||||
// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant"
|
||||
// +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceQuota",description="The max amount of Namespaces can be created"
|
||||
// +kubebuilder:printcolumn:name="Namespace count",type="integer",JSONPath=".status.size",description="The total amount of Namespaces in use"
|
||||
// +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
|
||||
|
||||
// Tenant is the Schema for the tenants API
|
||||
type Tenant struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec TenantSpec `json:"spec,omitempty"`
|
||||
Status TenantStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Tenant) Hub() {}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
// TenantList contains a list of Tenant
|
||||
type TenantList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Tenant `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Tenant{}, &TenantList{})
|
||||
}
|
||||
392
api/v1beta1/zz_generated.deepcopy.go
Normal file
392
api/v1beta1/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,392 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
|
||||
*out = *in
|
||||
if in.AdditionalLabels != nil {
|
||||
in, out := &in.AdditionalLabels, &out.AdditionalLabels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.AdditionalAnnotations != nil {
|
||||
in, out := &in.AdditionalAnnotations, &out.AdditionalAnnotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec.
|
||||
func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalMetadataSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) {
|
||||
*out = *in
|
||||
if in.Subjects != nil {
|
||||
in, out := &in.Subjects, &out.Subjects
|
||||
*out = make([]v1.Subject, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec.
|
||||
func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalRoleBindingsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) {
|
||||
*out = *in
|
||||
if in.Exact != nil {
|
||||
in, out := &in.Exact, &out.Exact
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec.
|
||||
func (in *AllowedListSpec) DeepCopy() *AllowedListSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AllowedListSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) {
|
||||
*out = *in
|
||||
if in.Allowed != nil {
|
||||
in, out := &in.Allowed, &out.Allowed
|
||||
*out = make([]AllowedIP, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExternalServiceIPsSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) {
|
||||
*out = *in
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]corev1.LimitRangeSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec.
|
||||
func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LimitRangesSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) {
|
||||
*out = *in
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]networkingv1.NetworkPolicySpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec.
|
||||
func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NetworkPolicySpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) {
|
||||
*out = *in
|
||||
if in.ProxyOperations != nil {
|
||||
in, out := &in.ProxyOperations, &out.ProxyOperations
|
||||
*out = make([]ProxySettings, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerSpec.
|
||||
func (in *OwnerSpec) DeepCopy() *OwnerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProxySettings) DeepCopyInto(out *ProxySettings) {
|
||||
*out = *in
|
||||
if in.Operations != nil {
|
||||
in, out := &in.Operations, &out.Operations
|
||||
*out = make([]ProxyOperation, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxySettings.
|
||||
func (in *ProxySettings) DeepCopy() *ProxySettings {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProxySettings)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) {
|
||||
*out = *in
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]corev1.ResourceQuotaSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec.
|
||||
func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResourceQuotaSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Tenant) DeepCopyInto(out *Tenant) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tenant.
|
||||
func (in *Tenant) DeepCopy() *Tenant {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Tenant)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Tenant) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantList) DeepCopyInto(out *TenantList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Tenant, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantList.
|
||||
func (in *TenantList) DeepCopy() *TenantList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TenantList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = *in
|
||||
if in.Owners != nil {
|
||||
in, out := &in.Owners, &out.Owners
|
||||
*out = make([]OwnerSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.NamespaceQuota != nil {
|
||||
in, out := &in.NamespaceQuota, &out.NamespaceQuota
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.NamespacesMetadata != nil {
|
||||
in, out := &in.NamespacesMetadata, &out.NamespacesMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServicesMetadata != nil {
|
||||
in, out := &in.ServicesMetadata, &out.ServicesMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.StorageClasses != nil {
|
||||
in, out := &in.StorageClasses, &out.StorageClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.IngressClasses != nil {
|
||||
in, out := &in.IngressClasses, &out.IngressClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.IngressHostnames != nil {
|
||||
in, out := &in.IngressHostnames, &out.IngressHostnames
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ContainerRegistries != nil {
|
||||
in, out := &in.ContainerRegistries, &out.ContainerRegistries
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.NetworkPolicies != nil {
|
||||
in, out := &in.NetworkPolicies, &out.NetworkPolicies
|
||||
*out = new(NetworkPolicySpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.LimitRanges != nil {
|
||||
in, out := &in.LimitRanges, &out.LimitRanges
|
||||
*out = new(LimitRangesSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ResourceQuota != nil {
|
||||
in, out := &in.ResourceQuota, &out.ResourceQuota
|
||||
*out = new(ResourceQuotaSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AdditionalRoleBindings != nil {
|
||||
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
|
||||
*out = make([]AdditionalRoleBindingsSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ExternalServiceIPs != nil {
|
||||
in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs
|
||||
*out = new(ExternalServiceIPsSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ImagePullPolicies != nil {
|
||||
in, out := &in.ImagePullPolicies, &out.ImagePullPolicies
|
||||
*out = make([]ImagePullPolicySpec, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.PriorityClasses != nil {
|
||||
in, out := &in.PriorityClasses, &out.PriorityClasses
|
||||
*out = new(AllowedListSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec.
|
||||
func (in *TenantSpec) DeepCopy() *TenantSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantStatus) DeepCopyInto(out *TenantStatus) {
|
||||
*out = *in
|
||||
if in.Namespaces != nil {
|
||||
in, out := &in.Namespaces, &out.Namespaces
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus.
|
||||
func (in *TenantStatus) DeepCopy() *TenantStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -60,6 +60,7 @@ Here the values you can override:
|
||||
|
||||
Parameter | Description | Default
|
||||
--- | --- | ---
|
||||
`manager.hostNetwork` | Specifies if the container should be started in `hostNetwork` mode. | `false`
|
||||
`manager.options.logLevel` | Set the log verbosity of the controller with a value from 1 to 10.| `4`
|
||||
`manager.options.forceTenantPrefix` | Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash | `false`
|
||||
`manager.options.capsuleUserGroup` | Override the Capsule user group | `capsule.clastix.io`
|
||||
@@ -88,6 +89,9 @@ Parameter | Description | Default
|
||||
`replicaCount` | Set the replica count for Capsule pod. | `1`
|
||||
`affinity` | Set affinity rules for the Capsule pod. | `{}`
|
||||
`podSecurityPolicy.enabled` | Specify if a Pod Security Policy must be created. | `false`
|
||||
`serviceMonitor.enabled` | Specify if a Service Monitor must be created. | `false`
|
||||
`serviceMonitor.serviceAccount.name` | Specify Service Account name for metrics scrape. | `capsule`
|
||||
`serviceMonitor.serviceAccount.namespace` | Specify Service Account namespace for metrics scrape. | `capsule-system`
|
||||
|
||||
## Created resources
|
||||
|
||||
@@ -107,8 +111,10 @@ This Helm Chart cretes the following Kubernetes resources in the release namespa
|
||||
And optionally, depending on the values set:
|
||||
|
||||
* Capsule ServiceAccount
|
||||
* Capsule Service Monitor
|
||||
* PodSecurityPolicy
|
||||
* RBAC ClusterRole and RoleBinding for pod security policy
|
||||
* RBAC Role and Rolebinding for metrics scrape
|
||||
|
||||
## Notes on installing Custom Resource Definitions with Helm3
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,5 +9,5 @@ spec:
|
||||
- {{ . }}
|
||||
{{- end}}
|
||||
protectedNamespaceRegex: {{ .Values.manager.options.protectedNamespaceRegex | quote }}
|
||||
allowTenantIngressHostnamesCollision: {{ .Values.manager.options.allowIngressHostnameCollision }}
|
||||
allowIngressHostnameCollision: {{ .Values.manager.options.allowTenantIngressHostnamesCollision }}
|
||||
allowTenantIngressHostnamesCollision: {{ .Values.manager.options.allowTenantIngressHostnamesCollision }}
|
||||
allowIngressHostnameCollision: {{ .Values.manager.options.allowIngressHostnameCollision }}
|
||||
|
||||
@@ -23,6 +23,9 @@ spec:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "capsule.serviceAccountName" . }}
|
||||
{{- if .Values.manager.hostNetwork }}
|
||||
hostNetwork: true
|
||||
{{- end }}
|
||||
priorityClassName: {{ .Values.priorityClassName }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
|
||||
42
charts/capsule/templates/metrics-rbac.yaml
Normal file
42
charts/capsule/templates/metrics-rbac.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- if .Values.serviceMonitor.labels }}
|
||||
{{- toYaml .Values.serviceMonitor.labels | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "capsule.fullname" . }}-metrics-role
|
||||
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- if .Values.serviceMonitor.labels }}
|
||||
{{- toYaml .Values.serviceMonitor.labels | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "capsule.fullname" . }}-metrics-rolebinding
|
||||
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ include "capsule.fullname" . }}-metrics-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ .Values.serviceMonitor.serviceAccount.name }}
|
||||
namespace: {{ .Values.serviceMonitor.serviceAccount.namespace | default .Release.Namespace }}
|
||||
{{- end }}
|
||||
@@ -6,29 +6,30 @@ metadata:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /mutate-v1-namespace-owner-reference
|
||||
path: /namespace-owner-reference
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
matchPolicy: Equivalent
|
||||
name: owner.namespace.capsule.clastix.io
|
||||
namespaceSelector: {}
|
||||
objectSelector: {}
|
||||
reinvocationPolicy: Never
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- namespaces
|
||||
scope: '*'
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- namespaces
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.mutatingWebhooksTimeoutSeconds }}
|
||||
|
||||
23
charts/capsule/templates/servicemonitor.yaml
Normal file
23
charts/capsule/templates/servicemonitor.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ include "capsule.fullname" . }}-monitor
|
||||
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
{{- if .Values.serviceMonitor.labels }}
|
||||
{{- toYaml .Values.serviceMonitor.labels | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
endpoints:
|
||||
- interval: 15s
|
||||
port: metrics
|
||||
path: /metrics
|
||||
jobLabel: app.kubernetes.io/name
|
||||
selector:
|
||||
matchLabels: {{ include "capsule.labels" . | nindent 6 }}
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
@@ -6,133 +6,143 @@ metadata:
|
||||
{{- include "capsule.labels" . | nindent 4 }}
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-ingress
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: ingress-v1beta1.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
- extensions
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- ingresses
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-ingress
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: ingress-v1.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- ingresses
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validate-v1-namespace-quota
|
||||
path: /cordoning
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: quota.namespace.capsule.clastix.io
|
||||
matchPolicy: Equivalent
|
||||
name: cordoning.tenant.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- '*'
|
||||
apiVersions:
|
||||
- '*'
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- '*'
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /ingresses
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Equivalent
|
||||
name: ingress.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
- extensions
|
||||
apiVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- ingresses
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /namespaces
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Equivalent
|
||||
name: namespaces.capsule.clastix.io
|
||||
namespaceSelector: {}
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- namespaces
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- namespaces
|
||||
scope: '*'
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-v1-network-policy
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: validating.network-policy.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- networkpolicies
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-podpriority
|
||||
failurePolicy: Ignore
|
||||
name: podpriority.capsule.clastix.io
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /networkpolicies
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Equivalent
|
||||
name: networkpolicies.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- networkpolicies
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /pods
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: pods.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
@@ -147,146 +157,94 @@ webhooks:
|
||||
- CREATE
|
||||
resources:
|
||||
- pods
|
||||
sideEffects: NoneOnDryRun
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-v1-pvc
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: pvc.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- persistentvolumeclaims
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-v1-tenant
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: tenant.capsule.clastix.io
|
||||
namespaceSelector: {}
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- capsule.clastix.io
|
||||
apiVersions:
|
||||
- v1alpha1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- tenants
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-v1-namespace-tenant-prefix
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: prefix.namespace.capsule.clastix.io
|
||||
namespaceSelector: {}
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- namespaces
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-v1-registry
|
||||
port: 443
|
||||
failurePolicy: Ignore
|
||||
matchPolicy: Exact
|
||||
name: pod.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- pods
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validating-external-service-ips
|
||||
port: 443
|
||||
namespace: capsule-system
|
||||
path: /persistentvolumeclaims
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: validating-external-service-ips.capsule.clastix.io
|
||||
name: pvc.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- services
|
||||
scope: '*'
|
||||
sideEffects: NoneOnDryRun
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- persistentvolumeclaims
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /services
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: services.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- services
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /tenants
|
||||
port: 443
|
||||
failurePolicy: Fail
|
||||
matchPolicy: Exact
|
||||
name: tenants.capsule.clastix.io
|
||||
namespaceSelector: {}
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- capsule.clastix.io
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- tenants
|
||||
scope: '*'
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
|
||||
@@ -7,6 +7,14 @@ manager:
|
||||
repository: quay.io/clastix/capsule
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ''
|
||||
|
||||
# Specifies if the container should be started in hostNetwork mode.
|
||||
#
|
||||
# Required for use in some managed kubernetes clusters (such as AWS EKS) with custom
|
||||
# CNI (such as calico), because control-plane managed by AWS cannot communicate
|
||||
# with pods' IP CIDR and admission webhooks are not working
|
||||
hostNetwork: false
|
||||
|
||||
# Additional Capsule options
|
||||
options:
|
||||
logLevel: '4'
|
||||
@@ -33,9 +41,9 @@ manager:
|
||||
memory: 128Mi
|
||||
jobs:
|
||||
image:
|
||||
repository: bitnami/kubectl
|
||||
repository: quay.io/clastix/kubectl
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "1.18"
|
||||
tag: "v1.20.7"
|
||||
mutatingWebhooksTimeoutSeconds: 30
|
||||
validatingWebhooksTimeoutSeconds: 30
|
||||
imagePullSecrets: []
|
||||
@@ -56,3 +64,15 @@ replicaCount: 1
|
||||
affinity: {}
|
||||
podSecurityPolicy:
|
||||
enabled: false
|
||||
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
# Install the ServiceMonitor into a different Namespace, as the monitoring stack one (default: the release one)
|
||||
namespace:
|
||||
# Assign additional labels according to Prometheus' serviceMonitorSelector matching labels
|
||||
labels: {}
|
||||
annotations: {}
|
||||
matchLabels: {}
|
||||
serviceAccount:
|
||||
name: capsule
|
||||
namespace: capsule-system
|
||||
|
||||
@@ -563,6 +563,611 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: false
|
||||
subresources:
|
||||
status: {}
|
||||
- additionalPrinterColumns:
|
||||
- description: The actual state of the Tenant
|
||||
jsonPath: .status.state
|
||||
name: State
|
||||
type: string
|
||||
- description: The max amount of Namespaces can be created
|
||||
jsonPath: .spec.namespaceQuota
|
||||
name: Namespace quota
|
||||
type: integer
|
||||
- description: The total amount of Namespaces in use
|
||||
jsonPath: .status.size
|
||||
name: Namespace count
|
||||
type: integer
|
||||
- description: Node Selector applied to Pods
|
||||
jsonPath: .spec.nodeSelector
|
||||
name: Node selector
|
||||
type: string
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1beta1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Tenant is the Schema for the tenants API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: TenantSpec defines the desired state of Tenant
|
||||
properties:
|
||||
additionalRoleBindings:
|
||||
items:
|
||||
properties:
|
||||
clusterRoleName:
|
||||
type: string
|
||||
subjects:
|
||||
description: kubebuilder:validation:Minimum=1
|
||||
items:
|
||||
description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names.
|
||||
properties:
|
||||
apiGroup:
|
||||
description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error.
|
||||
type: string
|
||||
name:
|
||||
description: Name of the object being referenced.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error.
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- clusterRoleName
|
||||
- subjects
|
||||
type: object
|
||||
type: array
|
||||
containerRegistries:
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
type: string
|
||||
type: object
|
||||
enableNodePorts:
|
||||
default: true
|
||||
type: boolean
|
||||
externalServiceIPs:
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- allowed
|
||||
type: object
|
||||
imagePullPolicies:
|
||||
items:
|
||||
enum:
|
||||
- Always
|
||||
- Never
|
||||
- IfNotPresent
|
||||
type: string
|
||||
type: array
|
||||
ingressClasses:
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
type: string
|
||||
type: object
|
||||
ingressHostnames:
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
type: string
|
||||
type: object
|
||||
limitRanges:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
description: LimitRangeSpec defines a min/max usage limit for resources that match on kind.
|
||||
properties:
|
||||
limits:
|
||||
description: Limits is the list of LimitRangeItem objects that are enforced.
|
||||
items:
|
||||
description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
|
||||
properties:
|
||||
default:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Default resource requirement limit value by resource name if resource limit is omitted.
|
||||
type: object
|
||||
defaultRequest:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
|
||||
type: object
|
||||
max:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Max usage constraints on this kind by resource name.
|
||||
type: object
|
||||
maxLimitRequestRatio:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.
|
||||
type: object
|
||||
min:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Min usage constraints on this kind by resource name.
|
||||
type: object
|
||||
type:
|
||||
description: Type of resource that this limit applies to.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- limits
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
namespaceQuota:
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
namespacesMetadata:
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
additionalLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
networkPolicies:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
description: NetworkPolicySpec provides the specification of a NetworkPolicy
|
||||
properties:
|
||||
egress:
|
||||
description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
|
||||
items:
|
||||
description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8
|
||||
properties:
|
||||
ports:
|
||||
description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
|
||||
items:
|
||||
description: NetworkPolicyPort describes a port to allow traffic on
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers.
|
||||
x-kubernetes-int-or-string: true
|
||||
protocol:
|
||||
default: TCP
|
||||
description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
to:
|
||||
description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
|
||||
items:
|
||||
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
|
||||
properties:
|
||||
ipBlock:
|
||||
description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
|
||||
properties:
|
||||
cidr:
|
||||
description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
|
||||
type: string
|
||||
except:
|
||||
description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- cidr
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
podSelector:
|
||||
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
ingress:
|
||||
description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
|
||||
items:
|
||||
description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from.
|
||||
properties:
|
||||
from:
|
||||
description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
|
||||
items:
|
||||
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
|
||||
properties:
|
||||
ipBlock:
|
||||
description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
|
||||
properties:
|
||||
cidr:
|
||||
description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
|
||||
type: string
|
||||
except:
|
||||
description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- cidr
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
podSelector:
|
||||
description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
ports:
|
||||
description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
|
||||
items:
|
||||
description: NetworkPolicyPort describes a port to allow traffic on
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers.
|
||||
x-kubernetes-int-or-string: true
|
||||
protocol:
|
||||
default: TCP
|
||||
description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
podSelector:
|
||||
description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
policyTypes:
|
||||
description: List of rule types that the NetworkPolicy relates to. Valid options are "Ingress", "Egress", or "Ingress,Egress". If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
|
||||
items:
|
||||
description: Policy Type string describes the NetworkPolicy type This type is beta-level in 1.8
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- podSelector
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
nodeSelector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
owners:
|
||||
items:
|
||||
description: OwnerSpec defines tenant owner name and kind
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- User
|
||||
- Group
|
||||
- ServiceAccount
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
proxySettings:
|
||||
items:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- Nodes
|
||||
- StorageClasses
|
||||
- IngressClasses
|
||||
type: string
|
||||
operations:
|
||||
items:
|
||||
enum:
|
||||
- List
|
||||
- Update
|
||||
- Delete
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- kind
|
||||
- operations
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
priorityClasses:
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
type: string
|
||||
type: object
|
||||
resourceQuotas:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
|
||||
properties:
|
||||
hard:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/'
|
||||
type: object
|
||||
scopeSelector:
|
||||
description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: A list of scope selector requirements by scope of the resources.
|
||||
items:
|
||||
description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
|
||||
properties:
|
||||
operator:
|
||||
description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
|
||||
type: string
|
||||
scopeName:
|
||||
description: The name of the scope that the selector applies to.
|
||||
type: string
|
||||
values:
|
||||
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- operator
|
||||
- scopeName
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
scopes:
|
||||
description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
|
||||
items:
|
||||
description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
servicesMetadata:
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
additionalLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
storageClasses:
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- owners
|
||||
type: object
|
||||
status:
|
||||
description: TenantStatus defines the observed state of Tenant
|
||||
properties:
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
size:
|
||||
type: integer
|
||||
state:
|
||||
default: active
|
||||
enum:
|
||||
- cordoned
|
||||
- active
|
||||
type: string
|
||||
required:
|
||||
- size
|
||||
- state
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
|
||||
@@ -9,3 +9,6 @@ resources:
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
|
||||
patchesStrategicMerge:
|
||||
- patches/webhook_in_tenants.yaml
|
||||
|
||||
@@ -4,13 +4,15 @@ nameReference:
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- kind: CustomResourceDefinition
|
||||
version: v1
|
||||
group: apiextensions.k8s.io
|
||||
path: spec/conversion/webhookClientConfig/service/name
|
||||
path: spec/conversion/webhook/clientConfig/service/name
|
||||
|
||||
namespace:
|
||||
- kind: CustomResourceDefinition
|
||||
version: v1
|
||||
group: apiextensions.k8s.io
|
||||
path: spec/conversion/webhookClientConfig/service/namespace
|
||||
path: spec/conversion/webhook/clientConfig/service/namespace
|
||||
create: false
|
||||
|
||||
varReference:
|
||||
|
||||
17
config/crd/patches/webhook_in_tenants.yaml
Normal file
17
config/crd/patches/webhook_in_tenants.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# The following patch enables a conversion webhook for the CRD
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tenants.capsule.clastix.io
|
||||
spec:
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
clientConfig:
|
||||
service:
|
||||
namespace: system
|
||||
name: webhook-service
|
||||
path: /convert
|
||||
conversionReviewVersions:
|
||||
- v1alpha1
|
||||
- v1beta1
|
||||
921
config/grafana/dashboard.json
Normal file
921
config/grafana/dashboard.json
Normal file
@@ -0,0 +1,921 @@
|
||||
{
|
||||
"__inputs": [
|
||||
{
|
||||
"name": "DS_PROMETHEUS",
|
||||
"label": "prometheus",
|
||||
"description": "",
|
||||
"type": "datasource",
|
||||
"pluginId": "prometheus",
|
||||
"pluginName": "Prometheus"
|
||||
}
|
||||
],
|
||||
"__requires": [
|
||||
{
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "8.0.1"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "heatmap",
|
||||
"name": "Heatmap",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "datasource",
|
||||
"id": "prometheus",
|
||||
"name": "Prometheus",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "stat",
|
||||
"name": "Stat",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "timeseries",
|
||||
"name": "Time series",
|
||||
"version": ""
|
||||
}
|
||||
],
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1624217784640,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"cards": {
|
||||
"cardPadding": null,
|
||||
"cardRound": null
|
||||
},
|
||||
"color": {
|
||||
"cardColor": "#b4ff00",
|
||||
"colorScale": "sqrt",
|
||||
"colorScheme": "interpolateOranges",
|
||||
"exponent": 0.5,
|
||||
"mode": "spectrum"
|
||||
},
|
||||
"dataFormat": "timeseries",
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"heatmap": {},
|
||||
"hideZeroBuckets": false,
|
||||
"highlightCards": true,
|
||||
"id": 21,
|
||||
"legend": {
|
||||
"show": false
|
||||
},
|
||||
"maxPerRow": 2,
|
||||
"pluginVersion": "8.0.1",
|
||||
"repeat": "controller_name",
|
||||
"repeatDirection": "h",
|
||||
"reverseYBuckets": false,
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "sum by (le) (increase(workqueue_work_duration_seconds_bucket{service=\"capsule-controller-manager-metrics-service\",name=\"$controller_name\"}[1m]))",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "$controller_name reconcile latency",
|
||||
"tooltip": {
|
||||
"show": true,
|
||||
"showHistogram": false
|
||||
},
|
||||
"type": "heatmap",
|
||||
"xAxis": {
|
||||
"show": true
|
||||
},
|
||||
"xBucketNumber": null,
|
||||
"xBucketSize": null,
|
||||
"yAxis": {
|
||||
"decimals": null,
|
||||
"format": "ms",
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true,
|
||||
"splitFactor": null
|
||||
},
|
||||
"yBucketBound": "auto",
|
||||
"yBucketNumber": null,
|
||||
"yBucketSize": null
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"id": 17,
|
||||
"panels": [],
|
||||
"title": "Requests",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 17
|
||||
},
|
||||
"id": 22,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "8.0.1",
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"200\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "200",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"201\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "201",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "E"
|
||||
},
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"403\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "403",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"409\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "409",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"500\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "500",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "D"
|
||||
}
|
||||
],
|
||||
"title": "Rate & Errors",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 17
|
||||
},
|
||||
"id": 19,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"text": {},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "8.0.1",
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"200\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "200",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"201\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "201",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"403\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "403",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"409\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "409",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "D"
|
||||
},
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(rest_client_requests_total{service=\"capsule-controller-manager-metrics-service\",code=\"500\"}[30s]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "500",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "E"
|
||||
}
|
||||
],
|
||||
"title": "Rate & Errors",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"cards": {
|
||||
"cardPadding": null,
|
||||
"cardRound": null
|
||||
},
|
||||
"color": {
|
||||
"cardColor": "#b4ff00",
|
||||
"colorScale": "sqrt",
|
||||
"colorScheme": "interpolateOranges",
|
||||
"exponent": 0.5,
|
||||
"mode": "spectrum"
|
||||
},
|
||||
"dataFormat": "timeseries",
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 28
|
||||
},
|
||||
"heatmap": {},
|
||||
"hideZeroBuckets": false,
|
||||
"highlightCards": true,
|
||||
"id": 20,
|
||||
"legend": {
|
||||
"show": false
|
||||
},
|
||||
"pluginVersion": "8.0.1",
|
||||
"reverseYBuckets": false,
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "sum by (le) (increase(rest_client_request_latency_seconds_bucket{service=\"capsule-controller-manager-metrics-service\"}[1m]))",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{le}}",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Client Operator Latency",
|
||||
"tooltip": {
|
||||
"show": true,
|
||||
"showHistogram": false
|
||||
},
|
||||
"type": "heatmap",
|
||||
"xAxis": {
|
||||
"show": true
|
||||
},
|
||||
"xBucketNumber": null,
|
||||
"xBucketSize": null,
|
||||
"yAxis": {
|
||||
"decimals": null,
|
||||
"format": "ms",
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true,
|
||||
"splitFactor": null
|
||||
},
|
||||
"yBucketBound": "auto",
|
||||
"yBucketNumber": null,
|
||||
"yBucketSize": null
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 35
|
||||
},
|
||||
"id": 12,
|
||||
"panels": [],
|
||||
"title": "Saturation",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 9,
|
||||
"x": 0,
|
||||
"y": 36
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "rate(process_cpu_seconds_total{service=\"capsule-controller-manager-metrics-service\"}[1m])",
|
||||
"interval": "",
|
||||
"legendFormat": "{{pod}}",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "CPU Usage",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 1,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "smooth",
|
||||
"lineStyle": {
|
||||
"fill": "solid"
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "decbytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 9,
|
||||
"x": 9,
|
||||
"y": 36
|
||||
},
|
||||
"id": 24,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "process_resident_memory_bytes{service=\"capsule-controller-manager-metrics-service\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "{{pod}}",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Memory Usage",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 3,
|
||||
"x": 18,
|
||||
"y": 36
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"text": {
|
||||
"valueSize": 80
|
||||
},
|
||||
"textMode": "value_and_name"
|
||||
},
|
||||
"pluginVersion": "8.0.1",
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(up{service=\"capsule-controller-manager-metrics-service\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "Active Controllers",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Stats",
|
||||
"transparent": true,
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 44
|
||||
},
|
||||
"id": 10,
|
||||
"title": "Workqueue",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "service time to complete needed actions",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 45
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"text": {},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "8.0.1",
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "histogram_quantile(0.99, sum(rate(workqueue_queue_duration_seconds_bucket{service=\"capsule-controller-manager-metrics-service\"}[1m])) by (instance, name, le))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Workqueue latency",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "number of actions per unit time",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 45
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"text": {},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "8.0.1",
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": true,
|
||||
"expr": "sum(rate(workqueue_adds_total{service=\"capsule-controller-manager-metrics-service\"}[1m])) by (instance, name)",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Workqueue rate",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "number of actions waiting in the queue to be performed",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"x": 16,
|
||||
"y": 45
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"text": {},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "8.0.1",
|
||||
"targets": [
|
||||
{
|
||||
"exemplar": false,
|
||||
"expr": "sum(rate(workqueue_depth{service=\"capsule-controller-manager-metrics-service\"}[1m])) by (instance, name)",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{name}}",
|
||||
"queryType": "randomWalk",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Workqueue depth",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"refresh": "1m",
|
||||
"schemaVersion": 30,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"allValue": null,
|
||||
"current": {},
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"definition": "label_values(workqueue_work_duration_seconds_bucket{service=\"capsule-controller-manager-metrics-service\"},name)",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"multi": true,
|
||||
"name": "controller_name",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(workqueue_work_duration_seconds_bucket{service=\"capsule-controller-manager-metrics-service\"},name)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-3h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "capsule",
|
||||
"uid": "nEFvaoR7z",
|
||||
"version": 28
|
||||
}
|
||||
@@ -7,4 +7,4 @@ kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: quay.io/clastix/capsule
|
||||
newTag: v0.0.5
|
||||
newTag: v0.1.0-rc2
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
resources:
|
||||
- monitor.yaml
|
||||
- role.yaml
|
||||
- rolebinding.yaml
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
|
||||
# Prometheus Monitor Service (Metrics)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
name: controller-manager-metrics-monitor
|
||||
name: capsule-monitor
|
||||
namespace: system
|
||||
spec:
|
||||
endpoints:
|
||||
- path: /metrics
|
||||
- interval: 15s
|
||||
path: /metrics
|
||||
port: metrics
|
||||
jobLabel: controller-manager
|
||||
namespaceSelector:
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: controller-manager
|
||||
|
||||
18
config/prometheus/role.yaml
Normal file
18
config/prometheus/role.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
name: capsule-metrics-role
|
||||
namespace: capsule-system
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
15
config/prometheus/rolebinding.yaml
Normal file
15
config/prometheus/rolebinding.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
name: capsule-metrics-rolebinding
|
||||
namespace: system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: capsule-metrics-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: capsule
|
||||
namespace: capsule-system
|
||||
7
config/samples/capsule_v1beta1_tenant.yaml
Normal file
7
config/samples/capsule_v1beta1_tenant.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: capsule.clastix.io/v1beta1
|
||||
kind: Tenant
|
||||
metadata:
|
||||
name: tenant-sample
|
||||
spec:
|
||||
# Add fields here
|
||||
foo: bar
|
||||
@@ -12,7 +12,7 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /mutate-v1-namespace-owner-reference
|
||||
path: /namespace-owner-reference
|
||||
failurePolicy: Fail
|
||||
name: owner.namespace.capsule.clastix.io
|
||||
rules:
|
||||
@@ -39,20 +39,20 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-ingress
|
||||
path: /cordoning
|
||||
failurePolicy: Fail
|
||||
name: ingress-v1beta1.capsule.clastix.io
|
||||
name: cordoning.tenant.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
- extensions
|
||||
- '*'
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
- '*'
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- ingresses
|
||||
- '*'
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
@@ -60,13 +60,15 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-ingress
|
||||
path: /ingresses
|
||||
failurePolicy: Fail
|
||||
name: ingress-v1.capsule.clastix.io
|
||||
name: ingress.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
- extensions
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
@@ -80,9 +82,9 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validate-v1-namespace-quota
|
||||
path: /namespaces
|
||||
failurePolicy: Fail
|
||||
name: quota.namespace.capsule.clastix.io
|
||||
name: namespaces.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
@@ -90,6 +92,8 @@ webhooks:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- namespaces
|
||||
sideEffects: None
|
||||
@@ -99,16 +103,15 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-network-policy
|
||||
path: /networkpolicies
|
||||
failurePolicy: Fail
|
||||
name: validating.network-policy.capsule.clastix.io
|
||||
name: networkpolicies.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
@@ -120,9 +123,9 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-podpriority
|
||||
failurePolicy: Ignore
|
||||
name: podpriority.capsule.clastix.io
|
||||
path: /pods
|
||||
failurePolicy: Fail
|
||||
name: pods.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
@@ -139,7 +142,7 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-pvc
|
||||
path: /persistentvolumeclaims
|
||||
failurePolicy: Fail
|
||||
name: pvc.capsule.clastix.io
|
||||
rules:
|
||||
@@ -158,28 +161,9 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-registry
|
||||
failurePolicy: Ignore
|
||||
name: pod.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- pods
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-external-service-ips
|
||||
path: /services
|
||||
failurePolicy: Fail
|
||||
name: validating-external-service-ips.capsule.clastix.io
|
||||
name: services.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
@@ -197,36 +181,18 @@ webhooks:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-tenant
|
||||
path: /tenants
|
||||
failurePolicy: Fail
|
||||
name: tenant.capsule.clastix.io
|
||||
name: tenants.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- capsule.clastix.io
|
||||
apiVersions:
|
||||
- v1alpha1
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- tenants
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-namespace-tenant-prefix
|
||||
failurePolicy: Fail
|
||||
name: prefix.namespace.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- namespaces
|
||||
sideEffects: None
|
||||
|
||||
@@ -35,8 +35,20 @@
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
- op: add
|
||||
path: /webhooks/7/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
path: /webhooks/0/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/1/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/3/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/4/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/5/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/6/rules/0/scope
|
||||
value: Namespaced
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package rbac
|
||||
|
||||
@@ -28,7 +15,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
"github.com/clastix/capsule/pkg/configuration"
|
||||
)
|
||||
|
||||
@@ -67,7 +54,7 @@ func forOptionPerInstanceName(instanceName string) builder.ForOption {
|
||||
|
||||
func (r *Manager) SetupWithManager(mgr ctrl.Manager, configurationName string) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&v1alpha1.CapsuleConfiguration{}, forOptionPerInstanceName(configurationName)).
|
||||
For(&capsulev1alpha1.CapsuleConfiguration{}, forOptionPerInstanceName(configurationName)).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package rbac
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package rbac
|
||||
|
||||
@@ -36,7 +23,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
"github.com/clastix/capsule/pkg/configuration"
|
||||
)
|
||||
|
||||
@@ -92,7 +79,7 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, configurationName string) (
|
||||
return r.filterByNames(genericEvent.Object.GetName())
|
||||
},
|
||||
})).
|
||||
Watches(source.NewKindWithCache(&v1alpha1.CapsuleConfiguration{}, mgr.GetCache()), handler.Funcs{
|
||||
Watches(source.NewKindWithCache(&capsulev1alpha1.CapsuleConfiguration{}, mgr.GetCache()), handler.Funcs{
|
||||
UpdateFunc: func(updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) {
|
||||
if updateEvent.ObjectNew.GetName() == configurationName {
|
||||
if crbErr := r.EnsureClusterRoleBindings(); crbErr != nil {
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package secret
|
||||
|
||||
@@ -24,11 +11,13 @@ import (
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"golang.org/x/sync/errgroup"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/utils/pointer"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
@@ -50,10 +39,45 @@ func (r *CAReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// By default helm doesn't allow to use templates in CRD (https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you).
|
||||
// In order to overcome this, we are setting conversion strategy in helm chart to None, and then update it with CA and namespace information.
|
||||
func (r *CAReconciler) UpdateCustomResourceDefinition(caBundle []byte) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
crd := &apiextensionsv1.CustomResourceDefinition{}
|
||||
err = r.Get(context.TODO(), types.NamespacedName{Name: "tenants.capsule.clastix.io"}, crd)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot retrieve CustomResourceDefinition")
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, crd, func() error {
|
||||
crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
|
||||
Strategy: "Webhook",
|
||||
Webhook: &apiextensionsv1.WebhookConversion{
|
||||
ClientConfig: &apiextensionsv1.WebhookClientConfig{
|
||||
Service: &apiextensionsv1.ServiceReference{
|
||||
Namespace: r.Namespace,
|
||||
Name: "capsule-webhook-service",
|
||||
Path: pointer.StringPtr("/convert"),
|
||||
Port: pointer.Int32Ptr(443),
|
||||
},
|
||||
CABundle: caBundle,
|
||||
},
|
||||
ConversionReviewVersions: []string{"v1alpha1", "v1beta1"},
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func (r CAReconciler) UpdateValidatingWebhookConfiguration(caBundle []byte) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
vw := &v1.ValidatingWebhookConfiguration{}
|
||||
vw := &admissionregistrationv1.ValidatingWebhookConfiguration{}
|
||||
err = r.Get(context.TODO(), types.NamespacedName{Name: "capsule-validating-webhook-configuration"}, vw)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot retrieve ValidatingWebhookConfiguration")
|
||||
@@ -72,7 +96,7 @@ func (r CAReconciler) UpdateValidatingWebhookConfiguration(caBundle []byte) erro
|
||||
//nolint:dupl
|
||||
func (r CAReconciler) UpdateMutatingWebhookConfiguration(caBundle []byte) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
mw := &v1.MutatingWebhookConfiguration{}
|
||||
mw := &admissionregistrationv1.MutatingWebhookConfiguration{}
|
||||
err = r.Get(context.TODO(), types.NamespacedName{Name: "capsule-mutating-webhook-configuration"}, mw)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot retrieve MutatingWebhookConfiguration")
|
||||
@@ -140,6 +164,9 @@ func (r CAReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl
|
||||
group.Go(func() error {
|
||||
return r.UpdateValidatingWebhookConfiguration(crt.Bytes())
|
||||
})
|
||||
group.Go(func() error {
|
||||
return r.UpdateCustomResourceDefinition(crt.Bytes())
|
||||
})
|
||||
|
||||
if err = group.Wait(); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package secret
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package secret
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package secret
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package secret
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package servicelabels
|
||||
|
||||
@@ -33,7 +20,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
type abstractServiceLabelsReconciler struct {
|
||||
@@ -74,15 +61,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) (*v1alpha1.Tenant, error) {
|
||||
func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*capsulev1beta1.Tenant, error) {
|
||||
ns := &corev1.Namespace{}
|
||||
tenant := &v1alpha1.Tenant{}
|
||||
tenant := &capsulev1beta1.Tenant{}
|
||||
|
||||
if err := client.Get(ctx, types.NamespacedName{Name: namespacedName.Namespace}, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
capsuleLabel, _ := v1alpha1.GetTypeLabel(&v1alpha1.Tenant{})
|
||||
capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
|
||||
if _, ok := ns.GetLabels()[capsuleLabel]; !ok {
|
||||
return nil, NewNonTenantObject(namespacedName.Name)
|
||||
}
|
||||
@@ -91,7 +78,7 @@ func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespa
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tenant.Spec.ServicesMetadata.AdditionalLabels == nil && tenant.Spec.ServicesMetadata.AdditionalAnnotations == nil {
|
||||
if tenant.Spec.ServicesMetadata == nil {
|
||||
return nil, NewNoServicesMetadata(namespacedName.Name)
|
||||
}
|
||||
|
||||
@@ -131,7 +118,7 @@ func (r *abstractServiceLabelsReconciler) forOptionPerInstanceName() builder.For
|
||||
}
|
||||
|
||||
func (r *abstractServiceLabelsReconciler) IsNamespaceInTenant(namespace string) bool {
|
||||
tl := &v1alpha1.TenantList{}
|
||||
tl := &capsulev1beta1.TenantList{}
|
||||
if err := r.client.List(context.Background(), tl, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(".status.namespaces", namespace),
|
||||
}); err != nil {
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package servicelabels
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package servicelabels
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package servicelabels
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package servicelabels
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package controllers
|
||||
|
||||
@@ -36,26 +23,28 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
"github.com/clastix/capsule/controllers/rbac"
|
||||
)
|
||||
|
||||
// TenantReconciler reconciles a Tenant object
|
||||
type TenantReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
Recorder record.EventRecorder
|
||||
}
|
||||
|
||||
func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&capsulev1alpha1.Tenant{}).
|
||||
For(&capsulev1beta1.Tenant{}).
|
||||
Owns(&corev1.Namespace{}).
|
||||
Owns(&networkingv1.NetworkPolicy{}).
|
||||
Owns(&corev1.LimitRange{}).
|
||||
@@ -68,7 +57,7 @@ func (r TenantReconciler) Reconcile(ctx context.Context, request ctrl.Request) (
|
||||
r.Log = r.Log.WithValues("Request.Name", request.Name)
|
||||
|
||||
// Fetch the Tenant instance
|
||||
instance := &capsulev1alpha1.Tenant{}
|
||||
instance := &capsulev1beta1.Tenant{}
|
||||
err = r.Get(ctx, request.NamespacedName, instance)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
@@ -78,6 +67,11 @@ func (r TenantReconciler) Reconcile(ctx context.Context, request ctrl.Request) (
|
||||
r.Log.Error(err, "Error reading the object")
|
||||
return
|
||||
}
|
||||
// Ensuring the Tenant Status
|
||||
if err = r.updateTenantStatus(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot update Tenant status")
|
||||
return
|
||||
}
|
||||
|
||||
// Ensuring all namespaces are collected
|
||||
r.Log.Info("Ensuring all Namespaces are collected")
|
||||
@@ -92,27 +86,33 @@ func (r TenantReconciler) Reconcile(ctx context.Context, request ctrl.Request) (
|
||||
return
|
||||
}
|
||||
|
||||
r.Log.Info("Starting processing of Network Policies", "items", len(instance.Spec.NetworkPolicies))
|
||||
if err = r.syncNetworkPolicies(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync NetworkPolicy items")
|
||||
return
|
||||
if instance.Spec.NetworkPolicies != nil {
|
||||
r.Log.Info("Starting processing of Network Policies", "items", len(instance.Spec.NetworkPolicies.Items))
|
||||
if err = r.syncNetworkPolicies(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync NetworkPolicy items")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.Log.Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges))
|
||||
if err = r.syncLimitRanges(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync LimitRange items")
|
||||
return
|
||||
if instance.Spec.LimitRanges != nil {
|
||||
r.Log.Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges.Items))
|
||||
if err = r.syncLimitRanges(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync LimitRange items")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.Log.Info("Starting processing of Resource Quotas", "items", len(instance.Spec.ResourceQuota))
|
||||
if err = r.syncResourceQuotas(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync ResourceQuota items")
|
||||
return
|
||||
if instance.Spec.ResourceQuota != nil {
|
||||
r.Log.Info("Starting processing of Resource Quotas", "items", len(instance.Spec.ResourceQuota.Items))
|
||||
if err = r.syncResourceQuotas(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync ResourceQuota items")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.Log.Info("Ensuring PSP for owner")
|
||||
r.Log.Info("Ensuring additional RoleBindings for owner")
|
||||
if err = r.syncAdditionalRoleBindings(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync additional Role Bindings items")
|
||||
r.Log.Error(err, "Cannot sync additional RoleBindings items")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func (r TenantReconciler) Reconcile(ctx context.Context, request ctrl.Request) (
|
||||
// pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or
|
||||
// NetworkPolicy using the "exists" and "notin" LabelSelector to perform an outer-join removal.
|
||||
func (r *TenantReconciler) pruningResources(ns string, keys []string, obj client.Object) error {
|
||||
capsuleLabel, err := capsulev1alpha1.GetTypeLabel(obj)
|
||||
capsuleLabel, err := capsulev1beta1.GetTypeLabel(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -191,8 +191,8 @@ func (r *TenantReconciler) resourceQuotasUpdate(resourceName corev1.ResourceName
|
||||
found.Annotations = make(map[string]string)
|
||||
}
|
||||
found.Labels = rq.Labels
|
||||
found.Annotations[capsulev1alpha1.UsedQuotaFor(resourceName)] = actual.String()
|
||||
found.Annotations[capsulev1alpha1.HardQuotaFor(resourceName)] = limit.String()
|
||||
found.Annotations[capsulev1beta1.UsedQuotaFor(resourceName)] = actual.String()
|
||||
found.Annotations[capsulev1beta1.HardQuotaFor(resourceName)] = limit.String()
|
||||
// Updating the Resource according to the actual.Cmp result
|
||||
found.Spec.Hard = rq.Spec.Hard
|
||||
return nil
|
||||
@@ -215,25 +215,31 @@ func (r *TenantReconciler) resourceQuotasUpdate(resourceName corev1.ResourceName
|
||||
|
||||
// Additional Role Bindings can be used in many ways: applying Pod Security Policies or giving
|
||||
// access to CRDs or specific API groups.
|
||||
func (r *TenantReconciler) syncAdditionalRoleBindings(tenant *capsulev1alpha1.Tenant) (err error) {
|
||||
func (r *TenantReconciler) syncAdditionalRoleBindings(tenant *capsulev1beta1.Tenant) (err error) {
|
||||
// hashing the RoleBinding name due to DNS RFC-1123 applied to Kubernetes labels
|
||||
hash := func(value string) string {
|
||||
hash := func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string {
|
||||
h := fnv.New64a()
|
||||
_, _ = h.Write([]byte(value))
|
||||
|
||||
_, _ = h.Write([]byte(binding.ClusterRoleName))
|
||||
|
||||
for _, sub := range binding.Subjects {
|
||||
_, _ = h.Write([]byte(sub.Kind + sub.Name))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum64())
|
||||
}
|
||||
// getting requested Role Binding keys
|
||||
var keys []string
|
||||
for _, i := range tenant.Spec.AdditionalRoleBindings {
|
||||
keys = append(keys, hash(i.ClusterRoleName))
|
||||
keys = append(keys, hash(i))
|
||||
}
|
||||
|
||||
var tl, ll string
|
||||
tl, err = capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
tl, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ll, err = capsulev1alpha1.GetTypeLabel(&rbacv1.RoleBinding{})
|
||||
ll, err = capsulev1beta1.GetTypeLabel(&rbacv1.RoleBinding{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -242,11 +248,11 @@ func (r *TenantReconciler) syncAdditionalRoleBindings(tenant *capsulev1alpha1.Te
|
||||
if err = r.pruningResources(ns, keys, &rbacv1.RoleBinding{}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range tenant.Spec.AdditionalRoleBindings {
|
||||
lv := hash(i.ClusterRoleName)
|
||||
for i, roleBinding := range tenant.Spec.AdditionalRoleBindings {
|
||||
lv := hash(roleBinding)
|
||||
rb := &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("capsule-%s-%s", tenant.Name, i.ClusterRoleName),
|
||||
Name: fmt.Sprintf("capsule-%s-%d-%s", tenant.Name, i, roleBinding.ClusterRoleName),
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
@@ -259,11 +265,15 @@ func (r *TenantReconciler) syncAdditionalRoleBindings(tenant *capsulev1alpha1.Te
|
||||
rb.RoleRef = rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "ClusterRole",
|
||||
Name: i.ClusterRoleName,
|
||||
Name: roleBinding.ClusterRoleName,
|
||||
}
|
||||
rb.Subjects = i.Subjects
|
||||
rb.Subjects = roleBinding.Subjects
|
||||
|
||||
return controllerutil.SetControllerReference(tenant, rb, r.Scheme)
|
||||
})
|
||||
|
||||
r.emitEvent(tenant, rb.GetNamespace(), res, fmt.Sprintf("Ensuring additional RoleBinding %s", rb.GetName()), err)
|
||||
|
||||
if err != nil {
|
||||
r.Log.Error(err, "Cannot sync Additional RoleBinding")
|
||||
}
|
||||
@@ -285,19 +295,19 @@ func (r *TenantReconciler) syncAdditionalRoleBindings(tenant *capsulev1alpha1.Te
|
||||
// .Status.Used value as the .Hard value.
|
||||
// This will trigger a following reconciliation but that's ok: the mutateFn will re-use the same business logic, letting
|
||||
// the mutateFn along with the CreateOrUpdate to don't perform the update since resources are identical.
|
||||
func (r *TenantReconciler) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) syncResourceQuotas(tenant *capsulev1beta1.Tenant) error {
|
||||
// getting requested ResourceQuota keys
|
||||
keys := make([]string, 0, len(tenant.Spec.ResourceQuota))
|
||||
for i := range tenant.Spec.ResourceQuota {
|
||||
keys := make([]string, 0, len(tenant.Spec.ResourceQuota.Items))
|
||||
for i := range tenant.Spec.ResourceQuota.Items {
|
||||
keys = append(keys, strconv.Itoa(i))
|
||||
}
|
||||
|
||||
// getting ResourceQuota labels for the mutateFn
|
||||
tenantLabel, err := capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
tenantLabel, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
typeLabel, err := capsulev1alpha1.GetTypeLabel(&corev1.ResourceQuota{})
|
||||
typeLabel, err := capsulev1beta1.GetTypeLabel(&corev1.ResourceQuota{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -306,19 +316,18 @@ func (r *TenantReconciler) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) er
|
||||
if err := r.pruningResources(ns, keys, &corev1.ResourceQuota{}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i, q := range tenant.Spec.ResourceQuota {
|
||||
for i, q := range tenant.Spec.ResourceQuota.Items {
|
||||
target := &corev1.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("capsule-%s-%d", tenant.Name, i),
|
||||
Namespace: ns,
|
||||
Annotations: make(map[string]string),
|
||||
Labels: map[string]string{
|
||||
tenantLabel: tenant.Name,
|
||||
typeLabel: strconv.Itoa(i),
|
||||
},
|
||||
Name: fmt.Sprintf("capsule-%s-%d", tenant.Name, i),
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.Client, target, func() (err error) {
|
||||
target.SetLabels(map[string]string{
|
||||
tenantLabel: tenant.Name,
|
||||
typeLabel: strconv.Itoa(i),
|
||||
})
|
||||
// Requirement to list ResourceQuota of the current Tenant
|
||||
tr, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tenant.Name})
|
||||
if err != nil {
|
||||
@@ -395,6 +404,9 @@ func (r *TenantReconciler) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) er
|
||||
}
|
||||
return controllerutil.SetControllerReference(tenant, target, r.Scheme)
|
||||
})
|
||||
|
||||
r.emitEvent(tenant, target.GetNamespace(), res, fmt.Sprintf("Ensuring ResourceQuota %s", target.GetName()), err)
|
||||
|
||||
r.Log.Info("Resource Quota sync result: "+string(res), "name", target.Name, "namespace", target.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -406,19 +418,19 @@ func (r *TenantReconciler) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) er
|
||||
}
|
||||
|
||||
// Ensuring all the LimitRange are applied to each Namespace handled by the Tenant.
|
||||
func (r *TenantReconciler) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) syncLimitRanges(tenant *capsulev1beta1.Tenant) error {
|
||||
// getting requested LimitRange keys
|
||||
keys := make([]string, 0, len(tenant.Spec.LimitRanges))
|
||||
for i := range tenant.Spec.LimitRanges {
|
||||
keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items))
|
||||
for i := range tenant.Spec.LimitRanges.Items {
|
||||
keys = append(keys, strconv.Itoa(i))
|
||||
}
|
||||
|
||||
// getting LimitRange labels for the mutateFn
|
||||
tl, err := capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
tl, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ll, err := capsulev1alpha1.GetTypeLabel(&corev1.LimitRange{})
|
||||
ll, err := capsulev1beta1.GetTypeLabel(&corev1.LimitRange{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -427,7 +439,7 @@ func (r *TenantReconciler) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error
|
||||
if err := r.pruningResources(ns, keys, &corev1.LimitRange{}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i, spec := range tenant.Spec.LimitRanges {
|
||||
for i, spec := range tenant.Spec.LimitRanges.Items {
|
||||
t := &corev1.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("capsule-%s-%d", tenant.Name, i),
|
||||
@@ -442,6 +454,9 @@ func (r *TenantReconciler) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error
|
||||
t.Spec = spec
|
||||
return controllerutil.SetControllerReference(tenant, t, r.Scheme)
|
||||
})
|
||||
|
||||
r.emitEvent(tenant, t.GetNamespace(), res, fmt.Sprintf("Ensuring LimitRange %s", t.GetName()), err)
|
||||
|
||||
r.Log.Info("LimitRange sync result: "+string(res), "name", t.Name, "namespace", t.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -452,65 +467,87 @@ func (r *TenantReconciler) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TenantReconciler) syncNamespaceMetadata(namespace string, tnt *capsulev1alpha1.Tenant) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
func (r *TenantReconciler) syncNamespaceMetadata(namespace string, tnt *capsulev1beta1.Tenant) (err error) {
|
||||
var res controllerutil.OperationResult
|
||||
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() (conflictErr error) {
|
||||
ns := &corev1.Namespace{}
|
||||
if err = r.Client.Get(context.TODO(), types.NamespacedName{Name: namespace}, ns); err != nil {
|
||||
if conflictErr = r.Client.Get(context.TODO(), types.NamespacedName{Name: namespace}, ns); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
a := tnt.Spec.NamespacesMetadata.AdditionalAnnotations
|
||||
if a == nil {
|
||||
a = make(map[string]string)
|
||||
}
|
||||
if tnt.Spec.NodeSelector != nil {
|
||||
var selector []string
|
||||
for k, v := range tnt.Spec.NodeSelector {
|
||||
selector = append(selector, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
a["scheduler.alpha.kubernetes.io/node-selector"] = strings.Join(selector, ",")
|
||||
}
|
||||
if tnt.Spec.IngressClasses != nil {
|
||||
if len(tnt.Spec.IngressClasses.Exact) > 0 {
|
||||
a[capsulev1alpha1.AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressClasses.Exact, ",")
|
||||
}
|
||||
if len(tnt.Spec.IngressClasses.Regex) > 0 {
|
||||
a[capsulev1alpha1.AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressClasses.Regex
|
||||
}
|
||||
}
|
||||
if tnt.Spec.StorageClasses != nil {
|
||||
if len(tnt.Spec.StorageClasses.Exact) > 0 {
|
||||
a[capsulev1alpha1.AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",")
|
||||
}
|
||||
if len(tnt.Spec.StorageClasses.Regex) > 0 {
|
||||
a[capsulev1alpha1.AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex
|
||||
}
|
||||
}
|
||||
if tnt.Spec.ContainerRegistries != nil {
|
||||
if len(tnt.Spec.ContainerRegistries.Exact) > 0 {
|
||||
a[capsulev1alpha1.AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",")
|
||||
}
|
||||
if len(tnt.Spec.ContainerRegistries.Regex) > 0 {
|
||||
a[capsulev1alpha1.AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex
|
||||
}
|
||||
}
|
||||
ns.SetAnnotations(a)
|
||||
res, conflictErr = controllerutil.CreateOrUpdate(context.TODO(), r.Client, ns, func() error {
|
||||
a := make(map[string]string)
|
||||
|
||||
l := tnt.Spec.NamespacesMetadata.AdditionalLabels
|
||||
if l == nil {
|
||||
l = make(map[string]string)
|
||||
}
|
||||
l["name"] = namespace
|
||||
capsuleLabel, _ := capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
l[capsuleLabel] = tnt.GetName()
|
||||
ns.SetLabels(l)
|
||||
if tnt.Spec.NamespacesMetadata != nil {
|
||||
for k, v := range tnt.Spec.NamespacesMetadata.AdditionalAnnotations {
|
||||
a[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return r.Client.Update(context.TODO(), ns, &client.UpdateOptions{})
|
||||
if tnt.Spec.NodeSelector != nil {
|
||||
var selector []string
|
||||
for k, v := range tnt.Spec.NodeSelector {
|
||||
selector = append(selector, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
a["scheduler.alpha.kubernetes.io/node-selector"] = strings.Join(selector, ",")
|
||||
}
|
||||
|
||||
if tnt.Spec.IngressClasses != nil {
|
||||
if len(tnt.Spec.IngressClasses.Exact) > 0 {
|
||||
a[capsulev1beta1.AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressClasses.Exact, ",")
|
||||
}
|
||||
if len(tnt.Spec.IngressClasses.Regex) > 0 {
|
||||
a[capsulev1beta1.AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressClasses.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if tnt.Spec.StorageClasses != nil {
|
||||
if len(tnt.Spec.StorageClasses.Exact) > 0 {
|
||||
a[capsulev1beta1.AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",")
|
||||
}
|
||||
if len(tnt.Spec.StorageClasses.Regex) > 0 {
|
||||
a[capsulev1beta1.AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if tnt.Spec.ContainerRegistries != nil {
|
||||
if len(tnt.Spec.ContainerRegistries.Exact) > 0 {
|
||||
a[capsulev1beta1.AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",")
|
||||
}
|
||||
if len(tnt.Spec.ContainerRegistries.Regex) > 0 {
|
||||
a[capsulev1beta1.AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex
|
||||
}
|
||||
}
|
||||
|
||||
ns.SetAnnotations(a)
|
||||
|
||||
l := make(map[string]string)
|
||||
|
||||
if tnt.Spec.NamespacesMetadata != nil {
|
||||
for k, v := range tnt.Spec.NamespacesMetadata.AdditionalLabels {
|
||||
l[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
l["name"] = namespace
|
||||
capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
|
||||
l[capsuleLabel] = tnt.GetName()
|
||||
ns.SetLabels(l)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
r.emitEvent(tnt, namespace, res, "Ensuring Namespace metadata", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Ensuring all annotations are applied to each Namespace handled by the Tenant.
|
||||
func (r *TenantReconciler) syncNamespaces(tenant *capsulev1alpha1.Tenant) (err error) {
|
||||
func (r *TenantReconciler) syncNamespaces(tenant *capsulev1beta1.Tenant) (err error) {
|
||||
group := errgroup.Group{}
|
||||
|
||||
for _, item := range tenant.Status.Namespaces {
|
||||
@@ -528,19 +565,19 @@ func (r *TenantReconciler) syncNamespaces(tenant *capsulev1alpha1.Tenant) (err e
|
||||
}
|
||||
|
||||
// Ensuring all the NetworkPolicies are applied to each Namespace handled by the Tenant.
|
||||
func (r *TenantReconciler) syncNetworkPolicies(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) syncNetworkPolicies(tenant *capsulev1beta1.Tenant) error {
|
||||
// getting requested NetworkPolicy keys
|
||||
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies))
|
||||
for i := range tenant.Spec.NetworkPolicies {
|
||||
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items))
|
||||
for i := range tenant.Spec.NetworkPolicies.Items {
|
||||
keys = append(keys, strconv.Itoa(i))
|
||||
}
|
||||
|
||||
// getting NetworkPolicy labels for the mutateFn
|
||||
tl, err := capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
tl, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nl, err := capsulev1alpha1.GetTypeLabel(&networkingv1.NetworkPolicy{})
|
||||
nl, err := capsulev1beta1.GetTypeLabel(&networkingv1.NetworkPolicy{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -549,21 +586,25 @@ func (r *TenantReconciler) syncNetworkPolicies(tenant *capsulev1alpha1.Tenant) e
|
||||
if err := r.pruningResources(ns, keys, &networkingv1.NetworkPolicy{}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i, spec := range tenant.Spec.NetworkPolicies {
|
||||
for i, spec := range tenant.Spec.NetworkPolicies.Items {
|
||||
t := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("capsule-%s-%d", tenant.Name, i),
|
||||
Namespace: ns,
|
||||
Labels: map[string]string{
|
||||
tl: tenant.Name,
|
||||
nl: strconv.Itoa(i),
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.Client, t, func() (err error) {
|
||||
t.SetLabels(map[string]string{
|
||||
tl: tenant.Name,
|
||||
nl: strconv.Itoa(i),
|
||||
})
|
||||
t.Spec = spec
|
||||
|
||||
return controllerutil.SetControllerReference(tenant, t, r.Scheme)
|
||||
})
|
||||
|
||||
r.emitEvent(tenant, t.GetNamespace(), res, fmt.Sprintf("Ensuring NetworkPolicy %s", t.GetName()), err)
|
||||
|
||||
r.Log.Info("Network Policy sync result: "+string(res), "name", t.Name, "namespace", t.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -578,19 +619,32 @@ func (r *TenantReconciler) syncNetworkPolicies(tenant *capsulev1alpha1.Tenant) e
|
||||
// Since RBAC is based on deny all first, some specific actions like editing Capsule resources are going to be blocked
|
||||
// via Dynamic Admission Webhooks.
|
||||
// TODO(prometherion): we could create a capsule:admin role rather than hitting webhooks for each action
|
||||
func (r *TenantReconciler) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) ownerRoleBinding(tenant *capsulev1beta1.Tenant) error {
|
||||
// getting RoleBinding label for the mutateFn
|
||||
tl, err := capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
var subjects []rbacv1.Subject
|
||||
|
||||
tl, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l := map[string]string{tl: tenant.Name}
|
||||
s := []rbacv1.Subject{
|
||||
{
|
||||
Kind: tenant.Spec.Owner.Kind.String(),
|
||||
Name: tenant.Spec.Owner.Name,
|
||||
},
|
||||
|
||||
for _, owner := range tenant.Spec.Owners {
|
||||
if owner.Kind == "ServiceAccount" {
|
||||
splitName := strings.Split(owner.Name, ":")
|
||||
subjects = append(subjects, rbacv1.Subject{
|
||||
Kind: owner.Kind.String(),
|
||||
Name: splitName[len(splitName)-1],
|
||||
Namespace: splitName[len(splitName)-2],
|
||||
})
|
||||
} else {
|
||||
subjects = append(subjects, rbacv1.Subject{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: owner.Kind.String(),
|
||||
Name: owner.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rbl := make(map[types.NamespacedName]rbacv1.RoleRef)
|
||||
@@ -618,10 +672,13 @@ func (r *TenantReconciler) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) erro
|
||||
var res controllerutil.OperationResult
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, target, func() (err error) {
|
||||
target.ObjectMeta.Labels = l
|
||||
target.Subjects = s
|
||||
target.Subjects = subjects
|
||||
target.RoleRef = rr
|
||||
return controllerutil.SetControllerReference(tenant, target, r.Scheme)
|
||||
})
|
||||
|
||||
r.emitEvent(tenant, target.GetNamespace(), res, fmt.Sprintf("Ensuring Capsule RoleBinding %s", target.GetName()), err)
|
||||
|
||||
r.Log.Info("Role Binding sync result: "+string(res), "name", target.Name, "namespace", target.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -630,10 +687,10 @@ func (r *TenantReconciler) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TenantReconciler) ensureNamespaceCount(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) ensureNamespaceCount(tenant *capsulev1beta1.Tenant) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
tenant.Status.Size = uint(len(tenant.Status.Namespaces))
|
||||
found := &capsulev1alpha1.Tenant{}
|
||||
found := &capsulev1beta1.Tenant{}
|
||||
if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: tenant.GetName()}, found); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -642,7 +699,17 @@ func (r *TenantReconciler) ensureNamespaceCount(tenant *capsulev1alpha1.Tenant)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *TenantReconciler) collectNamespaces(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) emitEvent(object runtime.Object, namespace string, res controllerutil.OperationResult, msg string, err error) {
|
||||
var eventType = corev1.EventTypeNormal
|
||||
if err != nil {
|
||||
eventType = corev1.EventTypeWarning
|
||||
res = "Error"
|
||||
}
|
||||
|
||||
r.Recorder.AnnotatedEventf(object, map[string]string{"OperationResult": string(res)}, eventType, namespace, msg)
|
||||
}
|
||||
|
||||
func (r *TenantReconciler) collectNamespaces(tenant *capsulev1beta1.Tenant) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
nl := &corev1.NamespaceList{}
|
||||
err = r.Client.List(context.TODO(), nl, client.MatchingFieldsSelector{
|
||||
@@ -658,3 +725,15 @@ func (r *TenantReconciler) collectNamespaces(tenant *capsulev1alpha1.Tenant) err
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (r *TenantReconciler) updateTenantStatus(tnt *capsulev1beta1.Tenant) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
|
||||
if tnt.IsCordoned() {
|
||||
tnt.Status.State = capsulev1beta1.TenantStateCordoned
|
||||
} else {
|
||||
tnt.Status.State = capsulev1beta1.TenantStateActive
|
||||
}
|
||||
|
||||
return r.Client.Status().Update(context.Background(), tnt)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ The first step is to set up your local development environment as stated below:
|
||||
## Setting up the development environment
|
||||
The following dependencies are mandatory:
|
||||
|
||||
- [Go 1.13.8](https://golang.org/dl/)
|
||||
- [OperatorSDK 1.9](https://github.com/operator-framework/operator-sdk)
|
||||
- [Go 1.16](https://golang.org/dl/)
|
||||
- [OperatorSDK 1.7.2](https://github.com/operator-framework/operator-sdk)
|
||||
- [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)
|
||||
- [KinD](https://github.com/kubernetes-sigs/kind)
|
||||
- [ngrok](https://ngrok.com/) (if you want to run locally)
|
||||
|
||||
149
docs/operator/managed-kubernetes/getting-started-amazon-eks.md
Normal file
149
docs/operator/managed-kubernetes/getting-started-amazon-eks.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Capsule with Amazon EKS
|
||||
|
||||
This is an example how to install Amazon EKS cluster and one user
|
||||
manged by capsule.
|
||||
|
||||
It is based on [Using IAM Groups to manage Kubernetes access](https://www.eksworkshop.com/beginner/091_iam-groups/intro/)
|
||||
|
||||
Create EKS cluster:
|
||||
|
||||
```bash
|
||||
export AWS_DEFAULT_REGION="eu-west-1"
|
||||
export AWS_ACCESS_KEY_ID="xxxxx"
|
||||
export AWS_SECRET_ACCESS_KEY="xxxxx"
|
||||
|
||||
eksctl create cluster \
|
||||
--name=test-k8s \
|
||||
--managed \
|
||||
--node-type=t3.small \
|
||||
--node-volume-size=20 \
|
||||
--kubeconfig=kubeconfig.conf
|
||||
```
|
||||
|
||||
Create AWS User `alice` using CloudFormation, create AWS access files and
|
||||
kubeconfig for such user:
|
||||
|
||||
```bash
|
||||
cat > cf.yml << \EOF
|
||||
Parameters:
|
||||
ClusterName:
|
||||
Type: String
|
||||
Resources:
|
||||
UserAlice:
|
||||
Type: AWS::IAM::User
|
||||
Properties:
|
||||
UserName: !Sub "alice-${ClusterName}"
|
||||
Policies:
|
||||
- PolicyName: !Sub "alice-${ClusterName}-policy"
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Sid: AllowAssumeOrganizationAccountRole
|
||||
Effect: Allow
|
||||
Action: sts:AssumeRole
|
||||
Resource: !GetAtt RoleAlice.Arn
|
||||
AccessKeyAlice:
|
||||
Type: AWS::IAM::AccessKey
|
||||
Properties:
|
||||
UserName: !Ref UserAlice
|
||||
RoleAlice:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
Description: !Sub "IAM role for the alice-${ClusterName} user"
|
||||
RoleName: !Sub "alice-${ClusterName}"
|
||||
AssumeRolePolicyDocument:
|
||||
Version: 2012-10-17
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
|
||||
Action: sts:AssumeRole
|
||||
Outputs:
|
||||
RoleAliceArn:
|
||||
Description: The ARN of the Alice IAM Role
|
||||
Value: !GetAtt RoleAlice.Arn
|
||||
Export:
|
||||
Name:
|
||||
Fn::Sub: "${AWS::StackName}-RoleAliceArn"
|
||||
AccessKeyAlice:
|
||||
Description: The AccessKey for Alice user
|
||||
Value: !Ref AccessKeyAlice
|
||||
Export:
|
||||
Name:
|
||||
Fn::Sub: "${AWS::StackName}-AccessKeyAlice"
|
||||
SecretAccessKeyAlice:
|
||||
Description: The SecretAccessKey for Alice user
|
||||
Value: !GetAtt AccessKeyAlice.SecretAccessKey
|
||||
Export:
|
||||
Name:
|
||||
Fn::Sub: "${AWS::StackName}-SecretAccessKeyAlice"
|
||||
EOF
|
||||
|
||||
eval aws cloudformation deploy --capabilities CAPABILITY_NAMED_IAM \
|
||||
--parameter-overrides "ClusterName=test-k8s" \
|
||||
--stack-name "test-k8s-users" --template-file cf.yml
|
||||
|
||||
AWS_CLOUDFORMATION_DETAILS=$(aws cloudformation describe-stacks --stack-name "test-k8s-users")
|
||||
ALICE_ROLE_ARN=$(echo "${AWS_CLOUDFORMATION_DETAILS}" | jq -r ".Stacks[0].Outputs[] | select(.OutputKey==\"RoleAliceArn\") .OutputValue")
|
||||
ALICE_USER_ACCESSKEY=$(echo "${AWS_CLOUDFORMATION_DETAILS}" | jq -r ".Stacks[0].Outputs[] | select(.OutputKey==\"AccessKeyAlice\") .OutputValue")
|
||||
ALICE_USER_SECRETACCESSKEY=$(echo "${AWS_CLOUDFORMATION_DETAILS}" | jq -r ".Stacks[0].Outputs[] | select(.OutputKey==\"SecretAccessKeyAlice\") .OutputValue")
|
||||
|
||||
eksctl create iamidentitymapping --cluster="test-k8s" --arn="${ALICE_ROLE_ARN}" --username alice --group capsule.clastix.io
|
||||
|
||||
cat > aws_config << EOF
|
||||
[profile alice]
|
||||
role_arn=${ALICE_ROLE_ARN}
|
||||
source_profile=alice
|
||||
EOF
|
||||
|
||||
cat > aws_credentials << EOF
|
||||
[alice]
|
||||
aws_access_key_id=${ALICE_USER_ACCESSKEY}
|
||||
aws_secret_access_key=${ALICE_USER_SECRETACCESSKEY}
|
||||
EOF
|
||||
|
||||
eksctl utils write-kubeconfig --cluster=test-k8s --kubeconfig="kubeconfig-alice.conf"
|
||||
cat >> kubeconfig-alice.conf << EOF
|
||||
- name: AWS_PROFILE
|
||||
value: alice
|
||||
- name: AWS_CONFIG_FILE
|
||||
value: aws_config
|
||||
- name: AWS_SHARED_CREDENTIALS_FILE
|
||||
value: aws_credentials
|
||||
EOF
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
Export "admin" kubeconfig to be able to install capsule:
|
||||
|
||||
```bash
|
||||
export KUBECONFIG=kubeconfig.conf
|
||||
```
|
||||
|
||||
Install capsule from helm chart:
|
||||
|
||||
```bash
|
||||
helm repo add clastix https://clastix.github.io/charts
|
||||
helm upgrade --install --version 0.0.19 --namespace capsule-system --create-namespace capsule clastix/capsule
|
||||
```
|
||||
|
||||
Use the default Tenant example:
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/master/config/samples/capsule_v1alpha1_tenant.yaml
|
||||
```
|
||||
|
||||
Based on the tenant configuration above the user `alice` should be able
|
||||
to create namespace...
|
||||
|
||||
Switch to new terminal tab and try to create namespace as user `alice`:
|
||||
|
||||
```bash
|
||||
# Unset AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY if defined
|
||||
unset AWS_ACCESS_KEY_ID
|
||||
unset AWS_SECRET_ACCESS_KEY
|
||||
kubectl create namespace test --kubeconfig="kubeconfig-alice.conf"
|
||||
|
||||
... do other commands allowed by Tenant configuration ...
|
||||
```
|
||||
43
docs/operator/use-cases/cordoning-tenant.md
Normal file
43
docs/operator/use-cases/cordoning-tenant.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Cordoning a Tenant
|
||||
|
||||
Bill needs to cordon a Tenant and its Namespaces for several reasons:
|
||||
|
||||
- Avoid accidental resource modification(s) including deletion during a Production Freeze Window
|
||||
- During Kubernetes upgrade, to prevent any workload updates
|
||||
- During incidents or outages
|
||||
- During planned maintenance of a dedicated nodes pool in a BYOD scenario
|
||||
|
||||
With this said, the Tenant Owner and the related Service Account living into managed Namespaces, cannot proceed to any update, create or delete action.
|
||||
|
||||
This is possible just labelling the Tenant as follows:
|
||||
|
||||
```shell
|
||||
$ kubectl label tenant oil capsule.clastix.io/cordon=enabled
|
||||
tenant oil labeled
|
||||
```
|
||||
|
||||
Any operation performed by Alice, the Tenant Owner, will be rejected.
|
||||
|
||||
```shell
|
||||
$ kubectl --as alice --as-group capsule.clastix.io -n oil-dev create deployment nginx --image nginx
|
||||
error: failed to create deployment: admission webhook "cordoning.tenant.capsule.clastix.io" denied the request: tenant oil is freezed: please, reach out to the system administrator
|
||||
|
||||
$ kubectl --as alice --as-group capsule.clastix.io -n oil-dev delete ingress,deployment,serviceaccount --all
|
||||
error: failed to create deployment: admission webhook "cordoning.tenant.capsule.clastix.io" denied the request: tenant oil is freezed: please, reach out to the system administrator
|
||||
```
|
||||
|
||||
Uncordoning can be done removing the said label:
|
||||
|
||||
```shell
|
||||
$ kubectl label tenant oil capsule.clastix.io/cordon-
|
||||
tenant.capsule.clastix.io/oil labeled
|
||||
|
||||
$ kubectl --as alice --as-group capsule.clastix.io -n oil-dev create deployment nginx --image nginx
|
||||
deployment.apps/nginx created
|
||||
```
|
||||
|
||||
# What’s next
|
||||
|
||||
This end our tour in Capsule use cases.
|
||||
As we improve Capsule, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future.
|
||||
Stay tuned!
|
||||
34
docs/operator/use-cases/images-pullpolicy.md
Normal file
34
docs/operator/use-cases/images-pullpolicy.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Enforcing Pod containers image PullPolicy
|
||||
|
||||
Bill is a cluster admin providing a Container as a Service platform using shared nodes.
|
||||
|
||||
Alice, a Tenant Owner, can start container images using private images: according to the Kubernetes architecture, the `kubelet` will download the layers on its cache.
|
||||
|
||||
Bob, an attacker, could try to schedule a Pod on the same node where Alice is running their Pod backed by private images: they could start new Pods using `ImagePullPolicy=IfNotPresent` and able to start them, even without required authentication since the image is cached on the node.
|
||||
|
||||
To avoid this kind of attack all the Tenant Owners must start their Pods using the `ImagePullPolicy` to `Always`, enforcing the `kubelet` to check the authorization first.
|
||||
|
||||
Capsule provides a way to enforce this behavior, as follows.
|
||||
|
||||
```yaml
|
||||
apiVersion: capsule.clastix.io/v1alpha1
|
||||
kind: Tenant
|
||||
metadata:
|
||||
name: oil
|
||||
annotations:
|
||||
capsule.clastix.io/allowed-image-pull-policy: Always
|
||||
spec:
|
||||
owner:
|
||||
name: alice
|
||||
kind: User
|
||||
```
|
||||
|
||||
> If you need to address specific use-case, the said annotation supports multiple values comme separated
|
||||
>
|
||||
> ```yaml
|
||||
> capsule.clastix.io/allowed-image-pull-policy: Always,IfNotPresent
|
||||
> ```
|
||||
|
||||
# What’s next
|
||||
|
||||
See how Bill, the cluster admin, can assign trusted images registries to Alice's tenant. [Assign Trusted Images Registries](./images-registries.md).
|
||||
@@ -107,4 +107,5 @@ kubectl create -f gas-production-ns.yaml
|
||||
>`Unable to assign namespace to tenant. Please use capsule.clastix.io/tenant label when creating a namespace.`
|
||||
|
||||
# What’s next
|
||||
This end our tour in Capsule use cases. As we improve Capsule, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future. Stay tuned!
|
||||
|
||||
See how Bill, the cluster admin, can cordon all the Namespaces belonging to a Tenant. [Cordoning a Tenant](./cordoning-tenant.md).
|
||||
|
||||
@@ -100,4 +100,5 @@ Error from server (Capsule Network Policies cannot be deleted: please, reach out
|
||||
```
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign trusted images registries to Alice's tenant. [Assign Trusted Images Registries](./images-registries.md).
|
||||
See how Bill can enforce the Pod containers image pull policy to `Always` to avoid leaking of private images when running on shared nodes.
|
||||
[Enforcing Pod containers image PullPolicy](./images-pullpolicy.md)
|
||||
|
||||
@@ -32,11 +32,14 @@ Bill, at Acme Corp. can use Capsule to address any of the following scenarios:
|
||||
* [Assign Storage Classes](./storage-classes.md)
|
||||
* [Disable NodePort Services](./node-ports.md)
|
||||
* [Assign Network Policies](./network-policies.md)
|
||||
* [Enforcing Pod containers image PullPolicy](./images-pullpolicy.md)
|
||||
* [Assign Trusted Images Registries](./images-registries.md)
|
||||
* [Assign Pod Security Policies](./pod-security-policies.md)
|
||||
* [Create Custom Resources](./custom-resources.md)
|
||||
* [Taint Namespaces](./taint-namespaces.md)
|
||||
* [Assign multiple Tenants to an owner](./multiple-tenants.md)
|
||||
* [Cordoning a Tenant](./cordoning-tenant.md)
|
||||
* [Velero Backup Restoration](./velero-backup-restoration.md)
|
||||
|
||||
> NB: as we improve Capsule, more use cases about multi-tenancy and cluster governance will be covered.
|
||||
|
||||
|
||||
@@ -31,3 +31,7 @@ With the said Tenant specification Alice can create Pod resource if `spec.priori
|
||||
- `tier-gold`, `tier-silver`, or `tier-bronze`, since these compile the regex declared in the annotation `priorityclass.capsule.clastix.io/allowed-regex`
|
||||
|
||||
If a Pod is going to use a non-allowed _Priority Class_, it will be rejected by the Validation Webhook enforcing it.
|
||||
|
||||
# What’s next
|
||||
|
||||
See how Bill, the cluster admin, can assign a pool of nodes to Alice's tenant. [Assign a nodes pool](./nodes-pool.md).
|
||||
@@ -210,4 +210,5 @@ no - no RBAC policy matched
|
||||
```
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign a pool of nodes to Alice's tenant. [Assign a nodes pool](./nodes-pool.md).
|
||||
|
||||
See how Bill, the cluster admin, can enforce the PriorityClass of Pods running of Alice's tenant Namespaces. [Enforce Pod Priority Classes](./pod-priority-class.md)
|
||||
|
||||
19
docs/operator/use-cases/velero-backup-restoration.md
Normal file
19
docs/operator/use-cases/velero-backup-restoration.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Velero Backup Restoration
|
||||
|
||||
Velero is a backup system that perform disaster recovery, and migrate Kubernetes cluster resources and persistent volumes.
|
||||
|
||||
Using this in a Kubernetes cluster where Capsule is installed can lead to an incomplete restore of the cluster's Tenants. This is due to the fact that Velero omits the `ownerReferences` section from the tenant's namespace manifests when backup them.
|
||||
|
||||
To avoid this problem you can use the script `velero-restore.sh` under the `hack/` folder.
|
||||
|
||||
Below are some examples on how to use the script to avoid incomplete restorations.
|
||||
|
||||
## Getting Started
|
||||
|
||||
In case of a data loss, the right thing to do is to restore the cluster with **Velero** at first. Once Velero has finished, you can proceed using the script to complete the restoration.
|
||||
|
||||
```bash
|
||||
./velero-restore.sh --kubeconfing /path/to/your/kubeconfig restore
|
||||
```
|
||||
|
||||
Running this command, we are going to patch the tenant's namespaces manifests that are actually `ownerReferences`-less. Once the command has finished its run, you got the cluster back.
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -27,20 +14,22 @@ import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating a Namespace with an additional Role Binding", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "additional-role-binding",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "dale",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "dale",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
AdditionalRoleBindings: []v1alpha1.AdditionalRoleBindings{
|
||||
AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{
|
||||
{
|
||||
ClusterRoleName: "crds-rolebinding",
|
||||
Subjects: []rbacv1.Subject{
|
||||
@@ -68,14 +57,14 @@ var _ = Describe("creating a Namespace with an additional Role Binding", func()
|
||||
It("should be assigned to each Namespace", func() {
|
||||
for _, ns := range []string{"rb-1", "rb-2", "rb-3"} {
|
||||
ns := NewNamespace(ns)
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
var rb *rbacv1.RoleBinding
|
||||
|
||||
Eventually(func() (err error) {
|
||||
cs := ownerClient(tnt)
|
||||
rb, err = cs.RbacV1().RoleBindings(ns.Name).Get(context.Background(), fmt.Sprintf("capsule-%s-%s", tnt.Name, "crds-rolebinding"), metav1.GetOptions{})
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
rb, err = cs.RbacV1().RoleBindings(ns.Name).Get(context.Background(), fmt.Sprintf("capsule-%s-0-%s", tnt.Name, "crds-rolebinding"), metav1.GetOptions{})
|
||||
return err
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
Expect(rb.RoleRef.Name).Should(Equal(tnt.Spec.AdditionalRoleBindings[0].ClusterRoleName))
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -27,21 +14,23 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "allowed-external-ip",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
ExternalServiceIPs: &v1alpha1.ExternalServiceIPs{
|
||||
Allowed: []v1alpha1.AllowedIP{
|
||||
ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{
|
||||
Allowed: []capsulev1beta1.AllowedIP{
|
||||
"10.20.0.0/16",
|
||||
"192.168.1.2/32",
|
||||
},
|
||||
@@ -61,7 +50,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
|
||||
It("should fail creating an evil service", func() {
|
||||
ns := NewNamespace("evil-service")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -86,7 +75,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
},
|
||||
}
|
||||
EventuallyCreation(func() error {
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
|
||||
return err
|
||||
}).ShouldNot(Succeed())
|
||||
@@ -94,7 +83,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
|
||||
It("should allow the first CIDR block", func() {
|
||||
ns := NewNamespace("allowed-service-cidr")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -119,7 +108,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
},
|
||||
}
|
||||
EventuallyCreation(func() error {
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
|
||||
return err
|
||||
}).Should(Succeed())
|
||||
@@ -127,7 +116,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
|
||||
It("should allow the /32 CIDR block", func() {
|
||||
ns := NewNamespace("allowed-service-strict")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -151,7 +140,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
},
|
||||
}
|
||||
EventuallyCreation(func() error {
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
|
||||
return err
|
||||
}).Should(Succeed())
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -27,20 +14,22 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("enforcing a Container Registry", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "container-registry",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "matt",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "matt",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
ContainerRegistries: &v1alpha1.AllowedListSpec{
|
||||
ContainerRegistries: &capsulev1beta1.AllowedListSpec{
|
||||
Exact: []string{"docker.io", "docker.tld"},
|
||||
Regex: `quay\.\w+`,
|
||||
},
|
||||
@@ -59,7 +48,7 @@ var _ = Describe("enforcing a Container Registry", func() {
|
||||
|
||||
It("should add labels to Namespace", func() {
|
||||
ns := NewNamespace("registry-labels")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.Name}, ns)).Should(Succeed())
|
||||
ok, _ = HaveKeyWithValue("capsule.clastix.io/allowed-registries", "docker.io,docker.tld").Match(ns.Annotations)
|
||||
@@ -76,7 +65,7 @@ var _ = Describe("enforcing a Container Registry", func() {
|
||||
|
||||
It("should deny running a gcr.io container", func() {
|
||||
ns := NewNamespace("registry-deny")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -91,14 +80,14 @@ var _ = Describe("enforcing a Container Registry", func() {
|
||||
},
|
||||
},
|
||||
}
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
Expect(err).ShouldNot(Succeed())
|
||||
})
|
||||
|
||||
It("should allow using an exact match", func() {
|
||||
ns := NewNamespace("registry-list")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -114,7 +103,7 @@ var _ = Describe("enforcing a Container Registry", func() {
|
||||
},
|
||||
}
|
||||
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
EventuallyCreation(func() error {
|
||||
_, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
return err
|
||||
@@ -123,7 +112,7 @@ var _ = Describe("enforcing a Container Registry", func() {
|
||||
|
||||
It("should allow using a regex match", func() {
|
||||
ns := NewNamespace("registry-regex")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -139,7 +128,7 @@ var _ = Describe("enforcing a Container Registry", func() {
|
||||
},
|
||||
}
|
||||
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
EventuallyCreation(func() error {
|
||||
_, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
return err
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -25,18 +12,22 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-group", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tenant-assigned-custom-group",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -52,33 +43,33 @@ 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 *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.UserGroups = []string{"test"}
|
||||
})
|
||||
|
||||
ns := NewNamespace("cg-namespace-fail")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).ShouldNot(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
|
||||
})
|
||||
|
||||
It("should succeed and be available in Tenant namespaces list with multiple groups", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.UserGroups = []string{"test", "alice"}
|
||||
})
|
||||
|
||||
ns := NewNamespace("cg-namespace-1")
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
})
|
||||
|
||||
It("should succeed and be available in Tenant namespaces list with default single group", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.UserGroups = []string{"capsule.clastix.io"}
|
||||
})
|
||||
|
||||
ns := NewNamespace("cg-namespace-2")
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -27,22 +14,22 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating a nodePort service when it is disabled for Tenant", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "disable-node-ports",
|
||||
Annotations: map[string]string{
|
||||
"capsule.clastix.io/enable-node-ports": "false",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
EnableNodePorts: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -58,7 +45,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f
|
||||
|
||||
It("should fail creating a service with NodePort type", func() {
|
||||
ns := NewNamespace("disable-node-ports")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -80,7 +67,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f
|
||||
},
|
||||
}
|
||||
EventuallyCreation(func() error {
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
|
||||
return err
|
||||
}).ShouldNot(Succeed())
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -27,18 +14,20 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating a nodePort service when it is enabled for Tenant", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "enable-node-ports",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -55,7 +44,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", fu
|
||||
|
||||
It("should allow creating a service with NodePort type", func() {
|
||||
ns := NewNamespace("enable-node-ports")
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -77,7 +66,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", fu
|
||||
},
|
||||
}
|
||||
EventuallyCreation(func() error {
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
|
||||
return err
|
||||
}).Should(Succeed())
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -25,29 +12,35 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating a Namespace with Tenant name prefix enforcement", func() {
|
||||
t1 := &v1alpha1.Tenant{
|
||||
t1 := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awesome",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
t2 := &v1alpha1.Tenant{
|
||||
t2 := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awesome-tenant",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -62,7 +55,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun
|
||||
return k8sClient.Create(context.TODO(), t2)
|
||||
}).Should(Succeed())
|
||||
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.ForceTenantPrefix = true
|
||||
})
|
||||
})
|
||||
@@ -70,27 +63,27 @@ 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 *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.ForceTenantPrefix = false
|
||||
})
|
||||
})
|
||||
|
||||
It("should fail when non using prefix", func() {
|
||||
ns := NewNamespace("awesome")
|
||||
NamespaceCreation(ns, t1, defaultTimeoutInterval).ShouldNot(Succeed())
|
||||
NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
|
||||
})
|
||||
|
||||
It("should succeed using prefix", func() {
|
||||
ns := NewNamespace("awesome-namespace")
|
||||
NamespaceCreation(ns, t1, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
})
|
||||
|
||||
It("should succeed and assigned according to closest match", func() {
|
||||
ns1 := NewNamespace("awesome-tenant")
|
||||
ns2 := NewNamespace("awesome-tenant-namespace")
|
||||
|
||||
NamespaceCreation(ns1, t1, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns2, t2, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns1, t1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns2, t2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns1.GetName()))
|
||||
TenantNamespaceList(t2, defaultTimeoutInterval).Should(ContainElement(ns2.GetName()))
|
||||
|
||||
121
e2e/imagepullpolicy_multiple_test.go
Normal file
121
e2e/imagepullpolicy_multiple_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
//+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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("enforcing some defined ImagePullPolicy", func() {
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "image-pull-policies",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "alex",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"},
|
||||
},
|
||||
}
|
||||
|
||||
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 just allow the defined policies", func() {
|
||||
ns := NewNamespace("allow-policy")
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
By("allowing Always", func() {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pull-always",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EventuallyCreation(func() (err error) {
|
||||
_, err = cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
|
||||
return
|
||||
}).Should(Succeed())
|
||||
})
|
||||
|
||||
By("allowing IfNotPresent", func() {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "if-not-present",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EventuallyCreation(func() (err error) {
|
||||
_, err = cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
|
||||
return
|
||||
}).Should(Succeed())
|
||||
})
|
||||
|
||||
By("blocking Never", func() {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "never",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EventuallyCreation(func() (err error) {
|
||||
_, err = cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
|
||||
return
|
||||
}).ShouldNot(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
121
e2e/imagepullpolicy_single_test.go
Normal file
121
e2e/imagepullpolicy_single_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
//+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"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("enforcing a defined ImagePullPolicy", func() {
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "image-pull-policy",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "axel",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always"},
|
||||
},
|
||||
}
|
||||
|
||||
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 just allow the defined policy", func() {
|
||||
ns := NewNamespace("allow-policies")
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
By("allowing Always", func() {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pull-always",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EventuallyCreation(func() (err error) {
|
||||
_, err = cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
|
||||
return
|
||||
}).Should(Succeed())
|
||||
})
|
||||
|
||||
By("blocking IfNotPresent", func() {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "if-not-present",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EventuallyCreation(func() (err error) {
|
||||
_, err = cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
|
||||
return
|
||||
}).ShouldNot(Succeed())
|
||||
})
|
||||
|
||||
By("blocking Never", func() {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "never",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "container",
|
||||
Image: "gcr.io/google_containers/pause-amd64:3.0",
|
||||
ImagePullPolicy: corev1.PullNever,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
EventuallyCreation(func() (err error) {
|
||||
_, err = cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{})
|
||||
|
||||
return
|
||||
}).ShouldNot(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -28,20 +15,22 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-class",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "ingress",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "ingress",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
IngressClasses: &v1alpha1.AllowedListSpec{
|
||||
IngressClasses: &capsulev1beta1.AllowedListSpec{
|
||||
Exact: []string{
|
||||
"nginx",
|
||||
"haproxy",
|
||||
@@ -63,9 +52,9 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
|
||||
It("should block a non allowed class", func() {
|
||||
ns := NewNamespace("ingress-class-disallowed")
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
By("non-specifying at all", func() {
|
||||
@@ -127,9 +116,9 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
|
||||
It("should allow enabled class using the deprecated annotation", func() {
|
||||
ns := NewNamespace("ingress-class-allowed-annotation")
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
for _, c := range tnt.Spec.IngressClasses.Exact {
|
||||
@@ -156,14 +145,14 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
|
||||
It("should allow enabled class using the ingressClassName field", func() {
|
||||
ns := NewNamespace("ingress-class-allowed-annotation")
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
maj, min, v := GetKubernetesSemVer()
|
||||
if maj == 1 && min < 18 {
|
||||
Skip("Running test on Kubernetes " + v + ", doesn't provide .spec.ingressClassName")
|
||||
}
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
for _, c := range tnt.Spec.IngressClasses.Exact {
|
||||
@@ -188,10 +177,10 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
|
||||
It("should allow enabled Ingress by regex using the deprecated annotation", func() {
|
||||
ns := NewNamespace("ingress-class-allowed-annotation")
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
ingressClass := "oil-ingress"
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
Eventually(func() (err error) {
|
||||
@@ -216,7 +205,7 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
|
||||
It("should allow enabled Ingress by regex using the ingressClassName field", func() {
|
||||
ns := NewNamespace("ingress-class-allowed-annotation")
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
ingressClass := "oil-haproxy"
|
||||
|
||||
maj, min, v := GetKubernetesSemVer()
|
||||
@@ -224,7 +213,7 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
Skip("Running test on Kubernetes " + v + ", doesn't provide .spec.ingressClassName")
|
||||
}
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
Eventually(func() (err error) {
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -27,18 +14,22 @@ import (
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("when handling Ingress hostnames collision", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-hostnames-allowed-collision",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "ingress-allowed",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "ingress-allowed",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -81,54 +72,100 @@ var _ = Describe("when handling Ingress hostnames collision", func() {
|
||||
return k8sClient.Create(context.TODO(), tnt)
|
||||
}).Should(Succeed())
|
||||
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.AllowIngressHostnameCollision = true
|
||||
})
|
||||
})
|
||||
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
|
||||
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.AllowIngressHostnameCollision = false
|
||||
})
|
||||
})
|
||||
|
||||
It("should not allow creating several Ingress with same hostname", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.AllowIngressHostnameCollision = false
|
||||
})
|
||||
|
||||
maj, min, _ := GetKubernetesSemVer()
|
||||
|
||||
ns := NewNamespace("denied-collision")
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
if maj == 1 && min > 18 {
|
||||
By("testing networking.k8s.io", func() {
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := networkingIngress("networking-1", "kubernetes.io")
|
||||
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}).Should(Succeed())
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := networkingIngress("networking-2", "kubernetes.io")
|
||||
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}).ShouldNot(Succeed())
|
||||
})
|
||||
}
|
||||
|
||||
if maj == 1 && min < 22 {
|
||||
By("testing extensions", func() {
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := extensionsIngress("extensions-1", "cncf.io")
|
||||
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}).Should(Succeed())
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := extensionsIngress("extensions-2", "cncf.io")
|
||||
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}).ShouldNot(Succeed())
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
It("should allow creating several Ingress with same hostname", func() {
|
||||
maj, min, _ := GetKubernetesSemVer()
|
||||
|
||||
ns := NewNamespace("allowed-collision")
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
if maj == 1 && min > 18 {
|
||||
By("testing networking.k8s.io", func() {
|
||||
Eventually(func() (err error) {
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := networkingIngress("networking-1", "kubernetes.io")
|
||||
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
Eventually(func() (err error) {
|
||||
}).Should(Succeed())
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := networkingIngress("networking-2", "kubernetes.io")
|
||||
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
}).Should(Succeed())
|
||||
})
|
||||
}
|
||||
|
||||
if maj == 1 && min < 22 {
|
||||
By("testing extensions", func() {
|
||||
Eventually(func() (err error) {
|
||||
obj := extensionsIngress("extensions-1", "kubernetes.io")
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := extensionsIngress("extensions-1", "cncf.io")
|
||||
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
Eventually(func() (err error) {
|
||||
obj := extensionsIngress("extensions-2", "kubernetes.io")
|
||||
}).Should(Succeed())
|
||||
EventuallyCreation(func() (err error) {
|
||||
obj := extensionsIngress("extensions-2", "cncf.io")
|
||||
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
}).Should(Succeed())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
@@ -27,18 +14,22 @@ import (
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("when handling Ingress hostnames collision", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ingress-hostnames-denied-collision",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: v1alpha1.OwnerSpec{
|
||||
Name: "ingress-denied",
|
||||
Kind: "User",
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
{
|
||||
Name: "ingress-denied",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -80,14 +71,14 @@ var _ = Describe("when handling Ingress hostnames collision", func() {
|
||||
return k8sClient.Create(context.TODO(), tnt)
|
||||
}).Should(Succeed())
|
||||
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.AllowIngressHostnameCollision = true
|
||||
})
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
|
||||
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *v1alpha1.CapsuleConfiguration) {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
|
||||
configuration.Spec.AllowIngressHostnameCollision = false
|
||||
})
|
||||
})
|
||||
@@ -96,9 +87,9 @@ var _ = Describe("when handling Ingress hostnames collision", func() {
|
||||
maj, min, _ := GetKubernetesSemVer()
|
||||
|
||||
ns := NewNamespace("allowed-collision")
|
||||
cs := ownerClient(tnt)
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
|
||||
NamespaceCreation(ns, tnt, defaultTimeoutInterval).Should(Succeed())
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
if maj == 1 && min > 18 {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user