2015-07-29 23:19:09 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-07-29 23:19:09 +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 (
2017-06-14 21:14:42 +00:00
"errors"
2015-07-29 23:19:09 +00:00
"fmt"
"io"
2015-09-27 00:00:39 +00:00
"net/url"
2017-02-21 16:19:13 +00:00
"time"
2015-07-29 23:19:09 +00:00
2015-08-05 22:05:17 +00:00
"github.com/golang/glog"
"github.com/spf13/cobra"
2016-02-20 18:53:11 +00:00
2017-01-11 14:09:48 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
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"
2017-10-16 11:41:50 +00:00
"k8s.io/kubernetes/pkg/api/legacyscheme"
2017-11-08 22:34:54 +00:00
api "k8s.io/kubernetes/pkg/apis/core"
2016-10-21 22:24:05 +00:00
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
2016-10-07 22:24:42 +00:00
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
2015-08-05 22:03:47 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
2017-07-07 04:04:11 +00:00
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
2015-07-29 23:19:09 +00:00
)
2016-05-20 17:49:56 +00:00
var (
2017-02-16 03:47:00 +00:00
attachExample = templates . Examples ( i18n . T ( `
2016-05-20 17:49:56 +00:00
# Get output from running pod 123456 - 7890 , using the first container by default
kubectl attach 123456 - 7890
2015-08-12 16:50:09 +00:00
2016-05-20 17:49:56 +00:00
# Get output from ruby - container from pod 123456 - 7890
kubectl attach 123456 - 7890 - c ruby - container
2015-07-29 23:19:09 +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-01-24 14:28:52 +00:00
kubectl attach 123456 - 7890 - c ruby - container - i - t
# Get output from the first pod of a ReplicaSet named nginx
kubectl attach rs / nginx
2017-03-15 03:49:10 +00:00
` ) )
2015-07-29 23:19:09 +00:00
)
2017-02-21 16:19:13 +00:00
const (
defaultPodAttachTimeout = 60 * time . Second
defaultPodLogsTimeout = 20 * time . Second
)
2016-10-13 00:18:39 +00:00
func NewCmdAttach ( f cmdutil . Factory , cmdIn io . Reader , cmdOut , cmdErr io . Writer ) * cobra . Command {
2015-07-29 23:19:09 +00:00
options := & AttachOptions {
2016-07-15 19:56:42 +00:00
StreamOptions : StreamOptions {
In : cmdIn ,
Out : cmdOut ,
Err : cmdErr ,
} ,
2015-07-29 23:19:09 +00:00
Attach : & DefaultRemoteAttach { } ,
}
cmd := & cobra . Command {
2017-10-11 06:26:02 +00:00
Use : "attach (POD | TYPE/NAME) -c CONTAINER" ,
DisableFlagsInUseLine : true ,
2017-01-25 01:00:32 +00:00
Short : i18n . T ( "Attach to a running container" ) ,
2015-11-25 10:57:03 +00:00
Long : "Attach to a process that is already running inside an existing container." ,
2017-02-16 03:47:00 +00:00
Example : attachExample ,
2015-07-29 23:19:09 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( options . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( options . Validate ( ) )
cmdutil . CheckErr ( options . Run ( ) )
} ,
}
2017-02-21 16:19:13 +00:00
cmdutil . AddPodRunningTimeoutFlag ( cmd , defaultPodAttachTimeout )
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-07-29 23:19:09 +00:00
return cmd
}
// RemoteAttach defines the interface accepted by the Attach command - provided for test stubbing
type RemoteAttach interface {
2017-02-15 10:34:49 +00:00
Attach ( method string , url * url . URL , config * restclient . Config , stdin io . Reader , stdout , stderr io . Writer , tty bool , terminalSizeQueue remotecommand . TerminalSizeQueue ) error
2015-07-29 23:19:09 +00:00
}
// DefaultRemoteAttach is the standard implementation of attaching
type DefaultRemoteAttach struct { }
2017-02-15 10:34:49 +00:00
func ( * DefaultRemoteAttach ) Attach ( 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-07-29 23:19:09 +00:00
}
// AttachOptions declare the arguments accepted by the Exec command
type AttachOptions struct {
2016-07-15 19:56:42 +00:00
StreamOptions
2015-07-29 23:19:09 +00:00
2018-01-19 19:38:52 +00:00
CommandName string
SuggestedCmdUsage string
2015-07-29 23:19:09 +00:00
2016-02-20 18:53:11 +00:00
Pod * api . Pod
2017-02-21 16:19:13 +00:00
Attach RemoteAttach
PodClient coreclient . PodsGetter
GetPodTimeout time . Duration
Config * restclient . Config
2015-07-29 23:19:09 +00:00
}
// Complete verifies command line arguments and loads data from the command environment
2016-10-13 00:18:39 +00:00
func ( p * AttachOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , argsIn [ ] string ) error {
2015-07-29 23:19:09 +00:00
if len ( argsIn ) == 0 {
2017-06-14 21:14:42 +00:00
return cmdutil . UsageErrorf ( cmd , "at least 1 argument is required for attach" )
2015-07-29 23:19:09 +00:00
}
2017-01-24 14:28:52 +00:00
if len ( argsIn ) > 2 {
2017-06-14 21:14:42 +00:00
return cmdutil . UsageErrorf ( cmd , "expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v" , len ( argsIn ) , argsIn )
2015-07-29 23:19:09 +00:00
}
namespace , _ , err := f . DefaultNamespace ( )
if err != nil {
return err
}
2017-01-24 14:28:52 +00:00
2017-02-21 16:19:13 +00:00
p . GetPodTimeout , err = cmdutil . GetPodRunningTimeoutFlag ( cmd )
if err != nil {
2017-06-14 21:14:42 +00:00
return cmdutil . UsageErrorf ( cmd , err . Error ( ) )
2017-02-21 16:19:13 +00:00
}
2017-08-02 20:23:07 +00:00
builder := f . NewBuilder ( ) .
2018-04-27 12:40:57 +00:00
Internal ( legacyscheme . Scheme ) .
2017-01-24 14:28:52 +00:00
NamespaceParam ( namespace ) . DefaultNamespace ( )
switch len ( argsIn ) {
case 1 :
builder . ResourceNames ( "pods" , argsIn [ 0 ] )
case 2 :
builder . ResourceNames ( argsIn [ 0 ] , argsIn [ 1 ] )
}
obj , err := builder . Do ( ) . Object ( )
if err != nil {
return err
}
2017-02-21 16:19:13 +00:00
attachablePod , err := f . AttachablePodForObject ( obj , p . GetPodTimeout )
2017-01-24 14:28:52 +00:00
if err != nil {
return err
}
p . PodName = attachablePod . Name
2015-07-29 23:19:09 +00:00
p . Namespace = namespace
2018-01-19 19:38:52 +00:00
fullCmdName := ""
cmdParent := cmd . Parent ( )
if cmdParent != nil {
fullCmdName = cmdParent . CommandPath ( )
}
if len ( 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." , fullCmdName , p . PodName , p . Namespace )
}
2015-07-29 23:19:09 +00:00
config , err := f . ClientConfig ( )
if err != nil {
return err
}
p . Config = config
2016-09-06 18:23:54 +00:00
clientset , err := f . ClientSet ( )
2015-07-29 23:19:09 +00:00
if err != nil {
return err
}
2016-09-06 18:23:54 +00:00
p . PodClient = clientset . Core ( )
2015-07-29 23:19:09 +00:00
2016-06-13 08:00:39 +00:00
if p . CommandName == "" {
p . CommandName = cmd . CommandPath ( )
}
2015-07-29 23:19:09 +00:00
return nil
}
// Validate checks that the provided attach options are specified.
func ( p * AttachOptions ) Validate ( ) error {
2015-11-05 02:39:32 +00:00
allErrs := [ ] error { }
2015-07-29 23:19:09 +00:00
if len ( p . PodName ) == 0 {
2017-06-14 21:14:42 +00:00
allErrs = append ( allErrs , errors . New ( "pod name must be specified" ) )
2015-07-29 23:19:09 +00:00
}
if p . Out == nil || p . Err == nil {
2017-06-14 21:14:42 +00:00
allErrs = append ( allErrs , errors . New ( "both output and error output must be provided" ) )
2015-07-29 23:19:09 +00:00
}
2016-09-06 18:23:54 +00:00
if p . Attach == nil || p . PodClient == nil || p . Config == nil {
2017-06-14 21:14:42 +00:00
allErrs = append ( allErrs , errors . New ( "client, client config, and attach must be provided" ) )
2015-07-29 23:19:09 +00:00
}
2015-11-05 02:39:32 +00:00
return utilerrors . NewAggregate ( allErrs )
2015-07-29 23:19:09 +00:00
}
// Run executes a validated remote execution against a pod.
func ( p * AttachOptions ) Run ( ) error {
2016-02-20 18:53:11 +00:00
if p . Pod == nil {
2016-12-07 13:26:33 +00:00
pod , err := p . PodClient . Pods ( p . Namespace ) . Get ( p . PodName , metav1 . GetOptions { } )
2016-02-20 18:53:11 +00:00
if err != nil {
return err
}
2016-06-24 12:35:02 +00:00
if pod . Status . Phase == api . PodSucceeded || pod . Status . Phase == api . PodFailed {
return fmt . Errorf ( "cannot attach a container in a completed pod; current phase is %s" , pod . Status . Phase )
2016-02-20 18:53:11 +00:00
}
2016-06-24 12:35:02 +00:00
2016-02-20 18:53:11 +00:00
p . Pod = pod
// TODO: convert this to a clean "wait" behavior
2015-07-29 23:19:09 +00:00
}
2016-02-20 18:53:11 +00:00
pod := p . Pod
2015-07-29 23:19:09 +00:00
2016-02-20 18:53:11 +00:00
// check for TTY
2016-06-24 11:05:59 +00:00
containerToAttach , err := p . containerToAttachTo ( pod )
if err != nil {
return fmt . Errorf ( "cannot attach to the container: %v" , err )
}
2016-07-15 19:56:42 +00:00
if p . TTY && ! containerToAttach . TTY {
p . TTY = false
if p . Err != nil {
fmt . Fprintf ( p . Err , "Unable to use a TTY - container %s did not allocate one\n" , containerToAttach . Name )
2015-07-29 23:19:09 +00:00
}
2016-07-15 19:56:42 +00:00
} else if ! p . TTY && containerToAttach . TTY {
// the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get
// an error "Unrecognized input header"
p . TTY = true
2015-07-29 23:19:09 +00:00
}
2016-07-15 19:56:42 +00:00
// ensure we can recover the terminal while attached
t := p . setupTTY ( )
2015-07-29 23:19:09 +00:00
2016-04-18 16:54:44 +00:00
// save p.Err so we can print the command prompt message below
stderr := p . Err
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
if size := t . GetSize ( ) ; size != nil {
// fake resizing +1 and then back to normal so that attach-detach-reattach will result in the
// screen being redrawn
sizePlusOne := * size
sizePlusOne . Width ++
sizePlusOne . Height ++
// this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t . MonitorSize ( & sizePlusOne , size )
}
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
p . Err = nil
}
2016-02-20 18:53:11 +00:00
fn := func ( ) error {
2016-09-06 18:23:54 +00:00
restClient , err := restclient . RESTClientFor ( p . Config )
if err != nil {
return err
}
2016-02-20 18:53:11 +00:00
// TODO: consider abstracting into a client invocation or client helper
2016-09-06 18:23:54 +00:00
req := restClient . Post ( ) .
2016-02-20 18:53:11 +00:00
Resource ( "pods" ) .
Name ( pod . Name ) .
Namespace ( pod . Namespace ) .
SubResource ( "attach" )
req . VersionedParams ( & api . PodAttachOptions {
Container : containerToAttach . Name ,
2016-04-18 16:54:44 +00:00
Stdin : p . Stdin ,
2016-02-20 18:53:11 +00:00
Stdout : p . Out != nil ,
Stderr : p . Err != nil ,
2016-07-15 19:56:42 +00:00
TTY : t . Raw ,
2017-10-16 11:41:50 +00:00
} , legacyscheme . ParameterCodec )
2016-02-20 18:53:11 +00:00
2016-07-15 19:56:42 +00:00
return p . Attach . Attach ( "POST" , req . URL ( ) , p . Config , p . In , p . Out , p . Err , t . Raw , sizeQueue )
2016-02-20 18:53:11 +00:00
}
2017-11-28 10:09:27 +00:00
if ! p . Quiet && stderr != nil {
fmt . Fprintln ( stderr , "If you don't see a command prompt, try pressing enter." )
}
2016-02-20 18:53:11 +00:00
if err := t . Safe ( fn ) ; err != nil {
2015-12-10 07:51:07 +00:00
return err
}
2016-02-20 18:53:11 +00:00
2016-07-15 19:56:42 +00:00
if p . Stdin && t . Raw && pod . Spec . RestartPolicy == api . RestartPolicyAlways {
2016-02-20 18:53:11 +00:00
fmt . Fprintf ( p . Out , "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n" , p . CommandName , pod . Name , containerToAttach . Name )
2015-12-10 07:51:07 +00:00
}
return nil
2015-07-29 23:19:09 +00:00
}
2015-09-29 19:43:15 +00:00
2016-06-24 11:05:59 +00:00
// containerToAttach returns a reference to the container to attach to, given
// by name or the first container if name is empty.
func ( p * AttachOptions ) containerToAttachTo ( pod * api . Pod ) ( * api . Container , error ) {
2015-09-29 19:43:15 +00:00
if len ( p . ContainerName ) > 0 {
2016-06-24 11:05:59 +00:00
for i := range pod . Spec . Containers {
if pod . Spec . Containers [ i ] . Name == p . ContainerName {
return & pod . Spec . Containers [ i ] , nil
2015-10-29 20:32:58 +00:00
}
}
2016-06-24 11:05:59 +00:00
for i := range pod . Spec . InitContainers {
if pod . Spec . InitContainers [ i ] . Name == p . ContainerName {
return & pod . Spec . InitContainers [ i ] , nil
2016-06-24 12:35:02 +00:00
}
}
2016-06-24 11:05:59 +00:00
return nil , fmt . Errorf ( "container not found (%s)" , p . ContainerName )
2015-09-29 19:43:15 +00:00
}
2018-01-19 19:38:52 +00:00
if len ( p . SuggestedCmdUsage ) > 0 {
fmt . Fprintf ( p . Err , "Defaulting container name to %s.\n" , pod . Spec . Containers [ 0 ] . Name )
fmt . Fprintf ( p . Err , "%s\n" , p . SuggestedCmdUsage )
}
2015-09-29 19:43:15 +00:00
glog . V ( 4 ) . Infof ( "defaulting container name to %s" , pod . Spec . Containers [ 0 ] . Name )
2016-06-24 11:05:59 +00:00
return & pod . Spec . Containers [ 0 ] , nil
2015-10-29 20:32:58 +00:00
}
// GetContainerName returns the name of the container to attach to, with a fallback.
2016-06-24 11:05:59 +00:00
func ( p * AttachOptions ) GetContainerName ( pod * api . Pod ) ( string , error ) {
c , err := p . containerToAttachTo ( pod )
if err != nil {
return "" , err
}
return c . Name , nil
2015-09-29 19:43:15 +00:00
}