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 }
// Run executes a validated remote execution against a pod. func (p *AttachOptions) Run() error { if p.Pod == nil { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { return fmt.Errorf("pod %s is not running and cannot be attached to; current phase is %s", p.PodName, pod.Status.Phase) } p.Pod = pod // TODO: convert this to a clean "wait" behavior } pod := p.Pod // ensure we can recover the terminal while attached t := term.TTY{Parent: p.InterruptParent} // check for TTY tty := p.TTY containerToAttach := p.GetContainer(pod) if tty && !containerToAttach.TTY { tty = false fmt.Fprintf(p.Err, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name) } if p.Stdin { t.In = p.In if tty && !t.IsTerminal() { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not a terminal or the right kind of file") } } t.Raw = tty fn := func() error { if tty { fmt.Fprintln(p.Out, "\nHit enter for command prompt") } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("attach") req.VersionedParams(&api.PodAttachOptions{ Container: containerToAttach.Name, Stdin: p.In != nil, Stdout: p.Out != nil, Stderr: p.Err != nil, TTY: tty, }, api.ParameterCodec) return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.Err, tty) } if err := t.Safe(fn); err != nil { return err } if p.Stdin && tty && pod.Spec.RestartPolicy == api.RestartPolicyAlways { 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) } return nil }
// Run executes a validated remote execution against a pod. func (p *AttachOptions) Run() error { if p.Pod == nil { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } 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) } p.Pod = pod // TODO: convert this to a clean "wait" behavior } pod := p.Pod // ensure we can recover the terminal while attached t := term.TTY{ Parent: p.InterruptParent, Out: p.Out, } // check for TTY tty := p.TTY containerToAttach, err := p.containerToAttachTo(pod) if err != nil { return fmt.Errorf("cannot attach to the container: %v", err) } if tty && !containerToAttach.TTY { tty = false fmt.Fprintf(p.Err, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name) } if p.Stdin { t.In = p.In if tty && !t.IsTerminalIn() { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not a terminal or the right kind of file") } } else { p.In = nil } t.Raw = tty // save p.Err so we can print the command prompt message below stderr := p.Err var sizeQueue term.TerminalSizeQueue if tty { 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 } fn := func() error { if stderr != nil { fmt.Fprintln(stderr, "If you don't see a command prompt, try pressing enter.") } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("attach") req.VersionedParams(&api.PodAttachOptions{ Container: containerToAttach.Name, Stdin: p.Stdin, Stdout: p.Out != nil, Stderr: p.Err != nil, TTY: tty, }, api.ParameterCodec) return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.Err, tty, sizeQueue) } if err := t.Safe(fn); err != nil { return err } if p.Stdin && tty && pod.Spec.RestartPolicy == api.RestartPolicyAlways { 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) } return nil }
// Run executes a validated remote execution against a pod. func (p *ExecOptions) Run() error { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) 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 { glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name) containerName = pod.Spec.Containers[0].Name } // ensure we can recover the terminal while attached t := term.TTY{ Parent: p.InterruptParent, Out: p.Out, } // check for TTY tty := p.TTY if p.Stdin { t.In = p.In if tty && !t.IsTerminalIn() { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not a terminal or the right kind of file") } } else { p.In = nil } t.Raw = tty var sizeQueue term.TerminalSizeQueue if tty { // 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 } fn := func() error { // TODO: consider abstracting into a client invocation or client helper req := p.Client.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, Stderr: p.Err != nil, TTY: tty, }, api.ParameterCodec) return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.Err, tty, sizeQueue) } if err := t.Safe(fn); err != nil { return err } return nil }