gha/test: Add userns integration mode

User namespace remapping currently has no full integration-suite CI
coverage. Add a graphdriver mode that starts dockerd with
DOCKER_REMAP_ROOT=default.

Skip tests that require privileged or host namespaces, and avoid the
host network optimization in TestUpdatePidsLimit, because dockerd
rejects those combinations when remapping is enabled.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski
2026-07-15 18:19:05 +02:00
parent 84dd0e86af
commit a446d73c33
9 changed files with 19 additions and 2 deletions

View File

@@ -216,6 +216,9 @@ jobs:
if [[ "$MODE" == *"rootless"* ]]; then
echo "DOCKER_ROOTLESS=1" >> $GITHUB_ENV
fi
if [[ "$MODE" == *"userns"* ]]; then
echo "DOCKER_REMAP_ROOT=default" >> $GITHUB_ENV
fi
if [[ "$MODE" == *"systemd"* ]]; then
echo "SYSTEMD=true" >> $GITHUB_ENV
CACHE_DEV_SCOPE="dev-systemd-${ARCH}"

View File

@@ -96,7 +96,7 @@ jobs:
- arch: amd64
storage: graphdriver
runnerSuffix: ""
extraModes: '["rootless", "systemd"]'
extraModes: '["rootless", "systemd", "userns"]'
- arch: arm64
storage: snapshotter
runnerSuffix: "-arm"

View File

@@ -13,6 +13,7 @@ import (
func TestLinksEtcHostsContentMatch(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.IsUserNamespace, "host network mode is incompatible with user namespaces")
hosts, err := os.ReadFile("/etc/hosts")
skip.If(t, os.IsNotExist(err))

View File

@@ -350,6 +350,7 @@ func TestMountDaemonRoot(t *testing.T) {
func TestContainerBindMountNonRecursive(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.IsRootless, "cannot be tested because RootlessKit executes the daemon in private mount namespace (https://github.com/rootless-containers/rootlesskit/issues/97)")
skip.If(t, testEnv.IsUserNamespace, "non-recursive bind mounts of directories containing submounts fail with EINVAL in a user namespace")
ctx := setupTest(t)

View File

@@ -60,6 +60,7 @@ func TestCgroupNamespacesRunPrivileged(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, !requirement.CgroupNamespacesEnabled())
skip.If(t, testEnv.DaemonInfo.CgroupVersion == "2", "on cgroup v2, privileged containers use private cgroupns")
skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces")
t.Parallel()
@@ -120,6 +121,7 @@ func TestCgroupNamespacesRunPrivilegedAndPrivate(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, !requirement.CgroupNamespacesEnabled())
skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces")
t.Parallel()

View File

@@ -33,6 +33,7 @@ import (
func TestNISDomainname(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsUserNamespace, "user namespaces cannot write the kernel domainname sysctl")
// Rootless supports custom Hostname but doesn't support custom Domainname
// OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \
@@ -136,6 +137,7 @@ func TestPrivilegedHostDevices(t *testing.T) {
// so needs to be same host.
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
@@ -375,6 +377,7 @@ func TestWorkingDirNormalization(t *testing.T) {
func TestSeccomp(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsUserNamespace, "privileged test cases are incompatible with user namespaces")
ctx := setupTest(t)
apiClient := testEnv.APIClient()

View File

@@ -186,8 +186,12 @@ func TestUpdatePidsLimit(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
opts := []func(*container.TestContainerConfig){container.WithPidsLimit(test.initial)}
// Using "network=host" to speed up creation (13.96s vs 6.54s)
cID := container.Run(ctx, t, apiClient, container.WithPidsLimit(test.initial), container.WithNetworkMode("host"))
if !testEnv.IsUserNamespace() {
opts = append(opts, container.WithNetworkMode("host"))
}
cID := container.Run(ctx, t, apiClient, opts...)
_, err := c.ContainerUpdate(ctx, cID, client.ContainerUpdateOptions{
Resources: &containertypes.Resources{

View File

@@ -1231,6 +1231,8 @@ func TestBridgeIPAMStatus(t *testing.T) {
// rolled back properly - the failed connection should not show up in container
// or network inspect, and the container should not gain a network interface.
func TestJoinError(t *testing.T) {
skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces")
ctx := setupTest(t)
d := daemon.New(t)
d.StartWithBusybox(ctx, t)

View File

@@ -226,6 +226,7 @@ func TestDaemonWithBipAndDefaultNetworkPool(t *testing.T) {
func TestServiceWithPredefinedNetwork(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode")
skip.If(t, testEnv.IsUserNamespace, "host network mode is incompatible with user namespaces")
ctx := setupTest(t)
d := swarm.NewSwarm(ctx, t, testEnv)