Skip to content

Commit

Permalink
Merge branch 'main' into docs/show_examples
Browse files Browse the repository at this point in the history
* main: (31 commits)
  feat: support for executing commands in a container with user, workDir and env (testcontainers#1914)
  fix(modules.kafka): Switch to MaxInt for 32-bit support (testcontainers#1923)
  docs: fix code snippet for image substitution (testcontainers#1918)
  Add database driver note to SQL Wait strategy docs (testcontainers#1916)
  Reduce flakiness in ClickHouse tests (testcontainers#1902)
  lint: enable nonamedreturns (testcontainers#1909)
  chore: deprecate BindMount APIs (testcontainers#1907)
  fix(reaper): fix race condition when reusing reapers (testcontainers#1904)
  feat: Allow the container working directory to be specified (testcontainers#1899)
  chore: make rabbitmq examples more readable (testcontainers#1905)
  chore(deps): bump github.com/twmb/franz-go and github.com/twmb/franz-go/pkg/kadm in /modules/redpanda (testcontainers#1896)
  Fix - respect ContainerCustomizer in neo4j module (testcontainers#1903)
  chore(deps): bump github.com/nats-io/nkeys and github.com/nats-io/nats.go in /modules/nats (testcontainers#1897)
  chore: add tests for withNetwork option (testcontainers#1894)
  chore(deps): bump google.golang.org/grpc and cloud.google.com/go/firestore in /modules/gcloud (testcontainers#1891)
  chore(deps): bump github.com/aws/aws-sdk-go and github.com/aws/aws-sdk-go-v2/config in /modules/localstack (testcontainers#1892)
  chore(deps): bump Github actions (testcontainers#1890)
  chore(deps): bump github.com/shirou/gopsutil/v3 from 3.23.9 to 3.23.10 (testcontainers#1858)
  chore(deps): bump github.com/hashicorp/consul/api in /examples/consul (testcontainers#1863)
  chore(deps): bump github.com/IBM/sarama in /modules/kafka (testcontainers#1874)
  ...
  • Loading branch information
mdelapenya committed Nov 20, 2023
2 parents 5cdf944 + 4e6fbdc commit 33d3ea6
Show file tree
Hide file tree
Showing 100 changed files with 1,442 additions and 1,134 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release-drafter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@65c5fb495d1e69aa8c08a3317bc44ff8aabe9772 # v5.19.0
- uses: release-drafter/release-drafter@09c613e259eb8d4e7c81c2cb00618eb5fc4575a7 # v5.19.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/scorecards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
persist-credentials: false

- name: "Run analysis"
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
Expand All @@ -43,7 +43,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: SARIF file
path: results.sarif
Expand Down
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ linters:
- gocritic
- gofumpt
- misspell
- nonamedreturns

linters-settings:
errorlint:
Expand Down
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ verify_ssl = true
[packages]
mkdocs = "==1.5.3"
mkdocs-codeinclude-plugin = "==0.2.1"
mkdocs-include-markdown-plugin = "==6.0.1"
mkdocs-include-markdown-plugin = "==6.0.3"
mkdocs-material = "==9.3.2"
mkdocs-markdownextradata-plugin = "==0.2.5"

Expand Down
211 changes: 105 additions & 106 deletions Pipfile.lock

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ type ContainerRequest struct {
WaitingFor wait.Strategy
Name string // for specifying container name
Hostname string
WorkingDir string // specify the working directory of the container
ExtraHosts []string // Deprecated: Use HostConfigModifier instead
Privileged bool // For starting privileged container
Networks []string // for specifying network names
Expand Down Expand Up @@ -304,6 +305,8 @@ func (c *ContainerRequest) validateContextOrImageIsSpecified() error {
return nil
}

// validateMounts ensures that the mounts do not have duplicate targets.
// It will check the Mounts and HostConfigModifier.Binds fields.
func (c *ContainerRequest) validateMounts() error {
targets := make(map[string]bool, len(c.Mounts))

Expand All @@ -316,5 +319,29 @@ func (c *ContainerRequest) validateMounts() error {
targets[targetPath] = true
}
}

if c.HostConfigModifier == nil {
return nil
}

hostConfig := container.HostConfig{}

c.HostConfigModifier(&hostConfig)

if hostConfig.Binds != nil && len(hostConfig.Binds) > 0 {
for _, bind := range hostConfig.Binds {
parts := strings.Split(bind, ":")
if len(parts) != 2 {
return fmt.Errorf("%w: %s", ErrInvalidBindMount, bind)
}
targetPath := parts[1]
if targets[targetPath] {
return fmt.Errorf("%w: %s", ErrDuplicateMountTarget, targetPath)
} else {
targets[targetPath] = true
}
}
}

return nil
}
77 changes: 19 additions & 58 deletions container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

"github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -55,16 +56,30 @@ func Test_ContainerValidation(t *testing.T) {
Name: "Can mount same source to multiple targets",
ExpectedError: nil,
ContainerRequest: ContainerRequest{
Image: "redis:latest",
Mounts: Mounts(BindMount("/data", "/srv"), BindMount("/data", "/data")),
Image: "redis:latest",
HostConfigModifier: func(hc *container.HostConfig) {
hc.Binds = []string{"/data:/srv", "/data:/data"}
},
},
},
{
Name: "Cannot mount multiple sources to same target",
ExpectedError: errors.New("duplicate mount target detected: /data"),
ContainerRequest: ContainerRequest{
Image: "redis:latest",
Mounts: Mounts(BindMount("/srv", "/data"), BindMount("/data", "/data")),
Image: "redis:latest",
HostConfigModifier: func(hc *container.HostConfig) {
hc.Binds = []string{"/data:/data", "/data:/data"}
},
},
},
{
Name: "Invalid bind mount",
ExpectedError: errors.New("invalid bind mount: /data:/data:/data"),
ContainerRequest: ContainerRequest{
Image: "redis:latest",
HostConfigModifier: func(hc *container.HostConfig) {
hc.Binds = []string{"/data:/data:/data"}
},
},
},
}
Expand Down Expand Up @@ -467,60 +482,6 @@ func TestShouldStartContainersInParallel(t *testing.T) {
}
}

func TestOverrideContainerRequest(t *testing.T) {
req := GenericContainerRequest{
ContainerRequest: ContainerRequest{
Env: map[string]string{
"BAR": "BAR",
},
Image: "foo",
ExposedPorts: []string{"12345/tcp"},
WaitingFor: wait.ForNop(
func(ctx context.Context, target wait.StrategyTarget) error {
return nil
},
),
Networks: []string{"foo", "bar", "baaz"},
NetworkAliases: map[string][]string{
"foo": {"foo0", "foo1", "foo2", "foo3"},
},
},
}

toBeMergedRequest := GenericContainerRequest{
ContainerRequest: ContainerRequest{
Env: map[string]string{
"FOO": "FOO",
},
Image: "bar",
ExposedPorts: []string{"67890/tcp"},
Networks: []string{"foo1", "bar1"},
NetworkAliases: map[string][]string{
"foo1": {"bar"},
},
WaitingFor: wait.ForLog("foo"),
},
}

// the toBeMergedRequest should be merged into the req
CustomizeRequest(toBeMergedRequest)(&req)

// toBeMergedRequest should not be changed
assert.Equal(t, "", toBeMergedRequest.Env["BAR"])
assert.Equal(t, 1, len(toBeMergedRequest.ExposedPorts))
assert.Equal(t, "67890/tcp", toBeMergedRequest.ExposedPorts[0])

// req should be merged with toBeMergedRequest
assert.Equal(t, "FOO", req.Env["FOO"])
assert.Equal(t, "BAR", req.Env["BAR"])
assert.Equal(t, "bar", req.Image)
assert.Equal(t, []string{"12345/tcp", "67890/tcp"}, req.ExposedPorts)
assert.Equal(t, []string{"foo", "bar", "baaz", "foo1", "bar1"}, req.Networks)
assert.Equal(t, []string{"foo0", "foo1", "foo2", "foo3"}, req.NetworkAliases["foo"])
assert.Equal(t, []string{"bar"}, req.NetworkAliases["foo1"])
assert.Equal(t, wait.ForLog("foo"), req.WaitingFor)
}

func TestParseDockerIgnore(t *testing.T) {
testCases := []struct {
filePath string
Expand Down
39 changes: 20 additions & 19 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,8 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
)

var (
// Implement interfaces
_ Container = (*DockerContainer)(nil)

ErrDuplicateMountTarget = errors.New("duplicate mount target detected")
)
// Implement interfaces
var _ Container = (*DockerContainer)(nil)

const (
Bridge = "bridge" // Bridge network name (as well as driver)
Expand Down Expand Up @@ -470,12 +466,16 @@ func (c *DockerContainer) NetworkAliases(ctx context.Context) (map[string][]stri

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,
Detach: false,
AttachStdout: true,
AttachStderr: true,
})

processOptions := tcexec.NewProcessOptions(cmd)

// processing all the options in a first loop because for the multiplexed option
// we first need to have a containerExecCreateResponse
for _, o := range options {
o.Apply(processOptions)
}

response, err := cli.ContainerExecCreate(ctx, c.ID, processOptions.ExecConfig)
if err != nil {
return 0, nil, err
}
Expand All @@ -485,12 +485,12 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string, options ...tce
return 0, nil, err
}

opt := &tcexec.ProcessOptions{
Reader: hijack.Reader,
}
processOptions.Reader = hijack.Reader

// second loop to process the multiplexed option, as now we have a reader
// from the created exec response.
for _, o := range options {
o.Apply(opt)
o.Apply(processOptions)
}

var exitCode int
Expand All @@ -508,7 +508,7 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string, options ...tce
time.Sleep(100 * time.Millisecond)
}

return exitCode, opt.Reader, nil
return exitCode, processOptions.Reader, nil
}

type FileFromContainer struct {
Expand Down Expand Up @@ -999,6 +999,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
Cmd: req.Cmd,
Hostname: req.Hostname,
User: req.User,
WorkingDir: req.WorkingDir,
}

hostConfig := &container.HostConfig{
Expand Down Expand Up @@ -1203,8 +1204,8 @@ func (p *DockerProvider) attemptToPullImage(ctx context.Context, tag string, pul

// Health measure the healthiness of the provider. Right now we leverage the
// docker-client Info endpoint to see if the daemon is reachable.
func (p *DockerProvider) Health(ctx context.Context) (err error) {
_, err = p.client.Info(ctx)
func (p *DockerProvider) Health(ctx context.Context) error {
_, err := p.client.Info(ctx)
defer p.Close()

return err
Expand Down
20 changes: 8 additions & 12 deletions docker_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func prepareLocalRegistryWithAuth(t *testing.T) {
ctx := context.Background()
wd, err := os.Getwd()
assert.NoError(t, err)
// bindMounts {
// copyDirectoryToContainer {
req := ContainerRequest{
Image: "registry:2",
ExposedPorts: []string{"5001:5000/tcp"},
Expand All @@ -253,18 +253,14 @@ func prepareLocalRegistryWithAuth(t *testing.T) {
"REGISTRY_AUTH_HTPASSWD_PATH": "/auth/htpasswd",
"REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY": "/data",
},
Mounts: ContainerMounts{
ContainerMount{
Source: GenericBindMountSource{
HostPath: fmt.Sprintf("%s/testdata/auth", wd),
},
Target: "/auth",
Files: []ContainerFile{
{
HostFilePath: fmt.Sprintf("%s/testdata/auth", wd),
ContainerFilePath: "/auth",
},
ContainerMount{
Source: GenericBindMountSource{
HostPath: fmt.Sprintf("%s/testdata/data", wd),
},
Target: "/data",
{
HostFilePath: fmt.Sprintf("%s/testdata/data", wd),
ContainerFilePath: "/data",
},
},
WaitingFor: wait.ForExposedPort(),
Expand Down
67 changes: 67 additions & 0 deletions docker_exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,73 @@ func TestExecWithMultiplexedResponse(t *testing.T) {
require.Equal(t, "html\n", str)
}

func TestExecWithOptions(t *testing.T) {
tests := []struct {
name string
cmds []string
opts []tcexec.ProcessOption
want string
}{
{
name: "with user",
cmds: []string{"whoami"},
opts: []tcexec.ProcessOption{
tcexec.WithUser("nginx"),
},
want: "nginx\n",
},
{
name: "with working dir",
cmds: []string{"pwd"},
opts: []tcexec.ProcessOption{
tcexec.WithWorkingDir("/var/log/nginx"),
},
want: "/var/log/nginx\n",
},
{
name: "with env",
cmds: []string{"env"},
opts: []tcexec.ProcessOption{
tcexec.WithEnv([]string{"TEST_ENV=test"}),
},
want: "TEST_ENV=test\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(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)

// always append the multiplexed option for having the output
// in a readable format
tt.opts = append(tt.opts, tcexec.Multiplexed())

code, reader, err := container.Exec(ctx, tt.cmds, tt.opts...)
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.Contains(t, str, tt.want)
})
}
}

func TestExecWithNonMultiplexedResponse(t *testing.T) {
ctx := context.Background()
req := ContainerRequest{
Expand Down

0 comments on commit 33d3ea6

Please sign in to comment.