Skip to content

Commit

Permalink
feat: Auto detect the use of Podman from DOCKER_HOST (#982)
Browse files Browse the repository at this point in the history
* feat: Autodetect the use of Podman if no provider is set, and DOCKER_HOST contains podman.sock

Updated documentation
Unit tests for auto-detection of ProviderPodman

Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com>

* Cleanup linter issues

* Use `t.Setenv()` in tests

* Fix the spelling of reap

---------

Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com>
Co-authored-by: Manuel de la Peña <mdelapenya@gmail.com>
  • Loading branch information
3 people committed Mar 30, 2023
1 parent d95da8e commit b1edcd4
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 10 deletions.
56 changes: 49 additions & 7 deletions docs/system_requirements/using_podman.md
@@ -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 reap containers, so the podman socket service must be started:
```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
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
@@ -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)
}
})
}
}

0 comments on commit b1edcd4

Please sign in to comment.