1
16
17 package config
18
19 import (
20 "encoding/base64"
21 "errors"
22 "fmt"
23 "io"
24 "reflect"
25 "strings"
26
27 "github.com/spf13/cobra"
28
29 "k8s.io/client-go/tools/clientcmd"
30 cliflag "k8s.io/component-base/cli/flag"
31 cmdutil "k8s.io/kubectl/pkg/cmd/util"
32 "k8s.io/kubectl/pkg/util/i18n"
33 "k8s.io/kubectl/pkg/util/templates"
34 )
35
36 type setOptions struct {
37 configAccess clientcmd.ConfigAccess
38 propertyName string
39 propertyValue string
40 setRawBytes cliflag.Tristate
41 }
42
43 var (
44 setLong = templates.LongDesc(i18n.T(`
45 Set an individual value in a kubeconfig file.
46
47 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.
48
49 PROPERTY_VALUE is the new value you want to set. Binary fields such as 'certificate-authority-data' expect a base64 encoded string unless the --set-raw-bytes flag is used.
50
51 Specifying an attribute name that already exists will merge new fields on top of existing values.`))
52
53 setExample = templates.Examples(`
54 # Set the server field on the my-cluster cluster to https://1.2.3.4
55 kubectl config set clusters.my-cluster.server https://1.2.3.4
56
57 # Set the certificate-authority-data field on the my-cluster cluster
58 kubectl config set clusters.my-cluster.certificate-authority-data $(echo "cert_data_here" | base64 -i -)
59
60 # Set the cluster field in the my-context context to my-cluster
61 kubectl config set contexts.my-context.cluster my-cluster
62
63 # Set the client-key-data field in the cluster-admin user using --set-raw-bytes option
64 kubectl config set users.cluster-admin.client-key-data cert_data_here --set-raw-bytes=true`)
65 )
66
67
68 func NewCmdConfigSet(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
69 options := &setOptions{configAccess: configAccess}
70
71 cmd := &cobra.Command{
72 Use: "set PROPERTY_NAME PROPERTY_VALUE",
73 DisableFlagsInUseLine: true,
74 Short: i18n.T("Set an individual value in a kubeconfig file"),
75 Long: setLong,
76 Example: setExample,
77 Run: func(cmd *cobra.Command, args []string) {
78 cmdutil.CheckErr(options.complete(cmd))
79 cmdutil.CheckErr(options.run())
80 fmt.Fprintf(out, "Property %q set.\n", options.propertyName)
81 },
82 }
83
84 f := cmd.Flags().VarPF(&options.setRawBytes, "set-raw-bytes", "", "When writing a []byte PROPERTY_VALUE, write the given string directly without base64 decoding.")
85 f.NoOptDefVal = "true"
86 return cmd
87 }
88
89 func (o setOptions) run() error {
90 err := o.validate()
91 if err != nil {
92 return err
93 }
94
95 config, err := o.configAccess.GetStartingConfig()
96 if err != nil {
97 return err
98 }
99 steps, err := newNavigationSteps(o.propertyName)
100 if err != nil {
101 return err
102 }
103
104 setRawBytes := false
105 if o.setRawBytes.Provided() {
106 setRawBytes = o.setRawBytes.Value()
107 }
108
109 err = modifyConfig(reflect.ValueOf(config), steps, o.propertyValue, false, setRawBytes)
110 if err != nil {
111 return err
112 }
113
114 if err := clientcmd.ModifyConfig(o.configAccess, *config, false); err != nil {
115 return err
116 }
117
118 return nil
119 }
120
121 func (o *setOptions) complete(cmd *cobra.Command) error {
122 endingArgs := cmd.Flags().Args()
123 if len(endingArgs) != 2 {
124 return helpErrorf(cmd, "Unexpected args: %v", endingArgs)
125 }
126
127 o.propertyValue = endingArgs[1]
128 o.propertyName = endingArgs[0]
129 return nil
130 }
131
132 func (o setOptions) validate() error {
133 if len(o.propertyValue) == 0 {
134 return errors.New("you cannot use set to unset a property")
135 }
136
137 if len(o.propertyName) == 0 {
138 return errors.New("you must specify a property")
139 }
140
141 return nil
142 }
143
144 func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue string, unset bool, setRawBytes bool) error {
145 currStep := steps.pop()
146
147 actualCurrValue := curr
148 if curr.Kind() == reflect.Pointer {
149 actualCurrValue = curr.Elem()
150 }
151
152 switch actualCurrValue.Kind() {
153 case reflect.Map:
154 if !steps.moreStepsRemaining() && !unset {
155 return fmt.Errorf("can't set a map to a value: %v", actualCurrValue)
156 }
157
158 mapKey := reflect.ValueOf(currStep.stepValue)
159 mapValueType := curr.Type().Elem().Elem()
160
161 if !steps.moreStepsRemaining() && unset {
162 actualCurrValue.SetMapIndex(mapKey, reflect.Value{})
163 return nil
164 }
165
166 currMapValue := actualCurrValue.MapIndex(mapKey)
167
168 needToSetNewMapValue := currMapValue.Kind() == reflect.Invalid
169 if needToSetNewMapValue {
170 if unset {
171 return fmt.Errorf("current map key `%v` is invalid", mapKey.Interface())
172 }
173 currMapValue = reflect.New(mapValueType.Elem()).Elem().Addr()
174 actualCurrValue.SetMapIndex(mapKey, currMapValue)
175 }
176
177 err := modifyConfig(currMapValue, steps, propertyValue, unset, setRawBytes)
178 if err != nil {
179 return err
180 }
181
182 return nil
183
184 case reflect.String:
185 if steps.moreStepsRemaining() {
186 return fmt.Errorf("can't have more steps after a string. %v", steps)
187 }
188 actualCurrValue.SetString(propertyValue)
189 return nil
190
191 case reflect.Slice:
192 if steps.moreStepsRemaining() {
193 return fmt.Errorf("can't have more steps after bytes. %v", steps)
194 }
195 innerKind := actualCurrValue.Type().Elem().Kind()
196 if innerKind != reflect.Uint8 {
197 return fmt.Errorf("unrecognized slice type. %v", innerKind)
198 }
199
200 if unset {
201 actualCurrValue.Set(reflect.Zero(actualCurrValue.Type()))
202 return nil
203 }
204
205 if setRawBytes {
206 actualCurrValue.SetBytes([]byte(propertyValue))
207 } else {
208 val, err := base64.StdEncoding.DecodeString(propertyValue)
209 if err != nil {
210 return fmt.Errorf("error decoding input value: %v", err)
211 }
212 actualCurrValue.SetBytes(val)
213 }
214 return nil
215
216 case reflect.Bool:
217 if steps.moreStepsRemaining() {
218 return fmt.Errorf("can't have more steps after a bool. %v", steps)
219 }
220 boolValue, err := toBool(propertyValue)
221 if err != nil {
222 return err
223 }
224 actualCurrValue.SetBool(boolValue)
225 return nil
226
227 case reflect.Struct:
228 for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
229 currFieldValue := actualCurrValue.Field(fieldIndex)
230 currFieldType := actualCurrValue.Type().Field(fieldIndex)
231 currYamlTag := currFieldType.Tag.Get("json")
232 currFieldTypeYamlName := strings.Split(currYamlTag, ",")[0]
233
234 if currFieldTypeYamlName == currStep.stepValue {
235 thisMapHasNoValue := (currFieldValue.Kind() == reflect.Map && currFieldValue.IsNil())
236
237 if thisMapHasNoValue {
238 newValue := reflect.MakeMap(currFieldValue.Type())
239 currFieldValue.Set(newValue)
240
241 if !steps.moreStepsRemaining() && unset {
242 return nil
243 }
244 }
245
246 if !steps.moreStepsRemaining() && unset {
247
248 newValue := reflect.New(currFieldValue.Type()).Elem()
249 currFieldValue.Set(newValue)
250 return nil
251 }
252
253 return modifyConfig(currFieldValue.Addr(), steps, propertyValue, unset, setRawBytes)
254 }
255 }
256
257 return fmt.Errorf("unable to locate path %#v under %v", currStep, actualCurrValue)
258
259 }
260
261 panic(fmt.Errorf("unrecognized type: %v", actualCurrValue))
262 }
263
View as plain text