mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Extract stream output handling to a new type.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
b1dfefc4bb
commit
a0ab33124a
15 changed files with 113 additions and 78 deletions
|
@ -31,27 +31,24 @@ type DockerCli struct {
|
|||
// configFile has the client configuration file
|
||||
configFile *configfile.ConfigFile
|
||||
// in holds the input stream and closer (io.ReadCloser) for the client.
|
||||
// TODO: remove
|
||||
in io.ReadCloser
|
||||
// out holds the output stream (io.Writer) for the client.
|
||||
out io.Writer
|
||||
// err holds the error stream (io.Writer) for the client.
|
||||
err io.Writer
|
||||
// keyFile holds the key file as a string.
|
||||
keyFile string
|
||||
// inFd holds the file descriptor of the client's STDIN (if valid).
|
||||
// TODO: remove
|
||||
inFd uintptr
|
||||
// outFd holds file descriptor of the client's STDOUT (if valid).
|
||||
outFd uintptr
|
||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
||||
// TODO: remove
|
||||
isTerminalIn bool
|
||||
// isTerminalOut indicates whether the client's STDOUT is a TTY
|
||||
isTerminalOut bool
|
||||
// client is the http client that performs all API operations
|
||||
client client.APIClient
|
||||
// inState holds the terminal input state
|
||||
// TODO: remove
|
||||
inState *term.State
|
||||
// outState holds the terminal output state
|
||||
outState *term.State
|
||||
out *OutStream
|
||||
}
|
||||
|
||||
// Client returns the APIClient
|
||||
|
@ -60,7 +57,7 @@ func (cli *DockerCli) Client() client.APIClient {
|
|||
}
|
||||
|
||||
// Out returns the writer used for stdout
|
||||
func (cli *DockerCli) Out() io.Writer {
|
||||
func (cli *DockerCli) Out() *OutStream {
|
||||
return cli.out
|
||||
}
|
||||
|
||||
|
@ -80,20 +77,11 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
|||
}
|
||||
|
||||
// IsTerminalIn returns true if the clients stdin is a TTY
|
||||
// TODO: remove
|
||||
func (cli *DockerCli) IsTerminalIn() bool {
|
||||
return cli.isTerminalIn
|
||||
}
|
||||
|
||||
// IsTerminalOut returns true if the clients stdout is a TTY
|
||||
func (cli *DockerCli) IsTerminalOut() bool {
|
||||
return cli.isTerminalOut
|
||||
}
|
||||
|
||||
// OutFd returns the fd for the stdout stream
|
||||
func (cli *DockerCli) OutFd() uintptr {
|
||||
return cli.outFd
|
||||
}
|
||||
|
||||
// CheckTtyInput checks if we are trying to attach to a container tty
|
||||
// from a non-tty client input stream, and if so, returns an error.
|
||||
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
||||
|
@ -119,12 +107,8 @@ func (cli *DockerCli) setRawTerminal() error {
|
|||
}
|
||||
cli.inState = state
|
||||
}
|
||||
if cli.isTerminalOut {
|
||||
state, err := term.SetRawTerminalOutput(cli.outFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli.outState = state
|
||||
if err := cli.out.setRawTerminal(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -134,9 +118,7 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
|||
if cli.inState != nil {
|
||||
term.RestoreTerminal(cli.inFd, cli.inState)
|
||||
}
|
||||
if cli.outState != nil {
|
||||
term.RestoreTerminal(cli.outFd, cli.outState)
|
||||
}
|
||||
cli.out.restoreTerminal()
|
||||
// WARNING: DO NOT REMOVE THE OS CHECK !!!
|
||||
// For some reason this Close call blocks on darwin..
|
||||
// As the client exists right after, simply discard the close
|
||||
|
@ -149,22 +131,17 @@ func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
|||
|
||||
// Initialize the dockerCli runs initialization that must happen after command
|
||||
// line flags are parsed.
|
||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
|
||||
cli.configFile = LoadDefaultConfigFile(cli.err)
|
||||
|
||||
client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cli.client = client
|
||||
|
||||
if cli.in != nil {
|
||||
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
|
||||
}
|
||||
if cli.out != nil {
|
||||
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
|
||||
}
|
||||
|
||||
if opts.Common.TrustKey == "" {
|
||||
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
|
||||
|
@ -177,11 +154,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
|||
|
||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
||||
return &DockerCli{
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
}
|
||||
return &DockerCli{in: in, out: NewOutStream(out), err: err}
|
||||
}
|
||||
|
||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||
|
|
|
@ -95,8 +95,8 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
|||
}
|
||||
defer resp.Close()
|
||||
|
||||
if c.Config.Tty && dockerCli.IsTerminalOut() {
|
||||
height, width := dockerCli.GetTtySize()
|
||||
if c.Config.Tty && dockerCli.Out().IsTerminal() {
|
||||
height, width := dockerCli.Out().GetTtySize()
|
||||
// To handle the case where a user repeatedly attaches/detaches without resizing their
|
||||
// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
|
||||
// resize it, then go back to normal. Without this, every attach after the first will
|
||||
|
|
|
@ -103,8 +103,8 @@ func pullImage(ctx context.Context, dockerCli *client.DockerCli, image string, o
|
|||
return jsonmessage.DisplayJSONMessagesStream(
|
||||
responseBody,
|
||||
out,
|
||||
dockerCli.OutFd(),
|
||||
dockerCli.IsTerminalOut(),
|
||||
dockerCli.Out().FD(),
|
||||
dockerCli.Out().IsTerminal(),
|
||||
nil)
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ func NewExportCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
}
|
||||
|
||||
func runExport(dockerCli *client.DockerCli, opts exportOptions) error {
|
||||
if opts.output == "" && dockerCli.IsTerminalOut() {
|
||||
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||
}
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
|
|||
// a far better user experience rather than relying on subsequent resizes
|
||||
// to cause things to catch up.
|
||||
if runtime.GOOS == "windows" {
|
||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
|
||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
|
||||
}
|
||||
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
|
@ -234,7 +234,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
|
|||
return runStartContainerErr(err)
|
||||
}
|
||||
|
||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
|
||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
|
||||
if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
|
||||
fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
|||
}
|
||||
|
||||
// 5. Wait for attachment to break.
|
||||
if c.Config.Tty && dockerCli.IsTerminalOut() {
|
||||
if c.Config.Tty && dockerCli.Out().IsTerminal() {
|
||||
if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
|
|||
|
||||
// Setup an upload progress bar
|
||||
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
|
||||
if !dockerCli.IsTerminalOut() {
|
||||
if !dockerCli.Out().IsTerminal() {
|
||||
progressOutput = &lastProgressOutput{output: progressOutput}
|
||||
}
|
||||
|
||||
|
@ -293,7 +293,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
|
|||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), nil)
|
||||
if err != nil {
|
||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||
// If no error code is set, default to 1
|
||||
|
|
|
@ -84,5 +84,5 @@ func runImport(dockerCli *client.DockerCli, opts importOptions) error {
|
|||
}
|
||||
defer responseBody.Close()
|
||||
|
||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
|
|||
defer file.Close()
|
||||
input = file
|
||||
}
|
||||
if !dockerCli.IsTerminalOut() {
|
||||
if !dockerCli.Out().IsTerminal() {
|
||||
opts.quiet = true
|
||||
}
|
||||
response, err := dockerCli.Client().ImageLoad(context.Background(), input, opts.quiet)
|
||||
|
@ -59,7 +59,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
|
|||
defer response.Body.Close()
|
||||
|
||||
if response.Body != nil && response.JSON {
|
||||
return jsonmessage.DisplayJSONMessagesStream(response.Body, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
||||
return jsonmessage.DisplayJSONMessagesToStream(response.Body, dockerCli.Out(), nil)
|
||||
}
|
||||
|
||||
_, err = io.Copy(dockerCli.Out(), response.Body)
|
||||
|
|
|
@ -57,6 +57,5 @@ func runPush(dockerCli *client.DockerCli, remote string) error {
|
|||
}
|
||||
|
||||
defer responseBody.Close()
|
||||
|
||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func NewSaveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
}
|
||||
|
||||
func runSave(dockerCli *client.DockerCli, opts saveOptions) error {
|
||||
if opts.output == "" && dockerCli.IsTerminalOut() {
|
||||
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||
}
|
||||
|
||||
|
|
67
api/client/out.go
Normal file
67
api/client/out.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
// OutStream is an output stream used by the DockerCli to write normal program
|
||||
// output.
|
||||
type OutStream struct {
|
||||
out io.Writer
|
||||
fd uintptr
|
||||
isTerminal bool
|
||||
state *term.State
|
||||
}
|
||||
|
||||
func (o *OutStream) Write(p []byte) (int, error) {
|
||||
return o.out.Write(p)
|
||||
}
|
||||
|
||||
// FD returns the file descriptor number for this stream
|
||||
func (o *OutStream) FD() uintptr {
|
||||
return o.fd
|
||||
}
|
||||
|
||||
// IsTerminal returns true if this stream is connected to a terminal
|
||||
func (o *OutStream) IsTerminal() bool {
|
||||
return o.isTerminal
|
||||
}
|
||||
|
||||
func (o *OutStream) setRawTerminal() (err error) {
|
||||
if os.Getenv("NORAW") != "" || !o.isTerminal {
|
||||
return nil
|
||||
}
|
||||
o.state, err = term.SetRawTerminalOutput(o.fd)
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *OutStream) restoreTerminal() {
|
||||
if o.state != nil {
|
||||
term.RestoreTerminal(o.fd, o.state)
|
||||
}
|
||||
}
|
||||
|
||||
// GetTtySize returns the height and width in characters of the tty
|
||||
func (o *OutStream) GetTtySize() (int, int) {
|
||||
if !o.isTerminal {
|
||||
return 0, 0
|
||||
}
|
||||
ws, err := term.GetWinsize(o.fd)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error getting size: %s", err)
|
||||
if ws == nil {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
return int(ws.Height), int(ws.Width)
|
||||
}
|
||||
|
||||
// NewOutStream returns a new OutStream object from a Writer
|
||||
func NewOutStream(out io.Writer) *OutStream {
|
||||
fd, isTerminal := term.GetFdInfo(out)
|
||||
return &OutStream{out: out, fd: fd, isTerminal: isTerminal}
|
||||
}
|
|
@ -440,14 +440,14 @@ func (cli *DockerCli) TrustedPush(ctx context.Context, repoInfo *registry.Reposi
|
|||
// We want trust signatures to always take an explicit tag,
|
||||
// otherwise it will act as an untrusted push.
|
||||
if tag == "" {
|
||||
if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil); err != nil {
|
||||
if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget); err != nil {
|
||||
if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -580,7 +580,7 @@ func (cli *DockerCli) ImagePullPrivileged(ctx context.Context, authConfig types.
|
|||
}
|
||||
defer responseBody.Close()
|
||||
|
||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
|
||||
return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
|
||||
}
|
||||
|
||||
// ImagePushPrivileged push the image
|
||||
|
|
|
@ -17,11 +17,11 @@ import (
|
|||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
|
||||
height, width := cli.GetTtySize()
|
||||
height, width := cli.Out().GetTtySize()
|
||||
cli.ResizeTtyTo(ctx, id, height, width, isExec)
|
||||
}
|
||||
|
||||
|
@ -70,10 +70,10 @@ func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool
|
|||
|
||||
if runtime.GOOS == "windows" {
|
||||
go func() {
|
||||
prevH, prevW := cli.GetTtySize()
|
||||
prevH, prevW := cli.Out().GetTtySize()
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
h, w := cli.GetTtySize()
|
||||
h, w := cli.Out().GetTtySize()
|
||||
|
||||
if prevW != w || prevH != h {
|
||||
cli.resizeTty(ctx, id, isExec)
|
||||
|
@ -94,21 +94,6 @@ func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetTtySize returns the height and width in characters of the tty
|
||||
func (cli *DockerCli) GetTtySize() (int, int) {
|
||||
if !cli.isTerminalOut {
|
||||
return 0, 0
|
||||
}
|
||||
ws, err := term.GetWinsize(cli.outFd)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error getting size: %s", err)
|
||||
if ws == nil {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
return int(ws.Height), int(ws.Width)
|
||||
}
|
||||
|
||||
// CopyToFile writes the content of the reader to the specified file
|
||||
func CopyToFile(outfile string, r io.Reader) error {
|
||||
tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
|
||||
|
|
|
@ -219,3 +219,14 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stream interface {
|
||||
io.Writer
|
||||
FD() uintptr
|
||||
IsTerminal() bool
|
||||
}
|
||||
|
||||
// DisplayJSONMessagesToStream prints json messages to the output stream
|
||||
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
|
||||
return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue