mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-03-01 01:00:45 +00:00
Compare commits
32 Commits
v0.1.0-rc3
...
v0.1.0-rc5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0d4aab582 | ||
|
|
6761fb93dc | ||
|
|
bf9e0f6b10 | ||
|
|
f937942c49 | ||
|
|
89d7f301c6 | ||
|
|
2a6ff09340 | ||
|
|
35f48107fc | ||
|
|
7aa62b6f1d | ||
|
|
58645f39bb | ||
|
|
0e55823a0c | ||
|
|
ba690480a7 | ||
|
|
faa2306a30 | ||
|
|
c1448c82e9 | ||
|
|
776a56b5bc | ||
|
|
e4883bb737 | ||
|
|
e70afb5e77 | ||
|
|
ee7af18f98 | ||
|
|
ac7de3bf88 | ||
|
|
8883b15aa9 | ||
|
|
e23132c820 | ||
|
|
bec59a585e | ||
|
|
9c649ac7eb | ||
|
|
3455aed503 | ||
|
|
ad1edf57ac | ||
|
|
d64dcb5a44 | ||
|
|
76d7697703 | ||
|
|
96f4f31c17 | ||
|
|
c3f9dfe652 | ||
|
|
502e9a556f | ||
|
|
6f208a6e0e | ||
|
|
1fb52003d5 | ||
|
|
98e1640d9b |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -23,6 +23,8 @@ jobs:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v1
|
||||
env:
|
||||
@@ -35,10 +37,10 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: make manifests
|
||||
- name: Checking if manifests are disaligned
|
||||
- run: make installer
|
||||
- name: Checking if YAML installer file is not aligned
|
||||
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi
|
||||
- name: Checking if manifests generated untracked files
|
||||
- name: Checking if YAML installer generated untracked files
|
||||
run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)"
|
||||
- name: Checking if source code is not formatted
|
||||
run: test -z "$(git diff 2> /dev/null)"
|
||||
|
||||
18
Makefile
18
Makefile
@@ -47,22 +47,26 @@ manager: generate fmt vet
|
||||
run: generate manifests
|
||||
go run ./main.go
|
||||
|
||||
# Creates the single file to install Capsule without any external dependency
|
||||
installer: manifests kustomize
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default > config/install.yaml
|
||||
|
||||
# Install CRDs into a cluster
|
||||
install: manifests kustomize
|
||||
install: installer
|
||||
$(KUSTOMIZE) build config/crd | kubectl apply -f -
|
||||
|
||||
# Uninstall CRDs from a cluster
|
||||
uninstall: manifests kustomize
|
||||
uninstall: installer
|
||||
$(KUSTOMIZE) build config/crd | kubectl delete -f -
|
||||
|
||||
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
|
||||
deploy: manifests kustomize
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default | kubectl apply -f -
|
||||
deploy: installer
|
||||
kubectl apply -f config/install.yaml
|
||||
|
||||
# Remove controller in the configured Kubernetes cluster in ~/.kube/config
|
||||
remove: manifests kustomize
|
||||
$(KUSTOMIZE) build config/default | kubectl delete -f -
|
||||
remove: installer
|
||||
kubectl delete -f config/install.yaml
|
||||
kubectl delete clusterroles.rbac.authorization.k8s.io capsule-namespace-deleter capsule-namespace-provisioner --ignore-not-found
|
||||
kubectl delete clusterrolebindings.rbac.authorization.k8s.io capsule-namespace-deleter capsule-namespace-provisioner --ignore-not-found
|
||||
|
||||
|
||||
23
README.md
23
README.md
@@ -61,17 +61,28 @@ Make sure you have access to a Kubernetes cluster as administrator.
|
||||
There are two ways to install Capsule:
|
||||
|
||||
* Use the Helm Chart available [here](./charts/capsule/README.md)
|
||||
* Use [`kustomize`](https://github.com/kubernetes-sigs/kustomize)
|
||||
* Use the [single YAML file installer](./config/install.yaml)
|
||||
|
||||
## Install with kustomize
|
||||
Ensure you have `kubectl` and `kustomize` installed in your `PATH`.
|
||||
## Install with the single YAML file installer
|
||||
|
||||
Ensure you have `kubectl` installed in your `PATH`.
|
||||
|
||||
Clone this repository and move to the repo folder:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/clastix/capsule
|
||||
$ cd capsule
|
||||
$ make deploy
|
||||
$ kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/master/config/install.yaml
|
||||
namespace/capsule-system created
|
||||
customresourcedefinition.apiextensions.k8s.io/capsuleconfigurations.capsule.clastix.io created
|
||||
customresourcedefinition.apiextensions.k8s.io/tenants.capsule.clastix.io created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/capsule-manager-rolebinding created
|
||||
secret/capsule-ca created
|
||||
secret/capsule-tls created
|
||||
service/capsule-controller-manager-metrics-service created
|
||||
service/capsule-webhook-service created
|
||||
deployment.apps/capsule-controller-manager created
|
||||
capsuleconfiguration.capsule.clastix.io/capsule-default created
|
||||
mutatingwebhookconfiguration.admissionregistration.k8s.io/capsule-mutating-webhook-configuration created
|
||||
validatingwebhookconfiguration.admissionregistration.k8s.io/capsule-validating-webhook-configuration created
|
||||
```
|
||||
|
||||
It will install the Capsule controller in a dedicated namespace `capsule-system`.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/conversion"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
@@ -21,52 +22,52 @@ const (
|
||||
podPriorityAllowedAnnotation = "priorityclass.capsule.clastix.io/allowed"
|
||||
podPriorityAllowedRegexAnnotation = "priorityclass.capsule.clastix.io/allowed-regex"
|
||||
|
||||
enableNodePortsAnnotation = "capsule.clastix.io/enable-node-ports"
|
||||
enableNodePortsAnnotation = "capsule.clastix.io/enable-node-ports"
|
||||
enableExternalNameAnnotation = "capsule.clastix.io/enable-external-name"
|
||||
|
||||
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"
|
||||
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"
|
||||
enablePriorityClassListingAnnotation = "capsule.clastix.io/enable-priorityclass-listing"
|
||||
enablePriorityClassUpdateAnnotation = "capsule.clastix.io/enable-priorityclass-update"
|
||||
enablePriorityClassDeletionAnnotation = "capsule.clastix.io/enable-priorityclass-deletion"
|
||||
)
|
||||
|
||||
func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() []capsulev1beta1.OwnerSpec {
|
||||
func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
|
||||
var serviceKindToAnnotationMap = map[capsulev1beta1.ProxyServiceKind][]string{
|
||||
nodesServiceKind: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
|
||||
storageClassesServiceKind: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
|
||||
ingressClassesServiceKind: {enableIngressClassListingAnnotation, enableIngressClassUpdateAnnotation, enableIngressClassDeletionAnnotation},
|
||||
capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
|
||||
capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
|
||||
capsulev1beta1.IngressClassesProxy: {enableIngressClassListingAnnotation, enableIngressClassUpdateAnnotation, enableIngressClassDeletionAnnotation},
|
||||
capsulev1beta1.PriorityClassesProxy: {enablePriorityClassListingAnnotation, enablePriorityClassUpdateAnnotation, enablePriorityClassDeletionAnnotation},
|
||||
}
|
||||
var annotationToOperationMap = map[string]capsulev1beta1.ProxyOperation{
|
||||
enableNodeListingAnnotation: listOperation,
|
||||
enableNodeUpdateAnnotation: updateOperation,
|
||||
enableNodeDeletionAnnotation: deleteOperation,
|
||||
enableStorageClassListingAnnotation: listOperation,
|
||||
enableStorageClassUpdateAnnotation: updateOperation,
|
||||
enableStorageClassDeletionAnnotation: deleteOperation,
|
||||
enableIngressClassListingAnnotation: listOperation,
|
||||
enableIngressClassUpdateAnnotation: updateOperation,
|
||||
enableIngressClassDeletionAnnotation: deleteOperation,
|
||||
enableNodeListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enableNodeUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enableNodeDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
enableStorageClassListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enableStorageClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enableStorageClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
enableIngressClassListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enableIngressClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enableIngressClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
enablePriorityClassListingAnnotation: capsulev1beta1.ListOperation,
|
||||
enablePriorityClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
|
||||
enablePriorityClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
|
||||
}
|
||||
var annotationToOwnerKindMap = map[string]capsulev1beta1.OwnerKind{
|
||||
ownerUsersAnnotation: "User",
|
||||
ownerGroupsAnnotation: "Group",
|
||||
ownerServiceAccountAnnotation: "ServiceAccount",
|
||||
ownerUsersAnnotation: capsulev1beta1.UserOwner,
|
||||
ownerGroupsAnnotation: capsulev1beta1.GroupOwner,
|
||||
ownerServiceAccountAnnotation: capsulev1beta1.ServiceAccountOwner,
|
||||
}
|
||||
annotations := t.GetAnnotations()
|
||||
|
||||
@@ -86,7 +87,7 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() []capsulev1beta1.OwnerSpec {
|
||||
}
|
||||
}
|
||||
|
||||
var owners []capsulev1beta1.OwnerSpec
|
||||
var owners capsulev1beta1.OwnerListSpec
|
||||
|
||||
var getProxySettingsForOwner = func(ownerName string) (settings []capsulev1beta1.ProxySettings) {
|
||||
ownerOperations, ok := operations[ownerName]
|
||||
@@ -143,9 +144,13 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
}
|
||||
}
|
||||
if t.Spec.ServicesMetadata != nil {
|
||||
dst.Spec.ServicesMetadata = &capsulev1beta1.AdditionalMetadataSpec{
|
||||
AdditionalLabels: t.Spec.ServicesMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: t.Spec.ServicesMetadata.AdditionalAnnotations,
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{
|
||||
AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{
|
||||
AdditionalLabels: t.Spec.ServicesMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: t.Spec.ServicesMetadata.AdditionalAnnotations,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.Spec.StorageClasses != nil {
|
||||
@@ -196,13 +201,12 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
}
|
||||
}
|
||||
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: make([]capsulev1beta1.AllowedIP, len(t.Spec.ExternalServiceIPs.Allowed)),
|
||||
}
|
||||
|
||||
dst.Spec.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{
|
||||
Allowed: allowedIPs,
|
||||
for i, IP := range t.Spec.ExternalServiceIPs.Allowed {
|
||||
dst.Spec.ExternalServiceIPs.Allowed[i] = capsulev1beta1.AllowedIP(IP)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +238,28 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, t.GetName()))
|
||||
}
|
||||
dst.Spec.EnableNodePorts = val
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
|
||||
}
|
||||
if dst.Spec.ServiceOptions.AllowedServices == nil {
|
||||
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
|
||||
}
|
||||
dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.BoolPtr(val)
|
||||
}
|
||||
|
||||
enableExternalName, ok := annotations[enableExternalNameAnnotation]
|
||||
if ok {
|
||||
val, err := strconv.ParseBool(enableExternalName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, t.GetName()))
|
||||
}
|
||||
if dst.Spec.ServiceOptions == nil {
|
||||
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
|
||||
}
|
||||
if dst.Spec.ServiceOptions.AllowedServices == nil {
|
||||
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
|
||||
}
|
||||
dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.BoolPtr(val)
|
||||
}
|
||||
|
||||
// Status
|
||||
@@ -248,6 +273,7 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
delete(dst.ObjectMeta.Annotations, podPriorityAllowedAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, podPriorityAllowedRegexAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableNodePortsAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableExternalNameAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerGroupsAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerUsersAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, ownerServiceAccountAnnotation)
|
||||
@@ -260,6 +286,9 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enableIngressClassDeletionAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enablePriorityClassListingAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enablePriorityClassUpdateAnnotation)
|
||||
delete(dst.ObjectMeta.Annotations, enablePriorityClassDeletionAnnotation)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -291,46 +320,57 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
|
||||
}
|
||||
} else {
|
||||
switch owner.Kind {
|
||||
case "User":
|
||||
case capsulev1beta1.UserOwner:
|
||||
ownersAnnotations[ownerUsersAnnotation] = append(ownersAnnotations[ownerUsersAnnotation], owner.Name)
|
||||
case "Group":
|
||||
case capsulev1beta1.GroupOwner:
|
||||
ownersAnnotations[ownerGroupsAnnotation] = append(ownersAnnotations[ownerGroupsAnnotation], owner.Name)
|
||||
case "ServiceAccount":
|
||||
case capsulev1beta1.ServiceAccountOwner:
|
||||
ownersAnnotations[ownerServiceAccountAnnotation] = append(ownersAnnotations[ownerServiceAccountAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
for _, setting := range owner.ProxyOperations {
|
||||
switch setting.Kind {
|
||||
case nodesServiceKind:
|
||||
case capsulev1beta1.NodesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case listOperation:
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enableNodeListingAnnotation] = append(proxyAnnotations[enableNodeListingAnnotation], owner.Name)
|
||||
case updateOperation:
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enableNodeUpdateAnnotation] = append(proxyAnnotations[enableNodeUpdateAnnotation], owner.Name)
|
||||
case deleteOperation:
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enableNodeDeletionAnnotation] = append(proxyAnnotations[enableNodeDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case storageClassesServiceKind:
|
||||
case capsulev1beta1.PriorityClassesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case listOperation:
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enablePriorityClassListingAnnotation] = append(proxyAnnotations[enablePriorityClassListingAnnotation], owner.Name)
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enablePriorityClassUpdateAnnotation] = append(proxyAnnotations[enablePriorityClassUpdateAnnotation], owner.Name)
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enablePriorityClassDeletionAnnotation] = append(proxyAnnotations[enablePriorityClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case capsulev1beta1.StorageClassesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enableStorageClassListingAnnotation] = append(proxyAnnotations[enableStorageClassListingAnnotation], owner.Name)
|
||||
case updateOperation:
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enableStorageClassUpdateAnnotation] = append(proxyAnnotations[enableStorageClassUpdateAnnotation], owner.Name)
|
||||
case deleteOperation:
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enableStorageClassDeletionAnnotation] = append(proxyAnnotations[enableStorageClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
case ingressClassesServiceKind:
|
||||
case capsulev1beta1.IngressClassesProxy:
|
||||
for _, operation := range setting.Operations {
|
||||
switch operation {
|
||||
case listOperation:
|
||||
case capsulev1beta1.ListOperation:
|
||||
proxyAnnotations[enableIngressClassListingAnnotation] = append(proxyAnnotations[enableIngressClassListingAnnotation], owner.Name)
|
||||
case updateOperation:
|
||||
case capsulev1beta1.UpdateOperation:
|
||||
proxyAnnotations[enableIngressClassUpdateAnnotation] = append(proxyAnnotations[enableIngressClassUpdateAnnotation], owner.Name)
|
||||
case deleteOperation:
|
||||
case capsulev1beta1.DeleteOperation:
|
||||
proxyAnnotations[enableIngressClassDeletionAnnotation] = append(proxyAnnotations[enableIngressClassDeletionAnnotation], owner.Name)
|
||||
}
|
||||
}
|
||||
@@ -372,10 +412,10 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
|
||||
AdditionalAnnotations: src.Spec.NamespacesMetadata.AdditionalAnnotations,
|
||||
}
|
||||
}
|
||||
if src.Spec.ServicesMetadata != nil {
|
||||
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AdditionalMetadata != nil {
|
||||
t.Spec.ServicesMetadata = &AdditionalMetadataSpec{
|
||||
AdditionalLabels: src.Spec.ServicesMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: src.Spec.ServicesMetadata.AdditionalAnnotations,
|
||||
AdditionalLabels: src.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels,
|
||||
AdditionalAnnotations: src.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations,
|
||||
}
|
||||
}
|
||||
if src.Spec.StorageClasses != nil {
|
||||
@@ -420,13 +460,12 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
|
||||
}
|
||||
}
|
||||
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: make([]AllowedIP, len(src.Spec.ExternalServiceIPs.Allowed)),
|
||||
}
|
||||
|
||||
t.Spec.ExternalServiceIPs = &ExternalServiceIPsSpec{
|
||||
Allowed: allowedIPs,
|
||||
for i, IP := range src.Spec.ExternalServiceIPs.Allowed {
|
||||
t.Spec.ExternalServiceIPs.Allowed[i] = AllowedIP(IP)
|
||||
}
|
||||
}
|
||||
if len(src.Spec.ImagePullPolicies) != 0 {
|
||||
@@ -446,7 +485,10 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
|
||||
}
|
||||
}
|
||||
|
||||
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(src.Spec.EnableNodePorts)
|
||||
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil {
|
||||
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
|
||||
t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
|
||||
}
|
||||
|
||||
// Status
|
||||
t.Status = TenantStatus{
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
@@ -42,6 +43,13 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
"foo": "bar",
|
||||
},
|
||||
}
|
||||
var v1beta1ServiceOptions = &capsulev1beta1.ServiceOptions{
|
||||
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
|
||||
AllowedServices: &capsulev1beta1.AllowedServices{
|
||||
NodePort: pointer.BoolPtr(false),
|
||||
ExternalName: pointer.BoolPtr(false),
|
||||
},
|
||||
}
|
||||
var v1beta1AllowedListSpec = &capsulev1beta1.AllowedListSpec{
|
||||
Exact: []string{"foo", "bar"},
|
||||
Regex: "^foo*",
|
||||
@@ -114,7 +122,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Kind: "User",
|
||||
Name: "alice",
|
||||
@@ -163,6 +171,10 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
Kind: "StorageClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
{
|
||||
Kind: "PriorityClasses",
|
||||
Operations: []capsulev1beta1.ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -212,7 +224,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
},
|
||||
NamespaceQuota: &namespaceQuota,
|
||||
NamespacesMetadata: v1beta1AdditionalMetadataSpec,
|
||||
ServicesMetadata: v1beta1AdditionalMetadataSpec,
|
||||
ServiceOptions: v1beta1ServiceOptions,
|
||||
StorageClasses: v1beta1AllowedListSpec,
|
||||
IngressClasses: v1beta1AllowedListSpec,
|
||||
IngressHostnames: v1beta1AllowedListSpec,
|
||||
@@ -247,7 +259,6 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
Exact: []string{"default"},
|
||||
Regex: "^tier-.*$",
|
||||
},
|
||||
EnableNodePorts: false,
|
||||
},
|
||||
Status: capsulev1beta1.TenantStatus{
|
||||
Size: 1,
|
||||
@@ -265,6 +276,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
podAllowedImagePullPolicyAnnotation: "Always,IfNotPresent",
|
||||
enableExternalNameAnnotation: "false",
|
||||
enableNodePortsAnnotation: "false",
|
||||
podPriorityAllowedAnnotation: "default",
|
||||
podPriorityAllowedRegexAnnotation: "^tier-.*$",
|
||||
@@ -279,6 +291,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
enableIngressClassListingAnnotation: "alice,owner-foo,owner-bar",
|
||||
enableIngressClassUpdateAnnotation: "alice,bob",
|
||||
enableIngressClassDeletionAnnotation: "alice,jack",
|
||||
enablePriorityClassListingAnnotation: "jack",
|
||||
},
|
||||
},
|
||||
Spec: TenantSpec{
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
package v1beta1
|
||||
|
||||
// OwnerSpec defines tenant owner name and kind
|
||||
type OwnerSpec struct {
|
||||
Kind OwnerKind `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
// Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
|
||||
Kind OwnerKind `json:"kind"`
|
||||
// Name of tenant owner.
|
||||
Name string `json:"name"`
|
||||
// Proxy settings for tenant owner.
|
||||
ProxyOperations []ProxySettings `json:"proxySettings,omitempty"`
|
||||
}
|
||||
|
||||
@@ -29,9 +31,24 @@ func (p ProxyOperation) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses
|
||||
// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses;PriorityClasses
|
||||
type ProxyServiceKind string
|
||||
|
||||
func (p ProxyServiceKind) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
const (
|
||||
NodesProxy ProxyServiceKind = "Nodes"
|
||||
StorageClassesProxy ProxyServiceKind = "StorageClasses"
|
||||
IngressClassesProxy ProxyServiceKind = "IngressClasses"
|
||||
PriorityClassesProxy ProxyServiceKind = "PriorityClasses"
|
||||
|
||||
ListOperation ProxyOperation = "List"
|
||||
UpdateOperation ProxyOperation = "Update"
|
||||
DeleteOperation ProxyOperation = "Delete"
|
||||
|
||||
UserOwner OwnerKind = "User"
|
||||
GroupOwner OwnerKind = "Group"
|
||||
ServiceAccountOwner OwnerKind = "ServiceAccount"
|
||||
)
|
||||
|
||||
34
api/v1beta1/owner_list.go
Normal file
34
api/v1beta1/owner_list.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
type OwnerListSpec []OwnerSpec
|
||||
|
||||
func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) {
|
||||
sort.Sort(ByKindAndName(o))
|
||||
i := sort.Search(len(o), func(i int) bool {
|
||||
return o[i].Kind >= kind && o[i].Name >= name
|
||||
})
|
||||
|
||||
if i < len(o) && o[i].Kind == kind && o[i].Name == name {
|
||||
return o[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ByKindAndName OwnerListSpec
|
||||
|
||||
func (b ByKindAndName) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
func (b ByKindAndName) Less(i, j int) bool {
|
||||
if b[i].Kind.String() != b[j].Kind.String() {
|
||||
return b[i].Kind.String() < b[j].Kind.String()
|
||||
}
|
||||
return b[i].Name < b[j].Name
|
||||
}
|
||||
func (b ByKindAndName) Swap(i, j int) {
|
||||
b[i], b[j] = b[j], b[i]
|
||||
}
|
||||
83
api/v1beta1/owner_list_test.go
Normal file
83
api/v1beta1/owner_list_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
var bla = OwnerSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "bla",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: IngressClassesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var bar = OwnerSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bar",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var baz = OwnerSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "baz",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"Update"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var fim = OwnerSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "fim",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: NodesProxy,
|
||||
Operations: []ProxyOperation{"List"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var bom = OwnerSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bom",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
{
|
||||
Kind: NodesProxy,
|
||||
Operations: []ProxyOperation{"Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var qip = OwnerSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "qip",
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
Kind: StorageClassesProxy,
|
||||
Operations: []ProxyOperation{"List", "Delete"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var owners = OwnerListSpec{bom, qip, bla, bar, baz, fim}
|
||||
|
||||
assert.Equal(t, owners.FindOwner("bom", GroupOwner), bom)
|
||||
assert.Equal(t, owners.FindOwner("qip", ServiceAccountOwner), qip)
|
||||
assert.Equal(t, owners.FindOwner("bla", UserOwner), bla)
|
||||
assert.Equal(t, owners.FindOwner("bar", GroupOwner), bar)
|
||||
assert.Equal(t, owners.FindOwner("baz", UserOwner), baz)
|
||||
assert.Equal(t, owners.FindOwner("fim", ServiceAccountOwner), fim)
|
||||
assert.Equal(t, owners.FindOwner("notfound", ServiceAccountOwner), OwnerSpec{})
|
||||
}
|
||||
20
api/v1beta1/service_options.go
Normal file
20
api/v1beta1/service_options.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
type ServiceOptions struct {
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
|
||||
AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
|
||||
// Block or deny certain type of Services. Optional.
|
||||
AllowedServices *AllowedServices `json:"allowedServices,omitempty"`
|
||||
}
|
||||
|
||||
type AllowedServices struct {
|
||||
//+kubebuilder:default=true
|
||||
// Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
NodePort *bool `json:"nodePort,omitempty"`
|
||||
//+kubebuilder:default=true
|
||||
// Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
ExternalName *bool `json:"externalName,omitempty"`
|
||||
}
|
||||
@@ -36,3 +36,7 @@ func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
|
||||
t.Status.Namespaces = l
|
||||
t.Status.Size = uint(len(l))
|
||||
}
|
||||
|
||||
func (t *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings {
|
||||
return t.Spec.Owners.FindOwner(name, kind).ProxyOperations
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ const (
|
||||
TenantStateCordoned tenantState = "cordoned"
|
||||
)
|
||||
|
||||
// TenantStatus defines the observed state of Tenant
|
||||
// Returns the observed state of the Tenant
|
||||
type TenantStatus struct {
|
||||
//+kubebuilder:default=active
|
||||
State tenantState `json:"state"`
|
||||
Size uint `json:"size"`
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
// The operational state of the Tenant. Possible values are "active", "cordoned".
|
||||
State tenantState `json:"state"`
|
||||
// How many namespaces are assigned to the Tenant.
|
||||
Size uint `json:"size"`
|
||||
// List of namespaces assigned to the Tenant.
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
}
|
||||
|
||||
@@ -9,27 +9,40 @@ import (
|
||||
|
||||
// TenantSpec defines the desired state of Tenant
|
||||
type TenantSpec struct {
|
||||
Owners []OwnerSpec `json:"owners"`
|
||||
// Specifies the owners of the Tenant. Mandatory.
|
||||
Owners OwnerListSpec `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"`
|
||||
// Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
|
||||
NamespaceQuota *int32 `json:"namespaceQuota,omitempty"`
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
|
||||
NamespacesMetadata *AdditionalMetadataSpec `json:"namespacesMetadata,omitempty"`
|
||||
// Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
|
||||
ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"`
|
||||
// Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
|
||||
StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"`
|
||||
// Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
|
||||
IngressClasses *AllowedListSpec `json:"ingressClasses,omitempty"`
|
||||
// Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional.
|
||||
IngressHostnames *AllowedListSpec `json:"ingressHostnames,omitempty"`
|
||||
// Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
|
||||
ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"`
|
||||
// Specifies the label to control the placement of pods on a given pool of worker nodes. All namesapces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
NetworkPolicies *NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
LimitRanges *LimitRangesSpec `json:"limitRanges,omitempty"`
|
||||
// Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
|
||||
ResourceQuota *ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
// Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
|
||||
AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
|
||||
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"`
|
||||
ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"`
|
||||
PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"`
|
||||
|
||||
//+kubebuilder:default=true
|
||||
EnableNodePorts bool `json:"enableNodePorts,omitempty"`
|
||||
// Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means all the IPs are allowed. Optional.
|
||||
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"`
|
||||
// Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
|
||||
ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"`
|
||||
// Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
|
||||
PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
@@ -83,6 +83,52 @@ func (in *AllowedListSpec) DeepCopy() *AllowedListSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AllowedServices) DeepCopyInto(out *AllowedServices) {
|
||||
*out = *in
|
||||
if in.NodePort != nil {
|
||||
in, out := &in.NodePort, &out.NodePort
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.ExternalName != nil {
|
||||
in, out := &in.ExternalName, &out.ExternalName
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices.
|
||||
func (in *AllowedServices) DeepCopy() *AllowedServices {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AllowedServices)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ByKindAndName, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByKindAndName.
|
||||
func (in ByKindAndName) DeepCopy() ByKindAndName {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ByKindAndName)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) {
|
||||
*out = *in
|
||||
@@ -147,6 +193,27 @@ func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(OwnerListSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerListSpec.
|
||||
func (in OwnerListSpec) DeepCopy() OwnerListSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerListSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) {
|
||||
*out = *in
|
||||
@@ -211,6 +278,31 @@ func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) {
|
||||
*out = *in
|
||||
if in.AdditionalMetadata != nil {
|
||||
in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AllowedServices != nil {
|
||||
in, out := &in.AllowedServices, &out.AllowedServices
|
||||
*out = new(AllowedServices)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions.
|
||||
func (in *ServiceOptions) DeepCopy() *ServiceOptions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceOptions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -275,7 +367,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = *in
|
||||
if in.Owners != nil {
|
||||
in, out := &in.Owners, &out.Owners
|
||||
*out = make([]OwnerSpec, len(*in))
|
||||
*out = make(OwnerListSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -290,9 +382,9 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServicesMetadata != nil {
|
||||
in, out := &in.ServicesMetadata, &out.ServicesMetadata
|
||||
*out = new(AdditionalMetadataSpec)
|
||||
if in.ServiceOptions != nil {
|
||||
in, out := &in.ServiceOptions, &out.ServiceOptions
|
||||
*out = new(ServiceOptions)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.StorageClasses != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.5.0
|
||||
creationTimestamp: null
|
||||
name: tenants.capsule.clastix.io
|
||||
spec:
|
||||
conversion:
|
||||
@@ -564,7 +565,7 @@ spec:
|
||||
served: true
|
||||
storage: false
|
||||
subresources:
|
||||
status: { }
|
||||
status: {}
|
||||
- additionalPrinterColumns:
|
||||
- description: The actual state of the Tenant
|
||||
jsonPath: .status.state
|
||||
@@ -603,6 +604,7 @@ spec:
|
||||
description: TenantSpec defines the desired state of Tenant
|
||||
properties:
|
||||
additionalRoleBindings:
|
||||
description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
|
||||
items:
|
||||
properties:
|
||||
clusterRoleName:
|
||||
@@ -635,6 +637,7 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
containerRegistries:
|
||||
description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -643,10 +646,8 @@ spec:
|
||||
allowedRegex:
|
||||
type: string
|
||||
type: object
|
||||
enableNodePorts:
|
||||
default: true
|
||||
type: boolean
|
||||
externalServiceIPs:
|
||||
description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means all the IPs are allowed. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -657,6 +658,7 @@ spec:
|
||||
- allowed
|
||||
type: object
|
||||
imagePullPolicies:
|
||||
description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
|
||||
items:
|
||||
enum:
|
||||
- Always
|
||||
@@ -665,6 +667,7 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
ingressClasses:
|
||||
description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -674,6 +677,7 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
ingressHostnames:
|
||||
description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -683,6 +687,7 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
limitRanges:
|
||||
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
@@ -751,10 +756,12 @@ spec:
|
||||
type: array
|
||||
type: object
|
||||
namespaceQuota:
|
||||
description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
namespacesMetadata:
|
||||
description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
@@ -766,6 +773,7 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
networkPolicies:
|
||||
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
@@ -1025,20 +1033,24 @@ spec:
|
||||
nodeSelector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namesapces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
|
||||
type: object
|
||||
owners:
|
||||
description: Specifies the owners of the Tenant. Mandatory.
|
||||
items:
|
||||
description: OwnerSpec defines tenant owner name and kind
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
|
||||
enum:
|
||||
- User
|
||||
- Group
|
||||
- ServiceAccount
|
||||
type: string
|
||||
name:
|
||||
description: Name of tenant owner.
|
||||
type: string
|
||||
proxySettings:
|
||||
description: Proxy settings for tenant owner.
|
||||
items:
|
||||
properties:
|
||||
kind:
|
||||
@@ -1046,6 +1058,7 @@ spec:
|
||||
- Nodes
|
||||
- StorageClasses
|
||||
- IngressClasses
|
||||
- PriorityClasses
|
||||
type: string
|
||||
operations:
|
||||
items:
|
||||
@@ -1066,6 +1079,7 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
priorityClasses:
|
||||
description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -1075,6 +1089,7 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
resourceQuotas:
|
||||
description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
@@ -1123,18 +1138,36 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
servicesMetadata:
|
||||
serviceOptions:
|
||||
description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
additionalMetadata:
|
||||
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
additionalLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
additionalLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
allowedServices:
|
||||
description: Block or deny certain type of Services. Optional.
|
||||
properties:
|
||||
externalName:
|
||||
default: true
|
||||
description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
type: boolean
|
||||
nodePort:
|
||||
default: true
|
||||
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
type: boolean
|
||||
type: object
|
||||
type: object
|
||||
storageClasses:
|
||||
description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -1147,16 +1180,19 @@ spec:
|
||||
- owners
|
||||
type: object
|
||||
status:
|
||||
description: TenantStatus defines the observed state of Tenant
|
||||
description: Returns the observed state of the Tenant
|
||||
properties:
|
||||
namespaces:
|
||||
description: List of namespaces assigned to the Tenant.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
size:
|
||||
description: How many namespaces are assigned to the Tenant.
|
||||
type: integer
|
||||
state:
|
||||
default: active
|
||||
description: The operational state of the Tenant. Possible values are "active", "cordoned".
|
||||
enum:
|
||||
- cordoned
|
||||
- active
|
||||
@@ -1169,7 +1205,7 @@ spec:
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: { }
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
||||
@@ -604,6 +604,7 @@ spec:
|
||||
description: TenantSpec defines the desired state of Tenant
|
||||
properties:
|
||||
additionalRoleBindings:
|
||||
description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
|
||||
items:
|
||||
properties:
|
||||
clusterRoleName:
|
||||
@@ -636,6 +637,7 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
containerRegistries:
|
||||
description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -644,10 +646,8 @@ spec:
|
||||
allowedRegex:
|
||||
type: string
|
||||
type: object
|
||||
enableNodePorts:
|
||||
default: true
|
||||
type: boolean
|
||||
externalServiceIPs:
|
||||
description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means all the IPs are allowed. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -658,6 +658,7 @@ spec:
|
||||
- allowed
|
||||
type: object
|
||||
imagePullPolicies:
|
||||
description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
|
||||
items:
|
||||
enum:
|
||||
- Always
|
||||
@@ -666,6 +667,7 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
ingressClasses:
|
||||
description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -675,6 +677,7 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
ingressHostnames:
|
||||
description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -684,6 +687,7 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
limitRanges:
|
||||
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
@@ -752,10 +756,12 @@ spec:
|
||||
type: array
|
||||
type: object
|
||||
namespaceQuota:
|
||||
description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
namespacesMetadata:
|
||||
description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
@@ -767,6 +773,7 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
networkPolicies:
|
||||
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
@@ -1026,20 +1033,24 @@ spec:
|
||||
nodeSelector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namesapces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
|
||||
type: object
|
||||
owners:
|
||||
description: Specifies the owners of the Tenant. Mandatory.
|
||||
items:
|
||||
description: OwnerSpec defines tenant owner name and kind
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
|
||||
enum:
|
||||
- User
|
||||
- Group
|
||||
- ServiceAccount
|
||||
type: string
|
||||
name:
|
||||
description: Name of tenant owner.
|
||||
type: string
|
||||
proxySettings:
|
||||
description: Proxy settings for tenant owner.
|
||||
items:
|
||||
properties:
|
||||
kind:
|
||||
@@ -1047,6 +1058,7 @@ spec:
|
||||
- Nodes
|
||||
- StorageClasses
|
||||
- IngressClasses
|
||||
- PriorityClasses
|
||||
type: string
|
||||
operations:
|
||||
items:
|
||||
@@ -1067,6 +1079,7 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
priorityClasses:
|
||||
description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -1076,6 +1089,7 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
resourceQuotas:
|
||||
description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
@@ -1124,18 +1138,36 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
servicesMetadata:
|
||||
serviceOptions:
|
||||
description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
additionalMetadata:
|
||||
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
additionalLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
additionalLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
allowedServices:
|
||||
description: Block or deny certain type of Services. Optional.
|
||||
properties:
|
||||
externalName:
|
||||
default: true
|
||||
description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
type: boolean
|
||||
nodePort:
|
||||
default: true
|
||||
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
|
||||
type: boolean
|
||||
type: object
|
||||
type: object
|
||||
storageClasses:
|
||||
description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
|
||||
properties:
|
||||
allowed:
|
||||
items:
|
||||
@@ -1148,16 +1180,19 @@ spec:
|
||||
- owners
|
||||
type: object
|
||||
status:
|
||||
description: TenantStatus defines the observed state of Tenant
|
||||
description: Returns the observed state of the Tenant
|
||||
properties:
|
||||
namespaces:
|
||||
description: List of namespaces assigned to the Tenant.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
size:
|
||||
description: How many namespaces are assigned to the Tenant.
|
||||
type: integer
|
||||
state:
|
||||
default: active
|
||||
description: The operational state of the Tenant. Possible values are "active", "cordoned".
|
||||
enum:
|
||||
- cordoned
|
||||
- active
|
||||
|
||||
1642
config/install.yaml
Normal file
1642
config/install.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,4 +7,4 @@ kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: quay.io/clastix/capsule
|
||||
newTag: v0.1.0-rc2
|
||||
newTag: v0.1.0-rc4
|
||||
|
||||
@@ -1,7 +1,136 @@
|
||||
---
|
||||
apiVersion: capsule.clastix.io/v1beta1
|
||||
kind: Tenant
|
||||
metadata:
|
||||
name: tenant-sample
|
||||
name: gas
|
||||
spec:
|
||||
# Add fields here
|
||||
foo: bar
|
||||
additionalRoleBindings:
|
||||
-
|
||||
clusterRoleName: tenant-sample-viewer
|
||||
subjects:
|
||||
-
|
||||
kind: User
|
||||
name: bob
|
||||
containerRegistries:
|
||||
allowed:
|
||||
- docker.io
|
||||
- quay.io
|
||||
allowedRegex: ^\w+.gcr.io$
|
||||
serviceOptions:
|
||||
additionalMetadata:
|
||||
additionalAnnotations:
|
||||
capsule.clastix.io/bgp: "true"
|
||||
additionalLabels:
|
||||
capsule.clastix.io/pool: gas
|
||||
allowedServices:
|
||||
nodePort: false
|
||||
externalName: false
|
||||
externalServiceIPs:
|
||||
allowed:
|
||||
- 10.20.0.0/16
|
||||
- "10.96.42.42"
|
||||
imagePullPolicies:
|
||||
- Always
|
||||
ingressClasses:
|
||||
allowed:
|
||||
- default
|
||||
allowedRegex: ^\w+-lb$
|
||||
ingressHostnames:
|
||||
allowed:
|
||||
- gas.acmecorp.com
|
||||
allowedRegex: ^.*acmecorp.com$
|
||||
limitRanges:
|
||||
items:
|
||||
-
|
||||
limits:
|
||||
-
|
||||
max:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
min:
|
||||
cpu: 50m
|
||||
memory: 5Mi
|
||||
type: Pod
|
||||
-
|
||||
default:
|
||||
cpu: 200m
|
||||
memory: 100Mi
|
||||
defaultRequest:
|
||||
cpu: 100m
|
||||
memory: 10Mi
|
||||
max:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
min:
|
||||
cpu: 50m
|
||||
memory: 5Mi
|
||||
type: Container
|
||||
-
|
||||
max:
|
||||
storage: 10Gi
|
||||
min:
|
||||
storage: 1Gi
|
||||
type: PersistentVolumeClaim
|
||||
namespaceQuota: 3
|
||||
namespacesMetadata:
|
||||
additionalAnnotations:
|
||||
capsule.clastix.io/backup: "false"
|
||||
additionalLabels:
|
||||
capsule.clastix.io/tenant: gas
|
||||
networkPolicies:
|
||||
items:
|
||||
-
|
||||
egress:
|
||||
-
|
||||
to:
|
||||
-
|
||||
ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 192.168.0.0/12
|
||||
ingress:
|
||||
-
|
||||
from:
|
||||
-
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
capsule.clastix.io/tenant: gas
|
||||
-
|
||||
podSelector: {}
|
||||
-
|
||||
ipBlock:
|
||||
cidr: 192.168.0.0/12
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
owners:
|
||||
-
|
||||
kind: User
|
||||
name: bob
|
||||
priorityClasses:
|
||||
allowed:
|
||||
- shared-nodes
|
||||
allowedRegex: ^\w-gas$
|
||||
resourceQuotas:
|
||||
items:
|
||||
-
|
||||
hard:
|
||||
limits.cpu: "8"
|
||||
limits.memory: 16Gi
|
||||
requests.cpu: "8"
|
||||
requests.memory: 16Gi
|
||||
scopes:
|
||||
- NotTerminating
|
||||
-
|
||||
hard:
|
||||
pods: "10"
|
||||
-
|
||||
hard:
|
||||
requests.storage: 100Gi
|
||||
storageClasses:
|
||||
allowed:
|
||||
- default
|
||||
allowedRegex: ^\w+fs$
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
resources:
|
||||
- capsule_v1alpha1_capsuleconfiguration.yaml
|
||||
- capsule_v1alpha1_tenant.yaml
|
||||
- capsule_v1beta1_tenant.yaml
|
||||
|
||||
@@ -53,8 +53,8 @@ func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, r.client, r.obj, func() (err error) {
|
||||
r.obj.SetLabels(r.sync(r.obj.GetLabels(), tenant.Spec.ServicesMetadata.AdditionalLabels))
|
||||
r.obj.SetAnnotations(r.sync(r.obj.GetAnnotations(), tenant.Spec.ServicesMetadata.AdditionalAnnotations))
|
||||
r.obj.SetLabels(r.sync(r.obj.GetLabels(), tenant.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels))
|
||||
r.obj.SetAnnotations(r.sync(r.obj.GetAnnotations(), tenant.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations))
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -78,7 +78,7 @@ func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespa
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tenant.Spec.ServicesMetadata == nil {
|
||||
if tenant.Spec.ServiceOptions == nil || tenant.Spec.ServiceOptions.AdditionalMetadata == nil {
|
||||
return nil, NewNoServicesMetadata(namespacedName.Name)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ spec:
|
||||
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
|
||||
> ```
|
||||
If you need to address specific use-case, the said annotation supports multiple values comma separated
|
||||
|
||||
```yaml
|
||||
capsule.clastix.io/allowed-image-pull-policy: Always,IfNotPresent
|
||||
```
|
||||
|
||||
# What’s next
|
||||
|
||||
|
||||
@@ -85,14 +85,14 @@ capsule-oil-0 <none> 42h
|
||||
production-network-policy <none> 3m
|
||||
```
|
||||
|
||||
an delete the namespace network-policies
|
||||
And delete the namespace network policies
|
||||
|
||||
```
|
||||
alice@caas# kubectl -n oil-production delete networkpolicy production-network-policy
|
||||
```
|
||||
|
||||
|
||||
However, the Capsule controller prevents Alice to delete the tenant network policy:
|
||||
However, the Capsule controller prevents Alice from deleting the tenant network policy:
|
||||
|
||||
```
|
||||
alice@caas# kubectl -n oil-production delete networkpolicy capsule-oil-0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Assign permissions
|
||||
Alice acts as the tenant admin. Other users can operate inside the tenant with different levels of permissions and authorizations. Alice is responsible for creating additional roles and assigning these roles to other users to work in the same tenant.
|
||||
|
||||
One of the key design principles of the Capsule is the self-provisioning management from the tenant owner's perspective. Alice, the tenant owner, does not need to interact with Bill, the cluster admin, to complete her day-by-day duties. On the other side, Bill has not to deal with multiple requests coming from multiple tenant owners that probably will overwhelm him.
|
||||
One of the key design principles of the Capsule is the self-provisioning management from the tenant owner's perspective. Alice, the tenant owner, does not need to interact with Bill, the cluster admin, to complete her day-by-day duties. On the other side, Bill does not have to deal with multiple requests coming from multiple tenant owners that probably will overwhelm him.
|
||||
|
||||
Capsule leaves Alice the freedom to create RBAC roles at the namespace level, or using the pre-defined cluster roles already available in Kubernetes, and assign them to other users in the tenant. Since roles and rolebindings are limited to a namespace scope, Alice can assign the roles to the other users accessing the same tenant only after the namespace is created. This gives Alice the power to administer the tenant without the intervention of the cluster admin.
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ roleRef:
|
||||
name: 'psp:privileged'
|
||||
```
|
||||
|
||||
With the above example, Capsule is forbidding to any authenticated user in `oil-production` namespace to run privileged pods and let them to performs privilege escalation as declared by the Cluster Role `psp:privileged`.
|
||||
With the above example, Capsule is forbidding any authenticated user in `oil-production` namespace to run privileged pods and to perform privilege escalation as declared by the Cluster Role `psp:privileged`.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign to Alice the permissions to create custom resources in her tenant. [Create Custom Resources](./custom-resources.md).
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
scopes:
|
||||
- NotTerminating
|
||||
- hard:
|
||||
pods: "100"
|
||||
pods: "10"
|
||||
services: "50"
|
||||
- hard:
|
||||
requests.storage: 10Gi
|
||||
|
||||
@@ -16,4 +16,12 @@ In case of a data loss, the right thing to do is to restore the cluster with **V
|
||||
./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.
|
||||
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.
|
||||
|
||||
Additionally you can also specify a selected range of tenants to be restored:
|
||||
|
||||
```bash
|
||||
./velero-restore.sh --tenant "gas oil" restore
|
||||
```
|
||||
|
||||
In this way, only the tenants **gas** and **oil** will be restored.
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("creating a Namespace with an additional Role Binding", func()
|
||||
Name: "additional-role-binding",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "dale",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() {
|
||||
Name: "allowed-external-ip",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("enforcing a Container Registry", func() {
|
||||
Name: "container-registry",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "matt",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
Name: "tenant-assigned-custom-group",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
|
||||
108
e2e/disable_externalname_test.go
Normal file
108
e2e/disable_externalname_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
//+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"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating an ExternalName service when it is disabled for Tenant", func() {
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "disable-external-service",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
ServiceOptions: &capsulev1beta1.ServiceOptions{
|
||||
AllowedServices: &capsulev1beta1.AllowedServices{
|
||||
ExternalName: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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 fail creating a service with ExternalService type", func() {
|
||||
ns := NewNamespace("disable-external-service")
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
EventuallyCreation(func() error {
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cluster-ip",
|
||||
Namespace: ns.GetName(),
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeClusterIP,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Port: 8888,
|
||||
TargetPort: intstr.IntOrString{
|
||||
Type: intstr.Int,
|
||||
IntVal: 8888,
|
||||
},
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
|
||||
return err
|
||||
}).Should(Succeed())
|
||||
|
||||
EventuallyCreation(func() error {
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "disable-external-service",
|
||||
Namespace: ns.GetName(),
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeExternalName,
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Port: 9999,
|
||||
TargetPort: intstr.IntOrString{
|
||||
Type: intstr.Int,
|
||||
IntVal: 9999,
|
||||
},
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cs := ownerClient(tnt.Spec.Owners[0])
|
||||
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
|
||||
return err
|
||||
}).ShouldNot(Succeed())
|
||||
})
|
||||
})
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
)
|
||||
@@ -23,13 +24,17 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f
|
||||
Name: "disable-node-ports",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
EnableNodePorts: false,
|
||||
ServiceOptions: &capsulev1beta1.ServiceOptions{
|
||||
AllowedServices: &capsulev1beta1.AllowedServices{
|
||||
NodePort: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", fu
|
||||
Name: "enable-node-ports",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun
|
||||
Name: "awesome",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
@@ -36,7 +36,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun
|
||||
Name: "awesome-tenant",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
|
||||
@@ -22,7 +22,7 @@ var _ = Describe("enforcing some defined ImagePullPolicy", func() {
|
||||
Name: "image-pull-policies",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "alex",
|
||||
Kind: "User",
|
||||
|
||||
@@ -22,7 +22,7 @@ var _ = Describe("enforcing a defined ImagePullPolicy", func() {
|
||||
Name: "image-pull-policy",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "axel",
|
||||
Kind: "User",
|
||||
|
||||
@@ -24,7 +24,7 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
Name: "ingress-class",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "ingress",
|
||||
Kind: "User",
|
||||
|
||||
@@ -25,7 +25,7 @@ var _ = Describe("when handling Ingress hostnames collision", func() {
|
||||
Name: "ingress-hostnames-allowed-collision",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "ingress-allowed",
|
||||
Kind: "User",
|
||||
|
||||
@@ -25,7 +25,7 @@ var _ = Describe("when handling Ingress hostnames collision", func() {
|
||||
Name: "ingress-hostnames-denied-collision",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "ingress-denied",
|
||||
Kind: "User",
|
||||
|
||||
@@ -25,7 +25,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
|
||||
Name: "ingress-hostnames",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "hostname",
|
||||
Kind: "User",
|
||||
|
||||
@@ -19,7 +19,7 @@ var _ = Describe("creating a Namespace creation with no Tenant assigned", func()
|
||||
It("should fail", func() {
|
||||
tnt := &capsulev1beta1.Tenant{
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "missing",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("creating several Namespaces for a Tenant", func() {
|
||||
Name: "capsule-labels",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "charlie",
|
||||
Kind: "User",
|
||||
|
||||
@@ -22,7 +22,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
|
||||
Name: "tenant-metadata",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
|
||||
@@ -21,7 +21,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun
|
||||
Name: "tenant-assigned",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
|
||||
@@ -22,7 +22,7 @@ var _ = Describe("creating a Namespace in over-quota of three", func() {
|
||||
Name: "over-quota-tenant",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "bob",
|
||||
Kind: "User",
|
||||
|
||||
@@ -26,7 +26,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
Name: "tenant-owner",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "ruby",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("enforcing a Priority Class", func() {
|
||||
Name: "priority-class",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "george",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled"
|
||||
Name: "tenant-protected-namespace",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
|
||||
@@ -27,7 +27,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() {
|
||||
Name: "tenant-resources-changes",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "bobby",
|
||||
Kind: "User",
|
||||
|
||||
@@ -22,7 +22,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func()
|
||||
Name: "tenant-non-owned",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "undefined",
|
||||
Kind: "User",
|
||||
|
||||
@@ -21,7 +21,7 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Name: "tenant-one",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
@@ -34,7 +34,7 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Name: "tenant-two",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
@@ -47,7 +47,7 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Name: "tenant-three",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "Group",
|
||||
@@ -60,7 +60,7 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Name: "tenant-four",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "Group",
|
||||
|
||||
@@ -21,7 +21,7 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi
|
||||
Name: "tenant-one",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
@@ -34,7 +34,7 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi
|
||||
Name: "tenant-two",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
|
||||
@@ -27,20 +27,22 @@ var _ = Describe("adding metadata to Service objects", func() {
|
||||
Name: "service-metadata",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
ServicesMetadata: &capsulev1beta1.AdditionalMetadataSpec{
|
||||
AdditionalLabels: map[string]string{
|
||||
"k8s.io/custom-label": "foo",
|
||||
"clastix.io/custom-label": "bar",
|
||||
},
|
||||
AdditionalAnnotations: map[string]string{
|
||||
"k8s.io/custom-annotation": "bizz",
|
||||
"clastix.io/custom-annotation": "buzz",
|
||||
ServiceOptions: &capsulev1beta1.ServiceOptions{
|
||||
AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{
|
||||
AdditionalLabels: map[string]string{
|
||||
"k8s.io/custom-label": "foo",
|
||||
"clastix.io/custom-label": "bar",
|
||||
},
|
||||
AdditionalAnnotations: map[string]string{
|
||||
"k8s.io/custom-annotation": "bizz",
|
||||
"clastix.io/custom-annotation": "buzz",
|
||||
},
|
||||
},
|
||||
},
|
||||
AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{
|
||||
@@ -98,7 +100,7 @@ var _ = Describe("adding metadata to Service objects", func() {
|
||||
By("checking additional labels", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: svc.GetName(), Namespace: ns.GetName()}, svc)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.ServicesMetadata.AdditionalLabels {
|
||||
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels {
|
||||
ok, _ = HaveKeyWithValue(k, v).Match(svc.Labels)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -110,7 +112,7 @@ var _ = Describe("adding metadata to Service objects", func() {
|
||||
By("checking additional annotations", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: svc.GetName(), Namespace: ns.GetName()}, svc)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.ServicesMetadata.AdditionalAnnotations {
|
||||
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations {
|
||||
ok, _ = HaveKeyWithValue(k, v).Match(svc.Annotations)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -153,7 +155,7 @@ var _ = Describe("adding metadata to Service objects", func() {
|
||||
By("checking additional labels", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ep.GetName(), Namespace: ns.GetName()}, ep)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.ServicesMetadata.AdditionalLabels {
|
||||
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels {
|
||||
ok, _ = HaveKeyWithValue(k, v).Match(ep.Labels)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -165,7 +167,7 @@ var _ = Describe("adding metadata to Service objects", func() {
|
||||
By("checking additional annotations", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ep.GetName(), Namespace: ns.GetName()}, ep)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.ServicesMetadata.AdditionalAnnotations {
|
||||
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations {
|
||||
ok, _ = HaveKeyWithValue(k, v).Match(ep.Annotations)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -212,7 +214,7 @@ var _ = Describe("adding metadata to Service objects", func() {
|
||||
By("checking additional annotations EndpointSlice", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.ServicesMetadata.AdditionalAnnotations {
|
||||
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations {
|
||||
ok, _ = HaveKeyWithValue(k, v).Match(eps.Annotations)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -224,7 +226,7 @@ var _ = Describe("adding metadata to Service objects", func() {
|
||||
By("checking additional labels on EndpointSlice", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.ServicesMetadata.AdditionalLabels {
|
||||
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels {
|
||||
ok, _ = HaveKeyWithValue(k, v).Match(eps.Labels)
|
||||
if !ok {
|
||||
return false
|
||||
|
||||
@@ -24,7 +24,7 @@ var _ = Describe("when Tenant handles Storage classes", func() {
|
||||
Name: "storage-class",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "storage",
|
||||
Kind: "User",
|
||||
|
||||
@@ -24,7 +24,7 @@ var _ = Describe("cordoning a Tenant", func() {
|
||||
Name: "tenant-cordoning",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "jim",
|
||||
Kind: "User",
|
||||
|
||||
@@ -23,7 +23,7 @@ var _ = Describe("when a second Tenant contains an already declared allowed Ingr
|
||||
Name: "allowed-collision-ingress-hostnames",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "first-user",
|
||||
Kind: "User",
|
||||
@@ -59,7 +59,7 @@ var _ = Describe("when a second Tenant contains an already declared allowed Ingr
|
||||
Name: fmt.Sprintf("%s-%d", tnt.GetName(), i),
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "second-user",
|
||||
Kind: "User",
|
||||
@@ -100,7 +100,7 @@ var _ = Describe("when a second Tenant contains an already declared allowed Ingr
|
||||
Name: fmt.Sprintf("%s-%d", tnt.GetName(), i),
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "second-user",
|
||||
Kind: "User",
|
||||
|
||||
@@ -22,7 +22,7 @@ var _ = Describe("when a second Tenant contains an already declared allowed Ingr
|
||||
Name: "no-collision-ingress-hostnames",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "first-user",
|
||||
Kind: "User",
|
||||
@@ -53,7 +53,7 @@ var _ = Describe("when a second Tenant contains an already declared allowed Ingr
|
||||
Name: fmt.Sprintf("%s-%d", tnt.GetName(), i),
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "second-user",
|
||||
Kind: "User",
|
||||
|
||||
@@ -21,7 +21,7 @@ var _ = Describe("creating a Tenant with wrong name", func() {
|
||||
Name: "non_rfc_dns_1123",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
|
||||
@@ -27,7 +27,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() {
|
||||
Name: "tenant-resources-changes",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "laura",
|
||||
Kind: "User",
|
||||
|
||||
@@ -27,7 +27,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() {
|
||||
Name: "tenant-resources",
|
||||
},
|
||||
Spec: capsulev1beta1.TenantSpec{
|
||||
Owners: []capsulev1beta1.OwnerSpec{
|
||||
Owners: capsulev1beta1.OwnerListSpec{
|
||||
{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
|
||||
@@ -30,6 +30,7 @@ fi
|
||||
|
||||
USER=$1
|
||||
TENANT=$2
|
||||
GROUP=$3
|
||||
|
||||
if [[ -z ${USER} ]]; then
|
||||
echo "User has not been specified!"
|
||||
@@ -41,13 +42,18 @@ if [[ -z ${TENANT} ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GROUP=capsule.clastix.io
|
||||
if [[ -z ${GROUP} ]]; then
|
||||
GROUP=capsule.clastix.io
|
||||
fi
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
echo "creating certs in TMPDIR ${TMPDIR} "
|
||||
|
||||
MERGED_GROUPS=$(echo "/O=$GROUP" | sed "s/,/\/O=/g")
|
||||
echo "merging groups ${MERGED_GROUPS}"
|
||||
|
||||
openssl genrsa -out ${TMPDIR}/tls.key 2048
|
||||
openssl req -new -key ${TMPDIR}/tls.key -subj "/CN=${USER}/O=${GROUP}" -out ${TMPDIR}/${USER}-${TENANT}.csr
|
||||
openssl req -new -key ${TMPDIR}/tls.key -subj "/CN=${USER}${MERGED_GROUPS}" -out ${TMPDIR}/${USER}-${TENANT}.csr
|
||||
|
||||
# Clean any previously created CSR for the same user.
|
||||
kubectl delete csr ${USER}-${TENANT} 2>/dev/null || true
|
||||
|
||||
@@ -24,6 +24,7 @@ fi
|
||||
|
||||
USER=$1
|
||||
TENANT=$2
|
||||
GROUP=$3
|
||||
|
||||
if [[ -z ${USER} ]]; then
|
||||
echo "User has not been specified!"
|
||||
@@ -35,13 +36,19 @@ if [[ -z ${TENANT} ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GROUP=capsule.clastix.io
|
||||
if [[ -z ${GROUP} ]]; then
|
||||
GROUP=capsule.clastix.io
|
||||
fi
|
||||
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
echo "creating certs in TMPDIR ${TMPDIR} "
|
||||
|
||||
MERGED_GROUPS=$(echo "/O=$GROUP" | sed "s/,/\/O=/g")
|
||||
echo "merging groups ${MERGED_GROUPS}"
|
||||
|
||||
openssl genrsa -out ${USER}-${TENANT}.key 2048
|
||||
openssl req -new -key ${USER}-${TENANT}.key -subj "/CN=${USER}/O=${GROUP}" -out ${TMPDIR}/${USER}-${TENANT}.csr
|
||||
openssl req -new -key ${USER}-${TENANT}.key -subj "/CN=${USER}${MERGED_GROUPS}" -out ${TMPDIR}/${USER}-${TENANT}.csr
|
||||
|
||||
# Clean any previously created CSR for the same user.
|
||||
kubectl delete csr ${USER}-${TENANT} 2>/dev/null || true
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
# let script exit if an unsed variable is used
|
||||
#set -o nounset
|
||||
|
||||
KUBECFGFILE="~/.kube/config"
|
||||
KUBECFGFILE="$HOME/.kube/config"
|
||||
KUBEOPTIONS="--kubeconfig=$KUBECFGFILE"
|
||||
TMPDIR=/tmp
|
||||
TENANTS=""
|
||||
|
||||
# Print usage to stdout.
|
||||
# Arguments:
|
||||
@@ -22,11 +23,13 @@ TMPDIR=/tmp
|
||||
usage () {
|
||||
printf "Usage: $0 [flags] commands\n"
|
||||
printf "Flags:\n"
|
||||
printf "\t-c, --kubeconfig\tPath to the kubeconfig file to use for CLI requests.\n"
|
||||
printf "\t-c, --kubeconfig /path/to/config\tPath to the kubeconfig file to use for CLI requests.\n"
|
||||
printf "\t-t, --tenant \"gas oil\"\t\tSpecify one or more tenants to be restored.\n"
|
||||
printf "Commands:\n"
|
||||
printf "\trestore\t\t\tPerform the restore on the cluster, patching the right object fields.\n"
|
||||
printf "\n"
|
||||
printf "E.g. [restore]:\t$0 -c /path/to/kubeconfig restore\n"
|
||||
printf "E.g. [restore]:\t$0 -t \"oil\" restore\n"
|
||||
}
|
||||
|
||||
# Update KUBEOPTIONS global var.
|
||||
@@ -59,9 +62,15 @@ check_prerequisite () {
|
||||
# Outputs:
|
||||
# list of the tenants.
|
||||
get_tenant_list () {
|
||||
tenants=$(kubectl "$KUBEOPTIONS" get tnt \
|
||||
--no-headers -o custom-columns=":.metadata.name")
|
||||
echo $tenants
|
||||
if [ ! -z "$TENANTS" ]; then
|
||||
echo "$TENANTS"
|
||||
return
|
||||
else
|
||||
tenants=$(kubectl "$KUBEOPTIONS" get tnt \
|
||||
--no-headers -o custom-columns=":.metadata.name")
|
||||
echo $tenants
|
||||
fi
|
||||
return
|
||||
}
|
||||
|
||||
# Retrive namespace list.
|
||||
@@ -87,12 +96,13 @@ get_namespace_list () {
|
||||
cluster_backup () {
|
||||
# Retrieve tenant names and uids.
|
||||
owner_reference=$(kubectl "$KUBEOPTIONS" get tenants.capsule.clastix.io --no-headers \
|
||||
-o custom-columns=":.metadata.name,:.metadata.uid")
|
||||
-o custom-columns=":apiVersion,:.metadata.name,:.metadata.uid")
|
||||
|
||||
# Store information inside /tmp/tenant_$(tenant_name).
|
||||
while IFS= read -r line; do
|
||||
tnt=$(echo "$line" | awk '{ print $1 }')
|
||||
uid=$(echo "$line" | awk '{ print $2 }')
|
||||
apiVersion=$(echo "$line" | awk '{ print $1 }')
|
||||
tnt=$(echo "$line" | awk '{ print $2 }')
|
||||
uid=$(echo "$line" | awk '{ print $3 }')
|
||||
|
||||
cat <<EOF > "$TMPDIR/tenant_$tnt"
|
||||
{
|
||||
@@ -100,7 +110,7 @@ cluster_backup () {
|
||||
"path": "/metadata/ownerReferences",
|
||||
"value": [
|
||||
{
|
||||
"apiVersion": "capsule.clastix.io/v1alpha1",
|
||||
"apiVersion": "${apiVersion}",
|
||||
"blockOwnerDeletion": true,
|
||||
"controller": true,
|
||||
"kind": "Tenant",
|
||||
@@ -168,6 +178,10 @@ while :; do
|
||||
update_kube_options
|
||||
shift
|
||||
;;
|
||||
-t|--tenant)
|
||||
TENANTS="${2}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
esac
|
||||
|
||||
@@ -189,7 +189,7 @@ func (h *handler) listTenantsForOwnerKind(ctx context.Context, ownerKind string,
|
||||
return tntList, err
|
||||
}
|
||||
|
||||
func (h *handler) isTenantOwner(owners []capsulev1beta1.OwnerSpec, userInfo authenticationv1.UserInfo) bool {
|
||||
func (h *handler) isTenantOwner(owners capsulev1beta1.OwnerListSpec, userInfo authenticationv1.UserInfo) bool {
|
||||
for _, owner := range owners {
|
||||
switch owner.Kind {
|
||||
case "User", "ServiceAccount":
|
||||
|
||||
@@ -41,3 +41,13 @@ func NewNodePortDisabledError() error {
|
||||
func (nodePortDisabled) Error() string {
|
||||
return "NodePort service types are forbidden for the tenant: please, reach out to the system administrators"
|
||||
}
|
||||
|
||||
type externalNameDisabled struct{}
|
||||
|
||||
func NewExternalNameDisabledError() error {
|
||||
return &externalNameDisabled{}
|
||||
}
|
||||
|
||||
func (externalNameDisabled) Error() string {
|
||||
return "ExternalName service types are forbidden for the tenant: please, reach out to the system administrators"
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
|
||||
|
||||
tnt := tntList.Items[0]
|
||||
|
||||
if svc.Spec.Type == corev1.ServiceTypeNodePort && !tnt.Spec.EnableNodePorts {
|
||||
if svc.Spec.Type == corev1.ServiceTypeNodePort && tnt.Spec.ServiceOptions != nil && tnt.Spec.ServiceOptions.AllowedServices != nil && !*tnt.Spec.ServiceOptions.AllowedServices.NodePort {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenNodePort", "Service %s/%s cannot be type of NodePort for the current Tenant", req.Namespace, req.Name)
|
||||
|
||||
response := admission.Denied(NewNodePortDisabledError().Error())
|
||||
@@ -52,6 +52,14 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
|
||||
return &response
|
||||
}
|
||||
|
||||
if svc.Spec.Type == corev1.ServiceTypeExternalName && tnt.Spec.ServiceOptions != nil && tnt.Spec.ServiceOptions.AllowedServices != nil && !*tnt.Spec.ServiceOptions.AllowedServices.ExternalName {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenExternalName", "Service %s/%s cannot be type of ExternalName for the current Tenant", req.Namespace, req.Name)
|
||||
|
||||
response := admission.Denied(NewExternalNameDisabledError().Error())
|
||||
|
||||
return &response
|
||||
}
|
||||
|
||||
if svc.Spec.ExternalIPs == nil || tnt.Spec.ExternalServiceIPs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user