From 2096532f5dcd9fd7d1e162d50a82f64db654f9d8 Mon Sep 17 00:00:00 2001 From: stefanprodan Date: Tue, 15 Sep 2020 16:36:21 +0300 Subject: [PATCH] Add create tenant command --- cmd/gotk/create_tenant.go | 201 +++++++++++++++++++++++++++++++++ cmd/gotk/utils.go | 2 + docs/cmd/gotk_create.md | 1 + docs/cmd/gotk_create_tenant.md | 36 ++++++ mkdocs.yml | 1 + 5 files changed, 241 insertions(+) create mode 100644 cmd/gotk/create_tenant.go create mode 100644 docs/cmd/gotk_create_tenant.md diff --git a/cmd/gotk/create_tenant.go b/cmd/gotk/create_tenant.go new file mode 100644 index 00000000..7345e006 --- /dev/null +++ b/cmd/gotk/create_tenant.go @@ -0,0 +1,201 @@ +/* +Copyright 2020 The Flux CD contributors. + +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 ( + "bytes" + "context" + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +var createTenantCmd = &cobra.Command{ + Use: "tenant", + Short: "Create or update a tenant", + Long: ` +The create tenant command generates a namespace and a role binding to limit the +reconcilers scope to the tenant namespace.`, + RunE: createTenantCmdRun, +} + +func init() { + createCmd.AddCommand(createTenantCmd) +} + +func createTenantCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("tenant name is required") + } + tenant := args[0] + + objLabels, err := parseLabels() + if err != nil { + return err + } + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: tenant, + Labels: objLabels, + }, + } + + roleBinding := rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gotk-reconciler", + Namespace: tenant, + Labels: objLabels, + }, + Subjects: []rbacv1.Subject{ + { + APIGroup: "rbac.authorization.k8s.io", + Kind: "User", + Name: fmt.Sprintf("gotk:%s:reconciler", tenant), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "cluster-admin", + }, + } + + if export { + return exportTenant(namespace, roleBinding) + } + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.kubeClient(kubeconfig) + if err != nil { + return err + } + + logger.Actionf("applying namespace %s", namespace.Name) + if err := upsertNamespace(ctx, kubeClient, namespace); err != nil { + return err + } + + logger.Actionf("applying role binding %s", roleBinding.Name) + if err := upsertRoleBinding(ctx, kubeClient, roleBinding); err != nil { + return err + } + + logger.Successf("tenant setup completed") + return nil +} + +func upsertNamespace(ctx context.Context, kubeClient client.Client, namespace corev1.Namespace) error { + namespacedName := types.NamespacedName{ + Namespace: namespace.GetNamespace(), + Name: namespace.GetName(), + } + + var existing corev1.Namespace + err := kubeClient.Get(ctx, namespacedName, &existing) + if err != nil { + if errors.IsNotFound(err) { + if err := kubeClient.Create(ctx, &namespace); err != nil { + return err + } else { + return nil + } + } + return err + } + + if !equality.Semantic.DeepDerivative(namespace.Labels, existing.Labels) { + existing.Labels = namespace.Labels + if err := kubeClient.Update(ctx, &existing); err != nil { + return err + } + } + + return nil +} + +func upsertRoleBinding(ctx context.Context, kubeClient client.Client, roleBinding rbacv1.RoleBinding) error { + namespacedName := types.NamespacedName{ + Namespace: roleBinding.GetNamespace(), + Name: roleBinding.GetName(), + } + + var existing rbacv1.RoleBinding + err := kubeClient.Get(ctx, namespacedName, &existing) + if err != nil { + if errors.IsNotFound(err) { + if err := kubeClient.Create(ctx, &roleBinding); err != nil { + return err + } else { + return nil + } + } + return err + } + + if !equality.Semantic.DeepDerivative(roleBinding.Subjects, existing.Subjects) || + !equality.Semantic.DeepDerivative(roleBinding.RoleRef, existing.RoleRef) || + !equality.Semantic.DeepDerivative(roleBinding.Labels, existing.Labels) { + if err := kubeClient.Delete(ctx, &existing); err != nil { + return err + } + if err := kubeClient.Create(ctx, &roleBinding); err != nil { + return err + } + } + + return nil +} + +func exportTenant(namespace corev1.Namespace, roleBinding rbacv1.RoleBinding) error { + namespace.TypeMeta = metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Namespace", + } + data, err := yaml.Marshal(namespace) + if err != nil { + return err + } + + fmt.Println("---") + data = bytes.Replace(data, []byte("spec: {}\n"), []byte(""), 1) + fmt.Println(resourceToString(data)) + + roleBinding.TypeMeta = metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "RoleBinding", + } + data, err = yaml.Marshal(roleBinding) + if err != nil { + return err + } + + fmt.Println("---") + fmt.Println(resourceToString(data)) + + return nil +} diff --git a/cmd/gotk/utils.go b/cmd/gotk/utils.go index e1dae097..16d0d6bf 100644 --- a/cmd/gotk/utils.go +++ b/cmd/gotk/utils.go @@ -27,6 +27,7 @@ import ( "text/template" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" @@ -118,6 +119,7 @@ func (*Utils) kubeClient(config string) (client.Client, error) { scheme := runtime.NewScheme() _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) _ = sourcev1.AddToScheme(scheme) _ = kustomizev1.AddToScheme(scheme) _ = helmv2.AddToScheme(scheme) diff --git a/docs/cmd/gotk_create.md b/docs/cmd/gotk_create.md index f16fe7d0..c96baf9a 100644 --- a/docs/cmd/gotk_create.md +++ b/docs/cmd/gotk_create.md @@ -30,4 +30,5 @@ The create sub-commands generate sources and resources. * [gotk create helmrelease](gotk_create_helmrelease.md) - Create or update a HelmRelease resource * [gotk create kustomization](gotk_create_kustomization.md) - Create or update a Kustomization resource * [gotk create source](gotk_create_source.md) - Create or update sources +* [gotk create tenant](gotk_create_tenant.md) - Create or update a tenant diff --git a/docs/cmd/gotk_create_tenant.md b/docs/cmd/gotk_create_tenant.md new file mode 100644 index 00000000..82d75550 --- /dev/null +++ b/docs/cmd/gotk_create_tenant.md @@ -0,0 +1,36 @@ +## gotk create tenant + +Create or update a tenant + +### Synopsis + + +The create tenant command generates a namespace and a role binding to limit the +reconcilers scope to the tenant namespace. + +``` +gotk create tenant [flags] +``` + +### Options + +``` + -h, --help help for tenant +``` + +### Options inherited from parent commands + +``` + --export export in YAML format to stdout + --interval duration source sync interval (default 1m0s) + --kubeconfig string path to the kubeconfig file (default "~/.kube/config") + --label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2) + --namespace string the namespace scope for this operation (default "gitops-system") + --timeout duration timeout for this operation (default 5m0s) + --verbose print generated objects +``` + +### SEE ALSO + +* [gotk create](gotk_create.md) - Create or update sources and resources + diff --git a/mkdocs.yml b/mkdocs.yml index ef9c36cf..6053cdc9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,6 +85,7 @@ nav: - Create source: cmd/gotk_create_source.md - Create source git: cmd/gotk_create_source_git.md - Create source helm: cmd/gotk_create_source_helm.md + - Create tenant: cmd/gotk_create_tenant.md - Delete: cmd/gotk_delete.md - Delete kustomization: cmd/gotk_delete_kustomization.md - Delete helmrelease: cmd/gotk_delete_helmrelease.md