mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	Merge pull request #43436 from xilabao/add-set-rolebinding-command
Automatic merge from submit-queue add set rolebinding/clusterrolebinding command add command to set user/group/serviceaccount in rolebinding/clusterrolebinding /cc @liggitt @deads2k
This commit is contained in:
		| @@ -89,6 +89,7 @@ docs/man/man1/kubectl-scale.1 | |||||||
| docs/man/man1/kubectl-set-image.1 | docs/man/man1/kubectl-set-image.1 | ||||||
| docs/man/man1/kubectl-set-resources.1 | docs/man/man1/kubectl-set-resources.1 | ||||||
| docs/man/man1/kubectl-set-selector.1 | docs/man/man1/kubectl-set-selector.1 | ||||||
|  | docs/man/man1/kubectl-set-subject.1 | ||||||
| docs/man/man1/kubectl-set.1 | docs/man/man1/kubectl-set.1 | ||||||
| docs/man/man1/kubectl-stop.1 | docs/man/man1/kubectl-stop.1 | ||||||
| docs/man/man1/kubectl-taint.1 | docs/man/man1/kubectl-taint.1 | ||||||
| @@ -180,6 +181,7 @@ docs/user-guide/kubectl/kubectl_set.md | |||||||
| docs/user-guide/kubectl/kubectl_set_image.md | docs/user-guide/kubectl/kubectl_set_image.md | ||||||
| docs/user-guide/kubectl/kubectl_set_resources.md | docs/user-guide/kubectl/kubectl_set_resources.md | ||||||
| docs/user-guide/kubectl/kubectl_set_selector.md | docs/user-guide/kubectl/kubectl_set_selector.md | ||||||
|  | docs/user-guide/kubectl/kubectl_set_subject.md | ||||||
| docs/user-guide/kubectl/kubectl_taint.md | docs/user-guide/kubectl/kubectl_taint.md | ||||||
| docs/user-guide/kubectl/kubectl_top.md | docs/user-guide/kubectl/kubectl_top.md | ||||||
| docs/user-guide/kubectl/kubectl_top_node.md | docs/user-guide/kubectl/kubectl_top_node.md | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								docs/man/man1/kubectl-set-subject.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/man/man1/kubectl-set-subject.1
									
									
									
									
									
										Normal 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. | ||||||
							
								
								
									
										3
									
								
								docs/user-guide/kubectl/kubectl_set_subject.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/user-guide/kubectl/kubectl_set_subject.md
									
									
									
									
									
										Normal 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. | ||||||
| @@ -3012,21 +3012,41 @@ runTests() { | |||||||
|     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.apiGroups}}{{.}}:{{end}}{{end}}" ':' | ||||||
|     kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.resourceNames}}{{.}}:{{end}}{{end}}" 'foo:' |     kube::test::get_object_assert clusterrole/resourcename-reader "{{range.rules}}{{range.resourceNames}}{{.}}:{{end}}{{end}}" 'foo:' | ||||||
|  |  | ||||||
|     # test `kubectl create clusterrolebinding` |     # test `kubectl create rolebinding/clusterrolebinding` | ||||||
|  |     # test `kubectl set subject rolebinding/clusterrolebinding` | ||||||
|     kubectl create "${kube_flags[@]}" clusterrolebinding super-admin --clusterrole=admin --user=super-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:' |     kube::test::get_object_assert clusterrolebinding/super-admin "{{range.subjects}}{{.name}}:{{end}}" 'super-admin:' | ||||||
|  |     kubectl set subject "${kube_flags[@]}" clusterrolebinding super-admin --user=foo | ||||||
|  |     kube::test::get_object_assert clusterrolebinding/super-admin "{{range.subjects}}{{.name}}:{{end}}" 'super-admin:foo:' | ||||||
|  |  | ||||||
|     kubectl create "${kube_flags[@]}" clusterrolebinding super-group --clusterrole=admin --group=the-group |     kubectl create "${kube_flags[@]}" clusterrolebinding super-group --clusterrole=admin --group=the-group | ||||||
|     kube::test::get_object_assert clusterrolebinding/super-group "{{range.subjects}}{{.name}}:{{end}}" 'the-group:' |     kube::test::get_object_assert clusterrolebinding/super-group "{{range.subjects}}{{.name}}:{{end}}" 'the-group:' | ||||||
|  |     kubectl set subject "${kube_flags[@]}" clusterrolebinding super-group --group=foo | ||||||
|  |     kube::test::get_object_assert clusterrolebinding/super-group "{{range.subjects}}{{.name}}:{{end}}" 'the-group:foo:' | ||||||
|  |  | ||||||
|     kubectl create "${kube_flags[@]}" clusterrolebinding super-sa --clusterrole=admin --serviceaccount=otherns:sa-name |     kubectl create "${kube_flags[@]}" clusterrolebinding super-sa --clusterrole=admin --serviceaccount=otherns:sa-name | ||||||
|     kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.namespace}}:{{end}}" 'otherns:' |     kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.namespace}}:{{end}}" 'otherns:' | ||||||
|     kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:' |     kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:' | ||||||
|  |     kubectl set subject "${kube_flags[@]}" clusterrolebinding super-sa --serviceaccount=otherfoo:foo | ||||||
|  |     kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.namespace}}:{{end}}" 'otherns:otherfoo:' | ||||||
|  |     kube::test::get_object_assert clusterrolebinding/super-sa "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:foo:' | ||||||
|  |  | ||||||
|     kubectl create "${kube_flags[@]}" rolebinding admin --clusterrole=admin --user=default-admin -n default |     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:' |     kube::test::get_object_assert rolebinding/admin "{{range.subjects}}{{.name}}:{{end}}" 'default-admin:' | ||||||
|  |     kubectl set subject "${kube_flags[@]}" rolebinding admin --user=foo -n default | ||||||
|  |     kube::test::get_object_assert rolebinding/admin "{{range.subjects}}{{.name}}:{{end}}" 'default-admin:foo:' | ||||||
|  |  | ||||||
|     kubectl create "${kube_flags[@]}" rolebinding localrole --role=localrole --group=the-group -n default |     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:' |     kube::test::get_object_assert rolebinding/localrole "{{range.subjects}}{{.name}}:{{end}}" 'the-group:' | ||||||
|  |     kubectl set subject "${kube_flags[@]}" rolebinding localrole --group=foo -n default | ||||||
|  |     kube::test::get_object_assert rolebinding/localrole "{{range.subjects}}{{.name}}:{{end}}" 'the-group:foo:' | ||||||
|  |  | ||||||
|     kubectl create "${kube_flags[@]}" rolebinding sarole --role=localrole --serviceaccount=otherns:sa-name -n default |     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}}{{.namespace}}:{{end}}" 'otherns:' | ||||||
|     kube::test::get_object_assert rolebinding/sarole "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:' |     kube::test::get_object_assert rolebinding/sarole "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:' | ||||||
|  |     kubectl set subject "${kube_flags[@]}" rolebinding sarole --serviceaccount=otherfoo:foo -n default | ||||||
|  |     kube::test::get_object_assert rolebinding/sarole "{{range.subjects}}{{.namespace}}:{{end}}" 'otherns:otherfoo:' | ||||||
|  |     kube::test::get_object_assert rolebinding/sarole "{{range.subjects}}{{.name}}:{{end}}" 'sa-name:foo:' | ||||||
|   fi |   fi | ||||||
|  |  | ||||||
|   ######## |   ######## | ||||||
|   | |||||||
| @@ -16,10 +16,12 @@ go_library( | |||||||
|         "set_image.go", |         "set_image.go", | ||||||
|         "set_resources.go", |         "set_resources.go", | ||||||
|         "set_selector.go", |         "set_selector.go", | ||||||
|  |         "set_subject.go", | ||||||
|     ], |     ], | ||||||
|     tags = ["automanaged"], |     tags = ["automanaged"], | ||||||
|     deps = [ |     deps = [ | ||||||
|         "//pkg/api:go_default_library", |         "//pkg/api:go_default_library", | ||||||
|  |         "//pkg/apis/rbac:go_default_library", | ||||||
|         "//pkg/kubectl:go_default_library", |         "//pkg/kubectl:go_default_library", | ||||||
|         "//pkg/kubectl/cmd/templates:go_default_library", |         "//pkg/kubectl/cmd/templates:go_default_library", | ||||||
|         "//pkg/kubectl/cmd/util:go_default_library", |         "//pkg/kubectl/cmd/util:go_default_library", | ||||||
| @@ -32,6 +34,7 @@ go_library( | |||||||
|         "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", | ||||||
|  |         "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", | ||||||
|     ], |     ], | ||||||
| @@ -42,6 +45,7 @@ go_test( | |||||||
|     srcs = [ |     srcs = [ | ||||||
|         "set_image_test.go", |         "set_image_test.go", | ||||||
|         "set_selector_test.go", |         "set_selector_test.go", | ||||||
|  |         "set_subject_test.go", | ||||||
|         "set_test.go", |         "set_test.go", | ||||||
|     ], |     ], | ||||||
|     data = [ |     data = [ | ||||||
| @@ -53,6 +57,7 @@ go_test( | |||||||
|         "//pkg/api:go_default_library", |         "//pkg/api:go_default_library", | ||||||
|         "//pkg/apis/batch:go_default_library", |         "//pkg/apis/batch:go_default_library", | ||||||
|         "//pkg/apis/extensions:go_default_library", |         "//pkg/apis/extensions:go_default_library", | ||||||
|  |         "//pkg/apis/rbac:go_default_library", | ||||||
|         "//pkg/kubectl/cmd/testing:go_default_library", |         "//pkg/kubectl/cmd/testing:go_default_library", | ||||||
|         "//pkg/kubectl/cmd/util:go_default_library", |         "//pkg/kubectl/cmd/util:go_default_library", | ||||||
|         "//pkg/kubectl/resource:go_default_library", |         "//pkg/kubectl/resource:go_default_library", | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ func NewCmdSet(f cmdutil.Factory, out, err io.Writer) *cobra.Command { | |||||||
| 	cmd.AddCommand(NewCmdImage(f, out, err)) | 	cmd.AddCommand(NewCmdImage(f, out, err)) | ||||||
| 	cmd.AddCommand(NewCmdResources(f, out, err)) | 	cmd.AddCommand(NewCmdResources(f, out, err)) | ||||||
| 	cmd.AddCommand(NewCmdSelector(f, out)) | 	cmd.AddCommand(NewCmdSelector(f, out)) | ||||||
|  | 	cmd.AddCommand(NewCmdSubject(f, out, err)) | ||||||
|  |  | ||||||
| 	return cmd | 	return cmd | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										278
									
								
								pkg/kubectl/cmd/set/set_subject.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								pkg/kubectl/cmd/set/set_subject.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,278 @@ | |||||||
|  | /* | ||||||
|  | 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 set | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  |  | ||||||
|  | 	"k8s.io/apimachinery/pkg/api/meta" | ||||||
|  | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
|  | 	"k8s.io/apimachinery/pkg/types" | ||||||
|  | 	utilerrors "k8s.io/apimachinery/pkg/util/errors" | ||||||
|  | 	"k8s.io/apimachinery/pkg/util/sets" | ||||||
|  | 	"k8s.io/kubernetes/pkg/apis/rbac" | ||||||
|  | 	"k8s.io/kubernetes/pkg/kubectl/cmd/templates" | ||||||
|  | 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" | ||||||
|  | 	"k8s.io/kubernetes/pkg/kubectl/resource" | ||||||
|  | 	"k8s.io/kubernetes/pkg/util/i18n" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	subject_long = templates.LongDesc(` | ||||||
|  | 	Update User, Group or ServiceAccount in a RoleBinding/ClusterRoleBinding.`) | ||||||
|  |  | ||||||
|  | 	subject_example = templates.Examples(` | ||||||
|  | 	# Update a ClusterRoleBinding for serviceaccount1 | ||||||
|  | 	kubectl set subject clusterrolebinding admin --serviceaccount=namespace:serviceaccount1 | ||||||
|  |  | ||||||
|  | 	# Update a RoleBinding for user1, user2, and group1 | ||||||
|  | 	kubectl set subject rolebinding admin --user=user1 --user=user2 --group=group1 | ||||||
|  |  | ||||||
|  | 	# Print the result (in yaml format) of updating rolebinding subjects from a local, without hitting the server | ||||||
|  | 	kubectl create rolebinding admin --role=admin --user=admin -o yaml --dry-run | kubectl set subject --local -f - --user=foo -o yaml`) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type updateSubjects func(existings []rbac.Subject, targets []rbac.Subject) (bool, []rbac.Subject) | ||||||
|  |  | ||||||
|  | // SubjectOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of | ||||||
|  | // referencing the cmd.Flags | ||||||
|  | type SubjectOptions struct { | ||||||
|  | 	resource.FilenameOptions | ||||||
|  |  | ||||||
|  | 	Mapper            meta.RESTMapper | ||||||
|  | 	Typer             runtime.ObjectTyper | ||||||
|  | 	Infos             []*resource.Info | ||||||
|  | 	Encoder           runtime.Encoder | ||||||
|  | 	Out               io.Writer | ||||||
|  | 	Err               io.Writer | ||||||
|  | 	Selector          string | ||||||
|  | 	ContainerSelector string | ||||||
|  | 	ShortOutput       bool | ||||||
|  | 	All               bool | ||||||
|  | 	DryRun            bool | ||||||
|  | 	Local             bool | ||||||
|  |  | ||||||
|  | 	Users           []string | ||||||
|  | 	Groups          []string | ||||||
|  | 	ServiceAccounts []string | ||||||
|  |  | ||||||
|  | 	PrintObject func(mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewCmdSubject(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { | ||||||
|  | 	options := &SubjectOptions{ | ||||||
|  | 		Out: out, | ||||||
|  | 		Err: errOut, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmd := &cobra.Command{ | ||||||
|  | 		Use:     "subject (-f FILENAME | TYPE NAME) [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]", | ||||||
|  | 		Short:   i18n.T("Update User, Group or ServiceAccount in a RoleBinding/ClusterRoleBinding"), | ||||||
|  | 		Long:    subject_long, | ||||||
|  | 		Example: subject_example, | ||||||
|  | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
|  | 			cmdutil.CheckErr(options.Complete(f, cmd, args)) | ||||||
|  | 			cmdutil.CheckErr(options.Validate()) | ||||||
|  | 			cmdutil.CheckErr(options.Run(f, addSubjects)) | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmdutil.AddPrinterFlags(cmd) | ||||||
|  | 	usage := "the resource to update the subjects" | ||||||
|  | 	cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) | ||||||
|  | 	cmd.Flags().BoolVar(&options.All, "all", false, "select all resources in the namespace of the specified resource types") | ||||||
|  | 	cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.") | ||||||
|  | 	cmd.Flags().BoolVar(&options.Local, "local", false, "If true, set resources will NOT contact api-server but run locally.") | ||||||
|  | 	cmdutil.AddDryRunFlag(cmd) | ||||||
|  | 	cmd.Flags().StringArrayVar(&options.Users, "user", []string{}, "usernames to bind to the role") | ||||||
|  | 	cmd.Flags().StringArrayVar(&options.Groups, "group", []string{}, "groups to bind to the role") | ||||||
|  | 	cmd.Flags().StringArrayVar(&options.ServiceAccounts, "serviceaccount", []string{}, "service accounts to bind to the role") | ||||||
|  | 	return cmd | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { | ||||||
|  | 	o.Local = cmdutil.GetFlagBool(cmd, "local") | ||||||
|  | 	o.Mapper, o.Typer = f.Object() | ||||||
|  | 	o.Encoder = f.JSONEncoder() | ||||||
|  | 	o.ShortOutput = cmdutil.GetFlagString(cmd, "output") == "name" | ||||||
|  | 	o.DryRun = cmdutil.GetDryRunFlag(cmd) | ||||||
|  | 	o.PrintObject = func(mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error { | ||||||
|  | 		return f.PrintObject(cmd, mapper, obj, out) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cmdNamespace, enforceNamespace, err := f.DefaultNamespace() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	builder := resource.NewBuilder(o.Mapper, f.CategoryExpander(), o.Typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). | ||||||
|  | 		ContinueOnError(). | ||||||
|  | 		NamespaceParam(cmdNamespace).DefaultNamespace(). | ||||||
|  | 		FilenameParam(enforceNamespace, &o.FilenameOptions). | ||||||
|  | 		Flatten() | ||||||
|  | 	if !o.Local { | ||||||
|  | 		builder = builder. | ||||||
|  | 			SelectorParam(o.Selector). | ||||||
|  | 			ResourceTypeOrNameArgs(o.All, args...). | ||||||
|  | 			Latest() | ||||||
|  | 	} | ||||||
|  | 	o.Infos, err = builder.Do().Infos() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *SubjectOptions) Validate() error { | ||||||
|  | 	if len(o.Users) == 0 && len(o.Groups) == 0 && len(o.ServiceAccounts) == 0 { | ||||||
|  | 		return fmt.Errorf("you must specify at least one value of user, group or serviceaccount") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, sa := range o.ServiceAccounts { | ||||||
|  | 		tokens := strings.Split(sa, ":") | ||||||
|  | 		if len(tokens) != 2 || tokens[1] == "" { | ||||||
|  | 			return fmt.Errorf("serviceaccount must be <namespace>:<name>") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, info := range o.Infos { | ||||||
|  | 			_, ok := info.Object.(*rbac.ClusterRoleBinding) | ||||||
|  | 			if ok && tokens[0] == "" { | ||||||
|  | 				return fmt.Errorf("serviceaccount must be <namespace>:<name>, namespace must be specified") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *SubjectOptions) Run(f cmdutil.Factory, fn updateSubjects) error { | ||||||
|  | 	var err error | ||||||
|  | 	patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) ([]byte, error) { | ||||||
|  | 		subjects := []rbac.Subject{} | ||||||
|  | 		for _, user := range sets.NewString(o.Users...).List() { | ||||||
|  | 			subject := rbac.Subject{ | ||||||
|  | 				Kind:     rbac.UserKind, | ||||||
|  | 				APIGroup: rbac.GroupName, | ||||||
|  | 				Name:     user, | ||||||
|  | 			} | ||||||
|  | 			subjects = append(subjects, subject) | ||||||
|  | 		} | ||||||
|  | 		for _, group := range sets.NewString(o.Groups...).List() { | ||||||
|  | 			subject := rbac.Subject{ | ||||||
|  | 				Kind:     rbac.GroupKind, | ||||||
|  | 				APIGroup: rbac.GroupName, | ||||||
|  | 				Name:     group, | ||||||
|  | 			} | ||||||
|  | 			subjects = append(subjects, subject) | ||||||
|  | 		} | ||||||
|  | 		for _, sa := range sets.NewString(o.ServiceAccounts...).List() { | ||||||
|  | 			tokens := strings.Split(sa, ":") | ||||||
|  | 			namespace := tokens[0] | ||||||
|  | 			name := tokens[1] | ||||||
|  | 			if len(namespace) == 0 { | ||||||
|  | 				namespace, _, err = f.DefaultNamespace() | ||||||
|  | 				if err != nil { | ||||||
|  | 					return nil, err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			subject := rbac.Subject{ | ||||||
|  | 				Kind:      rbac.ServiceAccountKind, | ||||||
|  | 				Namespace: namespace, | ||||||
|  | 				Name:      name, | ||||||
|  | 			} | ||||||
|  | 			subjects = append(subjects, subject) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		transformed, err := updateSubjectForObject(info.Object, subjects, fn) | ||||||
|  | 		if transformed && err == nil { | ||||||
|  | 			return runtime.Encode(o.Encoder, info.Object) | ||||||
|  | 		} | ||||||
|  | 		return nil, err | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	allErrs := []error{} | ||||||
|  | 	for _, patch := range patches { | ||||||
|  | 		info := patch.Info | ||||||
|  | 		if patch.Err != nil { | ||||||
|  | 			allErrs = append(allErrs, fmt.Errorf("error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err)) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		//no changes | ||||||
|  | 		if string(patch.Patch) == "{}" || len(patch.Patch) == 0 { | ||||||
|  | 			allErrs = append(allErrs, fmt.Errorf("info: %s %q was not changed\n", info.Mapping.Resource, info.Name)) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if o.Local || o.DryRun { | ||||||
|  | 			fmt.Fprintln(o.Out, "running in local/dry-run mode...") | ||||||
|  | 			return o.PrintObject(o.Mapper, info.Object, o.Out) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch) | ||||||
|  | 		if err != nil { | ||||||
|  | 			allErrs = append(allErrs, fmt.Errorf("failed to patch subjects to rolebinding: %v\n", err)) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		info.Refresh(obj, true) | ||||||
|  |  | ||||||
|  | 		cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, false, "subjects updated") | ||||||
|  | 	} | ||||||
|  | 	return utilerrors.NewAggregate(allErrs) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //Note: the obj mutates in the function | ||||||
|  | func updateSubjectForObject(obj runtime.Object, subjects []rbac.Subject, fn updateSubjects) (bool, error) { | ||||||
|  | 	switch t := obj.(type) { | ||||||
|  | 	case *rbac.RoleBinding: | ||||||
|  | 		transformed, result := fn(t.Subjects, subjects) | ||||||
|  | 		t.Subjects = result | ||||||
|  | 		return transformed, nil | ||||||
|  | 	case *rbac.ClusterRoleBinding: | ||||||
|  | 		transformed, result := fn(t.Subjects, subjects) | ||||||
|  | 		t.Subjects = result | ||||||
|  | 		return transformed, nil | ||||||
|  | 	default: | ||||||
|  | 		return false, fmt.Errorf("setting subjects is only supported for RoleBinding/ClusterRoleBinding") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func addSubjects(existings []rbac.Subject, targets []rbac.Subject) (bool, []rbac.Subject) { | ||||||
|  | 	transformed := false | ||||||
|  | 	updated := existings | ||||||
|  | 	for _, item := range targets { | ||||||
|  | 		if !contain(existings, item) { | ||||||
|  | 			updated = append(updated, item) | ||||||
|  | 			transformed = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return transformed, updated | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func contain(slice []rbac.Subject, item rbac.Subject) bool { | ||||||
|  | 	for _, v := range slice { | ||||||
|  | 		if v == item { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
							
								
								
									
										427
									
								
								pkg/kubectl/cmd/set/set_subject_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										427
									
								
								pkg/kubectl/cmd/set/set_subject_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,427 @@ | |||||||
|  | /* | ||||||
|  | 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 set | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
|  | 	"k8s.io/kubernetes/pkg/apis/rbac" | ||||||
|  | 	cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" | ||||||
|  | 	"k8s.io/kubernetes/pkg/kubectl/resource" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestValidate(t *testing.T) { | ||||||
|  | 	f, tf, _, _ := cmdtesting.NewAPIFactory() | ||||||
|  | 	tf.Namespace = "test" | ||||||
|  |  | ||||||
|  | 	tests := map[string]struct { | ||||||
|  | 		options   *SubjectOptions | ||||||
|  | 		expectErr bool | ||||||
|  | 	}{ | ||||||
|  | 		"test-missing-subjects": { | ||||||
|  | 			options: &SubjectOptions{ | ||||||
|  | 				Users:           []string{}, | ||||||
|  | 				Groups:          []string{}, | ||||||
|  | 				ServiceAccounts: []string{}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		"test-invalid-serviceaccounts": { | ||||||
|  | 			options: &SubjectOptions{ | ||||||
|  | 				Users:           []string{}, | ||||||
|  | 				Groups:          []string{}, | ||||||
|  | 				ServiceAccounts: []string{"foo"}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		"test-missing-serviceaccounts-name": { | ||||||
|  | 			options: &SubjectOptions{ | ||||||
|  | 				Users:           []string{}, | ||||||
|  | 				Groups:          []string{}, | ||||||
|  | 				ServiceAccounts: []string{"foo:"}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		"test-missing-serviceaccounts-namespace": { | ||||||
|  | 			options: &SubjectOptions{ | ||||||
|  | 				Infos: []*resource.Info{ | ||||||
|  | 					{ | ||||||
|  | 						Object: &rbac.ClusterRoleBinding{ | ||||||
|  | 							ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 								Name: "clusterrolebinding", | ||||||
|  | 							}, | ||||||
|  | 							RoleRef: rbac.RoleRef{ | ||||||
|  | 								APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 								Kind:     "ClusterRole", | ||||||
|  | 								Name:     "role", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Users:           []string{}, | ||||||
|  | 				Groups:          []string{}, | ||||||
|  | 				ServiceAccounts: []string{":foo"}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 		"test-valid-case": { | ||||||
|  | 			options: &SubjectOptions{ | ||||||
|  | 				Infos: []*resource.Info{ | ||||||
|  | 					{ | ||||||
|  | 						Object: &rbac.RoleBinding{ | ||||||
|  | 							ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 								Name:      "rolebinding", | ||||||
|  | 								Namespace: "one", | ||||||
|  | 							}, | ||||||
|  | 							RoleRef: rbac.RoleRef{ | ||||||
|  | 								APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 								Kind:     "ClusterRole", | ||||||
|  | 								Name:     "role", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Users:           []string{"foo"}, | ||||||
|  | 				Groups:          []string{"foo"}, | ||||||
|  | 				ServiceAccounts: []string{"ns:foo"}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for name, test := range tests { | ||||||
|  | 		test.options.Mapper, _ = f.Object() | ||||||
|  | 		err := test.options.Validate() | ||||||
|  | 		if test.expectErr && err != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if !test.expectErr && err != nil { | ||||||
|  | 			t.Errorf("%s: unexpected error: %v", name, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUpdateSubjectForObject(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		Name     string | ||||||
|  | 		obj      runtime.Object | ||||||
|  | 		subjects []rbac.Subject | ||||||
|  | 		expected []rbac.Subject | ||||||
|  | 		wantErr  bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			Name: "invalid object type", | ||||||
|  | 			obj: &rbac.Role{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      "role", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantErr: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: "add resource with users in rolebinding", | ||||||
|  | 			obj: &rbac.RoleBinding{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      "rolebinding", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 				}, | ||||||
|  | 				Subjects: []rbac.Subject{ | ||||||
|  | 					{ | ||||||
|  | 						APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 						Kind:     "User", | ||||||
|  | 						Name:     "a", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			subjects: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: "add resource with groups in rolebinding", | ||||||
|  | 			obj: &rbac.RoleBinding{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      "rolebinding", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 				}, | ||||||
|  | 				Subjects: []rbac.Subject{ | ||||||
|  | 					{ | ||||||
|  | 						APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 						Kind:     "Group", | ||||||
|  | 						Name:     "a", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			subjects: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "Group", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "Group", | ||||||
|  | 					Name:     "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "Group", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "Group", | ||||||
|  | 					Name:     "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: "add resource with serviceaccounts in rolebinding", | ||||||
|  | 			obj: &rbac.RoleBinding{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      "rolebinding", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 				}, | ||||||
|  | 				Subjects: []rbac.Subject{ | ||||||
|  | 					{ | ||||||
|  | 						Kind:      "ServiceAccount", | ||||||
|  | 						Namespace: "one", | ||||||
|  | 						Name:      "a", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			subjects: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: "add resource with serviceaccounts in clusterrolebinding", | ||||||
|  | 			obj: &rbac.ClusterRoleBinding{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name: "clusterrolebinding", | ||||||
|  | 				}, | ||||||
|  | 				Subjects: []rbac.Subject{ | ||||||
|  | 					{ | ||||||
|  | 						APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 						Kind:     "User", | ||||||
|  | 						Name:     "a", | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 						Kind:     "Group", | ||||||
|  | 						Name:     "a", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			subjects: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "Group", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantErr: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		if _, err := updateSubjectForObject(tt.obj, tt.subjects, addSubjects); (err != nil) != tt.wantErr { | ||||||
|  | 			t.Errorf("%q. updateSubjectForObject() error = %v, wantErr %v", tt.Name, err, tt.wantErr) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		want := tt.expected | ||||||
|  | 		var got []rbac.Subject | ||||||
|  | 		switch t := tt.obj.(type) { | ||||||
|  | 		case *rbac.RoleBinding: | ||||||
|  | 			got = t.Subjects | ||||||
|  | 		case *rbac.ClusterRoleBinding: | ||||||
|  | 			got = t.Subjects | ||||||
|  | 		} | ||||||
|  | 		if !reflect.DeepEqual(got, want) { | ||||||
|  | 			t.Errorf("%q. updateSubjectForObject() failed", tt.Name) | ||||||
|  | 			t.Errorf("Got: %v", got) | ||||||
|  | 			t.Errorf("Want: %v", want) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestAddSubject(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		Name       string | ||||||
|  | 		existing   []rbac.Subject | ||||||
|  | 		subjects   []rbac.Subject | ||||||
|  | 		expected   []rbac.Subject | ||||||
|  | 		wantChange bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			Name: "add resource with users", | ||||||
|  | 			existing: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			subjects: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "User", | ||||||
|  | 					Name:     "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantChange: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name: "add resource with serviceaccounts", | ||||||
|  | 			existing: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "b", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			subjects: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "two", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expected: []rbac.Subject{ | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "one", | ||||||
|  | 					Name:      "b", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Kind:      "ServiceAccount", | ||||||
|  | 					Namespace: "two", | ||||||
|  | 					Name:      "a", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			wantChange: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		changed := false | ||||||
|  | 		got := []rbac.Subject{} | ||||||
|  | 		if changed, got = addSubjects(tt.existing, tt.subjects); (changed != false) != tt.wantChange { | ||||||
|  | 			t.Errorf("%q. addSubjects() changed = %v, wantChange = %v", tt.Name, changed, tt.wantChange) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		want := tt.expected | ||||||
|  | 		if !reflect.DeepEqual(got, want) { | ||||||
|  | 			t.Errorf("%q. addSubjects() failed", tt.Name) | ||||||
|  | 			t.Errorf("Got: %v", got) | ||||||
|  | 			t.Errorf("Want: %v", want) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Submit Queue
					Kubernetes Submit Queue