Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: setup docker_host discovery strategies properly #1161

Merged
merged 70 commits into from May 26, 2023

Conversation

mdelapenya
Copy link
Collaborator

@mdelapenya mdelapenya commented May 9, 2023

What does this PR do?

This PR does a lot of internal refactoring to accomplish a proper bootstrapping of the Docker client provided by the library.

Docker host and Docker socket path extraction

We have defined an algorithm to locate the Docker host in a well-defined manner, using an array of Go functions that will be executed in a particular order. More concretely, for discovering the Docker host, we are adding functions that extracts the Docker host from the following locations:

  1. Docker host from the "tc.host" property in the ~/.testcontainers.properties file.
  2. DOCKER_HOST environment variable.
  3. Docker host from Go context (internally used for the creating the reaper instance)
  4. Docker host from the default docker socket path, without the unix schema.
  5. Docker host from the "docker.host" property in the ~/.testcontainers.properties file.
  6. Rootless docker socket path.
  7. Else, the default Docker socket including schema will be returned.

The algorithm will execute each of the above functions in that particular order, returning the Docker host location if and only if the function returns a non-empty string and a nil error. In the case a function returns an error, it's wrapped into an outer error, so that it will print all the locations that were checked in the case it's needed.

The first function, the one with most precedence, checks for the tc.host key in the testcontainers properties file, which is located at $HOME/.testcontainers.properties.

The last function checks for rootless-docker. This PR adds support for rootless Docker, checking for the Docker host in the following locations:

  1. ${XDG_RUNTIME_DIR}/.docker/run/docker.sock.
  2. ${HOME}/.docker/run/docker.sock.
  3. ${HOME}/.docker/desktop/docker.sock.
  4. /run/user/${UID}/docker.sock, where ${UID} is the user ID of the current user.

The code will use the same pattern as above: defining functions for each location that will be executed in the aforementioned order, wrapping an outer error if needed. When returned, it will include the unix:// schema as prefix of the Docker host.

At the same time, this PR is adding a way to detect the path of the Docker socket, which will basically use the above mechanism but including certain special checks:

  1. Read the TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable. Path to Docker's socket. Used by Ryuk, Docker Compose, and a few other containers that need to perform Docker actions. e.g: /var/run/docker-alt.sock
  2. If the Operative System retrieved by the Docker client is "Docker Desktop", return the default docker socket path for rootless docker.
  3. Get the current Docker Host from the existing strategies: see Docker host detection.
  4. If the socket contains the unix schema, the schema is removed (e.g. unix:///var/run/docker.sock -> /var/run/docker.sock)
  5. Else, the default location of the docker socket is used: /var/run/docker.sock

When returning the Docker socket path, it will remove the unix:// schema from the path. It's important to mention that at this moment we are not supporting Windows containers at the moment, so no npipe schema is added (although we have it in mind).

When extracting both the Docker host and the Docker socket path, it happens once: we have defined a sync.Once for each location so that the discovery is not happening for every call, but instead cached during the entire execution of the process.

For both scenarios, we have included a deep battery of unit tests, simulating the conditions each location needs: e.g. setting the env vars for HOME, XDG_RUNTIME_DIR to temporary dirs and creating the socket files in there.

We have also added docs to the website regarding this new discovery mechanism.

Docker rootless

In order to provided a seamless experience when running Testcontainers for Go with Docker rootless, and leveraging the above mechanism to discover the host and the socket, we have added a GH action that configures Docker in rootless-mode for the GH worker. There, we are running the tests to verify the library behaves as expected when it's run in rootless. This pipeline will run for each PR and merge commit.

Side works

Reading TestcontainersConfig

We have pushed certain core functionality to the internal packages so that it's not possible to use it from outside the library. A clear example is the initialisation of the Testcontainers config struct.

This has been needed as part of the refactors to bootstrap the Docker client, which needs a Docker host, which would eventually check the Testcontainers properties file. As a consequence, the public API for reading the config is still present, although will call the internal package to get it. Besides, the TestcontainersConfig deprecates all its attributes and defines a new one as an instance of the internal representation of the Config.

In the past, reading the properties also calculated the Docker host, now the responsibilities are properly decoupled and it only reads the properties file. The DOCKER_HOST variable is read with the aforementioned discovery mechanism, using this properties as an example.

DockerProvider struct and the Docker host

The struct holds a reference to the host, and it was calculated reading from the properties file (which read from DOCKER_HOST). Now with the consistent discovery mechanism, the provider extracts the Docker host and directly uses it.

Getting the default Docker registry from the Docker client

Now that we have an unified manner of getting the Docker client, the code to extract the default Docker registry does not need to instantiate a Provider struct, but instead simply get a Testcontainers Docker client.

Reaper mounts

Another consequence of having a proper calculation of the Docker socket path is when mounting the Docker socket into certain containers. Ryuk, our resource reaper, needs the Docker socket to talk to the running containers. We are extracting the Docker socket path instead of hardcoding it to /var/run/docker.sock

Integrated tests detection

While debugging rootless, we have detected a few integrated tests, or tests that were affected by the state produced by other tests that run before. For that reason, we have updated a few tests in order to make it work. As an example, the most illustrative case was a test for checking the Docker Auths with a private registry: testA was using the right auths to connect to the private registry and pull an image, testB should do the same but using wrong auth credentials therefore raising an error. For some reason, in rootfull-mode testB raised an error; but in rootless, testB detected that the image was already cached locally and used it, not raising the expected error. 🤷 We updated the test to always delete the image from local after testA and testB.

Why is it important?

We want to provide a consistent experience when using the library and interacting with the Docker client, for that reason we are unifying how it's bootstrapped and how it discovers the Docker host and the Docker socket from the different alternatives described above.

Related issues

How to test this PR

Well, the CI is defining the scenarios to test this PR, using both rootless and rootfull.

@mdelapenya mdelapenya added the feature New functionality or new behaviors on the existing one label May 9, 2023
@mdelapenya mdelapenya self-assigned this May 9, 2023
@mdelapenya mdelapenya marked this pull request as ready for review May 26, 2023 07:11
@mdelapenya mdelapenya requested a review from a team as a code owner May 26, 2023 07:11
* main: (22 commits)
  docs: document the Go version (testcontainers#1246)
  chore(deps): bump github.com/aws/aws-sdk-go-v2/config (testcontainers#1222)
  chore(deps): bump golang.org/x/sys from 0.7.0 to 0.8.0 (testcontainers#1202)
  chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (testcontainers#1232)
  chore(deps): bump cloud.google.com/go/spanner in /examples/spanner (testcontainers#1226)
  chore: Removes the refercence about docker 22.06 from the docker-compose docs and updates the replace directive for the compose module. (testcontainers#1243)
  chore(deps): bump go.mongodb.org/mongo-driver in /examples/mongodb (testcontainers#1233)
  chore(deps): bump google.golang.org/api from 0.123.0 to 0.124.0 in /examples (testcontainers#1244)
  chore(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 (testcontainers#1241)
  chore(deps): bump github.com/aws/aws-sdk-go in /modules/localstack (testcontainers#1242)
  added NATS JetStream example (testcontainers#1190)
  chore(deps): bump github.com/imdario/mergo from 0.3.12 to 0.3.15 (testcontainers#1204)
  chore(deps): bump cloud.google.com/go/firestore in /examples/firestore (testcontainers#1216)
  chore(deps): bump k8s.io/client-go from 0.22.5 to 0.27.2 in /modules/k3s (testcontainers#1211)
  chore(deps): bump go.mongodb.org/mongo-driver in /examples/mongodb (testcontainers#1092)
  chore(deps): bump github.com/neo4j/neo4j-go-driver/v5 in /modules/neo4j (testcontainers#1206)
  chore(deps): bump github.com/twmb/franz-go in /modules/redpanda (testcontainers#1201)
  chore(deps): bump google.golang.org/api from 0.121.0 to 0.123.0 in /examples (testcontainers#1229)
  chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (testcontainers#1228)
  ...
@sonarcloud
Copy link

sonarcloud bot commented May 26, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 2 Code Smells

No Coverage information No Coverage information
0.5% 0.5% Duplication

@mdelapenya mdelapenya merged commit 08a56c9 into testcontainers:main May 26, 2023
62 checks passed
@mdelapenya mdelapenya deleted the rootless-docker branch May 29, 2023 06:34
mdelapenya added a commit to mdelapenya/testcontainers-go that referenced this pull request Jun 15, 2023
It's not needed: we added it in an intermediate state of testcontainers#1161
mdelapenya added a commit that referenced this pull request Jun 15, 2023
* chore: simplify ReadConfig signature, not passing context

It's not needed: we added it in an intermediate state of #1161

* fix: more usages
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New functionality or new behaviors on the existing one
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants