diff --git a/.generated_docs b/.generated_docs index a5895bc781..1d24a4e88a 100644 --- a/.generated_docs +++ b/.generated_docs @@ -43,6 +43,7 @@ docs/man/man1/kubectl-create-deployment.1 docs/man/man1/kubectl-create-namespace.1 docs/man/man1/kubectl-create-poddisruptionbudget.1 docs/man/man1/kubectl-create-quota.1 +docs/man/man1/kubectl-create-rolebinding.1 docs/man/man1/kubectl-create-secret-docker-registry.1 docs/man/man1/kubectl-create-secret-generic.1 docs/man/man1/kubectl-create-secret-tls.1 @@ -126,6 +127,7 @@ docs/user-guide/kubectl/kubectl_create_deployment.md docs/user-guide/kubectl/kubectl_create_namespace.md docs/user-guide/kubectl/kubectl_create_poddisruptionbudget.md docs/user-guide/kubectl/kubectl_create_quota.md +docs/user-guide/kubectl/kubectl_create_rolebinding.md docs/user-guide/kubectl/kubectl_create_secret.md docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md docs/user-guide/kubectl/kubectl_create_secret_generic.md diff --git a/docs/man/man1/kubectl-create-rolebinding.1 b/docs/man/man1/kubectl-create-rolebinding.1 new file mode 100644 index 0000000000..b6fd7a0f98 --- /dev/null +++ b/docs/man/man1/kubectl-create-rolebinding.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_create_rolebinding.md b/docs/user-guide/kubectl/kubectl_create_rolebinding.md new file mode 100644 index 0000000000..fe154d447b --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_rolebinding.md @@ -0,0 +1,7 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_rolebinding.md?pixel)]() + diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index aceadb4c6a..bf29c76693 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -426,6 +426,13 @@ runTests() { kube::test::get_object_assert clusterrolebindings/cluster-admin "{{.metadata.name}}" 'cluster-admin' kubectl create "${kube_flags[@]}" clusterrolebinding super-admin --clusterrole=admin --user=super-admin kube::test::get_object_assert clusterrolebinding/super-admin "{{range.subjects}}{{.name}}:{{end}}" 'super-admin:' + kubectl create "${kube_flags[@]}" rolebinding admin --clusterrole=admin --user=default-admin -n default + kube::test::get_object_assert rolebinding/admin "{{range.subjects}}{{.name}}:{{end}}" 'default-admin:' + kubectl create "${kube_flags[@]}" rolebinding localrole --role=localrole --group=the-group -n default + kube::test::get_object_assert rolebinding/localrole "{{range.subjects}}{{.name}}:{{end}}" 'the-group:' + kubectl create "${kube_flags[@]}" rolebinding sarole --role=localrole --serviceaccount=otherns:sa-name -n default + kube::test::get_object_assert rolebinding/sarole "{{range.subjects}}{{.namespace}}:{{end}}" 'otherns:' + kube::test::get_object_assert rolebinding/sarole "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:' ########################### # POD creation / deletion # diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index 6afa560be6..83516cf2e8 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -32,6 +32,7 @@ go_library( "quota.go", "resource_filter.go", "resource_printer.go", + "rolebinding.go", "rollback.go", "rolling_updater.go", "rollout_status.go", diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 3ed3f63334..f21ee861f1 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -30,6 +30,7 @@ go_library( "create_namespace.go", "create_pdb.go", "create_quota.go", + "create_rolebinding.go", "create_secret.go", "create_service.go", "create_serviceaccount.go", diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index e4d6272f52..e7f5a7baea 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -88,6 +88,7 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { cmd.AddCommand(NewCmdCreateService(f, out, errOut)) cmd.AddCommand(NewCmdCreateDeployment(f, out)) cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, out)) + cmd.AddCommand(NewCmdCreateRoleBinding(f, out)) cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, out)) return cmd } diff --git a/pkg/kubectl/cmd/create_rolebinding.go b/pkg/kubectl/cmd/create_rolebinding.go new file mode 100644 index 0000000000..639c5a160a --- /dev/null +++ b/pkg/kubectl/cmd/create_rolebinding.go @@ -0,0 +1,88 @@ +/* +Copyright 2016 The Kubernetes 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 cmd + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +var ( + roleBindingLong = templates.LongDesc(` + Create a RoleBinding for a particular Role or ClusterRole.`) + + roleBindingExample = templates.Examples(` + # Create a RoleBinding for user1, user2, and group1 using the admin ClusterRole + kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1`) +) + +// RoleBinding is a command to ease creating RoleBindings. +func NewCmdCreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname] [--dry-run]", + Short: "Create a RoleBinding for a particular Role or ClusterRole", + Long: roleBindingLong, + Example: roleBindingExample, + Run: func(cmd *cobra.Command, args []string) { + err := CreateRoleBinding(f, cmdOut, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.RoleBindingV1GeneratorName) + cmd.Flags().String("clusterrole", "", "ClusterRole this RoleBinding should reference") + cmd.Flags().String("role", "", "Role this RoleBinding should reference") + cmd.Flags().StringSlice("user", []string{}, "usernames to bind to the role") + cmd.Flags().StringSlice("group", []string{}, "groups to bind to the role") + cmd.Flags().StringSlice("serviceaccount", []string{}, "service accounts to bind to the role") + return cmd +} + +func CreateRoleBinding(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.RoleBindingV1GeneratorName: + generator = &kubectl.RoleBindingGeneratorV1{ + Name: name, + ClusterRole: cmdutil.GetFlagString(cmd, "clusterrole"), + Role: cmdutil.GetFlagString(cmd, "role"), + Users: cmdutil.GetFlagStringSlice(cmd, "user"), + Groups: cmdutil.GetFlagStringSlice(cmd, "group"), + ServiceAccounts: cmdutil.GetFlagStringSlice(cmd, "serviceaccount"), + } + default: + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName)) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetDryRunFlag(cmd), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index 31dc8d1931..090565ab20 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -456,6 +456,7 @@ const ( SecretForTLSV1GeneratorName = "secret-for-tls/v1" ConfigMapV1GeneratorName = "configmap/v1" ClusterRoleBindingV1GeneratorName = "clusterrolebinding.rbac.authorization.k8s.io/v1alpha1" + RoleBindingV1GeneratorName = "rolebinding.rbac.authorization.k8s.io/v1alpha1" ClusterV1Beta1GeneratorName = "cluster/v1beta1" PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1" ) diff --git a/pkg/kubectl/rolebinding.go b/pkg/kubectl/rolebinding.go new file mode 100644 index 0000000000..000a5f43e7 --- /dev/null +++ b/pkg/kubectl/rolebinding.go @@ -0,0 +1,172 @@ +/* +Copyright 2016 The Kubernetes 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 kubectl + +import ( + "fmt" + + "strings" + + "k8s.io/kubernetes/pkg/apis/rbac" + "k8s.io/kubernetes/pkg/runtime" +) + +// RoleBindingGeneratorV1 supports stable generation of a roleBinding. +type RoleBindingGeneratorV1 struct { + // Name of roleBinding (required) + Name string + // ClusterRole for the roleBinding + ClusterRole string + // Role for the roleBinding + Role string + // Users to derive the roleBinding from (optional) + Users []string + // Groups to derive the roleBinding from (optional) + Groups []string + // ServiceAccounts to derive the roleBinding from in namespace:name format(optional) + ServiceAccounts []string +} + +// Ensure it supports the generator pattern that uses parameter injection. +var _ Generator = &RoleBindingGeneratorV1{} + +// Ensure it supports the generator pattern that uses parameters specified during construction. +var _ StructuredGenerator = &RoleBindingGeneratorV1{} + +// Generate returns a roleBinding using the specified parameters. +func (s RoleBindingGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { + err := ValidateParams(s.ParamNames(), genericParams) + if err != nil { + return nil, err + } + delegate := &RoleBindingGeneratorV1{} + fromFileStrings, found := genericParams["user"] + if found { + fromFileArray, isArray := fromFileStrings.([]string) + if !isArray { + return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings) + } + delegate.Users = fromFileArray + delete(genericParams, "user") + } + fromLiteralStrings, found := genericParams["group"] + if found { + fromLiteralArray, isArray := fromLiteralStrings.([]string) + if !isArray { + return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings) + } + delegate.Groups = fromLiteralArray + delete(genericParams, "group") + } + fromSAStrings, found := genericParams["serviceaccount"] + if found { + fromLiteralArray, isArray := fromSAStrings.([]string) + if !isArray { + return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings) + } + delegate.ServiceAccounts = fromLiteralArray + delete(genericParams, "serviceaccounts") + } + params := map[string]string{} + for key, value := range genericParams { + strVal, isString := value.(string) + if !isString { + return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) + } + params[key] = strVal + } + delegate.Name = params["name"] + delegate.ClusterRole = params["clusterrole"] + delegate.Role = params["role"] + return delegate.StructuredGenerate() +} + +// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern. +func (s RoleBindingGeneratorV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"clusterrole", false}, + {"role", false}, + {"user", false}, + {"group", false}, + {"serviceaccount", false}, + {"force", false}, + } +} + +// StructuredGenerate outputs a roleBinding object using the configured fields. +func (s RoleBindingGeneratorV1) StructuredGenerate() (runtime.Object, error) { + if err := s.validate(); err != nil { + return nil, err + } + roleBinding := &rbac.RoleBinding{} + roleBinding.Name = s.Name + + switch { + case len(s.Role) > 0: + roleBinding.RoleRef = rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "Role", + Name: s.Role, + } + case len(s.ClusterRole) > 0: + roleBinding.RoleRef = rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "ClusterRole", + Name: s.ClusterRole, + } + } + + for _, user := range s.Users { + roleBinding.Subjects = append(roleBinding.Subjects, rbac.Subject{ + Kind: rbac.UserKind, + APIVersion: "rbac/v1alpha1", + Name: user, + }) + } + for _, group := range s.Groups { + roleBinding.Subjects = append(roleBinding.Subjects, rbac.Subject{ + Kind: rbac.GroupKind, + APIVersion: "rbac/v1alpha1", + Name: group, + }) + } + for _, sa := range s.ServiceAccounts { + tokens := strings.Split(sa, ":") + if len(tokens) != 2 { + return nil, fmt.Errorf("serviceaccount must be :") + } + roleBinding.Subjects = append(roleBinding.Subjects, rbac.Subject{ + Kind: rbac.ServiceAccountKind, + Namespace: tokens[0], + Name: tokens[1], + }) + } + + return roleBinding, nil +} + +// validate validates required fields are set to support structured generation. +func (s RoleBindingGeneratorV1) validate() error { + if len(s.Name) == 0 { + return fmt.Errorf("name must be specified") + } + if (len(s.ClusterRole) == 0) == (len(s.Role) == 0) { + return fmt.Errorf("exactly one of clusterrole or role must be specified") + } + return nil +}