# -*- python -*-
# ex: set syntax=python:
## BUILDBOT CONFIGURATION

c = BuildmasterConfig = {}
 
####### DB URL

c['db'] = {'db_url' : "sqlite:////data/buildbot/db/state.sqlite"}

####### BUILDSLAVES
### BEGIN CODE FROM master/buildbot/buildslave/docker.py @ master
from io import BytesIO

from twisted.internet import defer
from twisted.internet import threads
from twisted.python import log

from buildbot import config
from buildbot.buildslave import AbstractLatentBuildSlave
from buildbot.interfaces import LatentBuildSlaveFailedToSubstantiate
from buildbot.util import json

try:
    import docker
    from docker import client
    _hush_pyflakes = [docker, client]
except ImportError:
    client = None

def handle_stream_line(line):
    """\
    Input is the json representation of: {'stream': "Content\ncontent"}
    Output is a generator yield "Content", and then "content"
    """
    # XXX This necessary processing is probably a bug from docker-py,
    # hence, might break if the bug is fixed, i.e. we should get decoded JSON
    # directly from the API.
    line = json.loads(line)
    if 'error' in line:
        content = "ERROR: " + line['error']
    else:
        content = line.get('stream', '')
    for streamline in content.split('\n'):
        if streamline:
            yield streamline

class DockerLatentBuildSlave(AbstractLatentBuildSlave):
    instance = None

    def __init__(self, name, password, docker_host, image=None, command=None,
                 volumes=None, dockerfile=None, version=None, tls=None,
                 followStartupLogs=False, environment=None, links=None,
                 cap_add=None, privileged=False, **kwargs):

        if not client:
            config.error("The python module 'docker-py' is needed to use a"
                         " DockerLatentBuildSlave")
        if not image and not dockerfile:
            config.error("DockerLatentBuildSlave: You need to specify at least"
                         " an image name, or a dockerfile")

        self.volumes = []
        self.binds = {}
        self.followStartupLogs = followStartupLogs
        self.environment = (environment or {})
        self.links = (links or {})
        self.cap_add = (cap_add or [])
        self.privileged = privileged
        for volume_string in (volumes or []):
            try:
                volume, bind = volume_string.split(":", 1)
            except ValueError:
                config.error("Invalid volume definition for docker "
                             "%s. Skipping..." % volume_string)
            self.volumes.append(volume)

            ro = False
            if bind.endswith(':ro') or bind.endswith(':rw'):
                ro = bind[-2:] == 'ro'
                bind = bind[:-3]
            self.binds[volume] = {'bind': bind, 'ro': ro}

        # Set build_wait_timeout to 0 if not explicitely set: Starting a
        # container is almost immediate, we can affort doing so for each build.
        if 'build_wait_timeout' not in kwargs:
            kwargs['build_wait_timeout'] = 0
        AbstractLatentBuildSlave.__init__(self, name, password, **kwargs)

        self.image = image
        self.command = command or []
        self.dockerfile = dockerfile

        # Prepare the parameters for the Docker Client object.
        self.client_args = {'base_url': docker_host}
        if version is not None:
            self.client_args['version'] = version
        if tls is not None:
            self.client_args['tls'] = tls

    def start_instance(self, build):
        if self.instance is not None:
            raise ValueError('instance active')
        return threads.deferToThread(self._thd_start_instance)

    def _image_exists(self, client, name=None):
        if name is None:
            name = self.image
        # Make sure the image exists
        for image in client.images():
            for tag in image['RepoTags']:
                if ':' in name and tag == name:
                    return True
                if tag.startswith(name + ':'):
                    return True
        return False

    def _thd_start_instance(self):
        docker_client = client.Client(**self.client_args)

        found = False
        if self.image is not None:
            found = self._image_exists(docker_client)
            image = self.image
        else:
            image = '%s_%s_image' % (self.slavename, id(self))
        if (not found) and (self.dockerfile is not None):
            log.msg("Image '%s' not found, building it from scratch" %
                    image)
            for line in docker_client.build(fileobj=BytesIO(self.dockerfile.encode('utf-8')),
                                            tag=image):
                for streamline in handle_stream_line(line):
                    log.msg(streamline)

        if (not self._image_exists(docker_client, image)):
            log.msg("Image '%s' not found" % image)
            raise LatentBuildSlaveFailedToSubstantiate(
                'Image "%s" not found on docker host.' % image
            )

        instance = docker_client.create_container(
            image,
            self.command,
            name='%s_%s' % (self.slavename, id(self)),
            volumes=self.volumes,
            environment=self.environment
        )

        if instance.get('Id') is None:
            log.msg('Failed to create the container')
            raise LatentBuildSlaveFailedToSubstantiate(
                'Failed to start container'
            )
        shortid = instance['Id'][:6]
        log.msg('Container created, Id: %s...' % (shortid,))
        instance['image'] = image
        self.instance = instance
        docker_client.start(instance, binds=self.binds,
            links=self.links, cap_add=self.cap_add, privileged=self.privileged)
        log.msg('Container started')
        if self.followStartupLogs:
            logs = docker_client.attach(container=instance, stdout=True, stderr=True, stream=True)
            for line in logs:
                log.msg("docker VM %s: %s" % (shortid, line.strip()))
                if self.conn:
                    break
            del logs
        return [instance['Id'], self.image]

    def stop_instance(self, fast=False):
        if self.instance is None:
            # be gentle. Something may just be trying to alert us that an
            # instance never attached, and it's because, somehow, we never
            # started.
            return defer.succeed(None)
        instance = self.instance
        self.instance = None
        return threads.deferToThread(self._thd_stop_instance, instance, fast)

    def _thd_stop_instance(self, instance, fast):
        docker_client = client.Client(**self.client_args)
        log.msg('Stopping container %s...' % instance['Id'][:6])
        docker_client.stop(instance['Id'])
        if not fast:
            docker_client.wait(instance['Id'])
        docker_client.remove_container(instance['Id'], v=True, force=True)
        if self.image is None:
            try:
                docker_client.remove_image(image=instance['image'])
            except docker.errors.APIError as e:
                log.msg('Error while removing the image: %s', e)
### END CODE FROM master/buildbot/buildslave/docker.py @ master

import buildbot.buildslave
import os

master_name = os.environ['DOCKER_CONTAINER_NAME']

c['slaves'] = [
DockerLatentBuildSlave('builder-01', 'password123',
        docker_host='unix:///var/run/docker.sock',
        image='bb-builder',
        version='1.17',
        environment={"SLAVE_NAME":"builder-01", "SLAVE_PASS":"password123"},
        links={master_name: "buildbot"})
]

c['protocols'] = {'pb': {'port': 9989}}

####### CHANGESOURCES
from buildbot.plugins import changes

c['change_source'] = [changes.PBChangeSource(
    port=9999, user='buildbot', passwd='password123')]

####### SCHEDULERS
from buildbot.schedulers.basic import SingleBranchScheduler

c['schedulers'] = [SingleBranchScheduler(
    name="tester", treeStableTimer=None, builderNames=["builder"],
    branch="master",
)]

####### BUILDERS
from buildbot.process.factory import BuildFactory
from buildbot.steps.shell import ShellCommand
from buildbot.config import BuilderConfig

builderFact = BuildFactory()
builderFact.addStep(ShellCommand(command=["sleep", "10"]))

c['builders'] = [BuilderConfig(
    name="builder", slavenames=["builder-01"], factory=builderFact)]

####### STATUS TARGETS
from buildbot.status import html
from buildbot.status.web import authz, auth

authz_cfg=authz.Authz(
    auth=auth.BasicAuth([("pyflakes","pyflakes")]),
    gracefulShutdown = False,
    forceBuild = 'auth', # use this to test your slave once it is set up
    forceAllBuilds = 'auth',  # ..or this
    pingBuilder = False,
    stopBuild = False,
    stopAllBuilds = False,
    cancelPendingBuild = False,
)
c['status'] = [html.WebStatus(http_port=8010, authz=authz_cfg)]
