2015-01-08 15:41:38 -05:00
/ *
2016-06-02 20:25:58 -04:00
Copyright 2014 The Kubernetes Authors .
2015-01-08 15:41:38 -05:00
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 .
* /
2018-10-05 07:06:12 -04:00
package exec
2015-01-08 15:41:38 -05:00
import (
2015-07-22 22:05:04 -04:00
"fmt"
2015-01-08 15:41:38 -05:00
"io"
2015-09-26 20:00:39 -04:00
"net/url"
2019-02-02 09:12:11 -05:00
"time"
2015-01-08 15:41:38 -05:00
2016-07-15 15:56:42 -04:00
dockerterm "github.com/docker/docker/pkg/term"
2015-08-05 18:05:17 -04:00
"github.com/spf13/cobra"
2018-08-02 14:24:22 -04:00
corev1 "k8s.io/api/core/v1"
2017-01-11 09:09:48 -05:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-08-21 06:46:39 -04:00
"k8s.io/cli-runtime/pkg/genericclioptions"
2019-02-02 09:12:11 -05:00
"k8s.io/cli-runtime/pkg/resource"
2018-08-02 14:24:22 -04:00
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
2017-01-19 13:27:59 -05:00
restclient "k8s.io/client-go/rest"
2017-04-14 05:33:57 -04:00
"k8s.io/client-go/tools/remotecommand"
2019-04-27 13:13:41 -04:00
2019-06-19 10:31:38 -04:00
"k8s.io/kubectl/pkg/util/interrupt"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
2015-08-05 18:03:47 -04:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2019-02-02 09:12:11 -05:00
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
2018-09-13 17:37:30 -04:00
"k8s.io/kubernetes/pkg/kubectl/scheme"
2017-07-07 00:04:11 -04:00
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
2015-01-08 15:41:38 -05:00
)
2016-05-20 13:49:56 -04:00
var (
2018-10-30 06:35:24 -04:00
execExample = templates . Examples ( i18n . T ( `
2019-02-02 09:12:11 -05:00
# Get output from running ' date ' command from pod mypod , using the first container by default
kubectl exec mypod date
2016-08-08 14:24:01 -04:00
2019-02-02 09:12:11 -05:00
# Get output from running ' date ' command in ruby - container from pod mypod
kubectl exec mypod - c ruby - container date
2015-02-20 16:28:43 -05:00
2019-02-02 09:12:11 -05:00
# Switch to raw terminal mode , sends stdin to ' bash ' in ruby - container from pod mypod
2016-05-20 13:49:56 -04:00
# and sends stdout / stderr from ' bash ' back to the client
2019-02-02 09:12:11 -05:00
kubectl exec mypod - c ruby - container - i - t -- bash - il
2017-03-25 08:11:07 -04:00
2019-02-02 09:12:11 -05:00
# List contents of / usr from the first container of pod mypod and sort by modification time .
2017-03-25 08:11:07 -04:00
# If the command you want to execute in the pod has any flags in common ( e . g . - i ) ,
# you must use two dashes ( -- ) to separate your command ' s flags / arguments .
# Also note , do not surround your command and its flags / arguments with quotes
# unless that is how you would execute it normally ( i . e . , do ls - t / usr , not "ls -t /usr" ) .
2019-02-02 09:12:11 -05:00
kubectl exec mypod - i - t -- ls - t / usr
# Get output from running ' date ' command from the first pod of the deployment mydeployment , using the first container by default
kubectl exec deploy / mydeployment date
# Get output from running ' date ' command from the first pod of the service myservice , using the first container by default
kubectl exec svc / myservice date
2017-03-25 08:11:07 -04:00
` ) )
2015-02-20 16:28:43 -05:00
)
2016-08-30 01:27:22 -04:00
const (
2019-02-02 09:12:11 -05:00
execUsageStr = "expected 'exec (POD | TYPE/NAME) COMMAND [ARG1] [ARG2] ... [ARGN]'.\nPOD or TYPE/NAME and COMMAND are required arguments for the exec command"
defaultPodExecTimeout = 60 * time . Second
2016-08-30 01:27:22 -04:00
)
2018-05-08 09:02:34 -04:00
func NewCmdExec ( f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
2015-07-22 22:05:04 -04:00
options := & ExecOptions {
2016-07-15 15:56:42 -04:00
StreamOptions : StreamOptions {
2018-05-08 09:02:34 -04:00
IOStreams : streams ,
2016-07-15 15:56:42 -04:00
} ,
2015-07-22 22:05:04 -04:00
2016-09-28 12:41:42 -04:00
Executor : & DefaultRemoteExecutor { } ,
2015-07-22 22:05:04 -04:00
}
2015-01-08 15:41:38 -05:00
cmd := & cobra . Command {
2019-05-07 15:47:03 -04:00
Use : "exec (POD | TYPE/NAME) [-c CONTAINER] [flags] -- COMMAND [args...]" ,
2017-10-11 02:26:02 -04:00
DisableFlagsInUseLine : true ,
2018-10-05 15:59:38 -04:00
Short : i18n . T ( "Execute a command in a container" ) ,
Long : "Execute a command in a container." ,
2018-10-30 06:35:24 -04:00
Example : execExample ,
2015-01-08 15:41:38 -05:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-09-18 03:57:20 -04:00
argsLenAtDash := cmd . ArgsLenAtDash ( )
cmdutil . CheckErr ( options . Complete ( f , cmd , args , argsLenAtDash ) )
2015-07-22 22:05:04 -04:00
cmdutil . CheckErr ( options . Validate ( ) )
cmdutil . CheckErr ( options . Run ( ) )
2015-03-09 18:08:16 -04:00
} ,
}
2019-02-02 09:12:11 -05:00
cmdutil . AddPodRunningTimeoutFlag ( cmd , defaultPodExecTimeout )
2015-03-09 18:08:16 -04:00
// TODO support UID
2018-02-24 02:55:55 -05:00
cmd . Flags ( ) . StringVarP ( & options . ContainerName , "container" , "c" , options . ContainerName , "Container name. If omitted, the first container in the pod will be chosen" )
cmd . Flags ( ) . BoolVarP ( & options . Stdin , "stdin" , "i" , options . Stdin , "Pass stdin to the container" )
cmd . Flags ( ) . BoolVarP ( & options . TTY , "tty" , "t" , options . TTY , "Stdin is a TTY" )
2015-03-09 18:08:16 -04:00
return cmd
}
2015-01-08 15:41:38 -05:00
2015-07-22 22:05:04 -04:00
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
type RemoteExecutor interface {
2017-02-15 05:34:49 -05:00
Execute ( method string , url * url . URL , config * restclient . Config , stdin io . Reader , stdout , stderr io . Writer , tty bool , terminalSizeQueue remotecommand . TerminalSizeQueue ) error
2015-05-04 13:13:55 -04:00
}
2015-07-22 22:05:04 -04:00
// DefaultRemoteExecutor is the standard implementation of remote command execution
type DefaultRemoteExecutor struct { }
2015-05-04 13:13:55 -04:00
2017-02-15 05:34:49 -05:00
func ( * DefaultRemoteExecutor ) Execute ( method string , url * url . URL , config * restclient . Config , stdin io . Reader , stdout , stderr io . Writer , tty bool , terminalSizeQueue remotecommand . TerminalSizeQueue ) error {
2017-07-07 17:54:34 -04:00
exec , err := remotecommand . NewSPDYExecutor ( config , method , url )
2015-09-26 20:00:39 -04:00
if err != nil {
return err
}
2016-04-18 12:54:44 -04:00
return exec . Stream ( remotecommand . StreamOptions {
2017-07-07 17:54:34 -04:00
Stdin : stdin ,
Stdout : stdout ,
Stderr : stderr ,
Tty : tty ,
TerminalSizeQueue : terminalSizeQueue ,
2016-04-18 12:54:44 -04:00
} )
2015-05-04 13:13:55 -04:00
}
2016-07-15 15:56:42 -04:00
type StreamOptions struct {
2015-07-22 22:05:04 -04:00
Namespace string
PodName string
ContainerName string
Stdin bool
TTY bool
2016-08-08 17:24:44 -04:00
// minimize unnecessary output
Quiet bool
2016-04-18 12:54:44 -04:00
// InterruptParent, if set, is used to handle interrupts while attached
InterruptParent * interrupt . Handler
2018-05-08 09:02:34 -04:00
genericclioptions . IOStreams
2016-07-15 15:56:42 -04:00
// for testing
overrideStreams func ( ) ( io . ReadCloser , io . Writer , io . Writer )
isTerminalIn func ( t term . TTY ) bool
}
// ExecOptions declare the arguments accepted by the Exec command
type ExecOptions struct {
StreamOptions
2016-04-18 12:54:44 -04:00
2019-02-02 09:12:11 -05:00
ResourceName string
Command [ ] string
2015-07-22 22:05:04 -04:00
2019-05-08 11:29:36 -04:00
ParentCommandName string
EnableSuggestedCmdUsage bool
2016-09-28 12:41:42 -04:00
2019-02-02 09:12:11 -05:00
Builder func ( ) * resource . Builder
ExecutablePodFn polymorphichelpers . AttachablePodForObjectFunc
restClientGetter genericclioptions . RESTClientGetter
Pod * corev1 . Pod
Executor RemoteExecutor
PodClient coreclient . PodsGetter
GetPodTimeout time . Duration
Config * restclient . Config
2015-05-04 13:13:55 -04:00
}
2015-07-22 22:05:04 -04:00
// Complete verifies command line arguments and loads data from the command environment
2016-10-12 20:18:39 -04:00
func ( p * ExecOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , argsIn [ ] string , argsLenAtDash int ) error {
2015-09-18 03:57:20 -04:00
// Let kubectl exec follow rules for `--`, see #13004 issue
2019-04-27 13:13:41 -04:00
if len ( argsIn ) == 0 || argsLenAtDash == 0 {
2017-06-14 17:14:42 -04:00
return cmdutil . UsageErrorf ( cmd , execUsageStr )
2015-03-09 18:08:16 -04:00
}
2019-04-27 13:13:41 -04:00
p . ResourceName = argsIn [ 0 ]
p . Command = argsIn [ 1 : ]
2015-05-20 19:00:19 -04:00
2019-02-02 09:12:11 -05:00
var err error
p . Namespace , _ , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
2017-11-05 21:19:08 -05:00
if err != nil {
return err
}
2019-02-02 09:12:11 -05:00
p . ExecutablePodFn = polymorphichelpers . AttachablePodForObjectFn
p . GetPodTimeout , err = cmdutil . GetPodRunningTimeoutFlag ( cmd )
if err != nil {
return cmdutil . UsageErrorf ( cmd , err . Error ( ) )
}
p . Builder = f . NewBuilder
p . restClientGetter = f
2017-11-05 21:19:08 -05:00
2016-09-28 12:41:42 -04:00
cmdParent := cmd . Parent ( )
if cmdParent != nil {
2019-05-08 11:29:36 -04:00
p . ParentCommandName = cmdParent . CommandPath ( )
2016-09-28 12:41:42 -04:00
}
2019-05-08 11:29:36 -04:00
if len ( p . ParentCommandName ) > 0 && cmdutil . IsSiblingCommandExists ( cmd , "describe" ) {
p . EnableSuggestedCmdUsage = true
2016-09-28 12:41:42 -04:00
}
2019-02-02 09:12:11 -05:00
p . Config , err = f . ToRESTConfig ( )
2015-07-22 22:05:04 -04:00
if err != nil {
return err
}
2015-01-08 15:41:38 -05:00
2018-08-02 14:24:22 -04:00
clientset , err := f . KubernetesClientSet ( )
2015-03-09 18:08:16 -04:00
if err != nil {
return err
}
2018-08-02 14:24:22 -04:00
p . PodClient = clientset . CoreV1 ( )
2015-01-08 15:41:38 -05:00
2015-07-22 22:05:04 -04:00
return nil
}
// Validate checks that the provided exec options are specified.
func ( p * ExecOptions ) Validate ( ) error {
2019-02-02 09:12:11 -05:00
if len ( p . PodName ) == 0 && len ( p . ResourceName ) == 0 {
return fmt . Errorf ( "pod or type/name must be specified" )
2015-07-22 22:05:04 -04:00
}
if len ( p . Command ) == 0 {
return fmt . Errorf ( "you must specify at least one command for the container" )
}
2018-05-08 09:02:34 -04:00
if p . Out == nil || p . ErrOut == nil {
2015-07-22 22:05:04 -04:00
return fmt . Errorf ( "both output and error output must be provided" )
}
return nil
}
2018-10-05 08:38:38 -04:00
func ( o * StreamOptions ) SetupTTY ( ) term . TTY {
2016-07-15 15:56:42 -04:00
t := term . TTY {
Parent : o . InterruptParent ,
Out : o . Out ,
}
if ! o . Stdin {
// need to nil out o.In to make sure we don't create a stream for stdin
o . In = nil
o . TTY = false
return t
}
t . In = o . In
if ! o . TTY {
return t
}
if o . isTerminalIn == nil {
o . isTerminalIn = func ( tty term . TTY ) bool {
return tty . IsTerminalIn ( )
}
}
if ! o . isTerminalIn ( t ) {
o . TTY = false
2018-05-08 09:02:34 -04:00
if o . ErrOut != nil {
fmt . Fprintln ( o . ErrOut , "Unable to use a TTY - input is not a terminal or the right kind of file" )
2016-07-15 15:56:42 -04:00
}
return t
}
// if we get to here, the user wants to attach stdin, wants a TTY, and o.In is a terminal, so we
// can safely set t.Raw to true
t . Raw = true
if o . overrideStreams == nil {
// use dockerterm.StdStreams() to get the right I/O handles on Windows
o . overrideStreams = dockerterm . StdStreams
}
stdin , stdout , _ := o . overrideStreams ( )
o . In = stdin
t . In = stdin
if o . Out != nil {
o . Out = stdout
t . Out = stdout
}
return t
}
2015-07-22 22:05:04 -04:00
// Run executes a validated remote execution against a pod.
func ( p * ExecOptions ) Run ( ) error {
2019-02-02 09:12:11 -05:00
var err error
// we still need legacy pod getter when PodName in ExecOptions struct is provided,
// since there are any other command run this function by providing Podname with PodsGetter
// and without resource builder, eg: `kubectl cp`.
if len ( p . PodName ) != 0 {
p . Pod , err = p . PodClient . Pods ( p . Namespace ) . Get ( p . PodName , metav1 . GetOptions { } )
if err != nil {
return err
}
} else {
builder := p . Builder ( ) .
WithScheme ( scheme . Scheme , scheme . Scheme . PrioritizedVersionsAllGroups ( ) ... ) .
NamespaceParam ( p . Namespace ) . DefaultNamespace ( ) . ResourceNames ( "pods" , p . ResourceName )
obj , err := builder . Do ( ) . Object ( )
if err != nil {
return err
}
p . Pod , err = p . ExecutablePodFn ( p . restClientGetter , obj , p . GetPodTimeout )
if err != nil {
return err
}
2015-03-09 18:08:16 -04:00
}
2015-01-08 15:41:38 -05:00
2019-02-02 09:12:11 -05:00
pod := p . Pod
2018-08-02 14:24:22 -04:00
if pod . Status . Phase == corev1 . PodSucceeded || pod . Status . Phase == corev1 . PodFailed {
2016-06-14 08:50:39 -04:00
return fmt . Errorf ( "cannot exec into a container in a completed pod; current phase is %s" , pod . Status . Phase )
}
2015-07-22 22:05:04 -04:00
containerName := p . ContainerName
2015-03-09 18:08:16 -04:00
if len ( containerName ) == 0 {
2016-09-07 11:23:21 -04:00
if len ( pod . Spec . Containers ) > 1 {
2019-05-08 11:29:36 -04:00
fmt . Fprintf ( p . ErrOut , "Defaulting container name to %s.\n" , pod . Spec . Containers [ 0 ] . Name )
if p . EnableSuggestedCmdUsage {
fmt . Fprintf ( p . ErrOut , "Use '%s describe pod/%s -n %s' to see all of the containers in this pod.\n" , p . ParentCommandName , pod . Name , p . Namespace )
2016-09-28 12:41:42 -04:00
}
2016-09-07 11:23:21 -04:00
}
2015-03-09 18:08:16 -04:00
containerName = pod . Spec . Containers [ 0 ] . Name
}
2015-01-08 15:41:38 -05:00
2016-04-18 12:54:44 -04:00
// ensure we can recover the terminal while attached
2018-10-05 08:38:38 -04:00
t := p . SetupTTY ( )
2016-04-18 12:54:44 -04:00
2017-02-15 05:34:49 -05:00
var sizeQueue remotecommand . TerminalSizeQueue
2016-07-15 15:56:42 -04:00
if t . Raw {
2016-04-18 12:54:44 -04:00
// this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t . MonitorSize ( t . GetSize ( ) )
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
2018-05-08 09:02:34 -04:00
p . ErrOut = nil
2015-03-09 18:08:16 -04:00
}
2015-01-08 15:41:38 -05:00
2016-04-18 12:54:44 -04:00
fn := func ( ) error {
2016-09-07 16:29:57 -04:00
restClient , err := restclient . RESTClientFor ( p . Config )
if err != nil {
return err
}
2016-04-18 12:54:44 -04:00
// TODO: consider abstracting into a client invocation or client helper
2016-09-07 16:29:57 -04:00
req := restClient . Post ( ) .
2016-04-18 12:54:44 -04:00
Resource ( "pods" ) .
Name ( pod . Name ) .
Namespace ( pod . Namespace ) .
2019-02-19 02:58:43 -05:00
SubResource ( "exec" )
2018-09-19 16:25:18 -04:00
req . VersionedParams ( & corev1 . PodExecOptions {
2016-04-18 12:54:44 -04:00
Container : containerName ,
Command : p . Command ,
Stdin : p . Stdin ,
Stdout : p . Out != nil ,
2018-05-08 09:02:34 -04:00
Stderr : p . ErrOut != nil ,
2016-07-15 15:56:42 -04:00
TTY : t . Raw ,
2018-09-13 17:37:30 -04:00
} , scheme . ParameterCodec )
2016-04-18 12:54:44 -04:00
2018-05-08 09:02:34 -04:00
return p . Executor . Execute ( "POST" , req . URL ( ) , p . Config , p . In , p . Out , p . ErrOut , t . Raw , sizeQueue )
2016-04-18 12:54:44 -04:00
}
if err := t . Safe ( fn ) ; err != nil {
return err
}
return nil
2015-01-08 15:41:38 -05:00
}