Merge pull request #273 from fluxcd/tenant-customization

Add support for multi-namespace tenant ownership
pull/279/head
Stefan Prodan 4 years ago committed by GitHub
commit 938edefea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
@ -36,12 +37,35 @@ var createTenantCmd = &cobra.Command{
Use: "tenant", Use: "tenant",
Short: "Create or update a tenant", Short: "Create or update a tenant",
Long: ` Long: `
The create tenant command generates a namespace and a role binding to limit the The create tenant command generates namespaces and role bindings to limit the
reconcilers scope to the tenant namespace.`, reconcilers scope to the tenant namespaces.`,
Example: ` # Create a tenant with access to a namespace
gotk create tenant dev-team \
--with-namespace=frontend \
--label=environment=dev
# Generate tenant namespaces and role bindings in YAML format
gotk create tenant dev-team \
--with-namespace=frontend \
--with-namespace=backend \
--export > dev-team.yaml
`,
RunE: createTenantCmdRun, RunE: createTenantCmdRun,
} }
const (
tenantLabel = "toolkit.fluxcd.io/tenant"
tenantRoleBinding = "gotk-reconciler"
)
var (
tenantNamespaces []string
tenantClusterRole string
)
func init() { func init() {
createTenantCmd.Flags().StringSliceVar(&tenantNamespaces, "with-namespace", nil, "namespace belonging to this tenant")
createTenantCmd.Flags().StringVar(&tenantClusterRole, "cluster-role", "cluster-admin", "cluster role of the tenant role binding")
createCmd.AddCommand(createTenantCmd) createCmd.AddCommand(createTenantCmd)
} }
@ -50,41 +74,70 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("tenant name is required") return fmt.Errorf("tenant name is required")
} }
tenant := args[0] tenant := args[0]
if err := validation.IsQualifiedName(tenant); len(err) > 0 {
return fmt.Errorf("invalid tenant name '%s': %v", tenant, err)
}
objLabels, err := parseLabels() if tenantClusterRole == "" {
if err != nil { return fmt.Errorf("cluster-role is required")
return err
} }
namespace := corev1.Namespace{ if tenantNamespaces == nil {
ObjectMeta: metav1.ObjectMeta{ return fmt.Errorf("with-namespace is required")
Name: tenant,
Labels: objLabels,
},
} }
roleBinding := rbacv1.RoleBinding{ var namespaces []corev1.Namespace
ObjectMeta: metav1.ObjectMeta{ var roleBindings []rbacv1.RoleBinding
Name: "gotk-reconciler",
Namespace: tenant, for _, ns := range tenantNamespaces {
Labels: objLabels, if err := validation.IsQualifiedName(ns); len(err) > 0 {
}, return fmt.Errorf("invalid namespace '%s': %v", ns, err)
Subjects: []rbacv1.Subject{ }
{
objLabels, err := parseLabels()
if err != nil {
return err
}
objLabels[tenantLabel] = tenant
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
Labels: objLabels,
},
}
namespaces = append(namespaces, namespace)
roleBinding := rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: tenantRoleBinding,
Namespace: ns,
Labels: objLabels,
},
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "User",
Name: fmt.Sprintf("gotk:%s:reconciler", ns),
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io", APIGroup: "rbac.authorization.k8s.io",
Kind: "User", Kind: "ClusterRole",
Name: fmt.Sprintf("gotk:%s:reconciler", tenant), Name: tenantClusterRole,
}, },
}, }
RoleRef: rbacv1.RoleRef{ roleBindings = append(roleBindings, roleBinding)
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "cluster-admin",
},
} }
if export { if export {
return exportTenant(namespace, roleBinding) for i, _ := range tenantNamespaces {
if err := exportTenant(namespaces[i], roleBindings[1]); err != nil {
return err
}
}
return nil
} }
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
@ -95,14 +148,16 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logger.Actionf("applying namespace %s", namespace.Name) for i, _ := range tenantNamespaces {
if err := upsertNamespace(ctx, kubeClient, namespace); err != nil { logger.Actionf("applying namespace %s", namespaces[i].Name)
return err if err := upsertNamespace(ctx, kubeClient, namespaces[i]); err != nil {
} return err
}
logger.Actionf("applying role binding %s", roleBinding.Name) logger.Actionf("applying role binding %s", roleBindings[i].Name)
if err := upsertRoleBinding(ctx, kubeClient, roleBinding); err != nil { if err := upsertRoleBinding(ctx, kubeClient, roleBindings[i]); err != nil {
return err return err
}
} }
logger.Successf("tenant setup completed") logger.Successf("tenant setup completed")

@ -5,17 +5,35 @@ Create or update a tenant
### Synopsis ### Synopsis
The create tenant command generates a namespace and a role binding to limit the The create tenant command generates namespaces and role bindings to limit the
reconcilers scope to the tenant namespace. reconcilers scope to the tenant namespaces.
``` ```
gotk create tenant [flags] gotk create tenant [flags]
``` ```
### Examples
```
# Create a tenant with access to a namespace
gotk create tenant dev-team \
--with-namespace=frontend \
--label=environment=dev
# Generate tenant namespaces and role bindings in YAML format
gotk create tenant dev-team \
--with-namespace=frontend \
--with-namespace=backend \
--export > dev-team.yaml
```
### Options ### Options
``` ```
-h, --help help for tenant --cluster-role string cluster role of the tenant role binding (default "cluster-admin")
-h, --help help for tenant
--with-namespace strings namespace belonging to this tenant
``` ```
### Options inherited from parent commands ### Options inherited from parent commands

Loading…
Cancel
Save