2017-08-25 04:06:29 +00:00
/ *
Copyright 2017 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 set
import (
"errors"
"fmt"
"regexp"
"sort"
"strings"
"github.com/spf13/cobra"
2018-04-26 13:30:21 +00:00
2017-11-10 18:36:12 +00:00
"k8s.io/api/core/v1"
2018-05-21 21:56:56 +00:00
meta "k8s.io/apimachinery/pkg/api/meta"
2017-08-25 04:06:29 +00:00
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
2018-04-26 13:30:21 +00:00
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2018-08-21 10:46:39 +00:00
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
2017-11-10 18:36:12 +00:00
"k8s.io/client-go/kubernetes"
2018-05-29 13:57:15 +00:00
envutil "k8s.io/kubernetes/pkg/kubectl/cmd/set/env"
2017-08-25 04:06:29 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2018-05-21 20:06:58 +00:00
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
2018-05-02 20:25:48 +00:00
"k8s.io/kubernetes/pkg/kubectl/scheme"
2018-10-10 18:29:30 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/templates"
2017-08-25 04:06:29 +00:00
)
var (
2017-09-04 19:56:06 +00:00
validEnvNameRegexp = regexp . MustCompile ( "[^a-zA-Z0-9_]" )
envResources = `
2017-08-25 04:06:29 +00:00
pod ( po ) , replicationcontroller ( rc ) , deployment ( deploy ) , daemonset ( ds ) , job , replicaset ( rs ) `
envLong = templates . LongDesc ( `
Update environment variables on a pod template .
List environment variable definitions in one or more pods , pod templates .
Add , update , or remove container environment variable definitions in one or
more pod templates ( within replication controllers or deployment configurations ) .
View or modify the environment variable definitions on all containers in the
specified pods or pod templates , or just those that match a wildcard .
If "--env -" is passed , environment variables can be read from STDIN using the standard env
syntax .
Possible resources include ( case insensitive ) :
` + envResources )
envExample = templates . Examples ( `
2018-05-20 09:55:47 +00:00
# Update deployment ' registry ' with a new environment variable
2017-08-25 04:06:29 +00:00
kubectl set env deployment / registry STORAGE_DIR = / local
# List the environment variables defined on a deployments ' sample - build '
kubectl set env deployment / sample - build -- list
# List the environment variables defined on all pods
kubectl set env pods -- all -- list
# Output modified deployment in YAML , and does not alter the object on the server
kubectl set env deployment / sample - build STORAGE_DIR = / data - o yaml
# Update all containers in all replication controllers in the project to have ENV = prod
kubectl set env rc -- all ENV = prod
# Import environment from a secret
2017-10-20 04:48:34 +00:00
kubectl set env -- from = secret / mysecret deployment / myapp
2017-08-25 04:06:29 +00:00
# Import environment from a config map with a prefix
2017-10-20 04:48:34 +00:00
kubectl set env -- from = configmap / myconfigmap -- prefix = MYSQL_ deployment / myapp
2017-08-25 04:06:29 +00:00
2018-05-20 09:55:47 +00:00
# Import specific keys from a config map
kubectl set env -- keys = my - example - key -- from = configmap / myconfigmap deployment / myapp
2017-08-25 04:06:29 +00:00
# Remove the environment variable ENV from container ' c1 ' in all deployment configs
kubectl set env deployments -- all -- containers = "c1" ENV -
# Remove the environment variable ENV from a deployment definition on disk and
# update the deployment config on the server
kubectl set env - f deploy . json ENV -
# Set some of the local shell environment into a deployment config on the server
2017-10-20 04:48:34 +00:00
env | grep RAILS_ | kubectl set env - e - deployment / registry ` )
2017-08-25 04:06:29 +00:00
)
2018-10-30 10:35:24 +00:00
// EnvOptions holds values for 'set env' command-lone options
2017-08-25 04:06:29 +00:00
type EnvOptions struct {
2018-05-02 19:15:47 +00:00
PrintFlags * genericclioptions . PrintFlags
2017-08-25 04:06:29 +00:00
resource . FilenameOptions
2018-04-26 13:30:21 +00:00
EnvParams [ ] string
All bool
Resolve bool
List bool
Local bool
Overwrite bool
2017-08-25 04:06:29 +00:00
ContainerSelector string
Selector string
From string
Prefix string
2018-05-20 09:55:47 +00:00
Keys [ ] string
2017-08-25 04:06:29 +00:00
2018-04-12 20:02:30 +00:00
PrintObj printers . ResourcePrinterFunc
2018-04-04 19:25:51 +00:00
2018-04-26 13:30:21 +00:00
envArgs [ ] string
resources [ ] string
output string
dryRun bool
builder func ( ) * resource . Builder
2018-05-21 20:06:58 +00:00
updatePodSpecForObject polymorphichelpers . UpdatePodSpecForObjectFunc
2018-04-26 13:30:21 +00:00
namespace string
enforceNamespace bool
clientset * kubernetes . Clientset
2018-04-25 12:32:08 +00:00
genericclioptions . IOStreams
2017-08-25 04:06:29 +00:00
}
2018-02-24 09:01:30 +00:00
// NewEnvOptions returns an EnvOptions indicating all containers in the selected
// pod templates are selected by default and allowing environment to be overwritten
2018-04-25 12:32:08 +00:00
func NewEnvOptions ( streams genericclioptions . IOStreams ) * EnvOptions {
2018-02-24 09:01:30 +00:00
return & EnvOptions {
2018-05-02 19:15:47 +00:00
PrintFlags : genericclioptions . NewPrintFlags ( "env updated" ) . WithTypeSetter ( scheme . Scheme ) ,
2018-04-04 19:25:51 +00:00
2018-02-24 09:01:30 +00:00
ContainerSelector : "*" ,
Overwrite : true ,
2018-04-25 12:32:08 +00:00
IOStreams : streams ,
2018-02-24 09:01:30 +00:00
}
}
2017-08-25 04:06:29 +00:00
// NewCmdEnv implements the OpenShift cli env command
2018-04-25 12:32:08 +00:00
func NewCmdEnv ( f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
2018-04-26 13:30:21 +00:00
o := NewEnvOptions ( streams )
2017-08-25 04:06:29 +00:00
cmd := & cobra . Command {
2018-10-05 19:59:38 +00:00
Use : "env RESOURCE/NAME KEY_1=VAL_1 ... KEY_N=VAL_N" ,
2017-10-11 06:26:02 +00:00
DisableFlagsInUseLine : true ,
2018-10-05 19:59:38 +00:00
Short : "Update environment variables on a pod template" ,
Long : envLong ,
Example : fmt . Sprintf ( envExample ) ,
2017-08-25 04:06:29 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2018-04-26 13:30:21 +00:00
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( o . Validate ( ) )
cmdutil . CheckErr ( o . RunEnv ( ) )
2017-08-25 04:06:29 +00:00
} ,
}
usage := "the resource to update the env"
2018-04-26 13:30:21 +00:00
cmdutil . AddFilenameOptionFlags ( cmd , & o . FilenameOptions , usage )
cmd . Flags ( ) . StringVarP ( & o . ContainerSelector , "containers" , "c" , o . ContainerSelector , "The names of containers in the selected pod templates to change - may use wildcards" )
cmd . Flags ( ) . StringVarP ( & o . From , "from" , "" , "" , "The name of a resource from which to inject environment variables" )
cmd . Flags ( ) . StringVarP ( & o . Prefix , "prefix" , "" , "" , "Prefix to append to variable names" )
cmd . Flags ( ) . StringArrayVarP ( & o . EnvParams , "env" , "e" , o . EnvParams , "Specify a key-value pair for an environment variable to set into each container." )
2018-05-20 09:55:47 +00:00
cmd . Flags ( ) . StringSliceVarP ( & o . Keys , "keys" , "" , o . Keys , "Comma-separated list of keys to import from specified resource" )
2018-04-26 13:30:21 +00:00
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." )
cmd . Flags ( ) . BoolVar ( & o . Resolve , "resolve" , o . Resolve , "If true, show secret or configmap references when listing variables" )
cmd . Flags ( ) . StringVarP ( & o . Selector , "selector" , "l" , o . Selector , "Selector (label query) to filter on" )
cmd . Flags ( ) . BoolVar ( & o . Local , "local" , o . Local , "If true, set env will NOT contact api-server but run locally." )
cmd . Flags ( ) . BoolVar ( & o . All , "all" , o . All , "If true, select all resources in the namespace of the specified resource types" )
cmd . Flags ( ) . BoolVar ( & o . Overwrite , "overwrite" , o . Overwrite , "If true, allow environment to be overwritten, otherwise reject updates that overwrite existing environment." )
o . PrintFlags . AddFlags ( cmd )
2017-08-25 04:06:29 +00:00
2018-04-04 19:25:51 +00:00
cmdutil . AddDryRunFlag ( cmd )
2017-08-25 04:06:29 +00:00
return cmd
}
2017-11-10 18:36:12 +00:00
func validateNoOverwrites ( existing [ ] v1 . EnvVar , env [ ] v1 . EnvVar ) error {
2017-08-25 04:06:29 +00:00
for _ , e := range env {
if current , exists := findEnv ( existing , e . Name ) ; exists && current . Value != e . Value {
return fmt . Errorf ( "'%s' already has a value (%s), and --overwrite is false" , current . Name , current . Value )
}
}
return nil
}
func keyToEnvName ( key string ) string {
return strings . ToUpper ( validEnvNameRegexp . ReplaceAllString ( key , "_" ) )
}
2018-05-20 09:55:47 +00:00
func contains ( key string , keyList [ ] string ) bool {
if len ( keyList ) == 0 {
return true
}
for _ , k := range keyList {
if k == key {
return true
}
}
return false
}
2018-10-30 10:35:24 +00:00
// Complete completes all required options
2018-02-23 13:21:16 +00:00
func ( o * EnvOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
if o . All && len ( o . Selector ) > 0 {
return fmt . Errorf ( "cannot set --all and --selector at the same time" )
2017-08-25 04:06:29 +00:00
}
2018-04-26 13:30:21 +00:00
ok := false
o . resources , o . envArgs , ok = envutil . SplitEnvironmentFromResources ( args )
2018-02-23 13:21:16 +00:00
if ! ok {
2018-04-26 13:30:21 +00:00
return fmt . Errorf ( "all resources must be specified before environment changes: %s" , strings . Join ( args , " " ) )
2017-08-25 04:06:29 +00:00
}
2018-05-21 20:06:58 +00:00
o . updatePodSpecForObject = polymorphichelpers . UpdatePodSpecForObjectFn
2018-04-26 13:30:21 +00:00
o . output = cmdutil . GetFlagString ( cmd , "output" )
o . dryRun = cmdutil . GetDryRunFlag ( cmd )
if o . dryRun {
2018-04-06 15:36:52 +00:00
// TODO(juanvallejo): This can be cleaned up even further by creating
// a PrintFlags struct that binds the --dry-run flag, and whose
// ToPrinter method returns a printer that understands how to print
// this success message.
o . PrintFlags . Complete ( "%s (dry run)" )
}
2018-04-04 19:25:51 +00:00
printer , err := o . PrintFlags . ToPrinter ( )
if err != nil {
return err
}
2018-04-12 20:02:30 +00:00
o . PrintObj = printer . PrintObj
2017-08-25 04:06:29 +00:00
2018-04-26 13:30:21 +00:00
o . clientset , err = f . KubernetesClientSet ( )
if err != nil {
return err
2017-08-25 04:06:29 +00:00
}
2018-05-24 13:33:36 +00:00
o . namespace , o . enforceNamespace , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
2018-04-26 13:30:21 +00:00
if err != nil {
return err
}
o . builder = f . NewBuilder
2017-08-25 04:06:29 +00:00
return nil
}
2018-10-30 10:35:24 +00:00
// Validate makes sure provided values for EnvOptions are valid
2018-04-26 13:30:21 +00:00
func ( o * EnvOptions ) Validate ( ) error {
if len ( o . Filenames ) == 0 && len ( o . resources ) < 1 {
return fmt . Errorf ( "one or more resources must be specified as <resource> <name> or <resource>/<name>" )
2017-08-25 04:06:29 +00:00
}
2018-04-26 13:30:21 +00:00
if o . List && len ( o . output ) > 0 {
return fmt . Errorf ( "--list and --output may not be specified together" )
2017-08-25 04:06:29 +00:00
}
2018-05-20 09:55:47 +00:00
if len ( o . Keys ) > 0 && len ( o . From ) == 0 {
return fmt . Errorf ( "when specifying --keys, a configmap or secret must be provided with --from" )
}
2018-04-26 13:30:21 +00:00
return nil
}
2017-08-25 04:06:29 +00:00
2018-04-26 13:30:21 +00:00
// RunEnv contains all the necessary functionality for the OpenShift cli env command
func ( o * EnvOptions ) RunEnv ( ) error {
env , remove , err := envutil . ParseEnv ( append ( o . EnvParams , o . envArgs ... ) , o . In )
2017-08-25 04:06:29 +00:00
if err != nil {
return err
}
if len ( o . From ) != 0 {
2018-04-26 13:30:21 +00:00
b := o . builder ( ) .
2018-05-07 12:32:20 +00:00
WithScheme ( scheme . Scheme , scheme . Scheme . PrioritizedVersionsAllGroups ( ) ... ) .
2017-11-15 04:03:06 +00:00
LocalParam ( o . Local ) .
2017-08-25 04:06:29 +00:00
ContinueOnError ( ) .
2018-04-26 13:30:21 +00:00
NamespaceParam ( o . namespace ) . DefaultNamespace ( ) .
FilenameParam ( o . enforceNamespace , & o . FilenameOptions ) .
2017-08-25 04:06:29 +00:00
Flatten ( )
2017-11-14 03:43:58 +00:00
if ! o . Local {
2017-08-25 04:06:29 +00:00
b = b .
2017-08-04 06:54:17 +00:00
LabelSelectorParam ( o . Selector ) .
2017-08-25 04:06:29 +00:00
ResourceTypeOrNameArgs ( o . All , o . From ) .
Latest ( )
}
infos , err := b . Do ( ) . Infos ( )
if err != nil {
return err
}
for _ , info := range infos {
2018-05-02 20:25:48 +00:00
switch from := info . Object . ( type ) {
2017-11-10 18:36:12 +00:00
case * v1 . Secret :
2017-08-25 04:06:29 +00:00
for key := range from . Data {
2018-05-20 09:55:47 +00:00
if contains ( key , o . Keys ) {
envVar := v1 . EnvVar {
Name : keyToEnvName ( key ) ,
ValueFrom : & v1 . EnvVarSource {
SecretKeyRef : & v1 . SecretKeySelector {
LocalObjectReference : v1 . LocalObjectReference {
Name : from . Name ,
} ,
Key : key ,
2017-08-25 04:06:29 +00:00
} ,
} ,
2018-05-20 09:55:47 +00:00
}
env = append ( env , envVar )
2017-08-25 04:06:29 +00:00
}
}
2017-11-10 18:36:12 +00:00
case * v1 . ConfigMap :
2017-08-25 04:06:29 +00:00
for key := range from . Data {
2018-05-20 09:55:47 +00:00
if contains ( key , o . Keys ) {
envVar := v1 . EnvVar {
Name : keyToEnvName ( key ) ,
ValueFrom : & v1 . EnvVarSource {
ConfigMapKeyRef : & v1 . ConfigMapKeySelector {
LocalObjectReference : v1 . LocalObjectReference {
Name : from . Name ,
} ,
Key : key ,
2017-08-25 04:06:29 +00:00
} ,
} ,
2018-05-20 09:55:47 +00:00
}
env = append ( env , envVar )
2017-08-25 04:06:29 +00:00
}
}
default :
return fmt . Errorf ( "unsupported resource specified in --from" )
}
}
}
if len ( o . Prefix ) != 0 {
for i := range env {
env [ i ] . Name = fmt . Sprintf ( "%s%s" , o . Prefix , env [ i ] . Name )
}
}
2018-04-26 13:30:21 +00:00
b := o . builder ( ) .
2018-05-07 12:32:20 +00:00
WithScheme ( scheme . Scheme , scheme . Scheme . PrioritizedVersionsAllGroups ( ) ... ) .
2017-11-15 04:03:06 +00:00
LocalParam ( o . Local ) .
2017-08-25 04:06:29 +00:00
ContinueOnError ( ) .
2018-04-26 13:30:21 +00:00
NamespaceParam ( o . namespace ) . DefaultNamespace ( ) .
FilenameParam ( o . enforceNamespace , & o . FilenameOptions ) .
2017-08-25 04:06:29 +00:00
Flatten ( )
2017-11-14 03:43:58 +00:00
if ! o . Local {
2017-11-15 04:03:06 +00:00
b . LabelSelectorParam ( o . Selector ) .
2018-04-26 13:30:21 +00:00
ResourceTypeOrNameArgs ( o . All , o . resources ... ) .
2017-08-25 04:06:29 +00:00
Latest ( )
}
2018-04-26 13:30:21 +00:00
infos , err := b . Do ( ) . Infos ( )
2017-08-25 04:06:29 +00:00
if err != nil {
return err
}
2018-05-21 21:56:56 +00:00
patches := CalculatePatches ( infos , scheme . DefaultJSONEncoder ( ) , func ( obj runtime . Object ) ( [ ] byte , error ) {
_ , err := o . updatePodSpecForObject ( obj , func ( spec * v1 . PodSpec ) error {
2017-08-25 04:06:29 +00:00
resolutionErrorsEncountered := false
containers , _ := selectContainers ( spec . Containers , o . ContainerSelector )
2018-05-21 21:56:56 +00:00
objName , err := meta . NewAccessor ( ) . Name ( obj )
if err != nil {
return err
}
gvks , _ , err := scheme . Scheme . ObjectKinds ( obj )
if err != nil {
return err
}
objKind := obj . GetObjectKind ( ) . GroupVersionKind ( ) . Kind
if len ( objKind ) == 0 {
for _ , gvk := range gvks {
if len ( gvk . Kind ) == 0 {
continue
}
if len ( gvk . Version ) == 0 || gvk . Version == runtime . APIVersionInternal {
continue
}
objKind = gvk . Kind
break
}
}
2017-08-25 04:06:29 +00:00
if len ( containers ) == 0 {
2018-05-21 21:56:56 +00:00
if gvks , _ , err := scheme . Scheme . ObjectKinds ( obj ) ; err == nil {
objKind := obj . GetObjectKind ( ) . GroupVersionKind ( ) . Kind
if len ( objKind ) == 0 {
for _ , gvk := range gvks {
if len ( gvk . Kind ) == 0 {
continue
}
if len ( gvk . Version ) == 0 || gvk . Version == runtime . APIVersionInternal {
continue
}
objKind = gvk . Kind
break
}
}
fmt . Fprintf ( o . ErrOut , "warning: %s/%s does not have any containers matching %q\n" , objKind , objName , o . ContainerSelector )
}
2017-08-25 04:06:29 +00:00
return nil
}
for _ , c := range containers {
if ! o . Overwrite {
if err := validateNoOverwrites ( c . Env , env ) ; err != nil {
return err
}
}
c . Env = updateEnv ( c . Env , env , remove )
if o . List {
resolveErrors := map [ string ] [ ] string { }
store := envutil . NewResourceStore ( )
2018-05-21 21:56:56 +00:00
fmt . Fprintf ( o . Out , "# %s %s, container %s\n" , objKind , objName , c . Name )
2017-08-25 04:06:29 +00:00
for _ , env := range c . Env {
// Print the simple value
if env . ValueFrom == nil {
fmt . Fprintf ( o . Out , "%s=%s\n" , env . Name , env . Value )
continue
}
// Print the reference version
if ! o . Resolve {
fmt . Fprintf ( o . Out , "# %s from %s\n" , env . Name , envutil . GetEnvVarRefString ( env . ValueFrom ) )
continue
}
2018-05-21 21:56:56 +00:00
value , err := envutil . GetEnvVarRefValue ( o . clientset , o . namespace , store , env . ValueFrom , obj , c )
2017-08-25 04:06:29 +00:00
// Print the resolved value
if err == nil {
fmt . Fprintf ( o . Out , "%s=%s\n" , env . Name , value )
continue
}
// Print the reference version and save the resolve error
fmt . Fprintf ( o . Out , "# %s from %s\n" , env . Name , envutil . GetEnvVarRefString ( env . ValueFrom ) )
errString := err . Error ( )
resolveErrors [ errString ] = append ( resolveErrors [ errString ] , env . Name )
resolutionErrorsEncountered = true
}
// Print any resolution errors
errs := [ ] string { }
for err , vars := range resolveErrors {
sort . Strings ( vars )
errs = append ( errs , fmt . Sprintf ( "error retrieving reference for %s: %v" , strings . Join ( vars , ", " ) , err ) )
}
sort . Strings ( errs )
for _ , err := range errs {
2018-04-25 12:32:08 +00:00
fmt . Fprintln ( o . ErrOut , err )
2017-08-25 04:06:29 +00:00
}
}
}
if resolutionErrorsEncountered {
return errors . New ( "failed to retrieve valueFrom references" )
}
return nil
} )
if err == nil {
2018-05-21 21:56:56 +00:00
return runtime . Encode ( scheme . DefaultJSONEncoder ( ) , obj )
2017-08-25 04:06:29 +00:00
}
return nil , err
} )
if o . List {
return nil
}
allErrs := [ ] error { }
for _ , patch := range patches {
info := patch . Info
if patch . Err != nil {
2018-06-29 14:36:44 +00:00
name := info . ObjectName ( )
allErrs = append ( allErrs , fmt . Errorf ( "error: %s %v\n" , name , patch . Err ) )
2017-08-25 04:06:29 +00:00
continue
}
// no changes
if string ( patch . Patch ) == "{}" || len ( patch . Patch ) == 0 {
continue
}
2018-04-26 13:30:21 +00:00
if o . Local || o . dryRun {
2018-05-14 14:51:51 +00:00
if err := o . PrintObj ( info . Object , o . Out ) ; err != nil {
2018-05-24 01:48:24 +00:00
allErrs = append ( allErrs , err )
2017-08-25 04:06:29 +00:00
}
continue
}
2018-08-30 13:33:34 +00:00
actual , err := resource . NewHelper ( info . Client , info . Mapping ) . Patch ( info . Namespace , info . Name , types . StrategicMergePatchType , patch . Patch , nil )
2017-08-25 04:06:29 +00:00
if err != nil {
2018-10-30 10:35:24 +00:00
allErrs = append ( allErrs , fmt . Errorf ( "failed to patch env update to pod template: %v" , err ) )
2017-08-25 04:06:29 +00:00
continue
}
// make sure arguments to set or replace environment variables are set
// before returning a successful message
2018-04-26 13:30:21 +00:00
if len ( env ) == 0 && len ( o . envArgs ) == 0 {
2017-08-25 04:06:29 +00:00
return fmt . Errorf ( "at least one environment variable must be provided" )
}
2018-05-14 14:51:51 +00:00
if err := o . PrintObj ( actual , o . Out ) ; err != nil {
2018-05-24 01:48:24 +00:00
allErrs = append ( allErrs , err )
2017-08-25 04:06:29 +00:00
}
}
return utilerrors . NewAggregate ( allErrs )
}