2019-01-12 04:58:27 +00:00
/ *
Copyright 2014 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 config
import (
"encoding/base64"
"errors"
"fmt"
"io"
"reflect"
"strings"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
2019-04-07 17:07:55 +00:00
cliflag "k8s.io/component-base/cli/flag"
2019-09-27 21:51:53 +00:00
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
2019-01-12 04:58:27 +00:00
)
type setOptions struct {
configAccess clientcmd . ConfigAccess
propertyName string
propertyValue string
2019-04-07 17:07:55 +00:00
setRawBytes cliflag . Tristate
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
var (
2020-12-01 01:06:26 +00:00
setLong = templates . LongDesc ( i18n . T ( `
2019-01-12 04:58:27 +00:00
Sets an individual value in a kubeconfig file
PROPERTY_NAME is a dot delimited name where each token represents either an attribute name or a map key . Map keys may not contain dots .
2019-04-07 17:07:55 +00:00
PROPERTY_VALUE is the new value you wish to set . Binary fields such as ' certificate - authority - data ' expect a base64 encoded string unless the -- set - raw - bytes flag is used .
2019-01-12 04:58:27 +00:00
2020-12-01 01:06:26 +00:00
Specifying a attribute name that already exists will merge new fields on top of existing values . ` ) )
2019-04-07 17:07:55 +00:00
setExample = templates . Examples ( `
# Set server field on the my - cluster cluster to https : //1.2.3.4
kubectl config set clusters . my - cluster . server https : //1.2.3.4
# Set certificate - authority - data field on the my - cluster cluster .
kubectl config set clusters . my - cluster . certificate - authority - data $ ( echo "cert_data_here" | base64 - i - )
# Set cluster field in the my - context context to my - cluster .
kubectl config set contexts . my - context . cluster my - cluster
# Set client - key - data field in the cluster - admin user using -- set - raw - bytes option .
kubectl config set users . cluster - admin . client - key - data cert_data_here -- set - raw - bytes = true ` )
)
// NewCmdConfigSet returns a Command instance for 'config set' sub command
2019-01-12 04:58:27 +00:00
func NewCmdConfigSet ( out io . Writer , configAccess clientcmd . ConfigAccess ) * cobra . Command {
options := & setOptions { configAccess : configAccess }
cmd := & cobra . Command {
Use : "set PROPERTY_NAME PROPERTY_VALUE" ,
DisableFlagsInUseLine : true ,
Short : i18n . T ( "Sets an individual value in a kubeconfig file" ) ,
2019-04-07 17:07:55 +00:00
Long : setLong ,
Example : setExample ,
2019-01-12 04:58:27 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( options . complete ( cmd ) )
cmdutil . CheckErr ( options . run ( ) )
fmt . Fprintf ( out , "Property %q set.\n" , options . propertyName )
} ,
}
f := cmd . Flags ( ) . VarPF ( & options . setRawBytes , "set-raw-bytes" , "" , "When writing a []byte PROPERTY_VALUE, write the given string directly without base64 decoding." )
f . NoOptDefVal = "true"
return cmd
}
func ( o setOptions ) run ( ) error {
err := o . validate ( )
if err != nil {
return err
}
config , err := o . configAccess . GetStartingConfig ( )
if err != nil {
return err
}
steps , err := newNavigationSteps ( o . propertyName )
if err != nil {
return err
}
setRawBytes := false
if o . setRawBytes . Provided ( ) {
setRawBytes = o . setRawBytes . Value ( )
}
err = modifyConfig ( reflect . ValueOf ( config ) , steps , o . propertyValue , false , setRawBytes )
if err != nil {
return err
}
if err := clientcmd . ModifyConfig ( o . configAccess , * config , false ) ; err != nil {
return err
}
return nil
}
func ( o * setOptions ) complete ( cmd * cobra . Command ) error {
endingArgs := cmd . Flags ( ) . Args ( )
if len ( endingArgs ) != 2 {
return helpErrorf ( cmd , "Unexpected args: %v" , endingArgs )
}
o . propertyValue = endingArgs [ 1 ]
o . propertyName = endingArgs [ 0 ]
return nil
}
func ( o setOptions ) validate ( ) error {
if len ( o . propertyValue ) == 0 {
return errors . New ( "you cannot use set to unset a property" )
}
if len ( o . propertyName ) == 0 {
return errors . New ( "you must specify a property" )
}
return nil
}
func modifyConfig ( curr reflect . Value , steps * navigationSteps , propertyValue string , unset bool , setRawBytes bool ) error {
currStep := steps . pop ( )
actualCurrValue := curr
if curr . Kind ( ) == reflect . Ptr {
actualCurrValue = curr . Elem ( )
}
switch actualCurrValue . Kind ( ) {
case reflect . Map :
if ! steps . moreStepsRemaining ( ) && ! unset {
return fmt . Errorf ( "can't set a map to a value: %v" , actualCurrValue )
}
mapKey := reflect . ValueOf ( currStep . stepValue )
mapValueType := curr . Type ( ) . Elem ( ) . Elem ( )
if ! steps . moreStepsRemaining ( ) && unset {
actualCurrValue . SetMapIndex ( mapKey , reflect . Value { } )
return nil
}
currMapValue := actualCurrValue . MapIndex ( mapKey )
needToSetNewMapValue := currMapValue . Kind ( ) == reflect . Invalid
if needToSetNewMapValue {
if unset {
return fmt . Errorf ( "current map key `%v` is invalid" , mapKey . Interface ( ) )
}
currMapValue = reflect . New ( mapValueType . Elem ( ) ) . Elem ( ) . Addr ( )
actualCurrValue . SetMapIndex ( mapKey , currMapValue )
}
err := modifyConfig ( currMapValue , steps , propertyValue , unset , setRawBytes )
if err != nil {
return err
}
return nil
case reflect . String :
if steps . moreStepsRemaining ( ) {
return fmt . Errorf ( "can't have more steps after a string. %v" , steps )
}
actualCurrValue . SetString ( propertyValue )
return nil
case reflect . Slice :
if steps . moreStepsRemaining ( ) {
return fmt . Errorf ( "can't have more steps after bytes. %v" , steps )
}
innerKind := actualCurrValue . Type ( ) . Elem ( ) . Kind ( )
if innerKind != reflect . Uint8 {
return fmt . Errorf ( "unrecognized slice type. %v" , innerKind )
}
if unset {
actualCurrValue . Set ( reflect . Zero ( actualCurrValue . Type ( ) ) )
return nil
}
if setRawBytes {
actualCurrValue . SetBytes ( [ ] byte ( propertyValue ) )
} else {
val , err := base64 . StdEncoding . DecodeString ( propertyValue )
if err != nil {
return fmt . Errorf ( "error decoding input value: %v" , err )
}
actualCurrValue . SetBytes ( val )
}
return nil
case reflect . Bool :
if steps . moreStepsRemaining ( ) {
return fmt . Errorf ( "can't have more steps after a bool. %v" , steps )
}
boolValue , err := toBool ( propertyValue )
if err != nil {
return err
}
actualCurrValue . SetBool ( boolValue )
return nil
case reflect . Struct :
for fieldIndex := 0 ; fieldIndex < actualCurrValue . NumField ( ) ; fieldIndex ++ {
currFieldValue := actualCurrValue . Field ( fieldIndex )
currFieldType := actualCurrValue . Type ( ) . Field ( fieldIndex )
currYamlTag := currFieldType . Tag . Get ( "json" )
currFieldTypeYamlName := strings . Split ( currYamlTag , "," ) [ 0 ]
if currFieldTypeYamlName == currStep . stepValue {
thisMapHasNoValue := ( currFieldValue . Kind ( ) == reflect . Map && currFieldValue . IsNil ( ) )
if thisMapHasNoValue {
newValue := reflect . MakeMap ( currFieldValue . Type ( ) )
currFieldValue . Set ( newValue )
if ! steps . moreStepsRemaining ( ) && unset {
return nil
}
}
if ! steps . moreStepsRemaining ( ) && unset {
// if we're supposed to unset the value or if the value is a map that doesn't exist, create a new value and overwrite
newValue := reflect . New ( currFieldValue . Type ( ) ) . Elem ( )
currFieldValue . Set ( newValue )
return nil
}
return modifyConfig ( currFieldValue . Addr ( ) , steps , propertyValue , unset , setRawBytes )
}
}
return fmt . Errorf ( "unable to locate path %#v under %v" , currStep , actualCurrValue )
}
panic ( fmt . Errorf ( "unrecognized type: %v" , actualCurrValue ) )
}