Example #1
0
 def exec_in_container(
     self,
     container_name_or_id: str,
     command: Union[List[str], str],
     interactive=False,
     detach=False,
     env_vars: Optional[Dict[str, Optional[str]]] = None,
     stdin: Optional[bytes] = None,
     user: Optional[str] = None,
     workdir: Optional[str] = None,
 ) -> Tuple[bytes, bytes]:
     env_file = None
     cmd = self._docker_cmd()
     cmd.append("exec")
     if interactive:
         cmd.append("--interactive")
     if detach:
         cmd.append("--detach")
     if user:
         cmd += ["--user", user]
     if workdir:
         cmd += ["--workdir", workdir]
     if env_vars:
         env_flag, env_file = Util.create_env_vars_file_flag(env_vars)
         cmd += env_flag
     cmd.append(container_name_or_id)
     cmd += command if isinstance(command, List) else [command]
     LOG.debug("Execute in container cmd: %s", cmd)
     result = self._run_async_cmd(cmd, stdin, container_name_or_id)
     Util.rm_env_vars_file(env_file)
     return result
Example #2
0
 def run_container(self,
                   image_name: str,
                   stdin=None,
                   **kwargs) -> Tuple[bytes, bytes]:
     cmd, env_file = self._build_run_create_cmd("run", image_name, **kwargs)
     LOG.debug("Run container with cmd: %s", cmd)
     result = self._run_async_cmd(cmd, stdin,
                                  kwargs.get("name") or "", image_name)
     Util.rm_env_vars_file(env_file)
     return result
Example #3
0
 def get_docker_image_names(self, strip_latest=True, include_tags=True):
     try:
         images = self.client().images.list()
         image_names = [tag for image in images for tag in image.tags if image.tags]
         if not include_tags:
             image_names = list(map(lambda image_name: image_name.split(":")[0], image_names))
         if strip_latest:
             Util.append_without_latest(image_names)
         return image_names
     except APIError as e:
         raise ContainerException() from e
Example #4
0
    def get_docker_image_names(self, strip_latest=True, include_tags=True):
        format_string = "{{.Repository}}:{{.Tag}}" if include_tags else "{{.Repository}}"
        cmd = self._docker_cmd()
        cmd += ["images", "--format", format_string]
        try:
            output = run(cmd)

            image_names = output.splitlines()
            if strip_latest:
                Util.append_without_latest(image_names)
            return image_names
        except Exception as e:
            LOG.info('Unable to list Docker images via "%s": %s', cmd, e)
            return []
Example #5
0
 def copy_from_container(
     self,
     container_name: str,
     local_path: str,
     container_path: str,
 ) -> None:
     LOG.debug("Copying file from %s:%s to %s", container_name, container_path, local_path)
     try:
         container = self.client().containers.get(container_name)
         bits, _ = container.get_archive(container_path)
         Util.untar_to_path(bits, local_path)
     except NotFound:
         raise NoSuchContainer(container_name)
     except APIError as e:
         raise ContainerException() from e
Example #6
0
 def create_container(self, image_name: str, **kwargs) -> str:
     cmd, env_file = self._build_run_create_cmd("create", image_name, **kwargs)
     LOG.debug("Create container with cmd: %s", cmd)
     try:
         container_id = run(cmd)
         # Note: strip off Docker warning messages like "DNS setting (--dns=127.0.0.1) may fail in containers"
         container_id = container_id.strip().split("\n")[-1]
         return container_id.strip()
     except subprocess.CalledProcessError as e:
         if "Unable to find image" in to_str(e.stdout):
             raise NoSuchImage(image_name, stdout=e.stdout, stderr=e.stderr)
         raise ContainerException(
             "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr
         )
     finally:
         Util.rm_env_vars_file(env_file)
Example #7
0
 def build_image(self, dockerfile_path: str, image_name: str, context_path: str = None):
     cmd = self._docker_cmd()
     dockerfile_path = Util.resolve_dockerfile_path(dockerfile_path)
     context_path = context_path or os.path.dirname(dockerfile_path)
     cmd += ["build", "-t", image_name, "-f", dockerfile_path, context_path]
     LOG.debug("Building Docker image: %s", cmd)
     try:
         run(cmd)
     except subprocess.CalledProcessError as e:
         raise ContainerException(
             f"Docker build process returned with error code {e.returncode}", e.stdout, e.stderr
         ) from e
Example #8
0
 def build_image(self, dockerfile_path: str, image_name: str, context_path: str = None):
     try:
         dockerfile_path = Util.resolve_dockerfile_path(dockerfile_path)
         context_path = context_path or os.path.dirname(dockerfile_path)
         LOG.debug("Building Docker image %s from %s", image_name, dockerfile_path)
         self.client().images.build(
             path=context_path,
             dockerfile=dockerfile_path,
             tag=image_name,
             rm=True,
         )
     except APIError as e:
         raise ContainerException("Unable to build Docker image") from e
Example #9
0
 def copy_into_container(
     self, container_name: str, local_path: str, container_path: str
 ) -> None:  # TODO behave like https://docs.docker.com/engine/reference/commandline/cp/
     LOG.debug("Copying file %s into %s:%s", local_path, container_name, container_path)
     try:
         container = self.client().containers.get(container_name)
         target_exists, target_isdir = self._container_path_info(container, container_path)
         target_path = container_path if target_isdir else os.path.dirname(container_path)
         with Util.tar_path(local_path, container_path, is_dir=target_isdir) as tar:
             container.put_archive(target_path, tar)
     except NotFound:
         raise NoSuchContainer(container_name)
     except APIError as e:
         raise ContainerException() from e
Example #10
0
 def _build_run_create_cmd(
     self,
     action: str,
     image_name: str,
     *,
     name: Optional[str] = None,
     entrypoint: Optional[str] = None,
     remove: bool = False,
     interactive: bool = False,
     tty: bool = False,
     detach: bool = False,
     command: Optional[Union[List[str], str]] = None,
     mount_volumes: Optional[List[SimpleVolumeBind]] = None,
     ports: Optional[PortMappings] = None,
     env_vars: Optional[Dict[str, str]] = None,
     user: Optional[str] = None,
     cap_add: Optional[List[str]] = None,
     cap_drop: Optional[List[str]] = None,
     network: Optional[str] = None,
     dns: Optional[str] = None,
     additional_flags: Optional[str] = None,
     workdir: Optional[str] = None,
 ) -> Tuple[List[str], str]:
     env_file = None
     cmd = self._docker_cmd() + [action]
     if remove:
         cmd.append("--rm")
     if name:
         cmd += ["--name", name]
     if entrypoint is not None:  # empty string entrypoint can be intentional
         cmd += ["--entrypoint", entrypoint]
     if mount_volumes:
         cmd += [
             volume
             for host_path, docker_path in dict(mount_volumes).items()
             for volume in ["-v", f"{host_path}:{docker_path}"]
         ]
     if interactive:
         cmd.append("--interactive")
     if tty:
         cmd.append("--tty")
     if detach:
         cmd.append("--detach")
     if ports:
         cmd += ports.to_list()
     if env_vars:
         env_flags, env_file = Util.create_env_vars_file_flag(env_vars)
         cmd += env_flags
     if user:
         cmd += ["--user", user]
     if cap_add:
         cmd += list(
             itertools.chain.from_iterable(["--cap-add", cap]
                                           for cap in cap_add))
     if cap_drop:
         cmd += list(
             itertools.chain.from_iterable(["--cap-drop", cap]
                                           for cap in cap_drop))
     if network:
         cmd += ["--network", network]
     if dns:
         cmd += ["--dns", dns]
     if workdir:
         cmd += ["--workdir", workdir]
     if additional_flags:
         cmd += shlex.split(additional_flags)
     cmd.append(image_name)
     if command:
         cmd += command if isinstance(command, List) else [command]
     return cmd, env_file
Example #11
0
    def create_container(
        self,
        image_name: str,
        *,
        name: Optional[str] = None,
        entrypoint: Optional[str] = None,
        remove: bool = False,
        interactive: bool = False,
        tty: bool = False,
        detach: bool = False,
        command: Optional[Union[List[str], str]] = None,
        mount_volumes: Optional[List[SimpleVolumeBind]] = None,
        ports: Optional[PortMappings] = None,
        env_vars: Optional[Dict[str, str]] = None,
        user: Optional[str] = None,
        cap_add: Optional[List[str]] = None,
        cap_drop: Optional[List[str]] = None,
        security_opt: Optional[List[str]] = None,
        network: Optional[str] = None,
        dns: Optional[str] = None,
        additional_flags: Optional[str] = None,
        workdir: Optional[str] = None,
    ) -> str:
        LOG.debug("Creating container with attributes: %s", locals())
        extra_hosts = None
        if additional_flags:
            env_vars, ports, mount_volumes, extra_hosts, network = Util.parse_additional_flags(
                additional_flags, env_vars, ports, mount_volumes, network
            )
        try:
            kwargs = {}
            if cap_add:
                kwargs["cap_add"] = cap_add
            if cap_drop:
                kwargs["cap_drop"] = cap_drop
            if security_opt:
                kwargs["security_opt"] = security_opt
            if dns:
                kwargs["dns"] = [dns]
            if ports:
                kwargs["ports"] = ports.to_dict()
            if workdir:
                kwargs["working_dir"] = workdir
            mounts = None
            if mount_volumes:
                mounts = Util.convert_mount_list_to_dict(mount_volumes)

            def create_container():
                return self.client().containers.create(
                    image=image_name,
                    command=command,
                    auto_remove=remove,
                    name=name,
                    stdin_open=interactive,
                    tty=tty,
                    entrypoint=entrypoint,
                    environment=env_vars,
                    detach=detach,
                    user=user,
                    network=network,
                    volumes=mounts,
                    extra_hosts=extra_hosts,
                    **kwargs,
                )

            try:
                container = create_container()
            except ImageNotFound:
                self.pull_image(image_name)
                container = create_container()
            return container.id
        except ImageNotFound:
            raise NoSuchImage(image_name)
        except APIError as e:
            raise ContainerException() from e
Example #12
0
def test_argument_parsing():
    test_port_string = "-p 80:8080/udp"
    test_port_string_with_host = "-p 127.0.0.1:6000:7000/tcp"
    test_port_string_many_to_one = "-p 9230-9231:9230"
    test_env_string = "-e TEST_ENV_VAR=test_string=123"
    test_mount_string = "-v /var/test:/opt/test"
    argument_string = f"{test_port_string} {test_env_string} {test_mount_string} {test_port_string_with_host} {test_port_string_many_to_one}"
    env_vars = {}
    ports = PortMappings()
    mounts = []
    Util.parse_additional_flags(argument_string, env_vars, ports, mounts)
    assert env_vars == {"TEST_ENV_VAR": "test_string=123"}
    assert ports.to_str() == "-p 80:8080/udp -p 6000:7000 -p 9230-9231:9230"
    assert mounts == [("/var/test", "/opt/test")]
    argument_string = (
        "--add-host host.docker.internal:host-gateway --add-host arbitrary.host:127.0.0.1"
    )
    _, _, _, extra_hosts, _ = Util.parse_additional_flags(
        argument_string, env_vars, ports, mounts)
    assert {
        "host.docker.internal": "host-gateway",
        "arbitrary.host": "127.0.0.1"
    } == extra_hosts

    with pytest.raises(NotImplementedError):
        argument_string = "--somerandomargument"
        Util.parse_additional_flags(argument_string, env_vars, ports, mounts)
    with pytest.raises(ValueError):
        argument_string = "--publish 80:80:80:80"
        Util.parse_additional_flags(argument_string, env_vars, ports, mounts)

    # Test windows paths
    argument_string = r'-v "C:\Users\SomeUser\SomePath:/var/task"'
    _, _, mounts, _, _ = Util.parse_additional_flags(argument_string)
    assert mounts == [(r"C:\Users\SomeUser\SomePath", "/var/task")]
    argument_string = r'-v "C:\Users\SomeUser\SomePath:/var/task:ro"'
    _, _, mounts, _, _ = Util.parse_additional_flags(argument_string)
    assert mounts == [(r"C:\Users\SomeUser\SomePath", "/var/task")]
    argument_string = r'-v "C:\Users\Some User\Some Path:/var/task:ro"'
    _, _, mounts, _, _ = Util.parse_additional_flags(argument_string)
    assert mounts == [(r"C:\Users\Some User\Some Path", "/var/task")]
    argument_string = r'-v "/var/test:/var/task:ro"'
    _, _, mounts, _, _ = Util.parse_additional_flags(argument_string)
    assert mounts == [("/var/test", "/var/task")]

    # Test file paths
    argument_string = r'-v "/tmp/test.jar:/tmp/foo bar/test.jar"'
    _, _, mounts, _, _ = Util.parse_additional_flags(argument_string)
    assert mounts == [(r"/tmp/test.jar", "/tmp/foo bar/test.jar")]
    argument_string = r'-v "/tmp/test-foo_bar.jar:/tmp/test-foo_bar2.jar"'
    _, _, mounts, _, _ = Util.parse_additional_flags(argument_string)
    assert mounts == [(r"/tmp/test-foo_bar.jar", "/tmp/test-foo_bar2.jar")]

    # Test file paths
    argument_string = r'-v "/tmp/test.jar:/tmp/foo bar/test.jar" --network mynet123'
    _, _, _, _, network = Util.parse_additional_flags(argument_string)
    assert network == "mynet123"