2018-01-09 18:57:14 +00:00
/ *
Copyright 2014 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 cmd
import (
"fmt"
"io"
"net/url"
dockerterm "github.com/docker/docker/pkg/term"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2018-07-18 14:47:22 +00:00
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
2018-01-09 18:57:14 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/util/term"
"k8s.io/kubernetes/pkg/util/interrupt"
)
var (
exec_example = templates . Examples ( i18n . T ( `
# Get output from running ' date ' from pod 123456 - 7890 , using the first container by default
kubectl exec 123456 - 7890 date
# Get output from running ' date ' in ruby - container from pod 123456 - 7890
kubectl exec 123456 - 7890 - c ruby - container date
# 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
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
` ) )
)
const (
execUsageStr = "expected 'exec POD_NAME COMMAND [ARG1] [ARG2] ... [ARGN]'.\nPOD_NAME and COMMAND are required arguments for the exec command"
)
2018-07-18 14:47:22 +00:00
func NewCmdExec ( f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
2018-01-09 18:57:14 +00:00
options := & ExecOptions {
StreamOptions : StreamOptions {
2018-07-18 14:47:22 +00:00
IOStreams : streams ,
2018-01-09 18:57:14 +00:00
} ,
Executor : & DefaultRemoteExecutor { } ,
}
cmd := & cobra . Command {
2018-03-06 22:33:18 +00:00
Use : "exec POD [-c CONTAINER] -- COMMAND [args...]" ,
DisableFlagsInUseLine : true ,
2018-01-09 18:57:14 +00:00
Short : i18n . T ( "Execute a command in a container" ) ,
Long : "Execute a command in a container." ,
Example : exec_example ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
argsLenAtDash := cmd . ArgsLenAtDash ( )
cmdutil . CheckErr ( options . Complete ( f , cmd , args , argsLenAtDash ) )
cmdutil . CheckErr ( options . Validate ( ) )
cmdutil . CheckErr ( options . Run ( ) )
} ,
}
2018-07-18 14:47:22 +00:00
cmd . Flags ( ) . StringVarP ( & options . PodName , "pod" , "p" , options . PodName , "Pod name" )
2018-01-09 18:57:14 +00:00
// TODO support UID
2018-07-18 14:47:22 +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" )
2018-01-09 18:57:14 +00:00
return cmd
}
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
type RemoteExecutor interface {
Execute ( method string , url * url . URL , config * restclient . Config , stdin io . Reader , stdout , stderr io . Writer , tty bool , terminalSizeQueue remotecommand . TerminalSizeQueue ) error
}
// DefaultRemoteExecutor is the standard implementation of remote command execution
type DefaultRemoteExecutor struct { }
func ( * DefaultRemoteExecutor ) Execute ( method string , url * url . URL , config * restclient . Config , stdin io . Reader , stdout , stderr io . Writer , tty bool , terminalSizeQueue remotecommand . TerminalSizeQueue ) error {
exec , err := remotecommand . NewSPDYExecutor ( config , method , url )
if err != nil {
return err
}
return exec . Stream ( remotecommand . StreamOptions {
Stdin : stdin ,
Stdout : stdout ,
Stderr : stderr ,
Tty : tty ,
TerminalSizeQueue : terminalSizeQueue ,
} )
}
type StreamOptions struct {
Namespace string
PodName string
ContainerName string
Stdin bool
TTY bool
// minimize unnecessary output
Quiet bool
// InterruptParent, if set, is used to handle interrupts while attached
InterruptParent * interrupt . Handler
2018-07-18 14:47:22 +00:00
genericclioptions . IOStreams
2018-01-09 18:57:14 +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
Command [ ] string
FullCmdName string
SuggestedCmdUsage string
Executor RemoteExecutor
PodClient coreclient . PodsGetter
Config * restclient . Config
}
// Complete verifies command line arguments and loads data from the command environment
func ( p * ExecOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , argsIn [ ] string , argsLenAtDash int ) error {
// Let kubectl exec follow rules for `--`, see #13004 issue
if len ( p . PodName ) == 0 && ( len ( argsIn ) == 0 || argsLenAtDash == 0 ) {
return cmdutil . UsageErrorf ( cmd , execUsageStr )
}
if len ( p . PodName ) != 0 {
2018-07-18 14:47:22 +00:00
printDeprecationWarning ( p . ErrOut , "exec POD_NAME" , "-p POD_NAME" )
2018-01-09 18:57:14 +00:00
if len ( argsIn ) < 1 {
return cmdutil . UsageErrorf ( cmd , execUsageStr )
}
p . Command = argsIn
} else {
p . PodName = argsIn [ 0 ]
p . Command = argsIn [ 1 : ]
if len ( p . Command ) < 1 {
return cmdutil . UsageErrorf ( cmd , execUsageStr )
}
}
2018-07-18 14:47:22 +00:00
namespace , _ , err := f . ToRawKubeConfigLoader ( ) . Namespace ( )
2018-01-09 18:57:14 +00:00
if err != nil {
return err
}
p . Namespace = namespace
cmdParent := cmd . Parent ( )
if cmdParent != nil {
p . FullCmdName = cmdParent . CommandPath ( )
}
if len ( p . FullCmdName ) > 0 && cmdutil . IsSiblingCommandExists ( cmd , "describe" ) {
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 )
}
2018-07-18 14:47:22 +00:00
config , err := f . ToRESTConfig ( )
2018-01-09 18:57:14 +00:00
if err != nil {
return err
}
p . Config = config
clientset , err := f . ClientSet ( )
if err != nil {
return err
}
p . PodClient = clientset . Core ( )
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-07-18 14:47:22 +00:00
if p . Out == nil || p . ErrOut == nil {
2018-01-09 18:57:14 +00:00
return fmt . Errorf ( "both output and error output must be provided" )
}
if p . Executor == nil || p . PodClient == nil || p . Config == nil {
return fmt . Errorf ( "client, client config, and executor must be provided" )
}
return nil
}
func ( o * StreamOptions ) setupTTY ( ) term . TTY {
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-07-18 14:47:22 +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" )
2018-01-09 18:57:14 +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
}
// Run executes a validated remote execution against a pod.
func ( p * ExecOptions ) Run ( ) error {
pod , err := p . PodClient . Pods ( p . Namespace ) . Get ( p . PodName , metav1 . GetOptions { } )
if err != nil {
return err
}
if pod . Status . Phase == api . PodSucceeded || pod . Status . Phase == api . PodFailed {
return fmt . Errorf ( "cannot exec into a container in a completed pod; current phase is %s" , pod . Status . Phase )
}
containerName := p . ContainerName
if len ( containerName ) == 0 {
if len ( pod . Spec . Containers ) > 1 {
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-07-18 14:47:22 +00:00
fmt . Fprintf ( p . ErrOut , "%s\n" , usageString )
2018-01-09 18:57:14 +00:00
}
containerName = pod . Spec . Containers [ 0 ] . Name
}
// ensure we can recover the terminal while attached
t := p . setupTTY ( )
var sizeQueue remotecommand . TerminalSizeQueue
if t . Raw {
// 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-07-18 14:47:22 +00:00
p . ErrOut = nil
2018-01-09 18:57:14 +00:00
}
fn := func ( ) error {
restClient , err := restclient . RESTClientFor ( p . Config )
if err != nil {
return err
}
// TODO: consider abstracting into a client invocation or client helper
req := restClient . Post ( ) .
Resource ( "pods" ) .
Name ( pod . Name ) .
Namespace ( pod . Namespace ) .
SubResource ( "exec" ) .
Param ( "container" , containerName )
req . VersionedParams ( & api . PodExecOptions {
Container : containerName ,
Command : p . Command ,
Stdin : p . Stdin ,
Stdout : p . Out != nil ,
2018-07-18 14:47:22 +00:00
Stderr : p . ErrOut != nil ,
2018-01-09 18:57:14 +00:00
TTY : t . Raw ,
} , legacyscheme . ParameterCodec )
2018-07-18 14:47:22 +00:00
return p . Executor . Execute ( "POST" , req . URL ( ) , p . Config , p . In , p . Out , p . ErrOut , t . Raw , sizeQueue )
2018-01-09 18:57:14 +00:00
}
if err := t . Safe ( fn ) ; err != nil {
return err
}
return nil
}