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  }