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