Repository: moby/moby
File: client/container_exec.go (Go)

Code before:
		DetachKeys:   options.DetachKeys,
		Env:          options.Env,
		WorkingDir:   options.WorkingDir,
		Cmd:          options.Cmd,
	}

	resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, req, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return ExecCreateResult{}, err
	}

	var response container.ExecCreateResponse
	err = json.NewDecoder(resp.Body).Decode(&response)
	return ExecCreateResult{ID: response.ID}, err
}

// ExecStartOptions holds options for starting a container exec.
type ExecStartOptions struct {
	// ExecStart will first check if it's detached
	Detach bool
	// Check if there's a tty
	Tty bool
	// Terminal size [height, width], unused if Tty == false
	ConsoleSize *[2]uint `json:",omitempty"`
}

// ExecStartResult holds the result of starting a container exec.
type ExecStartResult struct {
}

// ExecStart starts an exec process already created in the docker host.
func (cli *Client) ExecStart(ctx context.Context, execID string, options ExecStartOptions) (ExecStartResult, error) {
	req := container.ExecStartRequest{
		Detach:      options.Detach,
		Tty:         options.Tty,
		ConsoleSize: options.ConsoleSize,
	}
	resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, req, nil)
	defer ensureReaderClosed(resp)
	return ExecStartResult{}, err
}

// ExecAttachOptions holds options for attaching to a container exec.
type ExecAttachOptions struct {
	// ExecStart will first check if it's detached
	Detach bool
	// Check if there's a tty
	Tty bool
	// Terminal size [height, width], unused if Tty == false
	ConsoleSize *[2]uint `json:",omitempty"`

Review comment (suggestion):
IIUC, the `Detach` is actually not a noop for start:

With `Detach=false` the call would block until the exec exits whereas in the opposite case it would not block.

@thaJeztah 

Code after:
		AttachStdout: options.AttachStdout,
		DetachKeys:   options.DetachKeys,
		Env:          options.Env,
		WorkingDir:   options.WorkingDir,
		Cmd:          options.Cmd,
	}

	resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, req, nil)
	defer ensureReaderClosed(resp)
	if err != nil {
		return ExecCreateResult{}, err
	}

	var response container.ExecCreateResponse
	err = json.NewDecoder(resp.Body).Decode(&response)
	return ExecCreateResult{ID: response.ID}, err
}

type ConsoleSize struct {
	Height, Width uint
}

// ExecStartOptions holds options for starting a container exec.
type ExecStartOptions struct {
	// ExecStart will first check if it's detached
	Detach bool
	// Check if there's a tty
	TTY bool
	// Terminal size [height, width], unused if TTY == false
	ConsoleSize ConsoleSize `json:",omitzero"`
}

// ExecStartResult holds the result of starting a container exec.
type ExecStartResult struct {
}

// ExecStart starts an exec process already created in the docker host.
func (cli *Client) ExecStart(ctx context.Context, execID string, options ExecStartOptions) (ExecStartResult, error) {
	req := container.ExecStartRequest{
		Detach: options.Detach,
		Tty:    options.TTY,
	}
	if err := applyConsoleSize(&req, &options.ConsoleSize); err != nil {
		return ExecStartResult{}, err
	}
	resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, req, nil)
	defer ensureReaderClosed(resp)
	return ExecStartResult{}, err
}

// ExecAttachOptions holds options for attaching to a container exec.