2

I am trying to exec (interact with) a docker container, with Go. This is the code I am using:

func (docker *Docker) redirectResponseToOutputStream(outputStream, errorStream io.Writer, resp io.Reader) error {
    _, err := stdcopy.StdCopy(outputStream, errorStream, resp)
    return err
}

func (docker *Docker) holdHijackedConnection(inputStream io.Reader, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
    receiveStdout := make(chan error)
    if outputStream != nil || errorStream != nil {
        go func() {
            receiveStdout <- docker.redirectResponseToOutputStream(outputStream, errorStream, resp.Reader)
        }()
    }

    stdinDone := make(chan struct{})
    go func() {
        if inputStream != nil {
            io.Copy(resp.Conn, inputStream)
        }
        resp.CloseWrite()
        close(stdinDone)
    }()

    select {
    case err := <-receiveStdout:
        return err
    case <-stdinDone:
        if outputStream != nil || errorStream != nil {
            return <-receiveStdout
        }
    }

    return nil
}

...and call the the holdHijackedConnection here:

func (docker *Docker) ContainerExec(ctx context.Context, container *injection.Container) error {
    createResponse, err := docker.client.ContainerExecCreate(ctx, container.ID, types.ExecConfig{
        AttachStdout: true,
        AttachStderr: true,
        AttachStdin:  true,
        Detach:       true,
        Tty:          true,
        Cmd:          []string{"sh"},
    })
    if err != nil {
        return err
    }

    stream, err := docker.client.ContainerExecAttach(ctx, createResponse.ID, types.ExecStartCheck{})
    if err != nil {
        return err
    }

    defer stream.Close()
    docker.holdHijackedConnection(os.Stdin, os.Stdout, os.Stderr, stream)
    return nil
}

Some notes:

  • sh is necessary, it's an alpine image
  • injection.Container just holds information about the container, it's a custom struct
  • Docker is a struct, that holds the docker client (an instance from Client at github.com/docker/docker/client)

What I get as a result to the cli, if I execute my application, is something like this:

/usr/app $ ^[[43;12R

I far as I know, the ^[[43;12R is the ANSI escape code for the position of the cursor. I can execute commands, like ls or npm i whatever, but I alwasy get back these ANSI escape codes.

My question is, is there some way to remove these from the stdout?

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105

1 Answers1

1

I eventually found out.

The problem was, that I should use the github.com/docker/cli/cli/command package and its DockerCli instead of os.Std.... This can manage this for me, by setting the output, error and input stream like this:

func (docker *Docker) holdHijackedConnection(resp types.HijackedResponse) error {
    cli, _ := command.NewDockerCli()
    outputStream := cli.Out()
    errorStream := cli.Err()
    inputStream := cli.In()

    inputStream.SetRawTerminal()
    defer inputStream.RestoreTerminal()

    receiveStdout := make(chan error)
    if outputStream != nil || errorStream != nil {
        go func() {
            receiveStdout <- docker.redirectResponseToOutputStream(outputStream, errorStream, resp.Reader)
        }()
    }

    stdinDone := make(chan struct{})
    go func() {
        if inputStream != nil {
            io.Copy(resp.Conn, inputStream)
        }
        resp.CloseWrite()
        close(stdinDone)
    }()

    select {
    case err := <-receiveStdout:
        return err
    case <-stdinDone:
        if outputStream != nil || errorStream != nil {
            return <-receiveStdout
        }
    }

    return nil
}

If you want to add CTRL+C to escape, you should set the DetachKeys in ExecConfig at ContainerExecCreate. Otherwise executing exit will detach it.