diff --git a/.github/workflows/ci-rootless-podman.yml b/.github/workflows/ci-rootless-podman.yml new file mode 100644 index 00000000000..4d270cbfbc7 --- /dev/null +++ b/.github/workflows/ci-rootless-podman.yml @@ -0,0 +1,34 @@ +name: CI-Podman-Rootless + +on: + pull_request: {} + push: { branches: [main] } + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Uninstall unwanted packages + # docker would be preferred over podman + run: sudo apt-get -q -y --purge remove podman moby-engine moby-buildx && sudo rm -rf /var/run/docker.sock + - name: Configure podman + run: | + mkdir -p $HOME/.config/containers + echo '[[registry]]' > $HOME/.config/containers/registries.conf + echo 'prefix = "localhost"' >> $HOME/.config/containers/registries.conf + echo 'location = "localhost"' >> $HOME/.config/containers/registries.conf + echo 'insecure = true' >> $HOME/.config/containers/registries.conf + - name: Install latest podman release + # see https://podman.io/getting-started/installation#ubuntu + run: | + curl -fsSL https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/Release.key | gpg --dearmor | sudo tee /etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg > /dev/null + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg] https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_$(lsb_release -rs)/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list > /dev/null + sudo apt-get update -qq + sudo apt-get -qq -y install podman + systemctl --user enable --now podman.socket + - name: Build with Gradle + run: ./gradlew --no-daemon --scan -Dtest.profile=podman testcontainers:test diff --git a/build.gradle b/build.gradle index 0acf7a82cc2..afc436f8594 100644 --- a/build.gradle +++ b/build.gradle @@ -94,6 +94,15 @@ subprojects { failOnPassedAfterRetry = false } } + + if (System.properties['test.profile'] == 'podman') { + // The compose container relies on the deprecated link feature, which is not supported by podman + exclude '**/*Compose*' + filter { + // cgroups v2 don't support settings swappiness + excludeTestsMatching '*shouldReportOOMAfterWait' + } + } } tasks.withType(Test).all { diff --git a/core/src/main/java/org/testcontainers/dockerclient/RootlessPodmanClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/RootlessPodmanClientProviderStrategy.java new file mode 100644 index 00000000000..1473adc5234 --- /dev/null +++ b/core/src/main/java/org/testcontainers/dockerclient/RootlessPodmanClientProviderStrategy.java @@ -0,0 +1,95 @@ +package org.testcontainers.dockerclient; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +/** + * + * @deprecated this class is used by the SPI and should not be used directly + */ +@Deprecated +@Slf4j +public final class RootlessPodmanClientProviderStrategy extends DockerClientProviderStrategy { + + public static final int PRIORITY = UnixSocketClientProviderStrategy.PRIORITY - 1; + + @Getter(lazy = true) + @Nullable + private final Path socketPath = resolveSocketPath(); + + private Path resolveSocketPath() { + return tryEnv() + .orElseGet(() -> { + Path implicitPath = Paths.get("/run/user/" + LibC.INSTANCE.getuid()); + return tryFolder(implicitPath).orElse(null); + }); + } + + private Optional tryEnv() { + String xdgRuntimeDir = System.getenv("XDG_RUNTIME_DIR"); + if (StringUtils.isBlank(xdgRuntimeDir)) { + log.debug("$XDG_RUNTIME_DIR is not set."); + return Optional.empty(); + } + Path path = Paths.get(xdgRuntimeDir); + if (!Files.exists(path)) { + log.debug("$XDG_RUNTIME_DIR is set to '{}' but the folder does not exist.", path); + return Optional.empty(); + } + Path podmanSocketPath = path.resolve("podman/podman.sock"); + if (!Files.exists(podmanSocketPath)) { + log.debug("$XDG_RUNTIME_DIR is set but '{}' does not exist.", podmanSocketPath); + return Optional.empty(); + } + return Optional.of(podmanSocketPath); + } + + private Optional tryFolder(Path path) { + if (!Files.exists(path)) { + log.debug("'{}' does not exist.", path); + return Optional.empty(); + } + Path podmanSocketPath = path.resolve("podman/podman.sock"); + if (!Files.exists(podmanSocketPath)) { + log.debug("'{}' does not exist.", podmanSocketPath); + return Optional.empty(); + } + return Optional.of(podmanSocketPath); + } + + @Override + public TransportConfig getTransportConfig() throws InvalidConfigurationException { + return TransportConfig.builder().dockerHost(URI.create("unix://" + getSocketPath().toString())).build(); + } + + @Override + protected boolean isApplicable() { + return SystemUtils.IS_OS_LINUX && getSocketPath() != null && Files.exists(getSocketPath()); + } + + @Override + public String getDescription() { + return "Rootless Podman accessed via Unix socket (" + getSocketPath() + ")"; + } + + @Override + protected int getPriority() { + return PRIORITY; + } + + private interface LibC extends Library { + LibC INSTANCE = Native.loadLibrary("c", LibC.class); + int getuid(); + } +} diff --git a/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy b/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy index cae8bf53041..cafb957db55 100644 --- a/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy +++ b/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy @@ -5,3 +5,4 @@ org.testcontainers.dockerclient.DockerMachineClientProviderStrategy org.testcontainers.dockerclient.NpipeSocketClientProviderStrategy org.testcontainers.dockerclient.RootlessDockerClientProviderStrategy org.testcontainers.dockerclient.DockerDesktopClientProviderStrategy +org.testcontainers.dockerclient.RootlessPodmanClientProviderStrategy