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 .
* /
package cmd
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"
2016-05-20 17:49:56 +00:00
"github.com/renstrom/dedent"
2015-08-05 22:05:17 +00:00
"github.com/spf13/cobra"
2016-09-07 20:29:57 +00:00
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
2016-09-07 20:29:57 +00:00
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
2016-02-12 18:58:43 +00:00
"k8s.io/kubernetes/pkg/client/restclient"
2015-08-13 19:01:50 +00:00
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
2015-08-05 22:03:47 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2016-03-22 13:38:42 +00:00
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
2016-04-18 16:54:44 +00:00
"k8s.io/kubernetes/pkg/util/interrupt"
"k8s.io/kubernetes/pkg/util/term"
2015-01-08 20:41:38 +00:00
)
2016-05-20 17:49:56 +00:00
var (
exec_example = dedent . Dedent ( `
# 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
kubectl exec 123456 - 7890 - c ruby - container - i - t -- bash - il ` )
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
)
2016-09-07 15:23:21 +00:00
func NewCmdExec ( cmdFullName string , f * cmdutil . Factory , cmdIn io . Reader , cmdOut , cmdErr io . Writer ) * cobra . Command {
2015-07-23 02:05:04 +00:00
options := & ExecOptions {
2016-07-15 19:56:42 +00:00
StreamOptions : StreamOptions {
In : cmdIn ,
Out : cmdOut ,
Err : cmdErr ,
} ,
2015-07-23 02:05:04 +00:00
2016-09-07 15:23:21 +00:00
FullCmdName : cmdFullName ,
Executor : & DefaultRemoteExecutor { } ,
2015-07-23 02:05:04 +00:00
}
2015-01-08 20:41:38 +00:00
cmd := & cobra . Command {
2015-08-18 14:14:24 +00:00
Use : "exec POD [-c CONTAINER] -- COMMAND [args...]" ,
2016-06-07 17:38:04 +00:00
Short : "Execute a command in a container" ,
2015-02-20 21:28:43 +00:00
Long : "Execute a command in a container." ,
Example : exec_example ,
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
} ,
}
2015-07-23 02:05:04 +00:00
cmd . Flags ( ) . StringVarP ( & options . PodName , "pod" , "p" , "" , "Pod name" )
2015-03-09 22:08:16 +00:00
// TODO support UID
2015-08-18 14:14:24 +00:00
cmd . Flags ( ) . StringVarP ( & options . ContainerName , "container" , "c" , "" , "Container name. If omitted, the first container in the pod will be chosen" )
2015-07-23 02:05:04 +00:00
cmd . Flags ( ) . BoolVarP ( & options . Stdin , "stdin" , "i" , false , "Pass stdin to the container" )
cmd . Flags ( ) . BoolVarP ( & options . TTY , "tty" , "t" , false , "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 {
2016-04-18 16:54:44 +00:00
Execute ( method string , url * url . URL , config * restclient . Config , stdin io . Reader , stdout , stderr io . Writer , tty bool , terminalSizeQueue term . 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
2016-04-18 16:54:44 +00:00
func ( * DefaultRemoteExecutor ) Execute ( method string , url * url . URL , config * restclient . Config , stdin io . Reader , stdout , stderr io . Writer , tty bool , terminalSizeQueue term . TerminalSizeQueue ) error {
2015-09-27 00:00:39 +00:00
exec , err := remotecommand . NewExecutor ( config , method , url )
if err != nil {
return err
}
2016-04-18 16:54:44 +00:00
return exec . Stream ( remotecommand . StreamOptions {
SupportedProtocols : remotecommandserver . SupportedStreamingProtocols ,
Stdin : stdin ,
Stdout : stdout ,
Stderr : stderr ,
Tty : tty ,
TerminalSizeQueue : terminalSizeQueue ,
} )
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
2016-07-15 19:56:42 +00:00
In io . Reader
Out io . Writer
Err io . Writer
// 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-07 15:23:21 +00:00
FullCmdName string
Executor RemoteExecutor
2016-09-07 20:29:57 +00:00
PodClient coreclient . PodsGetter
2016-09-07 15:23:21 +00:00
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
2015-09-18 07:57:20 +00:00
func ( p * ExecOptions ) Complete ( f * cmdutil . Factory , cmd * cobra . Command , argsIn [ ] string , argsLenAtDash int ) error {
2016-09-07 15:23:21 +00:00
if len ( p . FullCmdName ) == 0 {
p . FullCmdName = "kubectl"
}
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 ) {
2016-08-30 05:27:22 +00:00
return cmdutil . UsageError ( cmd , execUsageStr )
2015-03-09 22:08:16 +00:00
}
2015-07-23 02:05:04 +00:00
if len ( p . PodName ) != 0 {
2016-08-31 05:17:53 +00:00
printDeprecationWarning ( "exec POD_NAME" , "-p POD_NAME" )
2015-06-03 02:45:51 +00:00
if len ( argsIn ) < 1 {
2016-08-30 05:27:22 +00:00
return cmdutil . UsageError ( 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 {
2016-08-30 05:27:22 +00:00
return cmdutil . UsageError ( 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
2015-06-26 20:49:34 +00:00
namespace , _ , err := f . DefaultNamespace ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-07-23 02:05:04 +00:00
p . Namespace = namespace
config , err := f . ClientConfig ( )
if err != nil {
return err
}
p . Config = config
2015-01-08 20:41:38 +00:00
2016-09-07 20:29:57 +00:00
clientset , err := f . ClientSet ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2016-09-07 20:29:57 +00:00
p . PodClient = clientset . Core ( )
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" )
}
if p . Out == nil || p . Err == nil {
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
}
2016-07-15 19:56:42 +00:00
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
if o . Err != nil {
fmt . Fprintln ( o . Err , "Unable to use a TTY - input is not a terminal or the right kind of file" )
}
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-09-07 20:29:57 +00:00
pod , err := p . PodClient . Pods ( p . Namespace ) . Get ( p . PodName )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-01-08 20:41:38 +00:00
2016-06-14 12:50:39 +00:00
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 )
}
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 {
fmt . Fprintf ( p . Err , "defaulting container name to %s, use '%s describe pod/%s' to see all container names\n" , pod . Spec . Containers [ 0 ] . Name , p . FullCmdName , p . PodName )
}
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
2016-07-15 19:56:42 +00:00
t := p . setupTTY ( )
2016-04-18 16:54:44 +00:00
var sizeQueue term . 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
p . Err = 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 ) .
SubResource ( "exec" ) .
Param ( "container" , containerName )
req . VersionedParams ( & api . PodExecOptions {
Container : containerName ,
Command : p . Command ,
Stdin : p . Stdin ,
Stdout : p . Out != nil ,
Stderr : p . Err != nil ,
2016-07-15 19:56:42 +00:00
TTY : t . Raw ,
2016-04-18 16:54:44 +00:00
} , api . ParameterCodec )
2016-07-15 19:56:42 +00:00
return p . Executor . Execute ( "POST" , req . URL ( ) , p . Config , p . In , p . Out , p . Err , 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
}