Added `kubectl create clusterrole` command.

Added `kubectl create clusterrole` command.
pull/6/head
Xing Zhou 2017-02-15 15:38:13 +08:00
parent 98d693e9d2
commit 433941f1fd
10 changed files with 355 additions and 60 deletions

View File

@ -37,6 +37,7 @@ docs/man/man1/kubectl-config.1
docs/man/man1/kubectl-convert.1
docs/man/man1/kubectl-cordon.1
docs/man/man1/kubectl-cp.1
docs/man/man1/kubectl-create-clusterrole.1
docs/man/man1/kubectl-create-clusterrolebinding.1
docs/man/man1/kubectl-create-configmap.1
docs/man/man1/kubectl-create-deployment.1
@ -122,6 +123,7 @@ docs/user-guide/kubectl/kubectl_convert.md
docs/user-guide/kubectl/kubectl_cordon.md
docs/user-guide/kubectl/kubectl_cp.md
docs/user-guide/kubectl/kubectl_create.md
docs/user-guide/kubectl/kubectl_create_clusterrole.md
docs/user-guide/kubectl/kubectl_create_clusterrolebinding.md
docs/user-guide/kubectl/kubectl_create_configmap.md
docs/user-guide/kubectl/kubectl_create_deployment.md

View File

@ -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.

View File

@ -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.

View File

@ -2803,6 +2803,23 @@ runTests() {
# make sure the server was properly bootstrapped with clusterroles and bindings
kube::test::get_object_assert clusterroles/cluster-admin "{{.metadata.name}}" 'cluster-admin'
kube::test::get_object_assert clusterrolebindings/cluster-admin "{{.metadata.name}}" 'cluster-admin'
# test `kubectl create clusterrole`
kubectl create "${kube_flags[@]}" clusterrole pod-admin --verb=* --resource=pods
kube::test::get_object_assert clusterrole/pod-admin "{{range.rules}}{{range.verbs}}{{.}}:{{end}}{{end}}" '\*:'
kube::test::get_object_assert clusterrole/pod-admin "{{range.rules}}{{range.resources}}{{.}}:{{end}}{{end}}" 'pods:'
kube::test::get_object_assert clusterrole/pod-admin "{{range.rules}}{{range.apiGroups}}{{.}}:{{end}}{{end}}" ':'
kubectl create "${kube_flags[@]}" clusterrole resource-reader --verb=get,list --resource=pods,deployments.extensions
kube::test::get_object_assert clusterrole/resource-reader "{{range.rules}}{{range.verbs}}{{.}}:{{end}}{{end}}" 'get:list:get:list:'
kube::test::get_object_assert clusterrole/resource-reader "{{range.rules}}{{range.resources}}{{.}}:{{end}}{{end}}" 'pods:deployments:'
kube::test::get_object_assert clusterrole/resource-reader "{{range.rules}}{{range.apiGroups}}{{.}}:{{end}}{{end}}" ':extensions:'
kubectl create "${kube_flags[@]}" clusterrole resourcename-reader --verb=get,list --resource=pods --resource-name=foo
kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.verbs}}{{.}}:{{end}}{{end}}" 'get:list:'
kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.resources}}{{.}}:{{end}}{{end}}" 'pods:'
kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.apiGroups}}{{.}}:{{end}}{{end}}" ':'
kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.resourceNames}}{{.}}:{{end}}{{end}}" 'foo:'
# test `kubectl create clusterrolebinding`
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[@]}" clusterrolebinding super-group --clusterrole=admin --group=the-group

View File

@ -24,6 +24,7 @@ go_library(
"convert.go",
"cp.go",
"create.go",
"create_clusterrole.go",
"create_clusterrolebinding.go",
"create_configmap.go",
"create_deployment.go",
@ -74,6 +75,7 @@ go_library(
"//pkg/apis/rbac:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion:go_default_library",
"//pkg/client/unversioned:go_default_library",
"//pkg/client/unversioned/remotecommand:go_default_library",
"//pkg/kubectl:go_default_library",
@ -141,6 +143,7 @@ go_test(
"clusterinfo_dump_test.go",
"cmd_test.go",
"cp_test.go",
"create_clusterrole_test.go",
"create_configmap_test.go",
"create_deployment_test.go",
"create_namespace_test.go",

View File

@ -95,6 +95,7 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdCreateServiceAccount(f, out))
cmd.AddCommand(NewCmdCreateService(f, out, errOut))
cmd.AddCommand(NewCmdCreateDeployment(f, out))
cmd.AddCommand(NewCmdCreateClusterRole(f, out))
cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, out))
cmd.AddCommand(NewCmdCreateRole(f, out))
cmd.AddCommand(NewCmdCreateRoleBinding(f, out))

View File

@ -0,0 +1,97 @@
/*
Copyright 2017 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 (
"io"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
clusterRoleLong = templates.LongDesc(`
Create a ClusterRole.`)
clusterRoleExample = templates.Examples(`
# Create a ClusterRole named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods
# Create a ClusterRole named "pod-reader" with ResourceName specified
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods --resource-name=readablepod`)
)
type CreateClusterRoleOptions struct {
*CreateRoleOptions
}
// ClusterRole is a command to ease creating ClusterRoles.
func NewCmdCreateClusterRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
c := &CreateClusterRoleOptions{
CreateRoleOptions: &CreateRoleOptions{
Out: cmdOut,
},
}
cmd := &cobra.Command{
Use: "clusterrole NAME --verb=verb --resource=resource.group [--resource-name=resourcename] [--dry-run]",
Short: clusterRoleLong,
Long: clusterRoleLong,
Example: clusterRoleExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(c.Complete(f, cmd, args))
cmdutil.CheckErr(c.Validate())
cmdutil.CheckErr(c.RunCreateRole())
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringSliceVar(&c.Verbs, "verb", []string{}, "verb that applies to the resources contained in the rule")
cmd.Flags().StringSlice("resource", []string{}, "resource that the rule applies to")
cmd.Flags().StringSliceVar(&c.ResourceNames, "resource-name", []string{}, "resource in the white list that the rule applies to")
return cmd
}
func (c *CreateClusterRoleOptions) RunCreateRole() error {
clusterRole := &rbac.ClusterRole{}
clusterRole.Name = c.Name
rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames)
if err != nil {
return err
}
clusterRole.Rules = rules
// Create ClusterRole.
if !c.DryRun {
_, err = c.Client.ClusterRoles().Create(clusterRole)
if err != nil {
return err
}
}
if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 {
cmdutil.PrintSuccess(c.Mapper, useShortOutput, c.Out, "clusterroles", c.Name, c.DryRun, "created")
return nil
}
return c.PrintObject(clusterRole)
}

View File

@ -0,0 +1,122 @@
/*
Copyright 2017 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 (
"bytes"
"io"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/apis/rbac"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
type testClusterRolePrinter struct {
CachedClusterRole *rbac.ClusterRole
}
func (t *testClusterRolePrinter) PrintObj(obj runtime.Object, out io.Writer) error {
t.CachedClusterRole = obj.(*rbac.ClusterRole)
return nil
}
func (t *testClusterRolePrinter) AfterPrint(output io.Writer, res string) error {
return nil
}
func (t *testClusterRolePrinter) HandledResources() []string {
return []string{}
}
func TestCreateClusterRole(t *testing.T) {
clusterRoleName := "my-cluster-role"
f, tf, _, _ := cmdtesting.NewAPIFactory()
printer := &testClusterRolePrinter{}
tf.Printer = printer
tf.Namespace = "test"
tf.Client = &fake.RESTClient{}
tf.ClientConfig = defaultClientConfig()
tests := map[string]struct {
verbs string
resources string
resourceNames string
expectedClusterRole *rbac.ClusterRole
}{
"test-duplicate-resources": {
verbs: "get,watch,list",
resources: "pods,pods",
expectedClusterRole: &rbac.ClusterRole{
ObjectMeta: v1.ObjectMeta{
Name: clusterRoleName,
},
Rules: []rbac.PolicyRule{
{
Verbs: []string{"get", "watch", "list"},
Resources: []string{"pods"},
APIGroups: []string{""},
ResourceNames: []string{},
},
},
},
},
"test-valid-case-with-multiple-apigroups": {
verbs: "get,watch,list",
resources: "pods,deployments.extensions",
expectedClusterRole: &rbac.ClusterRole{
ObjectMeta: v1.ObjectMeta{
Name: clusterRoleName,
},
Rules: []rbac.PolicyRule{
{
Verbs: []string{"get", "watch", "list"},
Resources: []string{"pods"},
APIGroups: []string{""},
ResourceNames: []string{},
},
{
Verbs: []string{"get", "watch", "list"},
Resources: []string{"deployments"},
APIGroups: []string{"extensions"},
ResourceNames: []string{},
},
},
},
},
}
for name, test := range tests {
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateClusterRole(f, buf)
cmd.Flags().Set("dry-run", "true")
cmd.Flags().Set("output", "object")
cmd.Flags().Set("verb", test.verbs)
cmd.Flags().Set("resource", test.resources)
if test.resourceNames != "" {
cmd.Flags().Set("resource-name", test.resourceNames)
}
cmd.Run(cmd, []string{clusterRoleName})
if !reflect.DeepEqual(test.expectedClusterRole, printer.CachedClusterRole) {
t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", name, test.expectedClusterRole, printer.CachedClusterRole)
}
}
}

View File

@ -23,9 +23,12 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/apis/rbac"
internalversionrbac "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
@ -50,20 +53,30 @@ type CreateRoleOptions struct {
Verbs []string
Resources []schema.GroupVersionResource
ResourceNames []string
DryRun bool
OutputFormat string
Namespace string
Client internalversionrbac.RbacInterface
Mapper meta.RESTMapper
Out io.Writer
PrintObject func(obj runtime.Object) error
}
// Role is a command to ease creating Roles.
func NewCmdCreateRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
c := &CreateRoleOptions{}
c := &CreateRoleOptions{
Out: cmdOut,
}
cmd := &cobra.Command{
Use: "role NAME --verb=verb --resource=resource.group [--resource-name=resourcename] [--dry-run]",
Short: roleLong,
Long: roleLong,
Example: roleExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(c.Complete(cmd, args))
cmdutil.CheckErr(c.Validate(f))
cmdutil.CheckErr(c.RunCreateRole(f, cmdOut, cmd, args))
cmdutil.CheckErr(c.Complete(f, cmd, args))
cmdutil.CheckErr(c.Validate())
cmdutil.CheckErr(c.RunCreateRole())
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
@ -77,7 +90,7 @@ func NewCmdCreateRole(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
return cmd
}
func (c *CreateRoleOptions) Complete(cmd *cobra.Command, args []string) error {
func (c *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
@ -120,10 +133,31 @@ func (c *CreateRoleOptions) Complete(cmd *cobra.Command, args []string) error {
}
c.ResourceNames = resourceNames
// Complete other options for Run.
c.Mapper, _ = f.Object()
c.DryRun = cmdutil.GetDryRunFlag(cmd)
c.OutputFormat = cmdutil.GetFlagString(cmd, "output")
c.Namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
}
c.PrintObject = func(obj runtime.Object) error {
return f.PrintObject(cmd, c.Mapper, obj, c.Out)
}
clientSet, err := f.ClientSet()
if err != nil {
return err
}
c.Client = clientSet.Rbac()
return nil
}
func (c *CreateRoleOptions) Validate(f cmdutil.Factory) error {
func (c *CreateRoleOptions) Validate() error {
if c.Name == "" {
return fmt.Errorf("name must be specified")
}
@ -140,14 +174,12 @@ func (c *CreateRoleOptions) Validate(f cmdutil.Factory) error {
}
// validate resources.
mapper, _ := f.Object()
if len(c.Resources) == 0 {
return fmt.Errorf("at least one resource must be specified")
}
for _, r := range c.Resources {
_, err := mapper.ResourceFor(r)
_, err := c.Mapper.ResourceFor(r)
if err != nil {
return err
}
@ -161,67 +193,29 @@ func (c *CreateRoleOptions) Validate(f cmdutil.Factory) error {
return nil
}
func (c *CreateRoleOptions) RunCreateRole(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
mapper, _ := f.Object()
dryRun, outputFormat := cmdutil.GetDryRunFlag(cmd), cmdutil.GetFlagString(cmd, "output")
// groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value
// is a string array of resources under this api group.
// E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]}
groupResourceMapping := map[string][]string{}
// This loop does the following work:
// 1. Constructs groupResourceMapping based on input resources.
// 2. Prevents pointing to non-existent resources.
// 3. Transfers resource short name to long name. E.g. rs.extensions is transferred to replicasets.extensions
for _, r := range c.Resources {
resource, err := mapper.ResourceFor(r)
if err != nil {
return err
}
if !arrayContains(groupResourceMapping[resource.Group], resource.Resource) {
groupResourceMapping[resource.Group] = append(groupResourceMapping[resource.Group], resource.Resource)
}
}
func (c *CreateRoleOptions) RunCreateRole() error {
role := &rbac.Role{}
// Create separate rule for each of the api group.
rules := []rbac.PolicyRule{}
for _, g := range sets.StringKeySet(groupResourceMapping).List() {
rule := rbac.PolicyRule{}
rule.Verbs = c.Verbs
rule.Resources = groupResourceMapping[g]
rule.APIGroups = []string{g}
rule.ResourceNames = c.ResourceNames
rules = append(rules, rule)
}
role.Name = c.Name
role.Rules = rules
// Create role.
namespace, _, err := f.DefaultNamespace()
rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames)
if err != nil {
return err
}
role.Rules = rules
if !dryRun {
client, err := f.ClientSet()
if err != nil {
return err
}
_, err = client.Rbac().Roles(namespace).Create(role)
// Create role.
if !c.DryRun {
_, err = c.Client.Roles(c.Namespace).Create(role)
if err != nil {
return err
}
}
if useShortOutput := outputFormat == "name"; useShortOutput || len(outputFormat) == 0 {
cmdutil.PrintSuccess(mapper, useShortOutput, cmdOut, "roles", c.Name, dryRun, "created")
if useShortOutput := c.OutputFormat == "name"; useShortOutput || len(c.OutputFormat) == 0 {
cmdutil.PrintSuccess(c.Mapper, useShortOutput, c.Out, "roles", c.Name, c.DryRun, "created")
return nil
}
return f.PrintObject(cmd, mapper, role, cmdOut)
return c.PrintObject(role)
}
func arrayContains(s []string, e string) bool {
@ -232,3 +226,37 @@ func arrayContains(s []string, e string) bool {
}
return false
}
func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []schema.GroupVersionResource, resourceNames []string) ([]rbac.PolicyRule, error) {
// groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value
// is a string array of resources under this api group.
// E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]}
groupResourceMapping := map[string][]string{}
// This loop does the following work:
// 1. Constructs groupResourceMapping based on input resources.
// 2. Prevents pointing to non-existent resources.
// 3. Transfers resource short name to long name. E.g. rs.extensions is transferred to replicasets.extensions
for _, r := range resources {
resource, err := mapper.ResourceFor(r)
if err != nil {
return []rbac.PolicyRule{}, err
}
if !arrayContains(groupResourceMapping[resource.Group], resource.Resource) {
groupResourceMapping[resource.Group] = append(groupResourceMapping[resource.Group], resource.Resource)
}
}
// Create separate rule for each of the api group.
rules := []rbac.PolicyRule{}
for _, g := range sets.StringKeySet(groupResourceMapping).List() {
rule := rbac.PolicyRule{}
rule.Verbs = verbs
rule.Resources = groupResourceMapping[g]
rule.APIGroups = []string{g}
rule.ResourceNames = resourceNames
rules = append(rules, rule)
}
return rules, nil
}

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/apis/rbac"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
)
@ -53,6 +54,8 @@ func TestCreateRole(t *testing.T) {
printer := &testRolePrinter{}
tf.Printer = printer
tf.Namespace = "test"
tf.Client = &fake.RESTClient{}
tf.ClientConfig = defaultClientConfig()
tests := map[string]struct {
verbs string
@ -214,7 +217,8 @@ func TestValidate(t *testing.T) {
}
for name, test := range tests {
err := test.roleOptions.Validate(f)
test.roleOptions.Mapper, _ = f.Object()
err := test.roleOptions.Validate()
if test.expectErr && err != nil {
continue
}
@ -230,6 +234,8 @@ func TestComplete(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Namespace = "test"
tf.Client = &fake.RESTClient{}
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateRole(f, buf)
@ -357,15 +363,28 @@ func TestComplete(t *testing.T) {
}
for name, test := range tests {
err := test.roleOptions.Complete(cmd, test.params)
err := test.roleOptions.Complete(f, cmd, test.params)
if !test.expectErr && err != nil {
t.Errorf("%s: unexpected error: %v", name, err)
}
if test.expectErr && err != nil {
continue
}
if !reflect.DeepEqual(test.roleOptions, test.expected) {
t.Errorf("%s:\nexpected:\n%#v\nsaw:\n%#v", name, test.expected, test.roleOptions)
if test.roleOptions.Name != test.expected.Name {
t.Errorf("%s:\nexpected name:\n%#v\nsaw name:\n%#v", name, test.expected.Name, test.roleOptions.Name)
}
if !reflect.DeepEqual(test.roleOptions.Verbs, test.expected.Verbs) {
t.Errorf("%s:\nexpected verbs:\n%#v\nsaw verbs:\n%#v", name, test.expected.Verbs, test.roleOptions.Verbs)
}
if !reflect.DeepEqual(test.roleOptions.Resources, test.expected.Resources) {
t.Errorf("%s:\nexpected resources:\n%#v\nsaw resources:\n%#v", name, test.expected.Resources, test.roleOptions.Resources)
}
if !reflect.DeepEqual(test.roleOptions.ResourceNames, test.expected.ResourceNames) {
t.Errorf("%s:\nexpected resource names:\n%#v\nsaw resource names:\n%#v", name, test.expected.ResourceNames, test.roleOptions.ResourceNames)
}
}
}