...

Source file src/k8s.io/kubectl/pkg/cmd/set/set_env.go

Documentation: k8s.io/kubectl/pkg/cmd/set

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  // EnvOptions holds values for 'set env' command-lone options
   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  // NewEnvOptions returns an EnvOptions indicating all containers in the selected
   138  // pod templates are selected by default and allowing environment to be overwritten
   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  // NewCmdEnv implements the OpenShift cli env command
   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  // Complete completes all required options
   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  	// Set default WarningPrinter if not already set.
   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  // Validate makes sure provided values for EnvOptions are valid
   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  // RunEnv contains all the necessary functionality for the OpenShift cli env command
   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  						// Print the simple value
   447  						if env.ValueFrom == nil {
   448  							fmt.Fprintf(o.Out, "%s=%s\n", env.Name, env.Value)
   449  							continue
   450  						}
   451  
   452  						// Print the reference version
   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  						// Print the resolved value
   460  						if err == nil {
   461  							fmt.Fprintf(o.Out, "%s=%s\n", env.Name, value)
   462  							continue
   463  						}
   464  
   465  						// Print the reference version and save the resolve error
   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  					// Print any resolution errors
   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  		// no changes
   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  		// make sure arguments to set or replace environment variables are set
   533  		// before returning a successful message
   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