From c44aba2236d08a223eace7dc814f07a3612573d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Tue, 28 Nov 2023 23:02:02 +0100 Subject: [PATCH] feat(tenant): add label with tenant name for each tenant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- api/v1beta2/owner.go | 1 + controllers/tenant/manager.go | 7 ++++ controllers/tenant/metadata.go | 23 ++++++++++++ e2e/tenant_metadata.go | 67 ++++++++++++++++++++++++++++++++++ main.go | 2 +- pkg/api/metadata_const.go | 8 ++++ pkg/webhook/tenant/metadata.go | 57 +++++++++++++++++++++++++++++ 7 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 controllers/tenant/metadata.go create mode 100644 e2e/tenant_metadata.go create mode 100644 pkg/api/metadata_const.go create mode 100644 pkg/webhook/tenant/metadata.go diff --git a/api/v1beta2/owner.go b/api/v1beta2/owner.go index 5e6845f5..fd746853 100644 --- a/api/v1beta2/owner.go +++ b/api/v1beta2/owner.go @@ -48,6 +48,7 @@ const ( PriorityClassesProxy ProxyServiceKind = "PriorityClasses" RuntimeClassesProxy ProxyServiceKind = "RuntimeClasses" PersistentVolumesProxy ProxyServiceKind = "PersistentVolumes" + TenantProxy ProxyServiceKind = "Tenant" ListOperation ProxyOperation = "List" UpdateOperation ProxyOperation = "Update" diff --git a/controllers/tenant/manager.go b/controllers/tenant/manager.go index 56eb6d72..5bc426c2 100644 --- a/controllers/tenant/manager.go +++ b/controllers/tenant/manager.go @@ -60,6 +60,13 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct return } + // Ensuring Metadata + if err = r.ensureMetadata(ctx, instance); err != nil { + r.Log.Error(err, "Cannot ensure metadata") + + return + } + // Ensuring ResourceQuota r.Log.Info("Ensuring limit resources count is updated") diff --git a/controllers/tenant/metadata.go b/controllers/tenant/metadata.go new file mode 100644 index 00000000..f64bf919 --- /dev/null +++ b/controllers/tenant/metadata.go @@ -0,0 +1,23 @@ +// Copyright 2020-2023 Project Capsule Authors. +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "context" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsuleapi "github.com/projectcapsule/capsule/pkg/api" +) + +// Sets a label on the Tenant object with it's name. +func (r *Manager) ensureMetadata(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) { + // Assign Labels + if tnt.Labels == nil { + tnt.Labels = make(map[string]string) + } + + tnt.Labels[capsuleapi.TenantNameLabel] = tnt.Name + + return r.Client.Update(ctx, tnt) +} diff --git a/e2e/tenant_metadata.go b/e2e/tenant_metadata.go new file mode 100644 index 00000000..4ae02435 --- /dev/null +++ b/e2e/tenant_metadata.go @@ -0,0 +1,67 @@ +//go:build e2e + +// Copyright 2020-2023 Project Capsule Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" +) + +func getLabels(tnt capsulev1beta2.Tenant) (map[string]string, error) { + current := &capsulev1beta2.Tenant{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, current) + if err != nil { + return nil, err + } + return current.GetLabels(), nil +} + +var _ = Describe("adding metadata to a Tenant", func() { + tnt := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-metadata", + Labels: map[string]string{ + "custom-label": "test", + }, + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ + { + Name: "jim", + Kind: "User", + }, + }, + }, + } + JustBeforeEach(func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + }) + + JustAfterEach(func() { + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + }) + + It("Should ensure label metadata", func() { + By("Default labels", func() { + currentlabels, _ := getLabels(*tnt) + Expect(currentlabels["kubernetes.io/metadata.name"]).To(Equal("tenant-metadata")) + Expect(currentlabels["custom-label"]).To(Equal("test")) + }) + By("Disallow name overwritte", func() { + tnt.Labels["kubernetes.io/metadata.name"] = "evil" + Expect(k8sClient.Update(context.TODO(), tnt)).ShouldNot(Succeed()) + }) + + }) +}) diff --git a/main.go b/main.go index 733adbc7..c525801b 100644 --- a/main.go +++ b/main.go @@ -225,7 +225,7 @@ func main() { route.Service(service.Handler()), route.TenantResourceObjects(utils.InCapsuleGroups(cfg, tntresource.WriteOpsHandler())), route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), - route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler()), + route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler(), tenant.MetaHandler()), route.OwnerReference(utils.InCapsuleGroups(cfg, ownerreference.Handler(cfg))), route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler(manager.GetClient())), route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))), diff --git a/pkg/api/metadata_const.go b/pkg/api/metadata_const.go new file mode 100644 index 00000000..110fd993 --- /dev/null +++ b/pkg/api/metadata_const.go @@ -0,0 +1,8 @@ +// Copyright 2020-2023 Project Capsule Authors. +// SPDX-License-Identifier: Apache-2.0 + +package api + +const ( + TenantNameLabel = "kubernetes.io/metadata.name" +) diff --git a/pkg/webhook/tenant/metadata.go b/pkg/webhook/tenant/metadata.go new file mode 100644 index 00000000..0bdd547b --- /dev/null +++ b/pkg/webhook/tenant/metadata.go @@ -0,0 +1,57 @@ +// Copyright 2020-2023 Project Capsule Authors. +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "context" + "fmt" + + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsuleapi "github.com/projectcapsule/capsule/pkg/api" + capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + "github.com/projectcapsule/capsule/pkg/webhook/utils" +) + +type metaHandler struct{} + +func MetaHandler() capsulewebhook.Handler { + return &metaHandler{} +} + +func (h *metaHandler) OnCreate(_ client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} + +func (h *metaHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} + +func (h *metaHandler) OnUpdate(_ client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tenant := &capsulev1beta2.Tenant{} + if err := decoder.Decode(req, tenant); err != nil { + return utils.ErroredResponse(err) + } + + if tenant.Labels != nil { + if tenant.Labels[capsuleapi.TenantNameLabel] != "" { + if tenant.Labels[capsuleapi.TenantNameLabel] != tenant.Name { + response := admission.Denied(fmt.Sprintf("tenant label '%s' is immutable", capsuleapi.TenantNameLabel)) + + return &response + } + } + } + + return nil + } +}