2015-01-08 20:41:38 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-01-08 20:41:38 +00: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 11:06:12 +00:00
package exec
2015-01-08 20:41:38 +00:00
import (
2015-07-23 02:05:04 +00:00
"fmt"
2015-01-08 20:41:38 +00:00
"io"
2015-09-27 00:00:39 +00:00
"net/url"
2015-01-08 20:41:38 +00:00
2016-07-15 19:56:42 +00:00
dockerterm "github.com/docker/docker/pkg/term"
2015-08-05 22:05:17 +00:00
"github.com/spf13/cobra"
2016-09-07 20:29:57 +00:00
2018-08-02 18:24:22 +00:00
corev1 "k8s.io/api/core/v1"
2017-01-11 14:09:48 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-08-21 10:46:39 +00:00
"k8s.io/cli-runtime/pkg/genericclioptions"
2018-08-02 18:24:22 +00:00
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
2017-01-19 18:27:59 +00:00
restclient "k8s.io/client-go/rest"
2017-04-14 09:33:57 +00:00
"k8s.io/client-go/tools/remotecommand"
2015-08-05 22:03:47 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2018-09-13 21:37:30 +00:00
"k8s.io/kubernetes/pkg/kubectl/scheme"
2017-07-07 04:04:11 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
2018-10-10 18:29:30 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/templates"
2017-06-29 22:14:52 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/term"
2016-04-18 16:54:44 +00:00
"k8s.io/kubernetes/pkg/util/interrupt"
2015-01-08 20:41:38 +00:00
)
2016-05-20 17:49:56 +00:00
var (
2018-10-30 10:35:24 +00:00
execExample = templates . Examples ( i18n . T ( `
2016-05-20 17:49:56 +00:00
# Get output from running ' date ' from pod 123456 - 7890 , using the first container by default
kubectl exec 123456 - 7890 date
2016-08-08 18:24:01 +00:00
2016-05-20 17:49:56 +00:00
# Get output from running ' date ' in ruby - container from pod 123456 - 7890
kubectl exec 123456 - 7890 - c ruby - container date
2015-02-20 21:28:43 +00:00
2016-05-20 17:49:56 +00:00
# Switch to raw terminal mode , sends stdin to ' bash ' in ruby - container from pod 123456 - 7890
# and sends stdout / stderr from ' bash ' back to the client
2017-03-25 12:11:07 +00:00
kubectl exec 123456 - 7890 - c ruby - container - i - t -- bash - il
# List contents of / usr from the first container of pod 123456 - 7890 and sort by modification time .
# 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" ) .
kubectl exec 123456 - 7890 - i - t -- ls - t / usr
` ) )
2015-02-20 21:28:43 +00:00
)
2016-08-30 05:27:22 +00:00
const (
2016-08-31 05:17:53 +00:00
execUsageStr = "expected 'exec POD_NAME COMMAND [ARG1] [ARG2] ... [ARGN]'.\nPOD_NAME and COMMAND are required arguments for the exec command"
2016-08-30 05:27:22 +00:00
)
2018-05-08 13:02:34 +00:00
func NewCmdExec ( f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
2015-07-23 02:05:04 +00:00
options := & ExecOptions {
2016-07-15 19:56:42 +00:00
StreamOptions : StreamOptions {
2018-05-08 13:02:34 +00:00
IOStreams : streams ,
2016-07-15 19:56:42 +00:00
} ,
2015-07-23 02:05:04 +00:00
2016-09-28 16:41:42 +00:00
Executor : & DefaultRemoteExecutor { } ,
2015-07-23 02:05:04 +00:00
}
2015-01-08 20:41:38 +00:00
cmd := & cobra . Command {
2018-10-05 19:59:38 +00:00
Use : "exec POD [-c CONTAINER] -- COMMAND [args...]" ,
2017-10-11 06:26:02 +00:00
DisableFlagsInUseLine : true ,
2018-10-05 19:59:38 +00:00
Short : i18n . T ( "Execute a command in a container" ) ,
Long : "Execute a command in a container." ,
2018-10-30 10:35:24 +00:00
Example : execExample ,
2015-01-08 20:41:38 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-09-18 07:57:20 +00:00
argsLenAtDash := cmd . ArgsLenAtDash ( )
cmdutil . CheckErr ( options . Complete ( f , cmd , args , argsLenAtDash ) )
2015-07-23 02:05:04 +00:00
cmdutil . CheckErr ( options . Validate ( ) )
cmdutil . CheckErr ( options . Run ( ) )
2015-03-09 22:08:16 +00:00
} ,
}
2018-02-24 07:55:55 +00:00
cmd . Flags ( ) . StringVarP ( & options . PodName , "pod" , "p" , options . PodName , "Pod name" )
2018-07-24 08:59:37 +00:00
cmd . Flags ( ) . MarkDeprecated ( "pod" , "This flag is deprecated and will be removed in future. Use exec POD_NAME instead." )
2015-03-09 22:08:16 +00:00
// TODO support UID
2018-02-24 07:55:55 +00: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 22:08:16 +00:00
return cmd
}
2015-01-08 20:41:38 +00:00
2015-07-23 02:05:04 +00:00
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
type RemoteExecutor interface {
2017-02-15 10:34:49 +00: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 17:13:55 +00:00
}
2015-07-23 02:05:04 +00:00
// DefaultRemoteExecutor is the standard implementation of remote command execution
type DefaultRemoteExecutor struct { }
2015-05-04 17:13:55 +00:00
2017-02-15 10:34:49 +00: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 21:54:34 +00:00
exec , err := remotecommand . NewSPDYExecutor ( config , method , url )
2015-09-27 00:00:39 +00:00
if err != nil {
return err
}
2016-04-18 16:54:44 +00:00
return exec . Stream ( remotecommand . StreamOptions {
2017-07-07 21:54:34 +00:00
Stdin : stdin ,
Stdout : stdout ,
Stderr : stderr ,
Tty : tty ,
TerminalSizeQueue : terminalSizeQueue ,
2016-04-18 16:54:44 +00:00
} )
2015-05-04 17:13:55 +00:00
}
2016-07-15 19:56:42 +00:00
type StreamOptions struct {
2015-07-23 02:05:04 +00:00
Namespace string
PodName string
ContainerName string
Stdin bool
TTY bool
2016-08-08 21:24:44 +00:00
// minimize unnecessary output
Quiet bool
2016-04-18 16:54:44 +00:00
// InterruptParent, if set, is used to handle interrupts while attached
InterruptParent * interrupt . Handler
2018-05-08 13:02:34 +00:00
genericclioptions . IOStreams
2016-07-15 19:56:42 +00: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 16:54:44 +00:00
2016-07-15 19:56:42 +00:00
Command [ ] string
2015-07-23 02:05:04 +00:00
2016-09-28 16:41:42 +00:00
FullCmdName string
SuggestedCmdUsage string
Executor RemoteExecutor
PodClient coreclient . PodsGetter
Config * restclient . Config
2015-05-04 17:13:55 +00:00
}
2015-07-23 02:05:04 +00:00
// Complete verifies command line arguments and loads data from the command environment
2016-10-13 00:18:39 +00:00
func ( p * ExecOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , argsIn [ ] string , argsLenAtDash int ) error {
2015-09-18 07:57:20 +00:00
// Let kubectl exec follow rules for `--`, see #13004 issue
if len ( p . PodName ) == 0 && ( len ( argsIn ) == 0 || argsLenAtDash == 0 ) {
2017-06-14 21:14:42 +00:00
return cmdutil . UsageErrorf ( cmd , execUsageStr )
2015-03-09 22:08:16 +00:00
}
2015-07-23 02:05:04 +00:00
if len ( p . PodName ) != 0 {
2015-06-03 02:45:51 +00:00
if len ( argsIn ) < 1 {
2017-06-14 21:14:42 +00:00
return cmdutil . UsageErrorf ( cmd , execUsageStr )
2015-05-20 23:00:19 +00:00
}
2015-07-23 02:05:04 +00:00
p . Command = argsIn
2015-05-20 23:00:19 +00:00
} else {
2015-07-23 02:05:04 +00:00
p . PodName = argsIn [ 0 ]
p . Command = argsIn [ 1 : ]
if len ( p . Command ) < 1 {
2017-06-14 21:14:42 +00:00
return cmdutil . UsageErrorf ( cmd , execUsageStr )
2015-05-20 23:00:19 +00:00
}
2015-03-09 22:08:16 +00:00
}
2015-05-20 23:00:19 +00:00
2018-05-24 13:33:36 +00:00
namespace , _ , err := f . ToRawKubeConfigLoader ( ) . Namespace ( )
2017-11-06 02:19:08 +00:00
if err != nil {
return err
}
p . Namespace = namespace
2016-09-28 16:41:42 +00:00
cmdParent := cmd . Parent ( )
if cmdParent != nil {
p . FullCmdName = cmdParent . CommandPath ( )
}
if len ( p . FullCmdName ) > 0 && cmdutil . IsSiblingCommandExists ( cmd , "describe" ) {
2017-11-06 02:19:08 +00:00
p . SuggestedCmdUsage = fmt . Sprintf ( "Use '%s describe pod/%s -n %s' to see all of the containers in this pod." , p . FullCmdName , p . PodName , p . Namespace )
2016-09-28 16:41:42 +00:00
}
2018-05-16 14:54:42 +00:00
config , err := f . ToRESTConfig ( )
2015-07-23 02:05:04 +00:00
if err != nil {
return err
}
p . Config = config
2015-01-08 20:41:38 +00:00
2018-08-02 18:24:22 +00:00
clientset , err := f . KubernetesClientSet ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2018-08-02 18:24:22 +00:00
p . PodClient = clientset . CoreV1 ( )
2015-01-08 20:41:38 +00:00
2015-07-23 02:05:04 +00:00
return nil
}
// Validate checks that the provided exec options are specified.
func ( p * ExecOptions ) Validate ( ) error {
if len ( p . PodName ) == 0 {
return fmt . Errorf ( "pod name must be specified" )
}
if len ( p . Command ) == 0 {
return fmt . Errorf ( "you must specify at least one command for the container" )
}
2018-05-08 13:02:34 +00:00
if p . Out == nil || p . ErrOut == nil {
2015-07-23 02:05:04 +00:00
return fmt . Errorf ( "both output and error output must be provided" )
}
2016-09-07 20:29:57 +00:00
if p . Executor == nil || p . PodClient == nil || p . Config == nil {
2015-07-23 02:05:04 +00:00
return fmt . Errorf ( "client, client config, and executor must be provided" )
}
return nil
}
2018-10-05 12:38:38 +00:00
func ( o * StreamOptions ) SetupTTY ( ) term . TTY {
2016-07-15 19:56:42 +00: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 13:02:34 +00: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 19:56:42 +00: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-23 02:05:04 +00:00
// Run executes a validated remote execution against a pod.
func ( p * ExecOptions ) Run ( ) error {
2016-12-07 13:26:33 +00:00
pod , err := p . PodClient . Pods ( p . Namespace ) . Get ( p . PodName , metav1 . GetOptions { } )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-01-08 20:41:38 +00:00
2018-08-02 18:24:22 +00:00
if pod . Status . Phase == corev1 . PodSucceeded || pod . Status . Phase == corev1 . PodFailed {
2016-06-14 12:50:39 +00:00
return fmt . Errorf ( "cannot exec into a container in a completed pod; current phase is %s" , pod . Status . Phase )
}
2015-07-23 02:05:04 +00:00
containerName := p . ContainerName
2015-03-09 22:08:16 +00:00
if len ( containerName ) == 0 {
2016-09-07 15:23:21 +00:00
if len ( pod . Spec . Containers ) > 1 {
2016-09-28 16:41:42 +00:00
usageString := fmt . Sprintf ( "Defaulting container name to %s." , pod . Spec . Containers [ 0 ] . Name )
if len ( p . SuggestedCmdUsage ) > 0 {
usageString = fmt . Sprintf ( "%s\n%s" , usageString , p . SuggestedCmdUsage )
}
2018-05-08 13:02:34 +00:00
fmt . Fprintf ( p . ErrOut , "%s\n" , usageString )
2016-09-07 15:23:21 +00:00
}
2015-03-09 22:08:16 +00:00
containerName = pod . Spec . Containers [ 0 ] . Name
}
2015-01-08 20:41:38 +00:00
2016-04-18 16:54:44 +00:00
// ensure we can recover the terminal while attached
2018-10-05 12:38:38 +00:00
t := p . SetupTTY ( )
2016-04-18 16:54:44 +00:00
2017-02-15 10:34:49 +00:00
var sizeQueue remotecommand . TerminalSizeQueue
2016-07-15 19:56:42 +00:00
if t . Raw {
2016-04-18 16:54:44 +00: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 13:02:34 +00:00
p . ErrOut = nil
2015-03-09 22:08:16 +00:00
}
2015-01-08 20:41:38 +00:00
2016-04-18 16:54:44 +00:00
fn := func ( ) error {
2016-09-07 20:29:57 +00:00
restClient , err := restclient . RESTClientFor ( p . Config )
if err != nil {
return err
}
2016-04-18 16:54:44 +00:00
// TODO: consider abstracting into a client invocation or client helper
2016-09-07 20:29:57 +00:00
req := restClient . Post ( ) .
2016-04-18 16:54:44 +00:00
Resource ( "pods" ) .
Name ( pod . Name ) .
Namespace ( pod . Namespace ) .
2019-02-19 07:58:43 +00:00
SubResource ( "exec" )
2018-09-19 20:25:18 +00:00
req . VersionedParams ( & corev1 . PodExecOptions {
2016-04-18 16:54:44 +00:00
Container : containerName ,
Command : p . Command ,
Stdin : p . Stdin ,
Stdout : p . Out != nil ,
2018-05-08 13:02:34 +00:00
Stderr : p . ErrOut != nil ,
2016-07-15 19:56:42 +00:00
TTY : t . Raw ,
2018-09-13 21:37:30 +00:00
} , scheme . ParameterCodec )
2016-04-18 16:54:44 +00:00
2018-05-08 13:02:34 +00:00
return p . Executor . Execute ( "POST" , req . URL ( ) , p . Config , p . In , p . Out , p . ErrOut , t . Raw , sizeQueue )
2016-04-18 16:54:44 +00:00
}
if err := t . Safe ( fn ) ; err != nil {
return err
}
return nil
2015-01-08 20:41:38 +00:00
}