Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: replace python docker-compose with docker compose #2343

Merged
merged 3 commits into from Dec 12, 2023

Conversation

buchdag
Copy link
Member

@buchdag buchdag commented Dec 12, 2023

This PR get rid of the deprecated Python docker-compose in favor of the now built in command docker compose.

This means that all docker compose test files must be at least valid compose v2 files, which pretty much all of them weren't.

Required for #2340

@buchdag buchdag added type/ci PR that change the CI configuration files and scripts type/test PR that add missing tests or correct existing tests labels Dec 12, 2023
@buchdag buchdag self-assigned this Dec 12, 2023
@buchdag buchdag merged commit f044423 into main Dec 12, 2023
2 checks passed
@buchdag buchdag deleted the docker-compose-upgrade branch December 12, 2023 21:07
@buchdag buchdag restored the docker-compose-upgrade branch December 15, 2023 11:56
@buchdag buchdag deleted the docker-compose-upgrade branch December 15, 2023 11:57
@SchoNie
Copy link
Contributor

SchoNie commented Dec 28, 2023

Sorry for posting in this PR. But I am lost why I cannot run the tests on my system anymore.
Since this PR I cannot run the pytests (make test-debian or make test-alpine) on my system anymore. It seems to fail in the docker_compose fixture, but I do not know why.

_____________________________________________________________________________________________________________________ ERROR at setup of test_unknown_virtual_host ______________________________________________________________________________________________________________________

monkey_patched_dns = None, docker_composer = <conftest.DockerComposer object at 0x7fd3ac7c6460>, docker_compose_file = '/home/user/nginx-proxy/test/test_DOCKER_HOST_unix_socket.yml'

    @pytest.fixture
    def docker_compose(monkey_patched_dns, docker_composer, docker_compose_file):
        """Ensures containers described in a docker compose file are started.

        A custom docker compose file name can be specified by overriding the `docker_compose_file`
        fixture.

        Also, in the case where pytest is running from a docker container, this fixture makes sure
        our container will be attached to all the docker networks.
        """
>       docker_composer.compose(docker_compose_file)

conftest.py:492:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
conftest.py:450: in compose
    docker_compose_up(docker_compose_file)
conftest.py:307: in docker_compose_up
    subprocess.check_output(shlex.split(f'{DOCKER_COMPOSE} -f {compose_file} up -d'), stderr=subprocess.STDOUT)
/usr/local/lib/python3.9/subprocess.py:424: in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
/usr/local/lib/python3.9/subprocess.py:505: in run
    with Popen(*popenargs, **kwargs) as process:
/usr/local/lib/python3.9/subprocess.py:951: in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <Popen: returncode: 255 args: ['docker', 'compose', '-f', '/home/user/nginx-...>, args = ['docker', 'compose', '-f', '/home/user/nginx-proxy/test/test_DOCKER_HOST_unix_socket.yml', 'up', '-d'], executable = b'docker', preexec_fn = None, close_fds = True, pass_fds = ()
cwd = None, env = None, startupinfo = None, creationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 16, c2pwrite = 17, errread = -1, errwrite = 17, restore_signals = True, gid = None, gids = None, uid = None, umask = -1, start_new_session = False

    def _execute_child(self, args, executable, preexec_fn, close_fds,
                       pass_fds, cwd, env,
                       startupinfo, creationflags, shell,
                       p2cread, p2cwrite,
                       c2pread, c2pwrite,
                       errread, errwrite,
                       restore_signals,
                       gid, gids, uid, umask,
                       start_new_session):
        """Execute program (POSIX version)"""

        if isinstance(args, (str, bytes)):
            args = [args]
        elif isinstance(args, os.PathLike):
            if shell:
                raise TypeError('path-like args is not allowed when '
                                'shell is true')
            args = [args]
        else:
            args = list(args)

        if shell:
            # On Android the default shell is at '/system/bin/sh'.
            unix_shell = ('/system/bin/sh' if
                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')
            args = [unix_shell, "-c"] + args
            if executable:
                args[0] = executable

        if executable is None:
            executable = args[0]

        sys.audit("subprocess.Popen", executable, args, cwd, env)

        if (_USE_POSIX_SPAWN
                and os.path.dirname(executable)
                and preexec_fn is None
                and not close_fds
                and not pass_fds
                and cwd is None
                and (p2cread == -1 or p2cread > 2)
                and (c2pwrite == -1 or c2pwrite > 2)
                and (errwrite == -1 or errwrite > 2)
                and not start_new_session
                and gid is None
                and gids is None
                and uid is None
                and umask < 0):
            self._posix_spawn(args, executable, env, restore_signals,
                              p2cread, p2cwrite,
                              c2pread, c2pwrite,
                              errread, errwrite)
            return

        orig_executable = executable

        # For transferring possible exec failure from child to parent.
        # Data format: "exception name:hex errno:description"
        # Pickle is not used; it is complex and involves memory allocation.
        errpipe_read, errpipe_write = os.pipe()
        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
        low_fds_to_close = []
        while errpipe_write < 3:
            low_fds_to_close.append(errpipe_write)
            errpipe_write = os.dup(errpipe_write)
        for low_fd in low_fds_to_close:
            os.close(low_fd)
        try:
            try:
                # We must avoid complex work that could involve
                # malloc or free in the child process to avoid
                # potential deadlocks, thus we do all this here.
                # and pass it to fork_exec()

                if env is not None:
                    env_list = []
                    for k, v in env.items():
                        k = os.fsencode(k)
                        if b'=' in k:
                            raise ValueError("illegal environment variable name")
                        env_list.append(k + b'=' + os.fsencode(v))
                else:
                    env_list = None  # Use execv instead of execve.
                executable = os.fsencode(executable)
                if os.path.dirname(executable):
                    executable_list = (executable,)
                else:
                    # This matches the behavior of os._execvpe().
                    executable_list = tuple(
                        os.path.join(os.fsencode(dir), executable)
                        for dir in os.get_exec_path(env))
                fds_to_keep = set(pass_fds)
                fds_to_keep.add(errpipe_write)
                self.pid = _posixsubprocess.fork_exec(
                        args, executable_list,
                        close_fds, tuple(sorted(map(int, fds_to_keep))),
                        cwd, env_list,
                        p2cread, p2cwrite, c2pread, c2pwrite,
                        errread, errwrite,
                        errpipe_read, errpipe_write,
                        restore_signals, start_new_session,
                        gid, gids, uid, umask,
                        preexec_fn)
                self._child_created = True
            finally:
                # be sure the FD is closed no matter what
                os.close(errpipe_write)

            self._close_pipe_fds(p2cread, p2cwrite,
                                 c2pread, c2pwrite,
                                 errread, errwrite)

            # Wait for exec to fail or succeed; possibly raising an
            # exception (limited in size)
            errpipe_data = bytearray()
            while True:
                part = os.read(errpipe_read, 50000)
                errpipe_data += part
                if not part or len(errpipe_data) > 50000:
                    break
        finally:
            # be sure the FD is closed no matter what
            os.close(errpipe_read)

        if errpipe_data:
            try:
                pid, sts = os.waitpid(self.pid, 0)
                if pid == self.pid:
                    self._handle_exitstatus(sts)
                else:
                    self.returncode = sys.maxsize
            except ChildProcessError:
                pass

            try:
                exception_name, hex_errno, err_msg = (
                        errpipe_data.split(b':', 2))
                # The encoding here should match the encoding
                # written in by the subprocess implementations
                # like _posixsubprocess
                err_msg = err_msg.decode()
            except ValueError:
                exception_name = b'SubprocessError'
                hex_errno = b'0'
                err_msg = 'Bad exception data from child: {!r}'.format(
                              bytes(errpipe_data))
            child_exception_type = getattr(
                    builtins, exception_name.decode('ascii'),
                    SubprocessError)
            if issubclass(child_exception_type, OSError) and hex_errno:
                errno_num = int(hex_errno, 16)
                child_exec_never_called = (err_msg == "noexec")
                if child_exec_never_called:
                    err_msg = ""
                    # The error must be from chdir(cwd).
                    err_filename = cwd
                else:
                    err_filename = orig_executable
                if errno_num != 0:
                    err_msg = os.strerror(errno_num)
>               raise child_exception_type(errno_num, err_msg, err_filename)
E               FileNotFoundError: [Errno 2] No such file or directory: 'docker'

/usr/local/lib/python3.9/subprocess.py:1837: FileNotFoundError
-------------------------------------------------------------------------------------------------------------------------------- Captured stderr setup ---------------------------------------------------------------------------------------------------------------------------------
INFO:root:docker compose -f /home/user/nginx-proxy/test/test_DOCKER_HOST_unix_socket.yml up -d
---------------------------------------------------------------------------------------------------------------------------------- Captured log setup ----------------------------------------------------------------------------------------------------------------------------------
INFO     root:conftest.py:305 docker compose -f /home/user/nginx-proxy/test/test_DOCKER_HOST_unix_socket.yml up -d

Uptodate Rocky Linux OS. compose V2 is there.

Client: Docker Engine - Community
 Version:    24.0.7
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.11.2
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.21.0
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 3
 Server Version: 24.0.7
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 3dd1e886e55dd695541fdcd67420c2888645a495
 runc version: v1.1.10-0-g18a0cb0
 init version: de40ad0
 Security Options:
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 5.14.0-362.13.1.el9_3.x86_64
 Operating System: Rocky Linux 9.3 (Blue Onyx)
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 660.6MiB
 Name: localhost.localdomain
 ID: ffbd1fbc-1ea7-4b62-a2ce-f8b701d57d8f
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

You have any idea? Trough github actions they run fine so seems to be a combination on my system.

@buchdag
Copy link
Member Author

buchdag commented Dec 28, 2023

Hi @SchoNie, I think that's what @pini-gh fixed in #2349

@SchoNie
Copy link
Contributor

SchoNie commented Dec 28, 2023

Yes I checked that but that makes no difference so I got confused.
Rocky Linux is rhel based and the v2 of compose is installed.
‘docker compose’ v2 command works and the old ‘docker-compose’ v1 is not found so I would expect that’s all good.

@buchdag
Copy link
Member Author

buchdag commented Dec 30, 2023

Could you try adding to shell=True to this subprocess.check_output() call ?

subprocess.check_output(shlex.split(f'{DOCKER_COMPOSE} -f {compose_file} up -d'), stderr=subprocess.STDOUT)

@SchoNie
Copy link
Contributor

SchoNie commented Jan 2, 2024

Thanks, tried that. And there is a different error, but not working.

Command '['docker', 'compose', '-f', '/home/user/nginx-proxy/test/test_fallback.data/nohttps-on-app.yml', 'up', '-d']' returned non-zero exit status 127.

During handling of the above exception, another exception occurred:
Error while runninng 'docker compose -f /home/user/nginx-proxy/test/test_fallback.data/nohttps-on-app.yml up -d':
b'compose: 1: docker: not found\n'

Installed a fresh Ubuntu 22.04 LTS server minimal, installed docker and tried to run the tests. And they fail. So should be reproducable.

@SchoNie
Copy link
Contributor

SchoNie commented Jan 4, 2024

I added RUN curl -sSL https://get.docker.com/ | sh to Dockerfile-nginx-proxy-tester and now I can run the tests.
Could it be for the Github Action Runner the docker / docker compose binaries are in the Hosted tool cache ?

@buchdag
Copy link
Member Author

buchdag commented Jan 5, 2024

The situation with compose is a bit messy :

  • the version 1 was a python package you had to install separately.
  • the version 2 is a go application that is packaged as a plugin for Docker.

On Windows and MacOs the v2 is bundled with Docker Desktop.

On Linux it's automatically installed when you use the get.docker.com script and you distro's version of Docker is >= 20.10. If you don't use this script I guess you have to manually install Docker Compose.

I think (but could not find confirmation with a quick search) that GitHub Actions runners comes with the compose plugin pre installed alongside Docker.

We should probably install Docker Compose in the nginx-proxy-tester Dockerfile and warn in the test's README that Compose is required to run the tests on the host (either v2 or v1 with #2349 fix). Do you want to handle this ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/ci PR that change the CI configuration files and scripts type/test PR that add missing tests or correct existing tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants