Charm: oneiric/buildbot-master
Revision: 1
Hook: local.py
# Copyright 2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Shared functions for the buildbot master and slave"""
__metaclass__ = type
__all__ = [
'buildbot_reconfig',
'buildslave_start',
'buildslave_stop',
'config_json',
'create_slave',
'fetch_history',
'generate_string',
'get_bucket',
'get_key',
'get_wrapper_cfg_path',
'HTTP_PORT_PROTOCOL',
'put_history',
'slave_json',
'WRAPPER_FN',
]
import os
import re
import subprocess
import uuid
from shelltoolbox import (
cd,
run,
Serializer,
su,
)
from helpers import (
get_config,
log,
)
HTTP_PORT_PROTOCOL = '8010/TCP'
WRAPPER_FN = 'wrapper.cfg'
def _get_buildbot_dir():
config = get_config()
return config.get('installdir')
def _get_tac_filename(buildbot_dir):
return os.path.join(buildbot_dir, 'buildbot.tac')
def get_wrapper_cfg_path():
return os.path.join(
_get_buildbot_dir(), WRAPPER_FN)
def generate_string(prefix=""):
"""Generate a unique string and return it."""
return prefix + uuid.uuid4().hex
def buildbot_create(buildbot_dir):
"""Create a buildbot instance in `buildbot_dir`."""
if not os.path.exists(_get_tac_filename(buildbot_dir)):
with su('buildbot'):
return run(
'buildbot', 'create-master', '--config', WRAPPER_FN,
buildbot_dir)
def buildbot_running(buildbot_dir):
pidfile = os.path.join(buildbot_dir, 'twistd.pid')
if os.path.exists(pidfile):
buildbot_pid = open(pidfile).read().strip()
try:
# Is buildbot running?
run('kill', '-0', buildbot_pid)
except subprocess.CalledProcessError:
return False
return True
return False
def buildbot_stop():
buildbot_dir = _get_buildbot_dir()
if buildbot_running(buildbot_dir):
# Buildbot is running, stop it.
log('--> Stopping buildbot')
with su('buildbot'):
run('buildbot', 'stop', buildbot_dir)
log('<-- Stopping buildbot')
def buildbot_reconfig():
buildbot_dir = _get_buildbot_dir()
pidfile = os.path.join(buildbot_dir, 'twistd.pid')
running = False
if os.path.exists(pidfile):
buildbot_pid = open(pidfile).read().strip()
try:
# Is buildbot running?
run('kill', '-0', buildbot_pid)
except subprocess.CalledProcessError:
# Buildbot isn't running, so no need to reconfigure it.
pass
else:
# Buildbot is running, reconfigure it.
log('--> Reconfiguring buildbot')
with su('buildbot'):
# Reconfig is broken in 0.8.3 (Oneiric), so we can't do this:
# run('buildbot', 'reconfig', buildbot_dir)
run('buildbot', 'stop', buildbot_dir)
run('buildbot', 'start', buildbot_dir)
log('<-- Reconfiguring buildbot')
running = True
if not running:
# Buildbot isn't running so start afresh.
if os.path.exists(os.path.join(buildbot_dir, 'master.cfg')):
log('--> Starting buildbot')
with su('buildbot'):
run('buildbot', 'start', buildbot_dir)
log('<-- Starting buildbot')
def buildslave_stop(buildbot_dir=None):
if buildbot_dir is None:
buildbot_dir = _get_buildbot_dir()
with su('buildbot'):
# In recent versions of buildbot, it provides a "buildslave" binary.
# Earlier versions have the "buildbot" binary only, providing the
# same behavior.
try:
exit_code = subprocess.call(['buildslave', 'stop', buildbot_dir])
except OSError:
exit_code = subprocess.call(['buildbot', 'stop', buildbot_dir])
tac_file = _get_tac_filename(buildbot_dir)
if os.path.exists(tac_file):
os.remove(tac_file)
return exit_code
def buildslave_start(buildbot_dir=None):
if buildbot_dir is None:
buildbot_dir = _get_buildbot_dir()
with su('buildbot'):
# In recent versions of buildbot, it provides a "buildslave" binary.
# Earlier versions have the "buildbot" binary only, providing the
# same behavior.
try:
return subprocess.call(['buildslave', 'start', buildbot_dir])
except OSError:
return subprocess.call(['buildbot', 'start', buildbot_dir])
def create_slave(name, passwd, host='localhost', buildbot_dir=None):
if buildbot_dir is None:
buildbot_dir = _get_buildbot_dir()
with su('buildbot'):
if not os.path.exists(buildbot_dir):
os.makedirs(buildbot_dir)
# In recent versions of buildbot, it provides a "buildslave" binary.
# Earlier versions have the "buildbot" binary only, providing the
# same behavior.
try:
return subprocess.call(
['buildslave',
'create-slave', buildbot_dir, host, name, passwd])
except OSError:
return subprocess.call(
['buildbot',
'create-slave', buildbot_dir, host + ":9989", name, passwd])
slave_json = Serializer('/tmp/slave_info.json')
config_json = Serializer('/tmp/config.json')
def get_bucket(config, bucket_name=None):
"""Return an S3 bucket or None."""
# Late import to ensure python-boto package has been installed.
import boto
access_key = config.get('access-key')
secret_key = config.get('secret-key')
bucket = None
if access_key and secret_key:
if bucket_name is None:
bucket_name = str(access_key + '-buildbot-history').lower()
conn = boto.connect_s3(access_key, secret_key)
bucket = conn.create_bucket(bucket_name)
log("Using bucket: " + bucket.name)
return bucket
def get_key(bucket):
"""Return an S3 key for the bucket."""
# Late import to ensure python-boto package has been installed.
from boto.s3.key import Key
key = Key(bucket)
value = os.environ['JUJU_UNIT_NAME']
key.key = value
log("Using key: " + key.key)
return key
VERSION_TO_STORE = {
'0.7': "*/builder",
'0.8': "state.sqlite",
}
def get_buildbot_version():
"""Get the major version (x.y) of buildbot and return as a string.
Return None if the output from buildbot cannot be parsed.
"""
version = None
output = run('buildbot', '--version')
match = re.search('Buildbot version: (\d+\.\d+)(\.\d+)*\n', output)
if match and len(match.groups()) > 0:
version = match.group(1)
return version
def put_history(config):
"""Put the buildbot history to an external store, if set up."""
log("put_history called")
bucket_name = config.get('bucket-name')
bucket = get_bucket(config, bucket_name)
success = False
if bucket:
key = get_key(bucket)
target = '/tmp/history-put.tgz'
version = get_buildbot_version()
store_pattern = VERSION_TO_STORE.get(version)
assert store_pattern is not None, (
"Buildbot version not supported: {}".format(version))
with cd(config['installdir']):
with su('buildbot'):
try:
run('tar', 'czf', target, store_pattern)
key.set_contents_from_filename(target)
# If would be natural to just log the success here, but we
# are su-ed to the buildbot user and that causes permission
# problems, so instead set a flag.
success = True
except subprocess.CalledProcessError as e:
print e
print e.output
raise
os.unlink(target)
else:
log("Bucket not found: " + bucket_name)
if success:
log("History stored to S3.")
def fetch_history(config, diff):
"""Fetch the buildbot history from an external store."""
# Currently only S3 is supported.
restart_required = False
log("fetching history")
if 'secret-key' not in diff.added_or_changed:
log("skipping fetch of history")
return restart_required
bucket_name = config.get('bucket-name')
bucket = get_bucket(config, bucket_name)
if bucket:
key = get_key(bucket)
if key.exists():
target = '/tmp/history-fetched.tgz'
key.get_contents_to_filename(target)
with cd(config['installdir']):
with su('buildbot'):
try:
run('tar', 'xzf', target)
except subprocess.CalledProcessError as e:
print e
print e.output
raise
os.unlink(target)
log("History fetched from S3.")
restart_required = True
else:
log("Key does not exist: " + key.key)
else:
log("Bucket not found: " + bucket_name)
return restart_required