diff --git a/docs/.generated_docs b/docs/.generated_docs index 5d0ada9c2a5..efcaad246a8 100644 --- a/docs/.generated_docs +++ b/docs/.generated_docs @@ -38,6 +38,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 @@ -124,6 +125,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 diff --git a/docs/man/man1/kubectl-create-clusterrole.1 b/docs/man/man1/kubectl-create-clusterrole.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-create-clusterrole.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_clusterrole.md b/docs/user-guide/kubectl/kubectl_create_clusterrole.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_clusterrole.md @@ -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/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index faf33356382..76a1d63f0d9 100644 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -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 diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 1ddff19e5aa..95645cd6b92 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -25,6 +25,7 @@ go_library( "convert.go", "cp.go", "create.go", + "create_clusterrole.go", "create_clusterrolebinding.go", "create_configmap.go", "create_deployment.go", @@ -75,6 +76,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", @@ -143,6 +145,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", diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 36bb5174b38..90adab0346f 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -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)) diff --git a/pkg/kubectl/cmd/create_clusterrole.go b/pkg/kubectl/cmd/create_clusterrole.go new file mode 100644 index 00000000000..eeaf25996c6 --- /dev/null +++ b/pkg/kubectl/cmd/create_clusterrole.go @@ -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) +} diff --git a/pkg/kubectl/cmd/create_clusterrole_test.go b/pkg/kubectl/cmd/create_clusterrole_test.go new file mode 100644 index 00000000000..750c12acbc9 --- /dev/null +++ b/pkg/kubectl/cmd/create_clusterrole_test.go @@ -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) + } + } +} diff --git a/pkg/kubectl/cmd/create_role.go b/pkg/kubectl/cmd/create_role.go index 135d7d6793c..2ca72f47ead 100644 --- a/pkg/kubectl/cmd/create_role.go +++ b/pkg/kubectl/cmd/create_role.go @@ -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 +} diff --git a/pkg/kubectl/cmd/create_role_test.go b/pkg/kubectl/cmd/create_role_test.go index d737f95b1c5..4e7e3187d5c 100644 --- a/pkg/kubectl/cmd/create_role_test.go +++ b/pkg/kubectl/cmd/create_role_test.go @@ -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) } } }