Skip to content

Commit

Permalink
feat: setup docker_host discovery strategies properly (#1161)
Browse files Browse the repository at this point in the history
* chore: add helper function to check if a file exists

* chore: wrap the extraction of the current docker host into a reusable function

* chore: extract part of the algorithm to calculate the docker socket

* chore: use constant for default docker socket in tests

* chore: extract docker socket from Go context to a function

* chore: reuse constant for default docker socket path

* chore: detect DOCKER_HOST first

* chore: do not fallback to default, return empty string

* chore: apply extract docker host function

* chore: read properties with context

* docs: document the docker host detection strategies

* feat: look up rootless docker if docker host was not found

* chore: extract modules generator pipeline

* chore: add a pipeline for rootless docker

* chore: remove duplicated pipeline

* chore: define docekr socket path for unix/windows

* chore: convert internal constants into vars for testability

* chore: use t.Cleanup to restore test state

* feat: support for reading the default docker host from unix/windows

* chore: move config to internal package

* chore: unify how the docker client is retrieved

* chore: delegate singleton instance to the internal config

* chore: remove DOCKER_HOST env var from config

* chore: support resetting the config singleton for testing

* feat: read docker host from properties

* fix: typo

* fix: remove white lines in imports

* fix: handle error in tests

* chore: apply #1160 after merge

* chore: do not go through the provider to get a client

* fix: honour passed Docker client opts

* fix: read properties does not consider reading DOCKER_HOST env var

* fix: defer client close after docker ping

* feat: extract the docker host when bootstrapping a docker client

* chore: honour passed opts for the client

* chore: cache docker_host to avoid unnecesary calculations

* chor: convert socket schema into constants

* chore: support parsing URLs for remote hosts

* fix: reaper mounts a socket path without schema

* fix: proper comment structure for go linting using golangci-lint

* chore: get socket mount without schema

* fix: support for disabling rootless for testing

* fix: get provider host from the strategies

* chore: proper separation of concerns between docker socket and docker host

* docs: improve docs for Docker host detection

* fix: comments indentation

* fix: proper list format in Go comments

* docs: document rootless socket path alternatives in code

* feat: support for testcontainers host strategy

* docs: include new strategy in go comment

* fix: use tc.host

* fix: proper initialisation of baseRunDir

* fix: unset DOCKER_HOST for tests that does not need it

* chore: do not print config

* fix: include schema in the rootless docker socket path

* fix: add schema properly

* fix: always prepend the schema

$XDG_RUNTIME_DIR does not contain it

* fix: proper return when sanitising the docker socket path

* chore: proper use of docker socket path

* chore: defensively restore env var in tests

* fix: make sure the already built image is removed from the local cache

* fix: use testcontainers docker client when removing image

* fix: update docs

* chore: rename variable

* fix: add back module generator tests

* chore: do not break users for the deprecated fields in the tc config

* fix: tc.host takes precedence on TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE for the docker socket
  • Loading branch information
mdelapenya committed May 26, 2023
1 parent 2afa0f2 commit 08a56c9
Show file tree
Hide file tree
Showing 24 changed files with 1,745 additions and 620 deletions.
72 changes: 72 additions & 0 deletions .github/workflows/ci-rootless-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Rootless Docker pipeline

on:
push:
paths-ignore:
- 'mkdocs.yml'
- 'docs/**'
- 'README.md'
pull_request:
paths-ignore:
- 'mkdocs.yml'
- 'docs/**'
- 'README.md'

concurrency:
group: "${{ github.workflow }}-${{ github.head_ref || github.sha }}"
cancel-in-progress: true

jobs:
test-rootless-docker:
strategy:
matrix:
go-version: [1.19.x, 1.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
env:
TESTCONTAINERS_RYUK_DISABLED: "false"
steps:

- name: Setup rootless Docker
uses: ScribeMD/rootless-docker@0.2.2

- name: Remove Docket root socket
run: sudo rm -rf /var/run/docker.sock

- 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: modVerify
run: go mod verify

- name: modTidy
run: make tools-tidy

- name: ensure compilation
env:
GOOS: linux
run: go build

- 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?
if: ${{ matrix.platform == 'ubuntu-latest' }}
run: make test-unit

- name: Run checker
run: |
./scripts/check_environment.sh
- name: Test Summary
uses: test-summary/action@4ee9ece4bca777a38f05c8fc578ac2007fe266f7
with:
paths: "**/TEST-*.xml"
if: always()
105 changes: 21 additions & 84 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,101 +1,38 @@
package testcontainers

import (
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"context"

"github.com/magiconair/properties"
"github.com/testcontainers/testcontainers-go/internal/config"
)

var tcConfig TestcontainersConfig
var tcConfigOnce *sync.Once = new(sync.Once)

// TestcontainersConfig represents the configuration for Testcontainers
// testcontainersConfig {
type TestcontainersConfig struct {
Host string `properties:"docker.host,default="`
TLSVerify int `properties:"docker.tls.verify,default=0"`
CertPath string `properties:"docker.cert.path,default="`
RyukDisabled bool `properties:"ryuk.disabled,default=false"`
RyukPrivileged bool `properties:"ryuk.container.privileged,default=false"`
Host string `properties:"docker.host,default="` // Deprecated: use Config.Host instead
TLSVerify int `properties:"docker.tls.verify,default=0"` // Deprecated: use Config.TLSVerify instead
CertPath string `properties:"docker.cert.path,default="` // Deprecated: use Config.CertPath instead
RyukDisabled bool `properties:"ryuk.disabled,default=false"` // Deprecated: use Config.RyukDisabled instead
RyukPrivileged bool `properties:"ryuk.container.privileged,default=false"` // Deprecated: use Config.RyukPrivileged instead
Config config.Config
}

// }

// ReadConfig reads from testcontainers properties file, storing the result in a singleton instance
// of the TestcontainersConfig struct
// Deprecated use ReadConfigWithContext instead
func ReadConfig() TestcontainersConfig {
tcConfigOnce.Do(func() {
tcConfig = readConfig()

if tcConfig.RyukDisabled {
ryukDisabledMessage := `
**********************************************************************************************
Ryuk has been disabled for the current execution. This can cause unexpected behavior in your environment.
More on this: https://golang.testcontainers.org/features/garbage_collector/
**********************************************************************************************`
Logger.Printf(ryukDisabledMessage)
Logger.Printf("\n%+v", tcConfig)
}
})

return tcConfig
}

// readConfig reads from testcontainers properties file, if it exists
// it is possible that certain values get overridden when set as environment variables
func readConfig() TestcontainersConfig {
config := TestcontainersConfig{}

applyEnvironmentConfiguration := func(config TestcontainersConfig) TestcontainersConfig {
if dockerHostEnv := os.Getenv("DOCKER_HOST"); dockerHostEnv != "" {
config.Host = dockerHostEnv
}
if config.Host == "" {
config.Host = "unix:///var/run/docker.sock"
}

ryukDisabledEnv := os.Getenv("TESTCONTAINERS_RYUK_DISABLED")
if parseBool(ryukDisabledEnv) {
config.RyukDisabled = ryukDisabledEnv == "true"
}

ryukPrivilegedEnv := os.Getenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED")
if parseBool(ryukPrivilegedEnv) {
config.RyukPrivileged = ryukPrivilegedEnv == "true"
}

return config
}

home, err := os.UserHomeDir()
if err != nil {
return applyEnvironmentConfiguration(config)
}

tcProp := filepath.Join(home, ".testcontainers.properties")
// init from a file
properties, err := properties.LoadFile(tcProp, properties.UTF8)
if err != nil {
return applyEnvironmentConfiguration(config)
}

if err := properties.Decode(&config); err != nil {
fmt.Printf("invalid testcontainers properties file, returning an empty Testcontainers configuration: %v\n", err)
return applyEnvironmentConfiguration(config)
}

fmt.Printf("Testcontainers properties file has been found: %s\n", tcProp)

return applyEnvironmentConfiguration(config)
return ReadConfigWithContext(context.Background())
}

func parseBool(input string) bool {
if _, err := strconv.ParseBool(input); err == nil {
return true
// ReadConfigWithContext reads from testcontainers properties file, storing the result in a singleton instance
// of the TestcontainersConfig struct
func ReadConfigWithContext(ctx context.Context) TestcontainersConfig {
cfg := config.Read(ctx)
return TestcontainersConfig{
Host: cfg.Host,
TLSVerify: cfg.TLSVerify,
CertPath: cfg.CertPath,
RyukDisabled: cfg.RyukDisabled,
RyukPrivileged: cfg.RyukPrivileged,
Config: cfg,
}
return false
}

0 comments on commit 08a56c9

Please sign in to comment.