Commit ce496420 authored by Adrian DC's avatar Adrian DC
Browse files

chore: continue #170: refactor engines with typing codestyle

parent 8ebf17f9
Loading
Loading
Loading
Loading
+40 −18
Changes for gitlabci_local/engines/base.py: 40 added lines, 18 removed lines.
Original line number Diff line number Diff line
@@ -2,39 +2,60 @@

# Standard libraries
from abc import ABC, abstractmethod
from typing import Any, Dict, Iterator, List, NamedTuple

# Interface class
class Interface(ABC): # pragma: no cover
# Components
from ..types.volumes import Volumes

# Commands type
Commands = List[str]

# ContainerName type
ContainerName = str

# ExecResult type
class ExecResult(NamedTuple):

    # Properties
    exit_code: int
    output: str
    returncode: int

# LogsResult type
LogsResult = Iterator[bytes]

# Base engine class, pylint: disable=unused-argument
class BaseEngine(ABC): # pragma: no cover

    # Command exec
    @abstractmethod
    def cmd_exec(self) -> str:
        pass
        return ''

    # Container
    @property
    @abstractmethod
    def container(self) -> str:
        pass
    def container(self) -> ContainerName:
        return ''

    # Exec
    @abstractmethod
    def exec(self, command):
        pass
    def exec(self, command: Commands) -> ExecResult:
        return ExecResult(0, '', 0)

    # Get
    @abstractmethod
    def get(self, image):
    def get(self, image: str) -> None:
        pass

    # Logs
    @abstractmethod
    def logs(self):
        pass
    def logs(self) -> LogsResult:
        return iter()

    # Pull
    @abstractmethod
    def pull(self, image, force=False) -> None:
    def pull(self, image: str, force: bool = False) -> None:
        pass

    # Remove
@@ -44,26 +65,27 @@ class Interface(ABC): # pragma: no cover

    # Remove image
    @abstractmethod
    def rmi(self, image) -> None:
    def rmi(self, image: str) -> None:
        pass

    # Run, pylint: disable=too-many-arguments
    @abstractmethod
    def run(self, image, command, entrypoint, variables, network, option_sockets,
            services, volumes, directory, temp_folder):
    def run(self, image: str, commands: Commands, entrypoint: Any,
            variables: Dict[str, str], network: str, option_sockets: bool, services: bool,
            volumes: Volumes, directory: str, temp_folder: str) -> None:
        pass

    # Stop
    @abstractmethod
    def stop(self, timeout) -> None:
    def stop(self, timeout: int) -> None:
        pass

    # Supports
    @abstractmethod
    def supports(self, binary) -> bool:
        pass
    def supports(self, binary: str) -> bool:
        return False

    # Wait
    @abstractmethod
    def wait(self) -> bool:
        pass
        return False
+27 −22
Changes for gitlabci_local/engines/docker.py: 27 added lines, 22 removed lines.
Original line number Diff line number Diff line
@@ -2,18 +2,21 @@

# Standard libraries
from os import environ
from typing import Any, Dict, Optional

# Modules libraries
from docker import DockerClient, from_env
from docker.errors import APIError, DockerException, ImageNotFound
from docker.models.containers import Container

# Components
from ..jobs.outputs import Outputs
from ..system.platform import Platform
from .interface import Interface
from ..types.volumes import Volumes
from .base import BaseEngine, Commands, ContainerName, ExecResult, LogsResult

# Docker class
class Docker(Interface):
# Docker engine class
class DockerEngine(BaseEngine):

    # Constants
    ENV_DOCKER_CERT_PATH: str = 'DOCKER_CERT_PATH'
@@ -22,7 +25,7 @@ class Docker(Interface):

    # Members
    __client: DockerClient = None
    __container = None
    __container: Optional[Container] = None

    # Constructor
    def __init__(self) -> None:
@@ -38,28 +41,29 @@ class Docker(Interface):
            raise ModuleNotFoundError() from None

    # Sockets, pylint: disable=no-self-use
    def __sockets(self, variables, volumes) -> None:
    def __sockets(self, variables: Dict[str, str], volumes: Volumes) -> None:

        # Variables
        docker_host = ''

        # Detect TLS configurations
        if Docker.ENV_DOCKER_TLS_VERIFY in environ:
            variables[Docker.ENV_DOCKER_TLS_VERIFY] = environ[
                Docker.ENV_DOCKER_TLS_VERIFY]
        if DockerEngine.ENV_DOCKER_TLS_VERIFY in environ:
            variables[DockerEngine.ENV_DOCKER_TLS_VERIFY] = environ[
                DockerEngine.ENV_DOCKER_TLS_VERIFY]

        # Detect certificates configurations
        if Docker.ENV_DOCKER_CERT_PATH in environ:
            variables[Docker.ENV_DOCKER_CERT_PATH] = '/certs'
            volumes.add(environ[Docker.ENV_DOCKER_CERT_PATH], '/certs', 'ro', True)
        if DockerEngine.ENV_DOCKER_CERT_PATH in environ:
            variables[DockerEngine.ENV_DOCKER_CERT_PATH] = '/certs'
            volumes.add(environ[DockerEngine.ENV_DOCKER_CERT_PATH], '/certs', 'ro', True)

        # Detect host configurations
        if Docker.ENV_DOCKER_HOST in environ and environ[Docker.ENV_DOCKER_HOST]:
            docker_host = environ[Docker.ENV_DOCKER_HOST]
        if DockerEngine.ENV_DOCKER_HOST in environ and environ[
                DockerEngine.ENV_DOCKER_HOST]:
            docker_host = environ[DockerEngine.ENV_DOCKER_HOST]

        # Network Docker socket
        if docker_host[0:7] == 'http://' or docker_host[0:6] == 'tcp://':
            variables[Docker.ENV_DOCKER_HOST] = docker_host
            variables[DockerEngine.ENV_DOCKER_HOST] = docker_host

        # Local Docker socket
        elif docker_host[0:7] == 'unix://': # pragma: no cover
@@ -79,7 +83,7 @@ class Docker(Interface):
        # Unknown feature
        else: # pragma: no cover
            Outputs.warning(
                f'The {Docker.ENV_DOCKER_HOST} = {docker_host}' \
                f'The {DockerEngine.ENV_DOCKER_HOST} = {docker_host}' \
                    ' configuration is not supported yet...'
            )

@@ -91,13 +95,13 @@ class Docker(Interface):

    # Container
    @property
    def container(self) -> str:
    def container(self) -> ContainerName:

        # Result
        return self.__container.name
        return str(self.__container.name)

    # Exec
    def exec(self, command: str):
    def exec(self, command: Commands) -> ExecResult:

        # Execute command in container
        return self.__container.exec_run(command)
@@ -114,7 +118,7 @@ class Docker(Interface):
            self.pull(image)

    # Logs
    def logs(self):
    def logs(self) -> LogsResult:

        # Return logs stream
        return self.__container.logs(stream=True)
@@ -174,8 +178,9 @@ class Docker(Interface):
            pass

    # Run, pylint: disable=too-many-arguments,unused-argument
    def run(self, image, command, entrypoint, variables, network, option_sockets,
            services, volumes, directory, temp_folder) -> None:
    def run(self, image: str, commands: Commands, entrypoint: Any,
            variables: Dict[str, str], network: str, option_sockets: bool, services: bool,
            volumes: Volumes, directory: str, temp_folder: str) -> None:

        # Append sockets mounts
        if option_sockets:
@@ -184,7 +189,7 @@ class Docker(Interface):
        # Run container image
        self.__container = self.__client.containers.run(
            image=image,
            command=command,
            command=commands,
            detach=True,
            entrypoint=entrypoint,
            environment=variables,
+25 −17
Changes for gitlabci_local/engines/engine.py: 25 added lines, 17 removed lines.
Original line number Diff line number Diff line
@@ -3,11 +3,13 @@
# Standard libraries
from argparse import Namespace
from enum import Enum
from typing import List
from typing import Any, Dict, List

# Components
from .docker import Docker
from .podman import Podman
from ..types.volumes import Volumes
from .base import BaseEngine, Commands, ContainerName, ExecResult, LogsResult
from .docker import DockerEngine
from .podman import PodmanEngine

# Backend enumeration
class Backend(Enum):
@@ -66,12 +68,15 @@ def supported() -> List[str]:
class Engine:

    # Members
    __engine = None
    __engine: BaseEngine
    __name: str = ''

    # Constructor
    def __init__(self, options: Namespace) -> None:

        # Variables
        engine: BaseEngine | None = None

        # Acquire engine names
        names: List[str] = Names.get(options.engine)

@@ -81,36 +86,39 @@ class Engine:
            # Detect Docker engine
            if name == Names.DOCKER:
                try:
                    self.__engine = Docker()
                    engine = DockerEngine()
                    self.__name = Names.DOCKER
                    break
                except (KeyboardInterrupt, ModuleNotFoundError):
                    self.__engine = None
                    engine = None

            # Detect Podman engine
            elif name == Names.PODMAN:
                try:
                    self.__engine = Podman()
                    engine = PodmanEngine()
                    self.__name = Names.PODMAN
                    break
                except (KeyboardInterrupt, ModuleNotFoundError):
                    self.__engine = None
                    engine = None

        # Unknown engine fallback
        if not self.__engine:
        if not engine:
            raise NotImplementedError('Unknown or unsupported container engine...')

        # Store engine
        self.__engine = engine

    # Command exec
    def cmd_exec(self) -> str:
        return self.__engine.cmd_exec()

    # Container
    @property
    def container(self) -> str:
    def container(self) -> ContainerName:
        return self.__engine.container

    # Exec
    def exec(self, command): # pragma: no cover
    def exec(self, command: Commands) -> ExecResult: # pragma: no cover
        return self.__engine.exec(command)

    # Get
@@ -118,7 +126,7 @@ class Engine:
        self.__engine.get(image)

    # Logs
    def logs(self):
    def logs(self) -> LogsResult:
        return self.__engine.logs()

    # Name
@@ -139,12 +147,12 @@ class Engine:
        self.__engine.rmi(image)

    # Run, pylint: disable=too-many-arguments
    def run(self, image: str, command, entrypoint: str, variables, network: str,
            option_sockets: bool, services: bool, volumes, directory: str,
            temp_folder: str):
        return self.__engine.run(
    def run(self, image: str, commands: Commands, entrypoint: Any,
            variables: Dict[str, str], network: str, option_sockets: bool, services: bool,
            volumes: Volumes, directory: str, temp_folder: str) -> None:
        self.__engine.run(
            image=image,
            command=command,
            commands=commands,
            entrypoint=entrypoint,
            variables=variables,
            network=network,
+26 −21
Changes for gitlabci_local/engines/podman.py: 26 added lines, 21 removed lines.
Original line number Diff line number Diff line
@@ -3,16 +3,23 @@
# Standard libraries
from os import environ
from subprocess import DEVNULL, PIPE, Popen, run
from typing import List
from typing import Any, Dict, Iterator, List, NamedTuple

# Components
from ..package.bundle import Bundle
from ..system.platform import Platform
from ..types.volumes import Volumes
from .interface import Interface
from .base import BaseEngine, Commands, ContainerName, LogsResult

# Podman class
class Podman(Interface):
# Podman engine class
class PodmanEngine(BaseEngine):

    # ExecResult type
    class ExecResult(NamedTuple):

        # Properties
        returncode: int
        stdout: Any

    # Members
    __binary: str = 'podman'
@@ -37,7 +44,7 @@ class Podman(Interface):
            raise ModuleNotFoundError() from None

    # Internal execution
    def __exec(self, arguments: List[str], quiet: bool = False):
    def __exec(self, arguments: List[str], quiet: bool = False) -> ExecResult:

        # Execute quiet command
        if quiet:
@@ -48,12 +55,12 @@ class Podman(Interface):
        return run([self.__binary] + arguments, check=False, stdout=PIPE, stderr=PIPE)

    # Internal watcher
    def __watch(self, arguments: List[str]):
    def __watch(self, arguments: List[str]) -> Iterator[bytes]:

        # Watch command outputs, pylint: disable=consider-using-with
        stdout = Popen([self.__binary] + arguments, stdout=PIPE).stdout
        if not stdout: # pragma: no cover
            return None
            return iter()
        return iter(stdout.readline, b'')

    # Command exec
@@ -66,7 +73,7 @@ class Podman(Interface):

    # Container
    @property
    def container(self) -> str:
    def container(self) -> ContainerName:

        # Result
        result = self.__exec(
@@ -74,7 +81,7 @@ class Podman(Interface):
        return result.stdout.strip().decode('utf-8') if result.returncode == 0 else ''

    # Exec
    def exec(self, command):
    def exec(self, command: Commands) -> ExecResult:

        # Adapt command
        if isinstance(command, str): # pragma: no cover
@@ -101,7 +108,7 @@ class Podman(Interface):
            self.pull(image)

    # Logs
    def logs(self):
    def logs(self) -> LogsResult:

        # Return logs stream
        return self.__watch(['logs', '--follow', self.__container])
@@ -168,28 +175,26 @@ class Podman(Interface):
            ])

    # Run, pylint: disable=too-many-arguments,too-many-locals,unused-argument
    def run(self, image: str, command, entrypoint: str, variables, network: str,
            option_sockets: bool, services: bool, volumes, directory: str,
            temp_folder: str) -> None:
    def run(self, image: str, commands: Commands, entrypoint: Any,
            variables: Dict[str, str], network: str, option_sockets: bool, services: bool,
            volumes: Volumes, directory: str, temp_folder: str) -> None:

        # Variables
        args_command = []
        args_commands: Commands = []
        args_entrypoint: List[str] = []
        args_env: List[str] = []
        args_run: List[str] = []
        args_volumes: List[str] = []

        # Adapt command
        if isinstance(command, list): # pragma: no cover
            args_command = command
        elif isinstance(command, str):
            args_command = [command]
        if commands:
            args_commands += commands

        # Adapt entrypoint
        if isinstance(entrypoint, list):
            if len(entrypoint) > 1:
                args_command = [' '.join(args_command)]
                args_command[0:0] = entrypoint[1:]
                args_commands = [' '.join(args_commands)]
                args_commands[0:0] = entrypoint[1:]
            entrypoint = entrypoint[0]
        if isinstance(entrypoint, str):
            args_entrypoint = ['--entrypoint', entrypoint]
@@ -215,7 +220,7 @@ class Podman(Interface):
        args_run += ['--security-opt', 'label=disable']
        args_run += ['--workdir', directory]
        args_run += [image]
        args_run += args_command
        args_run += args_commands

        # Create container image
        result = self.__exec(args_run)
+1 −1
Changes for gitlabci_local/features/images.py: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ from ..engines.engine import Engine
class ImagesFeature:

    # Members
    __engine = None
    __engine: Engine
    __images: List[str]
    __options: Namespace

Loading