From 8899c03ec176d41be695c05227237d19104918c6 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Wed, 21 Sep 2022 10:20:37 +0200 Subject: [PATCH 01/26] perf(dockerfile): using go modules caching --- Dockerfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 097361bc..7dcab99c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,6 @@ # Build the manager binary FROM golang:1.18 as builder -ARG TARGETARCH -ARG GIT_HEAD_COMMIT -ARG GIT_TAG_COMMIT -ARG GIT_LAST_TAG -ARG GIT_MODIFIED -ARG GIT_REPO -ARG BUILD_DATE - WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod @@ -17,6 +9,14 @@ COPY go.sum go.sum # and so that source changes don't invalidate our downloaded layer RUN go mod download +ARG TARGETARCH +ARG GIT_HEAD_COMMIT +ARG GIT_TAG_COMMIT +ARG GIT_LAST_TAG +ARG GIT_MODIFIED +ARG GIT_REPO +ARG BUILD_DATE + # Copy the go source COPY main.go main.go COPY version.go version.go From 940a109748498dbe829266a96601fb1b4c298ee4 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 22 Sep 2022 22:29:37 +0200 Subject: [PATCH 02/26] chore(makefile): downloading locally golangci-lint --- .golangci.yml | 2 +- Makefile | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 9980ae83..7f62eb65 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -40,7 +40,7 @@ issues: - Using the variable on range scope .* in function literal service: - golangci-lint-version: 1.33.x + golangci-lint-version: 1.45.2 run: skip-files: diff --git a/Makefile b/Makefile index c4b6b668..ab42d297 100644 --- a/Makefile +++ b/Makefile @@ -213,10 +213,14 @@ bundle-build: goimports: goimports -w -l -local "github.com/clastix/capsule" . +GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint +golangci-lint: ## Download golangci-lint locally if necessary. + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2) + # Linting code as PR is expecting .PHONY: golint -golint: - golangci-lint run -c .golangci.yml +golint: golangci-lint + $(GOLANGCI_LINT) run -c .golangci.yml # Running e2e tests in a KinD instance .PHONY: e2e From 191e13a1a7b6f7275b0525cb648eaecdd569d95f Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:20:05 -0400 Subject: [PATCH 03/26] chore(controller-gen): upgrading to 0.10.0 --- Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ab42d297..451cfb3a 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,6 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) # Image URL to use all building/pushing image targets IMG ?= clastix/capsule:$(VERSION) -# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) -CRD_OPTIONS ?= "crd:preserveUnknownFields=false" # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -72,7 +70,7 @@ remove: installer # Generate manifests e.g. CRD, RBAC etc. manifests: controller-gen - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases # Generate code generate: controller-gen @@ -164,7 +162,7 @@ docker-push: CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. - $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0) + $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0) APIDOCS_GEN = $(shell pwd)/bin/crdoc apidocs-gen: ## Download crdoc locally if necessary. From bdac162f442aad4edebddbc60f0a3de428a54558 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:35:25 +0200 Subject: [PATCH 04/26] feat: introducing v1beta2 api group --- PROJECT | 17 + api/v1alpha1/capsuleconfiguration_types.go | 3 + api/v1beta1/allowed_list.go | 1 + api/v1beta1/deny_wildcard.go | 4 +- api/v1beta1/forbidden_list.go | 1 + api/v1beta2/additional_metadata.go | 9 + api/v1beta2/additional_role_bindings.go | 12 + api/v1beta2/allowed_list.go | 37 ++ api/v1beta2/allowed_list_test.go | 73 +++ api/v1beta2/capsuleconfiguration_funcs.go | 131 ++++ api/v1beta2/capsuleconfiguration_types.go | 75 +++ api/v1beta2/conversion_hub.go | 401 ++++++++++++ api/v1beta2/custom_resource_quota.go | 59 ++ api/v1beta2/forbidden_list.go | 37 ++ api/v1beta2/forbidden_list_test.go | 73 +++ api/v1beta2/groupversion_info.go | 23 + api/v1beta2/hostname_collision_scope.go | 14 + api/v1beta2/image_pull_policy.go | 11 + api/v1beta2/ingress_options.go | 26 + api/v1beta2/limit_ranges.go | 10 + api/v1beta2/namespace_options.go | 16 + api/v1beta2/network_policy.go | 12 + api/v1beta2/owner.go | 57 ++ api/v1beta2/owner_list.go | 41 ++ api/v1beta2/owner_list_test.go | 86 +++ api/v1beta2/resource_quota.go | 21 + api/v1beta2/service_allowed_ips.go | 11 + api/v1beta2/service_allowed_types.go | 16 + api/v1beta2/service_options.go | 13 + api/v1beta2/tenant_annotations.go | 26 + api/v1beta2/tenant_func.go | 38 ++ api/v1beta2/tenant_labels.go | 32 + api/v1beta2/tenant_status.go | 23 + api/v1beta2/tenant_types.go | 74 +++ api/v1beta2/zz_generated.deepcopy.go | 672 +++++++++++++++++++++ main.go | 2 + 36 files changed, 2155 insertions(+), 2 deletions(-) create mode 100644 api/v1beta2/additional_metadata.go create mode 100644 api/v1beta2/additional_role_bindings.go create mode 100644 api/v1beta2/allowed_list.go create mode 100644 api/v1beta2/allowed_list_test.go create mode 100644 api/v1beta2/capsuleconfiguration_funcs.go create mode 100644 api/v1beta2/capsuleconfiguration_types.go create mode 100644 api/v1beta2/conversion_hub.go create mode 100644 api/v1beta2/custom_resource_quota.go create mode 100644 api/v1beta2/forbidden_list.go create mode 100644 api/v1beta2/forbidden_list_test.go create mode 100644 api/v1beta2/groupversion_info.go create mode 100644 api/v1beta2/hostname_collision_scope.go create mode 100644 api/v1beta2/image_pull_policy.go create mode 100644 api/v1beta2/ingress_options.go create mode 100644 api/v1beta2/limit_ranges.go create mode 100644 api/v1beta2/namespace_options.go create mode 100644 api/v1beta2/network_policy.go create mode 100644 api/v1beta2/owner.go create mode 100644 api/v1beta2/owner_list.go create mode 100644 api/v1beta2/owner_list_test.go create mode 100644 api/v1beta2/resource_quota.go create mode 100644 api/v1beta2/service_allowed_ips.go create mode 100644 api/v1beta2/service_allowed_types.go create mode 100644 api/v1beta2/service_options.go create mode 100644 api/v1beta2/tenant_annotations.go create mode 100644 api/v1beta2/tenant_func.go create mode 100644 api/v1beta2/tenant_labels.go create mode 100644 api/v1beta2/tenant_status.go create mode 100644 api/v1beta2/tenant_types.go create mode 100644 api/v1beta2/zz_generated.deepcopy.go diff --git a/PROJECT b/PROJECT index 78f90d36..72aada3c 100644 --- a/PROJECT +++ b/PROJECT @@ -36,4 +36,21 @@ resources: kind: Tenant path: github.com/clastix/capsule/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: false + domain: clastix.io + group: capsule + kind: Tenant + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 +- api: + crdVersion: v1 + namespaced: false + controller: true + domain: clastix.io + group: capsule + kind: CapsuleConfiguration + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 version: "3" diff --git a/api/v1alpha1/capsuleconfiguration_types.go b/api/v1alpha1/capsuleconfiguration_types.go index 21b6e9df..147bc485 100644 --- a/api/v1alpha1/capsuleconfiguration_types.go +++ b/api/v1alpha1/capsuleconfiguration_types.go @@ -20,6 +20,7 @@ type CapsuleConfigurationSpec struct { ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"` } +// +kubebuilder:storageversion // +kubebuilder:object:root=true // +kubebuilder:resource:scope=Cluster @@ -31,6 +32,8 @@ type CapsuleConfiguration struct { Spec CapsuleConfigurationSpec `json:"spec,omitempty"` } +func (in *CapsuleConfiguration) Hub() {} + // +kubebuilder:object:root=true // CapsuleConfigurationList contains a list of CapsuleConfiguration. diff --git a/api/v1beta1/allowed_list.go b/api/v1beta1/allowed_list.go index b5fd1aff..d7e24cac 100644 --- a/api/v1beta1/allowed_list.go +++ b/api/v1beta1/allowed_list.go @@ -1,5 +1,6 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 + //nolint:dupl package v1beta1 diff --git a/api/v1beta1/deny_wildcard.go b/api/v1beta1/deny_wildcard.go index b6084977..10528f5e 100644 --- a/api/v1beta1/deny_wildcard.go +++ b/api/v1beta1/deny_wildcard.go @@ -4,11 +4,11 @@ package v1beta1 const ( - denyWildcard = "capsule.clastix.io/deny-wildcard" + DenyWildcard = "capsule.clastix.io/deny-wildcard" ) func (t *Tenant) IsWildcardDenied() bool { - if v, ok := t.Annotations[denyWildcard]; ok && v == "true" { + if v, ok := t.Annotations[DenyWildcard]; ok && v == "true" { return true } diff --git a/api/v1beta1/forbidden_list.go b/api/v1beta1/forbidden_list.go index 0b02d75d..7816dd5b 100644 --- a/api/v1beta1/forbidden_list.go +++ b/api/v1beta1/forbidden_list.go @@ -1,5 +1,6 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 + //nolint:dupl package v1beta1 diff --git a/api/v1beta2/additional_metadata.go b/api/v1beta2/additional_metadata.go new file mode 100644 index 00000000..9d708e73 --- /dev/null +++ b/api/v1beta2/additional_metadata.go @@ -0,0 +1,9 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type AdditionalMetadataSpec struct { + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` +} diff --git a/api/v1beta2/additional_role_bindings.go b/api/v1beta2/additional_role_bindings.go new file mode 100644 index 00000000..298aa8bd --- /dev/null +++ b/api/v1beta2/additional_role_bindings.go @@ -0,0 +1,12 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import rbacv1 "k8s.io/api/rbac/v1" + +type AdditionalRoleBindingsSpec struct { + ClusterRoleName string `json:"clusterRoleName"` + // kubebuilder:validation:Minimum=1 + Subjects []rbacv1.Subject `json:"subjects"` +} diff --git a/api/v1beta2/allowed_list.go b/api/v1beta2/allowed_list.go new file mode 100644 index 00000000..33e02c46 --- /dev/null +++ b/api/v1beta2/allowed_list.go @@ -0,0 +1,37 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "regexp" + "sort" + "strings" +) + +type AllowedListSpec struct { + Exact []string `json:"allowed,omitempty"` + Regex string `json:"allowedRegex,omitempty"` +} + +func (in *AllowedListSpec) ExactMatch(value string) (ok bool) { + if len(in.Exact) > 0 { + sort.SliceStable(in.Exact, func(i, j int) bool { + return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) + }) + + i := sort.SearchStrings(in.Exact, value) + + ok = i < len(in.Exact) && in.Exact[i] == value + } + + return +} + +func (in *AllowedListSpec) RegexMatch(value string) (ok bool) { + if len(in.Regex) > 0 { + ok = regexp.MustCompile(in.Regex).MatchString(value) + } + + return +} diff --git a/api/v1beta2/allowed_list_test.go b/api/v1beta2/allowed_list_test.go new file mode 100644 index 00000000..77754933 --- /dev/null +++ b/api/v1beta2/allowed_list_test.go @@ -0,0 +1,73 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 +//nolint:dupl +package v1beta2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAllowedListSpec_ExactMatch(t *testing.T) { + type tc struct { + In []string + True []string + False []string + } + + for _, tc := range []tc{ + { + []string{"foo", "bar", "bizz", "buzz"}, + []string{"foo", "bar", "bizz", "buzz"}, + []string{"bing", "bong"}, + }, + { + []string{"one", "two", "three"}, + []string{"one", "two", "three"}, + []string{"a", "b", "c"}, + }, + { + nil, + nil, + []string{"any", "value"}, + }, + } { + a := AllowedListSpec{ + Exact: tc.In, + } + + for _, ok := range tc.True { + assert.True(t, a.ExactMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.ExactMatch(ko)) + } + } +} + +func TestAllowedListSpec_RegexMatch(t *testing.T) { + type tc struct { + Regex string + True []string + False []string + } + + for _, tc := range []tc{ + {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, + {``, nil, []string{"any", "value"}}, + } { + a := AllowedListSpec{ + Regex: tc.Regex, + } + + for _, ok := range tc.True { + assert.True(t, a.RegexMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.RegexMatch(ko)) + } + } +} diff --git a/api/v1beta2/capsuleconfiguration_funcs.go b/api/v1beta2/capsuleconfiguration_funcs.go new file mode 100644 index 00000000..7e029cad --- /dev/null +++ b/api/v1beta2/capsuleconfiguration_funcs.go @@ -0,0 +1,131 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" +) + +func (in *CapsuleConfiguration) ConvertTo(raw conversion.Hub) error { + dst, ok := raw.(*capsulev1alpha1.CapsuleConfiguration) + if !ok { + return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", dst) + } + + dst.ObjectMeta = in.ObjectMeta + dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString + dst.Spec.UserGroups = in.Spec.UserGroups + dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString + + annotations := dst.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + + if in.Spec.NodeMetadata != nil { + annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",") + annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",") + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex + } + + annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName] = fmt.Sprintf("%t", in.Spec.EnableTLSReconciler) + annotations[capsulev1alpha1.TLSSecretNameAnnotation] = in.Spec.CapsuleResources.TLSSecretName + annotations[capsulev1alpha1.MutatingWebhookConfigurationName] = in.Spec.CapsuleResources.MutatingWebhookConfigurationName + annotations[capsulev1alpha1.ValidatingWebhookConfigurationName] = in.Spec.CapsuleResources.ValidatingWebhookConfigurationName + + dst.SetAnnotations(annotations) + + return nil +} + +func (in *CapsuleConfiguration) ConvertFrom(raw conversion.Hub) error { + src, ok := raw.(*capsulev1alpha1.CapsuleConfiguration) + if !ok { + return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", src) + } + + in.ObjectMeta = src.ObjectMeta + in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString + in.Spec.UserGroups = src.Spec.UserGroups + in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString + + annotations := src.GetAnnotations() + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenLabels.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsAnnotation) + } + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenLabels.Regex = value + + delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation) + } + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenAnnotations.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation) + } + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenAnnotations.Regex = value + + delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation) + } + + if value, found := annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName]; found { + v, _ := strconv.ParseBool(value) + + in.Spec.EnableTLSReconciler = v + + delete(annotations, capsulev1alpha1.EnableTLSConfigurationAnnotationName) + } + + if value, found := annotations[capsulev1alpha1.TLSSecretNameAnnotation]; found { + in.Spec.CapsuleResources.TLSSecretName = value + + delete(annotations, capsulev1alpha1.TLSSecretNameAnnotation) + } + + if value, found := annotations[capsulev1alpha1.MutatingWebhookConfigurationName]; found { + in.Spec.CapsuleResources.MutatingWebhookConfigurationName = value + + delete(annotations, capsulev1alpha1.MutatingWebhookConfigurationName) + } + + if value, found := annotations[capsulev1alpha1.ValidatingWebhookConfigurationName]; found { + in.Spec.CapsuleResources.ValidatingWebhookConfigurationName = value + + delete(annotations, capsulev1alpha1.ValidatingWebhookConfigurationName) + } + + in.SetAnnotations(annotations) + + return nil +} diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go new file mode 100644 index 00000000..207cd00b --- /dev/null +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -0,0 +1,75 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CapsuleConfigurationSpec defines the Capsule configuration. +type CapsuleConfigurationSpec struct { + // Names of the groups for Capsule users. + // +kubebuilder:default={capsule.clastix.io} + UserGroups []string `json:"userGroups,omitempty"` + // Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, + // separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + // +kubebuilder:default=false + ForceTenantPrefix bool `json:"forceTenantPrefix,omitempty"` + // Disallow creation of namespaces, whose name matches this regexp + ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"` + // Allows to set different name rather than the canonical one for the Capsule configuration objects, + // such as webhook secret or configurations. + CapsuleResources CapsuleResources `json:"overrides"` + // Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. + // This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + NodeMetadata *NodeMetadata `json:"nodeMetadata"` + // Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks + // when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. + // +kubebuilder:default=true + EnableTLSReconciler bool `json:"enableTLSReconciler"` //nolint:tagliatelle +} + +type NodeMetadata struct { + // Define the labels that a Tenant Owner cannot set for their nodes. + ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + // Define the annotations that a Tenant Owner cannot set for their nodes. + ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` +} + +type CapsuleResources struct { + // Defines the Secret name used for the webhook server. + // Must be in the same Namespace where the Capsule Deployment is deployed. + // +kubebuilder:default=capsule-tls + TLSSecretName string `json:"TLSSecretName"` //nolint:tagliatelle + // Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + // +kubebuilder:default=capsule-mutating-webhook-configuration + MutatingWebhookConfigurationName string `json:"mutatingWebhookConfigurationName"` + // Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + // +kubebuilder:default=capsule-validating-webhook-configuration + ValidatingWebhookConfigurationName string `json:"validatingWebhookConfigurationName"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster + +// CapsuleConfiguration is the Schema for the Capsule configuration API. +type CapsuleConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CapsuleConfigurationSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// CapsuleConfigurationList contains a list of CapsuleConfiguration. +type CapsuleConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CapsuleConfiguration `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CapsuleConfiguration{}, &CapsuleConfigurationList{}) +} diff --git a/api/v1beta2/conversion_hub.go b/api/v1beta2/conversion_hub.go new file mode 100644 index 00000000..0c57e00c --- /dev/null +++ b/api/v1beta2/conversion_hub.go @@ -0,0 +1,401 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +//nolint:nestif,cyclop,maintidx +package v1beta2 + +import ( + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" +) + +//nolint:gocyclo +func (in *Tenant) ConvertFrom(raw conversion.Hub) error { + src, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := src.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + in.ObjectMeta = src.ObjectMeta + in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners)) + + for index, owner := range src.Spec.Owners { + proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, ProxyOperation(op)) + } + + proxySettings = append(proxySettings, ProxySettings{ + Kind: ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{ + Kind: OwnerKind(owner.Kind), + Name: owner.Name, + ClusterRoles: owner.GetRoles(*src, index), + ProxyOperations: proxySettings, + }) + } + + if nsOpts := src.Spec.NamespaceOptions; nsOpts != nil { + in.Spec.NamespaceOptions = &NamespaceOptions{} + + in.Spec.NamespaceOptions.Quota = src.Spec.NamespaceOptions.Quota + + if nsOpts.AdditionalMetadata != nil { + in.Spec.NamespaceOptions.AdditionalMetadata = &AdditionalMetadataSpec{} + + in.Spec.NamespaceOptions.AdditionalMetadata.Annotations = nsOpts.AdditionalMetadata.Annotations + in.Spec.NamespaceOptions.AdditionalMetadata.Labels = nsOpts.AdditionalMetadata.Labels + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation) + } + } + + if svcOpts := src.Spec.ServiceOptions; svcOpts != nil { + in.Spec.ServiceOptions = &ServiceOptions{} + + if metadata := svcOpts.AdditionalMetadata; metadata != nil { + in.Spec.ServiceOptions.AdditionalMetadata = &AdditionalMetadataSpec{ + Labels: metadata.Labels, + Annotations: metadata.Annotations, + } + } + + if types := svcOpts.AllowedServices; types != nil { + in.Spec.ServiceOptions.AllowedServices = &AllowedServices{ + NodePort: types.NodePort, + ExternalName: types.ExternalName, + LoadBalancer: types.LoadBalancer, + } + } + + if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { + allowed := make([]AllowedIP, 0, len(externalIPs.Allowed)) + + for _, ip := range externalIPs.Allowed { + allowed = append(allowed, AllowedIP(ip)) + } + + in.Spec.ServiceOptions.ExternalServiceIPs = &ExternalServiceIPsSpec{ + Allowed: allowed, + } + } + } + + if sc := src.Spec.StorageClasses; sc != nil { + in.Spec.StorageClasses = &AllowedListSpec{ + Exact: sc.Exact, + Regex: sc.Regex, + } + } + + if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 { + in.Spec.IngressOptions.HostnameCollisionScope = HostnameCollisionScope(scope) + } + + v, found := annotations[capsulev1beta1.DenyWildcard] + if found { + value, err := strconv.ParseBool(v) + if err == nil { + in.Spec.IngressOptions.AllowWildcardHostnames = value + + delete(annotations, capsulev1beta1.DenyWildcard) + } + } + + if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { + in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ + Exact: ingressClass.Exact, + Regex: ingressClass.Regex, + } + } + + if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil { + in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ + Exact: hostnames.Exact, + Regex: hostnames.Regex, + } + } + + if allowed := src.Spec.ContainerRegistries; allowed != nil { + in.Spec.ContainerRegistries = &AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + in.Spec.NodeSelector = src.Spec.NodeSelector + + if items := src.Spec.NetworkPolicies.Items; len(items) > 0 { + in.Spec.NetworkPolicies.Items = items + } + + if items := src.Spec.LimitRanges.Items; len(items) > 0 { + in.Spec.LimitRanges.Items = items + } + + if scope := src.Spec.ResourceQuota.Scope; len(scope) > 0 { + in.Spec.ResourceQuota.Scope = ResourceQuotaScope(scope) + } + + if items := src.Spec.ResourceQuota.Items; len(items) > 0 { + in.Spec.ResourceQuota.Items = items + } + + in.Spec.AdditionalRoleBindings = make([]AdditionalRoleBindingsSpec, 0, len(src.Spec.AdditionalRoleBindings)) + for _, rb := range src.Spec.AdditionalRoleBindings { + in.Spec.AdditionalRoleBindings = append(in.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{ + ClusterRoleName: rb.ClusterRoleName, + Subjects: rb.Subjects, + }) + } + + in.Spec.ImagePullPolicies = make([]ImagePullPolicySpec, 0, len(src.Spec.ImagePullPolicies)) + for _, policy := range src.Spec.ImagePullPolicies { + in.Spec.ImagePullPolicies = append(in.Spec.ImagePullPolicies, ImagePullPolicySpec(policy)) + } + + if allowed := src.Spec.PriorityClasses; allowed != nil { + in.Spec.PriorityClasses = &AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + if v, found := annotations["capsule.clastix.io/cordon"]; found { + value, err := strconv.ParseBool(v) + if err == nil { + delete(annotations, "capsule.clastix.io/cordon") + } + + in.Spec.Cordoned = value + } + + if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found { + in.Spec.PreventDeletion = true + + delete(annotations, capsulev1beta1.ProtectedTenantAnnotation) + } + + in.SetAnnotations(annotations) + + in.Status.Namespaces = src.Status.Namespaces + in.Status.Size = src.Status.Size + in.Status.State = tenantState(src.Status.State) + + return nil +} + +func (in *Tenant) ConvertTo(raw conversion.Hub) error { + dst, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := in.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + dst.ObjectMeta = in.ObjectMeta + dst.Spec.Owners = make(capsulev1beta1.OwnerListSpec, 0, len(in.Spec.Owners)) + + for index, owner := range in.Spec.Owners { + proxySettings := make([]capsulev1beta1.ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]capsulev1beta1.ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, capsulev1beta1.ProxyOperation(op)) + } + + proxySettings = append(proxySettings, capsulev1beta1.ProxySettings{ + Kind: capsulev1beta1.ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + dst.Spec.Owners = append(dst.Spec.Owners, capsulev1beta1.OwnerSpec{ + Kind: capsulev1beta1.OwnerKind(owner.Kind), + Name: owner.Name, + ProxyOperations: proxySettings, + }) + + if clusterRoles := owner.ClusterRoles; len(clusterRoles) > 0 { + annotations[fmt.Sprintf("%s/%d", capsulev1beta1.ClusterRoleNamesAnnotation, index)] = strings.Join(owner.ClusterRoles, ",") + } + } + + if nsOpts := in.Spec.NamespaceOptions; nsOpts != nil { + dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} + + if quota := nsOpts.Quota; quota != nil { + dst.Spec.NamespaceOptions.Quota = quota + } + + if metadata := nsOpts.AdditionalMetadata; metadata != nil { + dst.Spec.NamespaceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ + Labels: metadata.Labels, + Annotations: metadata.Annotations, + } + } + + if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex + } + + if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex + } + } + + if svcOpts := in.Spec.ServiceOptions; svcOpts != nil { + dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + + if metadata := svcOpts.AdditionalMetadata; metadata != nil { + dst.Spec.ServiceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ + Labels: metadata.Labels, + Annotations: metadata.Annotations, + } + } + + if allowed := svcOpts.AllowedServices; allowed != nil { + dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{ + NodePort: allowed.NodePort, + ExternalName: allowed.ExternalName, + LoadBalancer: allowed.ExternalName, + } + } + + if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { + allowed := make([]capsulev1beta1.AllowedIP, 0, len(externalIPs.Allowed)) + + for _, ip := range externalIPs.Allowed { + allowed = append(allowed, capsulev1beta1.AllowedIP(ip)) + } + + dst.Spec.ServiceOptions.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{ + Allowed: allowed, + } + } + } + + if storageClass := in.Spec.StorageClasses; storageClass != nil { + dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{ + Exact: storageClass.Exact, + Regex: storageClass.Regex, + } + } + + dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(in.Spec.IngressOptions.HostnameCollisionScope) + + if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil { + dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil { + dst.Spec.IngressOptions.AllowedHostnames = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + annotations[capsulev1beta1.DenyWildcard] = fmt.Sprintf("%t", in.Spec.IngressOptions.AllowWildcardHostnames) + + if allowed := in.Spec.ContainerRegistries; allowed != nil { + dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + dst.Spec.NodeSelector = in.Spec.NodeSelector + + dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{ + Items: in.Spec.NetworkPolicies.Items, + } + + dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{ + Items: in.Spec.LimitRanges.Items, + } + + dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{ + Scope: capsulev1beta1.ResourceQuotaScope(in.Spec.ResourceQuota.Scope), + Items: in.Spec.ResourceQuota.Items, + } + + dst.Spec.AdditionalRoleBindings = make([]capsulev1beta1.AdditionalRoleBindingsSpec, 0, len(in.Spec.AdditionalRoleBindings)) + for _, item := range in.Spec.AdditionalRoleBindings { + dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{ + ClusterRoleName: item.ClusterRoleName, + Subjects: item.Subjects, + }) + } + + dst.Spec.ImagePullPolicies = make([]capsulev1beta1.ImagePullPolicySpec, 0, len(in.Spec.ImagePullPolicies)) + for _, item := range in.Spec.ImagePullPolicies { + dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(item)) + } + + if allowed := in.Spec.PriorityClasses; allowed != nil { + dst.Spec.PriorityClasses = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + dst.SetAnnotations(annotations) + + return nil +} diff --git a/api/v1beta2/custom_resource_quota.go b/api/v1beta2/custom_resource_quota.go new file mode 100644 index 00000000..42ccb730 --- /dev/null +++ b/api/v1beta2/custom_resource_quota.go @@ -0,0 +1,59 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strconv" +) + +const ( + ResourceQuotaAnnotationPrefix = "quota.resources.capsule.clastix.io" + ResourceUsedAnnotationPrefix = "used.resources.capsule.clastix.io" +) + +func UsedAnnotationForResource(kindGroup string) string { + return fmt.Sprintf("%s/%s", ResourceUsedAnnotationPrefix, kindGroup) +} + +func LimitAnnotationForResource(kindGroup string) string { + return fmt.Sprintf("%s/%s", ResourceQuotaAnnotationPrefix, kindGroup) +} + +func GetUsedResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) { + usedStr, ok := tenant.GetAnnotations()[UsedAnnotationForResource(kindGroup)] + if !ok { + usedStr = "0" + } + + used, _ := strconv.ParseInt(usedStr, 10, 10) + + return used, nil +} + +type NonLimitedResourceError struct { + kindGroup string +} + +func NewNonLimitedResourceError(kindGroup string) *NonLimitedResourceError { + return &NonLimitedResourceError{kindGroup: kindGroup} +} + +func (n NonLimitedResourceError) Error() string { + return fmt.Sprintf("resource %s is not limited for the current tenant", n.kindGroup) +} + +func GetLimitResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) { + limitStr, ok := tenant.GetAnnotations()[LimitAnnotationForResource(kindGroup)] + if !ok { + return 0, NewNonLimitedResourceError(kindGroup) + } + + limit, err := strconv.ParseInt(limitStr, 10, 10) + if err != nil { + return 0, fmt.Errorf("resource %s limit cannot be parsed, %w", kindGroup, err) + } + + return limit, nil +} diff --git a/api/v1beta2/forbidden_list.go b/api/v1beta2/forbidden_list.go new file mode 100644 index 00000000..65c8e74e --- /dev/null +++ b/api/v1beta2/forbidden_list.go @@ -0,0 +1,37 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "regexp" + "sort" + "strings" +) + +type ForbiddenListSpec struct { + Exact []string `json:"denied,omitempty"` + Regex string `json:"deniedRegex,omitempty"` +} + +func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) { + if len(in.Exact) > 0 { + sort.SliceStable(in.Exact, func(i, j int) bool { + return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) + }) + + i := sort.SearchStrings(in.Exact, value) + + ok = i < len(in.Exact) && in.Exact[i] == value + } + + return +} + +func (in ForbiddenListSpec) RegexMatch(value string) (ok bool) { + if len(in.Regex) > 0 { + ok = regexp.MustCompile(in.Regex).MatchString(value) + } + + return +} diff --git a/api/v1beta2/forbidden_list_test.go b/api/v1beta2/forbidden_list_test.go new file mode 100644 index 00000000..30bc9ac0 --- /dev/null +++ b/api/v1beta2/forbidden_list_test.go @@ -0,0 +1,73 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 +//nolint:dupl +package v1beta2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestForbiddenListSpec_ExactMatch(t *testing.T) { + type tc struct { + In []string + True []string + False []string + } + + for _, tc := range []tc{ + { + []string{"foo", "bar", "bizz", "buzz"}, + []string{"foo", "bar", "bizz", "buzz"}, + []string{"bing", "bong"}, + }, + { + []string{"one", "two", "three"}, + []string{"one", "two", "three"}, + []string{"a", "b", "c"}, + }, + { + nil, + nil, + []string{"any", "value"}, + }, + } { + a := ForbiddenListSpec{ + Exact: tc.In, + } + + for _, ok := range tc.True { + assert.True(t, a.ExactMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.ExactMatch(ko)) + } + } +} + +func TestForbiddenListSpec_RegexMatch(t *testing.T) { + type tc struct { + Regex string + True []string + False []string + } + + for _, tc := range []tc{ + {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, + {``, nil, []string{"any", "value"}}, + } { + a := ForbiddenListSpec{ + Regex: tc.Regex, + } + + for _, ok := range tc.True { + assert.True(t, a.RegexMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.RegexMatch(ko)) + } + } +} diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go new file mode 100644 index 00000000..3117d80f --- /dev/null +++ b/api/v1beta2/groupversion_info.go @@ -0,0 +1,23 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +// Package v1beta2 contains API Schema definitions for the capsule v1beta2 API group +//+kubebuilder:object:generate=true +//+groupName=capsule.clastix.io +package v1beta2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects. + GroupVersion = schema.GroupVersion{Group: "capsule.clastix.io", Version: "v1beta2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1beta2/hostname_collision_scope.go b/api/v1beta2/hostname_collision_scope.go new file mode 100644 index 00000000..a46ab934 --- /dev/null +++ b/api/v1beta2/hostname_collision_scope.go @@ -0,0 +1,14 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +const ( + HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster" + HostnameCollisionScopeTenant HostnameCollisionScope = "Tenant" + HostnameCollisionScopeNamespace HostnameCollisionScope = "Namespace" + HostnameCollisionScopeDisabled HostnameCollisionScope = "Disabled" +) + +// +kubebuilder:validation:Enum=Cluster;Tenant;Namespace;Disabled +type HostnameCollisionScope string diff --git a/api/v1beta2/image_pull_policy.go b/api/v1beta2/image_pull_policy.go new file mode 100644 index 00000000..cb9e8e2f --- /dev/null +++ b/api/v1beta2/image_pull_policy.go @@ -0,0 +1,11 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +// +kubebuilder:validation:Enum=Always;Never;IfNotPresent +type ImagePullPolicySpec string + +func (i ImagePullPolicySpec) String() string { + return string(i) +} diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go new file mode 100644 index 00000000..d55c0cfd --- /dev/null +++ b/api/v1beta2/ingress_options.go @@ -0,0 +1,26 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type IngressOptions struct { + // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + AllowedClasses *AllowedListSpec `json:"allowedClasses,omitempty"` + // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. + // + // + // - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. + // + // - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. + // + // - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. + // + // + // Optional. + // +kubebuilder:default=Disabled + HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` + // Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + AllowedHostnames *AllowedListSpec `json:"allowedHostnames,omitempty"` + // Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + AllowWildcardHostnames bool `json:"allowWildcardHostnames"` +} diff --git a/api/v1beta2/limit_ranges.go b/api/v1beta2/limit_ranges.go new file mode 100644 index 00000000..cd5d0a13 --- /dev/null +++ b/api/v1beta2/limit_ranges.go @@ -0,0 +1,10 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import corev1 "k8s.io/api/core/v1" + +type LimitRangesSpec struct { + Items []corev1.LimitRangeSpec `json:"items,omitempty"` +} diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go new file mode 100644 index 00000000..10d7e906 --- /dev/null +++ b/api/v1beta2/namespace_options.go @@ -0,0 +1,16 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type NamespaceOptions struct { + //+kubebuilder:validation:Minimum=1 + // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + Quota *int32 `json:"quota,omitempty"` + // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + // Define the labels that a Tenant Owner cannot set for their Namespace resources. + ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + // Define the annotations that a Tenant Owner cannot set for their Namespace resources. + ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` +} diff --git a/api/v1beta2/network_policy.go b/api/v1beta2/network_policy.go new file mode 100644 index 00000000..67668129 --- /dev/null +++ b/api/v1beta2/network_policy.go @@ -0,0 +1,12 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + networkingv1 "k8s.io/api/networking/v1" +) + +type NetworkPolicySpec struct { + Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"` +} diff --git a/api/v1beta2/owner.go b/api/v1beta2/owner.go new file mode 100644 index 00000000..7ae29010 --- /dev/null +++ b/api/v1beta2/owner.go @@ -0,0 +1,57 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type OwnerSpec struct { + // Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + Kind OwnerKind `json:"kind"` + // Name of tenant owner. + Name string `json:"name"` + // Defines additional cluster-roles for the specific Owner. + // +kubebuilder:default={admin,capsule-namespace-deleter} + ClusterRoles []string `json:"clusterRoles,omitempty"` + // Proxy settings for tenant owner. + ProxyOperations []ProxySettings `json:"proxySettings,omitempty"` +} + +// +kubebuilder:validation:Enum=User;Group;ServiceAccount +type OwnerKind string + +func (k OwnerKind) String() string { + return string(k) +} + +type ProxySettings struct { + Kind ProxyServiceKind `json:"kind"` + Operations []ProxyOperation `json:"operations"` +} + +// +kubebuilder:validation:Enum=List;Update;Delete +type ProxyOperation string + +func (p ProxyOperation) String() string { + return string(p) +} + +// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses;PriorityClasses +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" +) diff --git a/api/v1beta2/owner_list.go b/api/v1beta2/owner_list.go new file mode 100644 index 00000000..3677acc6 --- /dev/null +++ b/api/v1beta2/owner_list.go @@ -0,0 +1,41 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "sort" +) + +type OwnerListSpec []OwnerSpec + +func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) { + sort.Sort(ByKindAndName(o)) + i := sort.Search(len(o), func(i int) bool { + return o[i].Kind >= kind && o[i].Name >= name + }) + + if i < len(o) && o[i].Kind == kind && o[i].Name == name { + return o[i] + } + + return +} + +type ByKindAndName OwnerListSpec + +func (b ByKindAndName) Len() int { + return len(b) +} + +func (b ByKindAndName) Less(i, j int) bool { + if b[i].Kind.String() != b[j].Kind.String() { + return b[i].Kind.String() < b[j].Kind.String() + } + + return b[i].Name < b[j].Name +} + +func (b ByKindAndName) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} diff --git a/api/v1beta2/owner_list_test.go b/api/v1beta2/owner_list_test.go new file mode 100644 index 00000000..93697b93 --- /dev/null +++ b/api/v1beta2/owner_list_test.go @@ -0,0 +1,86 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOwnerListSpec_FindOwner(t *testing.T) { + bla := OwnerSpec{ + Kind: UserOwner, + Name: "bla", + ProxyOperations: []ProxySettings{ + { + Kind: IngressClassesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + }, + } + bar := OwnerSpec{ + Kind: GroupOwner, + Name: "bar", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + }, + } + baz := OwnerSpec{ + Kind: UserOwner, + Name: "baz", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"Update"}, + }, + }, + } + fim := OwnerSpec{ + Kind: ServiceAccountOwner, + Name: "fim", + ProxyOperations: []ProxySettings{ + { + Kind: NodesProxy, + Operations: []ProxyOperation{"List"}, + }, + }, + } + bom := OwnerSpec{ + Kind: GroupOwner, + Name: "bom", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + { + Kind: NodesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + }, + } + qip := OwnerSpec{ + Kind: ServiceAccountOwner, + Name: "qip", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"List", "Delete"}, + }, + }, + } + owners := OwnerListSpec{bom, qip, bla, bar, baz, fim} + + assert.Equal(t, owners.FindOwner("bom", GroupOwner), bom) + assert.Equal(t, owners.FindOwner("qip", ServiceAccountOwner), qip) + assert.Equal(t, owners.FindOwner("bla", UserOwner), bla) + assert.Equal(t, owners.FindOwner("bar", GroupOwner), bar) + assert.Equal(t, owners.FindOwner("baz", UserOwner), baz) + assert.Equal(t, owners.FindOwner("fim", ServiceAccountOwner), fim) + assert.Equal(t, owners.FindOwner("notfound", ServiceAccountOwner), OwnerSpec{}) +} diff --git a/api/v1beta2/resource_quota.go b/api/v1beta2/resource_quota.go new file mode 100644 index 00000000..80534c91 --- /dev/null +++ b/api/v1beta2/resource_quota.go @@ -0,0 +1,21 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import corev1 "k8s.io/api/core/v1" + +// +kubebuilder:validation:Enum=Tenant;Namespace +type ResourceQuotaScope string + +const ( + ResourceQuotaScopeTenant ResourceQuotaScope = "Tenant" + ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace" +) + +type ResourceQuotaSpec struct { + // +kubebuilder:default=Tenant + // Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + Scope ResourceQuotaScope `json:"scope,omitempty"` + Items []corev1.ResourceQuotaSpec `json:"items,omitempty"` +} diff --git a/api/v1beta2/service_allowed_ips.go b/api/v1beta2/service_allowed_ips.go new file mode 100644 index 00000000..9eb6247e --- /dev/null +++ b/api/v1beta2/service_allowed_ips.go @@ -0,0 +1,11 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" +type AllowedIP string + +type ExternalServiceIPsSpec struct { + Allowed []AllowedIP `json:"allowed"` +} diff --git a/api/v1beta2/service_allowed_types.go b/api/v1beta2/service_allowed_types.go new file mode 100644 index 00000000..1f4abd4e --- /dev/null +++ b/api/v1beta2/service_allowed_types.go @@ -0,0 +1,16 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type AllowedServices struct { + //+kubebuilder:default=true + // Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + NodePort *bool `json:"nodePort,omitempty"` + //+kubebuilder:default=true + // Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + ExternalName *bool `json:"externalName,omitempty"` + //+kubebuilder:default=true + // Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + LoadBalancer *bool `json:"loadBalancer,omitempty"` +} diff --git a/api/v1beta2/service_options.go b/api/v1beta2/service_options.go new file mode 100644 index 00000000..a232aa5d --- /dev/null +++ b/api/v1beta2/service_options.go @@ -0,0 +1,13 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type ServiceOptions struct { + // Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + // Block or deny certain type of Services. Optional. + AllowedServices *AllowedServices `json:"allowedServices,omitempty"` + // Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"` +} diff --git a/api/v1beta2/tenant_annotations.go b/api/v1beta2/tenant_annotations.go new file mode 100644 index 00000000..79fde6b5 --- /dev/null +++ b/api/v1beta2/tenant_annotations.go @@ -0,0 +1,26 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strings" +) + +const ( + AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" + AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" + AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" + AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" + AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" + AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" +) + +func UsedQuotaFor(resource fmt.Stringer) string { + return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_") +} + +func HardQuotaFor(resource fmt.Stringer) string { + return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_") +} diff --git a/api/v1beta2/tenant_func.go b/api/v1beta2/tenant_func.go new file mode 100644 index 00000000..166956aa --- /dev/null +++ b/api/v1beta2/tenant_func.go @@ -0,0 +1,38 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "sort" + + corev1 "k8s.io/api/core/v1" +) + +func (in *Tenant) IsFull() bool { + // we don't have limits on assigned Namespaces + if in.Spec.NamespaceOptions == nil || in.Spec.NamespaceOptions.Quota == nil { + return false + } + + return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceOptions.Quota) +} + +func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { + var l []string + + for _, ns := range namespaces { + if ns.Status.Phase == corev1.NamespaceActive { + l = append(l, ns.GetName()) + } + } + + sort.Strings(l) + + in.Status.Namespaces = l + in.Status.Size = uint(len(l)) +} + +func (in *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings { + return in.Spec.Owners.FindOwner(name, kind).ProxyOperations +} diff --git a/api/v1beta2/tenant_labels.go b/api/v1beta2/tenant_labels.go new file mode 100644 index 00000000..98d0e064 --- /dev/null +++ b/api/v1beta2/tenant_labels.go @@ -0,0 +1,32 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func GetTypeLabel(t runtime.Object) (label string, err error) { + switch v := t.(type) { + case *Tenant: + return "capsule.clastix.io/tenant", nil + case *corev1.LimitRange: + return "capsule.clastix.io/limit-range", nil + case *networkingv1.NetworkPolicy: + return "capsule.clastix.io/network-policy", nil + case *corev1.ResourceQuota: + return "capsule.clastix.io/resource-quota", nil + case *rbacv1.RoleBinding: + return "capsule.clastix.io/role-binding", nil + default: + err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v) + } + + return +} diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go new file mode 100644 index 00000000..de6b44dc --- /dev/null +++ b/api/v1beta2/tenant_status.go @@ -0,0 +1,23 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +// +kubebuilder:validation:Enum=Cordoned;Active +type tenantState string + +const ( + TenantStateActive tenantState = "Active" + TenantStateCordoned tenantState = "Cordoned" +) + +// Returns the observed state of the Tenant. +type TenantStatus struct { + //+kubebuilder:default=Active + // The operational state of the Tenant. Possible values are "Active", "Cordoned". + State tenantState `json:"state"` + // How many namespaces are assigned to the Tenant. + Size uint `json:"size"` + // List of namespaces assigned to the Tenant. + Namespaces []string `json:"namespaces,omitempty"` +} diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go new file mode 100644 index 00000000..adc0c66b --- /dev/null +++ b/api/v1beta2/tenant_types.go @@ -0,0 +1,74 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TenantSpec defines the desired state of Tenant. +type TenantSpec struct { + // Specifies the owners of the Tenant. Mandatory. + Owners OwnerListSpec `json:"owners"` + // Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` + // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + ServiceOptions *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 options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + IngressOptions IngressOptions `json:"ingressOptions,omitempty"` + // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"` + // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"` + // 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"` + // 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 priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"` + // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + Cordoned bool `json:"cordoned,omitempty"` + // Prevent accidental deletion of the Tenant. + // When enabled, the deletion request will be declined. + PreventDeletion bool `json:"preventDeletion,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,shortName=tnt +// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" +// +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created" +// +kubebuilder:printcolumn:name="Namespace count",type="integer",JSONPath=".status.size",description="The total amount of Namespaces in use" +// +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age" + +// Tenant is the Schema for the tenants API. +type Tenant struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TenantSpec `json:"spec,omitempty"` + Status TenantStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TenantList contains a list of Tenant. +type TenantList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Tenant `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Tenant{}, &TenantList{}) +} diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go new file mode 100644 index 00000000..71908929 --- /dev/null +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -0,0 +1,672 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta2 + +import ( + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. +func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { + if in == nil { + return nil + } + out := new(AdditionalMetadataSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]v1.Subject, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. +func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { + if in == nil { + return nil + } + out := new(AdditionalRoleBindingsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. +func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { + if in == nil { + return nil + } + out := new(AllowedListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { + *out = *in + if in.NodePort != nil { + in, out := &in.NodePort, &out.NodePort + *out = new(bool) + **out = **in + } + if in.ExternalName != nil { + in, out := &in.ExternalName, &out.ExternalName + *out = new(bool) + **out = **in + } + if in.LoadBalancer != nil { + in, out := &in.LoadBalancer, &out.LoadBalancer + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices. +func (in *AllowedServices) DeepCopy() *AllowedServices { + if in == nil { + return nil + } + out := new(AllowedServices) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) { + { + in := &in + *out = make(ByKindAndName, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByKindAndName. +func (in ByKindAndName) DeepCopy() ByKindAndName { + if in == nil { + return nil + } + out := new(ByKindAndName) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleConfiguration) DeepCopyInto(out *CapsuleConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfiguration. +func (in *CapsuleConfiguration) DeepCopy() *CapsuleConfiguration { + if in == nil { + return nil + } + out := new(CapsuleConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CapsuleConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleConfigurationList) DeepCopyInto(out *CapsuleConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CapsuleConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationList. +func (in *CapsuleConfigurationList) DeepCopy() *CapsuleConfigurationList { + if in == nil { + return nil + } + out := new(CapsuleConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CapsuleConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleConfigurationSpec) DeepCopyInto(out *CapsuleConfigurationSpec) { + *out = *in + if in.UserGroups != nil { + in, out := &in.UserGroups, &out.UserGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.CapsuleResources = in.CapsuleResources + if in.NodeMetadata != nil { + in, out := &in.NodeMetadata, &out.NodeMetadata + *out = new(NodeMetadata) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationSpec. +func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec { + if in == nil { + return nil + } + out := new(CapsuleConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleResources) DeepCopyInto(out *CapsuleResources) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleResources. +func (in *CapsuleResources) DeepCopy() *CapsuleResources { + if in == nil { + return nil + } + out := new(CapsuleResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { + *out = *in + if in.Allowed != nil { + in, out := &in.Allowed, &out.Allowed + *out = make([]AllowedIP, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. +func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { + if in == nil { + return nil + } + out := new(ExternalServiceIPsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. +func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { + if in == nil { + return nil + } + out := new(ForbiddenListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { + *out = *in + if in.AllowedClasses != nil { + in, out := &in.AllowedClasses, &out.AllowedClasses + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } + if in.AllowedHostnames != nil { + in, out := &in.AllowedHostnames, &out.AllowedHostnames + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressOptions. +func (in *IngressOptions) DeepCopy() *IngressOptions { + if in == nil { + return nil + } + out := new(IngressOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.LimitRangeSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. +func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { + if in == nil { + return nil + } + out := new(LimitRangesSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { + *out = *in + if in.Quota != nil { + in, out := &in.Quota, &out.Quota + *out = new(int32) + **out = **in + } + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } + in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels) + in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceOptions. +func (in *NamespaceOptions) DeepCopy() *NamespaceOptions { + if in == nil { + return nil + } + out := new(NamespaceOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]networkingv1.NetworkPolicySpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. +func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { + if in == nil { + return nil + } + out := new(NetworkPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeMetadata) DeepCopyInto(out *NodeMetadata) { + *out = *in + in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels) + in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetadata. +func (in *NodeMetadata) DeepCopy() *NodeMetadata { + if in == nil { + return nil + } + out := new(NodeMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NonLimitedResourceError. +func (in *NonLimitedResourceError) DeepCopy() *NonLimitedResourceError { + if in == nil { + return nil + } + out := new(NonLimitedResourceError) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) { + { + in := &in + *out = make(OwnerListSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerListSpec. +func (in OwnerListSpec) DeepCopy() OwnerListSpec { + if in == nil { + return nil + } + out := new(OwnerListSpec) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) { + *out = *in + if in.ClusterRoles != nil { + in, out := &in.ClusterRoles, &out.ClusterRoles + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ProxyOperations != nil { + in, out := &in.ProxyOperations, &out.ProxyOperations + *out = make([]ProxySettings, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerSpec. +func (in *OwnerSpec) DeepCopy() *OwnerSpec { + if in == nil { + return nil + } + out := new(OwnerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxySettings) DeepCopyInto(out *ProxySettings) { + *out = *in + if in.Operations != nil { + in, out := &in.Operations, &out.Operations + *out = make([]ProxyOperation, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxySettings. +func (in *ProxySettings) DeepCopy() *ProxySettings { + if in == nil { + return nil + } + out := new(ProxySettings) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.ResourceQuotaSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. +func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { + if in == nil { + return nil + } + out := new(ResourceQuotaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { + *out = *in + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } + if in.AllowedServices != nil { + in, out := &in.AllowedServices, &out.AllowedServices + *out = new(AllowedServices) + (*in).DeepCopyInto(*out) + } + if in.ExternalServiceIPs != nil { + in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs + *out = new(ExternalServiceIPsSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions. +func (in *ServiceOptions) DeepCopy() *ServiceOptions { + if in == nil { + return nil + } + out := new(ServiceOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tenant) DeepCopyInto(out *Tenant) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tenant. +func (in *Tenant) DeepCopy() *Tenant { + if in == nil { + return nil + } + out := new(Tenant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Tenant) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantList) DeepCopyInto(out *TenantList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Tenant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantList. +func (in *TenantList) DeepCopy() *TenantList { + if in == nil { + return nil + } + out := new(TenantList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TenantList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { + *out = *in + if in.Owners != nil { + in, out := &in.Owners, &out.Owners + *out = make(OwnerListSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NamespaceOptions != nil { + in, out := &in.NamespaceOptions, &out.NamespaceOptions + *out = new(NamespaceOptions) + (*in).DeepCopyInto(*out) + } + if in.ServiceOptions != nil { + in, out := &in.ServiceOptions, &out.ServiceOptions + *out = new(ServiceOptions) + (*in).DeepCopyInto(*out) + } + if in.StorageClasses != nil { + in, out := &in.StorageClasses, &out.StorageClasses + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } + in.IngressOptions.DeepCopyInto(&out.IngressOptions) + if in.ContainerRegistries != nil { + in, out := &in.ContainerRegistries, &out.ContainerRegistries + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.NetworkPolicies.DeepCopyInto(&out.NetworkPolicies) + in.LimitRanges.DeepCopyInto(&out.LimitRanges) + in.ResourceQuota.DeepCopyInto(&out.ResourceQuota) + if in.AdditionalRoleBindings != nil { + in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings + *out = make([]AdditionalRoleBindingsSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ImagePullPolicies != nil { + in, out := &in.ImagePullPolicies, &out.ImagePullPolicies + *out = make([]ImagePullPolicySpec, len(*in)) + copy(*out, *in) + } + if in.PriorityClasses != nil { + in, out := &in.PriorityClasses, &out.PriorityClasses + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec. +func (in *TenantSpec) DeepCopy() *TenantSpec { + if in == nil { + return nil + } + out := new(TenantSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantStatus) DeepCopyInto(out *TenantStatus) { + *out = *in + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus. +func (in *TenantStatus) DeepCopy() *TenantStatus { + if in == nil { + return nil + } + out := new(TenantStatus) + in.DeepCopyInto(out) + return out +} diff --git a/main.go b/main.go index 7e89b52e..94f103be 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ import ( capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" configcontroller "github.com/clastix/capsule/controllers/config" rbaccontroller "github.com/clastix/capsule/controllers/rbac" servicelabelscontroller "github.com/clastix/capsule/controllers/servicelabels" @@ -57,6 +58,7 @@ func init() { utilruntime.Must(capsulev1alpha1.AddToScheme(scheme)) utilruntime.Must(capsulev1beta1.AddToScheme(scheme)) + utilruntime.Must(capsulev1beta2.AddToScheme(scheme)) utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) } From 884016ce1d73c61cdf2ee7cefbf4ac48ff850da4 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:36:19 +0200 Subject: [PATCH 05/26] chore(kustomize): introducing v1beta2 api group --- .../crds/capsuleconfiguration-crd.yaml | 104 +- ...sule.clastix.io_capsuleconfigurations.yaml | 140 +- .../crd/bases/capsule.clastix.io_tenants.yaml | 2168 +++++++++++++++-- config/crd/kustomization.yaml | 1 + .../webhook_in_capsuleconfiguration.yaml | 18 + config/crd/patches/webhook_in_tenants.yaml | 1 + config/install.yaml | 858 ++++++- config/webhook/manifests.yaml | 2 - 8 files changed, 3054 insertions(+), 238 deletions(-) create mode 100644 config/crd/patches/webhook_in_capsuleconfiguration.yaml diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml index 7665e040..40cdab7d 100644 --- a/charts/capsule/crds/capsuleconfiguration-crd.yaml +++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml @@ -3,9 +3,20 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.5.0 - creationTimestamp: null name: capsuleconfigurations.capsule.clastix.io spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: CapsuleConfiguration @@ -48,9 +59,98 @@ spec: type: object served: true storage: true + - name: v1beta2 + schema: + openAPIV3Schema: + description: CapsuleConfiguration is the Schema for the Capsule configuration API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CapsuleConfigurationSpec defines the Capsule configuration. + properties: + enableTLSReconciler: + default: true + description: Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. + type: boolean + forceTenantPrefix: + default: false + description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + type: boolean + nodeMetadata: + description: Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + properties: + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + overrides: + description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. + properties: + TLSSecretName: + default: capsule-tls + description: Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed. + type: string + mutatingWebhookConfigurationName: + default: capsule-mutating-webhook-configuration + description: Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + validatingWebhookConfigurationName: + default: capsule-validating-webhook-configuration + description: Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + required: + - TLSSecretName + - mutatingWebhookConfigurationName + - validatingWebhookConfigurationName + type: object + protectedNamespaceRegex: + description: Disallow creation of namespaces, whose name matches this regexp + type: string + userGroups: + default: + - capsule.clastix.io + description: Names of the groups for Capsule users. + items: + type: string + type: array + required: + - enableTLSReconciler + - nodeMetadata + - overrides + type: object + type: object + served: true + storage: false status: acceptedNames: kind: "" plural: "" conditions: [] - storedVersions: [] \ No newline at end of file + storedVersions: [] diff --git a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml index 5ce5bdfe..aae170bf 100644 --- a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml +++ b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: capsuleconfigurations.capsule.clastix.io spec: @@ -19,13 +18,18 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: CapsuleConfiguration is the Schema for the Capsule configuration API. + description: CapsuleConfiguration is the Schema for the Capsule configuration + API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -34,10 +38,14 @@ spec: properties: forceTenantPrefix: default: false - description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + description: Enforces the Tenant owner, during Namespace creation, + to name it using the selected Tenant name as prefix, separated by + a dash. This is useful to avoid Namespace name collision in a public + CaaS environment. type: boolean protectedNamespaceRegex: - description: Disallow creation of namespaces, whose name matches this regexp + description: Disallow creation of namespaces, whose name matches this + regexp type: string userGroups: default: @@ -50,9 +58,115 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - name: v1beta2 + schema: + openAPIV3Schema: + description: CapsuleConfiguration is the Schema for the Capsule configuration + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CapsuleConfigurationSpec defines the Capsule configuration. + properties: + enableTLSReconciler: + default: true + description: Toggles the TLS reconciler, the controller that is able + to generate CA and certificates for the webhooks when not using + an already provided CA and certificate, or when these are managed + externally with Vault, or cert-manager. + type: boolean + forceTenantPrefix: + default: false + description: Enforces the Tenant owner, during Namespace creation, + to name it using the selected Tenant name as prefix, separated by + a dash. This is useful to avoid Namespace name collision in a public + CaaS environment. + type: boolean + nodeMetadata: + description: Allows to set the forbidden metadata for the worker nodes + that could be patched by a Tenant. This applies only if the Tenant + has an active NodeSelector, and the Owner have right to patch their + nodes. + properties: + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot + set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set + for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + overrides: + description: Allows to set different name rather than the canonical + one for the Capsule configuration objects, such as webhook secret + or configurations. + properties: + TLSSecretName: + default: capsule-tls + description: Defines the Secret name used for the webhook server. + Must be in the same Namespace where the Capsule Deployment is + deployed. + type: string + mutatingWebhookConfigurationName: + default: capsule-mutating-webhook-configuration + description: Name of the MutatingWebhookConfiguration which contains + the dynamic admission controller paths and resources. + type: string + validatingWebhookConfigurationName: + default: capsule-validating-webhook-configuration + description: Name of the ValidatingWebhookConfiguration which + contains the dynamic admission controller paths and resources. + type: string + required: + - TLSSecretName + - mutatingWebhookConfigurationName + - validatingWebhookConfigurationName + type: object + protectedNamespaceRegex: + description: Disallow creation of namespaces, whose name matches this + regexp + type: string + userGroups: + default: + - capsule.clastix.io + description: Names of the groups for Capsule users. + items: + type: string + type: array + required: + - enableTLSReconciler + - nodeMetadata + - overrides + type: object + type: object + served: true + storage: false diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 0298fb28..474977cf 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: tenants.capsule.clastix.io spec: @@ -49,10 +48,14 @@ spec: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -67,24 +70,37 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. type: string kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. type: string required: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -130,12 +146,15 @@ spec: type: object limitRanges: items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + description: LimitRangeSpec defines a min/max usage limit for resources + that match on kind. properties: limits: - description: Limits is the list of LimitRangeItem objects that are enforced. + description: Limits is the list of LimitRangeItem objects that + are enforced. items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: default: additionalProperties: @@ -144,7 +163,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. + description: Default resource requirement limit value + by resource name if resource limit is omitted. type: object defaultRequest: additionalProperties: @@ -153,7 +173,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + description: DefaultRequest is the default resource requirement + request value by resource name if resource request is + omitted. type: object max: additionalProperties: @@ -162,7 +184,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. + description: Max usage constraints on this kind by resource + name. type: object maxLimitRequestRatio: additionalProperties: @@ -171,7 +194,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + description: MaxLimitRequestRatio if specified, the named + resource must have a request and limit that are both + non-zero where limit divided by request is less than + or equal to the enumerated value; this represents the + max burst for the named resource. type: object min: additionalProperties: @@ -180,10 +207,12 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: Min usage constraints on this kind by resource + name. type: object type: - description: Type of resource that this limit applies to. + description: Type of resource that this limit applies + to. type: string required: - type @@ -213,44 +242,93 @@ spec: description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic matches at least one egress rule + across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure that + the pods it selects are isolated by default). This field is + beta-level in 1.8 items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and to. This type is beta-level in 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of destination ports for outgoing traffic. + Each item in this list is combined using a logical OR. + If this field is empty or missing, this rule matches + all ports (traffic not restricted by port). If this + field is present and contains at least one item, then + this rule allows traffic only if the traffic matches + at least one port in the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port to allow + traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list are + combined using a logical OR operation. If this field + is empty or missing, this rule matches all destinations + (traffic not restricted by destination). If this field + is present and contains at least one item, this rule + allows traffic only if the traffic matches at least + one item in the to list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range items: type: string type: array @@ -258,21 +336,43 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -284,25 +384,54 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -314,31 +443,65 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: List of ingress rules to be applied to the selected + pods. Traffic is allowed to a pod if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic source is the pod's local node, + OR if the traffic matches at least one ingress rule across + all of the NetworkPolicy objects whose podSelector matches + the pod. If this field is empty then this NetworkPolicy does + not allow any traffic (and serves solely to ensure that the + pods it selects are isolated by default) items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: List of sources which should be able to access + the pods selected for this rule. Items in this list + are combined using a logical OR operation. If this field + is empty or missing, this rule matches all sources (traffic + not restricted by source). If this field is present + and contains at least one item, this rule allows traffic + only if the traffic matches at least one item in the + from list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range items: type: string type: array @@ -346,21 +509,43 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -372,25 +557,54 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -402,50 +616,94 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in this + list is combined using a logical OR. If this field is + empty or missing, this rule matches all ports (traffic + not restricted by port). If this field is present and + contains at least one item, then this rule allows traffic + only if the traffic matches at least one port in the + list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port to allow + traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: Selects the pods to which this NetworkPolicy object + applies. The array of ingress rules is applied to any pods + selected by this field. Multiple network policies can select + the same set of pods. In this case, the ingress rules for + each are combined additively. This field is NOT optional and + follows standard label selector semantics. An empty podSelector + matches all pods in this namespace. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. items: type: string type: array @@ -457,13 +715,31 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress section) + are assumed to affect Ingress. If you want to write an egress-only + policy, you must explicitly specify policyTypes [ "Egress" + ]. Likewise, if you want to write a policy that specifies + that no egress is allowed, you must specify a policyTypes + value that include "Egress" (since such a policy would not + include an Egress section and would otherwise default to just + [ "Ingress" ]). This field is beta-level in 1.8 items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string type: array required: @@ -490,7 +766,8 @@ spec: type: object resourceQuotas: items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + description: ResourceQuotaSpec defines the desired hard limits to + enforce for Quota. properties: hard: additionalProperties: @@ -499,24 +776,39 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + description: 'hard is the set of desired hard limits for each + named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + description: scopeSelector is also a collection of filters like + scopes that must match each object tracked by a quota but + expressed using ScopeSelectorOperator in combination with + possible values. For a resource to match, both scopes AND + scopeSelector (if specified in spec), must be matched. properties: matchExpressions: - description: A list of scope selector requirements by scope of the resources. + description: A list of scope selector requirements by scope + of the resources. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: A scoped-resource selector requirement is + a selector that contains values, a scope name, and an + operator that relates the scope name and values. properties: operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + description: Represents a scope's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. type: string scopeName: - description: The name of the scope that the selector applies to. + description: The name of the scope that the selector + applies to. type: string values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: An array of string values. If the operator + is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during + a strategic merge patch. items: type: string type: array @@ -526,10 +818,14 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + description: A collection of filters that must match each object + tracked by a quota. If not specified, the quota matches all + objects. items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + description: A ResourceQuotaScope defines a filter that must + match each object tracked by a quota type: string type: array type: object @@ -601,10 +897,14 @@ spec: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -612,7 +912,9 @@ spec: description: TenantSpec defines the desired state of Tenant. properties: additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. items: properties: clusterRoleName: @@ -620,24 +922,37 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. type: string kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. type: string required: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -645,7 +960,9 @@ spec: type: object type: array containerRegistries: - description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. properties: allowed: items: @@ -655,7 +972,9 @@ spec: type: string type: object imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. items: enum: - Always @@ -664,10 +983,14 @@ spec: type: string type: array ingressOptions: - description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. properties: allowedClasses: - description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + Optional. properties: allowed: items: @@ -677,7 +1000,10 @@ spec: type: string type: object allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. properties: allowed: items: @@ -688,7 +1014,15 @@ spec: type: object hostnameCollisionScope: default: Disabled - description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." enum: - Cluster - Tenant @@ -697,16 +1031,21 @@ spec: type: string type: object limitRanges: - description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. properties: limits: - description: Limits is the list of LimitRangeItem objects that are enforced. + description: Limits is the list of LimitRangeItem objects + that are enforced. items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: default: additionalProperties: @@ -715,7 +1054,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. + description: Default resource requirement limit value + by resource name if resource limit is omitted. type: object defaultRequest: additionalProperties: @@ -724,7 +1064,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. type: object max: additionalProperties: @@ -733,7 +1075,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. + description: Max usage constraints on this kind by + resource name. type: object maxLimitRequestRatio: additionalProperties: @@ -742,7 +1085,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. type: object min: additionalProperties: @@ -751,10 +1098,12 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: Min usage constraints on this kind by + resource name. type: object type: - description: Type of resource that this limit applies to. + description: Type of resource that this limit applies + to. type: string required: - type @@ -766,10 +1115,14 @@ spec: type: array type: object namespaceOptions: - description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -781,57 +1134,116 @@ spec: type: object type: object quota: - description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. format: int32 minimum: 1 type: integer type: object networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy + description: NetworkPolicySpec provides the specification of + a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -839,21 +1251,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -865,25 +1301,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -895,31 +1361,67 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -927,21 +1429,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -953,25 +1479,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -983,50 +1539,96 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. items: type: string type: array @@ -1038,13 +1640,32 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string type: array required: @@ -1055,14 +1676,19 @@ spec: nodeSelector: additionalProperties: type: string - description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. type: object owners: description: Specifies the owners of the Tenant. Mandatory. items: properties: kind: - description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" enum: - User - Group @@ -1101,7 +1727,9 @@ spec: type: object type: array priorityClasses: - description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. Optional. properties: allowed: items: @@ -1111,11 +1739,17 @@ spec: type: string type: object resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. properties: items: items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. properties: hard: additionalProperties: @@ -1124,24 +1758,40 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. properties: matchExpressions: - description: A list of scope selector requirements by scope of the resources. + description: A list of scope selector requirements by + scope of the resources. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. properties: operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. type: string scopeName: - description: The name of the scope that the selector applies to. + description: The name of the scope that the selector + applies to. type: string values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. items: type: string type: array @@ -1151,27 +1801,35 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota type: string type: array type: object type: array scope: default: Tenant - description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant enum: - Tenant - Namespace type: string type: object serviceOptions: - description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -1187,19 +1845,24 @@ spec: properties: externalName: default: true - description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean loadBalancer: default: true - description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean nodePort: default: true - description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean type: object externalIPs: - description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. properties: allowed: items: @@ -1211,7 +1874,10 @@ spec: type: object type: object storageClasses: - description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + Optional. properties: allowed: items: @@ -1236,7 +1902,8 @@ spec: type: integer state: default: Active - description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". enum: - Cordoned - Active @@ -1250,9 +1917,1098 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects + type: object + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable + resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. + items: + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created + in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects + that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value + by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by + resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by + resource name. + type: object + type: + description: Type of resource that this limit applies + to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot + set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set + for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of + a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 + properties: + ports: + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. + items: + description: NetworkPolicyPort describes a port + to allow traffic on + properties: + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. + properties: + from: + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port + to allow traffic on + properties: + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific + Owner. + items: + type: string + type: array + kind: + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: + items: + enum: + - List + - Update + - Delete + type: string + type: array + required: + - kind + - operations + type: object + type: array + required: + - kind + - name + type: object + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, + the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by + scope of the resources. + items: + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. + properties: + operator: + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector + applies to. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. + items: + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 1f37ca41..4c753b21 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,3 +12,4 @@ configurations: patchesStrategicMerge: - patches/webhook_in_tenants.yaml +- patches/webhook_in_capsuleconfiguration.yaml diff --git a/config/crd/patches/webhook_in_capsuleconfiguration.yaml b/config/crd/patches/webhook_in_capsuleconfiguration.yaml new file mode 100644 index 00000000..a23cd9e4 --- /dev/null +++ b/config/crd/patches/webhook_in_capsuleconfiguration.yaml @@ -0,0 +1,18 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: capsuleconfigurations.capsule.clastix.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + - v1beta2 diff --git a/config/crd/patches/webhook_in_tenants.yaml b/config/crd/patches/webhook_in_tenants.yaml index 342c408c..bc8182da 100644 --- a/config/crd/patches/webhook_in_tenants.yaml +++ b/config/crd/patches/webhook_in_tenants.yaml @@ -15,3 +15,4 @@ spec: conversionReviewVersions: - v1alpha1 - v1beta1 + - v1beta2 diff --git a/config/install.yaml b/config/install.yaml index e7efa7c2..1c7a1525 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -9,10 +9,21 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.10.0 name: capsuleconfigurations.capsule.clastix.io spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: CapsuleConfiguration @@ -55,18 +66,101 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - name: v1beta2 + schema: + openAPIV3Schema: + description: CapsuleConfiguration is the Schema for the Capsule configuration API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CapsuleConfigurationSpec defines the Capsule configuration. + properties: + enableTLSReconciler: + default: true + description: Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. + type: boolean + forceTenantPrefix: + default: false + description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + type: boolean + nodeMetadata: + description: Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + properties: + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + overrides: + description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. + properties: + TLSSecretName: + default: capsule-tls + description: Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed. + type: string + mutatingWebhookConfigurationName: + default: capsule-mutating-webhook-configuration + description: Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + validatingWebhookConfigurationName: + default: capsule-validating-webhook-configuration + description: Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + required: + - TLSSecretName + - mutatingWebhookConfigurationName + - validatingWebhookConfigurationName + type: object + protectedNamespaceRegex: + description: Disallow creation of namespaces, whose name matches this regexp + type: string + userGroups: + default: + - capsule.clastix.io + description: Names of the groups for Capsule users. + items: + type: string + type: array + required: + - enableTLSReconciler + - nodeMetadata + - overrides + type: object + type: object + served: true + storage: false --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 name: tenants.capsule.clastix.io spec: conversion: @@ -80,6 +174,7 @@ spec: conversionReviewVersions: - v1alpha1 - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: Tenant @@ -157,6 +252,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -359,6 +455,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -389,6 +486,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -447,6 +545,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -477,6 +576,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -532,6 +632,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -598,6 +699,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -710,6 +812,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -940,6 +1043,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -970,6 +1074,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -1028,6 +1133,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -1058,6 +1164,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -1113,6 +1220,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -1223,6 +1331,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -1322,12 +1431,731 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects + type: object + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + items: + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific Owner. + items: + type: string + type: array + kind: + description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: + items: + enum: + - List + - Update + - Delete + type: string + type: array + required: + - kind + - operations + type: object + type: array + required: + - kind + - name + type: object + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index f76c3196..768a76f0 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -1,4 +1,3 @@ - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration @@ -26,7 +25,6 @@ webhooks: resources: - namespaces sideEffects: None - --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration From 0e4c7475450dc7322c874cb30f5ed490f7d5ddb0 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:36:30 +0200 Subject: [PATCH 06/26] chore(helm)!: introducing v1beta2 api group --- .../crds/capsuleconfiguration-crd.yaml | 10 +- charts/capsule/crds/tenant-crd.yaml | 752 +++++++++++++++++- 2 files changed, 746 insertions(+), 16 deletions(-) diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml index 40cdab7d..d20fb126 100644 --- a/charts/capsule/crds/capsuleconfiguration-crd.yaml +++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml @@ -1,8 +1,10 @@ + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 name: capsuleconfigurations.capsule.clastix.io spec: conversion: @@ -148,9 +150,3 @@ spec: type: object served: true storage: false -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index 538006a9..f171bd4b 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -1,9 +1,10 @@ + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.10.0 name: tenants.capsule.clastix.io spec: conversion: @@ -14,10 +15,10 @@ spec: name: capsule-webhook-service namespace: capsule-system path: /convert - port: 443 conversionReviewVersions: - v1alpha1 - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: Tenant @@ -95,6 +96,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -297,6 +299,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -327,6 +330,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -385,6 +389,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -415,6 +420,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -470,6 +476,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -536,6 +543,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -648,6 +656,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -878,6 +887,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -908,6 +918,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -966,6 +977,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -996,6 +1008,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -1051,6 +1064,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -1161,6 +1175,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -1260,9 +1275,728 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects + type: object + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + items: + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific Owner. + items: + type: string + type: array + kind: + description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: + items: + enum: + - List + - Update + - Delete + type: string + type: array + required: + - kind + - operations + type: object + type: array + required: + - kind + - name + type: object + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} From f3f9f0170a321606df536718e936788d7162ed8b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:37:02 +0200 Subject: [PATCH 07/26] feat: support for ca update on crds objects --- controllers/tls/manager.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/controllers/tls/manager.go b/controllers/tls/manager.go index d67e3a61..c2cafa25 100644 --- a/controllers/tls/manager.go +++ b/controllers/tls/manager.go @@ -132,7 +132,10 @@ func (r Reconciler) ReconcileCertificates(ctx context.Context, certSecret *corev return r.updateValidatingWebhookConfiguration(ctx, caBundle) }) group.Go(func() error { - return r.updateCustomResourceDefinition(ctx, caBundle) + return r.updateTenantCustomResourceDefinition(ctx, "tenants.capsule.clastix.io", caBundle) + }) + group.Go(func() error { + return r.updateTenantCustomResourceDefinition(ctx, "capsuleconfigurations.capsule.clastix.io", caBundle) }) operatorPods, err := r.getOperatorPods(ctx) @@ -214,10 +217,10 @@ func (r Reconciler) shouldUpdateCertificate(secret *corev1.Secret) bool { // By default helm doesn't allow to use templates in CRD (https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you). // In order to overcome this, we are setting conversion strategy in helm chart to None, and then update it with CA and namespace information. -func (r *Reconciler) updateCustomResourceDefinition(ctx context.Context, caBundle []byte) error { +func (r *Reconciler) updateTenantCustomResourceDefinition(ctx context.Context, name string, caBundle []byte) error { return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { crd := &apiextensionsv1.CustomResourceDefinition{} - err = r.Get(ctx, types.NamespacedName{Name: "tenants.capsule.clastix.io"}, crd) + err = r.Get(ctx, types.NamespacedName{Name: name}, crd) if err != nil { r.Log.Error(err, "cannot retrieve CustomResourceDefinition") From f6e691881e8b65b85336196a484acc9550da5742 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:37:45 +0200 Subject: [PATCH 08/26] docs: introducing v1beta2 api group --- docs/content/general/tenant-crd.md | 2174 +++++++++++++++++++++++++++- 1 file changed, 2170 insertions(+), 4 deletions(-) diff --git a/docs/content/general/tenant-crd.md b/docs/content/general/tenant-crd.md index 13d8cb3f..4a34dc2e 100644 --- a/docs/content/general/tenant-crd.md +++ b/docs/content/general/tenant-crd.md @@ -20,6 +20,7 @@ Packages: - [capsule.clastix.io/v1alpha1](#capsuleclastixiov1alpha1) +- [capsule.clastix.io/v1beta2](#capsuleclastixiov1beta2) - [capsule.clastix.io/v1beta1](#capsuleclastixiov1beta1) # capsule.clastix.io/v1alpha1 @@ -638,14 +639,14 @@ LimitRangeItem defines a min/max usage limit for any resource that matches on ki - additionalAnnotations + annotations map[string]string
false - additionalLabels + labels map[string]string
@@ -1497,14 +1498,14 @@ A scoped-resource selector requirement is a selector that contains values, a sco - additionalAnnotations + annotations map[string]string
false - additionalLabels + labels map[string]string
@@ -1579,6 +1580,2171 @@ TenantStatus defines the observed state of Tenant. +# capsule.clastix.io/v1beta2 + +Resource Types: + +- [CapsuleConfiguration](#capsuleconfiguration) + +- [Tenant](#tenant) + + + + +## CapsuleConfiguration + + + + + + +CapsuleConfiguration is the Schema for the Capsule configuration API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringCapsuleConfigurationtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + CapsuleConfigurationSpec defines the Capsule configuration.
+
false
+ + +### CapsuleConfiguration.spec + + + +CapsuleConfigurationSpec defines the Capsule configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
enableTLSReconcilerboolean + Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager.
+
+ Default: true
+
true
nodeMetadataobject + Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes.
+
true
overridesobject + Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.
+
true
forceTenantPrefixboolean + Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
+
+ Default: false
+
false
protectedNamespaceRegexstring + Disallow creation of namespaces, whose name matches this regexp
+
false
userGroups[]string + Names of the groups for Capsule users.
+
+ Default: [capsule.clastix.io]
+
false
+ + +### CapsuleConfiguration.spec.nodeMetadata + + + +Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
forbiddenAnnotationsobject + Define the annotations that a Tenant Owner cannot set for their nodes.
+
true
forbiddenLabelsobject + Define the labels that a Tenant Owner cannot set for their nodes.
+
true
+ + +### CapsuleConfiguration.spec.nodeMetadata.forbiddenAnnotations + + + +Define the annotations that a Tenant Owner cannot set for their nodes. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### CapsuleConfiguration.spec.nodeMetadata.forbiddenLabels + + + +Define the labels that a Tenant Owner cannot set for their nodes. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### CapsuleConfiguration.spec.overrides + + + +Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
TLSSecretNamestring + Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed.
+
+ Default: capsule-tls
+
true
mutatingWebhookConfigurationNamestring + Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
+
+ Default: capsule-mutating-webhook-configuration
+
true
validatingWebhookConfigurationNamestring + Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
+
+ Default: capsule-validating-webhook-configuration
+
true
+ +## Tenant + + + + + + +Tenant is the Schema for the tenants API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringTenanttrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + TenantSpec defines the desired state of Tenant.
+
false
statusobject + Returns the observed state of the Tenant.
+
false
+ + +### Tenant.spec + + + +TenantSpec defines the desired state of Tenant. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
owners[]object + Specifies the owners of the Tenant. Mandatory.
+
true
additionalRoleBindings[]object + Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
+
false
containerRegistriesobject + Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
+
false
cordonedboolean + Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
+
false
imagePullPolicies[]enum + Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
+
false
ingressOptionsobject + Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+
false
limitRangesobject + Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+
false
namespaceOptionsobject + Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+
false
networkPoliciesobject + Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
false
nodeSelectormap[string]string + Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
+
false
preventDeletionboolean + Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
+
false
priorityClassesobject + Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
+
false
resourceQuotasobject + Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
+
false
serviceOptionsobject + Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+
false
storageClassesobject + Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
+
false
+ + +### Tenant.spec.owners[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
clusterRoles[]string + Defines additional cluster-roles for the specific Owner.
+
true
kindenum + Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
+
+ Enum: User, Group, ServiceAccount
+
true
namestring + Name of tenant owner.
+
true
proxySettings[]object + Proxy settings for tenant owner.
+
false
+ + +### Tenant.spec.owners[index].proxySettings[index] + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindenum +
+
+ Enum: Nodes, StorageClasses, IngressClasses, PriorityClasses
+
true
operations[]enum +
+
true
+ + +### Tenant.spec.additionalRoleBindings[index] + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
clusterRoleNamestring +
+
true
subjects[]object + kubebuilder:validation:Minimum=1
+
true
+ + +### Tenant.spec.additionalRoleBindings[index].subjects[index] + + + +Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error.
+
true
namestring + Name of the object being referenced.
+
true
apiGroupstring + APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
+
false
namespacestring + Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error.
+
false
+ + +### Tenant.spec.containerRegistries + + + +Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.ingressOptions + + + +Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowWildcardHostnamesboolean + Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.
+
true
allowedClassesobject + Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
+
false
allowedHostnamesobject + Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional.
+
false
hostnameCollisionScopeenum + Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. + - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. + - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. + - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. + Optional.
+
+ Enum: Cluster, Tenant, Namespace, Disabled
+ Default: Disabled
+
false
+ + +### Tenant.spec.ingressOptions.allowedClasses + + + +Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.ingressOptions.allowedHostnames + + + +Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.limitRanges + + + +Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
items[]object +
+
false
+ + +### Tenant.spec.limitRanges.items[index] + + + +LimitRangeSpec defines a min/max usage limit for resources that match on kind. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
limits[]object + Limits is the list of LimitRangeItem objects that are enforced.
+
true
+ + +### Tenant.spec.limitRanges.items[index].limits[index] + + + +LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
typestring + Type of resource that this limit applies to.
+
true
defaultmap[string]int or string + Default resource requirement limit value by resource name if resource limit is omitted.
+
false
defaultRequestmap[string]int or string + DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
+
false
maxmap[string]int or string + Max usage constraints on this kind by resource name.
+
false
maxLimitRequestRatiomap[string]int or string + MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.
+
false
minmap[string]int or string + Min usage constraints on this kind by resource name.
+
false
+ + +### Tenant.spec.namespaceOptions + + + +Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
forbiddenAnnotationsobject + Define the annotations that a Tenant Owner cannot set for their Namespace resources.
+
true
forbiddenLabelsobject + Define the labels that a Tenant Owner cannot set for their Namespace resources.
+
true
additionalMetadataobject + Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
+
false
quotainteger + Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+
+ Format: int32
+ Minimum: 1
+
false
+ + +### Tenant.spec.namespaceOptions.forbiddenAnnotations + + + +Define the annotations that a Tenant Owner cannot set for their Namespace resources. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### Tenant.spec.namespaceOptions.forbiddenLabels + + + +Define the labels that a Tenant Owner cannot set for their Namespace resources. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### Tenant.spec.namespaceOptions.additionalMetadata + + + +Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
annotationsmap[string]string +
+
false
labelsmap[string]string +
+
false
+ + +### Tenant.spec.networkPolicies + + + +Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
items[]object +
+
false
+ + +### Tenant.spec.networkPolicies.items[index] + + + +NetworkPolicySpec provides the specification of a NetworkPolicy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
podSelectorobject + Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
+
true
egress[]object + List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
+
false
ingress[]object + List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
+
false
policyTypes[]string + List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
+
false
+ + +### Tenant.spec.networkPolicies.items[index].podSelector + + + +Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].podSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index] + + + +NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
ports[]object + List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+
false
to[]object + List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].ports[index] + + + +NetworkPolicyPort describes a port to allow traffic on + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
endPortinteger + If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+
+ Format: int32
+
false
portint or string + The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+
false
protocolstring + The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index] + + + +NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
ipBlockobject + IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
false
namespaceSelectorobject + Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+
false
podSelectorobject + This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].ipBlock + + + +IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
cidrstring + CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+
true
except[]string + Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector + + + +Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector + + + +This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index] + + + +NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
from[]object + List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
+
false
ports[]object + List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index] + + + +NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
ipBlockobject + IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
false
namespaceSelectorobject + Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+
false
podSelectorobject + This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].ipBlock + + + +IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
cidrstring + CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+
true
except[]string + Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector + + + +Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector + + + +This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].ports[index] + + + +NetworkPolicyPort describes a port to allow traffic on + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
endPortinteger + If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+
+ Format: int32
+
false
portint or string + The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+
false
protocolstring + The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+
false
+ + +### Tenant.spec.priorityClasses + + + +Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.resourceQuotas + + + +Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
items[]object +
+
false
scopeenum + Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
+
+ Enum: Tenant, Namespace
+ Default: Tenant
+
false
+ + +### Tenant.spec.resourceQuotas.items[index] + + + +ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
hardmap[string]int or string + hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/
+
false
scopeSelectorobject + scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
+
false
scopes[]string + A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
+
false
+ + +### Tenant.spec.resourceQuotas.items[index].scopeSelector + + + +scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + A list of scope selector requirements by scope of the resources.
+
false
+ + +### Tenant.spec.resourceQuotas.items[index].scopeSelector.matchExpressions[index] + + + +A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
operatorstring + Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
+
true
scopeNamestring + The name of the scope that the selector applies to.
+
true
values[]string + An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.serviceOptions + + + +Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
additionalMetadataobject + Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
+
false
allowedServicesobject + Block or deny certain type of Services. Optional.
+
false
externalIPsobject + Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
+
false
+ + +### Tenant.spec.serviceOptions.additionalMetadata + + + +Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
annotationsmap[string]string +
+
false
labelsmap[string]string +
+
false
+ + +### Tenant.spec.serviceOptions.allowedServices + + + +Block or deny certain type of Services. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
externalNameboolean + Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+
false
loadBalancerboolean + Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+
false
nodePortboolean + Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+
false
+ + +### Tenant.spec.serviceOptions.externalIPs + + + +Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
true
+ + +### Tenant.spec.storageClasses + + + +Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.status + + + +Returns the observed state of the Tenant. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
sizeinteger + How many namespaces are assigned to the Tenant.
+
true
stateenum + The operational state of the Tenant. Possible values are "Active", "Cordoned".
+
+ Enum: Cordoned, Active
+ Default: Active
+
true
namespaces[]string + List of namespaces assigned to the Tenant.
+
false
+ # capsule.clastix.io/v1beta1 Resource Types: From aca062f6b00e02d7919aa97d1dfc5cc2baac444b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 27 Sep 2022 18:27:47 +0200 Subject: [PATCH 09/26] refactor: abstracting types used by several api versions --- api/v1alpha1/additional_metadata.go | 6 +- api/v1alpha1/allowed_list.go | 37 -- api/v1alpha1/allowed_list_test.go | 73 ---- api/v1alpha1/conversion_hub.go | 262 +++++------- api/v1alpha1/conversion_hub_test.go | 43 +- api/v1alpha1/tenant_func.go | 16 +- api/v1alpha1/tenant_types.go | 18 +- api/v1alpha1/tenant_webhook.go | 8 +- api/v1alpha1/zz_generated.deepcopy.go | 100 +---- api/v1beta1/additional_role_bindings.go | 12 - api/v1beta1/allowed_list.go | 38 -- api/v1beta1/deny_wildcard.go | 4 +- api/v1beta1/forbidden_list.go | 38 -- api/v1beta1/forbidden_list_test.go | 73 ---- api/v1beta1/hostname_collision_scope.go | 14 - api/v1beta1/image_pull_policy.go | 11 - api/v1beta1/ingress_options.go | 10 +- api/v1beta1/limit_ranges.go | 10 - api/v1beta1/namespace_options.go | 40 +- api/v1beta1/owner_list.go | 28 +- api/v1beta1/owner_role.go | 4 +- api/v1beta1/service_allowed_ips.go | 11 - api/v1beta1/service_options.go | 10 +- api/v1beta1/tenant_annotations.go | 6 - api/v1beta1/tenant_func.go | 20 +- api/v1beta1/tenant_labels.go | 32 -- api/v1beta1/tenant_types.go | 22 +- api/v1beta1/zz_generated.deepcopy.go | 205 +-------- api/v1beta2/allowed_list_test.go | 73 ---- ...=> capsuleconfiguration_convertion_hub.go} | 19 +- api/v1beta2/capsuleconfiguration_types.go | 6 +- api/v1beta2/conversion_hub.go | 401 ------------------ api/v1beta2/ingress_options.go | 10 +- api/v1beta2/namespace_options.go | 10 +- api/v1beta2/network_policy.go | 12 - api/v1beta2/resource_quota.go | 21 - api/v1beta2/service_allowed_ips.go | 11 - api/v1beta2/tenant_annotations.go | 26 -- api/v1beta2/tenant_conversion_hub.go | 246 +++++++++++ api/v1beta2/tenant_types.go | 20 +- api/v1beta2/zz_generated.deepcopy.go | 207 +-------- charts/capsule/crds/tenant-crd.yaml | 8 +- controllers/servicelabels/abstract.go | 3 +- .../tenant/annotations.go | 14 +- controllers/tenant/limitranges.go | 5 +- controllers/tenant/namespaces.go | 15 +- controllers/tenant/networkpolicies.go | 5 +- controllers/tenant/resourcequotas.go | 14 +- controllers/tenant/rolebindings.go | 16 +- controllers/tenant/utils.go | 2 +- e2e/additional_role_bindings_test.go | 3 +- e2e/allowed_external_ips_test.go | 7 +- e2e/container_registry_test.go | 3 +- e2e/disable_externalname_test.go | 5 +- e2e/disable_loadbalancer_test.go | 5 +- e2e/disable_node_ports_test.go | 5 +- e2e/enable_loadbalancer_test.go | 5 +- e2e/imagepullpolicy_multiple_test.go | 3 +- e2e/imagepullpolicy_single_test.go | 3 +- e2e/ingress_class_extensions_test.go | 3 +- e2e/ingress_class_networking_test.go | 3 +- ..._hostnames_collision_cluster_scope_test.go | 5 +- ...gress_hostnames_collision_disabled_test.go | 3 +- ...ostnames_collision_namespace_scope_test.go | 3 +- ...s_hostnames_collision_tenant_scope_test.go | 3 +- e2e/ingress_hostnames_test.go | 3 +- e2e/namespace_additional_metadata_test.go | 3 +- e2e/owner_webhooks_test.go | 9 +- e2e/pod_priority_class_test.go | 3 +- e2e/resource_quota_exceeded_test.go | 5 +- e2e/selecting_non_owned_tenant_test.go | 3 +- e2e/selecting_tenant_with_label_test.go | 3 +- e2e/service_metadata_test.go | 6 +- e2e/storage_class_test.go | 3 +- e2e/tenant_resources_changes_test.go | 7 +- e2e/tenant_resources_test.go | 7 +- .../api}/additional_metadata.go | 4 +- .../api}/additional_role_bindings.go | 4 +- {api/v1beta2 => pkg/api}/allowed_list.go | 4 +- {api/v1beta1 => pkg/api}/allowed_list_test.go | 3 +- .../api}/external_service_ips.go | 4 +- {api/v1beta2 => pkg/api}/forbidden_list.go | 6 +- .../api}/forbidden_list_test.go | 3 +- .../api}/hostname_collision_scope.go | 2 +- {api/v1beta2 => pkg/api}/image_pull_policy.go | 2 +- {api/v1beta2 => pkg/api}/limit_ranges.go | 4 +- {api/v1beta1 => pkg/api}/network_policy.go | 4 +- {api/v1beta1 => pkg/api}/resource_quota.go | 4 +- .../api}/service_allowed_types.go | 4 +- {api/v1beta2 => pkg/api}/service_options.go | 4 +- pkg/api/zz_generated.deepcopy.go | 250 +++++++++++ pkg/configuration/client.go | 2 +- pkg/configuration/configuration.go | 2 +- {api/v1alpha1 => pkg/utils}/tenant_labels.go | 8 +- pkg/webhook/ingress/errors.go | 18 +- pkg/webhook/ingress/validate_collision.go | 13 +- pkg/webhook/namespace/errors.go | 2 +- pkg/webhook/namespace/patch.go | 3 +- pkg/webhook/networkpolicy/validating.go | 3 +- pkg/webhook/node/errors.go | 2 +- pkg/webhook/ownerreference/patching.go | 3 +- pkg/webhook/pod/containerregistry_errors.go | 6 +- pkg/webhook/pod/priorityclass_errors.go | 6 +- pkg/webhook/pvc/errors.go | 12 +- pkg/webhook/service/errors.go | 4 +- 105 files changed, 1009 insertions(+), 1783 deletions(-) delete mode 100644 api/v1alpha1/allowed_list.go delete mode 100644 api/v1alpha1/allowed_list_test.go delete mode 100644 api/v1beta1/additional_role_bindings.go delete mode 100644 api/v1beta1/allowed_list.go delete mode 100644 api/v1beta1/forbidden_list.go delete mode 100644 api/v1beta1/forbidden_list_test.go delete mode 100644 api/v1beta1/hostname_collision_scope.go delete mode 100644 api/v1beta1/image_pull_policy.go delete mode 100644 api/v1beta1/limit_ranges.go delete mode 100644 api/v1beta1/service_allowed_ips.go delete mode 100644 api/v1beta1/tenant_labels.go delete mode 100644 api/v1beta2/allowed_list_test.go rename api/v1beta2/{capsuleconfiguration_funcs.go => capsuleconfiguration_convertion_hub.go} (84%) delete mode 100644 api/v1beta2/conversion_hub.go delete mode 100644 api/v1beta2/network_policy.go delete mode 100644 api/v1beta2/resource_quota.go delete mode 100644 api/v1beta2/service_allowed_ips.go delete mode 100644 api/v1beta2/tenant_annotations.go create mode 100644 api/v1beta2/tenant_conversion_hub.go rename api/v1alpha1/tenant_annotations.go => controllers/tenant/annotations.go (69%) rename {api/v1beta1 => pkg/api}/additional_metadata.go (82%) rename {api/v1alpha1 => pkg/api}/additional_role_bindings.go (85%) rename {api/v1beta2 => pkg/api}/allowed_list.go (93%) rename {api/v1beta1 => pkg/api}/allowed_list_test.go (98%) rename {api/v1alpha1 => pkg/api}/external_service_ips.go (84%) rename {api/v1beta2 => pkg/api}/forbidden_list.go (85%) rename {api/v1beta2 => pkg/api}/forbidden_list_test.go (98%) rename {api/v1beta2 => pkg/api}/hostname_collision_scope.go (96%) rename {api/v1beta2 => pkg/api}/image_pull_policy.go (93%) rename {api/v1beta2 => pkg/api}/limit_ranges.go (80%) rename {api/v1beta1 => pkg/api}/network_policy.go (82%) rename {api/v1beta1 => pkg/api}/resource_quota.go (92%) rename {api/v1beta2 => pkg/api}/service_allowed_types.go (92%) rename {api/v1beta2 => pkg/api}/service_options.go (92%) create mode 100644 pkg/api/zz_generated.deepcopy.go rename {api/v1alpha1 => pkg/utils}/tenant_labels.go (79%) diff --git a/api/v1alpha1/additional_metadata.go b/api/v1alpha1/additional_metadata.go index 0f032997..377688df 100644 --- a/api/v1alpha1/additional_metadata.go +++ b/api/v1alpha1/additional_metadata.go @@ -3,7 +3,7 @@ package v1alpha1 -type AdditionalMetadataSpec struct { - AdditionalLabels map[string]string `json:"additionalLabels,omitempty"` - AdditionalAnnotations map[string]string `json:"additionalAnnotations,omitempty"` +type AdditionalMetadata struct { + Labels map[string]string `json:"additionalLabels,omitempty"` + Annotations map[string]string `json:"additionalAnnotations,omitempty"` } diff --git a/api/v1alpha1/allowed_list.go b/api/v1alpha1/allowed_list.go deleted file mode 100644 index eac46577..00000000 --- a/api/v1alpha1/allowed_list.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1alpha1 - -import ( - "regexp" - "sort" - "strings" -) - -type AllowedListSpec struct { - Exact []string `json:"allowed,omitempty"` - Regex string `json:"allowedRegex,omitempty"` -} - -func (in *AllowedListSpec) ExactMatch(value string) (ok bool) { - if len(in.Exact) > 0 { - sort.SliceStable(in.Exact, func(i, j int) bool { - return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) - }) - - i := sort.SearchStrings(in.Exact, value) - - ok = i < len(in.Exact) && in.Exact[i] == value - } - - return -} - -func (in AllowedListSpec) RegexMatch(value string) (ok bool) { - if len(in.Regex) > 0 { - ok = regexp.MustCompile(in.Regex).MatchString(value) - } - - return -} diff --git a/api/v1alpha1/allowed_list_test.go b/api/v1alpha1/allowed_list_test.go deleted file mode 100644 index 7aef5b60..00000000 --- a/api/v1alpha1/allowed_list_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1alpha1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAllowedListSpec_ExactMatch(t *testing.T) { - type tc struct { - In []string - True []string - False []string - } - - for _, tc := range []tc{ - { - []string{"foo", "bar", "bizz", "buzz"}, - []string{"foo", "bar", "bizz", "buzz"}, - []string{"bing", "bong"}, - }, - { - []string{"one", "two", "three"}, - []string{"one", "two", "three"}, - []string{"a", "b", "c"}, - }, - { - nil, - nil, - []string{"any", "value"}, - }, - } { - a := AllowedListSpec{ - Exact: tc.In, - } - - for _, ok := range tc.True { - assert.True(t, a.ExactMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.ExactMatch(ko)) - } - } -} - -func TestAllowedListSpec_RegexMatch(t *testing.T) { - type tc struct { - Regex string - True []string - False []string - } - - for _, tc := range []tc{ - {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, - {``, nil, []string{"any", "value"}}, - } { - a := AllowedListSpec{ - Regex: tc.Regex, - } - - for _, ok := range tc.True { - assert.True(t, a.RegexMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.RegexMatch(ko)) - } - } -} diff --git a/api/v1alpha1/conversion_hub.go b/api/v1alpha1/conversion_hub.go index 00beee62..fe636631 100644 --- a/api/v1alpha1/conversion_hub.go +++ b/api/v1alpha1/conversion_hub.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/conversion" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) const ( @@ -48,7 +49,7 @@ const ( ingressHostnameCollisionScope = "ingress.capsule.clastix.io/hostname-collision-scope" ) -func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { +func (in *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { serviceKindToAnnotationMap := map[capsulev1beta1.ProxyServiceKind][]string{ capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation}, capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation}, @@ -75,7 +76,7 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { ownerServiceAccountAnnotation: capsulev1beta1.ServiceAccountOwner, } - annotations := t.GetAnnotations() + annotations := in.GetAnnotations() operations := make(map[string]map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation) @@ -111,9 +112,9 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { } owners = append(owners, capsulev1beta1.OwnerSpec{ - Kind: capsulev1beta1.OwnerKind(t.Spec.Owner.Kind), - Name: t.Spec.Owner.Name, - ProxyOperations: getProxySettingsForOwner(t.Spec.Owner.Name), + Kind: capsulev1beta1.OwnerKind(in.Spec.Owner.Kind), + Name: in.Spec.Owner.Name, + ProxyOperations: getProxySettingsForOwner(in.Spec.Owner.Name), }) for ownerAnnotation, ownerKind := range annotationToOwnerKindMap { @@ -133,150 +134,134 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { } // nolint:gocognit,gocyclo,cyclop,maintidx -func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { +func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error { dst, ok := dstRaw.(*capsulev1beta1.Tenant) if !ok { return fmt.Errorf("expected type *capsulev1beta1.Tenant, got %T", dst) } - annotations := t.GetAnnotations() + annotations := in.GetAnnotations() // ObjectMeta - dst.ObjectMeta = t.ObjectMeta + dst.ObjectMeta = in.ObjectMeta // Spec - if t.Spec.NamespaceQuota != nil { + if in.Spec.NamespaceQuota != nil { if dst.Spec.NamespaceOptions == nil { dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} } - dst.Spec.NamespaceOptions.Quota = t.Spec.NamespaceQuota + dst.Spec.NamespaceOptions.Quota = in.Spec.NamespaceQuota } - dst.Spec.NodeSelector = t.Spec.NodeSelector + dst.Spec.NodeSelector = in.Spec.NodeSelector - dst.Spec.Owners = t.convertV1Alpha1OwnerToV1Beta1() + dst.Spec.Owners = in.convertV1Alpha1OwnerToV1Beta1() - if t.Spec.NamespacesMetadata != nil { + if in.Spec.NamespacesMetadata != nil { if dst.Spec.NamespaceOptions == nil { dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} } - dst.Spec.NamespaceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ - Labels: t.Spec.NamespacesMetadata.AdditionalLabels, - Annotations: t.Spec.NamespacesMetadata.AdditionalAnnotations, + dst.Spec.NamespaceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{ + Labels: in.Spec.NamespacesMetadata.Labels, + Annotations: in.Spec.NamespacesMetadata.Annotations, } } - if t.Spec.ServicesMetadata != nil { + if in.Spec.ServicesMetadata != nil { if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{ - AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{ - Labels: t.Spec.ServicesMetadata.AdditionalLabels, - Annotations: t.Spec.ServicesMetadata.AdditionalAnnotations, - }, - } + dst.Spec.ServiceOptions = &api.ServiceOptions{} } - } - if t.Spec.StorageClasses != nil { - dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.StorageClasses.Exact, - Regex: t.Spec.StorageClasses.Regex, + dst.Spec.ServiceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{ + Labels: in.Spec.ServicesMetadata.Labels, + Annotations: in.Spec.ServicesMetadata.Annotations, } } - if v, annotationOk := t.Annotations[ingressHostnameCollisionScope]; annotationOk { + if in.Spec.StorageClasses != nil { + dst.Spec.StorageClasses = in.Spec.StorageClasses + } + + if v, annotationOk := in.Annotations[ingressHostnameCollisionScope]; annotationOk { switch v { - case string(capsulev1beta1.HostnameCollisionScopeCluster), string(capsulev1beta1.HostnameCollisionScopeTenant), string(capsulev1beta1.HostnameCollisionScopeNamespace): - dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(v) + case string(api.HostnameCollisionScopeCluster), string(api.HostnameCollisionScopeTenant), string(api.HostnameCollisionScopeNamespace): + dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScope(v) default: - dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScopeDisabled + dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScopeDisabled } } - if t.Spec.IngressClasses != nil { - dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.IngressClasses.Exact, - Regex: t.Spec.IngressClasses.Regex, + if in.Spec.IngressClasses != nil { + dst.Spec.IngressOptions.AllowedClasses = &api.AllowedListSpec{ + Exact: in.Spec.IngressClasses.Exact, + Regex: in.Spec.IngressClasses.Regex, } } - if t.Spec.IngressHostnames != nil { - dst.Spec.IngressOptions.AllowedHostnames = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.IngressHostnames.Exact, - Regex: t.Spec.IngressHostnames.Regex, + if in.Spec.IngressHostnames != nil { + dst.Spec.IngressOptions.AllowedHostnames = &api.AllowedListSpec{ + Exact: in.Spec.IngressHostnames.Exact, + Regex: in.Spec.IngressHostnames.Regex, } } - if t.Spec.ContainerRegistries != nil { - dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.ContainerRegistries.Exact, - Regex: t.Spec.ContainerRegistries.Regex, + if in.Spec.ContainerRegistries != nil { + dst.Spec.ContainerRegistries = &api.AllowedListSpec{ + Exact: in.Spec.ContainerRegistries.Exact, + Regex: in.Spec.ContainerRegistries.Regex, } } - if len(t.Spec.NetworkPolicies) > 0 { - dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{ - Items: t.Spec.NetworkPolicies, + if len(in.Spec.NetworkPolicies) > 0 { + dst.Spec.NetworkPolicies = api.NetworkPolicySpec{ + Items: in.Spec.NetworkPolicies, } } - if len(t.Spec.LimitRanges) > 0 { - dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{ - Items: t.Spec.LimitRanges, + if len(in.Spec.LimitRanges) > 0 { + dst.Spec.LimitRanges = api.LimitRangesSpec{ + Items: in.Spec.LimitRanges, } } - if len(t.Spec.ResourceQuota) > 0 { - dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{ - Scope: func() capsulev1beta1.ResourceQuotaScope { - if v, annotationOk := t.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk { + if len(in.Spec.ResourceQuota) > 0 { + dst.Spec.ResourceQuota = api.ResourceQuotaSpec{ + Scope: func() api.ResourceQuotaScope { + if v, annotationOk := in.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk { switch v { - case string(capsulev1beta1.ResourceQuotaScopeNamespace): - return capsulev1beta1.ResourceQuotaScopeNamespace - case string(capsulev1beta1.ResourceQuotaScopeTenant): - return capsulev1beta1.ResourceQuotaScopeTenant + case string(api.ResourceQuotaScopeNamespace): + return api.ResourceQuotaScopeNamespace + case string(api.ResourceQuotaScopeTenant): + return api.ResourceQuotaScopeTenant } } - return capsulev1beta1.ResourceQuotaScopeTenant + return api.ResourceQuotaScopeTenant }(), - Items: t.Spec.ResourceQuota, + Items: in.Spec.ResourceQuota, } } - if len(t.Spec.AdditionalRoleBindings) > 0 { - for _, rb := range t.Spec.AdditionalRoleBindings { - dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{ - ClusterRoleName: rb.ClusterRoleName, - Subjects: rb.Subjects, - }) - } - } + dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings - if t.Spec.ExternalServiceIPs != nil { + if in.Spec.ExternalServiceIPs != nil { if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} - } - - dst.Spec.ServiceOptions.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: make([]capsulev1beta1.AllowedIP, len(t.Spec.ExternalServiceIPs.Allowed)), + dst.Spec.ServiceOptions = &api.ServiceOptions{} } - for i, IP := range t.Spec.ExternalServiceIPs.Allowed { - dst.Spec.ServiceOptions.ExternalServiceIPs.Allowed[i] = capsulev1beta1.AllowedIP(IP) - } + dst.Spec.ServiceOptions.ExternalServiceIPs = in.Spec.ExternalServiceIPs } pullPolicies, ok := annotations[podAllowedImagePullPolicyAnnotation] if ok { for _, policy := range strings.Split(pullPolicies, ",") { - dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(policy)) + dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, api.ImagePullPolicySpec(policy)) } } - priorityClasses := capsulev1beta1.AllowedListSpec{} + priorityClasses := api.AllowedListSpec{} priorityClassAllowed, ok := annotations[podPriorityAllowedAnnotation] @@ -298,15 +283,15 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { if ok { val, err := strconv.ParseBool(enableNodePorts) if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, t.GetName())) + return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, in.GetName())) } if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + dst.Spec.ServiceOptions = &api.ServiceOptions{} } if dst.Spec.ServiceOptions.AllowedServices == nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{} + dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.BoolPtr(val) @@ -316,15 +301,15 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { if ok { val, err := strconv.ParseBool(enableExternalName) if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, t.GetName())) + return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, in.GetName())) } if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + dst.Spec.ServiceOptions = &api.ServiceOptions{} } if dst.Spec.ServiceOptions.AllowedServices == nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{} + dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.BoolPtr(val) @@ -334,23 +319,23 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { if ok { val, err := strconv.ParseBool(loadBalancerService) if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, t.GetName())) + return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, in.GetName())) } if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + dst.Spec.ServiceOptions = &api.ServiceOptions{} } if dst.Spec.ServiceOptions.AllowedServices == nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{} + dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.BoolPtr(val) } // Status dst.Status = capsulev1beta1.TenantStatus{ - Size: t.Status.Size, - Namespaces: t.Status.Namespaces, + Size: in.Status.Size, + Namespaces: in.Status.Namespaces, } // Remove unneeded annotations delete(dst.ObjectMeta.Annotations, podAllowedImagePullPolicyAnnotation) @@ -381,7 +366,7 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { } // nolint:gocognit,gocyclo,cyclop -func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { +func (in *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { ownersAnnotations := map[string][]string{ ownerGroupsAnnotation: nil, ownerUsersAnnotation: nil, @@ -402,7 +387,7 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { for i, owner := range src.Spec.Owners { if i == 0 { - t.Spec.Owner = OwnerSpec{ + in.Spec.Owner = OwnerSpec{ Name: owner.Name, Kind: Kind(owner.Kind), } @@ -469,114 +454,89 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { for k, v := range ownersAnnotations { if len(v) > 0 { - t.Annotations[k] = strings.Join(v, ",") + in.Annotations[k] = strings.Join(v, ",") } } for k, v := range proxyAnnotations { if len(v) > 0 { - t.Annotations[k] = strings.Join(v, ",") + in.Annotations[k] = strings.Join(v, ",") } } } -// nolint:gocyclo,cyclop -func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error { +//nolint:cyclop +func (in *Tenant) ConvertFrom(srcRaw conversion.Hub) error { src, ok := srcRaw.(*capsulev1beta1.Tenant) if !ok { return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", srcRaw) } // ObjectMeta - t.ObjectMeta = src.ObjectMeta + in.ObjectMeta = src.ObjectMeta // Spec if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.Quota != nil { - t.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota + in.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota } - t.Spec.NodeSelector = src.Spec.NodeSelector + in.Spec.NodeSelector = src.Spec.NodeSelector - if t.Annotations == nil { - t.Annotations = make(map[string]string) + if in.Annotations == nil { + in.Annotations = make(map[string]string) } - t.convertV1Beta1OwnerToV1Alpha1(src) + in.convertV1Beta1OwnerToV1Alpha1(src) if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.AdditionalMetadata != nil { - t.Spec.NamespacesMetadata = &AdditionalMetadataSpec{ - AdditionalLabels: src.Spec.NamespaceOptions.AdditionalMetadata.Labels, - AdditionalAnnotations: src.Spec.NamespaceOptions.AdditionalMetadata.Annotations, + in.Spec.NamespacesMetadata = &AdditionalMetadata{ + Labels: src.Spec.NamespaceOptions.AdditionalMetadata.Labels, + Annotations: src.Spec.NamespaceOptions.AdditionalMetadata.Annotations, } } if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AdditionalMetadata != nil { - t.Spec.ServicesMetadata = &AdditionalMetadataSpec{ - AdditionalLabels: src.Spec.ServiceOptions.AdditionalMetadata.Labels, - AdditionalAnnotations: src.Spec.ServiceOptions.AdditionalMetadata.Annotations, + in.Spec.ServicesMetadata = &AdditionalMetadata{ + Labels: src.Spec.ServiceOptions.AdditionalMetadata.Labels, + Annotations: src.Spec.ServiceOptions.AdditionalMetadata.Annotations, } } if src.Spec.StorageClasses != nil { - t.Spec.StorageClasses = &AllowedListSpec{ - Exact: src.Spec.StorageClasses.Exact, - Regex: src.Spec.StorageClasses.Regex, - } + in.Spec.StorageClasses = src.Spec.StorageClasses } - t.Annotations[ingressHostnameCollisionScope] = string(src.Spec.IngressOptions.HostnameCollisionScope) + in.Annotations[ingressHostnameCollisionScope] = string(src.Spec.IngressOptions.HostnameCollisionScope) if src.Spec.IngressOptions.AllowedClasses != nil { - t.Spec.IngressClasses = &AllowedListSpec{ - Exact: src.Spec.IngressOptions.AllowedClasses.Exact, - Regex: src.Spec.IngressOptions.AllowedClasses.Regex, - } + in.Spec.IngressClasses = src.Spec.IngressOptions.AllowedClasses } if src.Spec.IngressOptions.AllowedHostnames != nil { - t.Spec.IngressHostnames = &AllowedListSpec{ - Exact: src.Spec.IngressOptions.AllowedHostnames.Exact, - Regex: src.Spec.IngressOptions.AllowedHostnames.Regex, - } + in.Spec.IngressHostnames = src.Spec.IngressOptions.AllowedHostnames } if src.Spec.ContainerRegistries != nil { - t.Spec.ContainerRegistries = &AllowedListSpec{ - Exact: src.Spec.ContainerRegistries.Exact, - Regex: src.Spec.ContainerRegistries.Regex, - } + in.Spec.ContainerRegistries = src.Spec.ContainerRegistries } if len(src.Spec.NetworkPolicies.Items) > 0 { - t.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items + in.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items } if len(src.Spec.LimitRanges.Items) > 0 { - t.Spec.LimitRanges = src.Spec.LimitRanges.Items + in.Spec.LimitRanges = src.Spec.LimitRanges.Items } if len(src.Spec.ResourceQuota.Items) > 0 { - t.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope) - t.Spec.ResourceQuota = src.Spec.ResourceQuota.Items + in.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope) + in.Spec.ResourceQuota = src.Spec.ResourceQuota.Items } - if len(src.Spec.AdditionalRoleBindings) > 0 { - for _, rb := range src.Spec.AdditionalRoleBindings { - t.Spec.AdditionalRoleBindings = append(t.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{ - ClusterRoleName: rb.ClusterRoleName, - Subjects: rb.Subjects, - }) - } - } + in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.ExternalServiceIPs != nil { - t.Spec.ExternalServiceIPs = &ExternalServiceIPsSpec{ - Allowed: make([]AllowedIP, len(src.Spec.ServiceOptions.ExternalServiceIPs.Allowed)), - } - - for i, IP := range src.Spec.ServiceOptions.ExternalServiceIPs.Allowed { - t.Spec.ExternalServiceIPs.Allowed[i] = AllowedIP(IP) - } + in.Spec.ExternalServiceIPs = src.Spec.ServiceOptions.ExternalServiceIPs } if len(src.Spec.ImagePullPolicies) != 0 { @@ -586,35 +546,35 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error { pullPolicies = append(pullPolicies, string(policy)) } - t.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",") + in.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",") } if src.Spec.PriorityClasses != nil { if len(src.Spec.PriorityClasses.Exact) != 0 { - t.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",") + in.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",") } if src.Spec.PriorityClasses.Regex != "" { - t.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex + in.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex } } if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil { if src.Spec.ServiceOptions.AllowedServices.NodePort != nil { - t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort) + in.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort) } if src.Spec.ServiceOptions.AllowedServices.ExternalName != nil { - t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName) + in.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName) } if src.Spec.ServiceOptions.AllowedServices.LoadBalancer != nil { - t.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer) + in.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer) } } // Status - t.Status = TenantStatus{ + in.Status = TenantStatus{ Size: src.Status.Size, Namespaces: src.Status.Namespaces, } diff --git a/api/v1alpha1/conversion_hub_test.go b/api/v1alpha1/conversion_hub_test.go index 5c6f9311..275bfc1b 100644 --- a/api/v1alpha1/conversion_hub_test.go +++ b/api/v1alpha1/conversion_hub_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) // nolint:maintidx @@ -25,19 +26,19 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { nodeSelector := map[string]string{ "foo": "bar", } - v1alpha1AdditionalMetadataSpec := &AdditionalMetadataSpec{ - AdditionalLabels: map[string]string{ + v1alpha1AdditionalMetadataSpec := &AdditionalMetadata{ + Labels: map[string]string{ "foo": "bar", }, - AdditionalAnnotations: map[string]string{ + Annotations: map[string]string{ "foo": "bar", }, } - v1alpha1AllowedListSpec := &AllowedListSpec{ + v1alpha1AllowedListSpec := &api.AllowedListSpec{ Exact: []string{"foo", "bar"}, Regex: "^foo*", } - v1beta1AdditionalMetadataSpec := &capsulev1beta1.AdditionalMetadataSpec{ + v1beta1AdditionalMetadataSpec := &api.AdditionalMetadataSpec{ Labels: map[string]string{ "foo": "bar", }, @@ -49,18 +50,18 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { Quota: &namespaceQuota, AdditionalMetadata: v1beta1AdditionalMetadataSpec, } - v1beta1ServiceOptions := &capsulev1beta1.ServiceOptions{ + v1beta1ServiceOptions := &api.ServiceOptions{ AdditionalMetadata: v1beta1AdditionalMetadataSpec, - AllowedServices: &capsulev1beta1.AllowedServices{ + AllowedServices: &api.AllowedServices{ NodePort: pointer.BoolPtr(false), ExternalName: pointer.BoolPtr(false), LoadBalancer: pointer.BoolPtr(false), }, - ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: []capsulev1beta1.AllowedIP{"192.168.0.1"}, + ExternalServiceIPs: &api.ExternalServiceIPsSpec{ + Allowed: []api.AllowedIP{"192.168.0.1"}, }, } - v1beta1AllowedListSpec := &capsulev1beta1.AllowedListSpec{ + v1beta1AllowedListSpec := &api.AllowedListSpec{ Exact: []string{"foo", "bar"}, Regex: "^foo*", } @@ -236,23 +237,23 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { ServiceOptions: v1beta1ServiceOptions, StorageClasses: v1beta1AllowedListSpec, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled, + HostnameCollisionScope: api.HostnameCollisionScopeDisabled, AllowedClasses: v1beta1AllowedListSpec, AllowedHostnames: v1beta1AllowedListSpec, }, ContainerRegistries: v1beta1AllowedListSpec, NodeSelector: nodeSelector, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{ Items: networkPolicies, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{ + LimitRanges: api.LimitRangesSpec{ Items: limitRanges, }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{ - Scope: capsulev1beta1.ResourceQuotaScopeNamespace, + ResourceQuota: api.ResourceQuotaSpec{ + Scope: api.ResourceQuotaScopeNamespace, Items: resourceQuotas, }, - AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{ + AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ { ClusterRoleName: "crds-rolebinding", Subjects: []rbacv1.Subject{ @@ -264,8 +265,8 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { }, }, }, - ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"}, - PriorityClasses: &capsulev1beta1.AllowedListSpec{ + ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"}, + PriorityClasses: &api.AllowedListSpec{ Exact: []string{"default"}, Regex: "^tier-.*$", }, @@ -323,7 +324,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { NetworkPolicies: networkPolicies, LimitRanges: limitRanges, ResourceQuota: resourceQuotas, - AdditionalRoleBindings: []AdditionalRoleBindingsSpec{ + AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ { ClusterRoleName: "crds-rolebinding", Subjects: []rbacv1.Subject{ @@ -335,8 +336,8 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { }, }, }, - ExternalServiceIPs: &ExternalServiceIPsSpec{ - Allowed: []AllowedIP{"192.168.0.1"}, + ExternalServiceIPs: &api.ExternalServiceIPsSpec{ + Allowed: []api.AllowedIP{"192.168.0.1"}, }, }, Status: TenantStatus{ diff --git a/api/v1alpha1/tenant_func.go b/api/v1alpha1/tenant_func.go index 07a3d7b5..6342f2d1 100644 --- a/api/v1alpha1/tenant_func.go +++ b/api/v1alpha1/tenant_func.go @@ -9,24 +9,24 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (t *Tenant) IsCordoned() bool { - if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { +func (in *Tenant) IsCordoned() bool { + if v, ok := in.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { return true } return false } -func (t *Tenant) IsFull() bool { +func (in *Tenant) IsFull() bool { // we don't have limits on assigned Namespaces - if t.Spec.NamespaceQuota == nil { + if in.Spec.NamespaceQuota == nil { return false } - return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceQuota) + return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceQuota) } -func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { +func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { var l []string for _, ns := range namespaces { @@ -37,6 +37,6 @@ func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { sort.Strings(l) - t.Status.Namespaces = l - t.Status.Size = uint(len(l)) + in.Status.Namespaces = l + in.Status.Size = uint(len(l)) } diff --git a/api/v1alpha1/tenant_types.go b/api/v1alpha1/tenant_types.go index b9f0f786..f8509f3f 100644 --- a/api/v1alpha1/tenant_types.go +++ b/api/v1alpha1/tenant_types.go @@ -7,6 +7,8 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // TenantSpec defines the desired state of Tenant. @@ -15,18 +17,18 @@ type TenantSpec struct { //+kubebuilder:validation:Minimum=1 NamespaceQuota *int32 `json:"namespaceQuota,omitempty"` - NamespacesMetadata *AdditionalMetadataSpec `json:"namespacesMetadata,omitempty"` - ServicesMetadata *AdditionalMetadataSpec `json:"servicesMetadata,omitempty"` - StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"` - IngressClasses *AllowedListSpec `json:"ingressClasses,omitempty"` - IngressHostnames *AllowedListSpec `json:"ingressHostnames,omitempty"` - ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + NamespacesMetadata *AdditionalMetadata `json:"namespacesMetadata,omitempty"` + ServicesMetadata *AdditionalMetadata `json:"servicesMetadata,omitempty"` + StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` + IngressClasses *api.AllowedListSpec `json:"ingressClasses,omitempty"` + IngressHostnames *api.AllowedListSpec `json:"ingressHostnames,omitempty"` + ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` NodeSelector map[string]string `json:"nodeSelector,omitempty"` NetworkPolicies []networkingv1.NetworkPolicySpec `json:"networkPolicies,omitempty"` LimitRanges []corev1.LimitRangeSpec `json:"limitRanges,omitempty"` ResourceQuota []corev1.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` - AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` - ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"` + AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` + ExternalServiceIPs *api.ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"` } // TenantStatus defines the observed state of Tenant. diff --git a/api/v1alpha1/tenant_webhook.go b/api/v1alpha1/tenant_webhook.go index 13443adb..0df967e9 100644 --- a/api/v1alpha1/tenant_webhook.go +++ b/api/v1alpha1/tenant_webhook.go @@ -4,18 +4,18 @@ package v1alpha1 import ( - "io/ioutil" + "os" ctrl "sigs.k8s.io/controller-runtime" ) -func (t *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error { - certData, _ := ioutil.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt") +func (in *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error { + certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt") if len(certData) == 0 { return nil } return ctrl.NewWebhookManagedBy(mgr). - For(t). + For(in). Complete() } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index efca1518..db654794 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -9,24 +9,24 @@ package v1alpha1 import ( + "github.com/clastix/capsule/pkg/api" corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" + "k8s.io/api/networking/v1" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { +func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) { *out = *in - if in.AdditionalLabels != nil { - in, out := &in.AdditionalLabels, &out.AdditionalLabels + if in.Labels != nil { + in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } - if in.AdditionalAnnotations != nil { - in, out := &in.AdditionalAnnotations, &out.AdditionalAnnotations + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val @@ -34,52 +34,12 @@ func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. -func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadata. +func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata { if in == nil { return nil } - out := new(AdditionalMetadataSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { - *out = *in - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]v1.Subject, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. -func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { - if in == nil { - return nil - } - out := new(AdditionalRoleBindingsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. -func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { - if in == nil { - return nil - } - out := new(AllowedListSpec) + out := new(AdditionalMetadata) in.DeepCopyInto(out) return out } @@ -162,26 +122,6 @@ func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { - *out = *in - if in.Allowed != nil { - in, out := &in.Allowed, &out.Allowed - *out = make([]AllowedIP, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. -func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { - if in == nil { - return nil - } - out := new(ExternalServiceIPsSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) { *out = *in @@ -267,32 +207,32 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.NamespacesMetadata != nil { in, out := &in.NamespacesMetadata, &out.NamespacesMetadata - *out = new(AdditionalMetadataSpec) + *out = new(AdditionalMetadata) (*in).DeepCopyInto(*out) } if in.ServicesMetadata != nil { in, out := &in.ServicesMetadata, &out.ServicesMetadata - *out = new(AdditionalMetadataSpec) + *out = new(AdditionalMetadata) (*in).DeepCopyInto(*out) } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.IngressClasses != nil { in, out := &in.IngressClasses, &out.IngressClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.IngressHostnames != nil { in, out := &in.IngressHostnames, &out.IngressHostnames - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.ContainerRegistries != nil { in, out := &in.ContainerRegistries, &out.ContainerRegistries - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.NodeSelector != nil { @@ -304,7 +244,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.NetworkPolicies != nil { in, out := &in.NetworkPolicies, &out.NetworkPolicies - *out = make([]networkingv1.NetworkPolicySpec, len(*in)) + *out = make([]v1.NetworkPolicySpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -325,14 +265,14 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.AdditionalRoleBindings != nil { in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings - *out = make([]AdditionalRoleBindingsSpec, len(*in)) + *out = make([]api.AdditionalRoleBindingsSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.ExternalServiceIPs != nil { in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs - *out = new(ExternalServiceIPsSpec) + *out = new(api.ExternalServiceIPsSpec) (*in).DeepCopyInto(*out) } } diff --git a/api/v1beta1/additional_role_bindings.go b/api/v1beta1/additional_role_bindings.go deleted file mode 100644 index f71e3cec..00000000 --- a/api/v1beta1/additional_role_bindings.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import rbacv1 "k8s.io/api/rbac/v1" - -type AdditionalRoleBindingsSpec struct { - ClusterRoleName string `json:"clusterRoleName"` - // kubebuilder:validation:Minimum=1 - Subjects []rbacv1.Subject `json:"subjects"` -} diff --git a/api/v1beta1/allowed_list.go b/api/v1beta1/allowed_list.go deleted file mode 100644 index d7e24cac..00000000 --- a/api/v1beta1/allowed_list.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -//nolint:dupl -package v1beta1 - -import ( - "regexp" - "sort" - "strings" -) - -type AllowedListSpec struct { - Exact []string `json:"allowed,omitempty"` - Regex string `json:"allowedRegex,omitempty"` -} - -func (in *AllowedListSpec) ExactMatch(value string) (ok bool) { - if len(in.Exact) > 0 { - sort.SliceStable(in.Exact, func(i, j int) bool { - return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) - }) - - i := sort.SearchStrings(in.Exact, value) - - ok = i < len(in.Exact) && in.Exact[i] == value - } - - return -} - -func (in AllowedListSpec) RegexMatch(value string) (ok bool) { - if len(in.Regex) > 0 { - ok = regexp.MustCompile(in.Regex).MatchString(value) - } - - return -} diff --git a/api/v1beta1/deny_wildcard.go b/api/v1beta1/deny_wildcard.go index 10528f5e..76460942 100644 --- a/api/v1beta1/deny_wildcard.go +++ b/api/v1beta1/deny_wildcard.go @@ -7,8 +7,8 @@ const ( DenyWildcard = "capsule.clastix.io/deny-wildcard" ) -func (t *Tenant) IsWildcardDenied() bool { - if v, ok := t.Annotations[DenyWildcard]; ok && v == "true" { +func (in *Tenant) IsWildcardDenied() bool { + if v, ok := in.Annotations[DenyWildcard]; ok && v == "true" { return true } diff --git a/api/v1beta1/forbidden_list.go b/api/v1beta1/forbidden_list.go deleted file mode 100644 index 7816dd5b..00000000 --- a/api/v1beta1/forbidden_list.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -//nolint:dupl -package v1beta1 - -import ( - "regexp" - "sort" - "strings" -) - -type ForbiddenListSpec struct { - Exact []string `json:"denied,omitempty"` - Regex string `json:"deniedRegex,omitempty"` -} - -func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) { - if len(in.Exact) > 0 { - sort.SliceStable(in.Exact, func(i, j int) bool { - return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) - }) - - i := sort.SearchStrings(in.Exact, value) - - ok = i < len(in.Exact) && in.Exact[i] == value - } - - return -} - -func (in ForbiddenListSpec) RegexMatch(value string) (ok bool) { - if len(in.Regex) > 0 { - ok = regexp.MustCompile(in.Regex).MatchString(value) - } - - return -} diff --git a/api/v1beta1/forbidden_list_test.go b/api/v1beta1/forbidden_list_test.go deleted file mode 100644 index ef80d762..00000000 --- a/api/v1beta1/forbidden_list_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 -//nolint:dupl -package v1beta1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestForbiddenListSpec_ExactMatch(t *testing.T) { - type tc struct { - In []string - True []string - False []string - } - - for _, tc := range []tc{ - { - []string{"foo", "bar", "bizz", "buzz"}, - []string{"foo", "bar", "bizz", "buzz"}, - []string{"bing", "bong"}, - }, - { - []string{"one", "two", "three"}, - []string{"one", "two", "three"}, - []string{"a", "b", "c"}, - }, - { - nil, - nil, - []string{"any", "value"}, - }, - } { - a := ForbiddenListSpec{ - Exact: tc.In, - } - - for _, ok := range tc.True { - assert.True(t, a.ExactMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.ExactMatch(ko)) - } - } -} - -func TestForbiddenListSpec_RegexMatch(t *testing.T) { - type tc struct { - Regex string - True []string - False []string - } - - for _, tc := range []tc{ - {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, - {``, nil, []string{"any", "value"}}, - } { - a := ForbiddenListSpec{ - Regex: tc.Regex, - } - - for _, ok := range tc.True { - assert.True(t, a.RegexMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.RegexMatch(ko)) - } - } -} diff --git a/api/v1beta1/hostname_collision_scope.go b/api/v1beta1/hostname_collision_scope.go deleted file mode 100644 index 6bed62b9..00000000 --- a/api/v1beta1/hostname_collision_scope.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -const ( - HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster" - HostnameCollisionScopeTenant HostnameCollisionScope = "Tenant" - HostnameCollisionScopeNamespace HostnameCollisionScope = "Namespace" - HostnameCollisionScopeDisabled HostnameCollisionScope = "Disabled" -) - -// +kubebuilder:validation:Enum=Cluster;Tenant;Namespace;Disabled -type HostnameCollisionScope string diff --git a/api/v1beta1/image_pull_policy.go b/api/v1beta1/image_pull_policy.go deleted file mode 100644 index 35076840..00000000 --- a/api/v1beta1/image_pull_policy.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -// +kubebuilder:validation:Enum=Always;Never;IfNotPresent -type ImagePullPolicySpec string - -func (i ImagePullPolicySpec) String() string { - return string(i) -} diff --git a/api/v1beta1/ingress_options.go b/api/v1beta1/ingress_options.go index d748e472..ab4baad7 100644 --- a/api/v1beta1/ingress_options.go +++ b/api/v1beta1/ingress_options.go @@ -3,9 +3,13 @@ package v1beta1 +import ( + "github.com/clastix/capsule/pkg/api" +) + type IngressOptions struct { // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. - AllowedClasses *AllowedListSpec `json:"allowedClasses,omitempty"` + AllowedClasses *api.AllowedListSpec `json:"allowedClasses,omitempty"` // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. // // @@ -18,7 +22,7 @@ type IngressOptions struct { // // Optional. // +kubebuilder:default=Disabled - HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` + HostnameCollisionScope api.HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` // Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. - AllowedHostnames *AllowedListSpec `json:"allowedHostnames,omitempty"` + AllowedHostnames *api.AllowedListSpec `json:"allowedHostnames,omitempty"` } diff --git a/api/v1beta1/limit_ranges.go b/api/v1beta1/limit_ranges.go deleted file mode 100644 index 81d0e431..00000000 --- a/api/v1beta1/limit_ranges.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import corev1 "k8s.io/api/core/v1" - -type LimitRangesSpec struct { - Items []corev1.LimitRangeSpec `json:"items,omitempty"` -} diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index cf35753b..4135c4d8 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -1,57 +1,61 @@ package v1beta1 -import "strings" +import ( + "strings" + + "github.com/clastix/capsule/pkg/api" +) type NamespaceOptions struct { //+kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` } -func (t *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { - if _, ok := t.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok { +func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { + if _, ok := in.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok { return true } - if _, ok := t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok { + if _, ok := in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok { return true } return false } -func (t *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool { - if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok { +func (in *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool { + if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok { return true } - if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { + if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { return true } return false } -func (t *Tenant) ForbiddenUserNamespaceLabels() *ForbiddenListSpec { - if !t.hasForbiddenNamespaceLabelsAnnotations() { +func (in *Tenant) ForbiddenUserNamespaceLabels() *api.ForbiddenListSpec { + if !in.hasForbiddenNamespaceLabelsAnnotations() { return nil } - return &ForbiddenListSpec{ - Exact: strings.Split(t.Annotations[ForbiddenNamespaceLabelsAnnotation], ","), - Regex: t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation], + return &api.ForbiddenListSpec{ + Exact: strings.Split(in.Annotations[ForbiddenNamespaceLabelsAnnotation], ","), + Regex: in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation], } } -func (t *Tenant) ForbiddenUserNamespaceAnnotations() *ForbiddenListSpec { - if !t.hasForbiddenNamespaceAnnotationsAnnotations() { +func (in *Tenant) ForbiddenUserNamespaceAnnotations() *api.ForbiddenListSpec { + if !in.hasForbiddenNamespaceAnnotationsAnnotations() { return nil } - return &ForbiddenListSpec{ - Exact: strings.Split(t.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","), - Regex: t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation], + return &api.ForbiddenListSpec{ + Exact: strings.Split(in.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","), + Regex: in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation], } } diff --git a/api/v1beta1/owner_list.go b/api/v1beta1/owner_list.go index 355ab7e1..dd0c4209 100644 --- a/api/v1beta1/owner_list.go +++ b/api/v1beta1/owner_list.go @@ -6,14 +6,14 @@ import ( type OwnerListSpec []OwnerSpec -func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) { - sort.Sort(ByKindAndName(o)) - i := sort.Search(len(o), func(i int) bool { - return o[i].Kind >= kind && o[i].Name >= name +func (in OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) { + sort.Sort(ByKindAndName(in)) + i := sort.Search(len(in), func(i int) bool { + return in[i].Kind >= kind && in[i].Name >= name }) - if i < len(o) && o[i].Kind == kind && o[i].Name == name { - return o[i] + if i < len(in) && in[i].Kind == kind && in[i].Name == name { + return in[i] } return @@ -21,18 +21,18 @@ func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) type ByKindAndName OwnerListSpec -func (b ByKindAndName) Len() int { - return len(b) +func (in ByKindAndName) Len() int { + return len(in) } -func (b ByKindAndName) Less(i, j int) bool { - if b[i].Kind.String() != b[j].Kind.String() { - return b[i].Kind.String() < b[j].Kind.String() +func (in ByKindAndName) Less(i, j int) bool { + if in[i].Kind.String() != in[j].Kind.String() { + return in[i].Kind.String() < in[j].Kind.String() } - return b[i].Name < b[j].Name + return in[i].Name < in[j].Name } -func (b ByKindAndName) Swap(i, j int) { - b[i], b[j] = b[j], b[i] +func (in ByKindAndName) Swap(i, j int) { + in[i], in[j] = in[j], in[i] } diff --git a/api/v1beta1/owner_role.go b/api/v1beta1/owner_role.go index dc73309d..27123c30 100644 --- a/api/v1beta1/owner_role.go +++ b/api/v1beta1/owner_role.go @@ -19,7 +19,7 @@ const ( // 2. the overall length of the annotation key that is exceeding 63 characters // For emails, the symbol @ can be replaced with the placeholder __AT__. // For the latter one, the index of the owner can be used to force the retrieval. -func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string { +func (in *OwnerSpec) GetRoles(tenant Tenant, index int) []string { for key, value := range tenant.GetAnnotations() { if !strings.HasPrefix(key, fmt.Sprintf("%s/", ClusterRoleNamesAnnotation)) { continue @@ -41,7 +41,7 @@ func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string { return []string{"admin", "capsule-namespace-deleter"} } -func (in OwnerSpec) convertMap() map[string]string { +func (in *OwnerSpec) convertMap() map[string]string { return map[string]string{ "__AT__": "@", } diff --git a/api/v1beta1/service_allowed_ips.go b/api/v1beta1/service_allowed_ips.go deleted file mode 100644 index 5dd65ba0..00000000 --- a/api/v1beta1/service_allowed_ips.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" -type AllowedIP string - -type ExternalServiceIPsSpec struct { - Allowed []AllowedIP `json:"allowed"` -} diff --git a/api/v1beta1/service_options.go b/api/v1beta1/service_options.go index dfdcc265..636a2f7f 100644 --- a/api/v1beta1/service_options.go +++ b/api/v1beta1/service_options.go @@ -3,11 +3,15 @@ package v1beta1 +import ( + "github.com/clastix/capsule/pkg/api" +) + type ServiceOptions struct { // Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` // Block or deny certain type of Services. Optional. - AllowedServices *AllowedServices `json:"allowedServices,omitempty"` + AllowedServices *api.AllowedServices `json:"allowedServices,omitempty"` // Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. - ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"` + ExternalServiceIPs *api.ExternalServiceIPsSpec `json:"externalIPs,omitempty"` } diff --git a/api/v1beta1/tenant_annotations.go b/api/v1beta1/tenant_annotations.go index 9c7f3ad7..8c21e925 100644 --- a/api/v1beta1/tenant_annotations.go +++ b/api/v1beta1/tenant_annotations.go @@ -9,12 +9,6 @@ import ( ) const ( - AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" - AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" - AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" - AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" - AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" - AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" ForbiddenNamespaceLabelsAnnotation = "capsule.clastix.io/forbidden-namespace-labels" ForbiddenNamespaceLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-labels-regexp" ForbiddenNamespaceAnnotationsAnnotation = "capsule.clastix.io/forbidden-namespace-annotations" diff --git a/api/v1beta1/tenant_func.go b/api/v1beta1/tenant_func.go index 2bb43969..8111c028 100644 --- a/api/v1beta1/tenant_func.go +++ b/api/v1beta1/tenant_func.go @@ -9,24 +9,24 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (t *Tenant) IsCordoned() bool { - if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { +func (in *Tenant) IsCordoned() bool { + if v, ok := in.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { return true } return false } -func (t *Tenant) IsFull() bool { +func (in *Tenant) IsFull() bool { // we don't have limits on assigned Namespaces - if t.Spec.NamespaceOptions == nil || t.Spec.NamespaceOptions.Quota == nil { + if in.Spec.NamespaceOptions == nil || in.Spec.NamespaceOptions.Quota == nil { return false } - return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceOptions.Quota) + return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceOptions.Quota) } -func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { +func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { var l []string for _, ns := range namespaces { @@ -37,10 +37,10 @@ func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { sort.Strings(l) - t.Status.Namespaces = l - t.Status.Size = uint(len(l)) + in.Status.Namespaces = l + in.Status.Size = uint(len(l)) } -func (t *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings { - return t.Spec.Owners.FindOwner(name, kind).ProxyOperations +func (in *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings { + return in.Spec.Owners.FindOwner(name, kind).ProxyOperations } diff --git a/api/v1beta1/tenant_labels.go b/api/v1beta1/tenant_labels.go deleted file mode 100644 index 836e6810..00000000 --- a/api/v1beta1/tenant_labels.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import ( - "fmt" - - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -func GetTypeLabel(t runtime.Object) (label string, err error) { - switch v := t.(type) { - case *Tenant: - return "capsule.clastix.io/tenant", nil - case *corev1.LimitRange: - return "capsule.clastix.io/limit-range", nil - case *networkingv1.NetworkPolicy: - return "capsule.clastix.io/network-policy", nil - case *corev1.ResourceQuota: - return "capsule.clastix.io/resource-quota", nil - case *rbacv1.RoleBinding: - return "capsule.clastix.io/role-binding", nil - default: - err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v) - } - - return -} diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 740c6f5a..9e17b53e 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -5,6 +5,8 @@ package v1beta1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // TenantSpec defines the desired state of Tenant. @@ -14,27 +16,27 @@ type TenantSpec struct { // Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. - ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"` + ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"` // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. - StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"` + StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. IngressOptions IngressOptions `json:"ingressOptions,omitempty"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. - ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. NodeSelector map[string]string `json:"nodeSelector,omitempty"` // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"` + NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"` // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"` + LimitRanges api.LimitRangesSpec `json:"limitRanges,omitempty"` // Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - ResourceQuota ResourceQuotaSpec `json:"resourceQuotas,omitempty"` + ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` // Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. - AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` + AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. - ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` + ImagePullPolicies []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. - PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"` + PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"` } //+kubebuilder:object:root=true @@ -56,7 +58,7 @@ type Tenant struct { Status TenantStatus `json:"status,omitempty"` } -func (t *Tenant) Hub() {} +func (in *Tenant) Hub() {} //+kubebuilder:object:root=true diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index f410020d..5ae9834b 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -9,81 +9,10 @@ package v1beta1 import ( - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" + "github.com/clastix/capsule/pkg/api" + runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. -func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { - if in == nil { - return nil - } - out := new(AdditionalMetadataSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { - *out = *in - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]v1.Subject, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. -func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { - if in == nil { - return nil - } - out := new(AdditionalRoleBindingsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. -func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { - if in == nil { - return nil - } - out := new(AllowedListSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { *out = *in @@ -135,57 +64,17 @@ func (in ByKindAndName) DeepCopy() ByKindAndName { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { - *out = *in - if in.Allowed != nil { - in, out := &in.Allowed, &out.Allowed - *out = make([]AllowedIP, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. -func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { - if in == nil { - return nil - } - out := new(ExternalServiceIPsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. -func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { - if in == nil { - return nil - } - out := new(ForbiddenListSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in if in.AllowedClasses != nil { in, out := &in.AllowedClasses, &out.AllowedClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.AllowedHostnames != nil { in, out := &in.AllowedHostnames, &out.AllowedHostnames - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } @@ -200,28 +89,6 @@ func (in *IngressOptions) DeepCopy() *IngressOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.LimitRangeSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. -func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { - if in == nil { - return nil - } - out := new(LimitRangesSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { *out = *in @@ -232,7 +99,7 @@ func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { } if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } } @@ -247,28 +114,6 @@ func (in *NamespaceOptions) DeepCopy() *NamespaceOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]networkingv1.NetworkPolicySpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. -func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { - if in == nil { - return nil - } - out := new(NetworkPolicySpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) { *out = *in @@ -347,44 +192,22 @@ func (in *ProxySettings) DeepCopy() *ProxySettings { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.ResourceQuotaSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. -func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { - if in == nil { - return nil - } - out := new(ResourceQuotaSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { *out = *in if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } if in.AllowedServices != nil { in, out := &in.AllowedServices, &out.AllowedServices - *out = new(AllowedServices) + *out = new(api.AllowedServices) (*in).DeepCopyInto(*out) } if in.ExternalServiceIPs != nil { in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs - *out = new(ExternalServiceIPsSpec) + *out = new(api.ExternalServiceIPsSpec) (*in).DeepCopyInto(*out) } } @@ -475,18 +298,18 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.ServiceOptions != nil { in, out := &in.ServiceOptions, &out.ServiceOptions - *out = new(ServiceOptions) + *out = new(api.ServiceOptions) (*in).DeepCopyInto(*out) } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } in.IngressOptions.DeepCopyInto(&out.IngressOptions) if in.ContainerRegistries != nil { in, out := &in.ContainerRegistries, &out.ContainerRegistries - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.NodeSelector != nil { @@ -501,19 +324,19 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { in.ResourceQuota.DeepCopyInto(&out.ResourceQuota) if in.AdditionalRoleBindings != nil { in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings - *out = make([]AdditionalRoleBindingsSpec, len(*in)) + *out = make([]api.AdditionalRoleBindingsSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.ImagePullPolicies != nil { in, out := &in.ImagePullPolicies, &out.ImagePullPolicies - *out = make([]ImagePullPolicySpec, len(*in)) + *out = make([]api.ImagePullPolicySpec, len(*in)) copy(*out, *in) } if in.PriorityClasses != nil { in, out := &in.PriorityClasses, &out.PriorityClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } diff --git a/api/v1beta2/allowed_list_test.go b/api/v1beta2/allowed_list_test.go deleted file mode 100644 index 77754933..00000000 --- a/api/v1beta2/allowed_list_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 -//nolint:dupl -package v1beta2 - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAllowedListSpec_ExactMatch(t *testing.T) { - type tc struct { - In []string - True []string - False []string - } - - for _, tc := range []tc{ - { - []string{"foo", "bar", "bizz", "buzz"}, - []string{"foo", "bar", "bizz", "buzz"}, - []string{"bing", "bong"}, - }, - { - []string{"one", "two", "three"}, - []string{"one", "two", "three"}, - []string{"a", "b", "c"}, - }, - { - nil, - nil, - []string{"any", "value"}, - }, - } { - a := AllowedListSpec{ - Exact: tc.In, - } - - for _, ok := range tc.True { - assert.True(t, a.ExactMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.ExactMatch(ko)) - } - } -} - -func TestAllowedListSpec_RegexMatch(t *testing.T) { - type tc struct { - Regex string - True []string - False []string - } - - for _, tc := range []tc{ - {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, - {``, nil, []string{"any", "value"}}, - } { - a := AllowedListSpec{ - Regex: tc.Regex, - } - - for _, ok := range tc.True { - assert.True(t, a.RegexMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.RegexMatch(ko)) - } - } -} diff --git a/api/v1beta2/capsuleconfiguration_funcs.go b/api/v1beta2/capsuleconfiguration_convertion_hub.go similarity index 84% rename from api/v1beta2/capsuleconfiguration_funcs.go rename to api/v1beta2/capsuleconfiguration_convertion_hub.go index 7e029cad..6f4c02b5 100644 --- a/api/v1beta2/capsuleconfiguration_funcs.go +++ b/api/v1beta2/capsuleconfiguration_convertion_hub.go @@ -30,10 +30,21 @@ func (in *CapsuleConfiguration) ConvertTo(raw conversion.Hub) error { } if in.Spec.NodeMetadata != nil { - annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",") - annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex - annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",") - annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex + if len(in.Spec.NodeMetadata.ForbiddenLabels.Exact) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",") + } + + if len(in.Spec.NodeMetadata.ForbiddenLabels.Regex) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex + } + + if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",") + } + + if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Regex) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex + } } annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName] = fmt.Sprintf("%t", in.Spec.EnableTLSReconciler) diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go index 207cd00b..d9a56424 100644 --- a/api/v1beta2/capsuleconfiguration_types.go +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -5,6 +5,8 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // CapsuleConfigurationSpec defines the Capsule configuration. @@ -32,9 +34,9 @@ type CapsuleConfigurationSpec struct { type NodeMetadata struct { // Define the labels that a Tenant Owner cannot set for their nodes. - ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels"` // Define the annotations that a Tenant Owner cannot set for their nodes. - ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` + ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"` } type CapsuleResources struct { diff --git a/api/v1beta2/conversion_hub.go b/api/v1beta2/conversion_hub.go deleted file mode 100644 index 0c57e00c..00000000 --- a/api/v1beta2/conversion_hub.go +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -//nolint:nestif,cyclop,maintidx -package v1beta2 - -import ( - "fmt" - "strconv" - "strings" - - "sigs.k8s.io/controller-runtime/pkg/conversion" - - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" -) - -//nolint:gocyclo -func (in *Tenant) ConvertFrom(raw conversion.Hub) error { - src, ok := raw.(*capsulev1beta1.Tenant) - if !ok { - return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) - } - - annotations := src.GetAnnotations() - if annotations == nil { - annotations = map[string]string{} - } - - in.ObjectMeta = src.ObjectMeta - in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners)) - - for index, owner := range src.Spec.Owners { - proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations)) - - for _, proxyOp := range owner.ProxyOperations { - ops := make([]ProxyOperation, 0, len(proxyOp.Operations)) - - for _, op := range proxyOp.Operations { - ops = append(ops, ProxyOperation(op)) - } - - proxySettings = append(proxySettings, ProxySettings{ - Kind: ProxyServiceKind(proxyOp.Kind), - Operations: ops, - }) - } - - in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{ - Kind: OwnerKind(owner.Kind), - Name: owner.Name, - ClusterRoles: owner.GetRoles(*src, index), - ProxyOperations: proxySettings, - }) - } - - if nsOpts := src.Spec.NamespaceOptions; nsOpts != nil { - in.Spec.NamespaceOptions = &NamespaceOptions{} - - in.Spec.NamespaceOptions.Quota = src.Spec.NamespaceOptions.Quota - - if nsOpts.AdditionalMetadata != nil { - in.Spec.NamespaceOptions.AdditionalMetadata = &AdditionalMetadataSpec{} - - in.Spec.NamespaceOptions.AdditionalMetadata.Annotations = nsOpts.AdditionalMetadata.Annotations - in.Spec.NamespaceOptions.AdditionalMetadata.Labels = nsOpts.AdditionalMetadata.Labels - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") - - delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation) - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value - - delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation) - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") - - delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation) - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value - - delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation) - } - } - - if svcOpts := src.Spec.ServiceOptions; svcOpts != nil { - in.Spec.ServiceOptions = &ServiceOptions{} - - if metadata := svcOpts.AdditionalMetadata; metadata != nil { - in.Spec.ServiceOptions.AdditionalMetadata = &AdditionalMetadataSpec{ - Labels: metadata.Labels, - Annotations: metadata.Annotations, - } - } - - if types := svcOpts.AllowedServices; types != nil { - in.Spec.ServiceOptions.AllowedServices = &AllowedServices{ - NodePort: types.NodePort, - ExternalName: types.ExternalName, - LoadBalancer: types.LoadBalancer, - } - } - - if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { - allowed := make([]AllowedIP, 0, len(externalIPs.Allowed)) - - for _, ip := range externalIPs.Allowed { - allowed = append(allowed, AllowedIP(ip)) - } - - in.Spec.ServiceOptions.ExternalServiceIPs = &ExternalServiceIPsSpec{ - Allowed: allowed, - } - } - } - - if sc := src.Spec.StorageClasses; sc != nil { - in.Spec.StorageClasses = &AllowedListSpec{ - Exact: sc.Exact, - Regex: sc.Regex, - } - } - - if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 { - in.Spec.IngressOptions.HostnameCollisionScope = HostnameCollisionScope(scope) - } - - v, found := annotations[capsulev1beta1.DenyWildcard] - if found { - value, err := strconv.ParseBool(v) - if err == nil { - in.Spec.IngressOptions.AllowWildcardHostnames = value - - delete(annotations, capsulev1beta1.DenyWildcard) - } - } - - if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { - in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ - Exact: ingressClass.Exact, - Regex: ingressClass.Regex, - } - } - - if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil { - in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ - Exact: hostnames.Exact, - Regex: hostnames.Regex, - } - } - - if allowed := src.Spec.ContainerRegistries; allowed != nil { - in.Spec.ContainerRegistries = &AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - in.Spec.NodeSelector = src.Spec.NodeSelector - - if items := src.Spec.NetworkPolicies.Items; len(items) > 0 { - in.Spec.NetworkPolicies.Items = items - } - - if items := src.Spec.LimitRanges.Items; len(items) > 0 { - in.Spec.LimitRanges.Items = items - } - - if scope := src.Spec.ResourceQuota.Scope; len(scope) > 0 { - in.Spec.ResourceQuota.Scope = ResourceQuotaScope(scope) - } - - if items := src.Spec.ResourceQuota.Items; len(items) > 0 { - in.Spec.ResourceQuota.Items = items - } - - in.Spec.AdditionalRoleBindings = make([]AdditionalRoleBindingsSpec, 0, len(src.Spec.AdditionalRoleBindings)) - for _, rb := range src.Spec.AdditionalRoleBindings { - in.Spec.AdditionalRoleBindings = append(in.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{ - ClusterRoleName: rb.ClusterRoleName, - Subjects: rb.Subjects, - }) - } - - in.Spec.ImagePullPolicies = make([]ImagePullPolicySpec, 0, len(src.Spec.ImagePullPolicies)) - for _, policy := range src.Spec.ImagePullPolicies { - in.Spec.ImagePullPolicies = append(in.Spec.ImagePullPolicies, ImagePullPolicySpec(policy)) - } - - if allowed := src.Spec.PriorityClasses; allowed != nil { - in.Spec.PriorityClasses = &AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - if v, found := annotations["capsule.clastix.io/cordon"]; found { - value, err := strconv.ParseBool(v) - if err == nil { - delete(annotations, "capsule.clastix.io/cordon") - } - - in.Spec.Cordoned = value - } - - if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found { - in.Spec.PreventDeletion = true - - delete(annotations, capsulev1beta1.ProtectedTenantAnnotation) - } - - in.SetAnnotations(annotations) - - in.Status.Namespaces = src.Status.Namespaces - in.Status.Size = src.Status.Size - in.Status.State = tenantState(src.Status.State) - - return nil -} - -func (in *Tenant) ConvertTo(raw conversion.Hub) error { - dst, ok := raw.(*capsulev1beta1.Tenant) - if !ok { - return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) - } - - annotations := in.GetAnnotations() - if annotations == nil { - annotations = map[string]string{} - } - - dst.ObjectMeta = in.ObjectMeta - dst.Spec.Owners = make(capsulev1beta1.OwnerListSpec, 0, len(in.Spec.Owners)) - - for index, owner := range in.Spec.Owners { - proxySettings := make([]capsulev1beta1.ProxySettings, 0, len(owner.ProxyOperations)) - - for _, proxyOp := range owner.ProxyOperations { - ops := make([]capsulev1beta1.ProxyOperation, 0, len(proxyOp.Operations)) - - for _, op := range proxyOp.Operations { - ops = append(ops, capsulev1beta1.ProxyOperation(op)) - } - - proxySettings = append(proxySettings, capsulev1beta1.ProxySettings{ - Kind: capsulev1beta1.ProxyServiceKind(proxyOp.Kind), - Operations: ops, - }) - } - - dst.Spec.Owners = append(dst.Spec.Owners, capsulev1beta1.OwnerSpec{ - Kind: capsulev1beta1.OwnerKind(owner.Kind), - Name: owner.Name, - ProxyOperations: proxySettings, - }) - - if clusterRoles := owner.ClusterRoles; len(clusterRoles) > 0 { - annotations[fmt.Sprintf("%s/%d", capsulev1beta1.ClusterRoleNamesAnnotation, index)] = strings.Join(owner.ClusterRoles, ",") - } - } - - if nsOpts := in.Spec.NamespaceOptions; nsOpts != nil { - dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} - - if quota := nsOpts.Quota; quota != nil { - dst.Spec.NamespaceOptions.Quota = quota - } - - if metadata := nsOpts.AdditionalMetadata; metadata != nil { - dst.Spec.NamespaceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ - Labels: metadata.Labels, - Annotations: metadata.Annotations, - } - } - - if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") - } - - if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex - } - - if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") - } - - if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex - } - } - - if svcOpts := in.Spec.ServiceOptions; svcOpts != nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} - - if metadata := svcOpts.AdditionalMetadata; metadata != nil { - dst.Spec.ServiceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ - Labels: metadata.Labels, - Annotations: metadata.Annotations, - } - } - - if allowed := svcOpts.AllowedServices; allowed != nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{ - NodePort: allowed.NodePort, - ExternalName: allowed.ExternalName, - LoadBalancer: allowed.ExternalName, - } - } - - if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { - allowed := make([]capsulev1beta1.AllowedIP, 0, len(externalIPs.Allowed)) - - for _, ip := range externalIPs.Allowed { - allowed = append(allowed, capsulev1beta1.AllowedIP(ip)) - } - - dst.Spec.ServiceOptions.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: allowed, - } - } - } - - if storageClass := in.Spec.StorageClasses; storageClass != nil { - dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{ - Exact: storageClass.Exact, - Regex: storageClass.Regex, - } - } - - dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(in.Spec.IngressOptions.HostnameCollisionScope) - - if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil { - dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil { - dst.Spec.IngressOptions.AllowedHostnames = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - annotations[capsulev1beta1.DenyWildcard] = fmt.Sprintf("%t", in.Spec.IngressOptions.AllowWildcardHostnames) - - if allowed := in.Spec.ContainerRegistries; allowed != nil { - dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - dst.Spec.NodeSelector = in.Spec.NodeSelector - - dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{ - Items: in.Spec.NetworkPolicies.Items, - } - - dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{ - Items: in.Spec.LimitRanges.Items, - } - - dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{ - Scope: capsulev1beta1.ResourceQuotaScope(in.Spec.ResourceQuota.Scope), - Items: in.Spec.ResourceQuota.Items, - } - - dst.Spec.AdditionalRoleBindings = make([]capsulev1beta1.AdditionalRoleBindingsSpec, 0, len(in.Spec.AdditionalRoleBindings)) - for _, item := range in.Spec.AdditionalRoleBindings { - dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{ - ClusterRoleName: item.ClusterRoleName, - Subjects: item.Subjects, - }) - } - - dst.Spec.ImagePullPolicies = make([]capsulev1beta1.ImagePullPolicySpec, 0, len(in.Spec.ImagePullPolicies)) - for _, item := range in.Spec.ImagePullPolicies { - dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(item)) - } - - if allowed := in.Spec.PriorityClasses; allowed != nil { - dst.Spec.PriorityClasses = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - dst.SetAnnotations(annotations) - - return nil -} diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go index d55c0cfd..3d4cd718 100644 --- a/api/v1beta2/ingress_options.go +++ b/api/v1beta2/ingress_options.go @@ -3,9 +3,13 @@ package v1beta2 +import ( + "github.com/clastix/capsule/pkg/api" +) + type IngressOptions struct { // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. - AllowedClasses *AllowedListSpec `json:"allowedClasses,omitempty"` + AllowedClasses *api.AllowedListSpec `json:"allowedClasses,omitempty"` // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. // // @@ -18,9 +22,9 @@ type IngressOptions struct { // // Optional. // +kubebuilder:default=Disabled - HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` + HostnameCollisionScope api.HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` // Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. - AllowedHostnames *AllowedListSpec `json:"allowedHostnames,omitempty"` + AllowedHostnames *api.AllowedListSpec `json:"allowedHostnames,omitempty"` // Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. AllowWildcardHostnames bool `json:"allowWildcardHostnames"` } diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go index 10d7e906..a76b79a5 100644 --- a/api/v1beta2/namespace_options.go +++ b/api/v1beta2/namespace_options.go @@ -3,14 +3,18 @@ package v1beta2 +import ( + "github.com/clastix/capsule/pkg/api" +) + type NamespaceOptions struct { //+kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` // Define the labels that a Tenant Owner cannot set for their Namespace resources. - ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels"` // Define the annotations that a Tenant Owner cannot set for their Namespace resources. - ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` + ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"` } diff --git a/api/v1beta2/network_policy.go b/api/v1beta2/network_policy.go deleted file mode 100644 index 67668129..00000000 --- a/api/v1beta2/network_policy.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -import ( - networkingv1 "k8s.io/api/networking/v1" -) - -type NetworkPolicySpec struct { - Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"` -} diff --git a/api/v1beta2/resource_quota.go b/api/v1beta2/resource_quota.go deleted file mode 100644 index 80534c91..00000000 --- a/api/v1beta2/resource_quota.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -import corev1 "k8s.io/api/core/v1" - -// +kubebuilder:validation:Enum=Tenant;Namespace -type ResourceQuotaScope string - -const ( - ResourceQuotaScopeTenant ResourceQuotaScope = "Tenant" - ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace" -) - -type ResourceQuotaSpec struct { - // +kubebuilder:default=Tenant - // Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant - Scope ResourceQuotaScope `json:"scope,omitempty"` - Items []corev1.ResourceQuotaSpec `json:"items,omitempty"` -} diff --git a/api/v1beta2/service_allowed_ips.go b/api/v1beta2/service_allowed_ips.go deleted file mode 100644 index 9eb6247e..00000000 --- a/api/v1beta2/service_allowed_ips.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" -type AllowedIP string - -type ExternalServiceIPsSpec struct { - Allowed []AllowedIP `json:"allowed"` -} diff --git a/api/v1beta2/tenant_annotations.go b/api/v1beta2/tenant_annotations.go deleted file mode 100644 index 79fde6b5..00000000 --- a/api/v1beta2/tenant_annotations.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -import ( - "fmt" - "strings" -) - -const ( - AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" - AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" - AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" - AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" - AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" - AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" -) - -func UsedQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_") -} - -func HardQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_") -} diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go new file mode 100644 index 00000000..de3dfb3a --- /dev/null +++ b/api/v1beta2/tenant_conversion_hub.go @@ -0,0 +1,246 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" +) + +func (in *Tenant) ConvertFrom(raw conversion.Hub) error { + src, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := src.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + in.ObjectMeta = src.ObjectMeta + in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners)) + + for index, owner := range src.Spec.Owners { + proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, ProxyOperation(op)) + } + + proxySettings = append(proxySettings, ProxySettings{ + Kind: ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{ + Kind: OwnerKind(owner.Kind), + Name: owner.Name, + ClusterRoles: owner.GetRoles(*src, index), + ProxyOperations: proxySettings, + }) + } + + if nsOpts := src.Spec.NamespaceOptions; nsOpts != nil { + in.Spec.NamespaceOptions = &NamespaceOptions{} + + in.Spec.NamespaceOptions.Quota = src.Spec.NamespaceOptions.Quota + + in.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation) + } + } + + in.Spec.ServiceOptions = src.Spec.ServiceOptions + in.Spec.StorageClasses = src.Spec.StorageClasses + + if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 { + in.Spec.IngressOptions.HostnameCollisionScope = scope + } + + v, found := annotations[capsulev1beta1.DenyWildcard] + if found { + value, err := strconv.ParseBool(v) + if err == nil { + in.Spec.IngressOptions.AllowWildcardHostnames = !value + + delete(annotations, capsulev1beta1.DenyWildcard) + } + } + + if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { + in.Spec.IngressOptions.AllowedClasses = ingressClass + } + + if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil { + in.Spec.IngressOptions.AllowedHostnames = hostnames + } + + in.Spec.ContainerRegistries = src.Spec.ContainerRegistries + in.Spec.NodeSelector = src.Spec.NodeSelector + in.Spec.NetworkPolicies = src.Spec.NetworkPolicies + in.Spec.LimitRanges = src.Spec.LimitRanges + in.Spec.ResourceQuota = src.Spec.ResourceQuota + in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings + in.Spec.ImagePullPolicies = src.Spec.ImagePullPolicies + in.Spec.PriorityClasses = src.Spec.PriorityClasses + + if v, found := annotations["capsule.clastix.io/cordon"]; found { + value, err := strconv.ParseBool(v) + if err == nil { + delete(annotations, "capsule.clastix.io/cordon") + } + + in.Spec.Cordoned = value + } + + if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found { + in.Spec.PreventDeletion = true + + delete(annotations, capsulev1beta1.ProtectedTenantAnnotation) + } + + in.SetAnnotations(annotations) + + in.Status.Namespaces = src.Status.Namespaces + in.Status.Size = src.Status.Size + in.Status.State = tenantState(src.Status.State) + + return nil +} + +func (in *Tenant) ConvertTo(raw conversion.Hub) error { + dst, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := in.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + dst.ObjectMeta = in.ObjectMeta + dst.Spec.Owners = make(capsulev1beta1.OwnerListSpec, 0, len(in.Spec.Owners)) + + for index, owner := range in.Spec.Owners { + proxySettings := make([]capsulev1beta1.ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]capsulev1beta1.ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, capsulev1beta1.ProxyOperation(op)) + } + + proxySettings = append(proxySettings, capsulev1beta1.ProxySettings{ + Kind: capsulev1beta1.ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + dst.Spec.Owners = append(dst.Spec.Owners, capsulev1beta1.OwnerSpec{ + Kind: capsulev1beta1.OwnerKind(owner.Kind), + Name: owner.Name, + ProxyOperations: proxySettings, + }) + + if clusterRoles := owner.ClusterRoles; len(clusterRoles) > 0 { + annotations[fmt.Sprintf("%s/%d", capsulev1beta1.ClusterRoleNamesAnnotation, index)] = strings.Join(owner.ClusterRoles, ",") + } + } + + if nsOpts := in.Spec.NamespaceOptions; nsOpts != nil { + dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} + + dst.Spec.NamespaceOptions.Quota = nsOpts.Quota + dst.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata + + if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex + } + + if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex + } + } + + dst.Spec.ServiceOptions = in.Spec.ServiceOptions + dst.Spec.StorageClasses = in.Spec.StorageClasses + + dst.Spec.IngressOptions.HostnameCollisionScope = in.Spec.IngressOptions.HostnameCollisionScope + + if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil { + dst.Spec.IngressOptions.AllowedClasses = allowed + } + + if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil { + dst.Spec.IngressOptions.AllowedHostnames = allowed + } + + annotations[capsulev1beta1.DenyWildcard] = fmt.Sprintf("%t", !in.Spec.IngressOptions.AllowWildcardHostnames) + + if allowed := in.Spec.ContainerRegistries; allowed != nil { + dst.Spec.ContainerRegistries = allowed + } + + dst.Spec.NodeSelector = in.Spec.NodeSelector + dst.Spec.NetworkPolicies = in.Spec.NetworkPolicies + dst.Spec.LimitRanges = in.Spec.LimitRanges + dst.Spec.ResourceQuota = in.Spec.ResourceQuota + dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings + dst.Spec.ImagePullPolicies = in.Spec.ImagePullPolicies + dst.Spec.PriorityClasses = in.Spec.PriorityClasses + + if in.Spec.PreventDeletion { + annotations[capsulev1beta1.ProtectedTenantAnnotation] = "true" //nolint:goconst + } + + if in.Spec.Cordoned { + annotations["capsule.clastix.io/cordon"] = "true" + } + + dst.SetAnnotations(annotations) + + return nil +} diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index adc0c66b..d366eaaf 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -5,6 +5,8 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // TenantSpec defines the desired state of Tenant. @@ -14,27 +16,27 @@ type TenantSpec struct { // Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. - ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"` + ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"` // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. - StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"` + StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. IngressOptions IngressOptions `json:"ingressOptions,omitempty"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. - ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. NodeSelector map[string]string `json:"nodeSelector,omitempty"` // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"` + NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"` // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"` + LimitRanges api.LimitRangesSpec `json:"limitRanges,omitempty"` // Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - ResourceQuota ResourceQuotaSpec `json:"resourceQuotas,omitempty"` + ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` // Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. - AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` + AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. - ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` + ImagePullPolicies []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. - PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"` + PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"` // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. Cordoned bool `json:"cordoned,omitempty"` // Prevent accidental deletion of the Tenant. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 71908929..4012bc96 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -9,8 +9,7 @@ package v1beta2 import ( - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" + "github.com/clastix/capsule/pkg/api" "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -64,56 +63,6 @@ func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. -func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { - if in == nil { - return nil - } - out := new(AllowedListSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { - *out = *in - if in.NodePort != nil { - in, out := &in.NodePort, &out.NodePort - *out = new(bool) - **out = **in - } - if in.ExternalName != nil { - in, out := &in.ExternalName, &out.ExternalName - *out = new(bool) - **out = **in - } - if in.LoadBalancer != nil { - in, out := &in.LoadBalancer, &out.LoadBalancer - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices. -func (in *AllowedServices) DeepCopy() *AllowedServices { - if in == nil { - return nil - } - out := new(AllowedServices) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) { { @@ -234,57 +183,17 @@ func (in *CapsuleResources) DeepCopy() *CapsuleResources { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { - *out = *in - if in.Allowed != nil { - in, out := &in.Allowed, &out.Allowed - *out = make([]AllowedIP, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. -func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { - if in == nil { - return nil - } - out := new(ExternalServiceIPsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. -func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { - if in == nil { - return nil - } - out := new(ForbiddenListSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in if in.AllowedClasses != nil { in, out := &in.AllowedClasses, &out.AllowedClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.AllowedHostnames != nil { in, out := &in.AllowedHostnames, &out.AllowedHostnames - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } @@ -299,28 +208,6 @@ func (in *IngressOptions) DeepCopy() *IngressOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.LimitRangeSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. -func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { - if in == nil { - return nil - } - out := new(LimitRangesSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { *out = *in @@ -331,7 +218,7 @@ func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { } if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels) @@ -348,28 +235,6 @@ func (in *NamespaceOptions) DeepCopy() *NamespaceOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]networkingv1.NetworkPolicySpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. -func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { - if in == nil { - return nil - } - out := new(NetworkPolicySpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeMetadata) DeepCopyInto(out *NodeMetadata) { *out = *in @@ -470,58 +335,6 @@ func (in *ProxySettings) DeepCopy() *ProxySettings { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.ResourceQuotaSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. -func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { - if in == nil { - return nil - } - out := new(ResourceQuotaSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { - *out = *in - if in.AdditionalMetadata != nil { - in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) - (*in).DeepCopyInto(*out) - } - if in.AllowedServices != nil { - in, out := &in.AllowedServices, &out.AllowedServices - *out = new(AllowedServices) - (*in).DeepCopyInto(*out) - } - if in.ExternalServiceIPs != nil { - in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs - *out = new(ExternalServiceIPsSpec) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions. -func (in *ServiceOptions) DeepCopy() *ServiceOptions { - if in == nil { - return nil - } - out := new(ServiceOptions) - in.DeepCopyInto(out) - return out -} - // 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 @@ -598,18 +411,18 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.ServiceOptions != nil { in, out := &in.ServiceOptions, &out.ServiceOptions - *out = new(ServiceOptions) + *out = new(api.ServiceOptions) (*in).DeepCopyInto(*out) } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } in.IngressOptions.DeepCopyInto(&out.IngressOptions) if in.ContainerRegistries != nil { in, out := &in.ContainerRegistries, &out.ContainerRegistries - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.NodeSelector != nil { @@ -624,19 +437,19 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { in.ResourceQuota.DeepCopyInto(&out.ResourceQuota) if in.AdditionalRoleBindings != nil { in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings - *out = make([]AdditionalRoleBindingsSpec, len(*in)) + *out = make([]api.AdditionalRoleBindingsSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.ImagePullPolicies != nil { in, out := &in.ImagePullPolicies, &out.ImagePullPolicies - *out = make([]ImagePullPolicySpec, len(*in)) + *out = make([]api.ImagePullPolicySpec, len(*in)) copy(*out, *in) } if in.PriorityClasses != nil { in, out := &in.PriorityClasses, &out.PriorityClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index f171bd4b..5b36f41e 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -211,11 +211,11 @@ spec: type: integer namespacesMetadata: properties: - additionalAnnotations: + annotations: additionalProperties: type: string type: object - additionalLabels: + labels: additionalProperties: type: string type: object @@ -554,11 +554,11 @@ spec: type: array servicesMetadata: properties: - additionalAnnotations: + annotations: additionalProperties: type: string type: object - additionalLabels: + labels: additionalProperties: type: string type: object diff --git a/controllers/servicelabels/abstract.go b/controllers/servicelabels/abstract.go index 90b22884..54dd2dfb 100644 --- a/controllers/servicelabels/abstract.go +++ b/controllers/servicelabels/abstract.go @@ -21,6 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/utils" ) type abstractServiceLabelsReconciler struct { @@ -77,7 +78,7 @@ func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespa return nil, err } - capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) if _, ok := ns.GetLabels()[capsuleLabel]; !ok { return nil, NewNonTenantObject(namespacedName.Name) } diff --git a/api/v1alpha1/tenant_annotations.go b/controllers/tenant/annotations.go similarity index 69% rename from api/v1alpha1/tenant_annotations.go rename to controllers/tenant/annotations.go index bb3d4877..94711e92 100644 --- a/api/v1alpha1/tenant_annotations.go +++ b/controllers/tenant/annotations.go @@ -1,11 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 - -import ( - "fmt" -) +package tenant const ( AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" @@ -15,11 +11,3 @@ const ( AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" ) - -func UsedQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/used-" + resource.String() -} - -func HardQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/hard-" + resource.String() -} diff --git a/controllers/tenant/limitranges.go b/controllers/tenant/limitranges.go index 8561c11f..4cd539d0 100644 --- a/controllers/tenant/limitranges.go +++ b/controllers/tenant/limitranges.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/utils" ) // nolint:dupl @@ -40,11 +41,11 @@ func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta1.Ten // getting LimitRange labels for the mutateFn var tenantLabel, limitRangeLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if limitRangeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.LimitRange{}); err != nil { + if limitRangeLabel, err = utils.GetTypeLabel(&corev1.LimitRange{}); err != nil { return err } diff --git a/controllers/tenant/namespaces.go b/controllers/tenant/namespaces.go index 00b2f64a..95537219 100644 --- a/controllers/tenant/namespaces.go +++ b/controllers/tenant/namespaces.go @@ -17,6 +17,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/utils" ) // Ensuring all annotations are applied to each Namespace handled by the Tenant. @@ -50,7 +51,7 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t return } - capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error { annotations := make(map[string]string) @@ -81,28 +82,28 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t if tnt.Spec.IngressOptions.AllowedClasses != nil { if len(tnt.Spec.IngressOptions.AllowedClasses.Exact) > 0 { - annotations[capsulev1beta1.AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",") + annotations[AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",") } if len(tnt.Spec.IngressOptions.AllowedClasses.Regex) > 0 { - annotations[capsulev1beta1.AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex + annotations[AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex } } if tnt.Spec.StorageClasses != nil { if len(tnt.Spec.StorageClasses.Exact) > 0 { - annotations[capsulev1beta1.AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",") + annotations[AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",") } if len(tnt.Spec.StorageClasses.Regex) > 0 { - annotations[capsulev1beta1.AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex + annotations[AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex } } if tnt.Spec.ContainerRegistries != nil { if len(tnt.Spec.ContainerRegistries.Exact) > 0 { - annotations[capsulev1beta1.AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",") + annotations[AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",") } if len(tnt.Spec.ContainerRegistries.Regex) > 0 { - annotations[capsulev1beta1.AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex + annotations[AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex } } diff --git a/controllers/tenant/networkpolicies.go b/controllers/tenant/networkpolicies.go index 34d35226..69f348bd 100644 --- a/controllers/tenant/networkpolicies.go +++ b/controllers/tenant/networkpolicies.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/utils" ) // nolint:dupl @@ -43,11 +44,11 @@ func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta1. // getting NetworkPolicy labels for the mutateFn var tenantLabel, networkPolicyLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if networkPolicyLabel, err = capsulev1beta1.GetTypeLabel(&networkingv1.NetworkPolicy{}); err != nil { + if networkPolicyLabel, err = utils.GetTypeLabel(&networkingv1.NetworkPolicy{}); err != nil { return err } diff --git a/controllers/tenant/resourcequotas.go b/controllers/tenant/resourcequotas.go index 88793529..ea0bd2e0 100644 --- a/controllers/tenant/resourcequotas.go +++ b/controllers/tenant/resourcequotas.go @@ -17,6 +17,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) // When the Resource Budget assigned to a Tenant is Tenant-scoped we have to rely on the ResourceQuota resources to @@ -36,15 +38,15 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta1 // getting ResourceQuota labels for the mutateFn var tenantLabel, typeLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if typeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { + if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { return err } // nolint:nestif - if tenant.Spec.ResourceQuota.Scope == capsulev1beta1.ResourceQuotaScopeTenant { + if tenant.Spec.ResourceQuota.Scope == api.ResourceQuotaScopeTenant { group := new(errgroup.Group) for i, q := range tenant.Spec.ResourceQuota.Items { @@ -157,11 +159,11 @@ func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta1. // getting ResourceQuota labels for the mutateFn var tenantLabel, typeLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if typeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { + if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { return err } // Pruning resource of non-requested resources @@ -188,7 +190,7 @@ func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta1. target.Spec.Scopes = resQuota.Scopes target.Spec.ScopeSelector = resQuota.ScopeSelector // In case of Namespace scope for the ResourceQuota we can easily apply the bare specification - if tenant.Spec.ResourceQuota.Scope == capsulev1beta1.ResourceQuotaScopeNamespace { + if tenant.Spec.ResourceQuota.Scope == api.ResourceQuotaScopeNamespace { target.Spec.Hard = resQuota.Hard } diff --git a/controllers/tenant/rolebindings.go b/controllers/tenant/rolebindings.go index 96682047..8b8f7cad 100644 --- a/controllers/tenant/rolebindings.go +++ b/controllers/tenant/rolebindings.go @@ -12,11 +12,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) // ownerClusterRoleBindings generates a Capsule AdditionalRoleBinding object for the Owner dynamic clusterrole in order // to take advantage of the additional role binding feature. -func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clusterRole string) capsulev1beta1.AdditionalRoleBindingsSpec { +func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec { var subject rbacv1.Subject if owner.Kind == "ServiceAccount" { @@ -35,7 +37,7 @@ func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clust } } - return capsulev1beta1.AdditionalRoleBindingsSpec{ + return api.AdditionalRoleBindingsSpec{ ClusterRoleName: clusterRole, Subjects: []rbacv1.Subject{ subject, @@ -47,7 +49,7 @@ func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clust // applying Pod Security Policies or giving access to CRDs or specific API groups. func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) { // hashing the RoleBinding name due to DNS RFC-1123 applied to Kubernetes labels - hashFn := func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string { + hashFn := func(binding api.AdditionalRoleBindingsSpec) string { h := fnv.New64a() _, _ = h.Write([]byte(binding.ClusterRoleName)) @@ -86,14 +88,14 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.T return group.Wait() } -func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta1.Tenant, ns string, keys []string, hashFn func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string) (err error) { +func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta1.Tenant, ns string, keys []string, hashFn func(binding api.AdditionalRoleBindingsSpec) string) (err error) { var tenantLabel, roleBindingLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return } - if roleBindingLabel, err = capsulev1beta1.GetTypeLabel(&rbacv1.RoleBinding{}); err != nil { + if roleBindingLabel, err = utils.GetTypeLabel(&rbacv1.RoleBinding{}); err != nil { return } @@ -101,7 +103,7 @@ func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsule return } - var roleBindings []capsulev1beta1.AdditionalRoleBindingsSpec + var roleBindings []api.AdditionalRoleBindingsSpec for index, owner := range tenant.Spec.Owners { for _, clusterRoleName := range owner.GetRoles(*tenant, index) { diff --git a/controllers/tenant/utils.go b/controllers/tenant/utils.go index 00b47518..f66d3038 100644 --- a/controllers/tenant/utils.go +++ b/controllers/tenant/utils.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/utils" ) // pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or diff --git a/e2e/additional_role_bindings_test.go b/e2e/additional_role_bindings_test.go index 29a7136d..ffda52fb 100644 --- a/e2e/additional_role_bindings_test.go +++ b/e2e/additional_role_bindings_test.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a Namespace with an additional Role Binding", func() { @@ -29,7 +30,7 @@ var _ = Describe("creating a Namespace with an additional Role Binding", func() Kind: "User", }, }, - AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{ + AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ { ClusterRoleName: "crds-rolebinding", Subjects: []rbacv1.Subject{ diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go index f63c1d64..038c1b17 100644 --- a/e2e/allowed_external_ips_test.go +++ b/e2e/allowed_external_ips_test.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing an allowed set of Service external IPs", func() { @@ -29,9 +30,9 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: []capsulev1beta1.AllowedIP{ + ServiceOptions: &api.ServiceOptions{ + ExternalServiceIPs: &api.ExternalServiceIPsSpec{ + Allowed: []api.AllowedIP{ "10.20.0.0/16", "192.168.1.2/32", }, diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index 7162daa7..ef854d06 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Container Registry", func() { @@ -29,7 +30,7 @@ var _ = Describe("enforcing a Container Registry", func() { Kind: "User", }, }, - ContainerRegistries: &capsulev1beta1.AllowedListSpec{ + ContainerRegistries: &api.AllowedListSpec{ Exact: []string{"docker.io", "myregistry.azurecr.io"}, Regex: `quay\.\w+`, }, diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index 39f54b44..855455d4 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating an ExternalName service when it is disabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ ExternalName: pointer.BoolPtr(false), }, }, diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index 76a050da..c532a180 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ LoadBalancer: pointer.BoolPtr(false), }, }, diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index d2cd2be7..c384f453 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a nodePort service when it is disabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ NodePort: pointer.BoolPtr(false), }, }, diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index a2262703..32a45e85 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ LoadBalancer: pointer.BoolPtr(true), }, }, diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go index af4528a4..f973e69c 100644 --- a/e2e/imagepullpolicy_multiple_test.go +++ b/e2e/imagepullpolicy_multiple_test.go @@ -14,6 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing some defined ImagePullPolicy", func() { @@ -28,7 +29,7 @@ var _ = Describe("enforcing some defined ImagePullPolicy", func() { Kind: "User", }, }, - ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"}, + ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"}, }, } diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go index a93d9a05..41918ad0 100644 --- a/e2e/imagepullpolicy_single_test.go +++ b/e2e/imagepullpolicy_single_test.go @@ -14,6 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a defined ImagePullPolicy", func() { @@ -28,7 +29,7 @@ var _ = Describe("enforcing a defined ImagePullPolicy", func() { Kind: "User", }, }, - ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always"}, + ImagePullPolicies: []api.ImagePullPolicySpec{"Always"}, }, } diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index b3775b00..f5f278b3 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", func() { @@ -34,7 +35,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }, }, IngressOptions: capsulev1beta1.IngressOptions{ - AllowedClasses: &capsulev1beta1.AllowedListSpec{ + AllowedClasses: &api.AllowedListSpec{ Exact: []string{ "nginx", "haproxy", diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index abc04a80..22459441 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -18,6 +18,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1", func() { @@ -33,7 +34,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" }, }, IngressOptions: capsulev1beta1.IngressOptions{ - AllowedClasses: &capsulev1beta1.AllowedListSpec{ + AllowedClasses: &api.AllowedListSpec{ Exact: []string{ "nginx", "haproxy", diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index 64204bcb..34ad3bb9 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Cluster scoped Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster, + HostnameCollisionScope: api.HostnameCollisionScopeCluster, }, }, } @@ -50,7 +51,7 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster, + HostnameCollisionScope: api.HostnameCollisionScopeCluster, }, }, } diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index b20f681e..f63cdc5a 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when disabling Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when disabling Ingress hostnames collision", func() { }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled, + HostnameCollisionScope: api.HostnameCollisionScopeDisabled, }, }, } diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index 6e7fe6a7..ecda0d38 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Namespace scoped Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeNamespace, + HostnameCollisionScope: api.HostnameCollisionScopeNamespace, }, }, } diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 4873f899..55385be1 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeTenant, + HostnameCollisionScope: api.HostnameCollisionScopeTenant, }, }, } diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index 2d27478d..d0f55640 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress hostnames", func() { @@ -34,7 +35,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }, }, IngressOptions: capsulev1beta1.IngressOptions{ - AllowedHostnames: &capsulev1beta1.AllowedListSpec{ + AllowedHostnames: &api.AllowedListSpec{ Exact: []string{"sigs.k8s.io", "operator.sdk", "domain.tld"}, Regex: `.*\.clastix\.io`, }, diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go index 413178da..3bded7d2 100644 --- a/e2e/namespace_additional_metadata_test.go +++ b/e2e/namespace_additional_metadata_test.go @@ -14,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a Namespace for a Tenant with additional metadata", func() { @@ -29,7 +30,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f }, }, NamespaceOptions: &capsulev1beta1.NamespaceOptions{ - AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{ + AdditionalMetadata: &api.AdditionalMetadataSpec{ Labels: map[string]string{ "k8s.io/custom-label": "foo", "clastix.io/custom-label": "bar", diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index 71ab1d81..1e9a4335 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant owner interacts with the webhooks", func() { @@ -32,13 +33,13 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Kind: "User", }, }, - StorageClasses: &capsulev1beta1.AllowedListSpec{ + StorageClasses: &api.AllowedListSpec{ Exact: []string{ "cephfs", "glusterfs", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -56,7 +57,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, }, }, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ { Egress: []networkingv1.NetworkPolicyEgressRule{ { @@ -77,7 +78,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, }, }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourcePods: resource.MustParse("10"), diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index a8d1ac32..fc7dcdf4 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Priority Class", func() { @@ -29,7 +30,7 @@ var _ = Describe("enforcing a Priority Class", func() { Kind: "User", }, }, - PriorityClasses: &capsulev1beta1.AllowedListSpec{ + PriorityClasses: &api.AllowedListSpec{ Exact: []string{"gold"}, Regex: "pc\\-\\w+", }, diff --git a/e2e/resource_quota_exceeded_test.go b/e2e/resource_quota_exceeded_test.go index fc8aa4df..d8edddbd 100644 --- a/e2e/resource_quota_exceeded_test.go +++ b/e2e/resource_quota_exceeded_test.go @@ -8,6 +8,7 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -33,7 +34,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() { Kind: "User", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -79,7 +80,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() { }, }, }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceLimitsCPU: resource.MustParse("8"), diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go index 2aa62593..f1a47828 100644 --- a/e2e/selecting_non_owned_tenant_test.go +++ b/e2e/selecting_non_owned_tenant_test.go @@ -7,6 +7,7 @@ package e2e import ( "context" + "github.com/clastix/capsule/pkg/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -44,7 +45,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func() var ns *corev1.Namespace By("assigning to the Namespace the Capsule Tenant label", func() { - l, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) ns := NewNamespace("tenant-non-owned-ns") diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go index ed1459b2..abc359e0 100644 --- a/e2e/selecting_tenant_with_label_test.go +++ b/e2e/selecting_tenant_with_label_test.go @@ -7,6 +7,7 @@ package e2e import ( "context" + "github.com/clastix/capsule/pkg/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -59,7 +60,7 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi It("should be assigned to the selected Tenant", func() { ns := NewNamespace("tenant-2-ns") By("assigning to the Namespace the Capsule Tenant label", func() { - l, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) ns.Labels = map[string]string{ l: t2.Name, diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 22f02df8..bb467e98 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -10,7 +10,6 @@ import ( "errors" "fmt" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -21,6 +20,9 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("adding metadata to Service objects", func() { @@ -36,7 +38,7 @@ var _ = Describe("adding metadata to Service objects", func() { }, }, ServiceOptions: &capsulev1beta1.ServiceOptions{ - AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{ + AdditionalMetadata: &api.AdditionalMetadataSpec{ Labels: map[string]string{ "k8s.io/custom-label": "foo", "clastix.io/custom-label": "bar", diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index 5f600677..8a25a0cc 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Storage classes", func() { @@ -30,7 +31,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { Kind: "User", }, }, - StorageClasses: &capsulev1beta1.AllowedListSpec{ + StorageClasses: &api.AllowedListSpec{ Exact: []string{ "cephfs", "glusterfs", diff --git a/e2e/tenant_resources_changes_test.go b/e2e/tenant_resources_changes_test.go index eacbaf4c..5cd2d0dd 100644 --- a/e2e/tenant_resources_changes_test.go +++ b/e2e/tenant_resources_changes_test.go @@ -8,6 +8,7 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -33,7 +34,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() { Kind: "User", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -79,7 +80,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() { }, }, }, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ { Ingress: []networkingv1.NetworkPolicyIngressRule{ { @@ -127,7 +128,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() { NodeSelector: map[string]string{ "kubernetes.io/os": "linux", }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceLimitsCPU: resource.MustParse("8"), diff --git a/e2e/tenant_resources_test.go b/e2e/tenant_resources_test.go index ca740e55..80742b8d 100644 --- a/e2e/tenant_resources_test.go +++ b/e2e/tenant_resources_test.go @@ -8,6 +8,7 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" "strings" . "github.com/onsi/ginkgo" @@ -33,7 +34,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() { Kind: "User", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -79,7 +80,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() { }, }, }, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ { Ingress: []networkingv1.NetworkPolicyIngressRule{ { @@ -127,7 +128,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() { NodeSelector: map[string]string{ "kubernetes.io/os": "linux", }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceLimitsCPU: resource.MustParse("8"), diff --git a/api/v1beta1/additional_metadata.go b/pkg/api/additional_metadata.go similarity index 82% rename from api/v1beta1/additional_metadata.go rename to pkg/api/additional_metadata.go index 3cf7f5e1..7e8ef104 100644 --- a/api/v1beta1/additional_metadata.go +++ b/pkg/api/additional_metadata.go @@ -1,7 +1,9 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta1 +package api + +// +kubebuilder:object:generate=true type AdditionalMetadataSpec struct { Labels map[string]string `json:"labels,omitempty"` diff --git a/api/v1alpha1/additional_role_bindings.go b/pkg/api/additional_role_bindings.go similarity index 85% rename from api/v1alpha1/additional_role_bindings.go rename to pkg/api/additional_role_bindings.go index ecbe686b..e7c7e7f3 100644 --- a/api/v1alpha1/additional_role_bindings.go +++ b/pkg/api/additional_role_bindings.go @@ -1,10 +1,12 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 +package api import rbacv1 "k8s.io/api/rbac/v1" +// +kubebuilder:object:generate=true + type AdditionalRoleBindingsSpec struct { ClusterRoleName string `json:"clusterRoleName"` // kubebuilder:validation:Minimum=1 diff --git a/api/v1beta2/allowed_list.go b/pkg/api/allowed_list.go similarity index 93% rename from api/v1beta2/allowed_list.go rename to pkg/api/allowed_list.go index 33e02c46..3bff873d 100644 --- a/api/v1beta2/allowed_list.go +++ b/pkg/api/allowed_list.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api import ( "regexp" @@ -9,6 +9,8 @@ import ( "strings" ) +// +kubebuilder:object:generate=true + type AllowedListSpec struct { Exact []string `json:"allowed,omitempty"` Regex string `json:"allowedRegex,omitempty"` diff --git a/api/v1beta1/allowed_list_test.go b/pkg/api/allowed_list_test.go similarity index 98% rename from api/v1beta1/allowed_list_test.go rename to pkg/api/allowed_list_test.go index 20364bf6..67fc247b 100644 --- a/api/v1beta1/allowed_list_test.go +++ b/pkg/api/allowed_list_test.go @@ -1,7 +1,8 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 + //nolint:dupl -package v1beta1 +package api import ( "testing" diff --git a/api/v1alpha1/external_service_ips.go b/pkg/api/external_service_ips.go similarity index 84% rename from api/v1alpha1/external_service_ips.go rename to pkg/api/external_service_ips.go index 2c1112d9..4bd1c9b6 100644 --- a/api/v1alpha1/external_service_ips.go +++ b/pkg/api/external_service_ips.go @@ -1,11 +1,13 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 +package api // +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" type AllowedIP string +// +kubebuilder:object:generate=true + type ExternalServiceIPsSpec struct { Allowed []AllowedIP `json:"allowed"` } diff --git a/api/v1beta2/forbidden_list.go b/pkg/api/forbidden_list.go similarity index 85% rename from api/v1beta2/forbidden_list.go rename to pkg/api/forbidden_list.go index 65c8e74e..acabb1ed 100644 --- a/api/v1beta2/forbidden_list.go +++ b/pkg/api/forbidden_list.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api import ( "regexp" @@ -9,12 +9,14 @@ import ( "strings" ) +// +kubebuilder:object:generate=true + type ForbiddenListSpec struct { Exact []string `json:"denied,omitempty"` Regex string `json:"deniedRegex,omitempty"` } -func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) { +func (in ForbiddenListSpec) ExactMatch(value string) (ok bool) { if len(in.Exact) > 0 { sort.SliceStable(in.Exact, func(i, j int) bool { return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) diff --git a/api/v1beta2/forbidden_list_test.go b/pkg/api/forbidden_list_test.go similarity index 98% rename from api/v1beta2/forbidden_list_test.go rename to pkg/api/forbidden_list_test.go index 30bc9ac0..48c0e676 100644 --- a/api/v1beta2/forbidden_list_test.go +++ b/pkg/api/forbidden_list_test.go @@ -1,7 +1,8 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 +// //nolint:dupl -package v1beta2 +package api import ( "testing" diff --git a/api/v1beta2/hostname_collision_scope.go b/pkg/api/hostname_collision_scope.go similarity index 96% rename from api/v1beta2/hostname_collision_scope.go rename to pkg/api/hostname_collision_scope.go index a46ab934..31a09bde 100644 --- a/api/v1beta2/hostname_collision_scope.go +++ b/pkg/api/hostname_collision_scope.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api const ( HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster" diff --git a/api/v1beta2/image_pull_policy.go b/pkg/api/image_pull_policy.go similarity index 93% rename from api/v1beta2/image_pull_policy.go rename to pkg/api/image_pull_policy.go index cb9e8e2f..3535f259 100644 --- a/api/v1beta2/image_pull_policy.go +++ b/pkg/api/image_pull_policy.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api // +kubebuilder:validation:Enum=Always;Never;IfNotPresent type ImagePullPolicySpec string diff --git a/api/v1beta2/limit_ranges.go b/pkg/api/limit_ranges.go similarity index 80% rename from api/v1beta2/limit_ranges.go rename to pkg/api/limit_ranges.go index cd5d0a13..649099ad 100644 --- a/api/v1beta2/limit_ranges.go +++ b/pkg/api/limit_ranges.go @@ -1,10 +1,12 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api import corev1 "k8s.io/api/core/v1" +// +kubebuilder:object:generate=true + type LimitRangesSpec struct { Items []corev1.LimitRangeSpec `json:"items,omitempty"` } diff --git a/api/v1beta1/network_policy.go b/pkg/api/network_policy.go similarity index 82% rename from api/v1beta1/network_policy.go rename to pkg/api/network_policy.go index 18c96489..b9789eb2 100644 --- a/api/v1beta1/network_policy.go +++ b/pkg/api/network_policy.go @@ -1,12 +1,14 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta1 +package api import ( networkingv1 "k8s.io/api/networking/v1" ) +// +kubebuilder:object:generate=true + type NetworkPolicySpec struct { Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"` } diff --git a/api/v1beta1/resource_quota.go b/pkg/api/resource_quota.go similarity index 92% rename from api/v1beta1/resource_quota.go rename to pkg/api/resource_quota.go index 4f0a48a6..1eb5ae48 100644 --- a/api/v1beta1/resource_quota.go +++ b/pkg/api/resource_quota.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta1 +package api import corev1 "k8s.io/api/core/v1" @@ -13,6 +13,8 @@ const ( ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace" ) +// +kubebuilder:object:generate=true + type ResourceQuotaSpec struct { // +kubebuilder:default=Tenant // Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant diff --git a/api/v1beta2/service_allowed_types.go b/pkg/api/service_allowed_types.go similarity index 92% rename from api/v1beta2/service_allowed_types.go rename to pkg/api/service_allowed_types.go index 1f4abd4e..3f8369a4 100644 --- a/api/v1beta2/service_allowed_types.go +++ b/pkg/api/service_allowed_types.go @@ -1,7 +1,9 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api + +// +kubebuilder:object:generate=true type AllowedServices struct { //+kubebuilder:default=true diff --git a/api/v1beta2/service_options.go b/pkg/api/service_options.go similarity index 92% rename from api/v1beta2/service_options.go rename to pkg/api/service_options.go index a232aa5d..22168d4c 100644 --- a/api/v1beta2/service_options.go +++ b/pkg/api/service_options.go @@ -1,7 +1,9 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api + +// +kubebuilder:object:generate=true type ServiceOptions struct { // Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go new file mode 100644 index 00000000..139a7ec8 --- /dev/null +++ b/pkg/api/zz_generated.deepcopy.go @@ -0,0 +1,250 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package api + +import ( + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/api/rbac/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. +func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { + if in == nil { + return nil + } + out := new(AdditionalMetadataSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]v1.Subject, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. +func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { + if in == nil { + return nil + } + out := new(AdditionalRoleBindingsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. +func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { + if in == nil { + return nil + } + out := new(AllowedListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { + *out = *in + if in.NodePort != nil { + in, out := &in.NodePort, &out.NodePort + *out = new(bool) + **out = **in + } + if in.ExternalName != nil { + in, out := &in.ExternalName, &out.ExternalName + *out = new(bool) + **out = **in + } + if in.LoadBalancer != nil { + in, out := &in.LoadBalancer, &out.LoadBalancer + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices. +func (in *AllowedServices) DeepCopy() *AllowedServices { + if in == nil { + return nil + } + out := new(AllowedServices) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { + *out = *in + if in.Allowed != nil { + in, out := &in.Allowed, &out.Allowed + *out = make([]AllowedIP, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. +func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { + if in == nil { + return nil + } + out := new(ExternalServiceIPsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. +func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { + if in == nil { + return nil + } + out := new(ForbiddenListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.LimitRangeSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. +func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { + if in == nil { + return nil + } + out := new(LimitRangesSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]networkingv1.NetworkPolicySpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. +func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { + if in == nil { + return nil + } + out := new(NetworkPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.ResourceQuotaSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. +func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { + if in == nil { + return nil + } + out := new(ResourceQuotaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { + *out = *in + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } + if in.AllowedServices != nil { + in, out := &in.AllowedServices, &out.AllowedServices + *out = new(AllowedServices) + (*in).DeepCopyInto(*out) + } + if in.ExternalServiceIPs != nil { + in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs + *out = new(ExternalServiceIPsSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions. +func (in *ServiceOptions) DeepCopy() *ServiceOptions { + if in == nil { + return nil + } + out := new(ServiceOptions) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/configuration/client.go b/pkg/configuration/client.go index 3a0dd009..951bde20 100644 --- a/pkg/configuration/client.go +++ b/pkg/configuration/client.go @@ -15,7 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) // capsuleConfiguration is the Capsule Configuration retrieval mode diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 5036ac62..f36ec723 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -6,7 +6,7 @@ package configuration import ( "regexp" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) const ( diff --git a/api/v1alpha1/tenant_labels.go b/pkg/utils/tenant_labels.go similarity index 79% rename from api/v1alpha1/tenant_labels.go rename to pkg/utils/tenant_labels.go index 7c49b68a..db97f7ae 100644 --- a/api/v1alpha1/tenant_labels.go +++ b/pkg/utils/tenant_labels.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 +package utils import ( "fmt" @@ -10,11 +10,15 @@ import ( networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" + + "github.com/clastix/capsule/api/v1alpha1" + "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/api/v1beta2" ) func GetTypeLabel(t runtime.Object) (label string, err error) { switch v := t.(type) { - case *Tenant: + case *v1alpha1.Tenant, *v1beta1.Tenant, *v1beta2.Tenant: return "capsule.clastix.io/tenant", nil case *corev1.LimitRange: return "capsule.clastix.io/limit-range", nil diff --git a/pkg/webhook/ingress/errors.go b/pkg/webhook/ingress/errors.go index cd0abc36..20988a52 100644 --- a/pkg/webhook/ingress/errors.go +++ b/pkg/webhook/ingress/errors.go @@ -7,15 +7,15 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type ingressClassForbiddenError struct { className string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewIngressClassForbidden(className string, spec capsulev1beta1.AllowedListSpec) error { +func NewIngressClassForbidden(className string, spec api.AllowedListSpec) error { return &ingressClassForbiddenError{ className: className, spec: spec, @@ -29,7 +29,7 @@ func (i ingressClassForbiddenError) Error() string { type ingressHostnameNotValidError struct { invalidHostnames []string notMatchingHostnames []string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } type ingressHostnameCollisionError struct { @@ -44,7 +44,7 @@ func NewIngressHostnameCollision(hostname string) error { return &ingressHostnameCollisionError{hostname: hostname} } -func NewIngressHostnamesNotValid(invalidHostnames []string, notMatchingHostnames []string, spec capsulev1beta1.AllowedListSpec) error { +func NewIngressHostnamesNotValid(invalidHostnames []string, notMatchingHostnames []string, spec api.AllowedListSpec) error { return &ingressHostnameNotValidError{invalidHostnames: invalidHostnames, notMatchingHostnames: notMatchingHostnames, spec: spec} } @@ -54,10 +54,10 @@ func (i ingressHostnameNotValidError) Error() string { } type ingressClassNotValidError struct { - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewIngressClassNotValid(spec capsulev1beta1.AllowedListSpec) error { +func NewIngressClassNotValid(spec api.AllowedListSpec) error { return &ingressClassNotValidError{ spec: spec, } @@ -68,7 +68,7 @@ func (i ingressClassNotValidError) Error() string { } // nolint:predeclared -func appendClassError(spec capsulev1beta1.AllowedListSpec) (append string) { +func appendClassError(spec api.AllowedListSpec) (append string) { if len(spec.Exact) > 0 { append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) } @@ -81,7 +81,7 @@ func appendClassError(spec capsulev1beta1.AllowedListSpec) (append string) { } // nolint:predeclared -func appendHostnameError(spec capsulev1beta1.AllowedListSpec) (append string) { +func appendHostnameError(spec api.AllowedListSpec) (append string) { if len(spec.Exact) > 0 { append = fmt.Sprintf(", specify one of the following (%s)", strings.Join(spec.Exact, ", ")) } diff --git a/pkg/webhook/ingress/validate_collision.go b/pkg/webhook/ingress/validate_collision.go index 62b2a33d..f7c508d4 100644 --- a/pkg/webhook/ingress/validate_collision.go +++ b/pkg/webhook/ingress/validate_collision.go @@ -19,6 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" "github.com/clastix/capsule/pkg/configuration" "github.com/clastix/capsule/pkg/indexer/ingress" capsulewebhook "github.com/clastix/capsule/pkg/webhook" @@ -48,7 +49,7 @@ func (r *collision) OnCreate(client client.Client, decoder *admission.Decoder, r return utils.ErroredResponse(err) } - if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == capsulev1beta1.HostnameCollisionScopeDisabled { + if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled { return nil } @@ -83,7 +84,7 @@ func (r *collision) OnUpdate(client client.Client, decoder *admission.Decoder, r return utils.ErroredResponse(err) } - if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == capsulev1beta1.HostnameCollisionScopeDisabled { + if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled { return nil } @@ -110,7 +111,7 @@ func (r *collision) OnDelete(client.Client, *admission.Decoder, record.EventReco } // nolint:gocognit,gocyclo,cyclop -func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope capsulev1beta1.HostnameCollisionScope) error { +func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope api.HostnameCollisionScope) error { for hostname, paths := range ing.HostnamePathsPairs() { for path := range paths { var ingressObjList client.ObjectList @@ -127,7 +128,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in namespaces := sets.NewString() // nolint:exhaustive switch scope { - case capsulev1beta1.HostnameCollisionScopeCluster: + case api.HostnameCollisionScopeCluster: tenantList := &capsulev1beta1.TenantList{} if err := clt.List(ctx, tenantList); err != nil { return err @@ -136,7 +137,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in for _, tenant := range tenantList.Items { namespaces.Insert(tenant.Status.Namespaces...) } - case capsulev1beta1.HostnameCollisionScopeTenant: + case api.HostnameCollisionScopeTenant: selector := client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", ing.Namespace())} tenantList := &capsulev1beta1.TenantList{} @@ -147,7 +148,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in for _, tenant := range tenantList.Items { namespaces.Insert(tenant.Status.Namespaces...) } - case capsulev1beta1.HostnameCollisionScopeNamespace: + case api.HostnameCollisionScopeNamespace: namespaces.Insert(ing.Namespace()) } diff --git a/pkg/webhook/namespace/errors.go b/pkg/webhook/namespace/errors.go index 6cdf732f..04e87804 100644 --- a/pkg/webhook/namespace/errors.go +++ b/pkg/webhook/namespace/errors.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) // nolint:predeclared diff --git a/pkg/webhook/namespace/patch.go b/pkg/webhook/namespace/patch.go index 47bc0c3e..1e32cb75 100644 --- a/pkg/webhook/namespace/patch.go +++ b/pkg/webhook/namespace/patch.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + utils2 "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -45,7 +46,7 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec } // Get Tenant Label - ln, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + ln, err := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) if err != nil { response := admission.Errored(http.StatusBadRequest, err) diff --git a/pkg/webhook/networkpolicy/validating.go b/pkg/webhook/networkpolicy/validating.go index 0ac22113..35d01507 100644 --- a/pkg/webhook/networkpolicy/validating.go +++ b/pkg/webhook/networkpolicy/validating.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + utils2 "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -40,7 +41,7 @@ func (r *handler) generic(ctx context.Context, req admission.Request, client cli tnt := &capsulev1beta1.Tenant{} - l, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, _ := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) if v, ok := np.GetLabels()[l]; ok { if err = client.Get(ctx, types.NamespacedName{Name: v}, tnt); err != nil { return nil, err diff --git a/pkg/webhook/node/errors.go b/pkg/webhook/node/errors.go index 355c74d0..e8435a78 100644 --- a/pkg/webhook/node/errors.go +++ b/pkg/webhook/node/errors.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) // nolint:predeclared diff --git a/pkg/webhook/ownerreference/patching.go b/pkg/webhook/ownerreference/patching.go index ac465ebd..9c166c21 100644 --- a/pkg/webhook/ownerreference/patching.go +++ b/pkg/webhook/ownerreference/patching.go @@ -21,6 +21,7 @@ import ( capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" "github.com/clastix/capsule/pkg/configuration" + utils2 "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -61,7 +62,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client return &response } - ln, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + ln, err := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) if err != nil { response := admission.Errored(http.StatusBadRequest, err) diff --git a/pkg/webhook/pod/containerregistry_errors.go b/pkg/webhook/pod/containerregistry_errors.go index 76454d52..776594a4 100644 --- a/pkg/webhook/pod/containerregistry_errors.go +++ b/pkg/webhook/pod/containerregistry_errors.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type missingContainerRegistryError struct { @@ -24,10 +24,10 @@ func NewMissingContainerRegistryError(image string) error { type registryClassForbiddenError struct { fqci string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewContainerRegistryForbidden(image string, spec capsulev1beta1.AllowedListSpec) error { +func NewContainerRegistryForbidden(image string, spec api.AllowedListSpec) error { return ®istryClassForbiddenError{ fqci: image, spec: spec, diff --git a/pkg/webhook/pod/priorityclass_errors.go b/pkg/webhook/pod/priorityclass_errors.go index ba7c9919..21b91d33 100644 --- a/pkg/webhook/pod/priorityclass_errors.go +++ b/pkg/webhook/pod/priorityclass_errors.go @@ -7,15 +7,15 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type podPriorityClassForbiddenError struct { priorityClassName string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewPodPriorityClassForbidden(priorityClassName string, spec capsulev1beta1.AllowedListSpec) error { +func NewPodPriorityClassForbidden(priorityClassName string, spec api.AllowedListSpec) error { return &podPriorityClassForbiddenError{ priorityClassName: priorityClassName, spec: spec, diff --git a/pkg/webhook/pvc/errors.go b/pkg/webhook/pvc/errors.go index 2774f835..196f6b06 100644 --- a/pkg/webhook/pvc/errors.go +++ b/pkg/webhook/pvc/errors.go @@ -7,21 +7,21 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type storageClassNotValidError struct { - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewStorageClassNotValid(storageClasses capsulev1beta1.AllowedListSpec) error { +func NewStorageClassNotValid(storageClasses api.AllowedListSpec) error { return &storageClassNotValidError{ spec: storageClasses, } } // nolint:predeclared -func appendError(spec capsulev1beta1.AllowedListSpec) (append string) { +func appendError(spec api.AllowedListSpec) (append string) { if len(spec.Exact) > 0 { append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) } @@ -39,10 +39,10 @@ func (s storageClassNotValidError) Error() (err string) { type storageClassForbiddenError struct { className string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewStorageClassForbidden(className string, storageClasses capsulev1beta1.AllowedListSpec) error { +func NewStorageClassForbidden(className string, storageClasses api.AllowedListSpec) error { return &storageClassForbiddenError{ className: className, spec: storageClasses, diff --git a/pkg/webhook/service/errors.go b/pkg/webhook/service/errors.go index 6802fda8..c85cb65b 100644 --- a/pkg/webhook/service/errors.go +++ b/pkg/webhook/service/errors.go @@ -7,14 +7,14 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type externalServiceIPForbiddenError struct { cidr []string } -func NewExternalServiceIPForbidden(allowedIps []capsulev1beta1.AllowedIP) error { +func NewExternalServiceIPForbidden(allowedIps []api.AllowedIP) error { cidr := make([]string, 0, len(allowedIps)) for _, i := range allowedIps { From f4ae20a6634ed256e687442513ef9786d1a985f6 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:45:06 +0200 Subject: [PATCH 10/26] chore(lint): adding goheader rule and removing unused ones --- .golangci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 7f62eb65..b611560a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,11 @@ linters-settings: - standard - default - prefix(github.com/clastix/capsule) + goheader: + template: |- + Copyright 2020-2021 Clastix Labs + SPDX-License-Identifier: Apache-2.0 + linters: enable-all: true disable: @@ -35,10 +40,6 @@ linters: - varnamelen - wrapcheck -issues: - exclude: - - Using the variable on range scope .* in function literal - service: golangci-lint-version: 1.45.2 From 92a1b0968a45b7b11481000fd6a13e54e950df29 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:47:06 +0200 Subject: [PATCH 11/26] style: conforming go files headers --- api/v1alpha1/capsuleconfiguration_annotations.go | 3 +++ api/v1beta1/namespace_options.go | 3 +++ api/v1beta1/owner_list.go | 3 +++ api/v1beta1/owner_list_test.go | 3 +++ controllers/tenant/limitranges.go | 3 +++ controllers/tenant/manager.go | 3 +++ controllers/tenant/networkpolicies.go | 3 +++ controllers/tenant/resourcequotas.go | 3 +++ controllers/tenant/resourcequotas_quota.go | 3 +++ controllers/tenant/rolebindings.go | 3 +++ controllers/tenant/utils.go | 3 +++ controllers/utils/name_matching.go | 3 +++ pkg/webhook/ingress/validate_wildcard.go | 3 +++ pkg/webhook/route/ownerreference.go | 3 +++ pkg/webhook/tenant/protected.go | 2 +- pkg/webhook/utils/is_capsule_user.go | 3 +++ pkg/webhook/utils/is_tenant_owner.go | 3 +++ 17 files changed, 49 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/capsuleconfiguration_annotations.go b/api/v1alpha1/capsuleconfiguration_annotations.go index 83ce062e..9fb42c54 100644 --- a/api/v1alpha1/capsuleconfiguration_annotations.go +++ b/api/v1alpha1/capsuleconfiguration_annotations.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1alpha1 const ( diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index 4135c4d8..88b45a28 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1beta1 import ( diff --git a/api/v1beta1/owner_list.go b/api/v1beta1/owner_list.go index dd0c4209..b3f9d728 100644 --- a/api/v1beta1/owner_list.go +++ b/api/v1beta1/owner_list.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1beta1 import ( diff --git a/api/v1beta1/owner_list_test.go b/api/v1beta1/owner_list_test.go index 6877478a..c8b62a03 100644 --- a/api/v1beta1/owner_list_test.go +++ b/api/v1beta1/owner_list_test.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1beta1 import ( diff --git a/controllers/tenant/limitranges.go b/controllers/tenant/limitranges.go index 4cd539d0..59e4e152 100644 --- a/controllers/tenant/limitranges.go +++ b/controllers/tenant/limitranges.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/manager.go b/controllers/tenant/manager.go index fe42c948..1ea17a12 100644 --- a/controllers/tenant/manager.go +++ b/controllers/tenant/manager.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/networkpolicies.go b/controllers/tenant/networkpolicies.go index 69f348bd..90f0379d 100644 --- a/controllers/tenant/networkpolicies.go +++ b/controllers/tenant/networkpolicies.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/resourcequotas.go b/controllers/tenant/resourcequotas.go index ea0bd2e0..348877fc 100644 --- a/controllers/tenant/resourcequotas.go +++ b/controllers/tenant/resourcequotas.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/resourcequotas_quota.go b/controllers/tenant/resourcequotas_quota.go index e0f4151d..c20be18f 100644 --- a/controllers/tenant/resourcequotas_quota.go +++ b/controllers/tenant/resourcequotas_quota.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/rolebindings.go b/controllers/tenant/rolebindings.go index 8b8f7cad..a1d0126d 100644 --- a/controllers/tenant/rolebindings.go +++ b/controllers/tenant/rolebindings.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/utils.go b/controllers/tenant/utils.go index f66d3038..bca8b446 100644 --- a/controllers/tenant/utils.go +++ b/controllers/tenant/utils.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/utils/name_matching.go b/controllers/utils/name_matching.go index 7b1c540c..ddc3dbdf 100644 --- a/controllers/utils/name_matching.go +++ b/controllers/utils/name_matching.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package utils import ( diff --git a/pkg/webhook/ingress/validate_wildcard.go b/pkg/webhook/ingress/validate_wildcard.go index 62f9e214..a865ae77 100644 --- a/pkg/webhook/ingress/validate_wildcard.go +++ b/pkg/webhook/ingress/validate_wildcard.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package ingress import ( diff --git a/pkg/webhook/route/ownerreference.go b/pkg/webhook/route/ownerreference.go index d4e3a098..9fb903a2 100644 --- a/pkg/webhook/route/ownerreference.go +++ b/pkg/webhook/route/ownerreference.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package route import ( diff --git a/pkg/webhook/tenant/protected.go b/pkg/webhook/tenant/protected.go index e5381bfa..b5a5a18d 100644 --- a/pkg/webhook/tenant/protected.go +++ b/pkg/webhook/tenant/protected.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 Clastix Labs +// Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 package tenant diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/webhook/utils/is_capsule_user.go index 0acb4293..ca50608b 100644 --- a/pkg/webhook/utils/is_capsule_user.go +++ b/pkg/webhook/utils/is_capsule_user.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package utils import ( diff --git a/pkg/webhook/utils/is_tenant_owner.go b/pkg/webhook/utils/is_tenant_owner.go index 34207f9f..0d6f39b3 100644 --- a/pkg/webhook/utils/is_tenant_owner.go +++ b/pkg/webhook/utils/is_tenant_owner.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package utils import ( From 81206c13df6cfadc93c7f36a953f0473621d3d4b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:48:38 +0200 Subject: [PATCH 12/26] refactor: using interfaces for accessing tenant namespaces --- api/v1beta1/tenant_types.go | 10 ++++++++++ api/v1beta2/tenant_types.go | 10 ++++++++++ pkg/api/status_namespaces.go | 8 ++++++++ 3 files changed, 28 insertions(+) create mode 100644 pkg/api/status_namespaces.go diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 9e17b53e..009a6f1a 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -72,3 +72,13 @@ type TenantList struct { func init() { SchemeBuilder.Register(&Tenant{}, &TenantList{}) } + +func (in *Tenant) GetNamespaces() (res []string) { + res = make([]string, 0, len(in.Status.Namespaces)) + + for _, ns := range in.Status.Namespaces { + res = append(res, ns) + } + + return +} diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index d366eaaf..04f8fa2c 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -62,6 +62,16 @@ type Tenant struct { Status TenantStatus `json:"status,omitempty"` } +func (in *Tenant) GetNamespaces() (res []string) { + res = make([]string, 0, len(in.Status.Namespaces)) + + for _, ns := range in.Status.Namespaces { + res = append(res, ns) + } + + return +} + //+kubebuilder:object:root=true // TenantList contains a list of Tenant. diff --git a/pkg/api/status_namespaces.go b/pkg/api/status_namespaces.go new file mode 100644 index 00000000..016ff841 --- /dev/null +++ b/pkg/api/status_namespaces.go @@ -0,0 +1,8 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package api + +type Tenant interface { + GetNamespaces() []string +} From d11910b60fc0ee2edd9155dadf2a86d3e421777c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:51:43 +0200 Subject: [PATCH 13/26] feat(api): globaltenantresource and tenantresource support --- PROJECT | 20 +- api/v1beta1/groupversion_info.go | 4 +- api/v1beta2/groupversion_info.go | 4 +- api/v1beta2/tenant_labels.go | 4 +- api/v1beta2/tenantresource_global.go | 49 ++++ api/v1beta2/tenantresource_namespaced.go | 75 ++++++ api/v1beta2/tenantresource_types.go | 72 ++++++ api/v1beta2/zz_generated.deepcopy.go | 312 +++++++++++++++++++++++ 8 files changed, 529 insertions(+), 11 deletions(-) create mode 100644 api/v1beta2/tenantresource_global.go create mode 100644 api/v1beta2/tenantresource_namespaced.go create mode 100644 api/v1beta2/tenantresource_types.go diff --git a/PROJECT b/PROJECT index 72aada3c..ff22733d 100644 --- a/PROJECT +++ b/PROJECT @@ -9,7 +9,6 @@ repo: github.com/clastix/capsule resources: - api: crdVersion: v1 - namespaced: false controller: true domain: clastix.io group: capsule @@ -21,7 +20,6 @@ resources: webhookVersion: v1 - api: crdVersion: v1 - namespaced: false controller: true domain: clastix.io group: capsule @@ -30,7 +28,6 @@ resources: version: v1alpha1 - api: crdVersion: v1 - namespaced: false domain: clastix.io group: capsule kind: Tenant @@ -38,7 +35,6 @@ resources: version: v1beta1 - api: crdVersion: v1 - namespaced: false domain: clastix.io group: capsule kind: Tenant @@ -46,11 +42,25 @@ resources: version: v1beta2 - api: crdVersion: v1 - namespaced: false controller: true domain: clastix.io group: capsule kind: CapsuleConfiguration path: github.com/clastix/capsule/api/v1beta2 version: v1beta2 +- api: + crdVersion: v1 + namespaced: true + domain: clastix.io + group: capsule + kind: TenantResource + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 +- api: + crdVersion: v1 + domain: clastix.io + group: capsule + kind: GlobalTenantResource + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 version: "3" diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go index 341689ec..4a99993c 100644 --- a/api/v1beta1/groupversion_info.go +++ b/api/v1beta1/groupversion_info.go @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // Package v1beta1 contains API Schema definitions for the capsule v1beta1 API group -//+kubebuilder:object:generate=true -//+groupName=capsule.clastix.io +// +kubebuilder:object:generate=true +// +groupName=capsule.clastix.io package v1beta1 import ( diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go index 3117d80f..37920ac4 100644 --- a/api/v1beta2/groupversion_info.go +++ b/api/v1beta2/groupversion_info.go @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // Package v1beta2 contains API Schema definitions for the capsule v1beta2 API group -//+kubebuilder:object:generate=true -//+groupName=capsule.clastix.io +// +kubebuilder:object:generate=true +// +groupName=capsule.clastix.io package v1beta2 import ( diff --git a/api/v1beta2/tenant_labels.go b/api/v1beta2/tenant_labels.go index 98d0e064..7bd2a922 100644 --- a/api/v1beta2/tenant_labels.go +++ b/api/v1beta2/tenant_labels.go @@ -9,10 +9,10 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func GetTypeLabel(t runtime.Object) (label string, err error) { +func GetTypeLabel(t metav1.Object) (label string, err error) { switch v := t.(type) { case *Tenant: return "capsule.clastix.io/tenant", nil diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go new file mode 100644 index 00000000..537b2caa --- /dev/null +++ b/api/v1beta2/tenantresource_global.go @@ -0,0 +1,49 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. +type GlobalTenantResourceSpec struct { + // Defines the Tenant selector used target the tenants on which resources must be propagated. + TenantSelector metav1.LabelSelector `json:"tenantSelector,omitempty"` + TenantResourceSpec `json:",inline"` +} + +// GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. +type GlobalTenantResourceStatus struct { + // List of Tenants addressed by the GlobalTenantResource. + SelectedTenants []string `json:"selectedTenants"` + // List of the replicated resources for the given TenantResource. + ProcessedItems []ObjectReferenceStatus `json:"processedItems"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster + +// GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. +type GlobalTenantResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GlobalTenantResourceSpec `json:"spec,omitempty"` + Status GlobalTenantResourceStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GlobalTenantResourceList contains a list of GlobalTenantResource. +type GlobalTenantResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GlobalTenantResource `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GlobalTenantResource{}, &GlobalTenantResourceList{}) +} diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go new file mode 100644 index 00000000..d0852613 --- /dev/null +++ b/api/v1beta2/tenantresource_namespaced.go @@ -0,0 +1,75 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// TenantResourceSpec defines the desired state of TenantResource. +type TenantResourceSpec struct { + // Define the period of time upon a second reconciliation must be invoked. + // Keep in mind that any change to the manifests will trigger a new reconciliation. + // +kubebuilder:default="60s" + ResyncPeriod metav1.Duration `json:"resyncPeriod"` + // When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. + // Disable this to keep replicated resources although the deletion of the replication manifest. + // +kubebuilder:default=true + PruningOnDelete *bool `json:"pruningOnDelete,omitempty"` + // Defines the rules to select targeting Namespace, along with the objects that must be replicated. + Resources []ResourceSpec `json:"resources"` +} + +type ResourceSpec struct { + // Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. + // In case of nil value, all the Tenant Namespaces are targeted. + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` + // List of the resources already existing in other Namespaces that must be replicated. + NamespacedItems []ObjectReference `json:"namespacedItems,omitempty"` + // List of raw resources that must be replicated. + RawItems []RawExtension `json:"rawItems,omitempty"` + // Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be + // added to the replicated resources. + AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` +} + +// +kubebuilder:validation:XEmbeddedResource +// +kubebuilder:validation:XPreserveUnknownFields +type RawExtension struct { + runtime.RawExtension `json:",inline"` +} + +// TenantResourceStatus defines the observed state of TenantResource. +type TenantResourceStatus struct { + // List of the replicated resources for the given TenantResource. + ProcessedItems []ObjectReferenceStatus `json:"processedItems"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. +// The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. +// For such cases, the GlobalTenantResource must be used. +type TenantResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TenantResourceSpec `json:"spec,omitempty"` + Status TenantResourceStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TenantResourceList contains a list of TenantResource. +type TenantResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TenantResource `json:"items"` +} + +func init() { + SchemeBuilder.Register(&TenantResource{}, &TenantResourceList{}) +} diff --git a/api/v1beta2/tenantresource_types.go b/api/v1beta2/tenantresource_types.go new file mode 100644 index 00000000..8f701b22 --- /dev/null +++ b/api/v1beta2/tenantresource_types.go @@ -0,0 +1,72 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ObjectReferenceAbstract struct { + // Kind of the referent. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` + // Namespace of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + Namespace string `json:"namespace"` + // API version of the referent. + APIVersion string `json:"apiVersion,omitempty"` +} + +type ObjectReferenceStatus struct { + ObjectReferenceAbstract `json:",inline"` + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Name string `json:"name"` +} + +type ObjectReference struct { + ObjectReferenceAbstract `json:",inline"` + // Label selector used to select the given resources in the given Namespace. + Selector metav1.LabelSelector `json:"selector"` +} + +func (in *ObjectReferenceStatus) String() string { + return fmt.Sprintf("Kind=%s,APIVersion=%s,Namespace=%s,Name=%s", in.Kind, in.APIVersion, in.Namespace, in.Name) +} + +func (in *ObjectReferenceStatus) ParseFromString(value string) error { + rawParts := strings.Split(value, ",") + + if len(rawParts) != 4 { + return fmt.Errorf("unexpected raw parts") + } + + for _, i := range rawParts { + parts := strings.Split(i, "=") + + if len(parts) != 2 { + return fmt.Errorf("unrecognized separator") + } + + k, v := parts[0], parts[1] + + switch k { + case "Kind": + in.Kind = v + case "APIVersion": + in.APIVersion = v + case "Namespace": + in.Namespace = v + case "Name": + in.Name = v + default: + return fmt.Errorf("unrecognized marker: %s", k) + } + } + + return nil +} diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 4012bc96..f0994bb1 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -11,6 +11,7 @@ package v1beta2 import ( "github.com/clastix/capsule/pkg/api" "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -183,6 +184,107 @@ func (in *CapsuleResources) DeepCopy() *CapsuleResources { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResource) DeepCopyInto(out *GlobalTenantResource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResource. +func (in *GlobalTenantResource) DeepCopy() *GlobalTenantResource { + if in == nil { + return nil + } + out := new(GlobalTenantResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GlobalTenantResource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResourceList) DeepCopyInto(out *GlobalTenantResourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GlobalTenantResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceList. +func (in *GlobalTenantResourceList) DeepCopy() *GlobalTenantResourceList { + if in == nil { + return nil + } + out := new(GlobalTenantResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GlobalTenantResourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResourceSpec) DeepCopyInto(out *GlobalTenantResourceSpec) { + *out = *in + in.TenantSelector.DeepCopyInto(&out.TenantSelector) + in.TenantResourceSpec.DeepCopyInto(&out.TenantResourceSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceSpec. +func (in *GlobalTenantResourceSpec) DeepCopy() *GlobalTenantResourceSpec { + if in == nil { + return nil + } + out := new(GlobalTenantResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResourceStatus) DeepCopyInto(out *GlobalTenantResourceStatus) { + *out = *in + if in.SelectedTenants != nil { + in, out := &in.SelectedTenants, &out.SelectedTenants + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ProcessedItems != nil { + in, out := &in.ProcessedItems, &out.ProcessedItems + *out = make([]ObjectReferenceStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceStatus. +func (in *GlobalTenantResourceStatus) DeepCopy() *GlobalTenantResourceStatus { + if in == nil { + return nil + } + out := new(GlobalTenantResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in @@ -267,6 +369,54 @@ func (in *NonLimitedResourceError) DeepCopy() *NonLimitedResourceError { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { + *out = *in + out.ObjectReferenceAbstract = in.ObjectReferenceAbstract + in.Selector.DeepCopyInto(&out.Selector) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. +func (in *ObjectReference) DeepCopy() *ObjectReference { + if in == nil { + return nil + } + out := new(ObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReferenceAbstract) DeepCopyInto(out *ObjectReferenceAbstract) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferenceAbstract. +func (in *ObjectReferenceAbstract) DeepCopy() *ObjectReferenceAbstract { + if in == nil { + return nil + } + out := new(ObjectReferenceAbstract) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReferenceStatus) DeepCopyInto(out *ObjectReferenceStatus) { + *out = *in + out.ObjectReferenceAbstract = in.ObjectReferenceAbstract +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferenceStatus. +func (in *ObjectReferenceStatus) DeepCopy() *ObjectReferenceStatus { + if in == nil { + return nil + } + out := new(ObjectReferenceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) { { @@ -335,6 +485,61 @@ func (in *ProxySettings) DeepCopy() *ProxySettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RawExtension) DeepCopyInto(out *RawExtension) { + *out = *in + in.RawExtension.DeepCopyInto(&out.RawExtension) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RawExtension. +func (in *RawExtension) DeepCopy() *RawExtension { + if in == nil { + return nil + } + out := new(RawExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) { + *out = *in + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.NamespacedItems != nil { + in, out := &in.NamespacedItems, &out.NamespacedItems + *out = make([]ObjectReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.RawItems != nil { + in, out := &in.RawItems, &out.RawItems + *out = make([]RawExtension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSpec. +func (in *ResourceSpec) DeepCopy() *ResourceSpec { + if in == nil { + return nil + } + out := new(ResourceSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Tenant) DeepCopyInto(out *Tenant) { *out = *in @@ -394,6 +599,113 @@ func (in *TenantList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResource) DeepCopyInto(out *TenantResource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResource. +func (in *TenantResource) DeepCopy() *TenantResource { + if in == nil { + return nil + } + out := new(TenantResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TenantResource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResourceList) DeepCopyInto(out *TenantResourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TenantResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceList. +func (in *TenantResourceList) DeepCopy() *TenantResourceList { + if in == nil { + return nil + } + out := new(TenantResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TenantResourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResourceSpec) DeepCopyInto(out *TenantResourceSpec) { + *out = *in + out.ResyncPeriod = in.ResyncPeriod + if in.PruningOnDelete != nil { + in, out := &in.PruningOnDelete, &out.PruningOnDelete + *out = new(bool) + **out = **in + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]ResourceSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceSpec. +func (in *TenantResourceSpec) DeepCopy() *TenantResourceSpec { + if in == nil { + return nil + } + out := new(TenantResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResourceStatus) DeepCopyInto(out *TenantResourceStatus) { + *out = *in + if in.ProcessedItems != nil { + in, out := &in.ProcessedItems, &out.ProcessedItems + *out = make([]ObjectReferenceStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceStatus. +func (in *TenantResourceStatus) DeepCopy() *TenantResourceStatus { + if in == nil { + return nil + } + out := new(TenantResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = *in From 6a0de88ffbe4716f3d27d5a0146e3f70440ae36b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:24:24 -0400 Subject: [PATCH 14/26] chore(kustomize): globaltenantresource and tenantresource support --- ...sule.clastix.io_globaltenantresources.yaml | 283 ++++++++++++ .../capsule.clastix.io_tenantresources.yaml | 232 ++++++++++ config/crd/kustomization.yaml | 2 + .../cainjection_in_globaltenantresources.yaml | 7 + .../cainjection_in_tenantresources.yaml | 7 + .../webhook_in_globaltenantresources.yaml | 16 + .../patches/webhook_in_tenantresources.yaml | 16 + config/install.yaml | 405 ++++++++++++++++++ .../globaltenantresource_editor_role.yaml | 24 ++ .../globaltenantresource_viewer_role.yaml | 20 + config/rbac/tenantresource_editor_role.yaml | 24 ++ config/rbac/tenantresource_viewer_role.yaml | 20 + .../capsule_v1beta2_globaltenantresource.yaml | 39 ++ .../capsule_v1beta2_tenantresource.yaml | 36 ++ 14 files changed, 1131 insertions(+) create mode 100644 config/crd/bases/capsule.clastix.io_globaltenantresources.yaml create mode 100644 config/crd/bases/capsule.clastix.io_tenantresources.yaml create mode 100644 config/crd/patches/cainjection_in_globaltenantresources.yaml create mode 100644 config/crd/patches/cainjection_in_tenantresources.yaml create mode 100644 config/crd/patches/webhook_in_globaltenantresources.yaml create mode 100644 config/crd/patches/webhook_in_tenantresources.yaml create mode 100644 config/rbac/globaltenantresource_editor_role.yaml create mode 100644 config/rbac/globaltenantresource_viewer_role.yaml create mode 100644 config/rbac/tenantresource_editor_role.yaml create mode 100644 config/rbac/tenantresource_viewer_role.yaml create mode 100644 config/samples/capsule_v1beta2_globaltenantresource.yaml create mode 100644 config/samples/capsule_v1beta2_tenantresource.yaml diff --git a/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml b/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml new file mode 100644 index 00000000..c6b3869a --- /dev/null +++ b/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml @@ -0,0 +1,283 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: globaltenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: GlobalTenantResource + listKind: GlobalTenantResourceList + plural: globaltenantresources + singular: globaltenantresource + scope: Cluster + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: GlobalTenantResource allows to propagate resource replications + to a specific subset of Tenant resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all + the objects replicated so far will be automatically deleted. Disable + this to keep replicated resources although the deletion of the replication + manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along + with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource + controller, defines additional metadata that must be added + to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant + Namespaces on which the resources must be propagated. In case + of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other + Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources + in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values + array must be non-empty. If the operator is + Exists or DoesNotExist, the values array must + be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation + must be invoked. Keep in mind that any change to the manifests will + trigger a new reconciliation. + type: string + tenantSelector: + description: Defines the Tenant selector used target the tenants on + which resources must be propagated. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - resources + - resyncPeriod + type: object + status: + description: GlobalTenantResourceStatus defines the observed state of + GlobalTenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + selectedTenants: + description: List of Tenants addressed by the GlobalTenantResource. + items: + type: string + type: array + required: + - processedItems + - selectedTenants + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/capsule.clastix.io_tenantresources.yaml b/config/crd/bases/capsule.clastix.io_tenantresources.yaml new file mode 100644 index 00000000..bd2a3681 --- /dev/null +++ b/config/crd/bases/capsule.clastix.io_tenantresources.yaml @@ -0,0 +1,232 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: tenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: TenantResource + listKind: TenantResourceList + plural: tenantresources + singular: tenantresource + scope: Namespaced + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: TenantResource allows a Tenant Owner, if enabled with proper + RBAC, to propagate resources in its Namespace. The object must be deployed + in a Tenant Namespace, and cannot reference object living in non-Tenant + namespaces. For such cases, the GlobalTenantResource must be used. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantResourceSpec defines the desired state of TenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all + the objects replicated so far will be automatically deleted. Disable + this to keep replicated resources although the deletion of the replication + manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along + with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource + controller, defines additional metadata that must be added + to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant + Namespaces on which the resources must be propagated. In case + of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other + Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources + in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values + array must be non-empty. If the operator is + Exists or DoesNotExist, the values array must + be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation + must be invoked. Keep in mind that any change to the manifests will + trigger a new reconciliation. + type: string + required: + - resources + - resyncPeriod + type: object + status: + description: TenantResourceStatus defines the observed state of TenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + required: + - processedItems + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 4c753b21..07d5edd3 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,8 @@ resources: - bases/capsule.clastix.io_tenants.yaml - bases/capsule.clastix.io_capsuleconfigurations.yaml +- bases/capsule.clastix.io_tenantresources.yaml +- bases/capsule.clastix.io_globaltenantresources.yaml # +kubebuilder:scaffold:crdkustomizeresource # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_globaltenantresources.yaml b/config/crd/patches/cainjection_in_globaltenantresources.yaml new file mode 100644 index 00000000..fdf9c307 --- /dev/null +++ b/config/crd/patches/cainjection_in_globaltenantresources.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: globaltenantresources.capsule.clastix.io diff --git a/config/crd/patches/cainjection_in_tenantresources.yaml b/config/crd/patches/cainjection_in_tenantresources.yaml new file mode 100644 index 00000000..e7158ad9 --- /dev/null +++ b/config/crd/patches/cainjection_in_tenantresources.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tenantresources.capsule.clastix.io diff --git a/config/crd/patches/webhook_in_globaltenantresources.yaml b/config/crd/patches/webhook_in_globaltenantresources.yaml new file mode 100644 index 00000000..12cfc50a --- /dev/null +++ b/config/crd/patches/webhook_in_globaltenantresources.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: globaltenantresources.capsule.clastix.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tenantresources.yaml b/config/crd/patches/webhook_in_tenantresources.yaml new file mode 100644 index 00000000..827ccf34 --- /dev/null +++ b/config/crd/patches/webhook_in_tenantresources.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tenantresources.capsule.clastix.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/install.yaml b/config/install.yaml index 1c7a1525..89655950 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -158,6 +158,411 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: globaltenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: GlobalTenantResource + listKind: GlobalTenantResourceList + plural: globaltenantresources + singular: globaltenantresource + scope: Cluster + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + tenantSelector: + description: Defines the Tenant selector used target the tenants on which resources must be propagated. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - resources + - resyncPeriod + type: object + status: + description: GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + selectedTenants: + description: List of Tenants addressed by the GlobalTenantResource. + items: + type: string + type: array + required: + - processedItems + - selectedTenants + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: tenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: TenantResource + listKind: TenantResourceList + plural: tenantresources + singular: tenantresource + scope: Namespaced + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. For such cases, the GlobalTenantResource must be used. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantResourceSpec defines the desired state of TenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + required: + - resources + - resyncPeriod + type: object + status: + description: TenantResourceStatus defines the observed state of TenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + required: + - processedItems + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.10.0 diff --git a/config/rbac/globaltenantresource_editor_role.yaml b/config/rbac/globaltenantresource_editor_role.yaml new file mode 100644 index 00000000..60d87ab1 --- /dev/null +++ b/config/rbac/globaltenantresource_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit globaltenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: globaltenantresource-editor-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources/status + verbs: + - get diff --git a/config/rbac/globaltenantresource_viewer_role.yaml b/config/rbac/globaltenantresource_viewer_role.yaml new file mode 100644 index 00000000..d535a89b --- /dev/null +++ b/config/rbac/globaltenantresource_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view globaltenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: globaltenantresource-viewer-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources + verbs: + - get + - list + - watch +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources/status + verbs: + - get diff --git a/config/rbac/tenantresource_editor_role.yaml b/config/rbac/tenantresource_editor_role.yaml new file mode 100644 index 00000000..2812649e --- /dev/null +++ b/config/rbac/tenantresource_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit tenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tenantresource-editor-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources/status + verbs: + - get diff --git a/config/rbac/tenantresource_viewer_role.yaml b/config/rbac/tenantresource_viewer_role.yaml new file mode 100644 index 00000000..b37ea627 --- /dev/null +++ b/config/rbac/tenantresource_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view tenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tenantresource-viewer-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources + verbs: + - get + - list + - watch +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources/status + verbs: + - get diff --git a/config/samples/capsule_v1beta2_globaltenantresource.yaml b/config/samples/capsule_v1beta2_globaltenantresource.yaml new file mode 100644 index 00000000..3d2853ce --- /dev/null +++ b/config/samples/capsule_v1beta2_globaltenantresource.yaml @@ -0,0 +1,39 @@ +apiVersion: capsule.clastix.io/v1beta2 +kind: GlobalTenantResource +metadata: + name: green-production +spec: + tenantSelector: + matchLabels: + energy: green + resyncPeriod: 60s + pruningOnDelete: true + resources: + - namespaceSelector: + matchLabels: + environment: production + additionalMetadata: + labels: + labels.energy.io: green + annotations: + annotations.energy.io: green + namespacedItems: + - apiVersion: v1 + kind: Secret + namespace: default + selector: + matchLabels: + replicate: green + rawItems: + - apiVersion: v1 + kind: Secret + metadata: + name: raw-secret-1 + - apiVersion: v1 + kind: Secret + metadata: + name: raw-secret-2 + - apiVersion: v1 + kind: Secret + metadata: + name: raw-secret-3 \ No newline at end of file diff --git a/config/samples/capsule_v1beta2_tenantresource.yaml b/config/samples/capsule_v1beta2_tenantresource.yaml new file mode 100644 index 00000000..db255f5d --- /dev/null +++ b/config/samples/capsule_v1beta2_tenantresource.yaml @@ -0,0 +1,36 @@ +apiVersion: capsule.clastix.io/v1beta2 +kind: TenantResource +metadata: + name: wind-objects +spec: + resyncPeriod: 60s + pruningOnDelete: true + resources: + - namespaceSelector: + matchLabels: + environment: production + additionalMetadata: + labels: + labels.energy.io: wind + annotations: + annotations.energy.io: wind + namespacedItems: + - apiVersion: v1 + kind: Secret + namespace: wind-production + selector: + matchLabels: + replicate: solar + rawItems: + - apiVersion: v1 + kind: Secret + metadata: + name: wind-secret-1 + - apiVersion: v1 + kind: Secret + metadata: + name: wind-secret-2 + - apiVersion: v1 + kind: Secret + metadata: + name: wind-secret-3 \ No newline at end of file From 0fb2df911944f63c32da58d9df31a8084de9418c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:54:38 +0200 Subject: [PATCH 15/26] chore(helm)!: globaltenantresource and tenantresource support --- .../crds/globaltenantresources-crd.yaml | 222 ++++++++++++++++++ charts/capsule/crds/tenantresources-crd.yaml | 185 +++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 charts/capsule/crds/globaltenantresources-crd.yaml create mode 100644 charts/capsule/crds/tenantresources-crd.yaml diff --git a/charts/capsule/crds/globaltenantresources-crd.yaml b/charts/capsule/crds/globaltenantresources-crd.yaml new file mode 100644 index 00000000..5519a4b6 --- /dev/null +++ b/charts/capsule/crds/globaltenantresources-crd.yaml @@ -0,0 +1,222 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: globaltenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: GlobalTenantResource + listKind: GlobalTenantResourceList + plural: globaltenantresources + singular: globaltenantresource + scope: Cluster + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + tenantSelector: + description: Defines the Tenant selector used target the tenants on which resources must be propagated. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - resources + - resyncPeriod + type: object + status: + description: GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + selectedTenants: + description: List of Tenants addressed by the GlobalTenantResource. + items: + type: string + type: array + required: + - processedItems + - selectedTenants + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/capsule/crds/tenantresources-crd.yaml b/charts/capsule/crds/tenantresources-crd.yaml new file mode 100644 index 00000000..c1d2a4c7 --- /dev/null +++ b/charts/capsule/crds/tenantresources-crd.yaml @@ -0,0 +1,185 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: tenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: TenantResource + listKind: TenantResourceList + plural: tenantresources + singular: tenantresource + scope: Namespaced + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. For such cases, the GlobalTenantResource must be used. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantResourceSpec defines the desired state of TenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + required: + - resources + - resyncPeriod + type: object + status: + description: TenantResourceStatus defines the observed state of TenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + required: + - processedItems + type: object + type: object + served: true + storage: true + subresources: + status: {} From 8cbdecf9841848498da8710f207a851d90c10e54 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:55:21 +0200 Subject: [PATCH 16/26] feat: globaltenantresource and tenantresource reconciliation --- api/v1beta2/additional_metadata.go | 9 - api/v1beta2/tenantresource_namespaced.go | 4 +- api/v1beta2/zz_generated.deepcopy.go | 31 +-- controllers/resources/global.go | 186 ++++++++++++++++ controllers/resources/namespaced.go | 144 ++++++++++++ controllers/resources/processor.go | 17 ++ controllers/resources/processor_finalizer.go | 54 +++++ controllers/resources/processor_pruning.go | 67 ++++++ controllers/resources/processor_section.go | 223 +++++++++++++++++++ go.sum | 1 - main.go | 11 + pkg/indexer/indexer.go | 5 +- pkg/indexer/tenant/namespaces.go | 16 +- 13 files changed, 716 insertions(+), 52 deletions(-) delete mode 100644 api/v1beta2/additional_metadata.go create mode 100644 controllers/resources/global.go create mode 100644 controllers/resources/namespaced.go create mode 100644 controllers/resources/processor.go create mode 100644 controllers/resources/processor_finalizer.go create mode 100644 controllers/resources/processor_pruning.go create mode 100644 controllers/resources/processor_section.go diff --git a/api/v1beta2/additional_metadata.go b/api/v1beta2/additional_metadata.go deleted file mode 100644 index 9d708e73..00000000 --- a/api/v1beta2/additional_metadata.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -type AdditionalMetadataSpec struct { - Labels map[string]string `json:"labels,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` -} diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go index d0852613..9a0d18d6 100644 --- a/api/v1beta2/tenantresource_namespaced.go +++ b/api/v1beta2/tenantresource_namespaced.go @@ -6,6 +6,8 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + + "github.com/clastix/capsule/pkg/api" ) // TenantResourceSpec defines the desired state of TenantResource. @@ -32,7 +34,7 @@ type ResourceSpec struct { RawItems []RawExtension `json:"rawItems,omitempty"` // Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be // added to the replicated resources. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` } // +kubebuilder:validation:XEmbeddedResource diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index f0994bb1..b6c0df5d 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -15,35 +15,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. -func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { - if in == nil { - return nil - } - out := new(AdditionalMetadataSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { *out = *in @@ -525,7 +496,7 @@ func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) { } if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } } diff --git a/controllers/resources/global.go b/controllers/resources/global.go new file mode 100644 index 00000000..be485319 --- /dev/null +++ b/controllers/resources/global.go @@ -0,0 +1,186 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + "github.com/hashicorp/go-multierror" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +type Global struct { + client client.Client + processor Processor +} + +func (r *Global) enqueueRequestFromTenant(object client.Object) (reqs []reconcile.Request) { + tnt := object.(*capsulev1beta2.Tenant) //nolint:forcetypeassert + + resList := capsulev1beta2.GlobalTenantResourceList{} + if err := r.client.List(context.Background(), &resList); err != nil { + return nil + } + + set := sets.NewString() + + for _, res := range resList.Items { + selector, err := metav1.LabelSelectorAsSelector(&res.Spec.TenantSelector) + if err != nil { + continue + } + + if selector.Matches(labels.Set(tnt.GetLabels())) { + set.Insert(res.GetName()) + } + } + // No need of ordered value here + for res := range set { + reqs = append(reqs, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: res, + }, + }) + } + + return reqs +} + +func (r *Global) SetupWithManager(mgr ctrl.Manager) error { + unstructuredCachingClient, err := client.NewDelegatingClient( + client.NewDelegatingClientInput{ + Client: mgr.GetClient(), + CacheReader: mgr.GetCache(), + CacheUnstructured: true, + }, + ) + if err != nil { + return err + } + + r.client = mgr.GetClient() + r.processor = Processor{ + client: r.client, + unstructuredClient: unstructuredCachingClient, + } + + return ctrl.NewControllerManagedBy(mgr). + For(&capsulev1beta2.GlobalTenantResource{}). + Watches(&source.Kind{Type: &capsulev1beta2.Tenant{}}, handler.EnqueueRequestsFromMapFunc(r.enqueueRequestFromTenant)). + Complete(r) +} + +func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + log.Info("start processing") + + tntResource := capsulev1beta2.GlobalTenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { + if errors.IsNotFound(err) { + log.Info("Request object not found, could have been deleted after reconcile request") + + return reconcile.Result{}, nil + } + + return reconcile.Result{}, err + } + // Adding the default value for the status + if tntResource.Status.ProcessedItems == nil { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) + } + // Handling the finalizer section for the given GlobalTenantResource + enqueueBack, err := r.processor.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) + if err != nil || enqueueBack { + return reconcile.Result{}, err + } + // Retrieving the list of the Tenants up to the selector provided by the GlobalTenantResource resource. + tntSelector, err := metav1.LabelSelectorAsSelector(&tntResource.Spec.TenantSelector) + if err != nil { + log.Error(err, "cannot create MatchingLabelsSelector for Global filtering") + + return reconcile.Result{}, err + } + + tntList := capsulev1beta2.TenantList{} + if err = r.client.List(ctx, &tntList, &client.MatchingLabelsSelector{Selector: tntSelector}); err != nil { + log.Error(err, "cannot list Tenants matching the provided selector") + + return reconcile.Result{}, err + } + // This is the list of newer Tenants that are matching the provided GlobalTenantResource Selector: + // upon replication and pruning, this will be updated in the status of the resource. + tntSet := sets.NewString() + + err = new(multierror.Error) + // A TenantResource is made of several Resource sections, each one with specific options: + // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status. + processedItems := sets.NewString() + + for index, resource := range tntResource.Spec.Resources { + tenantLabel, labelErr := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) + if labelErr != nil { + log.Error(labelErr, "expected label for selection") + + return reconcile.Result{}, labelErr + } + + for _, tnt := range tntList.Items { + tntSet.Insert(tnt.GetName()) + + items, sectionErr := r.processor.HandleSection(ctx, tnt, true, tenantLabel, index, resource) + if sectionErr != nil { + // Upon a process error storing the last error occurred and continuing to iterate, + // avoid to block the whole processing. + err = multierror.Append(err, sectionErr) + } else { + processedItems.Insert(items...) + } + } + } + + if err.(*multierror.Error).ErrorOrNil() != nil { //nolint:errorlint,forcetypeassert + log.Error(err, "unable to replicate the requested resources") + + return reconcile.Result{}, err + } + + shouldUpdateStatus := !sets.NewString(tntResource.Status.SelectedTenants...).Equal(tntSet) + + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + + for _, item := range processedItems.List() { + if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { + tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) + } + } + + shouldUpdateStatus = true + } + + if shouldUpdateStatus { + tntResource.Status.SelectedTenants = tntSet.List() + + if updateErr := r.client.Status().Update(ctx, &tntResource); updateErr != nil { + log.Error(updateErr, "unable to update TenantResource status") + } + } + + log.Info("processing completed") + + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} diff --git a/controllers/resources/namespaced.go b/controllers/resources/namespaced.go new file mode 100644 index 00000000..891d3a3d --- /dev/null +++ b/controllers/resources/namespaced.go @@ -0,0 +1,144 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + "github.com/hashicorp/go-multierror" + apierr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/util/retry" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +type Namespaced struct { + client client.Client + finalizer Processor +} + +func (r *Namespaced) SetupWithManager(mgr ctrl.Manager) error { + unstructuredCachingClient, err := client.NewDelegatingClient( + client.NewDelegatingClientInput{ + Client: mgr.GetClient(), + CacheReader: mgr.GetCache(), + CacheUnstructured: true, + }, + ) + if err != nil { + return err + } + + r.client = mgr.GetClient() + r.finalizer = Processor{ + client: r.client, + unstructuredClient: unstructuredCachingClient, + } + + return ctrl.NewControllerManagedBy(mgr). + For(&capsulev1beta2.TenantResource{}). + Complete(r) +} + +func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + log.Info("start processing") + // Retrieving the TenantResource + tntResource := capsulev1beta2.TenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { + if apierr.IsNotFound(err) { + log.Info("Request object not found, could have been deleted after reconcile request") + + return reconcile.Result{}, nil + } + + log.Error(err, "cannot retrieve capsulev1beta2.TenantResource") + + return reconcile.Result{}, err + } + // Adding the default value for the status + if tntResource.Status.ProcessedItems == nil { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) + } + // Handling the finalizer section for the given TenantResource + enqueueBack, err := r.finalizer.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) + if err != nil || enqueueBack { + return reconcile.Result{}, err + } + // Retrieving the parent of the Global Resource: + // can be owned, or being deployed in one of its Namespace. + tl := &capsulev1beta2.TenantList{} + if err = r.client.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", tntResource.GetNamespace())}); err != nil { + log.Error(err, "unable to detect the Global for the given TenantResource") + + return reconcile.Result{}, err + } + + if len(tl.Items) == 0 { + log.Info("skipping sync, the current Namespace is not belonging to any Global") + + return reconcile.Result{}, nil + } + + err = new(multierror.Error) + // A TenantResource is made of several Resource sections, each one with specific options: + // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status. + processedItems := sets.NewString() + + tenantLabel, labelErr := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) + if labelErr != nil { + log.Error(labelErr, "expected label for selection") + + return reconcile.Result{}, labelErr + } + + for index, resource := range tntResource.Spec.Resources { + items, sectionErr := r.finalizer.HandleSection(ctx, tl.Items[0], false, tenantLabel, index, resource) + if sectionErr != nil { + // Upon a process error storing the last error occurred and continuing to iterate, + // avoid to block the whole processing. + err = multierror.Append(err, sectionErr) + } else { + processedItems.Insert(items...) + } + } + + if err.(*multierror.Error).ErrorOrNil() != nil { //nolint:errorlint,forcetypeassert + log.Error(err, "unable to replicate the requested resources") + + return reconcile.Result{}, err + } + + if r.finalizer.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { + statusErr := retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { + if err = r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { + return err + } + + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + + for _, item := range processedItems.List() { + if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { + tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) + } + } + + return r.client.Status().Update(ctx, &tntResource) + }) + if statusErr != nil { + log.Error(statusErr, "unable to update TenantResource status") + } + } + + log.Info("processing completed") + + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go new file mode 100644 index 00000000..ed71c665 --- /dev/null +++ b/controllers/resources/processor.go @@ -0,0 +1,17 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + finalizer = "capsule.clastix.io/resources" +) + +type Processor struct { + client client.Client + unstructuredClient client.Client +} diff --git a/controllers/resources/processor_finalizer.go b/controllers/resources/processor_finalizer.go new file mode 100644 index 00000000..edc666bb --- /dev/null +++ b/controllers/resources/processor_finalizer.go @@ -0,0 +1,54 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +func (r *Processor) HandleFinalizer(ctx context.Context, obj client.Object, shouldPrune bool, items []capsulev1beta2.ObjectReferenceStatus) (enqueueBack bool, err error) { + log := ctrllog.FromContext(ctx) + // If the object has been marked for deletion, + // we have to clean up the created resources before removing the finalizer. + if obj.GetDeletionTimestamp() != nil { + log.Info("pruning prior finalizer removal") + + if shouldPrune { + _ = r.HandlePruning(ctx, items, nil) + } + + obj.SetFinalizers(nil) + + if err = r.client.Update(ctx, obj); err != nil { + log.Error(err, "cannot remove finalizer") + + return true, err + } + + return true, nil + } + // When the pruning for the given resource is enabled, a finalizer is required when the TenantResource is marked + // for deletion: this allows to perform a clean-up of all the underlying resources. + if shouldPrune && !sets.NewString(obj.GetFinalizers()...).Has(finalizer) { + obj.SetFinalizers(append(obj.GetFinalizers(), finalizer)) + + if err = r.client.Update(ctx, obj); err != nil { + log.Error(err, "cannot add finalizer") + + return true, err + } + + log.Info("added finalizer, enqueuing back for processing") + + return true, nil + } + + return false, nil +} diff --git a/controllers/resources/processor_pruning.go b/controllers/resources/processor_pruning.go new file mode 100644 index 00000000..a6a37c83 --- /dev/null +++ b/controllers/resources/processor_pruning.go @@ -0,0 +1,67 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + apierr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +func (r *Processor) HandlePruning(ctx context.Context, current []capsulev1beta2.ObjectReferenceStatus, desired sets.String) (updateStatus bool) { + log := ctrllog.FromContext(ctx) + // The status items are the actual replicated resources, these must be collected in order to perform the resulting + // diff that will be cleaned-up. + status := sets.NewString() + + for _, item := range current { + status.Insert(item.String()) + } + + diff := status.Difference(desired) + // We don't want to trigger a reconciliation of the Status every time, + // rather, only in case of a difference between the processed and the actual status. + // This can happen upon the first reconciliation, or a removal, or a change, of a resource. + updateStatus = diff.Len() > 0 || status.Len() != desired.Len() + + if diff.Len() > 0 { + log.Info("starting processing pruning", "length", diff.Len()) + } + + // The outer resources must be removed, iterating over these to clean-up + for item := range diff { + or := capsulev1beta2.ObjectReferenceStatus{} + if err := or.ParseFromString(item); err != nil { + log.Error(err, "unable to parse resource to prune", "resource", item) + + continue + } + + obj := unstructured.Unstructured{} + obj.SetNamespace(or.Namespace) + obj.SetName(or.Name) + obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(or.APIVersion, or.Kind)) + + if err := r.unstructuredClient.Delete(ctx, &obj); err != nil { + if apierr.IsNotFound(err) { + // Object may have been already deleted, we can ignore this error + continue + } + + log.Error(err, "unable to prune resource", "resource", item) + + continue + } + + log.Info("resource has been pruned", "resource", item) + } + + return updateStatus +} diff --git a/controllers/resources/processor_section.go b/controllers/resources/processor_section.go new file mode 100644 index 00000000..77c1b662 --- /dev/null +++ b/controllers/resources/processor_section.go @@ -0,0 +1,223 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant, allowCrossNamespaceSelection bool, tenantLabel string, resourceIndex int, spec capsulev1beta2.ResourceSpec) ([]string, error) { + log := ctrllog.FromContext(ctx) + + var err error + // Creating Namespace selector + var selector labels.Selector + + if spec.NamespaceSelector != nil { + selector, err = metav1.LabelSelectorAsSelector(spec.NamespaceSelector) + if err != nil { + log.Error(err, "cannot create Namespace selector for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + } else { + selector = labels.NewSelector() + } + // Resources can be replicated only on Namespaces belonging to the same Global: + // preventing a boundary cross by enforcing the selection. + tntRequirement, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tnt.GetName()}) + if err != nil { + log.Error(err, "unable to create requirement for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + + selector = selector.Add(*tntRequirement) + // Selecting the targeted Namespace according to the TenantResource specification. + namespaces := corev1.NamespaceList{} + if err = r.client.List(ctx, &namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil { + log.Error(err, "cannot retrieve Namespaces for resource", "index", resourceIndex) + + return nil, err + } + // Generating additional metadata + objAnnotations, objLabels := map[string]string{}, map[string]string{} + + if spec.AdditionalMetadata != nil { + objAnnotations = spec.AdditionalMetadata.Annotations + objLabels = spec.AdditionalMetadata.Labels + } + + objAnnotations[tenantLabel] = tnt.GetName() + + objLabels["capsule.clastix.io/resources"] = fmt.Sprintf("%d", resourceIndex) + objLabels[tenantLabel] = tnt.GetName() + // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones: + // these are required to perform a final pruning once the replication has been occurred. + processed := sets.NewString() + + tntNamespaces := sets.NewString(tnt.Status.Namespaces...) + + syncErr := new(multierror.Error) + + for nsIndex, item := range spec.NamespacedItems { + keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} + // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned + // Namespace: this must be blocked by checking it this is the case. + if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { + log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Global", keysAndValues...) + + continue + } + // Namespaced Items are relying on selecting resources, rather than specifying a specific name: + // creating it to get used by the client List action. + itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) + if err != nil { + log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) + + continue + } + + objs := unstructured.UnstructuredList{} + objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) + + if clientErr := r.unstructuredClient.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { + log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, clientErr) + + continue + } + + multiErr := new(multierror.Group) + // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: + // in case of error during the create or update function, this will be appended to the list of errors. + for _, o := range objs.Items { + obj := o + + multiErr.Go(func() error { + nsItems, nsErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if nsErr != nil { + log.Error(err, "unable to sync namespacedItems", keysAndValues...) + + return nsErr + } + + processed.Insert(nsItems...) + + return nil + }) + } + + if objsErr := multiErr.Wait(); objsErr != nil { + syncErr = multierror.Append(syncErr, objsErr) + } + } + + codecFactory := serializer.NewCodecFactory(r.client.Scheme()) + + for rawIndex, item := range spec.RawItems { + obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} + + if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { + log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, decodeErr) + + continue + } + + syncedRaw, rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if rawErr != nil { + log.Info("unable to sync rawItem", keysAndValues...) + // In case of error processing an item in one of any selected Namespaces, storing it to report it lately + // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. + syncErr = multierror.Append(syncErr, rawErr) + } else { + processed.Insert(syncedRaw...) + } + } + + return processed.List(), syncErr.ErrorOrNil() +} + +// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: +// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, +// along adding the additional metadata, if required. +func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string, namespaces corev1.NamespaceList) ([]string, error) { + log := ctrllog.FromContext(ctx) + + errGroup := new(multierror.Group) + + var items []string + + for _, item := range namespaces.Items { + ns := item.GetName() + + errGroup.Go(func() (err error) { + actual, desired := obj.DeepCopy(), obj.DeepCopy() + // Using a deferred function to properly log the results, and adding the item to the processed set. + defer func() { + keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} + + if err != nil { + log.Error(err, "unable to replicate resource", keysAndValues...) + + return + } + + log.Info("resource has been replicated", keysAndValues...) + + replicatedItem := &capsulev1beta2.ObjectReferenceStatus{ + Name: obj.GetName(), + } + replicatedItem.Kind = obj.GetKind() + replicatedItem.Namespace = ns + replicatedItem.APIVersion = obj.GetAPIVersion() + + items = append(items, replicatedItem.String()) + }() + + actual.SetNamespace(ns) + + _, err = controllerutil.CreateOrUpdate(ctx, r.unstructuredClient, actual, func() error { + UID := actual.GetUID() + + actual.SetUnstructuredContent(desired.Object) + actual.SetNamespace(ns) + actual.SetLabels(labels) + actual.SetAnnotations(annotations) + actual.SetResourceVersion("") + actual.SetUID(UID) + + return nil + }) + + return + }) + } + // Wait returns *multierror.Error that implements stdlib error: + // the nil check must be performed down here rather than at the caller level to avoid wrong casting. + if err := errGroup.Wait(); err != nil { + return items, err + } + + return items, nil +} diff --git a/go.sum b/go.sum index 06ade127..1972247b 100644 --- a/go.sum +++ b/go.sum @@ -694,7 +694,6 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index 94f103be..900a2825 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ import ( capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" configcontroller "github.com/clastix/capsule/controllers/config" rbaccontroller "github.com/clastix/capsule/controllers/rbac" + "github.com/clastix/capsule/controllers/resources" servicelabelscontroller "github.com/clastix/capsule/controllers/servicelabels" tenantcontroller "github.com/clastix/capsule/controllers/tenant" tlscontroller "github.com/clastix/capsule/controllers/tls" @@ -266,6 +267,16 @@ func main() { os.Exit(1) } + if err = (&resources.Global{}).SetupWithManager(manager); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "resources.Global") + os.Exit(1) + } + + if err = (&resources.Namespaced{}).SetupWithManager(manager); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "resources.Namespaced") + os.Exit(1) + } + setupLog.Info("starting manager") if err = manager.Start(ctx); err != nil { diff --git a/pkg/indexer/indexer.go b/pkg/indexer/indexer.go index 2e3e88f2..a80c7497 100644 --- a/pkg/indexer/indexer.go +++ b/pkg/indexer/indexer.go @@ -16,6 +16,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/indexer/ingress" "github.com/clastix/capsule/pkg/indexer/namespace" "github.com/clastix/capsule/pkg/indexer/tenant" @@ -29,7 +31,8 @@ type CustomIndexer interface { func AddToManager(ctx context.Context, log logr.Logger, mgr manager.Manager) error { indexers := []CustomIndexer{ - tenant.NamespacesReference{}, + tenant.NamespacesReference{Obj: &capsulev1beta1.Tenant{}}, + tenant.NamespacesReference{Obj: &capsulev1beta2.Tenant{}}, tenant.OwnerReference{}, namespace.OwnerReference{}, ingress.HostnamePath{Obj: &extensionsv1beta1.Ingress{}}, diff --git a/pkg/indexer/tenant/namespaces.go b/pkg/indexer/tenant/namespaces.go index f292d07b..d4388f92 100644 --- a/pkg/indexer/tenant/namespaces.go +++ b/pkg/indexer/tenant/namespaces.go @@ -6,13 +6,15 @@ package tenant import ( "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) -type NamespacesReference struct{} +type NamespacesReference struct { + Obj client.Object +} func (o NamespacesReference) Object() client.Object { - return &capsulev1beta1.Tenant{} + return o.Obj } func (o NamespacesReference) Field() string { @@ -22,12 +24,6 @@ func (o NamespacesReference) Field() string { // nolint:forcetypeassert func (o NamespacesReference) Func() client.IndexerFunc { return func(object client.Object) []string { - namespaces := object.(*capsulev1beta1.Tenant).DeepCopy().Status.Namespaces - - if namespaces == nil { - return []string{} - } - - return namespaces + return object.(api.Tenant).GetNamespaces() } } From 5076393d390f0ba2a7594b39eb24b0feb62f8203 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:55:54 +0200 Subject: [PATCH 17/26] test: globaltenantresource and tenantresource support --- e2e/globaltenantresource_test.go | 300 ++++++++++++++++++++++++++++ e2e/suite_test.go | 9 +- e2e/tenantresource_test.go | 326 +++++++++++++++++++++++++++++++ 3 files changed, 630 insertions(+), 5 deletions(-) create mode 100644 e2e/globaltenantresource_test.go create mode 100644 e2e/tenantresource_test.go diff --git a/e2e/globaltenantresource_test.go b/e2e/globaltenantresource_test.go new file mode 100644 index 00000000..0f2c9144 --- /dev/null +++ b/e2e/globaltenantresource_test.go @@ -0,0 +1,300 @@ +//go:build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + "fmt" + "time" + + . "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/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" +) + +var _ = Describe("Creating a GlobalTenantResource object", func() { + solar := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "energy-solar", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "solar-user", + Kind: "User", + }, + }, + }, + } + + wind := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "energy-wind", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "wind-user", + Kind: "User", + }, + }, + }, + } + + namespacedItem := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy-secret", + Namespace: "default", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Type: corev1.SecretTypeOpaque, + } + + gtr := &capsulev1beta2.GlobalTenantResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "replicate-energies", + }, + Spec: capsulev1beta2.GlobalTenantResourceSpec{ + TenantSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + TenantResourceSpec: capsulev1beta2.TenantResourceSpec{ + ResyncPeriod: metav1.Duration{Duration: time.Minute}, + PruningOnDelete: pointer.Bool(true), + Resources: []capsulev1beta2.ResourceSpec{ + { + NamespacedItems: []capsulev1beta2.ObjectReference{ + { + ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ + Kind: "Secret", + Namespace: "default", + APIVersion: "v1", + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + }, + }, + RawItems: []capsulev1beta2.RawExtension{ + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-1", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-2", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-3", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + }, + AdditionalMetadata: &api.AdditionalMetadataSpec{ + Labels: map[string]string{ + "labels.energy.io": "replicate", + }, + Annotations: map[string]string{ + "annotations.energy.io": "replicate", + }, + }, + }, + }, + }, + }, + } + + JustBeforeEach(func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), solar) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), wind) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), gtr) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), namespacedItem) + }).Should(Succeed()) + }) + + JustAfterEach(func() { + Expect(k8sClient.Delete(context.TODO(), solar)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), wind)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), gtr)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), namespacedItem)).Should(Succeed()) + }) + + It("should replicate resources to all Tenants", func() { + solarNs, windNs := []string{"solar-one", "solar-two", "solar-three"}, []string{"wind-one", "wind-two", "wind-three"} + + By("creating solar Namespaces", func() { + for _, ns := range solarNs { + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + } + }) + + By("creating wind Namespaces", func() { + for _, ns := range windNs { + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, wind.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + } + }) + + for _, ns := range append(solarNs, windNs...) { + By(fmt.Sprintf("waiting for replicated resources in %s Namespace", ns), func() { + Eventually(func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + } + + By("removing a Namespace from labels", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: wind.GetName()}, wind)).ToNot(HaveOccurred()) + + wind.SetLabels(nil) + Expect(k8sClient.Update(context.TODO(), wind)).ToNot(HaveOccurred()) + + By("expecting no more items in the wind Tenant namespaces due to label update", func() { + for _, ns := range windNs { + Eventually(func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0)) + } + }) + }) + + By("using a Namespace selector", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: gtr.GetName()}, gtr)).ToNot(HaveOccurred()) + + gtr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "solar-three", + }, + } + + Expect(k8sClient.Update(context.TODO(), gtr)).ToNot(HaveOccurred()) + + checkFn := func(ns string) func() []corev1.Secret { + return func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + } + } + + for _, ns := range []string{"solar-one", "solar-two"} { + Eventually(checkFn(ns), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0)) + } + + Eventually(checkFn("solar-three"), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + + By("checking if replicated object have annotations and labels", func() { + for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { + secret := corev1.Secret{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: "solar-three"}, &secret)).ToNot(HaveOccurred()) + + for k, v := range gtr.Spec.Resources[0].AdditionalMetadata.Labels { + _, err := HaveKeyWithValue(k, v).Match(secret.GetLabels()) + Expect(err).ToNot(HaveOccurred()) + } + + for k, v := range gtr.Spec.Resources[0].AdditionalMetadata.Annotations { + _, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations()) + Expect(err).ToNot(HaveOccurred()) + } + } + }) + }) +}) diff --git a/e2e/suite_test.go b/e2e/suite_test.go index f3af00af..4748f5b3 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -22,6 +22,7 @@ import ( capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -57,11 +58,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).ToNot(HaveOccurred()) Expect(cfg).ToNot(BeNil()) - err = capsulev1beta1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - err = capsulev1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) + Expect(capsulev1alpha1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) + Expect(capsulev1beta1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) + Expect(capsulev1beta2.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).ToNot(HaveOccurred()) diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go new file mode 100644 index 00000000..e1cb8c1a --- /dev/null +++ b/e2e/tenantresource_test.go @@ -0,0 +1,326 @@ +//go:build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + "fmt" + "math/rand" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" +) + +var _ = Describe("Creating a TenantResource object", func() { + solar := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "energy-solar", + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "solar-user", + Kind: "User", + }, + }, + }, + } + + tntItem := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy-secret", + Namespace: "solar-system", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Type: corev1.SecretTypeOpaque, + } + + crossNamespaceItem := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cross-reference-secret", + Namespace: "default", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Type: corev1.SecretTypeOpaque, + } + + tr := &capsulev1beta2.TenantResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "replicate-energies", + Namespace: "solar-system", + }, + Spec: capsulev1beta2.TenantResourceSpec{ + ResyncPeriod: metav1.Duration{Duration: time.Minute}, + PruningOnDelete: pointer.Bool(true), + Resources: []capsulev1beta2.ResourceSpec{ + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + NamespacedItems: []capsulev1beta2.ObjectReference{ + { + ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ + Kind: "Secret", + Namespace: "solar-system", + APIVersion: "v1", + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + }, + }, + RawItems: []capsulev1beta2.RawExtension{ + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-1", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-2", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-3", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + }, + AdditionalMetadata: &api.AdditionalMetadataSpec{ + Labels: map[string]string{ + "labels.energy.io": "replicate", + }, + Annotations: map[string]string{ + "annotations.energy.io": "replicate", + }, + }, + }, + }, + }, + } + + JustBeforeEach(func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), solar) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), crossNamespaceItem) + }).Should(Succeed()) + }) + + JustAfterEach(func() { + Expect(k8sClient.Delete(context.TODO(), crossNamespaceItem)).Should(Succeed()) + _ = k8sClient.Delete(context.TODO(), solar) + }) + + It("should replicate resources to all Tenant Namespaces", func() { + solarNs := []string{"solar-one", "solar-two", "solar-three"} + + By("creating solar Namespaces", func() { + for _, ns := range append(solarNs, "solar-system") { + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + } + }) + + By("labelling Namespaces", func() { + for _, name := range []string{"solar-one", "solar-two", "solar-three"} { + EventuallyWithOffset(1, func() error { + ns := corev1.Namespace{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name}, &ns)).Should(Succeed()) + + labels := ns.GetLabels() + if labels == nil { + return fmt.Errorf("missing labels") + } + labels["replicate"] = "true" + ns.SetLabels(labels) + + return k8sClient.Update(context.TODO(), &ns) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + } + }) + + By("creating the namespaced item", func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), tntItem) + }).Should(Succeed()) + }) + + By("creating the TenantResource", func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), tr) + }).Should(Succeed()) + }) + + for _, ns := range solarNs { + By(fmt.Sprintf("waiting for replicated resources in %s Namespace", ns), func() { + Eventually(func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + } + + By("using a Namespace selector", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred()) + + tr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "solar-three", + }, + } + + Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred()) + + checkFn := func(ns string) func() []corev1.Secret { + return func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + } + } + + for _, ns := range []string{"solar-one", "solar-two"} { + Eventually(checkFn(ns), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0)) + } + + Eventually(checkFn("solar-three"), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + + By("checking if replicated object have annotations and labels", func() { + for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { + secret := corev1.Secret{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: "solar-three"}, &secret)).ToNot(HaveOccurred()) + + for k, v := range tr.Spec.Resources[0].AdditionalMetadata.Labels { + _, err := HaveKeyWithValue(k, v).Match(secret.GetLabels()) + Expect(err).ToNot(HaveOccurred()) + } + + for k, v := range tr.Spec.Resources[0].AdditionalMetadata.Annotations { + _, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations()) + Expect(err).ToNot(HaveOccurred()) + } + } + }) + + By("checking that cross-namespace objects are not replicated", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred()) + tr.Spec.Resources[0].NamespacedItems = append(tr.Spec.Resources[0].NamespacedItems, capsulev1beta2.ObjectReference{ + ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ + Kind: crossNamespaceItem.Kind, + Namespace: crossNamespaceItem.GetName(), + APIVersion: crossNamespaceItem.APIVersion, + }, + Selector: metav1.LabelSelector{ + MatchLabels: crossNamespaceItem.GetLabels(), + }, + }) + + Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred()) + // Ensuring that although the deletion of TenantResource object, + // the replicated objects are not deleted. + Consistently(func() error { + return k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: solarNs[rand.Intn(len(solarNs))], Name: crossNamespaceItem.GetName()}, &corev1.Secret{}) + }, 10*time.Second, time.Second).Should(HaveOccurred()) + }) + + By("checking pruning is deleted", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred()) + Expect(*tr.Spec.PruningOnDelete).Should(BeTrue()) + + tr.Spec.PruningOnDelete = pointer.Bool(false) + + Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred()) + + By("deleting the TenantResource", func() { + // Ensuring that although the deletion of TenantResource object, + // the replicated objects are not deleted. + Expect(k8sClient.Delete(context.TODO(), tr)).Should(Succeed()) + + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + Expect(err).ToNot(HaveOccurred()) + + Consistently(func() []corev1.Secret { + secrets := corev1.SecretList{} + + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: "solar-three"}) + Expect(err).ToNot(HaveOccurred()) + + return secrets.Items + }, 10*time.Second, time.Second).Should(HaveLen(4)) + }) + }) + }) +}) From f3d3160f24d0d732e03fbce827922a782e91906a Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:56:14 +0200 Subject: [PATCH 18/26] docs: globaltenantresource and tenantresource support --- Makefile | 2 +- .../general/{tenant-crd.md => crds-apis.md} | 1121 +++++++++++++++-- docs/content/general/references.md | 2 +- docs/content/general/tutorial.md | 126 ++ docs/gridsome.server.js | 2 +- 5 files changed, 1178 insertions(+), 75 deletions(-) rename docs/content/general/{tenant-crd.md => crds-apis.md} (86%) diff --git a/Makefile b/Makefile index 451cfb3a..c496a090 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ generate: controller-gen $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." apidoc: apidocs-gen - $(APIDOCS_GEN) crdoc --resources config/crd/bases --output docs/content/general/tenant-crd.md --template docs/template/reference-cr.tmpl + $(APIDOCS_GEN) crdoc --resources config/crd/bases --output docs/content/general/crds-apis.md --template docs/template/reference-cr.tmpl # Helm SRC_ROOT = $(shell git rev-parse --show-toplevel) diff --git a/docs/content/general/tenant-crd.md b/docs/content/general/crds-apis.md similarity index 86% rename from docs/content/general/tenant-crd.md rename to docs/content/general/crds-apis.md index 4a34dc2e..b6b091dd 100644 --- a/docs/content/general/tenant-crd.md +++ b/docs/content/general/crds-apis.md @@ -639,14 +639,14 @@ LimitRangeItem defines a min/max usage limit for any resource that matches on ki - annotations + additionalAnnotations map[string]string
false - labels + additionalLabels map[string]string
@@ -1498,14 +1498,14 @@ A scoped-resource selector requirement is a selector that contains values, a sco - annotations + additionalAnnotations map[string]string
false - labels + additionalLabels map[string]string
@@ -1586,6 +1586,10 @@ Resource Types: - [CapsuleConfiguration](#capsuleconfiguration) +- [GlobalTenantResource](#globaltenantresource) + +- [TenantResource](#tenantresource) + - [Tenant](#tenant) @@ -1848,14 +1852,14 @@ Allows to set different name rather than the canonical one for the Capsule confi -## Tenant +## GlobalTenantResource -Tenant is the Schema for the tenants API. +GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. @@ -1875,7 +1879,7 @@ Tenant is the Schema for the tenants API. - + @@ -1884,28 +1888,28 @@ Tenant is the Schema for the tenants API. - + - +
kind stringTenantGlobalTenantResource true
Refer to the Kubernetes API documentation for the fields of the `metadata` field. true
specspec object - TenantSpec defines the desired state of Tenant.
+ GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
false
statusstatus object - Returns the observed state of the Tenant.
+ GlobalTenantResourceStatus defines the observed state of GlobalTenantResource.
false
-### Tenant.spec +### GlobalTenantResource.spec -TenantSpec defines the desired state of Tenant. +GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. @@ -1917,119 +1921,246 @@ TenantSpec defines the desired state of Tenant. - + - - + + - + - - + + - - + + - - - + +
ownersresources []object - Specifies the owners of the Tenant. Mandatory.
+ Defines the rules to select targeting Namespace, along with the objects that must be replicated.
true
additionalRoleBindings[]objectresyncPeriodstring - 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.
+ Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation.
+
+ Default: 60s
falsetrue
containerRegistriesobjectpruningOnDeleteboolean - 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.
+ When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest.
+
+ Default: true
false
cordonedbooleantenantSelectorobject - Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
+ Defines the Tenant selector used target the tenants on which resources must be propagated.
false
imagePullPolicies[]enum
+ + +### GlobalTenantResource.spec.resources[index] + + + + + + + + + + + + + + + + + - + - - + + - - + + - - - + +
NameTypeDescriptionRequired
additionalMetadataobject - 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.
+ Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
false
ingressOptionsnamespaceSelector object - Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+ Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted.
false
limitRangesobjectnamespacedItems[]object - Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+ List of the resources already existing in other Namespaces that must be replicated.
false
namespaceOptionsobjectrawItems[]RawExtension - Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+ List of raw resources that must be replicated.
false
networkPoliciesobject
+ + +### GlobalTenantResource.spec.resources[index].additionalMetadata + + + +Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + + + + + + + + + + + + + - + - - - + +
NameTypeDescriptionRequired
annotationsmap[string]string - Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
false
nodeSelectorlabels map[string]string - Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
+
false
preventDeletionboolean
+ + +### GlobalTenantResource.spec.resources[index].namespaceSelector + + + +Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + + + + + + + + + + + + + - - + + + +
NameTypeDescriptionRequired
matchExpressions[]object - Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
false
priorityClassesobjectmatchLabelsmap[string]string - Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
+ matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
false
+ + +### GlobalTenantResource.spec.resources[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + - - + + - + - - + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
resourceQuotasobjectoperatorstring - 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.
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
falsetrue
serviceOptionsobjectvalues[]string - Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+ values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
false
+ + +### GlobalTenantResource.spec.resources[index].namespacedItems[index] + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
storageClassesnamespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
selector object - Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
+ Label selector used to select the given resources in the given Namespace.
+
true
apiVersionstring + API version of the referent.
false
-### Tenant.spec.owners[index] - +### GlobalTenantResource.spec.resources[index].namespacedItems[index].selector +Label selector used to select the given resources in the given Namespace. @@ -2041,28 +2172,874 @@ TenantSpec defines the desired state of Tenant. - - + + - + - - + + + + +
clusterRoles[]stringmatchExpressions[]object - Defines additional cluster-roles for the specific Owner.
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
truefalse
kindenummatchLabelsmap[string]string - Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
-
- Enum: User, Group, ServiceAccount
+ matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### GlobalTenantResource.spec.resources[index].namespacedItems[index].selector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + - + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
true
nameoperator string - Name of tenant owner.
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### GlobalTenantResource.spec.tenantSelector + + + +Defines the Tenant selector used target the tenants on which resources must be propagated. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### GlobalTenantResource.spec.tenantSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### GlobalTenantResource.status + + + +GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
processedItems[]object + List of the replicated resources for the given TenantResource.
+
true
selectedTenants[]string + List of Tenants addressed by the GlobalTenantResource.
+
true
+ + +### GlobalTenantResource.status.processedItems[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
namestring + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+
true
namespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
apiVersionstring + API version of the referent.
+
false
+ +## TenantResource + + + + + + +TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. For such cases, the GlobalTenantResource must be used. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringTenantResourcetrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + TenantResourceSpec defines the desired state of TenantResource.
+
false
statusobject + TenantResourceStatus defines the observed state of TenantResource.
+
false
+ + +### TenantResource.spec + + + +TenantResourceSpec defines the desired state of TenantResource. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
resources[]object + Defines the rules to select targeting Namespace, along with the objects that must be replicated.
+
true
resyncPeriodstring + Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation.
+
+ Default: 60s
+
true
pruningOnDeleteboolean + When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest.
+
+ Default: true
+
false
+ + +### TenantResource.spec.resources[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
additionalMetadataobject + Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
+
false
namespaceSelectorobject + Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted.
+
false
namespacedItems[]object + List of the resources already existing in other Namespaces that must be replicated.
+
false
rawItems[]RawExtension + List of raw resources that must be replicated.
+
false
+ + +### TenantResource.spec.resources[index].additionalMetadata + + + +Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
annotationsmap[string]string +
+
false
labelsmap[string]string +
+
false
+ + +### TenantResource.spec.resources[index].namespaceSelector + + + +Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### TenantResource.spec.resources[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### TenantResource.spec.resources[index].namespacedItems[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
namespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
selectorobject + Label selector used to select the given resources in the given Namespace.
+
true
apiVersionstring + API version of the referent.
+
false
+ + +### TenantResource.spec.resources[index].namespacedItems[index].selector + + + +Label selector used to select the given resources in the given Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### TenantResource.spec.resources[index].namespacedItems[index].selector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### TenantResource.status + + + +TenantResourceStatus defines the observed state of TenantResource. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
processedItems[]object + List of the replicated resources for the given TenantResource.
+
true
+ + +### TenantResource.status.processedItems[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
namestring + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+
true
namespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
apiVersionstring + API version of the referent.
+
false
+ +## Tenant + + + + + + +Tenant is the Schema for the tenants API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringTenanttrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + TenantSpec defines the desired state of Tenant.
+
false
statusobject + Returns the observed state of the Tenant.
+
false
+ + +### Tenant.spec + + + +TenantSpec defines the desired state of Tenant. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
owners[]object + Specifies the owners of the Tenant. Mandatory.
+
true
additionalRoleBindings[]object + Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
+
false
containerRegistriesobject + Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
+
false
cordonedboolean + Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
+
false
imagePullPolicies[]enum + Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
+
false
ingressOptionsobject + Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+
false
limitRangesobject + Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+
false
namespaceOptionsobject + Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+
false
networkPoliciesobject + Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
false
nodeSelectormap[string]string + Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
+
false
preventDeletionboolean + Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
+
false
priorityClassesobject + Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
+
false
resourceQuotasobject + Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
+
false
serviceOptionsobject + Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+
false
storageClassesobject + Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
+
false
+ + +### Tenant.spec.owners[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/content/general/references.md b/docs/content/general/references.md index bc91c311..8b959c7c 100644 --- a/docs/content/general/references.md +++ b/docs/content/general/references.md @@ -6,7 +6,7 @@ Reference document for Capsule Operator configuration Capsule operator uses a Custom Resources Definition (CRD) for _Tenants_. Tenants are cluster wide resources, so you need cluster level permissions to work with tenants. -You can learn about tenant CRDs in the following [section](./tenant-crd) +You can learn about tenant CRDs in the following [section](./crds-apis) ## Capsule Configuration diff --git a/docs/content/general/tutorial.md b/docs/content/general/tutorial.md index 4392cae7..7880afea 100644 --- a/docs/content/general/tutorial.md +++ b/docs/content/general/tutorial.md @@ -1724,6 +1724,132 @@ spec: EOF ``` +## Replicating resources across a set of Tenants' Namespaces + +When developing an Internal Developer Platform the Platform Administrator could want to propagate a set of resources. +These could be Secret, ConfigMap, or other kinds of resources that the tenants would require to use the platform. + +> A generic example could be the container registry secrets, especially in the context where the Tenants can just use a specific registry. + +Starting from Capsule v0.2.0, a new set of Custom Resource Definitions have been introduced, such as the `GlobalTenantResource`, let's start with a potential use-case using the personas described at the beginning of this document. + +**Bill** created the Tenants for **Alice** using the `Tenant` CRD, and labels these resources using the following command: + +``` +$: kubectl label tnt/oil energy=fossil +tenant oil labeled + +$: kubectl label tnt/gas energy=fossil +tenant oil labeled +``` + +In the said scenario, these Tenants must use container images from a trusted registry, and that would require the usage of specific credentials for the image pull. + +The said container registry is deployed in the cluster in the namespace `harbor-system`, and this Namespace contains all image pull secret for each Tenant, e.g.: a secret named `harbor-system/fossil-pull-secret` as follows. + +``` +$: kubectl -n harbor-system get secret --show-labels +NAME TYPE DATA AGE LABELS +fossil-pull-secret Opaque 1 28s tenant=fossil +``` + +These credentials would be distributed to the Tenant owners manually, or vice-versa, the owners would require those. +Such a scenario would be against the concept of the self-service solution offered by Capsule, and **Bill** can solve this by creating the `GlobalTenantResource` as follows. + +```yaml +apiVersion: capsule.clastix.io/v1beta2 +kind: GlobalTenantResource +metadata: + name: fossil-pull-secrets +spec: + tenantSelector: + matchLabels: + energy: fossil + resyncPeriod: 60s + resources: + - namespacedItems: + - apiVersion: v1 + kind: Secret + namespace: harbor-system + selector: + matchLabels: + tenant: fossil +``` + +A full reference of the API is available in the [CRDs API section](/docs/general/crds-apis), just explaining the expected behaviour and the resulting outcome: + +> Capsule will select all the Tenant resources according to the key `tenantSelector`. +> Each object defined in the `namespacedItems` and matching the provided `selector` will be replicated into each Namespace bounded to the selected Tenants. +> Capsule will check every 60 seconds if the resources are replicated and in sync, as defined in the key `resyncPeriod`. + +The `GlobalTenantResource` is a cluster-scoped resource, thus it has been designed for cluster administrators and cannot be used by Tenant owners: for that purpose, the `TenantResource` one can help. + +## Replicating resources across Namespaces of a Tenant + +Although Capsule is supporting a few amounts of personas, it can be used to allow building an Internal Developer Platform used barely by Tenant owners, or users created by these thanks to Service Account. + +In a such scenario, a Tenant Owner would like to distribute resources across all the Namespace of their Tenant, without the need to establish a manual procedure, or the need for writing a custom automation. + +The Namespaced-scope API `TenantResource` allows to replicate resources across the Tenant's Namespace. + +> The Tenant owners must have proper RBAC configured in order to create, get, update, and delete their `TenantResource` CRD instances. +> This can be achieved using the Tenant key `additionalRoleBindings` or a custom Tenant owner role, compared to the default one (`admin`). + +For our example, **Alice**, the project lead for the `solar` tenant, wants to provision automatically a **DataBase** resource for each Namespace of their Tenant: these are the Namespace list. + +``` +$: kubectl get namespaces -l capsule.clastix.io/tenant=solar --show-labels +NAME STATUS AGE LABELS +solar-1 Active 59s capsule.clastix.io/tenant=solar,environment=production,kubernetes.io/metadata.name=solar-1,name=solar-1 +solar-2 Active 58s capsule.clastix.io/tenant=solar,environment=production,kubernetes.io/metadata.name=solar-2,name=solar-2 +solar-system Active 62s capsule.clastix.io/tenant=solar,kubernetes.io/metadata.name=solar-system,name=solar-system +``` + +**Alice** creates a `TenantResource` in the Tenant namespace `solar-system` as follows. + +```yaml +apiVersion: capsule.clastix.io/v1beta2 +kind: TenantResource +metadata: + name: solar-db + namespace: solar-system +spec: + resyncPeriod: 60s + resources: + - namespaceSelector: + matchLabels: + environment: production + rawItems: + - apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + metadata: + name: postgresql + spec: + description: PostgreSQL cluster for the Solar project + instances: 3 + postgresql: + pg_hba: + - hostssl app all all cert + primaryUpdateStrategy: unsupervised + storage: + size: 1Gi +``` + +The expected result will be the object `Cluster` for the API version `postgresql.cnpg.io/v1` to get created in all the Solar tenant namespaces matching the label selector declared by the key `namespaceSelector`. + +``` +$: kubectl get clusters.postgresql.cnpg.io -A +NAMESPACE NAME AGE INSTANCES READY STATUS PRIMARY +solar-1 postgresql 80s 3 3 Cluster in healthy state postgresql-1 +solar-2 postgresql 80s 3 3 Cluster in healthy state postgresql-1 +``` + +The `TenantResource` object has been created in the namespace `solar-system` that doesn't satisfy the Namespace selector. Furthermore, Capsule will automatically inject the required labels to avoid a `TenantResource` could start polluting other Namespaces. + +Eventually, using the key `namespacedItem`, it is possible to reference existing objects to get propagated across the other Tenant namespaces: in this case, a Tenant Owner can just refer to objects in their Namespaces, preventing a possible escalation referring to non owned objects. + +As with `GlobalTenantResource`, the full reference of the API is available in the [CRDs API section](/docs/general/crds-apis). + --- This ends our tutorial on how to implement complex multi-tenancy and policy-driven scenarios with Capsule. As we improve it, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future. diff --git a/docs/gridsome.server.js b/docs/gridsome.server.js index f85a7649..a4d02b80 100644 --- a/docs/gridsome.server.js +++ b/docs/gridsome.server.js @@ -39,7 +39,7 @@ module.exports = function (api) { }, { label: 'CRDs APIs', - path: '/docs/general/tenant-crd' + path: '/docs/general/crds-apis' }, { label: 'Multi-Tenant Benchmark', From ac073f5ee8319ca1e0e142720c6864db1f61b962 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 17:57:48 -0400 Subject: [PATCH 19/26] refactor(test): generating namespace names avoiding collision --- e2e/allowed_external_ips_test.go | 6 +++--- e2e/container_registry_test.go | 10 +++++----- e2e/custom_capsule_group_test.go | 6 +++--- e2e/disable_externalname_test.go | 2 +- e2e/disable_ingress_wildcard_test.go | 6 +++--- e2e/disable_loadbalancer_test.go | 2 +- e2e/disable_node_ports_test.go | 2 +- e2e/enable_loadbalancer_test.go | 2 +- e2e/enable_node_ports_test.go | 2 +- e2e/imagepullpolicy_multiple_test.go | 2 +- e2e/imagepullpolicy_single_test.go | 2 +- e2e/ingress_class_extensions_test.go | 10 +++++----- e2e/ingress_class_networking_test.go | 10 +++++----- ...ress_hostnames_collision_cluster_scope_test.go | 4 ++-- e2e/ingress_hostnames_collision_disabled_test.go | 4 ++-- ...ss_hostnames_collision_namespace_scope_test.go | 4 ++-- ...gress_hostnames_collision_tenant_scope_test.go | 4 ++-- e2e/ingress_hostnames_test.go | 12 ++++++------ e2e/missing_tenant_test.go | 2 +- e2e/namespace_additional_metadata_test.go | 2 +- e2e/namespace_capsule_label_test.go | 6 +++--- e2e/namespace_user_metadata_test.go | 15 ++++++++------- e2e/new_namespace_test.go | 7 ++++--- e2e/overquota_namespace_test.go | 2 +- e2e/owner_webhooks_test.go | 14 +++++++------- e2e/pod_priority_class_test.go | 6 +++--- e2e/selecting_non_owned_tenant_test.go | 2 +- e2e/selecting_tenant_fail_test.go | 2 +- e2e/selecting_tenant_with_label_test.go | 2 +- e2e/service_metadata_test.go | 6 +++--- e2e/storage_class_test.go | 4 ++-- e2e/tenant_cordoning_test.go | 2 +- e2e/utils_test.go | 8 +++++++- 33 files changed, 89 insertions(+), 81 deletions(-) diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go index 038c1b17..0842e080 100644 --- a/e2e/allowed_external_ips_test.go +++ b/e2e/allowed_external_ips_test.go @@ -52,7 +52,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { }) It("should fail creating an evil service", func() { - ns := NewNamespace("evil-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ @@ -85,7 +85,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { }) It("should allow the first CIDR block", func() { - ns := NewNamespace("allowed-service-cidr") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ @@ -118,7 +118,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { }) It("should allow the /32 CIDR block", func() { - ns := NewNamespace("allowed-service-strict") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index ef854d06..92d8ec67 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -48,7 +48,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should add labels to Namespace", func() { - ns := NewNamespace("registry-labels") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) Eventually(func() (ok bool) { Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.Name}, ns)).Should(Succeed()) @@ -65,7 +65,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should deny running a gcr.io container", func() { - ns := NewNamespace("registry-deny") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -87,7 +87,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should allow using a registry only match", func() { - ns := NewNamespace("registry-only") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -112,7 +112,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should allow using an exact match", func() { - ns := NewNamespace("registry-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -137,7 +137,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should allow using a regex match", func() { - ns := NewNamespace("registry-regex") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ diff --git a/e2e/custom_capsule_group_test.go b/e2e/custom_capsule_group_test.go index 64a20807..531c97d6 100644 --- a/e2e/custom_capsule_group_test.go +++ b/e2e/custom_capsule_group_test.go @@ -47,7 +47,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro configuration.Spec.UserGroups = []string{"test"} }) - ns := NewNamespace("cg-namespace-fail") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) @@ -56,7 +56,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro configuration.Spec.UserGroups = []string{"test", "alice"} }) - ns := NewNamespace("cg-namespace-1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -67,7 +67,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro configuration.Spec.UserGroups = []string{"capsule.clastix.io"} }) - ns := NewNamespace("cg-namespace-2") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index 855455d4..9d3a1e87 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -51,7 +51,7 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan }) It("should fail creating a service with ExternalService type", func() { - ns := NewNamespace("disable-external-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) EventuallyCreation(func() error { diff --git a/e2e/disable_ingress_wildcard_test.go b/e2e/disable_ingress_wildcard_test.go index 4e848e99..901a39d7 100644 --- a/e2e/disable_ingress_wildcard_test.go +++ b/e2e/disable_ingress_wildcard_test.go @@ -60,7 +60,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the } } - ns := NewNamespace("extensions-v1beta1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -141,7 +141,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the } } - ns := NewNamespace("networking-v1beta1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -222,7 +222,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the } } - ns := NewNamespace("networking-v1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index c532a180..d94983b7 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -50,7 +50,7 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant }) It("should fail creating a service with LoadBalancer type", func() { - ns := NewNamespace("disable-loadbalancer-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index c384f453..8e5cb2d9 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -50,7 +50,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f }) It("should fail creating a service with NodePort type", func() { - ns := NewNamespace("disable-node-ports") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index 32a45e85..e6201cc0 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -50,7 +50,7 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" }) It("should succeed creating a service with LoadBalancer type", func() { - ns := NewNamespace("enable-loadbalancer-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/enable_node_ports_test.go b/e2e/enable_node_ports_test.go index b0a27ba7..b8803f42 100644 --- a/e2e/enable_node_ports_test.go +++ b/e2e/enable_node_ports_test.go @@ -43,7 +43,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", fu }) It("should allow creating a service with NodePort type", func() { - ns := NewNamespace("enable-node-ports") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go index f973e69c..d725c4f6 100644 --- a/e2e/imagepullpolicy_multiple_test.go +++ b/e2e/imagepullpolicy_multiple_test.go @@ -45,7 +45,7 @@ var _ = Describe("enforcing some defined ImagePullPolicy", func() { }) It("should just allow the defined policies", func() { - ns := NewNamespace("allow-policy") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) cs := ownerClient(tnt.Spec.Owners[0]) diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go index 41918ad0..9b99e349 100644 --- a/e2e/imagepullpolicy_single_test.go +++ b/e2e/imagepullpolicy_single_test.go @@ -45,7 +45,7 @@ var _ = Describe("enforcing a defined ImagePullPolicy", func() { }) It("should just allow the defined policy", func() { - ns := NewNamespace("allow-policies") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) cs := ownerClient(tnt.Spec.Owners[0]) diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index f5f278b3..deb949c2 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -57,7 +57,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should block a non allowed class for extensions/v1beta1", func() { - ns := NewNamespace("ingress-class-disallowed-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -142,7 +142,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should allow enabled class using the deprecated annotation", func() { - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -189,7 +189,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", Skip("Running test on Kubernetes " + version.String() + ", doesn't provide .spec.ingressClassName") } - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -216,7 +216,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should allow enabled Ingress by regex using the deprecated annotation", func() { - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-ingress" @@ -251,7 +251,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should allow enabled Ingress by regex using the ingressClassName field", func() { - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-haproxy" diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 22459441..14a70227 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -63,7 +63,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-disallowed-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -146,7 +146,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -186,7 +186,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -224,7 +224,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-ingress" @@ -263,7 +263,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-haproxy" diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index 34ad3bb9..de503367 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -144,12 +144,12 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun }) It("should ensure Cluster scope for Ingress hostname and path collision", func() { - ns1 := NewNamespace("tenant-one-ns") + ns1 := NewNamespace("") cs1 := ownerClient(tnt1.Spec.Owners[0]) NamespaceCreation(ns1, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns1.GetName())) - ns2 := NewNamespace("tenant-two-ns") + ns2 := NewNamespace("") cs2 := ownerClient(tnt2.Spec.Owners[0]) NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.GetName())) diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index f63cdc5a..081dfc9c 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -120,8 +120,8 @@ var _ = Describe("when disabling Ingress hostnames collision", func() { }) It("should not check any kind of collision", func() { - ns1 := NewNamespace("namespace-collision-one") - ns2 := NewNamespace("namespace-collision-two") + ns1 := NewNamespace("") + ns2 := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index ecda0d38..bfc43b95 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -120,8 +120,8 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f }) It("should ensure Namespace scope for Ingress hostname and path collision", func() { - ns1 := NewNamespace("namespace-collision-one") - ns2 := NewNamespace("namespace-collision-two") + ns1 := NewNamespace("") + ns2 := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 55385be1..6e3b67b9 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -120,9 +120,9 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func It("should ensure Tenant scope for Ingress hostname and path collision", func() { - ns1 := NewNamespace("cluster-collision-one") + ns1 := NewNamespace("") - ns2 := NewNamespace("cluster-collision-two") + ns2 := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index d0f55640..0c12fd80 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -121,7 +121,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should block a non allowed Hostname", func() { - ns := NewNamespace("disallowed-hostname-networking") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -144,7 +144,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should block a non allowed Hostname", func() { - ns := NewNamespace("disallowed-hostname-extensions") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -167,7 +167,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in list", func() { - ns := NewNamespace("allowed-hostname-list-networking") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -192,7 +192,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in list", func() { - ns := NewNamespace("allowed-hostname-list-extensions") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -217,7 +217,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in regex", func() { - ns := NewNamespace("allowed-hostname-regex-networking") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -242,7 +242,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in regex", func() { - ns := NewNamespace("allowed-hostname-regex-extensions") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/missing_tenant_test.go b/e2e/missing_tenant_test.go index dd78b24d..1ff51b66 100644 --- a/e2e/missing_tenant_test.go +++ b/e2e/missing_tenant_test.go @@ -27,7 +27,7 @@ var _ = Describe("creating a Namespace creation with no Tenant assigned", func() }, }, } - ns := NewNamespace("no-namespace") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).ShouldNot(Succeed()) diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go index 3bded7d2..68452022 100644 --- a/e2e/namespace_additional_metadata_test.go +++ b/e2e/namespace_additional_metadata_test.go @@ -54,7 +54,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f }) It("should contain additional Namespace metadata", func() { - ns := NewNamespace("namespace-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/namespace_capsule_label_test.go b/e2e/namespace_capsule_label_test.go index 49777ca3..bb223550 100644 --- a/e2e/namespace_capsule_label_test.go +++ b/e2e/namespace_capsule_label_test.go @@ -44,9 +44,9 @@ var _ = Describe("creating several Namespaces for a Tenant", func() { It("should contains the default Capsule label", func() { namespaces := []*v1.Namespace{ - NewNamespace("first-capsule-ns"), - NewNamespace("second-capsule-ns"), - NewNamespace("third-capsule-ns"), + NewNamespace(""), + NewNamespace(""), + NewNamespace(""), } for _, ns := range namespaces { NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/namespace_user_metadata_test.go b/e2e/namespace_user_metadata_test.go index 2abcea68..e624b27f 100644 --- a/e2e/namespace_user_metadata_test.go +++ b/e2e/namespace_user_metadata_test.go @@ -8,10 +8,11 @@ package e2e import ( "context" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" ) var _ = Describe("creating a Namespace with user-specified labels and annotations", func() { @@ -47,12 +48,12 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation It("should allow", func() { By("specifying non-forbidden labels", func() { - ns := NewNamespace("namespace-user-metadata-allowed-labels") + ns := NewNamespace("") ns.SetLabels(map[string]string{"bim": "baz"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) }) By("specifying non-forbidden annotations", func() { - ns := NewNamespace("namespace-user-metadata-allowed-annotations") + ns := NewNamespace("") ns.SetAnnotations(map[string]string{"bim": "baz"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) }) @@ -60,22 +61,22 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation It("should fail", func() { By("specifying forbidden labels using exact match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetLabels(map[string]string{"foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden labels using regex match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetLabels(map[string]string{"gatsby-foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using exact match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetAnnotations(map[string]string{"foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using regex match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetAnnotations(map[string]string{"gatsby-foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) diff --git a/e2e/new_namespace_test.go b/e2e/new_namespace_test.go index 440821b4..a14e6c04 100644 --- a/e2e/new_namespace_test.go +++ b/e2e/new_namespace_test.go @@ -7,6 +7,7 @@ package e2e import ( "context" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,7 +49,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun }) It("should be available in Tenant namespaces list and RoleBindings should be present when created", func() { - ns := NewNamespace("new-namespace-user") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) @@ -57,7 +58,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun } }) It("should be available in Tenant namespaces list and RoleBindings should present when created as Group", func() { - ns := NewNamespace("new-namespace-group") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[1], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) @@ -66,7 +67,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun } }) It("should be available in Tenant namespaces list and RoleBindings should present when created as ServiceAccount", func() { - ns := NewNamespace("new-namespace-sa") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[2], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) diff --git a/e2e/overquota_namespace_test.go b/e2e/overquota_namespace_test.go index aec9e7e7..986c55c5 100644 --- a/e2e/overquota_namespace_test.go +++ b/e2e/overquota_namespace_test.go @@ -52,7 +52,7 @@ var _ = Describe("creating a Namespace in over-quota of three", func() { } }) - ns := NewNamespace("bob-fail") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).ShouldNot(Succeed()) diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index 1e9a4335..63a8fecc 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -100,7 +100,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { It("should disallow deletions", func() { By("blocking Capsule Limit ranges", func() { - ns := NewNamespace("limit-range-disallow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -114,7 +114,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Expect(cs.CoreV1().LimitRanges(ns.GetName()).Delete(context.TODO(), lr.Name, metav1.DeleteOptions{})).ShouldNot(Succeed()) }) By("blocking Capsule Network Policy", func() { - ns := NewNamespace("network-policy-disallow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -128,7 +128,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Expect(cs.NetworkingV1().NetworkPolicies(ns.GetName()).Delete(context.TODO(), np.Name, metav1.DeleteOptions{})).ShouldNot(Succeed()) }) By("blocking Capsule Resource Quota", func() { - ns := NewNamespace("resource-quota-disallow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -145,7 +145,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { It("should allow", func() { By("listing Limit Range", func() { - ns := NewNamespace("limit-range-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -156,7 +156,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) By("listing Network Policy", func() { - ns := NewNamespace("network-policy-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -167,7 +167,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) By("listing Resource Quota", func() { - ns := NewNamespace("resource-quota-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -180,7 +180,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }) It("should allow all actions to Tenant owner Network Policy", func() { - ns := NewNamespace("network-policy-allow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index fc7dcdf4..852ec00b 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -48,7 +48,7 @@ var _ = Describe("enforcing a Priority Class", func() { }) It("should block non allowed Priority Class", func() { - ns := NewNamespace("system-node-critical") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -87,7 +87,7 @@ var _ = Describe("enforcing a Priority Class", func() { Expect(k8sClient.Delete(context.TODO(), pc)).Should(Succeed()) }() - ns := NewNamespace("pc-exact-match") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -113,7 +113,7 @@ var _ = Describe("enforcing a Priority Class", func() { }) It("should allow regex match", func() { - ns := NewNamespace("pc-regex-match") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go index f1a47828..82ec38f3 100644 --- a/e2e/selecting_non_owned_tenant_test.go +++ b/e2e/selecting_non_owned_tenant_test.go @@ -48,7 +48,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func() l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) - ns := NewNamespace("tenant-non-owned-ns") + ns := NewNamespace("") ns.SetLabels(map[string]string{ l: tnt.Name, }) diff --git a/e2e/selecting_tenant_fail_test.go b/e2e/selecting_tenant_fail_test.go index bfa4150f..960a98ec 100644 --- a/e2e/selecting_tenant_fail_test.go +++ b/e2e/selecting_tenant_fail_test.go @@ -70,7 +70,7 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns } It("should fail", func() { - ns := NewNamespace("fail-ns") + ns := NewNamespace("") By("user owns 2 tenants", func() { EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t1) }).Should(Succeed()) EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t2) }).Should(Succeed()) diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go index abc359e0..9f1270c7 100644 --- a/e2e/selecting_tenant_with_label_test.go +++ b/e2e/selecting_tenant_with_label_test.go @@ -58,7 +58,7 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi }) It("should be assigned to the selected Tenant", func() { - ns := NewNamespace("tenant-2-ns") + ns := NewNamespace("") By("assigning to the Namespace the Capsule Tenant label", func() { l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index bb467e98..99a1ec77 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -65,7 +65,7 @@ var _ = Describe("adding metadata to Service objects", func() { }) It("should apply them to Service", func() { - ns := NewNamespace("service-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -142,7 +142,7 @@ var _ = Describe("adding metadata to Service objects", func() { }) It("should apply them to Endpoints", func() { - ns := NewNamespace("endpoints-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -226,7 +226,7 @@ var _ = Describe("adding metadata to Service objects", func() { } } - ns := NewNamespace("endpointslice-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index 8a25a0cc..e5a1c273 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -52,7 +52,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { }) It("should fails", func() { - ns := NewNamespace("storage-class-disallowed") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -99,7 +99,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { }) It("should allow", func() { - ns := NewNamespace("storage-class-allowed") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/tenant_cordoning_test.go b/e2e/tenant_cordoning_test.go index fa12d391..4ece4159 100644 --- a/e2e/tenant_cordoning_test.go +++ b/e2e/tenant_cordoning_test.go @@ -46,7 +46,7 @@ var _ = Describe("cordoning a Tenant", func() { It("should block or allow operations", func() { cs := ownerClient(tnt.Spec.Owners[0]) - ns := NewNamespace("cordoned-namespace") + ns := NewNamespace("") pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 56eb9052..ad0cc1ba 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -8,10 +8,12 @@ package e2e import ( "context" "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" "strings" "time" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -31,6 +33,10 @@ const ( ) func NewNamespace(name string) *corev1.Namespace { + if len(name) == 0 { + name = rand.String(10) + } + return &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: name, From 682862be70758f215bf0583dc75e9ede2c6eacf7 Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Thu, 27 Oct 2022 22:29:37 +0300 Subject: [PATCH 20/26] feat: refactor resources controller Co-authored-by: Maksim Fedotov --- api/v1beta2/tenantresource_global.go | 14 +- api/v1beta2/tenantresource_namespaced.go | 2 +- api/v1beta2/zz_generated.deepcopy.go | 23 +- controllers/resources/global.go | 89 ++++-- controllers/resources/namespaced.go | 114 ++++--- controllers/resources/processor.go | 263 +++++++++++++++- controllers/resources/processor_finalizer.go | 54 ---- controllers/resources/processor_pruning.go | 67 ---- controllers/resources/processor_section.go | 223 ------------- go.mod | 81 +++-- go.sum | 311 ++++++++++--------- main.go | 24 ++ 12 files changed, 660 insertions(+), 605 deletions(-) delete mode 100644 controllers/resources/processor_finalizer.go delete mode 100644 controllers/resources/processor_pruning.go delete mode 100644 controllers/resources/processor_section.go diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go index 537b2caa..fd918fa6 100644 --- a/api/v1beta2/tenantresource_global.go +++ b/api/v1beta2/tenantresource_global.go @@ -5,6 +5,7 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" ) // GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. @@ -19,7 +20,18 @@ type GlobalTenantResourceStatus struct { // List of Tenants addressed by the GlobalTenantResource. SelectedTenants []string `json:"selectedTenants"` // List of the replicated resources for the given TenantResource. - ProcessedItems []ObjectReferenceStatus `json:"processedItems"` + ProcessedItems ProcessedItems `json:"processedItems"` +} + +type ProcessedItems []ObjectReferenceStatus + +func (p *ProcessedItems) AsSet() sets.String { + set := sets.NewString() + for _, i := range *p { + set.Insert(i.String()) + } + + return set } //+kubebuilder:object:root=true diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go index 9a0d18d6..3d19a952 100644 --- a/api/v1beta2/tenantresource_namespaced.go +++ b/api/v1beta2/tenantresource_namespaced.go @@ -46,7 +46,7 @@ type RawExtension struct { // TenantResourceStatus defines the observed state of TenantResource. type TenantResourceStatus struct { // List of the replicated resources for the given TenantResource. - ProcessedItems []ObjectReferenceStatus `json:"processedItems"` + ProcessedItems ProcessedItems `json:"processedItems"` } //+kubebuilder:object:root=true diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index b6c0df5d..06023ba2 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -241,7 +241,7 @@ func (in *GlobalTenantResourceStatus) DeepCopyInto(out *GlobalTenantResourceStat } if in.ProcessedItems != nil { in, out := &in.ProcessedItems, &out.ProcessedItems - *out = make([]ObjectReferenceStatus, len(*in)) + *out = make(ProcessedItems, len(*in)) copy(*out, *in) } } @@ -436,6 +436,25 @@ func (in *OwnerSpec) DeepCopy() *OwnerSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ProcessedItems) DeepCopyInto(out *ProcessedItems) { + { + in := &in + *out = make(ProcessedItems, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProcessedItems. +func (in ProcessedItems) DeepCopy() ProcessedItems { + if in == nil { + return nil + } + out := new(ProcessedItems) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxySettings) DeepCopyInto(out *ProxySettings) { *out = *in @@ -662,7 +681,7 @@ func (in *TenantResourceStatus) DeepCopyInto(out *TenantResourceStatus) { *out = *in if in.ProcessedItems != nil { in, out := &in.ProcessedItems, &out.ProcessedItems - *out = make([]ObjectReferenceStatus, len(*in)) + *out = make(ProcessedItems, len(*in)) copy(*out, *in) } } diff --git a/controllers/resources/global.go b/controllers/resources/global.go index be485319..673cc367 100644 --- a/controllers/resources/global.go +++ b/controllers/resources/global.go @@ -7,13 +7,16 @@ import ( "context" "github.com/hashicorp/go-multierror" - "k8s.io/apimachinery/pkg/api/errors" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -60,21 +63,9 @@ func (r *Global) enqueueRequestFromTenant(object client.Object) (reqs []reconcil } func (r *Global) SetupWithManager(mgr ctrl.Manager) error { - unstructuredCachingClient, err := client.NewDelegatingClient( - client.NewDelegatingClientInput{ - Client: mgr.GetClient(), - CacheReader: mgr.GetCache(), - CacheUnstructured: true, - }, - ) - if err != nil { - return err - } - r.client = mgr.GetClient() r.processor = Processor{ - client: r.client, - unstructuredClient: unstructuredCachingClient, + client: mgr.GetClient(), } return ctrl.NewControllerManagedBy(mgr). @@ -83,14 +74,15 @@ func (r *Global) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } +//nolint:dupl func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := ctrllog.FromContext(ctx) log.Info("start processing") - - tntResource := capsulev1beta2.GlobalTenantResource{} - if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { - if errors.IsNotFound(err) { + // Retrieving the GlobalTenantResource + tntResource := &capsulev1beta2.GlobalTenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, tntResource); err != nil { + if apierrors.IsNotFound(err) { log.Info("Request object not found, could have been deleted after reconcile request") return reconcile.Result{}, nil @@ -98,15 +90,40 @@ func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reco return reconcile.Result{}, err } - // Adding the default value for the status + + patchHelper, err := patch.NewHelper(tntResource, r.client) + if err != nil { + return reconcile.Result{}, errors.Wrap(err, "failed to init patch helper") + } + + defer func() { + if e := patchHelper.Patch(ctx, tntResource); e != nil { + if err == nil { + err = errors.Wrap(e, "failed to patch GlobalTenantResource") + } + } + }() + + // Handle deleted GlobalTenantResource + if !tntResource.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, tntResource) + } + + // Handle non-deleted GlobalTenantResource + return r.reconcileNormal(ctx, tntResource) +} + +func (r *Global) reconcileNormal(ctx context.Context, tntResource *capsulev1beta2.GlobalTenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + controllerutil.AddFinalizer(tntResource, finalizer) + } + if tntResource.Status.ProcessedItems == nil { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) } - // Handling the finalizer section for the given GlobalTenantResource - enqueueBack, err := r.processor.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) - if err != nil || enqueueBack { - return reconcile.Result{}, err - } + // Retrieving the list of the Tenants up to the selector provided by the GlobalTenantResource resource. tntSelector, err := metav1.LabelSelectorAsSelector(&tntResource.Spec.TenantSelector) if err != nil { @@ -158,9 +175,7 @@ func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reco return reconcile.Result{}, err } - shouldUpdateStatus := !sets.NewString(tntResource.Status.SelectedTenants...).Equal(tntSet) - - if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), processedItems) { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) for _, item := range processedItems.List() { @@ -168,16 +183,22 @@ func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reco tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) } } - - shouldUpdateStatus = true } - if shouldUpdateStatus { - tntResource.Status.SelectedTenants = tntSet.List() + tntResource.Status.SelectedTenants = tntSet.List() - if updateErr := r.client.Status().Update(ctx, &tntResource); updateErr != nil { - log.Error(updateErr, "unable to update TenantResource status") - } + log.Info("processing completed") + + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} + +func (r *Global) reconcileDelete(ctx context.Context, tntResource *capsulev1beta2.GlobalTenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), nil) + + controllerutil.RemoveFinalizer(tntResource, finalizer) } log.Info("processing completed") diff --git a/controllers/resources/namespaced.go b/controllers/resources/namespaced.go index 891d3a3d..92a2d1ca 100644 --- a/controllers/resources/namespaced.go +++ b/controllers/resources/namespaced.go @@ -7,12 +7,14 @@ import ( "context" "github.com/hashicorp/go-multierror" - apierr "k8s.io/apimachinery/pkg/api/errors" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/util/retry" + "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -21,25 +23,13 @@ import ( type Namespaced struct { client client.Client - finalizer Processor + processor Processor } func (r *Namespaced) SetupWithManager(mgr ctrl.Manager) error { - unstructuredCachingClient, err := client.NewDelegatingClient( - client.NewDelegatingClientInput{ - Client: mgr.GetClient(), - CacheReader: mgr.GetCache(), - CacheUnstructured: true, - }, - ) - if err != nil { - return err - } - r.client = mgr.GetClient() - r.finalizer = Processor{ - client: r.client, - unstructuredClient: unstructuredCachingClient, + r.processor = Processor{ + client: mgr.GetClient(), } return ctrl.NewControllerManagedBy(mgr). @@ -47,37 +37,62 @@ func (r *Namespaced) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } +//nolint:dupl func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := ctrllog.FromContext(ctx) log.Info("start processing") // Retrieving the TenantResource - tntResource := capsulev1beta2.TenantResource{} - if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { - if apierr.IsNotFound(err) { + tntResource := &capsulev1beta2.TenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, tntResource); err != nil { + if apierrors.IsNotFound(err) { log.Info("Request object not found, could have been deleted after reconcile request") return reconcile.Result{}, nil } - log.Error(err, "cannot retrieve capsulev1beta2.TenantResource") - return reconcile.Result{}, err } + + patchHelper, err := patch.NewHelper(tntResource, r.client) + if err != nil { + return reconcile.Result{}, errors.Wrap(err, "failed to init patch helper") + } + + defer func() { + if e := patchHelper.Patch(ctx, tntResource); e != nil { + if err == nil { + err = errors.Wrap(e, "failed to patch TenantResource") + } + } + }() + + // Handle deleted TenantResource + if !tntResource.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, tntResource) + } + + // Handle non-deleted TenantResource + return r.reconcileNormal(ctx, tntResource) +} + +func (r *Namespaced) reconcileNormal(ctx context.Context, tntResource *capsulev1beta2.TenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + controllerutil.AddFinalizer(tntResource, finalizer) + } + // Adding the default value for the status if tntResource.Status.ProcessedItems == nil { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) } - // Handling the finalizer section for the given TenantResource - enqueueBack, err := r.finalizer.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) - if err != nil || enqueueBack { - return reconcile.Result{}, err - } - // Retrieving the parent of the Global Resource: + + // Retrieving the parent of the Tenant Resource: // can be owned, or being deployed in one of its Namespace. tl := &capsulev1beta2.TenantList{} - if err = r.client.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", tntResource.GetNamespace())}); err != nil { - log.Error(err, "unable to detect the Global for the given TenantResource") + if err := r.client.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", tntResource.GetNamespace())}); err != nil { + log.Error(err, "unable to detect the Tenant for the given TenantResource") return reconcile.Result{}, err } @@ -88,7 +103,7 @@ func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) ( return reconcile.Result{}, nil } - err = new(multierror.Error) + err := new(multierror.Error) // A TenantResource is made of several Resource sections, each one with specific options: // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status. processedItems := sets.NewString() @@ -101,7 +116,7 @@ func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) ( } for index, resource := range tntResource.Spec.Resources { - items, sectionErr := r.finalizer.HandleSection(ctx, tl.Items[0], false, tenantLabel, index, resource) + items, sectionErr := r.processor.HandleSection(ctx, tl.Items[0], false, tenantLabel, index, resource) if sectionErr != nil { // Upon a process error storing the last error occurred and continuing to iterate, // avoid to block the whole processing. @@ -111,33 +126,36 @@ func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) ( } } - if err.(*multierror.Error).ErrorOrNil() != nil { //nolint:errorlint,forcetypeassert + if err.ErrorOrNil() != nil { log.Error(err, "unable to replicate the requested resources") return reconcile.Result{}, err } - if r.finalizer.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { - statusErr := retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { - if err = r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { - return err + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), processedItems) { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + + for _, item := range processedItems.List() { + if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { + tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) } + } + } - tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + log.Info("processing completed") - for _, item := range processedItems.List() { - if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { - tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) - } - } + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} - return r.client.Status().Update(ctx, &tntResource) - }) - if statusErr != nil { - log.Error(statusErr, "unable to update TenantResource status") - } +func (r *Namespaced) reconcileDelete(ctx context.Context, tntResource *capsulev1beta2.TenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), nil) } + controllerutil.RemoveFinalizer(tntResource, finalizer) + log.Info("processing completed") return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index ed71c665..841fee19 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -4,7 +4,24 @@ package resources import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + corev1 "k8s.io/api/core/v1" + apierr "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) const ( @@ -12,6 +29,248 @@ const ( ) type Processor struct { - client client.Client - unstructuredClient client.Client + client client.Client +} + +func (r *Processor) HandlePruning(ctx context.Context, current, desired sets.String) (updateStatus bool) { + log := ctrllog.FromContext(ctx) + + diff := current.Difference(desired) + // We don't want to trigger a reconciliation of the Status every time, + // rather, only in case of a difference between the processed and the actual status. + // This can happen upon the first reconciliation, or a removal, or a change, of a resource. + updateStatus = diff.Len() > 0 || current.Len() != desired.Len() + + if diff.Len() > 0 { + log.Info("starting processing pruning", "length", diff.Len()) + } + + // The outer resources must be removed, iterating over these to clean-up + for item := range diff { + or := capsulev1beta2.ObjectReferenceStatus{} + if err := or.ParseFromString(item); err != nil { + log.Error(err, "unable to parse resource to prune", "resource", item) + + continue + } + + obj := unstructured.Unstructured{} + obj.SetNamespace(or.Namespace) + obj.SetName(or.Name) + obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(or.APIVersion, or.Kind)) + + if err := r.client.Delete(ctx, &obj); err != nil { + if apierr.IsNotFound(err) { + // Object may have been already deleted, we can ignore this error + continue + } + + log.Error(err, "unable to prune resource", "resource", item) + + continue + } + + log.Info("resource has been pruned", "resource", item) + } + + return updateStatus +} + +func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant, allowCrossNamespaceSelection bool, tenantLabel string, resourceIndex int, spec capsulev1beta2.ResourceSpec) ([]string, error) { + log := ctrllog.FromContext(ctx) + + var err error + // Creating Namespace selector + var selector labels.Selector + + if spec.NamespaceSelector != nil { + selector, err = metav1.LabelSelectorAsSelector(spec.NamespaceSelector) + if err != nil { + log.Error(err, "cannot create Namespace selector for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + } else { + selector = labels.NewSelector() + } + // Resources can be replicated only on Namespaces belonging to the same Global: + // preventing a boundary cross by enforcing the selection. + tntRequirement, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tnt.GetName()}) + if err != nil { + log.Error(err, "unable to create requirement for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + + selector = selector.Add(*tntRequirement) + // Selecting the targeted Namespace according to the TenantResource specification. + namespaces := corev1.NamespaceList{} + if err = r.client.List(ctx, &namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil { + log.Error(err, "cannot retrieve Namespaces for resource", "index", resourceIndex) + + return nil, err + } + // Generating additional metadata + objAnnotations, objLabels := map[string]string{}, map[string]string{} + + if spec.AdditionalMetadata != nil { + objAnnotations = spec.AdditionalMetadata.Annotations + objLabels = spec.AdditionalMetadata.Labels + } + + objAnnotations[tenantLabel] = tnt.GetName() + + objLabels["capsule.clastix.io/resources"] = fmt.Sprintf("%d", resourceIndex) + objLabels[tenantLabel] = tnt.GetName() + // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones: + // these are required to perform a final pruning once the replication has been occurred. + processed := sets.NewString() + + tntNamespaces := sets.NewString(tnt.Status.Namespaces...) + + syncErr := new(multierror.Error) + + for nsIndex, item := range spec.NamespacedItems { + keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} + // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned + // Namespace: this must be blocked by checking it this is the case. + if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { + log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Global", keysAndValues...) + + continue + } + // Namespaced Items are relying on selecting resources, rather than specifying a specific name: + // creating it to get used by the client List action. + itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) + if err != nil { + log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) + + continue + } + + objs := unstructured.UnstructuredList{} + objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) + + if clientErr := r.client.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { + log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, clientErr) + + continue + } + + multiErr := new(multierror.Group) + // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: + // in case of error during the create or update function, this will be appended to the list of errors. + for _, o := range objs.Items { + obj := o + + multiErr.Go(func() error { + nsItems, nsErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if nsErr != nil { + log.Error(err, "unable to sync namespacedItems", keysAndValues...) + + return nsErr + } + + processed.Insert(nsItems...) + + return nil + }) + } + + if objsErr := multiErr.Wait(); objsErr != nil { + syncErr = multierror.Append(syncErr, objsErr) + } + } + + codecFactory := serializer.NewCodecFactory(r.client.Scheme()) + + for rawIndex, item := range spec.RawItems { + obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} + + if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { + log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, decodeErr) + + continue + } + + syncedRaw, rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if rawErr != nil { + log.Info("unable to sync rawItem", keysAndValues...) + // In case of error processing an item in one of any selected Namespaces, storing it to report it lately + // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. + syncErr = multierror.Append(syncErr, rawErr) + } else { + processed.Insert(syncedRaw...) + } + } + + return processed.List(), syncErr.ErrorOrNil() +} + +// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: +// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, +// along adding the additional metadata, if required. +func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string, namespaces corev1.NamespaceList) ([]string, error) { + log := ctrllog.FromContext(ctx) + + errGroup := new(multierror.Group) + + var items []string + + for _, item := range namespaces.Items { + ns := item.GetName() + + errGroup.Go(func() (err error) { + actual, desired := obj.DeepCopy(), obj.DeepCopy() + // Using a deferred function to properly log the results, and adding the item to the processed set. + defer func() { + keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} + + if err != nil { + log.Error(err, "unable to replicate resource", keysAndValues...) + + return + } + + log.Info("resource has been replicated", keysAndValues...) + + replicatedItem := &capsulev1beta2.ObjectReferenceStatus{ + Name: obj.GetName(), + } + replicatedItem.Kind = obj.GetKind() + replicatedItem.Namespace = ns + replicatedItem.APIVersion = obj.GetAPIVersion() + + items = append(items, replicatedItem.String()) + }() + + actual.SetNamespace(ns) + + _, err = controllerutil.CreateOrUpdate(ctx, r.client, actual, func() error { + UID := actual.GetUID() + + actual.SetUnstructuredContent(desired.Object) + actual.SetNamespace(ns) + actual.SetLabels(labels) + actual.SetAnnotations(annotations) + actual.SetResourceVersion("") + actual.SetUID(UID) + + return nil + }) + + return + }) + } + // Wait returns *multierror.Error that implements stdlib error: + // the nil check must be performed down here rather than at the caller level to avoid wrong casting. + if err := errGroup.Wait(); err != nil { + return items, err + } + + return items, nil } diff --git a/controllers/resources/processor_finalizer.go b/controllers/resources/processor_finalizer.go deleted file mode 100644 index edc666bb..00000000 --- a/controllers/resources/processor_finalizer.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package resources - -import ( - "context" - - "k8s.io/apimachinery/pkg/util/sets" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" -) - -func (r *Processor) HandleFinalizer(ctx context.Context, obj client.Object, shouldPrune bool, items []capsulev1beta2.ObjectReferenceStatus) (enqueueBack bool, err error) { - log := ctrllog.FromContext(ctx) - // If the object has been marked for deletion, - // we have to clean up the created resources before removing the finalizer. - if obj.GetDeletionTimestamp() != nil { - log.Info("pruning prior finalizer removal") - - if shouldPrune { - _ = r.HandlePruning(ctx, items, nil) - } - - obj.SetFinalizers(nil) - - if err = r.client.Update(ctx, obj); err != nil { - log.Error(err, "cannot remove finalizer") - - return true, err - } - - return true, nil - } - // When the pruning for the given resource is enabled, a finalizer is required when the TenantResource is marked - // for deletion: this allows to perform a clean-up of all the underlying resources. - if shouldPrune && !sets.NewString(obj.GetFinalizers()...).Has(finalizer) { - obj.SetFinalizers(append(obj.GetFinalizers(), finalizer)) - - if err = r.client.Update(ctx, obj); err != nil { - log.Error(err, "cannot add finalizer") - - return true, err - } - - log.Info("added finalizer, enqueuing back for processing") - - return true, nil - } - - return false, nil -} diff --git a/controllers/resources/processor_pruning.go b/controllers/resources/processor_pruning.go deleted file mode 100644 index a6a37c83..00000000 --- a/controllers/resources/processor_pruning.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package resources - -import ( - "context" - - apierr "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/sets" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" -) - -func (r *Processor) HandlePruning(ctx context.Context, current []capsulev1beta2.ObjectReferenceStatus, desired sets.String) (updateStatus bool) { - log := ctrllog.FromContext(ctx) - // The status items are the actual replicated resources, these must be collected in order to perform the resulting - // diff that will be cleaned-up. - status := sets.NewString() - - for _, item := range current { - status.Insert(item.String()) - } - - diff := status.Difference(desired) - // We don't want to trigger a reconciliation of the Status every time, - // rather, only in case of a difference between the processed and the actual status. - // This can happen upon the first reconciliation, or a removal, or a change, of a resource. - updateStatus = diff.Len() > 0 || status.Len() != desired.Len() - - if diff.Len() > 0 { - log.Info("starting processing pruning", "length", diff.Len()) - } - - // The outer resources must be removed, iterating over these to clean-up - for item := range diff { - or := capsulev1beta2.ObjectReferenceStatus{} - if err := or.ParseFromString(item); err != nil { - log.Error(err, "unable to parse resource to prune", "resource", item) - - continue - } - - obj := unstructured.Unstructured{} - obj.SetNamespace(or.Namespace) - obj.SetName(or.Name) - obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(or.APIVersion, or.Kind)) - - if err := r.unstructuredClient.Delete(ctx, &obj); err != nil { - if apierr.IsNotFound(err) { - // Object may have been already deleted, we can ignore this error - continue - } - - log.Error(err, "unable to prune resource", "resource", item) - - continue - } - - log.Info("resource has been pruned", "resource", item) - } - - return updateStatus -} diff --git a/controllers/resources/processor_section.go b/controllers/resources/processor_section.go deleted file mode 100644 index 77c1b662..00000000 --- a/controllers/resources/processor_section.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package resources - -import ( - "context" - "fmt" - - "github.com/hashicorp/go-multierror" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/util/sets" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" -) - -func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant, allowCrossNamespaceSelection bool, tenantLabel string, resourceIndex int, spec capsulev1beta2.ResourceSpec) ([]string, error) { - log := ctrllog.FromContext(ctx) - - var err error - // Creating Namespace selector - var selector labels.Selector - - if spec.NamespaceSelector != nil { - selector, err = metav1.LabelSelectorAsSelector(spec.NamespaceSelector) - if err != nil { - log.Error(err, "cannot create Namespace selector for Namespace filtering and resource replication", "index", resourceIndex) - - return nil, err - } - } else { - selector = labels.NewSelector() - } - // Resources can be replicated only on Namespaces belonging to the same Global: - // preventing a boundary cross by enforcing the selection. - tntRequirement, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tnt.GetName()}) - if err != nil { - log.Error(err, "unable to create requirement for Namespace filtering and resource replication", "index", resourceIndex) - - return nil, err - } - - selector = selector.Add(*tntRequirement) - // Selecting the targeted Namespace according to the TenantResource specification. - namespaces := corev1.NamespaceList{} - if err = r.client.List(ctx, &namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil { - log.Error(err, "cannot retrieve Namespaces for resource", "index", resourceIndex) - - return nil, err - } - // Generating additional metadata - objAnnotations, objLabels := map[string]string{}, map[string]string{} - - if spec.AdditionalMetadata != nil { - objAnnotations = spec.AdditionalMetadata.Annotations - objLabels = spec.AdditionalMetadata.Labels - } - - objAnnotations[tenantLabel] = tnt.GetName() - - objLabels["capsule.clastix.io/resources"] = fmt.Sprintf("%d", resourceIndex) - objLabels[tenantLabel] = tnt.GetName() - // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones: - // these are required to perform a final pruning once the replication has been occurred. - processed := sets.NewString() - - tntNamespaces := sets.NewString(tnt.Status.Namespaces...) - - syncErr := new(multierror.Error) - - for nsIndex, item := range spec.NamespacedItems { - keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} - // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned - // Namespace: this must be blocked by checking it this is the case. - if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { - log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Global", keysAndValues...) - - continue - } - // Namespaced Items are relying on selecting resources, rather than specifying a specific name: - // creating it to get used by the client List action. - itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) - if err != nil { - log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) - - continue - } - - objs := unstructured.UnstructuredList{} - objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) - - if clientErr := r.unstructuredClient.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { - log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) - - syncErr = multierror.Append(syncErr, clientErr) - - continue - } - - multiErr := new(multierror.Group) - // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: - // in case of error during the create or update function, this will be appended to the list of errors. - for _, o := range objs.Items { - obj := o - - multiErr.Go(func() error { - nsItems, nsErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) - if nsErr != nil { - log.Error(err, "unable to sync namespacedItems", keysAndValues...) - - return nsErr - } - - processed.Insert(nsItems...) - - return nil - }) - } - - if objsErr := multiErr.Wait(); objsErr != nil { - syncErr = multierror.Append(syncErr, objsErr) - } - } - - codecFactory := serializer.NewCodecFactory(r.client.Scheme()) - - for rawIndex, item := range spec.RawItems { - obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} - - if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { - log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) - - syncErr = multierror.Append(syncErr, decodeErr) - - continue - } - - syncedRaw, rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) - if rawErr != nil { - log.Info("unable to sync rawItem", keysAndValues...) - // In case of error processing an item in one of any selected Namespaces, storing it to report it lately - // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. - syncErr = multierror.Append(syncErr, rawErr) - } else { - processed.Insert(syncedRaw...) - } - } - - return processed.List(), syncErr.ErrorOrNil() -} - -// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: -// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, -// along adding the additional metadata, if required. -func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string, namespaces corev1.NamespaceList) ([]string, error) { - log := ctrllog.FromContext(ctx) - - errGroup := new(multierror.Group) - - var items []string - - for _, item := range namespaces.Items { - ns := item.GetName() - - errGroup.Go(func() (err error) { - actual, desired := obj.DeepCopy(), obj.DeepCopy() - // Using a deferred function to properly log the results, and adding the item to the processed set. - defer func() { - keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} - - if err != nil { - log.Error(err, "unable to replicate resource", keysAndValues...) - - return - } - - log.Info("resource has been replicated", keysAndValues...) - - replicatedItem := &capsulev1beta2.ObjectReferenceStatus{ - Name: obj.GetName(), - } - replicatedItem.Kind = obj.GetKind() - replicatedItem.Namespace = ns - replicatedItem.APIVersion = obj.GetAPIVersion() - - items = append(items, replicatedItem.String()) - }() - - actual.SetNamespace(ns) - - _, err = controllerutil.CreateOrUpdate(ctx, r.unstructuredClient, actual, func() error { - UID := actual.GetUID() - - actual.SetUnstructuredContent(desired.Object) - actual.SetNamespace(ns) - actual.SetLabels(labels) - actual.SetAnnotations(annotations) - actual.SetResourceVersion("") - actual.SetUID(UID) - - return nil - }) - - return - }) - } - // Wait returns *multierror.Error that implements stdlib error: - // the nil check must be performed down here rather than at the caller level to avoid wrong casting. - if err := errGroup.Wait(); err != nil { - return items, err - } - - return items, nil -} diff --git a/go.mod b/go.mod index 16b7865c..daf6c332 100644 --- a/go.mod +++ b/go.mod @@ -3,68 +3,81 @@ module github.com/clastix/capsule go 1.18 require ( - github.com/go-logr/logr v0.4.0 + github.com/go-logr/logr v1.2.0 github.com/hashicorp/go-multierror v1.1.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.14.0 + github.com/onsi/gomega v1.18.1 github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.7.0 - go.uber.org/zap v1.18.1 + github.com/stretchr/testify v1.7.1 + go.uber.org/zap v1.19.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - k8s.io/api v0.22.0 - k8s.io/apiextensions-apiserver v0.22.0 - k8s.io/apimachinery v0.22.0 - k8s.io/client-go v0.22.0 - k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 - sigs.k8s.io/controller-runtime v0.9.5 + k8s.io/api v0.24.2 + k8s.io/apiextensions-apiserver v0.24.2 + k8s.io/apimachinery v0.24.2 + k8s.io/client-go v0.24.2 + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 + sigs.k8s.io/cluster-api v1.2.4 + sigs.k8s.io/controller-runtime v0.12.3 ) require ( cloud.google.com/go v0.81.0 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/evanphx/json-patch v4.11.0+incompatible // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/go-logr/zapr v0.4.0 // indirect + github.com/emicklei/go-restful v2.15.0+incompatible // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/go-logr/zapr v1.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/gobuffalo/flect v0.2.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.5 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.2.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/json-iterator/go v1.1.11 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect - golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect + golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect + golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.26.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/component-base v0.22.0 // indirect - k8s.io/klog/v2 v2.9.0 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect + k8s.io/component-base v0.24.2 // indirect + k8s.io/klog/v2 v2.60.1 // indirect + k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect + sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 1972247b..6888b749 100644 --- a/go.sum +++ b/go.sum @@ -38,25 +38,26 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -64,9 +65,13 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e h1:GCzyKMDDjSGnlpl3clrdAK7I1AaVoaiKDOYkUzChZzg= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -77,13 +82,16 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -91,22 +99,21 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= +github.com/coredns/corefile-migration v1.0.17 h1:tNwh8+4WOANV6NjSljwgW7qViJfhvPUt1kosj4rR8yg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -114,30 +121,35 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -151,23 +163,23 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= +github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -175,7 +187,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -211,6 +223,11 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.10.1 h1:MQBGSZGnDwh7T/un+mzGKOMz3x+4E/GDPprWjDL+1Jg= +github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -221,11 +238,13 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -240,27 +259,21 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -280,13 +293,13 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -296,13 +309,14 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -314,28 +328,27 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -343,16 +356,20 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -363,21 +380,22 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= -github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -386,6 +404,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -394,8 +413,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -405,22 +425,25 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -434,48 +457,47 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= @@ -498,29 +520,35 @@ go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -544,7 +572,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -554,10 +581,10 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -576,7 +603,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -599,13 +625,17 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -617,8 +647,10 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -645,10 +677,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -673,7 +703,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -690,17 +719,21 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -711,13 +744,12 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -731,7 +763,6 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -775,12 +806,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= @@ -846,8 +877,8 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -857,6 +888,9 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -878,6 +912,7 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -889,15 +924,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -920,8 +956,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -931,49 +968,45 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= -k8s.io/api v0.22.0 h1:elCpMZ9UE8dLdYxr55E06TmSeji9I3KH494qH70/y+c= -k8s.io/api v0.22.0/go.mod h1:0AoXXqst47OI/L0oGKq9DG61dvGRPXs7X4/B7KyjBCU= -k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= -k8s.io/apiextensions-apiserver v0.22.0 h1:QTuZIQggaE7N8FTjur+1zxLmEPziphK7nNm8t+VNO3g= -k8s.io/apiextensions-apiserver v0.22.0/go.mod h1:+9w/QQC/lwH2qTbpqndXXjwBgidlSmytvIUww16UACE= -k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= -k8s.io/apimachinery v0.22.0 h1:CqH/BdNAzZl+sr3tc0D3VsK3u6ARVSo3GWyLmfIjbP0= -k8s.io/apimachinery v0.22.0/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= -k8s.io/apiserver v0.22.0/go.mod h1:04kaIEzIQrTGJ5syLppQWvpkLJXQtJECHmae+ZGc/nc= -k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= -k8s.io/client-go v0.22.0 h1:sD6o9O6tCwUKCENw8v+HFsuAbq2jCu8cWC61/ydwA50= -k8s.io/client-go v0.22.0/go.mod h1:GUjIuXR5PiEv/RVK5OODUsm6eZk7wtSWZSaSJbpFdGg= -k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= -k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= -k8s.io/component-base v0.22.0 h1:ZTmX8hUqH9T9gc0mM42O+KDgtwTYbVTt2MwmLP0eK8A= -k8s.io/component-base v0.22.0/go.mod h1:SXj6Z+V6P6GsBhHZVbWCw9hFjUdUYnJerlhhPnYCBCg= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQG4WsMej0WXaHxunmU= +k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= +k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= +k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= +k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= +k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= +k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apiserver v0.24.2 h1:orxipm5elPJSkkFNlwH9ClqaKEDJJA3yR2cAAlCnyj4= +k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= +k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= +k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= +k8s.io/cluster-bootstrap v0.24.0 h1:MTs2x3Vfcl/PWvB5bfX7gzTFRyi4ZSbNSQgGJTCb6Sw= +k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= +k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI= -k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.9.5 h1:WThcFE6cqctTn2jCZprLICO6BaKZfhsT37uAapTNfxc= -sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= +sigs.k8s.io/cluster-api v1.2.4 h1:wxfm/p8y+Q3qWVkkIPAIVqabA5lJVvqoRA02Nhup3uk= +sigs.k8s.io/cluster-api v1.2.4/go.mod h1:YaLJOC9mSsIOpdbh7BpthGmC8uxIJADzrMMIGpgahfM= +sigs.k8s.io/controller-runtime v0.12.3 h1:FCM8xeY/FI8hoAfh/V4XbbYMY20gElh9yh+A98usMio= +sigs.k8s.io/controller-runtime v0.12.3/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go index 900a2825..2d1b4d06 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,9 @@ import ( utilVersion "k8s.io/apimachinery/pkg/util/version" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -71,6 +73,27 @@ func printVersion() { setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH)) } +func newDelegatingClient(cache cache.Cache, config *rest.Config, options client.Options, uncachedObjects ...client.Object) (client.Client, error) { + cl, err := client.New(config, options) + if err != nil { + return nil, err + } + + delegatingClient, err := client.NewDelegatingClient( + client.NewDelegatingClientInput{ + Client: cl, + CacheReader: cache, + UncachedObjects: uncachedObjects, + CacheUnstructured: true, + }, + ) + if err != nil { + return nil, err + } + + return delegatingClient, nil +} + // nolint:maintidx func main() { var enableLeaderElection, version bool @@ -121,6 +144,7 @@ func main() { LeaderElection: enableLeaderElection, LeaderElectionID: "42c733ea.clastix.capsule.io", HealthProbeBindAddress: ":10080", + NewClient: newDelegatingClient, }) if err != nil { setupLog.Error(err, "unable to start manager") From a2f1dc7624941c5ccd8577ea49dc869f94299f40 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:29:30 -0400 Subject: [PATCH 21/26] refactor: deprecating tenant v1alpha1 version --- api/v1alpha1/tenant_types.go | 1 + config/crd/bases/capsule.clastix.io_tenants.yaml | 3 +++ config/install.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/api/v1alpha1/tenant_types.go b/api/v1alpha1/tenant_types.go index f8509f3f..e1a1e2a5 100644 --- a/api/v1alpha1/tenant_types.go +++ b/api/v1alpha1/tenant_types.go @@ -46,6 +46,7 @@ type TenantStatus struct { // +kubebuilder:printcolumn:name="Owner kind",type="string",JSONPath=".spec.owner.kind",description="The assigned Tenant owner kind" // +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age" +// +kubebuilder:deprecatedversion:warning="This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version." // Tenant is the Schema for the tenants API. type Tenant struct { diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 474977cf..9db86c24 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -42,6 +42,9 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version + of Capsule; please, migrate to v1beta2 version. name: v1alpha1 schema: openAPIV3Schema: diff --git a/config/install.yaml b/config/install.yaml index 89655950..e2fe2c56 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -615,6 +615,8 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version. name: v1alpha1 schema: openAPIV3Schema: From c7b6636ee8a8bc95930f586fee1f00111023fe27 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:42:21 -0400 Subject: [PATCH 22/26] style: kubebuilder annotations start with empty space --- api/v1alpha1/tenant_types.go | 2 +- api/v1beta1/namespace_options.go | 2 +- api/v1beta1/service_allowed_types.go | 6 +++--- api/v1beta1/tenant_status.go | 2 +- api/v1beta1/tenant_types.go | 8 ++++---- api/v1beta2/namespace_options.go | 2 +- api/v1beta2/tenant_status.go | 2 +- api/v1beta2/tenant_types.go | 6 +++--- api/v1beta2/tenantresource_global.go | 6 +++--- api/v1beta2/tenantresource_namespaced.go | 6 +++--- pkg/api/service_allowed_types.go | 6 +++--- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/api/v1alpha1/tenant_types.go b/api/v1alpha1/tenant_types.go index e1a1e2a5..e0b8c884 100644 --- a/api/v1alpha1/tenant_types.go +++ b/api/v1alpha1/tenant_types.go @@ -15,7 +15,7 @@ import ( type TenantSpec struct { Owner OwnerSpec `json:"owner"` - //+kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Minimum=1 NamespaceQuota *int32 `json:"namespaceQuota,omitempty"` NamespacesMetadata *AdditionalMetadata `json:"namespacesMetadata,omitempty"` ServicesMetadata *AdditionalMetadata `json:"servicesMetadata,omitempty"` diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index 88b45a28..0d7a564a 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -10,7 +10,7 @@ import ( ) type NamespaceOptions struct { - //+kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. diff --git a/api/v1beta1/service_allowed_types.go b/api/v1beta1/service_allowed_types.go index 38e692b5..3f57c648 100644 --- a/api/v1beta1/service_allowed_types.go +++ b/api/v1beta1/service_allowed_types.go @@ -4,13 +4,13 @@ package v1beta1 type AllowedServices struct { - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. NodePort *bool `json:"nodePort,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. ExternalName *bool `json:"externalName,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. LoadBalancer *bool `json:"loadBalancer,omitempty"` } diff --git a/api/v1beta1/tenant_status.go b/api/v1beta1/tenant_status.go index ef248080..8122633d 100644 --- a/api/v1beta1/tenant_status.go +++ b/api/v1beta1/tenant_status.go @@ -13,7 +13,7 @@ const ( // Returns the observed state of the Tenant. type TenantStatus struct { - //+kubebuilder:default=Active + // +kubebuilder:default=Active // The operational state of the Tenant. Possible values are "Active", "Cordoned". State tenantState `json:"state"` // How many namespaces are assigned to the Tenant. diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 009a6f1a..72fa6c89 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -39,9 +39,9 @@ type TenantSpec struct { PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status -//+kubebuilder:storageversion +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:storageversion // +kubebuilder:resource:scope=Cluster,shortName=tnt // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" // +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created" @@ -60,7 +60,7 @@ type Tenant struct { func (in *Tenant) Hub() {} -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // TenantList contains a list of Tenant. type TenantList struct { diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go index a76b79a5..5be847c9 100644 --- a/api/v1beta2/namespace_options.go +++ b/api/v1beta2/namespace_options.go @@ -8,7 +8,7 @@ import ( ) type NamespaceOptions struct { - //+kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go index de6b44dc..e3204ed1 100644 --- a/api/v1beta2/tenant_status.go +++ b/api/v1beta2/tenant_status.go @@ -13,7 +13,7 @@ const ( // Returns the observed state of the Tenant. type TenantStatus struct { - //+kubebuilder:default=Active + // +kubebuilder:default=Active // The operational state of the Tenant. Possible values are "Active", "Cordoned". State tenantState `json:"state"` // How many namespaces are assigned to the Tenant. diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index 04f8fa2c..f6960412 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -44,8 +44,8 @@ type TenantSpec struct { PreventDeletion bool `json:"preventDeletion,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,shortName=tnt // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" // +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created" @@ -72,7 +72,7 @@ func (in *Tenant) GetNamespaces() (res []string) { return } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // TenantList contains a list of Tenant. type TenantList struct { diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go index fd918fa6..1feced99 100644 --- a/api/v1beta2/tenantresource_global.go +++ b/api/v1beta2/tenantresource_global.go @@ -34,8 +34,8 @@ func (p *ProcessedItems) AsSet() sets.String { return set } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster // GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. @@ -47,7 +47,7 @@ type GlobalTenantResource struct { Status GlobalTenantResourceStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // GlobalTenantResourceList contains a list of GlobalTenantResource. type GlobalTenantResourceList struct { diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go index 3d19a952..ac6c764c 100644 --- a/api/v1beta2/tenantresource_namespaced.go +++ b/api/v1beta2/tenantresource_namespaced.go @@ -49,8 +49,8 @@ type TenantResourceStatus struct { ProcessedItems ProcessedItems `json:"processedItems"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. // The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. @@ -63,7 +63,7 @@ type TenantResource struct { Status TenantResourceStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // TenantResourceList contains a list of TenantResource. type TenantResourceList struct { diff --git a/pkg/api/service_allowed_types.go b/pkg/api/service_allowed_types.go index 3f8369a4..e1e604e2 100644 --- a/pkg/api/service_allowed_types.go +++ b/pkg/api/service_allowed_types.go @@ -6,13 +6,13 @@ package api // +kubebuilder:object:generate=true type AllowedServices struct { - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. NodePort *bool `json:"nodePort,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. ExternalName *bool `json:"externalName,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. LoadBalancer *bool `json:"loadBalancer,omitempty"` } From 4f71650d94eab31774d6549b9a5db832e8c24032 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:57:54 +0200 Subject: [PATCH 23/26] chore(helm)!: release of v0.2.0 --- charts/capsule/Chart.yaml | 2 +- charts/capsule/crds/tenant-crd.yaml | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 54894323..44db37f6 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.1.11 +version: 0.2.0 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index 5b36f41e..d018eabf 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -54,6 +54,8 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version. name: v1alpha1 schema: openAPIV3Schema: @@ -211,11 +213,11 @@ spec: type: integer namespacesMetadata: properties: - annotations: + additionalAnnotations: additionalProperties: type: string type: object - labels: + additionalLabels: additionalProperties: type: string type: object @@ -554,11 +556,11 @@ spec: type: array servicesMetadata: properties: - annotations: + additionalAnnotations: additionalProperties: type: string type: object - labels: + additionalLabels: additionalProperties: type: string type: object From f0032e45990e5cb45d53bd7bf130c581e92c7b13 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 19 Nov 2022 12:55:28 +0100 Subject: [PATCH 24/26] feat: support for kubernetes 1.25 --- controllers/servicelabels/endpoint_slices.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/servicelabels/endpoint_slices.go b/controllers/servicelabels/endpoint_slices.go index c73ff6cf..32a43f9b 100644 --- a/controllers/servicelabels/endpoint_slices.go +++ b/controllers/servicelabels/endpoint_slices.go @@ -30,10 +30,10 @@ func (r *EndpointSlicesLabelsReconciler) SetupWithManager(ctx context.Context, m r.Log.Info("Skipping controller setup, as EndpointSlices are not supported on current kubernetes version", "VersionMajor", r.VersionMajor, "VersionMinor", r.VersionMinor) return nil - case r.VersionMajor == 1 && r.VersionMinor >= 21: - r.abstractServiceLabelsReconciler.obj = &discoveryv1.EndpointSlice{} - default: + case r.VersionMajor == 1 && r.VersionMinor < 25: r.abstractServiceLabelsReconciler.obj = &discoveryv1beta1.EndpointSlice{} + default: + r.abstractServiceLabelsReconciler.obj = &discoveryv1.EndpointSlice{} } return ctrl.NewControllerManagedBy(mgr). From a1910fa3b5e83754dd249c4e80a05c079bec3ba6 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 19 Nov 2022 13:01:51 +0100 Subject: [PATCH 25/26] test: support for endpointslice/v1 for k8s v1.25 --- e2e/service_metadata_test.go | 77 +++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 99a1ec77..9038247c 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -9,11 +9,13 @@ import ( "context" "errors" "fmt" + discoveryv1 "k8s.io/api/discovery/v1" + discoveryv1beta1 "k8s.io/api/discovery/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - discoveryv1beta1 "k8s.io/api/discovery/v1beta1" networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,7 +39,7 @@ var _ = Describe("adding metadata to Service objects", func() { Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ + ServiceOptions: &api.ServiceOptions{ AdditionalMetadata: &api.AdditionalMetadataSpec{ Labels: map[string]string{ "k8s.io/custom-label": "foo", @@ -218,7 +220,7 @@ var _ = Describe("adding metadata to Service objects", func() { }) }) - It("should apply them to EndpointSlice", func() { + It("should apply them to EndpointSlice in v1", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { missingAPIError := &meta.NoKindMatchError{} if errors.As(err, &missingAPIError) { @@ -230,24 +232,6 @@ var _ = Describe("adding metadata to Service objects", func() { NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) - eps := &discoveryv1beta1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Name: "endpointslice-metadata", - Namespace: ns.GetName(), - }, - AddressType: discoveryv1beta1.AddressTypeIPv4, - Endpoints: []discoveryv1beta1.Endpoint{ - { - Addresses: []string{"10.10.1.1"}, - }, - }, - Ports: []discoveryv1beta1.EndpointPort{ - { - Name: pointer.StringPtr("foo"), - Port: pointer.Int32Ptr(9999), - }, - }, - } // Waiting for the reconciliation of required RBAC EventuallyCreation(func() (err error) { pod := &corev1.Pod{ @@ -268,6 +252,53 @@ var _ = Describe("adding metadata to Service objects", func() { return }).Should(Succeed()) + var eps client.Object + + if err := k8sClient.List(context.Background(), &discoveryv1.EndpointSliceList{}); err != nil { + missingAPIError := &meta.NoKindMatchError{} + if errors.As(err, &missingAPIError) { + Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) + } + + eps = &discoveryv1beta1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpointslice-metadata", + Namespace: ns.GetName(), + }, + AddressType: discoveryv1beta1.AddressTypeIPv4, + Endpoints: []discoveryv1beta1.Endpoint{ + { + Addresses: []string{"10.10.1.1"}, + }, + }, + Ports: []discoveryv1beta1.EndpointPort{ + { + Name: pointer.StringPtr("foo"), + Port: pointer.Int32Ptr(9999), + }, + }, + } + } else { + eps = &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpointslice-metadata", + Namespace: ns.GetName(), + }, + AddressType: discoveryv1.AddressTypeIPv4, + Endpoints: []discoveryv1.Endpoint{ + { + Addresses: []string{"10.10.1.1"}, + }, + }, + Ports: []discoveryv1.EndpointPort{ + { + Name: pointer.StringPtr("foo"), + Port: pointer.Int32Ptr(9999), + }, + }, + } + } + EventuallyCreation(func() (err error) { return k8sClient.Create(context.TODO(), eps) }).Should(Succeed()) @@ -276,7 +307,7 @@ var _ = Describe("adding metadata to Service objects", func() { Eventually(func() (ok bool) { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed()) for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.Annotations { - ok, _ = HaveKeyWithValue(k, v).Match(eps.Annotations) + ok, _ = HaveKeyWithValue(k, v).Match(eps.GetAnnotations()) if !ok { return false } @@ -289,7 +320,7 @@ var _ = Describe("adding metadata to Service objects", func() { Eventually(func() (ok bool) { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed()) for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.Labels { - ok, _ = HaveKeyWithValue(k, v).Match(eps.Labels) + ok, _ = HaveKeyWithValue(k, v).Match(eps.GetLabels()) if !ok { return false } From f2319c061b407ae9625e31e98efd101bac6866e6 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 19 Nov 2022 13:04:29 +0100 Subject: [PATCH 26/26] fix(makefile): avoid race condition for local kind cluster --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c496a090..b2d08df0 100644 --- a/Makefile +++ b/Makefile @@ -226,7 +226,7 @@ e2e/%: ginkgo $(MAKE) e2e-build/$* && $(MAKE) e2e-exec || $(MAKE) e2e-destroy e2e-build/%: - kind create cluster --name capsule --image=kindest/node:$* + kind create cluster --wait=60s --name capsule --image=kindest/node:$* make docker-build kind load docker-image --nodes capsule-control-plane --name capsule $(IMG) helm upgrade \ @@ -247,4 +247,3 @@ e2e-exec: e2e-destroy: kind delete cluster --name capsule -
NameTypeDescriptionRequired
kindenum + Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
+
+ Enum: User, Group, ServiceAccount
+
true
namestring + Name of tenant owner.
+
true
clusterRoles[]string + Defines additional cluster-roles for the specific Owner.
+
+ Default: [admin capsule-namespace-deleter]
+
false
proxySettings []object