container.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package container
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "runtime"
  7. "sync"
  8. "testing"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/container"
  11. "github.com/docker/docker/api/types/network"
  12. "github.com/docker/docker/client"
  13. "github.com/docker/docker/pkg/stdcopy"
  14. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  15. "gotest.tools/v3/assert"
  16. )
  17. // TestContainerConfig holds container configuration struct that
  18. // are used in api calls.
  19. type TestContainerConfig struct {
  20. Name string
  21. Config *container.Config
  22. HostConfig *container.HostConfig
  23. NetworkingConfig *network.NetworkingConfig
  24. Platform *ocispec.Platform
  25. }
  26. // NewTestConfig creates a new TestContainerConfig with the provided options.
  27. //
  28. // If no options are passed, it creates a default config, which is a busybox
  29. // container running "top" (on Linux) or "sleep" (on Windows).
  30. func NewTestConfig(ops ...func(*TestContainerConfig)) *TestContainerConfig {
  31. cmd := []string{"top"}
  32. if runtime.GOOS == "windows" {
  33. cmd = []string{"sleep", "240"}
  34. }
  35. config := &TestContainerConfig{
  36. Config: &container.Config{
  37. Image: "busybox",
  38. Cmd: cmd,
  39. },
  40. HostConfig: &container.HostConfig{},
  41. NetworkingConfig: &network.NetworkingConfig{},
  42. }
  43. for _, op := range ops {
  44. op(config)
  45. }
  46. return config
  47. }
  48. // Create creates a container with the specified options, asserting that there was no error.
  49. func Create(ctx context.Context, t *testing.T, apiClient client.APIClient, ops ...func(*TestContainerConfig)) string {
  50. t.Helper()
  51. config := NewTestConfig(ops...)
  52. c, err := apiClient.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name)
  53. assert.NilError(t, err)
  54. return c.ID
  55. }
  56. // CreateFromConfig creates a container from the given TestContainerConfig.
  57. //
  58. // Example use:
  59. //
  60. // ctr, err := container.CreateFromConfig(ctx, apiClient, container.NewTestConfig(container.WithAutoRemove))
  61. // assert.Check(t, err)
  62. func CreateFromConfig(ctx context.Context, apiClient client.APIClient, config *TestContainerConfig) (container.CreateResponse, error) {
  63. return apiClient.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name)
  64. }
  65. // Run creates and start a container with the specified options
  66. func Run(ctx context.Context, t *testing.T, apiClient client.APIClient, ops ...func(*TestContainerConfig)) string {
  67. t.Helper()
  68. id := Create(ctx, t, apiClient, ops...)
  69. err := apiClient.ContainerStart(ctx, id, container.StartOptions{})
  70. assert.NilError(t, err)
  71. return id
  72. }
  73. type RunResult struct {
  74. ContainerID string
  75. ExitCode int
  76. Stdout *bytes.Buffer
  77. Stderr *bytes.Buffer
  78. }
  79. func RunAttach(ctx context.Context, t *testing.T, apiClient client.APIClient, ops ...func(config *TestContainerConfig)) RunResult {
  80. t.Helper()
  81. ops = append(ops, func(c *TestContainerConfig) {
  82. c.Config.AttachStdout = true
  83. c.Config.AttachStderr = true
  84. })
  85. id := Create(ctx, t, apiClient, ops...)
  86. aresp, err := apiClient.ContainerAttach(ctx, id, container.AttachOptions{
  87. Stream: true,
  88. Stdout: true,
  89. Stderr: true,
  90. })
  91. assert.NilError(t, err)
  92. err = apiClient.ContainerStart(ctx, id, container.StartOptions{})
  93. assert.NilError(t, err)
  94. s, err := demultiplexStreams(ctx, aresp)
  95. if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) {
  96. assert.NilError(t, err)
  97. }
  98. // Inspect to get the exit code. A new context is used here to make sure that if the context passed as argument as
  99. // reached timeout during the demultiplexStream call, we still return a RunResult.
  100. resp, err := apiClient.ContainerInspect(context.Background(), id)
  101. assert.NilError(t, err)
  102. return RunResult{ContainerID: id, ExitCode: resp.State.ExitCode, Stdout: &s.stdout, Stderr: &s.stderr}
  103. }
  104. type streams struct {
  105. stdout, stderr bytes.Buffer
  106. }
  107. // demultiplexStreams starts a goroutine to demultiplex stdout and stderr from the types.HijackedResponse resp and
  108. // waits until either multiplexed stream reaches EOF or the context expires. It unconditionally closes resp and waits
  109. // until the demultiplexing goroutine has finished its work before returning.
  110. func demultiplexStreams(ctx context.Context, resp types.HijackedResponse) (streams, error) {
  111. var s streams
  112. outputDone := make(chan error, 1)
  113. var wg sync.WaitGroup
  114. wg.Add(1)
  115. go func() {
  116. _, err := stdcopy.StdCopy(&s.stdout, &s.stderr, resp.Reader)
  117. outputDone <- err
  118. wg.Done()
  119. }()
  120. var err error
  121. select {
  122. case copyErr := <-outputDone:
  123. err = copyErr
  124. break
  125. case <-ctx.Done():
  126. err = ctx.Err()
  127. }
  128. resp.Close()
  129. wg.Wait()
  130. return s, err
  131. }
  132. func Remove(ctx context.Context, t *testing.T, apiClient client.APIClient, container string, options container.RemoveOptions) {
  133. t.Helper()
  134. err := apiClient.ContainerRemove(ctx, container, options)
  135. assert.NilError(t, err)
  136. }
  137. func Inspect(ctx context.Context, t *testing.T, apiClient client.APIClient, containerRef string) types.ContainerJSON {
  138. t.Helper()
  139. c, err := apiClient.ContainerInspect(ctx, containerRef)
  140. assert.NilError(t, err)
  141. return c
  142. }