Skip to content

Commit

Permalink
Merge branch 'testcontainers:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
danielhe4rt authored Jan 8, 2025
2 parents 82e5352 + 3330dc1 commit cf070be
Show file tree
Hide file tree
Showing 35 changed files with 2,450 additions and 163 deletions.
6 changes: 6 additions & 0 deletions .github/scripts/modules/ollama/install-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

curl -fsSL https://ollama.com/install.sh | sh

# kill any running ollama process so that the tests can start from a clean state
sudo systemctl stop ollama.service
10 changes: 10 additions & 0 deletions .github/workflows/ci-test-go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ jobs:
working-directory: ./${{ inputs.project-directory }}
run: go build

- name: Install dependencies
shell: bash
run: |
SCRIPT_PATH="./.github/scripts/${{ inputs.project-directory }}/install-dependencies.sh"
if [ -f "$SCRIPT_PATH" ]; then
$SCRIPT_PATH
else
echo "No dependencies script found at $SCRIPT_PATH - skipping installation"
fi
- name: go test
# 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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ jobs:
merge-multiple: true

- name: Analyze with SonarCloud
uses: sonarsource/sonarcloud-github-action@49e6cd3b187936a73b8280d59ffd9da69df63ec9 # v2.1.1
uses: sonarsource/sonarcloud-github-action@02ef91109b2d589e757aefcfb2854c2783fd7b19 # v4.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -67,7 +67,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -80,6 +80,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
category: "/language:${{matrix.language}}"
3 changes: 2 additions & 1 deletion .github/workflows/docker-moby-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ jobs:
- name: Notify to Slack on failures
if: failure()
id: slack
uses: slackapi/slack-github-action@v1.26.0
uses: slackapi/slack-github-action@v2.0.0
with:
payload-templated: true
payload-file-path: "./payload-slack-content.json"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DOCKER_LATEST_WEBHOOK }}
2 changes: 1 addition & 1 deletion .github/workflows/scorecards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ jobs:

# required for Code scanning alerts
- name: "Upload SARIF results to code scanning"
uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
sarif_file: results.sarif
7 changes: 4 additions & 3 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 57 additions & 41 deletions cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,74 @@ import (
"time"
)

// terminateOptions is a type that holds the options for terminating a container.
type terminateOptions struct {
ctx context.Context
timeout *time.Duration
volumes []string
// TerminateOptions is a type that holds the options for terminating a container.
type TerminateOptions struct {
ctx context.Context
stopTimeout *time.Duration
volumes []string
}

// TerminateOption is a type that represents an option for terminating a container.
type TerminateOption func(*terminateOptions)
type TerminateOption func(*TerminateOptions)

// NewTerminateOptions returns a fully initialised TerminateOptions.
// Defaults: StopTimeout: 10 seconds.
func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions {
timeout := time.Second * 10
options := &TerminateOptions{
stopTimeout: &timeout,
ctx: ctx,
}
for _, opt := range opts {
opt(options)
}
return options
}

// Context returns the context to use during a Terminate.
func (o *TerminateOptions) Context() context.Context {
return o.ctx
}

// StopTimeout returns the stop timeout to use during a Terminate.
func (o *TerminateOptions) StopTimeout() *time.Duration {
return o.stopTimeout
}

// Cleanup performs any clean up needed
func (o *TerminateOptions) Cleanup() error {
// TODO: simplify this when when perform the client refactor.
if len(o.volumes) == 0 {
return nil
}
client, err := NewDockerClientWithOpts(o.ctx)
if err != nil {
return fmt.Errorf("docker client: %w", err)
}
defer client.Close()
// Best effort to remove all volumes.
var errs []error
for _, volume := range o.volumes {
if errRemove := client.VolumeRemove(o.ctx, volume, true); errRemove != nil {
errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove))
}
}
return errors.Join(errs...)
}

// StopContext returns a TerminateOption that sets the context.
// Default: context.Background().
func StopContext(ctx context.Context) TerminateOption {
return func(c *terminateOptions) {
return func(c *TerminateOptions) {
c.ctx = ctx
}
}

// StopTimeout returns a TerminateOption that sets the timeout.
// Default: See [Container.Stop].
func StopTimeout(timeout time.Duration) TerminateOption {
return func(c *terminateOptions) {
c.timeout = &timeout
return func(c *TerminateOptions) {
c.stopTimeout = &timeout
}
}

Expand All @@ -39,7 +84,7 @@ func StopTimeout(timeout time.Duration) TerminateOption {
// which are not removed by default.
// Default: nil.
func RemoveVolumes(volumes ...string) TerminateOption {
return func(c *terminateOptions) {
return func(c *TerminateOptions) {
c.volumes = volumes
}
}
Expand All @@ -54,41 +99,12 @@ func TerminateContainer(container Container, options ...TerminateOption) error {
return nil
}

c := &terminateOptions{
ctx: context.Background(),
}

for _, opt := range options {
opt(c)
}

// TODO: Add a timeout when terminate supports it.
err := container.Terminate(c.ctx)
err := container.Terminate(context.Background(), options...)
if !isCleanupSafe(err) {
return fmt.Errorf("terminate: %w", err)
}

// Remove additional volumes if any.
if len(c.volumes) == 0 {
return nil
}

client, err := NewDockerClientWithOpts(c.ctx)
if err != nil {
return fmt.Errorf("docker client: %w", err)
}

defer client.Close()

// Best effort to remove all volumes.
var errs []error
for _, volume := range c.volumes {
if errRemove := client.VolumeRemove(c.ctx, volume, true); errRemove != nil {
errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove))
}
}

return errors.Join(errs...)
return nil
}

// isNil returns true if val is nil or an nil instance false otherwise.
Expand Down
23 changes: 18 additions & 5 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type Container interface {
Stop(context.Context, *time.Duration) error // stop the container

// Terminate stops and removes the container and its image if it was built and not flagged as kept.
Terminate(ctx context.Context) error
Terminate(ctx context.Context, opts ...TerminateOption) error

Logs(context.Context) (io.ReadCloser, error) // Get logs of the container
FollowOutput(LogConsumer) // Deprecated: it will be removed in the next major release
Expand All @@ -77,7 +77,7 @@ type ImageBuildInfo interface {
GetDockerfile() string // the relative path to the Dockerfile, including the file itself
GetRepo() string // get repo label for image
GetTag() string // get tag label for image
ShouldPrintBuildLog() bool // allow build log to be printed to stdout
BuildLogWriter() io.Writer // for output of build log, use io.Discard to disable the output
ShouldBuildImage() bool // return true if the image needs to be built
GetBuildArgs() map[string]*string // return the environment args used to build the from Dockerfile
GetAuthConfigs() map[string]registry.AuthConfig // Deprecated. Testcontainers will detect registry credentials automatically. Return the auth configs to be able to pull from an authenticated docker registry
Expand All @@ -92,7 +92,8 @@ type FromDockerfile struct {
Repo string // the repo label for image, defaults to UUID
Tag string // the tag label for image, defaults to UUID
BuildArgs map[string]*string // enable user to pass build args to docker daemon
PrintBuildLog bool // enable user to print build log
PrintBuildLog bool // Deprecated: Use BuildLogWriter instead
BuildLogWriter io.Writer // for output of build log, defaults to io.Discard
AuthConfigs map[string]registry.AuthConfig // Deprecated. Testcontainers will detect registry credentials automatically. Enable auth configs to be able to pull from an authenticated docker registry
// KeepImage describes whether DockerContainer.Terminate should not delete the
// container image. Useful for images that are built from a Dockerfile and take a
Expand Down Expand Up @@ -410,8 +411,20 @@ func (c *ContainerRequest) ShouldKeepBuiltImage() bool {
return c.FromDockerfile.KeepImage
}

func (c *ContainerRequest) ShouldPrintBuildLog() bool {
return c.FromDockerfile.PrintBuildLog
// BuildLogWriter returns the io.Writer for output of log when building a Docker image from
// a Dockerfile. It returns the BuildLogWriter from the ContainerRequest, defaults to io.Discard.
// For backward compatibility, if BuildLogWriter is default and PrintBuildLog is true,
// the function returns os.Stderr.
func (c *ContainerRequest) BuildLogWriter() io.Writer {
if c.FromDockerfile.BuildLogWriter != nil {
return c.FromDockerfile.BuildLogWriter
}
if c.FromDockerfile.PrintBuildLog {
c.FromDockerfile.BuildLogWriter = os.Stderr
} else {
c.FromDockerfile.BuildLogWriter = io.Discard
}
return c.FromDockerfile.BuildLogWriter
}

// BuildOptions returns the image build options when building a Docker image from a Dockerfile.
Expand Down
32 changes: 21 additions & 11 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,11 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro
// The following hooks are called in order:
// - [ContainerLifecycleHooks.PreTerminates]
// - [ContainerLifecycleHooks.PostTerminates]
func (c *DockerContainer) Terminate(ctx context.Context) error {
// ContainerRemove hardcodes stop timeout to 3 seconds which is too short
// to ensure that child containers are stopped so we manually call stop.
// TODO: make this configurable via a functional option.
timeout := 10 * time.Second
err := c.Stop(ctx, &timeout)
//
// Default: timeout is 10 seconds.
func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption) error {
options := NewTerminateOptions(ctx, opts...)
err := c.Stop(options.Context(), options.StopTimeout())
if err != nil && !isCleanupSafe(err) {
return fmt.Errorf("stop: %w", err)
}
Expand Down Expand Up @@ -343,6 +342,10 @@ func (c *DockerContainer) Terminate(ctx context.Context) error {
c.sessionID = ""
c.isRunning = false

if err = options.Cleanup(); err != nil {
errs = append(errs, err)
}

return errors.Join(errs...)
}

Expand Down Expand Up @@ -1004,10 +1007,7 @@ func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (st
}
defer resp.Body.Close()

output := io.Discard
if img.ShouldPrintBuildLog() {
output = os.Stderr
}
output := img.BuildLogWriter()

// Always process the output, even if it is not printed
// to ensure that errors during the build process are
Expand Down Expand Up @@ -1498,7 +1498,11 @@ func (p *DockerProvider) daemonHostLocked(ctx context.Context) (string, error) {
p.hostCache = daemonURL.Hostname()
case "unix", "npipe":
if core.InAContainer() {
ip, err := p.GetGatewayIP(ctx)
defaultNetwork, err := p.ensureDefaultNetworkLocked(ctx)
if err != nil {
return "", fmt.Errorf("ensure default network: %w", err)
}
ip, err := p.getGatewayIP(ctx, defaultNetwork)
if err != nil {
ip, err = core.DefaultGatewayIP()
if err != nil {
Expand Down Expand Up @@ -1598,7 +1602,10 @@ func (p *DockerProvider) GetGatewayIP(ctx context.Context) (string, error) {
if err != nil {
return "", fmt.Errorf("ensure default network: %w", err)
}
return p.getGatewayIP(ctx, defaultNetwork)
}

func (p *DockerProvider) getGatewayIP(ctx context.Context, defaultNetwork string) (string, error) {
nw, err := p.GetNetwork(ctx, NetworkRequest{Name: defaultNetwork})
if err != nil {
return "", err
Expand All @@ -1624,7 +1631,10 @@ func (p *DockerProvider) GetGatewayIP(ctx context.Context) (string, error) {
func (p *DockerProvider) ensureDefaultNetwork(ctx context.Context) (string, error) {
p.mtx.Lock()
defer p.mtx.Unlock()
return p.ensureDefaultNetworkLocked(ctx)
}

func (p *DockerProvider) ensureDefaultNetworkLocked(ctx context.Context) (string, error) {
if p.defaultNetwork != "" {
// Already set.
return p.defaultNetwork, nil
Expand Down
Loading

0 comments on commit cf070be

Please sign in to comment.