diff --git a/docs/system_requirements/using_podman.md b/docs/system_requirements/using_podman.md index b4d2d36ecc..fae9731a65 100644 --- a/docs/system_requirements/using_podman.md +++ b/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 @@ -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. \ No newline at end of file +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...** + diff --git a/provider.go b/provider.go index c16d449e50..8ee80c11b1 100644 --- a/provider.go +++ b/provider.go @@ -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 ) @@ -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 { diff --git a/provider_test.go b/provider_test.go new file mode 100644 index 0000000000..6700a40da3 --- /dev/null +++ b/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) + } + }) + } +}