From 90945a38d2fc3ee8d2ad155baeafb951cd624b2e Mon Sep 17 00:00:00 2001 From: joncrall Date: Sun, 26 Jun 2022 15:53:44 -0400 Subject: [PATCH] reworks --- cibuildwheel/docker_container.py | 24 +++- unit_test/docker_container_test.py | 186 +++++++++++++++-------------- 2 files changed, 115 insertions(+), 95 deletions(-) diff --git a/cibuildwheel/docker_container.py b/cibuildwheel/docker_container.py index 55372be1..4fd1b471 100644 --- a/cibuildwheel/docker_container.py +++ b/cibuildwheel/docker_container.py @@ -80,7 +80,6 @@ class DockerContainer: network_args = ["--network=host"] shell_args = ["linux32", "/bin/bash"] if self.simulate_32_bit else ["/bin/bash"] - subprocess.run( [ self.container_engine, @@ -89,8 +88,10 @@ class DockerContainer: f"--name={self.name}", "--interactive", *network_args, + # Do we need the hostmout? # Z-flags is for SELinux - "--volume=/:/host:Z", # ignored on CircleCI + # "--volume=/:/host:Z", # ignored on CircleCI + # "--volume=/:/host", self.docker_image, *shell_args, ], @@ -116,13 +117,13 @@ class DockerContainer: self.bash_stdout = self.process.stdout # run a noop command to block until the container is responding - self.call(["/bin/true"], cwd="") + self.call(["/bin/true"], cwd="/") if self.cwd: # 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 # 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 @@ -200,7 +201,20 @@ class DockerContainer: # note: we assume from_path is a dir 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 # translate to podman, which seems to need the steps spelled out # more explicitly. diff --git a/unit_test/docker_container_test.py b/unit_test/docker_container_test.py index dd317ee2..a52279e8 100644 --- a/unit_test/docker_container_test.py +++ b/unit_test/docker_container_test.py @@ -28,11 +28,14 @@ elif pm == "s390x": else: DEFAULT_IMAGE = "" - -temp_test_dir = None +# A dictionary to make it easier to manipulate globals +_STATE = { + 'temp_test_dir': None, + 'using_podman': False, +} -@atexit.register +# @atexit.register def _cleanup_tempdir(): """ 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 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 - - global temp_test_dir + temp_test_dir = _STATE['temp_test_dir'] if temp_test_dir is not None: - print(f"CLEANUP temp_test_dir = {temp_test_dir!r}") # type: ignore[unreachable] - for r, ds, fs in os.walk(temp_test_dir.name): - for d in ds: - dpath = os.path.join(r, d) - 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) + # When podman creates special directories, they cant be cleaned up + # unless you fake a UID of 0. The package rootlesskit helps with that. + if _STATE['using_podman']: + subprocess.call(['podman', 'unshare', 'rm', '-rf', temp_test_dir.name]) try: temp_test_dir.cleanup() except Exception as ex: print(f"Issue cleaning up ex = {ex!r}") - temp_test_dir = None + _STATE['temp_test_dir'] = None def basis_container_kwargs(): @@ -80,18 +70,27 @@ def basis_container_kwargs(): Generate keyword args that can be passed to to :class:`DockerContainer`. 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 podman we test the default configuration and a configuration with VFS (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 temp_test_dir is None: + if _STATE['temp_test_dir'] is None: # 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_PODMAN = bool(shutil.which("podman")) @@ -102,67 +101,11 @@ def basis_container_kwargs(): if HAVE_PODMAN: # Basic podman usage + _STATE['using_podman'] = True yield {"container_engine": "podman", "docker_image": DEFAULT_IMAGE} # VFS Podman usage (for the podman in docker use-case) - dpath = Path(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), - } - ) - + oci_environ = _setup_podman_vfs(temp_test_dir.name) yield { "container_engine": "podman", "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.parametrize("container_kwargs", basis_container_kwargs()) def test_simple(container_kwargs):