2017-03-21 06:48:25 +00:00
/ *
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"
"strings"
2018-04-04 20:23:33 +00:00
"k8s.io/kubernetes/pkg/printers"
2017-03-21 06:48:25 +00:00
"github.com/spf13/cobra"
"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"
2018-04-25 12:32:08 +00:00
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
2017-03-21 06:48:25 +00:00
"k8s.io/kubernetes/pkg/kubectl/resource"
2017-07-07 04:04:11 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
2017-03-21 06:48:25 +00:00
)
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 {
2018-04-04 20:23:33 +00:00
PrintFlags * printers . PrintFlags
2017-03-21 06:48:25 +00:00
resource . FilenameOptions
Infos [ ] * resource . Info
Selector string
ContainerSelector string
2017-05-23 07:49:07 +00:00
Output string
2017-03-21 06:48:25 +00:00
All bool
DryRun bool
Local bool
Users [ ] string
Groups [ ] string
ServiceAccounts [ ] string
2018-04-12 20:02:30 +00:00
PrintObj printers . ResourcePrinterFunc
2018-04-25 12:32:08 +00:00
genericclioptions . IOStreams
2017-03-21 06:48:25 +00:00
}
2018-04-25 12:32:08 +00:00
func NewSubjectOptions ( streams genericclioptions . IOStreams ) * SubjectOptions {
return & SubjectOptions {
2018-04-04 20:23:33 +00:00
PrintFlags : printers . NewPrintFlags ( "subjects updated" ) ,
2018-04-25 12:32:08 +00:00
IOStreams : streams ,
2017-03-21 06:48:25 +00:00
}
2018-04-25 12:32:08 +00:00
}
func NewCmdSubject ( f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
options := NewSubjectOptions ( streams )
2017-03-21 06:48:25 +00:00
cmd := & cobra . Command {
2017-10-11 06:26:02 +00:00
Use : "subject (-f FILENAME | TYPE NAME) [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run]" ,
DisableFlagsInUseLine : true ,
2017-03-21 06:48:25 +00:00
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 ) )
} ,
}
2018-04-04 20:23:33 +00:00
options . PrintFlags . AddFlags ( cmd )
2017-03-21 06:48:25 +00:00
usage := "the resource to update the subjects"
cmdutil . AddFilenameOptionFlags ( cmd , & options . FilenameOptions , usage )
2017-08-25 08:28:21 +00:00
cmd . Flags ( ) . BoolVar ( & options . All , "all" , options . All , "Select all resources, including uninitialized ones, in the namespace of the specified resource types" )
2018-02-24 09:01:30 +00:00
cmd . Flags ( ) . StringVarP ( & options . Selector , "selector" , "l" , options . Selector , "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)" )
2017-08-25 08:28:21 +00:00
cmd . Flags ( ) . BoolVar ( & options . Local , "local" , options . Local , "If true, set subject will NOT contact api-server but run locally." )
2017-03-21 06:48:25 +00:00
cmdutil . AddDryRunFlag ( cmd )
2018-02-24 09:01:30 +00:00
cmd . Flags ( ) . StringArrayVar ( & options . Users , "user" , options . Users , "Usernames to bind to the role" )
cmd . Flags ( ) . StringArrayVar ( & options . Groups , "group" , options . Groups , "Groups to bind to the role" )
cmd . Flags ( ) . StringArrayVar ( & options . ServiceAccounts , "serviceaccount" , options . ServiceAccounts , "Service accounts to bind to the role" )
2017-08-11 06:21:44 +00:00
cmdutil . AddIncludeUninitializedFlag ( cmd )
2017-03-21 06:48:25 +00:00
return cmd
}
func ( o * SubjectOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
2017-05-23 07:49:07 +00:00
o . Output = cmdutil . GetFlagString ( cmd , "output" )
2017-03-21 06:48:25 +00:00
o . DryRun = cmdutil . GetDryRunFlag ( cmd )
2018-04-04 20:23:33 +00:00
2018-04-06 15:36:52 +00:00
if o . DryRun {
o . PrintFlags . Complete ( "%s (dry run)" )
}
2018-04-04 20:23:33 +00:00
printer , err := o . PrintFlags . ToPrinter ( )
if err != nil {
return err
}
2018-04-12 20:02:30 +00:00
o . PrintObj = printer . PrintObj
2017-03-21 06:48:25 +00:00
cmdNamespace , enforceNamespace , err := f . DefaultNamespace ( )
if err != nil {
return err
}
2017-08-11 06:21:44 +00:00
includeUninitialized := cmdutil . ShouldIncludeUninitialized ( cmd , false )
2017-11-15 04:03:06 +00:00
builder := f . NewBuilder ( ) .
2017-11-15 06:10:30 +00:00
Internal ( ) .
2017-11-15 04:03:06 +00:00
LocalParam ( o . Local ) .
ContinueOnError ( ) .
NamespaceParam ( cmdNamespace ) . DefaultNamespace ( ) .
FilenameParam ( enforceNamespace , & o . FilenameOptions ) .
IncludeUninitialized ( includeUninitialized ) .
Flatten ( )
2017-11-14 04:01:51 +00:00
if o . Local {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource.
if len ( args ) > 0 {
return resource . LocalResourceError
}
2017-11-15 04:03:06 +00:00
} else {
2017-11-14 03:43:58 +00:00
builder = builder .
LabelSelectorParam ( o . Selector ) .
ResourceTypeOrNameArgs ( o . All , args ... ) .
Latest ( )
2017-03-21 06:48:25 +00:00
}
2017-07-11 14:19:27 +00:00
2017-03-21 06:48:25 +00:00
o . Infos , err = builder . Do ( ) . Infos ( )
if err != nil {
return err
}
return nil
}
func ( o * SubjectOptions ) Validate ( ) error {
2017-08-25 08:28:21 +00:00
if o . All && len ( o . Selector ) > 0 {
return fmt . Errorf ( "cannot set --all and --selector at the same time" )
}
2017-03-21 06:48:25 +00:00
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
2018-02-21 17:10:38 +00:00
patches := CalculatePatches ( o . Infos , cmdutil . InternalVersionJSONEncoder ( ) , func ( info * resource . Info ) ( [ ] byte , error ) {
2017-03-21 06:48:25 +00:00
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 {
2017-11-14 04:01:51 +00:00
// TODO: switch UpdatePodSpecForObject to work on v1.PodSpec
2018-02-21 17:10:38 +00:00
return runtime . Encode ( cmdutil . InternalVersionJSONEncoder ( ) , info . AsVersioned ( ) )
2017-03-21 06:48:25 +00:00
}
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 {
2018-04-12 20:02:30 +00:00
if err := o . PrintObj ( info . Object , o . Out ) ; err != nil {
2017-10-26 07:33:22 +00:00
return err
}
continue
2017-03-21 06:48:25 +00:00
}
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 )
2018-04-12 20:02:30 +00:00
return o . PrintObj ( info . AsVersioned ( ) , o . Out )
2017-03-21 06:48:25 +00:00
}
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
}