From 9f29702f54abe6636b47098307eb6019900038db Mon Sep 17 00:00:00 2001 From: Stefan Bickel Date: Wed, 11 Jun 2025 16:25:19 +0200 Subject: [PATCH 1/3] Add cli arg --with-service-account Signed-off-by: Stefan Bickel --- cmd/flux/create_tenant.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cmd/flux/create_tenant.go b/cmd/flux/create_tenant.go index 1b071ecb..1c7c75bc 100644 --- a/cmd/flux/create_tenant.go +++ b/cmd/flux/create_tenant.go @@ -59,6 +59,7 @@ const ( type tenantFlags struct { namespaces []string clusterRole string + account string } var tenantArgs tenantFlags @@ -66,6 +67,7 @@ var tenantArgs tenantFlags func init() { createTenantCmd.Flags().StringSliceVar(&tenantArgs.namespaces, "with-namespace", nil, "namespace belonging to this tenant") createTenantCmd.Flags().StringVar(&tenantArgs.clusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding") + createTenantCmd.Flags().StringVar(&tenantArgs.account, "with-service-account", "", "service account belonging to this tenant") createCmd.AddCommand(createTenantCmd) } @@ -107,9 +109,17 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error { } namespaces = append(namespaces, namespace) + accountName := tenant + if tenantArgs.account != "" { + accountName = tenantArgs.account + } + if err := validation.IsQualifiedName(accountName); len(err) > 0 { + return fmt.Errorf("invalid service-account name '%s': %v", accountName, err) + } + account := corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: tenant, + Name: accountName, Namespace: ns, Labels: objLabels, }, @@ -131,7 +141,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error { }, { Kind: "ServiceAccount", - Name: tenant, + Name: accountName, Namespace: ns, }, }, From 00d0e1af252f7206de5a10ac968e170600868f50 Mon Sep 17 00:00:00 2001 From: Stefan Bickel Date: Wed, 11 Jun 2025 16:51:32 +0200 Subject: [PATCH 2/3] Add tests and golden files for create tenant Signed-off-by: Stefan Bickel --- cmd/flux/create_tenant_test.go | 68 +++++++++++++++++++ .../testdata/create_tenant/tenant-basic.yaml | 34 ++++++++++ .../tenant-with-cluster-role.yaml | 34 ++++++++++ .../tenant-with-service-account.yaml | 34 ++++++++++ 4 files changed, 170 insertions(+) create mode 100644 cmd/flux/create_tenant_test.go create mode 100644 cmd/flux/testdata/create_tenant/tenant-basic.yaml create mode 100644 cmd/flux/testdata/create_tenant/tenant-with-cluster-role.yaml create mode 100644 cmd/flux/testdata/create_tenant/tenant-with-service-account.yaml diff --git a/cmd/flux/create_tenant_test.go b/cmd/flux/create_tenant_test.go new file mode 100644 index 00000000..f4fadb8f --- /dev/null +++ b/cmd/flux/create_tenant_test.go @@ -0,0 +1,68 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "testing" +) + +func TestCreateTenant(t *testing.T) { + tests := []struct { + name string + args string + assert assertFunc + }{ + { + name: "no args", + args: "create tenant", + assert: assertError("name is required"), + }, + { + name: "no namespace", + args: "create tenant dev-team --cluster-role=cluster-admin", + assert: assertError("with-namespace is required"), + }, + { + name: "basic tenant", + args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --export", + assert: assertGoldenFile("./testdata/create_tenant/tenant-basic.yaml"), + }, + { + name: "tenant with custom serviceaccount", + args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --with-service-account=flux-tenant --export", + assert: assertGoldenFile("./testdata/create_tenant/tenant-with-service-account.yaml"), + }, + { + name: "tenant with custom cluster role", + args: "create tenant dev-team --with-namespace=apps --cluster-role=custom-role --export", + assert: assertGoldenFile("./testdata/create_tenant/tenant-with-cluster-role.yaml"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.args, + assert: tt.assert, + } + cmd.runTestCmd(t) + }) + } +} diff --git a/cmd/flux/testdata/create_tenant/tenant-basic.yaml b/cmd/flux/testdata/create_tenant/tenant-basic.yaml new file mode 100644 index 00000000..5bf0da1a --- /dev/null +++ b/cmd/flux/testdata/create_tenant/tenant-basic.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: apps +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: dev-team + namespace: apps +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: dev-team-reconciler + namespace: apps +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: User + name: gotk:apps:reconciler +- kind: ServiceAccount + name: dev-team + namespace: apps diff --git a/cmd/flux/testdata/create_tenant/tenant-with-cluster-role.yaml b/cmd/flux/testdata/create_tenant/tenant-with-cluster-role.yaml new file mode 100644 index 00000000..69b5b2c5 --- /dev/null +++ b/cmd/flux/testdata/create_tenant/tenant-with-cluster-role.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: apps +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: dev-team + namespace: apps +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: dev-team-reconciler + namespace: apps +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: custom-role +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: User + name: gotk:apps:reconciler +- kind: ServiceAccount + name: dev-team + namespace: apps diff --git a/cmd/flux/testdata/create_tenant/tenant-with-service-account.yaml b/cmd/flux/testdata/create_tenant/tenant-with-service-account.yaml new file mode 100644 index 00000000..50a3e8fc --- /dev/null +++ b/cmd/flux/testdata/create_tenant/tenant-with-service-account.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: apps +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: flux-tenant + namespace: apps +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + toolkit.fluxcd.io/tenant: dev-team + name: dev-team-reconciler + namespace: apps +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: User + name: gotk:apps:reconciler +- kind: ServiceAccount + name: flux-tenant + namespace: apps From 1d34e5355b1d69dc6b67b0242df082dc8d6c5cdf Mon Sep 17 00:00:00 2001 From: Stefan Bickel Date: Fri, 13 Jun 2025 12:37:08 +0200 Subject: [PATCH 3/3] Make golden tests pass Signed-off-by: Stefan Bickel --- cmd/flux/create_tenant.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/flux/create_tenant.go b/cmd/flux/create_tenant.go index 1c7c75bc..442f0f73 100644 --- a/cmd/flux/create_tenant.go +++ b/cmd/flux/create_tenant.go @@ -293,9 +293,9 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol return err } - fmt.Println("---") + rootCmd.Println("---") data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1) - fmt.Println(resourceToString(data)) + rootCmd.Println(resourceToString(data)) account.TypeMeta = metav1.TypeMeta{ APIVersion: "v1", @@ -306,9 +306,9 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol return err } - fmt.Println("---") + rootCmd.Println("---") data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1) - fmt.Println(resourceToString(data)) + rootCmd.Println(resourceToString(data)) roleBinding.TypeMeta = metav1.TypeMeta{ APIVersion: "rbac.authorization.k8s.io/v1", @@ -319,8 +319,8 @@ func exportTenant(namespace corev1.Namespace, account corev1.ServiceAccount, rol return err } - fmt.Println("---") - fmt.Println(resourceToString(data)) + rootCmd.Println("---") + rootCmd.Println(resourceToString(data)) return nil }