# -*- python -*-
# ex: set filetype=python:

# This is a sample buildmaster config file. It must be installed as
# 'master.cfg' in your buildmaster's base directory.
#
# Are all your projects built the same way?
# Do you yearn for a way to do simple static configuration?
# If so, try writing a function!
#
# Here's an example that
# - uses a function to make adding new projects easy
# - provides a regular builder and a smoke test builder per project
# - stores secrets in separate files
# - integrates with GitLab, and does smoke builds on merge requests
# - demonstrates access control using GitLab authentication
#
# To use this example with your own local instance of GitLab:
#
# 0. Set up local mirrors of the gnu hello and time projects, e.g.
#  for proj in hello time
#  do
#    git clone --mirror git@gitlab.com:GNU/$proj.git
#    cd $proj
#    git push --mirror git@gitlab.example.com:build/gnu-$proj.git
#    cd ..
#  done
#
# 1. Edit this file to replace example.com with your own domain,
#  and adjust worker name and password in c['workers'].
#
# 2. Create secrets.dir next to master.cfg:
#  mkdir secrets.dir
#
# 3. Tell GitLab to use webhooks to request builds.
#  Pick a random password for our webhook and save it as a secret, e.g.
#    echo "<string-webhook-token>" > secrets.dir/my-webhook-token
#    chmod 600 secrets.dir/*
#  (where <value> is just a placeholder for a value).
#  For each project to build, create a webhook in the GitLab UI at
#    project / Settings / Integrations / Add Webhook
#  with a URL of e.g.
#    http://buildbot.example.com:8010/change_hook/gitlab
#  the secret chosen above,
#  and with push and merge request triggers checked.
#
#  Then start the build master and worker.
#  Test the webhook by visiting
#    project / Settings / Integrations / Webhooks
#  and clicking 'test' on your webhook.
#  If something goes wrong, GitLab will show a red banner with the reason.
#  GitLab merge requests should now trigger buildbot builds.
#
# 4. Tell buildbot to report build status to GitLab.
#  Uncomment sections below marked
#    "CONFIGME: uncomment for gitlab status reporting"
#  Create a GitLab access token (so buildbot can send status to GitLab).
#  Pick a display name for your buildbot and save it as a secret, e.g.
#    echo "<string-buildbot-name>" > secrets.dir/my-buildbot-name
#    chmod 600 secrets.dir/*
#  Create an access token in the GitLab UI at
#    "User Settings / Access Tokens / Add a personal access token"
#  using that display name as the context, and save it as a secret, e.g.
#    echo "<string-gitlab-token>" > secrets.dir/my-gitlab-token
#    chmod 600 secrets.dir/*
#
#  Then restart the master.
#  GitLab merge requests should now show status of buildbot's builds.
#
# 5. Tell GitLab to accept authentication requests from buildbot.
#  Enter the URL of your buildbot gitlab hook, e.g.
#    http://buildbot.example.com:8010/change_hook/gitlab
#  into the GitLab UI at
#    "User Settings / Applications / Add New Application",
#  with scopes 'api' and 'openid' ticked,
#  and save the appid and secret it produces:
#    echo "<longhexstring-appid>" > secrets.dir/my-gitlab-appid
#    echo "<longhexstring-appsecret>" > secrets.dir/my-gitlab-appsecret
#    chmod 600 secrets.dir/*
# 6. Restrict buildbot web UI access to logged in GitLab users.
#  Uncomment sections below marked
#    "CONFIGME: uncomment for buildbot authentication"
#  and replace <mygroup> with a valid GitLab group.
#
#  Then restart the master.
#  Buildbot's web ui should now require you to be logged in to
#  that GitLab group before it shows you much or lets you force builds.

from buildbot.plugins import *
import os
import re


def makeFactoryNormal(repourl, branch):
    '''
    A Factory that builds, tests, and uploads incoming changesets.
    The branch argument is a default in case the changeset lacks one.
    '''
    # Adjust this factory to match your site's build steps.
    # This example uses the canonical gnu configure/make steps.
    # Adjust to match your site's build system.
    f = util.BuildFactory()
    f.addStep(steps.GitLab(repourl=repourl, branch=branch))
    f.addStep(steps.ShellCommand(haltOnFailure=True,
              command=["if test -x ./bootstrap; then ./bootstrap; fi"]))
    f.addStep(steps.ShellCommand(haltOnFailure=True, command=["./configure"]))
    f.addStep(steps.ShellCommand(haltOnFailure=True, command=["make"]))
    f.addStep(steps.ShellCommand(haltOnFailure=True, command=["make check"]))
    f.addStep(steps.ShellCommand(haltOnFailure=True, command=[": insert upload step here"]))
    return f


def makeFactorySmoke(repourl, branch):
    '''
    A Factory that just builds and tests incoming changesets.
    The branch argument is a default in case the changeset lacks one.
    '''
    f = util.BuildFactory()
    f.addStep(steps.GitLab(repourl=repourl, branch=branch))
    f.addStep(steps.ShellCommand(haltOnFailure=True,
              command=["if test -x ./bootstrap; then ./bootstrap; fi"]))
    f.addStep(steps.ShellCommand(haltOnFailure=True, command=["./configure"]))
    f.addStep(steps.ShellCommand(haltOnFailure=True, command=["make"]))
    f.addStep(steps.ShellCommand(haltOnFailure=True, command=["make check"]))
    return f


def repoUrlToName(repourl):
    '''
    Gets project name from the repourl, ignoring namespace.
    '''
    # Strip off everything before project name
    # FIXME: parse this more artfully to allow projects in folders
    name = re.sub(r'^.*/', '', repourl)
    # Strip off .git suffix, if present
    return re.sub(r'\.git$', '', name)


def addBuilder(repourl, branch, flavor, workernames):
    '''
    Add a builder for the given project and branch on the given workers.
    Give each a Force button.
    flavor must be 'smoke' or 'normal'.
    '''

    factory = None
    changehook_category = None
    if flavor is "normal":
        # Respond to push events with a normal build
        changehook_category = "push"
        factory = makeFactoryNormal(repourl, branch)
    elif flavor is "smoke":
        # Respond to merge request events with a smoke build
        changehook_category = "merge_request"
        factory = makeFactorySmoke(repourl, branch)
    else:
        raise ValueError("wanted 'normal' or 'smoke', got '%s'" % flavor)

    name = repoUrlToName(repourl)
    id = name + "-" + branch + "-" + flavor
    builder = util.BuilderConfig(name=id,
                                 workernames=workernames,
                                 factory=factory)
    c['builders'].append(builder)

    c['schedulers'].append(schedulers.SingleBranchScheduler(
        name=id,
        change_filter=util.ChangeFilter(
            project=name, branch=branch, category=changehook_category),
        treeStableTimer=None,
        builderNames=[builder.name]))
    c['schedulers'].append(schedulers.ForceScheduler(
        name=id + '-force',
        builderNames=[builder.name]))


# For parts of buildbot that don't support Secret interpolation yet.
# Once https://github.com/buildbot/buildbot/issues/4118 is fixed,
# use util.Secret(s) instead.
def dumbSecret(s):
    with open(os.path.join(secrets_dir, s), 'r') as myfile:
        return myfile.read().replace('\n', '')


# This is the dictionary that the buildmaster pays attention to. We also use
# a shorter alias to save typing.
c = BuildmasterConfig = {}

####### SECRETS
# Checking secrets into your master.cfg is insecure;
# best practice is to keep them elsewhere else.

# Place the secrets directory next to master.cfg:
this_dir = os.path.dirname(os.path.abspath(__file__))
secrets_dir = os.path.join(this_dir, 'secrets.dir')
c['secretsProviders'] = [secrets.SecretInAFile(dirname=secrets_dir)]

####### WORKERS

# The 'workers' list defines the set of recognized workers.
# Each element is a Worker object, with a unique worker name and password.
# The same worker name and password must be configured on the worker.
# CONFIGME
c['workers'] = [
  worker.Worker("buildbot-worker",  "buildbot-pass"),
]
workernames = [x.name for x in c['workers']]

# 'protocols' contains information about protocols which master will use for
# communicating with workers. You must define at least a 'port' option;
# the master will listen on that port for connections from workers.
# 'port' must match the value configured into the workers (with their
# --master option)
c['protocols'] = {'pb': {'port': 9989}}

####### CHANGESOURCES

# the 'change_source' setting tells the buildmaster how it should find out
# about source code changes.

c['change_source'] = []

####### SCHEDULERS AND BUILDERS

# The Schedulers decide how to react to incoming changes.
c['schedulers'] = []
# The 'builders' list defines the Builders, which tell Buildbot how to
# perform a build: what steps, and which workers can execute them.
# Note that any particular build will only take place on one worker.
c['builders'] = []

# Call addBuilder for each similar project you want to build.
# It adds a builder with both normal and force schedulers.
# Note: urls must start with git@ and end with .git
addBuilder('git@gitlab.example.com:build/gnu-hello.git',
           branch='master', flavor='normal', workernames=workernames)
addBuilder('git@gitlab.example.com:build/gnu-hello.git',
           branch='master', flavor='smoke',  workernames=workernames)
addBuilder('git@gitlab.example.com:build/gnu-time.git',
           branch='master', flavor='normal', workernames=workernames)
addBuilder('git@gitlab.example.com:build/gnu-time.git',
           branch='master', flavor='smoke',  workernames=workernames)

####### BUILDBOT SERVICES

# 'services' is a list of BuildbotService items like reporter targets. The
# status of each build will be pushed to these targets. buildbot/reporters/*.py
# has a variety to choose from, like IRC bots.

c['services'] = []

## CONFIGME: uncomment for gitlab status reporting
## Report build status back to GitLab UI
#c['services'].append(reporters.GitLabStatusPush(
#    token=util.Secret('my-gitlab-token'),
#    context=util.Secret('my-buildbot-name'),
#    baseURL='https://gitlab.example.com',
#    verbose=True))

####### PROJECT IDENTITY

# the 'title' string will appear at the top of this buildbot installation's
# home pages (linked to the 'titleURL').

c['title'] = "Gnu Hello GitLab"
c['titleURL'] = "https://gitlab.example.com/build/"

# the 'buildbotURL' string should point to the location where the buildbot's
# internal web server is visible. This typically uses the port number set in
# the 'www' entry below, but with an externally-visible host name which the
# buildbot cannot figure out without some help.

c['buildbotURL'] = "http://buildbot.example.com:8010/"

# CONFIGME: uncomment for buildbot authentication
## This example tries to show nothing to anonymous users.
#authz = util.Authz(
#  allowRules=[
#    util.AnyEndpointMatcher(role="platform"),
#    util.AnyEndpointMatcher(role="xxend-of-listxx", defaultDeny=True),
#  ],
#  roleMatchers=[
#    util.RolesFromGroups()
#  ]
#)

# minimalistic config to activate new web UI
c['www'] = dict(
    port=8010,
    ## CONFIGME: uncomment for buildbot authentication
    #auth=util.GitLabAuth("https://gitlab.example.com",
    #                     dumbSecret('my-gitlab-appid'),
    #                     dumbSecret('my-gitlab-appsecret')),
    #authz=authz,
    change_hook_dialects=dict(
       gitlab={
           'secret': dumbSecret('my-webhook-token')
       },
    ),
    plugins=dict(waterfall_view={}, console_view={}, grid_view={}))


# Let buildbot developers know you're using gitlab support :-)
c['buildbotNetUsageData'] = 'basic'

####### DB URL

c['db'] = {
    # This specifies what database buildbot uses to store its state.
    # You can leave this at its default for all but the largest installations.
    'db_url': "sqlite:///state.sqlite",
}
