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: Auto detect the use of Podman from DOCKER_HOST #982

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions docs/system_requirements/using_podman.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# Using Podman instead of Docker

_Testcontainers for Go_ supports the use of Podman (rootless or rootful) instead of Docker.
In most scenarios no special setup is required.

In most scenarios no special setup is required in _Testcontainers for Go_.
_Testcontainers for Go_ will automatically discover the socket based on the `DOCKER_HOST` or the `TC_HOST` environment variables.
Alternatively you can configure the host with a `.testcontainers.properties` file.
The discovered Docker host is also taken into account when starting a reaper container.

There's currently only one special case where additional configuration is necessary: complex container network scenarios.
The discovered Docker host is taken into account when starting a reaper container.
The discovered socket is used to detect the use of Podman.

By default _Testcontainers for Go_ takes advantage of the default network settings both Docker and Podman are applying to newly created containers.
It only intervenes in scenarios where a `ContainerRequest` specifies networks and does not include the default network of the current container provider.
Unfortunately the default network for Docker is called _bridge_ where the default network in Podman is called _podman_.
It is not even possible to create a network called _bridge_ with Podman as Podman does not allow creating a network with the same name as an already existing network mode.

In such scenarios it is possible to explicitly make use of the `ProviderPodman` like so:
In complex container network scenarios it may be required to explicitly make use of the `ProviderPodman` like so:

```go

Expand All @@ -36,4 +35,47 @@ func TestSomething(t *testing.T) {
}
```

The `ProviderPodman` configures the `DockerProvider` with the correct default network for Podman to ensure also complex network scenarios are working as with Docker.
The `ProviderPodman` configures the `DockerProvider` with the correct default network for Podman to ensure complex network scenarios are working as with Docker.

## Podman socket activation

The reaper container needs to connect to the docker daemon to reape containers, so the podman socket service must be started:
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
```shell
> systemctl --user start podman.socket
```

## Fedora

`DOCKER_HOST` environment variable must be set

```
> export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock
```

SELinux may require a custom policy be applied to allow the reaper container to connect to and write to a socket. Once you experience the se-linux error, you can run the following commands to create and install a custom policy.

```
> sudo ausearch -c 'app' --raw | audit2allow -M my-podman
> sudo semodule -i my-podman.pp
```

The resulting my-podman.te file should look something like this:
```
module my-podman2 1.0;

require {
type user_tmp_t;
type container_runtime_t;
type container_t;
class sock_file write;
class unix_stream_socket connectto;
}

#============= container_t ==============
allow container_t container_runtime_t:unix_stream_socket connectto;
allow container_t user_tmp_t:sock_file write;

```

**NOTE: It will take two rounds of installing a policy, then experiencing the next se-linux issue, install new policy, etc...**

14 changes: 11 additions & 3 deletions provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"context"
"errors"
"fmt"
"os"
"strings"
)

// possible provider types
const (
ProviderDocker ProviderType = iota // Docker is default = 0
ProviderDefault ProviderType = iota // default will auto-detect provider from DOCKER_HOST environment variable
ProviderDocker
ProviderPodman
)

Expand Down Expand Up @@ -97,8 +100,13 @@ func (t ProviderType) GetProvider(opts ...GenericProviderOption) (GenericProvide
o.ApplyGenericTo(opt)
}

switch t {
case ProviderDocker:
pt := t
if pt == ProviderDefault && strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") {
pt = ProviderPodman
}

switch pt {
case ProviderDefault, ProviderDocker:
providerOptions := append(Generic2DockerOptions(opts...), WithDefaultBridgeNetwork(Bridge))
provider, err := NewDockerProvider(providerOptions...)
if err != nil {
Expand Down
74 changes: 74 additions & 0 deletions provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package testcontainers

import (
"testing"
)

func TestProviderTypeGetProviderAutodetect(t *testing.T) {
const dockerSocket = "unix:///var/run/docker.sock"
const podmanSocket = "unix://$XDG_RUNTIME_DIR/podman/podman.sock"

tests := []struct {
name string
tr ProviderType
DockerHost string
want string
wantErr bool
}{
{
name: "default provider without podman.socket",
tr: ProviderDefault,
DockerHost: dockerSocket,
want: Bridge,
},
{
name: "default provider with podman.socket",
tr: ProviderDefault,
DockerHost: podmanSocket,
want: Podman,
},
{
name: "docker provider without podman.socket",
tr: ProviderDocker,
DockerHost: dockerSocket,
want: Bridge,
},
{
// Explicitly setting Docker provider should not be overridden by auto-detect
name: "docker provider with podman.socket",
tr: ProviderDocker,
DockerHost: podmanSocket,
want: Bridge,
},
{
name: "Podman provider without podman.socket",
tr: ProviderPodman,
DockerHost: dockerSocket,
want: Podman,
},
{
name: "Podman provider with podman.socket",
tr: ProviderPodman,
DockerHost: podmanSocket,
want: Podman,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("DOCKER_HOST", tt.DockerHost)

got, err := tt.tr.GetProvider()
if (err != nil) != tt.wantErr {
t.Errorf("ProviderType.GetProvider() error = %v, wantErr %v", err, tt.wantErr)
return
}
provider, ok := got.(*DockerProvider)
if !ok {
t.Fatalf("ProviderType.GetProvider() = %T, want %T", got, &DockerProvider{})
}
if provider.defaultBridgeNetworkName != tt.want {
t.Errorf("ProviderType.GetProvider() = %v, want %v", provider.defaultBridgeNetworkName, tt.want)
}
})
}
}