Commit 4a0c8c60 authored by Adrian DC's avatar Adrian DC
Browse files

refactor: prepare #80: isolate the Docker engine as an abstract

parent 070920e0
Loading
Loading
Loading
Loading
+38 −81
Changes for gitlabci_local/engine.py: 38 added lines, 81 removed lines.
Original line number Diff line number Diff line
#!/usr/bin/env python3

# Libraries
import docker
import sys
from enum import Enum

# Components
from .engines import docker

# Backend enumeration
class Backend(Enum):
    DOCKER = 1
    UNKNOWN = 2

# Engine class
class Engine:

    # Members
    client = None
    backend = Backend.UNKNOWN
    engine = None

    # Constructor
    def __init__(self):

        # Engine client
        self.client = docker.from_env()
        # Docker engine detection
        if not self.engine:
            try:
                self.engine = docker.Docker()
                self.backend = Backend.DOCKER
            except:
                self.engine = None

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

    # Exec
    def exec(self, container, command):

        # Execute command in container
        container.exec_run(command)
        return self.engine.exec(container, command)

    # Help
    def help(self, type):

        # Exec help
        if type is 'exec':
            return 'docker exec -it'

        # Default fallback
        return ''
    def help(self, command):
        return self.engine.help(command)

    # Get
    def get(self, image):

        # Validate image exists
        try:
            self.client.images.get(image)

        # Pull missing image
        except docker.errors.ImageNotFound:
            pull(image)
        self.engine.get(image)

    # Logs
    def logs(self, container):

        # Return logs stream
        return container.logs(stream=True)
        return self.engine.logs(container)

    # Name
    def name(self, container):

        # Result
        return container.name
        return self.engine.name(container)

    # Pull
    def pull(self, image):

        # Pull image with logs stream
        for data in self.client.api.pull(image, stream=True, decode=True):

            # Layer progress logs
            if 'progress' in data:
                if sys.stdout.isatty():
                    print(
                        '\r\033[K%s: %s %s' %
                        (data['id'], data['status'], data['progress']), end='',
                        flush=True)

            # Layer event logs
            elif 'progressDetail' in data:
                if sys.stdout.isatty():
                    print('\r\033[K%s: %s' % (data['id'], data['status']), end='',
                          flush=True)

            # Layer completion logs
            elif 'id' in data:
                print('\r\033[K%s: %s' % (data['id'], data['status']), flush=True)

            # Image logs
            else:
                print('\r\033[K%s' % (data['status']), flush=True)

        # Footer
        print(' ', flush=True)
        self.engine.pull(image)

    # Remove
    def remove(self, container):

        # Remove container
        container.remove(force=True)
        self.engine.remove(container)

    # Run
    def run(self, image, command, entrypoint, variables, network, volumes, directory):

        # Run container image
        return self.client.containers.run(
            image, command=command, detach=True, entrypoint=entrypoint,
            environment=variables, network_mode=network, privileged=True, remove=False,
            stdout=True, stderr=True, stream=True, volumes=volumes, working_dir=directory)
        return self.engine.run(image, command, entrypoint, variables, network, volumes,
                               directory)

    # Sockets
    def sockets(self, volumes):

        # Add socket volume
        volumes['/var/run/docker.sock'] = { #
            'bind': '/var/run/docker.sock',
            'mode': 'rw'
        }
        self.engine.sockets(volumes)

    # Stop
    def stop(self, container, timeout):
        self.engine.stop(container, timeout)

        # Stop container
        container.stop(timeout=timeout)
    # Supports
    def supports(self, image, container, binary):
        return self.engine.supports(image, container, binary)

    # Wait
    def wait(self, container):

        # Wait container
        result = container.wait()

        # Result
        return result['StatusCode'] == 0
        return self.engine.wait(container)
+0 −0

Empty file added.

+136 −0
Changes for gitlabci_local/engines/docker.py: 136 added lines, 0 removed lines.
Original line number Diff line number Diff line
#!/usr/bin/env python3

# Libraries
import docker
import sys

# Docker class
class Docker:

    # Members
    client = None

    # Constructor
    def __init__(self):

        # Engine client
        self.client = docker.from_env()
        self.client.ping()

    # Exec
    def exec(self, container, command):

        # Execute command in container
        return container.exec_run(command)

    # Help
    def help(self, command):

        # Exec command
        if command is 'exec':
            return 'docker exec -it'

        # Default fallback
        return ''

    # Get
    def get(self, image):

        # Validate image exists
        try:
            self.client.images.get(image)

        # Pull missing image
        except docker.errors.ImageNotFound:
            self.pull(image)

    # Logs
    def logs(self, container):

        # Return logs stream
        return container.logs(stream=True)

    # Name
    def name(self, container):

        # Result
        return container.name

    # Pull
    def pull(self, image):

        # Pull image with logs stream
        for data in self.client.api.pull(image, stream=True, decode=True):

            # Layer progress logs
            if 'progress' in data:
                if sys.stdout.isatty():
                    print(
                        '\r\033[K%s: %s %s' %
                        (data['id'], data['status'], data['progress']), end='',
                        flush=True)

            # Layer event logs
            elif 'progressDetail' in data:
                if sys.stdout.isatty():
                    print('\r\033[K%s: %s' % (data['id'], data['status']), end='',
                          flush=True)

            # Layer completion logs
            elif 'id' in data:
                print('\r\033[K%s: %s' % (data['id'], data['status']), flush=True)

            # Image logs
            else:
                print('\r\033[K%s' % (data['status']), flush=True)

        # Footer
        print(' ', flush=True)

    # Remove
    def remove(self, container):

        # Remove container
        container.remove(force=True)

    # Run
    def run(self, image, command, entrypoint, variables, network, volumes, directory):

        # Run container image
        return self.client.containers.run(
            image, command=command, detach=True, entrypoint=entrypoint,
            environment=variables, network_mode=network, privileged=True, remove=False,
            stdout=True, stderr=True, stream=True, volumes=volumes, working_dir=directory)

    # Sockets
    def sockets(self, volumes):

        # Add socket volume
        volumes['/var/run/docker.sock'] = { #
            'bind': '/var/run/docker.sock',
            'mode': 'rw'
        }

    # Stop
    def stop(self, container, timeout):

        # Stop container
        container.stop(timeout=timeout)

    # Supports
    def supports(self, image, container, binary):

        # Validate binary support
        exit_code, output = self.exec(container, 'whereis %s' % (binary))

        # Result
        return exit_code == 0

    # Wait
    def wait(self, container):

        # Wait container
        result = container.wait()

        # Result
        return result['StatusCode'] == 0
+14 −3
Changes for gitlabci_local/runner.py: 14 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -308,22 +308,33 @@ def runner(options, job_data, last_result, time_launcher):
        # Show container logs
        try:
            for line in engine.logs(container):
                if isinstance(line, bytes):
                    if marker_debug in line.decode():
                        break
                    sys.stdout.buffer.write(line)
                    sys.stdout.buffer.flush()
                else:
                    if marker_debug in line:
                        break
                    sys.stdout.write(line)
                    sys.stdout.flush()
        except:
            pass

        # Runner bash or debug mode
        if options.bash or options.debug:
            print(' ')
            exit_code, output = engine.exec(container, 'which bash')

            # Select shell
            shell = 'sh'
            if exit_code == 0:
            if engine.supports(image, container, 'bash'):
                shell = 'bash'

            # Acquire container informations
            container_exec = engine.help('exec')
            container_name = engine.name(container)

            # Footer debugging informations
            print(' ')
            print(
                ' %s> INFORMATION: %sUse \'%s%s %s %s%s\' commands for debugging. Interrupt with Ctrl+C...%s'
                % (colored.fg('yellow') + colored.attr('bold'),