From 70932aa8539d51feeb8372c7d233905bdc07304a Mon Sep 17 00:00:00 2001 From: testcontainersbot <116765157+testcontainersbot@users.noreply.github.com> Date: Tue, 15 Nov 2022 13:50:36 +0100 Subject: [PATCH 1/5] chore: sync governance files (#622) --- docs/language-logos/java.svg | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/language-logos/java.svg b/docs/language-logos/java.svg index 986704918f..590da12d96 100644 --- a/docs/language-logos/java.svg +++ b/docs/language-logos/java.svg @@ -1,17 +1,17 @@ - - - - - - + + + + + + + + \ No newline at end of file From f72dfeef32a0c9239d3a6c3d69ab2c3cdee918df Mon Sep 17 00:00:00 2001 From: pantafive <56078241+pantafive@users.noreply.github.com> Date: Tue, 15 Nov 2022 19:36:25 +0100 Subject: [PATCH 2/5] update gotest.md - fix errors in the example (#623) --- docs/quickstart/gotest.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/quickstart/gotest.md b/docs/quickstart/gotest.md index d5c0451423..2c2cb11b03 100644 --- a/docs/quickstart/gotest.md +++ b/docs/quickstart/gotest.md @@ -17,6 +17,9 @@ go get github.com/testcontainers/testcontainers-go ```go import ( + "context" + "testing" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) @@ -36,8 +39,8 @@ func TestWithRedis(t *testing.T) { t.Error(err) } defer func() { - if err := redisC.terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %w", err) + if err := redisC.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate container: %s", err.Error()) } }() } From 1f88e88e96b3920578dac30da9037f900da15b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 16 Nov 2022 12:26:32 +0100 Subject: [PATCH 3/5] chore: retire podman pipeline (#625) --- .github/workflows/ci-podman.yml | 74 --------------------------------- 1 file changed, 74 deletions(-) delete mode 100644 .github/workflows/ci-podman.yml diff --git a/.github/workflows/ci-podman.yml b/.github/workflows/ci-podman.yml deleted file mode 100644 index 64d68f3347..0000000000 --- a/.github/workflows/ci-podman.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Podman pipeline - -on: [push, pull_request] - -jobs: - build: - strategy: - matrix: - go-version: [1.18.x, 1.x] - runs-on: ubuntu-22.04 - steps: - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go-version }} - id: go - - - name: Check out code into the Go module directory - uses: actions/checkout@v3 - - - name: Enable systemd user process - run: | - loginctl enable-linger $(whoami) - sleep 1 - - - name: Set XDG_RUNTIME_DIR - run: echo "XDG_RUNTIME_DIR=/run/user/$UID" >> $GITHUB_ENV - - - name: Install Podman - run: | - set -x - sudo apt-get remove -y podman docker-ce docker docker-engine docker.io containerd runc ||: - sudo apt update - sudo apt install -y software-properties-common debian-archive-keyring dirmngr - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0E98404D386FA1D9 - sudo add-apt-repository -y "deb http://deb.debian.org/debian experimental main" - sudo apt update - sudo apt -y -t experimental install podman crun docker-compose - sudo systemctl daemon-reload - sleep 5 - sudo systemctl start podman.socket - sudo podman info - - - name: Set DOCKER_HOST - run: echo "DOCKER_HOST=unix:///run/podman/podman.sock" >> $GITHUB_ENV - - - name: modVerify - run: go mod verify - - - name: modTidy - run: go mod tidy - - - name: gotestsum - # only run tests on linux, there are a number of things that won't allow the tests to run on anything else - # many (maybe, all?) images used can only be build on Linux, they don't have Windows in their manifest, and - # we can't put Windows Server in "Linux Mode" in Github actions - # another, host mode is only available on Linux, and we have tests around that, do we skip them? - run: sudo make test-unit - - - name: modTidy e2e tests - run: make -C e2e tools-tidy - - - name: Run e2e tests - run: sudo make test-e2e - - - name: Run checker - run: | - ./scripts/check_environment.sh - - - name: Test Summary - uses: test-summary/action@77bb5a9f9c572416423f9a157cbf1159c1e75a4c - with: - paths: "**/TEST-*.xml" - if: always() From 4c4def7778f19ea18fcfaea3605f52703e5e242f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 16 Nov 2022 19:42:26 +0100 Subject: [PATCH 4/5] fix: do not prepend garbage in the container.Exec response (#624) * chore: add a test demonstrating the bug * fix: handle container exec attach using TTY * Revert "fix: handle container exec attach using TTY" This reverts commit 5733288954c0ce56c39dec986ef9c8995593bff1. * chore: handle multiplexed output --- container.go | 3 +- docker.go | 13 +++++++-- docker_exec_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++ exec/processor.go | 44 +++++++++++++++++++++++++++++ wait/exec_test.go | 3 +- wait/exit_test.go | 3 +- wait/log_test.go | 3 +- wait/wait.go | 3 +- 8 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 docker_exec_test.go create mode 100644 exec/processor.go diff --git a/container.go b/container.go index ecf0186305..dc03eb82ed 100644 --- a/container.go +++ b/container.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/go-connections/nat" + tcexec "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/wait" ) @@ -54,7 +55,7 @@ type Container interface { State(context.Context) (*types.ContainerState, error) // returns container's running state Networks(context.Context) ([]string, error) // get container networks NetworkAliases(context.Context) (map[string][]string, error) // get container network aliases for a network - Exec(ctx context.Context, cmd []string) (int, io.Reader, error) + Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error) ContainerIP(context.Context) (string, error) // get container ip ContainerIPs(context.Context) ([]string, error) // get all container IPs CopyToContainer(ctx context.Context, fileContent []byte, containerFilePath string, fileMode int64) error diff --git a/docker.go b/docker.go index 04c70a6487..6e3b681942 100644 --- a/docker.go +++ b/docker.go @@ -33,6 +33,7 @@ import ( "github.com/moby/term" specs "github.com/opencontainers/image-spec/specs-go/v1" + tcexec "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/wait" ) @@ -433,7 +434,7 @@ func (c *DockerContainer) NetworkAliases(ctx context.Context) (map[string][]stri return a, nil } -func (c *DockerContainer) Exec(ctx context.Context, cmd []string) (int, io.Reader, error) { +func (c *DockerContainer) Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error) { cli := c.provider.client response, err := cli.ContainerExecCreate(ctx, c.ID, types.ExecConfig{ Cmd: cmd, @@ -450,6 +451,14 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string) (int, io.Reade return 0, nil, err } + opt := &tcexec.ProcessOptions{ + Reader: hijack.Reader, + } + + for _, o := range options { + o.Apply(opt) + } + var exitCode int for { execResp, err := cli.ContainerExecInspect(ctx, response.ID) @@ -465,7 +474,7 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string) (int, io.Reade time.Sleep(100 * time.Millisecond) } - return exitCode, hijack.Reader, nil + return exitCode, opt.Reader, nil } type FileFromContainer struct { diff --git a/docker_exec_test.go b/docker_exec_test.go new file mode 100644 index 0000000000..a31b1eeb01 --- /dev/null +++ b/docker_exec_test.go @@ -0,0 +1,67 @@ +package testcontainers + +import ( + "context" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" + tcexec "github.com/testcontainers/testcontainers-go/exec" +) + +func TestExecWithMultiplexedResponse(t *testing.T) { + ctx := context.Background() + req := ContainerRequest{ + Image: nginxAlpineImage, + } + + container, err := GenericContainer(ctx, GenericContainerRequest{ + ProviderType: providerType, + ContainerRequest: req, + Started: true, + }) + + require.NoError(t, err) + terminateContainerOnEnd(t, ctx, container) + + code, reader, err := container.Exec(ctx, []string{"ls", "/usr/share/nginx"}, tcexec.Multiplexed()) + require.NoError(t, err) + require.Zero(t, code) + require.NotNil(t, reader) + + b, err := io.ReadAll(reader) + require.NoError(t, err) + require.NotNil(t, b) + + str := string(b) + require.Equal(t, "html\n", str) +} + +func TestExecWithNonMultiplexedResponse(t *testing.T) { + ctx := context.Background() + req := ContainerRequest{ + Image: nginxAlpineImage, + } + + container, err := GenericContainer(ctx, GenericContainerRequest{ + ProviderType: providerType, + ContainerRequest: req, + Started: true, + }) + + require.NoError(t, err) + terminateContainerOnEnd(t, ctx, container) + + code, reader, err := container.Exec(ctx, []string{"ls", "/usr/share/nginx"}) + require.NoError(t, err) + require.Zero(t, code) + require.NotNil(t, reader) + + b, err := io.ReadAll(reader) + require.NoError(t, err) + require.NotNil(t, b) + + str := string(b) + require.True(t, strings.HasSuffix(str, "html\n")) +} diff --git a/exec/processor.go b/exec/processor.go new file mode 100644 index 0000000000..63987e461f --- /dev/null +++ b/exec/processor.go @@ -0,0 +1,44 @@ +package exec + +import ( + "bytes" + "io" + + "github.com/docker/docker/pkg/stdcopy" +) + +// ProcessOptions defines options applicable to the reader processor +type ProcessOptions struct { + Reader io.Reader +} + +// ProcessOption defines a common interface to modify the reader processor +// These options can be passed to the Exec function in a variadic way to customize the returned Reader instance +type ProcessOption interface { + Apply(opts *ProcessOptions) +} + +type ProcessOptionFunc func(opts *ProcessOptions) + +func (fn ProcessOptionFunc) Apply(opts *ProcessOptions) { + fn(opts) +} + +func Multiplexed() ProcessOption { + return ProcessOptionFunc(func(opts *ProcessOptions) { + done := make(chan struct{}) + + var outBuff bytes.Buffer + var errBuff bytes.Buffer + go func() { + if _, err := stdcopy.StdCopy(&outBuff, &errBuff, opts.Reader); err != nil { + return + } + close(done) + }() + + <-done + + opts.Reader = &outBuff + }) +} diff --git a/wait/exec_test.go b/wait/exec_test.go index f796c8893a..7c267b5d60 100644 --- a/wait/exec_test.go +++ b/wait/exec_test.go @@ -12,6 +12,7 @@ import ( "github.com/docker/go-connections/nat" "github.com/testcontainers/testcontainers-go" + tcexec "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/wait" ) @@ -62,7 +63,7 @@ func (st mockExecTarget) Logs(_ context.Context) (io.ReadCloser, error) { return nil, errors.New("not implemented") } -func (st mockExecTarget) Exec(ctx context.Context, _ []string) (int, io.Reader, error) { +func (st mockExecTarget) Exec(ctx context.Context, _ []string, options ...tcexec.ProcessOption) (int, io.Reader, error) { time.Sleep(st.waitDuration) if err := ctx.Err(); err != nil { diff --git a/wait/exit_test.go b/wait/exit_test.go index 7e00136e90..9a4f7eded3 100644 --- a/wait/exit_test.go +++ b/wait/exit_test.go @@ -8,6 +8,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + tcexec "github.com/testcontainers/testcontainers-go/exec" ) type exitStrategyTarget struct { @@ -30,7 +31,7 @@ func (st exitStrategyTarget) Logs(ctx context.Context) (io.ReadCloser, error) { return nil, nil } -func (st exitStrategyTarget) Exec(ctx context.Context, cmd []string) (int, io.Reader, error) { +func (st exitStrategyTarget) Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error) { return 0, nil, nil } diff --git a/wait/log_test.go b/wait/log_test.go index c209cba0d6..bb902d0c44 100644 --- a/wait/log_test.go +++ b/wait/log_test.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + tcexec "github.com/testcontainers/testcontainers-go/exec" ) type noopStrategyTarget struct { @@ -31,7 +32,7 @@ func (st noopStrategyTarget) Logs(ctx context.Context) (io.ReadCloser, error) { return st.ioReaderCloser, nil } -func (st noopStrategyTarget) Exec(ctx context.Context, cmd []string) (int, io.Reader, error) { +func (st noopStrategyTarget) Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error) { return 0, nil, nil } func (st noopStrategyTarget) State(ctx context.Context) (*types.ContainerState, error) { diff --git a/wait/wait.go b/wait/wait.go index fedee44d5f..de6f581136 100644 --- a/wait/wait.go +++ b/wait/wait.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + tcexec "github.com/testcontainers/testcontainers-go/exec" ) type Strategy interface { @@ -18,7 +19,7 @@ type StrategyTarget interface { Ports(ctx context.Context) (nat.PortMap, error) MappedPort(context.Context, nat.Port) (nat.Port, error) Logs(context.Context) (io.ReadCloser, error) - Exec(ctx context.Context, cmd []string) (int, io.Reader, error) + Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error) State(context.Context) (*types.ContainerState, error) } From eb22bbd4d318d42e838ee478f50e9bc10c4ff0f6 Mon Sep 17 00:00:00 2001 From: Ofey Chan Date: Thu, 17 Nov 2022 20:26:28 +0800 Subject: [PATCH 5/5] docs: update method to `nginxC.Terminate` (#627) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5ee79ed3e8..a8637b15c8 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,8 @@ func TestIntegrationNginxLatestReturn(t *testing.T) { // Clean up the container after the test is complete defer func() { - if err := nginxC.terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %w", err) + if err := nginxC.Terminate(ctx); err != nil { + t.Fatalf("failed to terminate container: %v", err) } }()