set_env.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package set
  14. import (
  15. "errors"
  16. "fmt"
  17. "regexp"
  18. "sort"
  19. "strings"
  20. "github.com/spf13/cobra"
  21. "k8s.io/api/core/v1"
  22. meta "k8s.io/apimachinery/pkg/api/meta"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/types"
  25. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  26. "k8s.io/cli-runtime/pkg/genericclioptions"
  27. "k8s.io/cli-runtime/pkg/printers"
  28. "k8s.io/cli-runtime/pkg/resource"
  29. "k8s.io/client-go/kubernetes"
  30. envutil "k8s.io/kubernetes/pkg/kubectl/cmd/set/env"
  31. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  32. "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
  33. "k8s.io/kubernetes/pkg/kubectl/scheme"
  34. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  35. )
  36. var (
  37. validEnvNameRegexp = regexp.MustCompile("[^a-zA-Z0-9_]")
  38. envResources = `
  39. pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), job, replicaset (rs)`
  40. envLong = templates.LongDesc(`
  41. Update environment variables on a pod template.
  42. List environment variable definitions in one or more pods, pod templates.
  43. Add, update, or remove container environment variable definitions in one or
  44. more pod templates (within replication controllers or deployment configurations).
  45. View or modify the environment variable definitions on all containers in the
  46. specified pods or pod templates, or just those that match a wildcard.
  47. If "--env -" is passed, environment variables can be read from STDIN using the standard env
  48. syntax.
  49. Possible resources include (case insensitive):
  50. ` + envResources)
  51. envExample = templates.Examples(`
  52. # Update deployment 'registry' with a new environment variable
  53. kubectl set env deployment/registry STORAGE_DIR=/local
  54. # List the environment variables defined on a deployments 'sample-build'
  55. kubectl set env deployment/sample-build --list
  56. # List the environment variables defined on all pods
  57. kubectl set env pods --all --list
  58. # Output modified deployment in YAML, and does not alter the object on the server
  59. kubectl set env deployment/sample-build STORAGE_DIR=/data -o yaml
  60. # Update all containers in all replication controllers in the project to have ENV=prod
  61. kubectl set env rc --all ENV=prod
  62. # Import environment from a secret
  63. kubectl set env --from=secret/mysecret deployment/myapp
  64. # Import environment from a config map with a prefix
  65. kubectl set env --from=configmap/myconfigmap --prefix=MYSQL_ deployment/myapp
  66. # Import specific keys from a config map
  67. kubectl set env --keys=my-example-key --from=configmap/myconfigmap deployment/myapp
  68. # Remove the environment variable ENV from container 'c1' in all deployment configs
  69. kubectl set env deployments --all --containers="c1" ENV-
  70. # Remove the environment variable ENV from a deployment definition on disk and
  71. # update the deployment config on the server
  72. kubectl set env -f deploy.json ENV-
  73. # Set some of the local shell environment into a deployment config on the server
  74. env | grep RAILS_ | kubectl set env -e - deployment/registry`)
  75. )
  76. // EnvOptions holds values for 'set env' command-lone options
  77. type EnvOptions struct {
  78. PrintFlags *genericclioptions.PrintFlags
  79. resource.FilenameOptions
  80. EnvParams []string
  81. All bool
  82. Resolve bool
  83. List bool
  84. Local bool
  85. Overwrite bool
  86. ContainerSelector string
  87. Selector string
  88. From string
  89. Prefix string
  90. Keys []string
  91. PrintObj printers.ResourcePrinterFunc
  92. envArgs []string
  93. resources []string
  94. output string
  95. dryRun bool
  96. builder func() *resource.Builder
  97. updatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
  98. namespace string
  99. enforceNamespace bool
  100. clientset *kubernetes.Clientset
  101. genericclioptions.IOStreams
  102. }
  103. // NewEnvOptions returns an EnvOptions indicating all containers in the selected
  104. // pod templates are selected by default and allowing environment to be overwritten
  105. func NewEnvOptions(streams genericclioptions.IOStreams) *EnvOptions {
  106. return &EnvOptions{
  107. PrintFlags: genericclioptions.NewPrintFlags("env updated").WithTypeSetter(scheme.Scheme),
  108. ContainerSelector: "*",
  109. Overwrite: true,
  110. IOStreams: streams,
  111. }
  112. }
  113. // NewCmdEnv implements the OpenShift cli env command
  114. func NewCmdEnv(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
  115. o := NewEnvOptions(streams)
  116. cmd := &cobra.Command{
  117. Use: "env RESOURCE/NAME KEY_1=VAL_1 ... KEY_N=VAL_N",
  118. DisableFlagsInUseLine: true,
  119. Short: "Update environment variables on a pod template",
  120. Long: envLong,
  121. Example: fmt.Sprintf(envExample),
  122. Run: func(cmd *cobra.Command, args []string) {
  123. cmdutil.CheckErr(o.Complete(f, cmd, args))
  124. cmdutil.CheckErr(o.Validate())
  125. cmdutil.CheckErr(o.RunEnv())
  126. },
  127. }
  128. usage := "the resource to update the env"
  129. cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
  130. cmd.Flags().StringVarP(&o.ContainerSelector, "containers", "c", o.ContainerSelector, "The names of containers in the selected pod templates to change - may use wildcards")
  131. cmd.Flags().StringVarP(&o.From, "from", "", "", "The name of a resource from which to inject environment variables")
  132. cmd.Flags().StringVarP(&o.Prefix, "prefix", "", "", "Prefix to append to variable names")
  133. cmd.Flags().StringArrayVarP(&o.EnvParams, "env", "e", o.EnvParams, "Specify a key-value pair for an environment variable to set into each container.")
  134. cmd.Flags().StringSliceVarP(&o.Keys, "keys", "", o.Keys, "Comma-separated list of keys to import from specified resource")
  135. 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.")
  136. cmd.Flags().BoolVar(&o.Resolve, "resolve", o.Resolve, "If true, show secret or configmap references when listing variables")
  137. cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on")
  138. cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set env will NOT contact api-server but run locally.")
  139. cmd.Flags().BoolVar(&o.All, "all", o.All, "If true, select all resources in the namespace of the specified resource types")
  140. cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "If true, allow environment to be overwritten, otherwise reject updates that overwrite existing environment.")
  141. o.PrintFlags.AddFlags(cmd)
  142. cmdutil.AddDryRunFlag(cmd)
  143. return cmd
  144. }
  145. func validateNoOverwrites(existing []v1.EnvVar, env []v1.EnvVar) error {
  146. for _, e := range env {
  147. if current, exists := findEnv(existing, e.Name); exists && current.Value != e.Value {
  148. return fmt.Errorf("'%s' already has a value (%s), and --overwrite is false", current.Name, current.Value)
  149. }
  150. }
  151. return nil
  152. }
  153. func keyToEnvName(key string) string {
  154. return strings.ToUpper(validEnvNameRegexp.ReplaceAllString(key, "_"))
  155. }
  156. func contains(key string, keyList []string) bool {
  157. if len(keyList) == 0 {
  158. return true
  159. }
  160. for _, k := range keyList {
  161. if k == key {
  162. return true
  163. }
  164. }
  165. return false
  166. }
  167. // Complete completes all required options
  168. func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
  169. if o.All && len(o.Selector) > 0 {
  170. return fmt.Errorf("cannot set --all and --selector at the same time")
  171. }
  172. ok := false
  173. o.resources, o.envArgs, ok = envutil.SplitEnvironmentFromResources(args)
  174. if !ok {
  175. return fmt.Errorf("all resources must be specified before environment changes: %s", strings.Join(args, " "))
  176. }
  177. o.updatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
  178. o.output = cmdutil.GetFlagString(cmd, "output")
  179. o.dryRun = cmdutil.GetDryRunFlag(cmd)
  180. if o.dryRun {
  181. // TODO(juanvallejo): This can be cleaned up even further by creating
  182. // a PrintFlags struct that binds the --dry-run flag, and whose
  183. // ToPrinter method returns a printer that understands how to print
  184. // this success message.
  185. o.PrintFlags.Complete("%s (dry run)")
  186. }
  187. printer, err := o.PrintFlags.ToPrinter()
  188. if err != nil {
  189. return err
  190. }
  191. o.PrintObj = printer.PrintObj
  192. o.clientset, err = f.KubernetesClientSet()
  193. if err != nil {
  194. return err
  195. }
  196. o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
  197. if err != nil {
  198. return err
  199. }
  200. o.builder = f.NewBuilder
  201. return nil
  202. }
  203. // Validate makes sure provided values for EnvOptions are valid
  204. func (o *EnvOptions) Validate() error {
  205. if len(o.Filenames) == 0 && len(o.resources) < 1 {
  206. return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
  207. }
  208. if o.List && len(o.output) > 0 {
  209. return fmt.Errorf("--list and --output may not be specified together")
  210. }
  211. if len(o.Keys) > 0 && len(o.From) == 0 {
  212. return fmt.Errorf("when specifying --keys, a configmap or secret must be provided with --from")
  213. }
  214. return nil
  215. }
  216. // RunEnv contains all the necessary functionality for the OpenShift cli env command
  217. func (o *EnvOptions) RunEnv() error {
  218. env, remove, err := envutil.ParseEnv(append(o.EnvParams, o.envArgs...), o.In)
  219. if err != nil {
  220. return err
  221. }
  222. if len(o.From) != 0 {
  223. b := o.builder().
  224. WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
  225. LocalParam(o.Local).
  226. ContinueOnError().
  227. NamespaceParam(o.namespace).DefaultNamespace().
  228. FilenameParam(o.enforceNamespace, &o.FilenameOptions).
  229. Flatten()
  230. if !o.Local {
  231. b = b.
  232. LabelSelectorParam(o.Selector).
  233. ResourceTypeOrNameArgs(o.All, o.From).
  234. Latest()
  235. }
  236. infos, err := b.Do().Infos()
  237. if err != nil {
  238. return err
  239. }
  240. for _, info := range infos {
  241. switch from := info.Object.(type) {
  242. case *v1.Secret:
  243. for key := range from.Data {
  244. if contains(key, o.Keys) {
  245. envVar := v1.EnvVar{
  246. Name: keyToEnvName(key),
  247. ValueFrom: &v1.EnvVarSource{
  248. SecretKeyRef: &v1.SecretKeySelector{
  249. LocalObjectReference: v1.LocalObjectReference{
  250. Name: from.Name,
  251. },
  252. Key: key,
  253. },
  254. },
  255. }
  256. env = append(env, envVar)
  257. }
  258. }
  259. case *v1.ConfigMap:
  260. for key := range from.Data {
  261. if contains(key, o.Keys) {
  262. envVar := v1.EnvVar{
  263. Name: keyToEnvName(key),
  264. ValueFrom: &v1.EnvVarSource{
  265. ConfigMapKeyRef: &v1.ConfigMapKeySelector{
  266. LocalObjectReference: v1.LocalObjectReference{
  267. Name: from.Name,
  268. },
  269. Key: key,
  270. },
  271. },
  272. }
  273. env = append(env, envVar)
  274. }
  275. }
  276. default:
  277. return fmt.Errorf("unsupported resource specified in --from")
  278. }
  279. }
  280. }
  281. if len(o.Prefix) != 0 {
  282. for i := range env {
  283. env[i].Name = fmt.Sprintf("%s%s", o.Prefix, env[i].Name)
  284. }
  285. }
  286. b := o.builder().
  287. WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
  288. LocalParam(o.Local).
  289. ContinueOnError().
  290. NamespaceParam(o.namespace).DefaultNamespace().
  291. FilenameParam(o.enforceNamespace, &o.FilenameOptions).
  292. Flatten()
  293. if !o.Local {
  294. b.LabelSelectorParam(o.Selector).
  295. ResourceTypeOrNameArgs(o.All, o.resources...).
  296. Latest()
  297. }
  298. infos, err := b.Do().Infos()
  299. if err != nil {
  300. return err
  301. }
  302. patches := CalculatePatches(infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
  303. _, err := o.updatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
  304. resolutionErrorsEncountered := false
  305. containers, _ := selectContainers(spec.Containers, o.ContainerSelector)
  306. objName, err := meta.NewAccessor().Name(obj)
  307. if err != nil {
  308. return err
  309. }
  310. gvks, _, err := scheme.Scheme.ObjectKinds(obj)
  311. if err != nil {
  312. return err
  313. }
  314. objKind := obj.GetObjectKind().GroupVersionKind().Kind
  315. if len(objKind) == 0 {
  316. for _, gvk := range gvks {
  317. if len(gvk.Kind) == 0 {
  318. continue
  319. }
  320. if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
  321. continue
  322. }
  323. objKind = gvk.Kind
  324. break
  325. }
  326. }
  327. if len(containers) == 0 {
  328. if gvks, _, err := scheme.Scheme.ObjectKinds(obj); err == nil {
  329. objKind := obj.GetObjectKind().GroupVersionKind().Kind
  330. if len(objKind) == 0 {
  331. for _, gvk := range gvks {
  332. if len(gvk.Kind) == 0 {
  333. continue
  334. }
  335. if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
  336. continue
  337. }
  338. objKind = gvk.Kind
  339. break
  340. }
  341. }
  342. fmt.Fprintf(o.ErrOut, "warning: %s/%s does not have any containers matching %q\n", objKind, objName, o.ContainerSelector)
  343. }
  344. return nil
  345. }
  346. for _, c := range containers {
  347. if !o.Overwrite {
  348. if err := validateNoOverwrites(c.Env, env); err != nil {
  349. return err
  350. }
  351. }
  352. c.Env = updateEnv(c.Env, env, remove)
  353. if o.List {
  354. resolveErrors := map[string][]string{}
  355. store := envutil.NewResourceStore()
  356. fmt.Fprintf(o.Out, "# %s %s, container %s\n", objKind, objName, c.Name)
  357. for _, env := range c.Env {
  358. // Print the simple value
  359. if env.ValueFrom == nil {
  360. fmt.Fprintf(o.Out, "%s=%s\n", env.Name, env.Value)
  361. continue
  362. }
  363. // Print the reference version
  364. if !o.Resolve {
  365. fmt.Fprintf(o.Out, "# %s from %s\n", env.Name, envutil.GetEnvVarRefString(env.ValueFrom))
  366. continue
  367. }
  368. value, err := envutil.GetEnvVarRefValue(o.clientset, o.namespace, store, env.ValueFrom, obj, c)
  369. // Print the resolved value
  370. if err == nil {
  371. fmt.Fprintf(o.Out, "%s=%s\n", env.Name, value)
  372. continue
  373. }
  374. // Print the reference version and save the resolve error
  375. fmt.Fprintf(o.Out, "# %s from %s\n", env.Name, envutil.GetEnvVarRefString(env.ValueFrom))
  376. errString := err.Error()
  377. resolveErrors[errString] = append(resolveErrors[errString], env.Name)
  378. resolutionErrorsEncountered = true
  379. }
  380. // Print any resolution errors
  381. errs := []string{}
  382. for err, vars := range resolveErrors {
  383. sort.Strings(vars)
  384. errs = append(errs, fmt.Sprintf("error retrieving reference for %s: %v", strings.Join(vars, ", "), err))
  385. }
  386. sort.Strings(errs)
  387. for _, err := range errs {
  388. fmt.Fprintln(o.ErrOut, err)
  389. }
  390. }
  391. }
  392. if resolutionErrorsEncountered {
  393. return errors.New("failed to retrieve valueFrom references")
  394. }
  395. return nil
  396. })
  397. if err == nil {
  398. return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
  399. }
  400. return nil, err
  401. })
  402. if o.List {
  403. return nil
  404. }
  405. allErrs := []error{}
  406. for _, patch := range patches {
  407. info := patch.Info
  408. if patch.Err != nil {
  409. name := info.ObjectName()
  410. allErrs = append(allErrs, fmt.Errorf("error: %s %v\n", name, patch.Err))
  411. continue
  412. }
  413. // no changes
  414. if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
  415. continue
  416. }
  417. if o.Local || o.dryRun {
  418. if err := o.PrintObj(info.Object, o.Out); err != nil {
  419. allErrs = append(allErrs, err)
  420. }
  421. continue
  422. }
  423. actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
  424. if err != nil {
  425. allErrs = append(allErrs, fmt.Errorf("failed to patch env update to pod template: %v", err))
  426. continue
  427. }
  428. // make sure arguments to set or replace environment variables are set
  429. // before returning a successful message
  430. if len(env) == 0 && len(o.envArgs) == 0 {
  431. return fmt.Errorf("at least one environment variable must be provided")
  432. }
  433. if err := o.PrintObj(actual, o.Out); err != nil {
  434. allErrs = append(allErrs, err)
  435. }
  436. }
  437. return utilerrors.NewAggregate(allErrs)
  438. }