exec.go 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package container
  2. import (
  3. "bytes"
  4. "context"
  5. "testing"
  6. "github.com/docker/docker/api/types"
  7. "github.com/docker/docker/client"
  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. // AssertSuccess fails the test and stops execution if the command exited with a
  28. // nonzero status code.
  29. func (res ExecResult) AssertSuccess(t testing.TB) {
  30. t.Helper()
  31. if res.ExitCode != 0 {
  32. t.Logf("expected exit code 0, got %d", res.ExitCode)
  33. t.Logf("stdout: %s", res.Stdout())
  34. t.Logf("stderr: %s", res.Stderr())
  35. t.FailNow()
  36. }
  37. }
  38. // Exec executes a command inside a container, returning the result
  39. // containing stdout, stderr, and exit code. Note:
  40. // - this is a synchronous operation;
  41. // - cmd stdin is closed.
  42. func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []string, ops ...func(*types.ExecConfig)) (ExecResult, error) {
  43. // prepare exec
  44. execConfig := types.ExecConfig{
  45. AttachStdout: true,
  46. AttachStderr: true,
  47. Cmd: cmd,
  48. }
  49. for _, op := range ops {
  50. op(&execConfig)
  51. }
  52. cresp, err := apiClient.ContainerExecCreate(ctx, id, execConfig)
  53. if err != nil {
  54. return ExecResult{}, err
  55. }
  56. execID := cresp.ID
  57. // run it, with stdout/stderr attached
  58. aresp, err := apiClient.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
  59. if err != nil {
  60. return ExecResult{}, err
  61. }
  62. // read the output
  63. s, err := demultiplexStreams(ctx, aresp)
  64. if err != nil {
  65. return ExecResult{}, err
  66. }
  67. // get the exit code
  68. iresp, err := apiClient.ContainerExecInspect(ctx, execID)
  69. if err != nil {
  70. return ExecResult{}, err
  71. }
  72. return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &s.stdout, errBuffer: &s.stderr}, nil
  73. }
  74. // ExecT calls Exec() and aborts the test if an error occurs.
  75. func ExecT(ctx context.Context, t testing.TB, apiClient client.APIClient, id string, cmd []string, ops ...func(*types.ExecConfig)) ExecResult {
  76. t.Helper()
  77. res, err := Exec(ctx, apiClient, id, cmd, ops...)
  78. if err != nil {
  79. t.Fatal(err)
  80. }
  81. return res
  82. }