This commit is contained in:
joncrall
2022-06-26 15:53:44 -04:00
parent 65d70682af
commit 90945a38d2
2 changed files with 115 additions and 95 deletions
+19 -5
View File
@@ -80,7 +80,6 @@ class DockerContainer:
network_args = ["--network=host"] network_args = ["--network=host"]
shell_args = ["linux32", "/bin/bash"] if self.simulate_32_bit else ["/bin/bash"] shell_args = ["linux32", "/bin/bash"] if self.simulate_32_bit else ["/bin/bash"]
subprocess.run( subprocess.run(
[ [
self.container_engine, self.container_engine,
@@ -89,8 +88,10 @@ class DockerContainer:
f"--name={self.name}", f"--name={self.name}",
"--interactive", "--interactive",
*network_args, *network_args,
# Do we need the hostmout?
# Z-flags is for SELinux # Z-flags is for SELinux
"--volume=/:/host:Z", # ignored on CircleCI # "--volume=/:/host:Z", # ignored on CircleCI
# "--volume=/:/host",
self.docker_image, self.docker_image,
*shell_args, *shell_args,
], ],
@@ -116,13 +117,13 @@ class DockerContainer:
self.bash_stdout = self.process.stdout self.bash_stdout = self.process.stdout
# run a noop command to block until the container is responding # run a noop command to block until the container is responding
self.call(["/bin/true"], cwd="") self.call(["/bin/true"], cwd="/")
if self.cwd: if self.cwd:
# Although `docker create -w` does create the working dir if it # Although `docker create -w` does create the working dir if it
# does not exist, podman does not. There does not seem to be a way # does not exist, podman does not. There does not seem to be a way
# to setup a workdir for a container running in podman. # to setup a workdir for a container running in podman.
self.call(["mkdir", "-p", str(self.cwd)], cwd="") self.call(["mkdir", "-p", str(self.cwd)], cwd="/")
return self return self
@@ -200,7 +201,20 @@ class DockerContainer:
# note: we assume from_path is a dir # note: we assume from_path is a dir
to_path.mkdir(parents=True, exist_ok=True) to_path.mkdir(parents=True, exist_ok=True)
if self.container_engine == "podman": TRY_SIMPLE_CP = 0
if TRY_SIMPLE_CP:
# There is a bug in docker that prevents this simple implementation
# from working https://github.com/moby/moby/issues/38995
# It seems to also not workin podman as well
command = f"{self.container_engine} cp {self.name}:{shell_quote(from_path)} {shell_quote(to_path)}"
subprocess.run(
command,
shell=True,
check=True,
cwd=to_path,
env=self.env,
)
elif self.container_engine == "podman":
# The copy out logic that works for docker does not seem to # The copy out logic that works for docker does not seem to
# translate to podman, which seems to need the steps spelled out # translate to podman, which seems to need the steps spelled out
# more explicitly. # more explicitly.
+96 -90
View File
@@ -28,11 +28,14 @@ elif pm == "s390x":
else: else:
DEFAULT_IMAGE = "" DEFAULT_IMAGE = ""
# A dictionary to make it easier to manipulate globals
temp_test_dir = None _STATE = {
'temp_test_dir': None,
'using_podman': False,
}
@atexit.register # @atexit.register
def _cleanup_tempdir(): def _cleanup_tempdir():
""" """
Cleans up any configuration written by :func:`basis_container_kwargs`. Cleans up any configuration written by :func:`basis_container_kwargs`.
@@ -42,37 +45,24 @@ def _cleanup_tempdir():
It may be possible to handle this more cleanly in pytest itself, but using It may be possible to handle this more cleanly in pytest itself, but using
atexit works well enough for now. atexit works well enough for now.
The reason why permission errors occur on podman is documented in
[PodmanStoragePerms]_.
References:
.. [PodmanStoragePerms] https://podman.io/blogs/2018/10/03/podman-remove-content-homedir.html
""" """
import stat temp_test_dir = _STATE['temp_test_dir']
global temp_test_dir
if temp_test_dir is not None: if temp_test_dir is not None:
print(f"CLEANUP temp_test_dir = {temp_test_dir!r}") # type: ignore[unreachable] # When podman creates special directories, they cant be cleaned up
for r, ds, fs in os.walk(temp_test_dir.name): # unless you fake a UID of 0. The package rootlesskit helps with that.
for d in ds: if _STATE['using_podman']:
dpath = os.path.join(r, d) subprocess.call(['podman', 'unshare', 'rm', '-rf', temp_test_dir.name])
if not os.path.islink(dpath):
perms = os.lstat(dpath).st_mode
try:
os.chmod(dpath, stat.S_IWUSR | perms)
except Exception as ex:
print(f"issue with dpath = {dpath!r}, {ex!r}")
for f in fs:
fpath = os.path.join(r, f)
if not os.path.islink(fpath):
perms = os.lstat(fpath).st_mode
try:
os.chmod(fpath, stat.S_IWUSR | perms)
except Exception as ex:
print(f"issue with fpath = {fpath!r}, {ex!r}")
else:
os.unlink(fpath)
try: try:
temp_test_dir.cleanup() temp_test_dir.cleanup()
except Exception as ex: except Exception as ex:
print(f"Issue cleaning up ex = {ex!r}") print(f"Issue cleaning up ex = {ex!r}")
temp_test_dir = None _STATE['temp_test_dir'] = None
def basis_container_kwargs(): def basis_container_kwargs():
@@ -80,18 +70,27 @@ def basis_container_kwargs():
Generate keyword args that can be passed to to :class:`DockerContainer`. Generate keyword args that can be passed to to :class:`DockerContainer`.
This is used with :func:`pytest.mark.parametrize` to run each test with This is used with :func:`pytest.mark.parametrize` to run each test with
different configuraions of each supported containers engine. different configurations of each supported containers engine.
For docker we test the default configuration. For docker we test the default configuration.
For podman we test the default configuration and a configuration with VFS For podman we test the default configuration and a configuration with VFS
(virtual file system) enabled as the storage driver. (virtual file system) enabled as the storage driver.
Yields:
Dict: a configuration passed as ``container_kwargs`` to each
parameterized test.
""" """
global temp_test_dir if _STATE['temp_test_dir'] is None:
if temp_test_dir is None:
# Only setup the temp directory once for all tests # Only setup the temp directory once for all tests
temp_test_dir = tempfile.TemporaryDirectory(prefix="cibw_test_") _STATE['temp_test_dir'] = tempfile.TemporaryDirectory(prefix="cibw_test_")
# Register the special cleanup hook after the temp directory is created
# to ensure that it runs before the temp directory logic runs (which
# will not handle cases where there is a fake root UID).
atexit.register(_cleanup_tempdir)
temp_test_dir = _STATE['temp_test_dir']
HAVE_DOCKER = bool(shutil.which("docker")) HAVE_DOCKER = bool(shutil.which("docker"))
HAVE_PODMAN = bool(shutil.which("podman")) HAVE_PODMAN = bool(shutil.which("podman"))
@@ -102,67 +101,11 @@ def basis_container_kwargs():
if HAVE_PODMAN: if HAVE_PODMAN:
# Basic podman usage # Basic podman usage
_STATE['using_podman'] = True
yield {"container_engine": "podman", "docker_image": DEFAULT_IMAGE} yield {"container_engine": "podman", "docker_image": DEFAULT_IMAGE}
# VFS Podman usage (for the podman in docker use-case) # VFS Podman usage (for the podman in docker use-case)
dpath = Path(temp_test_dir.name) oci_environ = _setup_podman_vfs(temp_test_dir.name)
# This requires that we write configuration files and point to them
# with environment variables before we run podman
# https://github.com/containers/common/blob/main/docs/containers.conf.5.md
vfs_containers_conf_data = {
"containers": {
"default_capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"NET_BIND_SERVICE",
"SETFCAP",
"SETGID",
"SETPCAP",
"SETUID",
"SYS_CHROOT",
]
},
"engine": {"cgroup_manager": "cgroupfs", "events_logger": "file"},
}
# https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md
storage_root = dpath / ".local/share/containers/vfs-storage"
run_root = dpath / ".local/share/containers/vfs-runroot"
storage_root.mkdir(parents=True, exist_ok=True)
run_root.mkdir(parents=True, exist_ok=True)
vfs_containers_storage_conf_data = {
"storage": {
"driver": "vfs",
"graphroot": str(storage_root),
"runroot": str(run_root),
"rootless_storage_path": str(storage_root),
"options": {
# "remap-user": "containers",
"aufs": {"mountopt": "rw"},
"overlay": {"mountopt": "rw", "force_mask": "shared"},
# "vfs": {"ignore_chown_errors": "true"},
},
}
}
vfs_containers_conf_fpath = dpath / "temp_vfs_containers.conf"
vfs_containers_storage_conf_fpath = dpath / "temp_vfs_containers_storage.conf"
with open(vfs_containers_conf_fpath, "w") as file:
toml.dump(vfs_containers_conf_data, file)
with open(vfs_containers_storage_conf_fpath, "w") as file:
toml.dump(vfs_containers_storage_conf_data, file)
oci_environ = os.environ.copy()
oci_environ.update(
{
"CONTAINERS_CONF": str(vfs_containers_conf_fpath),
"CONTAINERS_STORAGE_CONF": str(vfs_containers_storage_conf_fpath),
}
)
yield { yield {
"container_engine": "podman", "container_engine": "podman",
"docker_image": DEFAULT_IMAGE, "docker_image": DEFAULT_IMAGE,
@@ -170,6 +113,69 @@ def basis_container_kwargs():
} }
def _setup_podman_vfs(dpath):
"""
Setup the filesystem and environment variables for the VFS podman test
"""
dpath = Path(dpath)
# This requires that we write configuration files and point to them
# with environment variables before we run podman
# https://github.com/containers/common/blob/main/docs/containers.conf.5.md
vfs_containers_conf_data = {
"containers": {
"default_capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"NET_BIND_SERVICE",
"SETFCAP",
"SETGID",
"SETPCAP",
"SETUID",
"SYS_CHROOT",
]
},
"engine": {"cgroup_manager": "cgroupfs", "events_logger": "file"},
}
# https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md
storage_root = dpath / ".local/share/containers/vfs-storage"
run_root = dpath / ".local/share/containers/vfs-runroot"
storage_root.mkdir(parents=True, exist_ok=True)
run_root.mkdir(parents=True, exist_ok=True)
vfs_containers_storage_conf_data = {
"storage": {
"driver": "vfs",
"graphroot": os.fspath(storage_root),
"runroot": os.fspath(run_root),
"rootless_storage_path": os.fspath(storage_root),
"options": {
# "remap-user": "containers",
"aufs": {"mountopt": "rw"},
"overlay": {"mountopt": "rw", "force_mask": "shared"},
# "vfs": {"ignore_chown_errors": "true"},
},
}
}
vfs_containers_conf_fpath = dpath / "temp_vfs_containers.conf"
vfs_containers_storage_conf_fpath = dpath / "temp_vfs_containers_storage.conf"
with open(vfs_containers_conf_fpath, "w") as file:
toml.dump(vfs_containers_conf_data, file)
with open(vfs_containers_storage_conf_fpath, "w") as file:
toml.dump(vfs_containers_storage_conf_data, file)
oci_environ = os.environ.copy()
oci_environ.update(
{
"CONTAINERS_CONF": os.fspath(vfs_containers_conf_fpath),
"CONTAINERS_STORAGE_CONF": os.fspath(vfs_containers_storage_conf_fpath),
}
)
return oci_environ
@pytest.mark.docker @pytest.mark.docker
@pytest.mark.parametrize("container_kwargs", basis_container_kwargs()) @pytest.mark.parametrize("container_kwargs", basis_container_kwargs())
def test_simple(container_kwargs): def test_simple(container_kwargs):