exec.go 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package container
  2. import (
  3. "bytes"
  4. "context"
  5. "github.com/docker/docker/api/types"
  6. "github.com/docker/docker/client"
  7. "github.com/docker/docker/pkg/stdcopy"
  8. )
  9. // ExecResult represents a result returned from Exec()
  10. type ExecResult struct {
  11. ExitCode int
  12. outBuffer *bytes.Buffer
  13. errBuffer *bytes.Buffer
  14. }
  15. // Stdout returns stdout output of a command run by Exec()
  16. func (res *ExecResult) Stdout() string {
  17. return res.outBuffer.String()
  18. }
  19. // Stderr returns stderr output of a command run by Exec()
  20. func (res *ExecResult) Stderr() string {
  21. return res.errBuffer.String()
  22. }
  23. // Combined returns combined stdout and stderr output of a command run by Exec()
  24. func (res *ExecResult) Combined() string {
  25. return res.outBuffer.String() + res.errBuffer.String()
  26. }
  27. // Exec executes a command inside a container, returning the result
  28. // containing stdout, stderr, and exit code. Note:
  29. // - this is a synchronous operation;
  30. // - cmd stdin is closed.
  31. func Exec(ctx context.Context, cli client.APIClient, id string, cmd []string, ops ...func(*types.ExecConfig)) (ExecResult, error) {
  32. // prepare exec
  33. execConfig := types.ExecConfig{
  34. AttachStdout: true,
  35. AttachStderr: true,
  36. Cmd: cmd,
  37. }
  38. for _, op := range ops {
  39. op(&execConfig)
  40. }
  41. cresp, err := cli.ContainerExecCreate(ctx, id, execConfig)
  42. if err != nil {
  43. return ExecResult{}, err
  44. }
  45. execID := cresp.ID
  46. // run it, with stdout/stderr attached
  47. aresp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
  48. if err != nil {
  49. return ExecResult{}, err
  50. }
  51. defer aresp.Close()
  52. // read the output
  53. var outBuf, errBuf bytes.Buffer
  54. outputDone := make(chan error, 1)
  55. go func() {
  56. // StdCopy demultiplexes the stream into two buffers
  57. _, err = stdcopy.StdCopy(&outBuf, &errBuf, aresp.Reader)
  58. outputDone <- err
  59. }()
  60. select {
  61. case err := <-outputDone:
  62. if err != nil {
  63. return ExecResult{}, err
  64. }
  65. break
  66. case <-ctx.Done():
  67. return ExecResult{}, ctx.Err()
  68. }
  69. // get the exit code
  70. iresp, err := cli.ContainerExecInspect(ctx, execID)
  71. if err != nil {
  72. return ExecResult{}, err
  73. }
  74. return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &outBuf, errBuffer: &errBuf}, nil
  75. }