Skip to content

Commit

Permalink
Merge branch 'main' into issue-525
Browse files Browse the repository at this point in the history
* main:
  chore: move internal/testcontainersdocker package's files to internal/core (testcontainers#2083)
  GenericContainer: in case of error: return a reference to the failed container (testcontainers#2082)
  [breaking] Add err chan to log producer and don't panic on error (testcontainers#1971)
  chore: enrich HTTP headers to the Docker daemon with the project path (testcontainers#2080)
  fix: align codeql versions in GH workflow (testcontainers#2081)
  chore(deps): bump go.mongodb.org/mongo-driver in /modules/mongodb (testcontainers#2065)
  chore(deps): bump github.com/shirou/gopsutil/v3 from 3.23.11 to 3.23.12 (testcontainers#2068)
  fix(modules.gcloud): pass as ptr to allow request customization (testcontainers#1972)
  chore(deps): bump github.com/twmb/franz-go in /modules/redpanda (testcontainers#2072)
  chore(deps): bump k8s.io/api, k8s.io/apimachinery, k8s.io/client-go from 0.28.4 to 0.29.0 in /modules/k3s (testcontainers#2078)
  chore(deps): bump github.com/ClickHouse/clickhouse-go/v2 (testcontainers#2066)
  chore(deps): bump github.com/google/uuid from 1.4.0 to 1.5.0 (testcontainers#2077)
  bump google.golang.org/api from 0.153.0 to 0.154.0, cloud.google.com/go/spanner from 1.53.1 to 1.54.0, bump google.golang.org/grpc from 1.59.0 to 1.60.1 in /modules/gcloud (testcontainers#2076)
  chore(deps): bump github.com/aws/aws-sdk-go-v2 from 1.23.5 to 1.24.0 (credentials from 1.16.9 to 1.16.13, service/s3 from 1.47.1 to 1.47.7)  in /modules/localstack (testcontainers#2075)
  chore(deps): bump github/codeql-action from 2 to 3 (testcontainers#2056)
  chore(deps): bump test-summary/action from 2.1 to 2.2 (testcontainers#2058)
  chore(deps): bump actions/setup-go from 4 to 5 (testcontainers#2057)
  • Loading branch information
mdelapenya committed Jan 9, 2024
2 parents c2bb78f + 62d6214 commit 2b08a80
Show file tree
Hide file tree
Showing 99 changed files with 701 additions and 419 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-test-go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4

- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
with:
go-version: '${{ inputs.go-version }}'
cache-dependency-path: '${{ inputs.project-directory }}/go.sum'
Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
./scripts/check_environment.sh
- name: Test Summary
uses: test-summary/action@62bc5c68de2a6a0d02039763b8c754569df99e3f # v2
uses: test-summary/action@fee35d7df20790255fe6aa92cf0f6d28092ecf2f # v2
with:
paths: "**/${{ inputs.project-directory }}/TEST-unit*.xml"
if: always()
2 changes: 1 addition & 1 deletion .github/workflows/ci-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
ref: ${{ github.event.client_payload.pull_request.head.ref }}

- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
with:
go-version-file: go.mod
id: go
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -64,7 +64,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@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2
uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.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 @@ -77,6 +77,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2
uses: github/codeql-action/analyze@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0
with:
category: "/language:${{matrix.language}}"
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@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
uses: github/codeql-action/upload-sarif@1500a131381b66de0c52ac28abb13cd79f4b7ecc # v2.22.12
with:
sarif_file: results.sarif
7 changes: 4 additions & 3 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/moby/patternmatcher/ignorefile"

tcexec "github.com/testcontainers/testcontainers-go/exec"
"github.com/testcontainers/testcontainers-go/internal/testcontainersdocker"
"github.com/testcontainers/testcontainers-go/internal/core"
"github.com/testcontainers/testcontainers-go/wait"
)

Expand Down Expand Up @@ -48,7 +48,7 @@ type Container interface {
Terminate(context.Context) error // terminate the container
Logs(context.Context) (io.ReadCloser, error) // Get logs of the container
FollowOutput(LogConsumer)
StartLogProducer(context.Context) error
StartLogProducer(context.Context, ...LogProducerOption) error
StopLogProducer() error
Name(context.Context) (string, error) // get container name
State(context.Context) (*types.ContainerState, error) // returns container's running state
Expand All @@ -61,6 +61,7 @@ type Container interface {
CopyDirToContainer(ctx context.Context, hostDirPath string, containerParentPath string, fileMode int64) error
CopyFileToContainer(ctx context.Context, hostFilePath string, containerFilePath string, fileMode int64) error
CopyFileFromContainer(ctx context.Context, filePath string) (io.ReadCloser, error)
GetLogProducerErrorChannel() <-chan error
}

// ImageBuildInfo defines what is needed to build an image
Expand Down Expand Up @@ -274,7 +275,7 @@ func (c *ContainerRequest) GetAuthConfigs() map[string]registry.AuthConfig {

// getAuthConfigsFromDockerfile returns the auth configs to be able to pull from an authenticated docker registry
func getAuthConfigsFromDockerfile(c *ContainerRequest) map[string]registry.AuthConfig {
images, err := testcontainersdocker.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs())
images, err := core.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs())
if err != nil {
return map[string]registry.AuthConfig{}
}
Expand Down
116 changes: 86 additions & 30 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"

"github.com/cenkalti/backoff/v4"
Expand All @@ -32,8 +33,7 @@ import (

tcexec "github.com/testcontainers/testcontainers-go/exec"
"github.com/testcontainers/testcontainers-go/internal/config"
"github.com/testcontainers/testcontainers-go/internal/testcontainersdocker"
"github.com/testcontainers/testcontainers-go/internal/testcontainerssession"
"github.com/testcontainers/testcontainers-go/internal/core"
"github.com/testcontainers/testcontainers-go/wait"
)

Expand Down Expand Up @@ -67,6 +67,9 @@ type DockerContainer struct {
raw *types.ContainerJSON
stopProducer chan bool
producerDone chan bool
producerError chan error
producerMutex sync.Mutex
producerTimeout *time.Duration
logger Logging
lifecycleHooks []ContainerLifecycleHooks
}
Expand Down Expand Up @@ -612,19 +615,68 @@ func (c *DockerContainer) CopyToContainer(ctx context.Context, fileContent []byt
return nil
}

type LogProducerOption func(*DockerContainer)

// WithLogProducerTimeout is a functional option that sets the timeout for the log producer.
// If the timeout is lower than 5s or greater than 60s it will be set to 5s or 60s respectively.
func WithLogProducerTimeout(timeout time.Duration) LogProducerOption {
return func(c *DockerContainer) {
c.producerTimeout = &timeout
}
}

// StartLogProducer will start a concurrent process that will continuously read logs
// from the container and will send them to each added LogConsumer
func (c *DockerContainer) StartLogProducer(ctx context.Context) error {
if c.stopProducer != nil {
return errors.New("log producer already started")
// from the container and will send them to each added LogConsumer.
// Default log producer timeout is 5s. It is used to set the context timeout
// which means that each log-reading loop will last at least the specified timeout
// and that it cannot be cancelled earlier.
// Use functional option WithLogProducerTimeout() to override default timeout. If it's
// lower than 5s and greater than 60s it will be set to 5s or 60s respectively.
func (c *DockerContainer) StartLogProducer(ctx context.Context, opts ...LogProducerOption) error {
{
c.producerMutex.Lock()
defer c.producerMutex.Unlock()

if c.stopProducer != nil {
return errors.New("log producer already started")
}
}

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

minProducerTimeout := time.Duration(5 * time.Second)
maxProducerTimeout := time.Duration(60 * time.Second)

if c.producerTimeout == nil {
c.producerTimeout = &minProducerTimeout
}

if *c.producerTimeout < minProducerTimeout {
c.producerTimeout = &minProducerTimeout
}

if *c.producerTimeout > maxProducerTimeout {
c.producerTimeout = &maxProducerTimeout
}

c.stopProducer = make(chan bool)
c.producerDone = make(chan bool)
c.producerError = make(chan error, 1)

go func(stop <-chan bool, done chan<- bool) {
go func(stop <-chan bool, done chan<- bool, errorCh chan error) {
// signal the producer is done once go routine exits, this prevents race conditions around start/stop
defer close(done)
// set c.stopProducer to nil so that it can be started again
defer func() {
defer c.producerMutex.Unlock()
close(done)
close(errorCh)
{
c.producerMutex.Lock()
c.stopProducer = nil
}
}()

since := ""
// if the socket is closed we will make additional logs request with updated Since timestamp
Expand All @@ -636,25 +688,20 @@ func (c *DockerContainer) StartLogProducer(ctx context.Context) error {
Since: since,
}

ctx, cancel := context.WithTimeout(ctx, time.Second*5)
ctx, cancel := context.WithTimeout(ctx, *c.producerTimeout)
defer cancel()

r, err := c.provider.client.ContainerLogs(ctx, c.GetContainerID(), options)
if err != nil {
// if we can't get the logs, panic, we can't return an error to anything
// from within this goroutine
panic(err)
errorCh <- err
return
}
defer c.provider.Close()

for {
select {
case <-stop:
err := r.Close()
if err != nil {
// we can't close the read closer, this should never happen
panic(err)
}
errorCh <- r.Close()
return
default:
h := make([]byte, 8)
Expand Down Expand Up @@ -710,24 +757,33 @@ func (c *DockerContainer) StartLogProducer(ctx context.Context) error {
}
}
}
}(c.stopProducer, c.producerDone)
}(c.stopProducer, c.producerDone, c.producerError)

return nil
}

// StopLogProducer will stop the concurrent process that is reading logs
// and sending them to each added LogConsumer
func (c *DockerContainer) StopLogProducer() error {
c.producerMutex.Lock()
defer c.producerMutex.Unlock()
if c.stopProducer != nil {
c.stopProducer <- true
// block until the producer is actually done in order to avoid strange races
<-c.producerDone
c.stopProducer = nil
c.producerDone = nil
return <-c.producerError
}
return nil
}

// GetLogProducerErrorChannel exposes the only way for the consumer
// to be able to listen to errors and react to them.
func (c *DockerContainer) GetLogProducerErrorChannel() <-chan error {
return c.producerError
}

// DockerNetwork represents a network started using Docker
type DockerNetwork struct {
ID string // Network ID from Docker
Expand Down Expand Up @@ -877,7 +933,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
// the reaper does not need to start a reaper for itself
isReaperContainer := strings.HasSuffix(imageName, config.ReaperDefaultImage)
if !tcConfig.RyukDisabled && !isReaperContainer {
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), testcontainerssession.SessionID(), p)
r, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), core.SessionID(), p)
if err != nil {
return nil, fmt.Errorf("%w: creating reaper failed", err)
}
Expand Down Expand Up @@ -973,7 +1029,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque

if !isReaperContainer {
// add the labels that the reaper will use to terminate the container to the request
for k, v := range testcontainersdocker.DefaultLabels(testcontainerssession.SessionID()) {
for k, v := range core.DefaultLabels(core.SessionID()) {
req.Labels[k] = v
}
}
Expand Down Expand Up @@ -1090,7 +1146,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
Image: imageName,
imageWasBuilt: req.ShouldBuildImage(),
keepBuiltImage: req.ShouldKeepBuiltImage(),
sessionID: testcontainerssession.SessionID(),
sessionID: core.SessionID(),
provider: p,
terminationSignal: termSignal,
stopProducer: nil,
Expand Down Expand Up @@ -1137,13 +1193,13 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
return p.CreateContainer(ctx, req)
}

sessionID := testcontainerssession.SessionID()
sessionID := core.SessionID()

tcConfig := p.Config().Config

var termSignal chan bool
if !tcConfig.RyukDisabled {
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), sessionID, p)
r, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), sessionID, p)
if err != nil {
return nil, fmt.Errorf("%w: creating reaper failed", err)
}
Expand Down Expand Up @@ -1257,10 +1313,10 @@ func daemonHost(ctx context.Context, p *DockerProvider) (string, error) {
case "http", "https", "tcp":
p.hostCache = url.Hostname()
case "unix", "npipe":
if testcontainersdocker.InAContainer() {
if core.InAContainer() {
ip, err := p.GetGatewayIP(ctx)
if err != nil {
ip, err = testcontainersdocker.DefaultGatewayIP()
ip, err = core.DefaultGatewayIP()
if err != nil {
ip = "localhost"
}
Expand Down Expand Up @@ -1308,11 +1364,11 @@ func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest)
IPAM: req.IPAM,
}

sessionID := testcontainerssession.SessionID()
sessionID := core.SessionID()

var termSignal chan bool
if !tcConfig.RyukDisabled {
r, err := reuseOrCreateReaper(context.WithValue(ctx, testcontainersdocker.DockerHostContextKey, p.host), sessionID, p)
r, err := reuseOrCreateReaper(context.WithValue(ctx, core.DockerHostContextKey, p.host), sessionID, p)
if err != nil {
return nil, fmt.Errorf("%w: creating network reaper failed", err)
}
Expand All @@ -1323,7 +1379,7 @@ func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest)
}

// add the labels that the reaper will use to terminate the network to the request
for k, v := range testcontainersdocker.DefaultLabels(sessionID) {
for k, v := range core.DefaultLabels(sessionID) {
req.Labels[k] = v
}

Expand Down Expand Up @@ -1419,7 +1475,7 @@ func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APICl
_, err = cli.NetworkCreate(ctx, reaperNetwork, types.NetworkCreate{
Driver: Bridge,
Attachable: true,
Labels: testcontainersdocker.DefaultLabels(testcontainerssession.SessionID()),
Labels: core.DefaultLabels(core.SessionID()),
})

if err != nil {
Expand Down Expand Up @@ -1450,7 +1506,7 @@ func containerFromDockerResponse(ctx context.Context, response types.Container)
}
container.provider = provider

container.sessionID = testcontainerssession.SessionID()
container.sessionID = core.SessionID()
container.consumers = []LogConsumer{}
container.stopProducer = nil
container.isRunning = response.State == "running"
Expand Down
8 changes: 4 additions & 4 deletions docker_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import (
"github.com/cpuguy83/dockercfg"
"github.com/docker/docker/api/types/registry"

"github.com/testcontainers/testcontainers-go/internal/testcontainersdocker"
"github.com/testcontainers/testcontainers-go/internal/core"
)

// DockerImageAuth returns the auth config for the given Docker image, extracting first its Docker registry.
// Finally, it will use the credential helpers to extract the information from the docker config file
// for that registry, if it exists.
func DockerImageAuth(ctx context.Context, image string) (string, registry.AuthConfig, error) {
defaultRegistry := defaultRegistry(ctx)
reg := testcontainersdocker.ExtractRegistry(image, defaultRegistry)
reg := core.ExtractRegistry(image, defaultRegistry)

cfgs, err := getDockerAuthConfigs()
if err != nil {
Expand Down Expand Up @@ -58,13 +58,13 @@ func getRegistryAuth(reg string, cfgs map[string]registry.AuthConfig) (registry.
func defaultRegistry(ctx context.Context) string {
client, err := NewDockerClientWithOpts(ctx)
if err != nil {
return testcontainersdocker.IndexDockerIO
return core.IndexDockerIO
}
defer client.Close()

info, err := client.Info(ctx)
if err != nil {
return testcontainersdocker.IndexDockerIO
return core.IndexDockerIO
}

return info.IndexServerAddress
Expand Down

0 comments on commit 2b08a80

Please sign in to comment.