1
16
17 package set
18
19 import (
20 "errors"
21 "fmt"
22 "regexp"
23 "sort"
24 "strings"
25
26 "github.com/spf13/cobra"
27
28 v1 "k8s.io/api/core/v1"
29 meta "k8s.io/apimachinery/pkg/api/meta"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/types"
32 utilerrors "k8s.io/apimachinery/pkg/util/errors"
33 "k8s.io/cli-runtime/pkg/genericclioptions"
34 "k8s.io/cli-runtime/pkg/genericiooptions"
35 "k8s.io/cli-runtime/pkg/printers"
36 "k8s.io/cli-runtime/pkg/resource"
37 "k8s.io/client-go/kubernetes"
38 envutil "k8s.io/kubectl/pkg/cmd/set/env"
39 cmdutil "k8s.io/kubectl/pkg/cmd/util"
40 "k8s.io/kubectl/pkg/polymorphichelpers"
41 "k8s.io/kubectl/pkg/scheme"
42 "k8s.io/kubectl/pkg/util/i18n"
43 "k8s.io/kubectl/pkg/util/templates"
44 "k8s.io/kubectl/pkg/util/term"
45 )
46
47 var (
48 validEnvNameRegexp = regexp.MustCompile("[^a-zA-Z0-9_]")
49 envResources = `
50 pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), statefulset (sts), cronjob (cj), replicaset (rs)`
51
52 envLong = templates.LongDesc(i18n.T(`
53 Update environment variables on a pod template.
54
55 List environment variable definitions in one or more pods, pod templates.
56 Add, update, or remove container environment variable definitions in one or
57 more pod templates (within replication controllers or deployment configurations).
58 View or modify the environment variable definitions on all containers in the
59 specified pods or pod templates, or just those that match a wildcard.
60
61 If "--env -" is passed, environment variables can be read from STDIN using the standard env
62 syntax.
63
64 Possible resources include (case insensitive):
65 `) + envResources)
66
67 envExample = templates.Examples(`
68 # Update deployment 'registry' with a new environment variable
69 kubectl set env deployment/registry STORAGE_DIR=/local
70
71 # List the environment variables defined on a deployments 'sample-build'
72 kubectl set env deployment/sample-build --list
73
74 # List the environment variables defined on all pods
75 kubectl set env pods --all --list
76
77 # Output modified deployment in YAML, and does not alter the object on the server
78 kubectl set env deployment/sample-build STORAGE_DIR=/data -o yaml
79
80 # Update all containers in all replication controllers in the project to have ENV=prod
81 kubectl set env rc --all ENV=prod
82
83 # Import environment from a secret
84 kubectl set env --from=secret/mysecret deployment/myapp
85
86 # Import environment from a config map with a prefix
87 kubectl set env --from=configmap/myconfigmap --prefix=MYSQL_ deployment/myapp
88
89 # Import specific keys from a config map
90 kubectl set env --keys=my-example-key --from=configmap/myconfigmap deployment/myapp
91
92 # Remove the environment variable ENV from container 'c1' in all deployment configs
93 kubectl set env deployments --all --containers="c1" ENV-
94
95 # Remove the environment variable ENV from a deployment definition on disk and
96 # update the deployment config on the server
97 kubectl set env -f deploy.json ENV-
98
99 # Set some of the local shell environment into a deployment config on the server
100 env | grep RAILS_ | kubectl set env -e - deployment/registry`)
101 )
102
103
104 type EnvOptions struct {
105 PrintFlags *genericclioptions.PrintFlags
106 resource.FilenameOptions
107
108 EnvParams []string
109 All bool
110 Resolve bool
111 List bool
112 Local bool
113 Overwrite bool
114 ContainerSelector string
115 Selector string
116 From string
117 Prefix string
118 Keys []string
119 fieldManager string
120
121 PrintObj printers.ResourcePrinterFunc
122
123 envArgs []string
124 resources []string
125 output string
126 dryRunStrategy cmdutil.DryRunStrategy
127 builder func() *resource.Builder
128 updatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
129 namespace string
130 enforceNamespace bool
131 clientset *kubernetes.Clientset
132
133 genericiooptions.IOStreams
134 WarningPrinter *printers.WarningPrinter
135 }
136
137
138
139 func NewEnvOptions(streams genericiooptions.IOStreams) *EnvOptions {
140 return &EnvOptions{
141 PrintFlags: genericclioptions.NewPrintFlags("env updated").WithTypeSetter(scheme.Scheme),
142
143 ContainerSelector: "*",
144 Overwrite: true,
145 IOStreams: streams,
146 }
147 }
148
149
150 func NewCmdEnv(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
151 o := NewEnvOptions(streams)
152
153 cmd := &cobra.Command{
154 Use: "env RESOURCE/NAME KEY_1=VAL_1 ... KEY_N=VAL_N",
155 DisableFlagsInUseLine: true,
156 Short: i18n.T("Update environment variables on a pod template"),
157 Long: envLong,
158 Example: envExample,
159 Run: func(cmd *cobra.Command, args []string) {
160 cmdutil.CheckErr(o.Complete(f, cmd, args))
161 cmdutil.CheckErr(o.Validate())
162 cmdutil.CheckErr(o.RunEnv())
163 },
164 }
165 usage := "the resource to update the env"
166 cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
167 cmd.Flags().StringVarP(&o.ContainerSelector, "containers", "c", o.ContainerSelector, "The names of containers in the selected pod templates to change - may use wildcards")
168 cmd.Flags().StringVarP(&o.From, "from", "", "", "The name of a resource from which to inject environment variables")
169 cmd.Flags().StringVarP(&o.Prefix, "prefix", "", "", "Prefix to append to variable names")
170 cmd.Flags().StringArrayVarP(&o.EnvParams, "env", "e", o.EnvParams, "Specify a key-value pair for an environment variable to set into each container.")
171 cmd.Flags().StringSliceVarP(&o.Keys, "keys", "", o.Keys, "Comma-separated list of keys to import from specified resource")
172 cmd.Flags().BoolVar(&o.List, "list", o.List, "If true, display the environment and any changes in the standard format. this flag will removed when we have kubectl view env.")
173 cmd.Flags().BoolVar(&o.Resolve, "resolve", o.Resolve, "If true, show secret or configmap references when listing variables")
174 cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set env will NOT contact api-server but run locally.")
175 cmd.Flags().BoolVar(&o.All, "all", o.All, "If true, select all resources in the namespace of the specified resource types")
176 cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "If true, allow environment to be overwritten, otherwise reject updates that overwrite existing environment.")
177 cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-set")
178 cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
179
180 o.PrintFlags.AddFlags(cmd)
181
182 cmdutil.AddDryRunFlag(cmd)
183 return cmd
184 }
185
186 func validateNoOverwrites(existing []v1.EnvVar, env []v1.EnvVar) error {
187 for _, e := range env {
188 if current, exists := findEnv(existing, e.Name); exists && current.Value != e.Value {
189 return fmt.Errorf("'%s' already has a value (%s), and --overwrite is false", current.Name, current.Value)
190 }
191 }
192 return nil
193 }
194
195 func contains(key string, keyList []string) bool {
196 if len(keyList) == 0 {
197 return true
198 }
199
200 for _, k := range keyList {
201 if k == key {
202 return true
203 }
204 }
205 return false
206 }
207
208 func (o *EnvOptions) keyToEnvName(key string) string {
209 envName := strings.ToUpper(validEnvNameRegexp.ReplaceAllString(key, "_"))
210 if envName != key {
211 o.WarningPrinter.Print(fmt.Sprintf("key %s transferred to %s", key, envName))
212 }
213 return envName
214 }
215
216
217 func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
218 if o.All && len(o.Selector) > 0 {
219 return fmt.Errorf("cannot set --all and --selector at the same time")
220 }
221 ok := false
222 o.resources, o.envArgs, ok = envutil.SplitEnvironmentFromResources(args)
223 if !ok {
224 return fmt.Errorf("all resources must be specified before environment changes: %s", strings.Join(args, " "))
225 }
226
227 o.updatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
228 o.output = cmdutil.GetFlagString(cmd, "output")
229 var err error
230 o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
231 if err != nil {
232 return err
233 }
234
235 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
236 printer, err := o.PrintFlags.ToPrinter()
237 if err != nil {
238 return err
239 }
240 o.PrintObj = printer.PrintObj
241
242 o.clientset, err = f.KubernetesClientSet()
243 if err != nil {
244 return err
245 }
246 o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
247 if err != nil {
248 return err
249 }
250 o.builder = f.NewBuilder
251
252 if o.WarningPrinter == nil {
253 o.WarningPrinter = printers.NewWarningPrinter(o.ErrOut, printers.WarningPrinterOptions{Color: term.AllowsColorOutput(o.ErrOut)})
254 }
255
256 return nil
257 }
258
259
260 func (o *EnvOptions) Validate() error {
261 if o.Local && o.dryRunStrategy == cmdutil.DryRunServer {
262 return fmt.Errorf("cannot specify --local and --dry-run=server - did you mean --dry-run=client?")
263 }
264 if len(o.Filenames) == 0 && len(o.resources) < 1 {
265 return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
266 }
267 if o.List && len(o.output) > 0 {
268 return fmt.Errorf("--list and --output may not be specified together")
269 }
270 if len(o.Keys) > 0 && len(o.From) == 0 {
271 return fmt.Errorf("when specifying --keys, a configmap or secret must be provided with --from")
272 }
273 if o.WarningPrinter == nil {
274 return fmt.Errorf("WarningPrinter can not be used without initialization")
275 }
276 return nil
277 }
278
279
280 func (o *EnvOptions) RunEnv() error {
281 env, remove, envFromStdin, err := envutil.ParseEnv(append(o.EnvParams, o.envArgs...), o.In)
282 if err != nil {
283 return err
284 }
285
286 if len(o.From) != 0 {
287 b := o.builder().
288 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
289 LocalParam(o.Local).
290 ContinueOnError().
291 NamespaceParam(o.namespace).DefaultNamespace().
292 FilenameParam(o.enforceNamespace, &o.FilenameOptions).
293 Flatten()
294
295 if !o.Local {
296 b = b.
297 LabelSelectorParam(o.Selector).
298 ResourceTypeOrNameArgs(o.All, o.From).
299 Latest()
300 }
301
302 if envFromStdin {
303 b = b.StdinInUse()
304 }
305
306 infos, err := b.Do().Infos()
307 if err != nil {
308 return err
309 }
310
311 for _, info := range infos {
312 switch from := info.Object.(type) {
313 case *v1.Secret:
314 for key := range from.Data {
315 if contains(key, o.Keys) {
316 envVar := v1.EnvVar{
317 Name: o.keyToEnvName(key),
318 ValueFrom: &v1.EnvVarSource{
319 SecretKeyRef: &v1.SecretKeySelector{
320 LocalObjectReference: v1.LocalObjectReference{
321 Name: from.Name,
322 },
323 Key: key,
324 },
325 },
326 }
327 env = append(env, envVar)
328 }
329 }
330 case *v1.ConfigMap:
331 for key := range from.Data {
332 if contains(key, o.Keys) {
333 envVar := v1.EnvVar{
334 Name: o.keyToEnvName(key),
335 ValueFrom: &v1.EnvVarSource{
336 ConfigMapKeyRef: &v1.ConfigMapKeySelector{
337 LocalObjectReference: v1.LocalObjectReference{
338 Name: from.Name,
339 },
340 Key: key,
341 },
342 },
343 }
344 env = append(env, envVar)
345 }
346 }
347 default:
348 return fmt.Errorf("unsupported resource specified in --from")
349 }
350 }
351 }
352
353 if len(o.Prefix) != 0 {
354 for i := range env {
355 env[i].Name = fmt.Sprintf("%s%s", o.Prefix, env[i].Name)
356 }
357 }
358
359 b := o.builder().
360 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
361 LocalParam(o.Local).
362 ContinueOnError().
363 NamespaceParam(o.namespace).DefaultNamespace().
364 FilenameParam(o.enforceNamespace, &o.FilenameOptions).
365 Flatten()
366
367 if !o.Local {
368 b.LabelSelectorParam(o.Selector).
369 ResourceTypeOrNameArgs(o.All, o.resources...).
370 Latest()
371 }
372
373 if envFromStdin {
374 b = b.StdinInUse()
375 }
376
377 infos, err := b.Do().Infos()
378 if err != nil {
379 return err
380 }
381 patches := CalculatePatches(infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
382 _, err := o.updatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
383 resolutionErrorsEncountered := false
384 initContainers, _ := selectContainers(spec.InitContainers, o.ContainerSelector)
385 containers, _ := selectContainers(spec.Containers, o.ContainerSelector)
386 containers = append(containers, initContainers...)
387 objName, err := meta.NewAccessor().Name(obj)
388 if err != nil {
389 return err
390 }
391
392 gvks, _, err := scheme.Scheme.ObjectKinds(obj)
393 if err != nil {
394 return err
395 }
396 objKind := obj.GetObjectKind().GroupVersionKind().Kind
397 if len(objKind) == 0 {
398 for _, gvk := range gvks {
399 if len(gvk.Kind) == 0 {
400 continue
401 }
402 if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
403 continue
404 }
405
406 objKind = gvk.Kind
407 break
408 }
409 }
410
411 if len(containers) == 0 {
412 if gvks, _, err := scheme.Scheme.ObjectKinds(obj); err == nil {
413 objKind := obj.GetObjectKind().GroupVersionKind().Kind
414 if len(objKind) == 0 {
415 for _, gvk := range gvks {
416 if len(gvk.Kind) == 0 {
417 continue
418 }
419 if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
420 continue
421 }
422
423 objKind = gvk.Kind
424 break
425 }
426 }
427
428 o.WarningPrinter.Print(fmt.Sprintf("%s/%s does not have any containers matching %q", objKind, objName, o.ContainerSelector))
429 }
430 return nil
431 }
432 for _, c := range containers {
433 if !o.Overwrite {
434 if err := validateNoOverwrites(c.Env, env); err != nil {
435 return err
436 }
437 }
438
439 c.Env = updateEnv(c.Env, env, remove)
440 if o.List {
441 resolveErrors := map[string][]string{}
442 store := envutil.NewResourceStore()
443
444 fmt.Fprintf(o.Out, "# %s %s, container %s\n", objKind, objName, c.Name)
445 for _, env := range c.Env {
446
447 if env.ValueFrom == nil {
448 fmt.Fprintf(o.Out, "%s=%s\n", env.Name, env.Value)
449 continue
450 }
451
452
453 if !o.Resolve {
454 fmt.Fprintf(o.Out, "# %s from %s\n", env.Name, envutil.GetEnvVarRefString(env.ValueFrom))
455 continue
456 }
457
458 value, err := envutil.GetEnvVarRefValue(o.clientset, o.namespace, store, env.ValueFrom, obj, c)
459
460 if err == nil {
461 fmt.Fprintf(o.Out, "%s=%s\n", env.Name, value)
462 continue
463 }
464
465
466 fmt.Fprintf(o.Out, "# %s from %s\n", env.Name, envutil.GetEnvVarRefString(env.ValueFrom))
467 errString := err.Error()
468 resolveErrors[errString] = append(resolveErrors[errString], env.Name)
469 resolutionErrorsEncountered = true
470 }
471
472
473 errs := []string{}
474 for err, vars := range resolveErrors {
475 sort.Strings(vars)
476 errs = append(errs, fmt.Sprintf("error retrieving reference for %s: %v", strings.Join(vars, ", "), err))
477 }
478 sort.Strings(errs)
479 for _, err := range errs {
480 fmt.Fprintln(o.ErrOut, err)
481 }
482 }
483 }
484 if resolutionErrorsEncountered {
485 return errors.New("failed to retrieve valueFrom references")
486 }
487 return nil
488 })
489
490 if err == nil {
491 return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
492 }
493 return nil, err
494 })
495
496 if o.List {
497 return nil
498 }
499
500 allErrs := []error{}
501
502 for _, patch := range patches {
503 info := patch.Info
504 if patch.Err != nil {
505 name := info.ObjectName()
506 allErrs = append(allErrs, fmt.Errorf("error: %s %v\n", name, patch.Err))
507 continue
508 }
509
510
511 if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
512 continue
513 }
514
515 if o.Local || o.dryRunStrategy == cmdutil.DryRunClient {
516 if err := o.PrintObj(info.Object, o.Out); err != nil {
517 allErrs = append(allErrs, err)
518 }
519 continue
520 }
521
522 actual, err := resource.
523 NewHelper(info.Client, info.Mapping).
524 DryRun(o.dryRunStrategy == cmdutil.DryRunServer).
525 WithFieldManager(o.fieldManager).
526 Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
527 if err != nil {
528 allErrs = append(allErrs, fmt.Errorf("failed to patch env update to pod template: %v", err))
529 continue
530 }
531
532
533
534 if len(env) == 0 && len(o.envArgs) == 0 {
535 return fmt.Errorf("at least one environment variable must be provided")
536 }
537
538 if err := o.PrintObj(actual, o.Out); err != nil {
539 allErrs = append(allErrs, err)
540 }
541 }
542 return utilerrors.NewAggregate(allErrs)
543 }
544
View as plain text