reworks
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user