Get build preparation working for bitbucket and do a lot of code cleanup around this process across all the triggers. Note: tests are not yet updated.

This commit is contained in:
Joseph Schorr 2015-04-29 17:04:52 -04:00
parent 6479f8ddc9
commit d5c70878c5
6 changed files with 432 additions and 226 deletions

View file

@ -10,7 +10,7 @@ from endpoints.api import (RepositoryParamResource, parse_args, query_param, nic
require_repo_read, require_repo_write, validate_json_request, require_repo_read, require_repo_write, validate_json_request,
ApiResource, internal_only, format_date, api, Unauthorized, NotFound, ApiResource, internal_only, format_date, api, Unauthorized, NotFound,
path_param, InvalidRequest, require_repo_admin) path_param, InvalidRequest, require_repo_admin)
from endpoints.common import start_build from endpoints.building import start_build, PreparedBuild
from endpoints.trigger import BuildTriggerHandler from endpoints.trigger import BuildTriggerHandler
from data import model, database from data import model, database
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
@ -191,8 +191,8 @@ class RepositoryBuildList(RepositoryParamResource):
raise Unauthorized() raise Unauthorized()
# Check if the dockerfile resource has already been used. If so, then it # Check if the dockerfile resource has already been used. If so, then it
# can only be reused if the user has access to the repository for which it # can only be reused if the user has access to the repository in which the
# was used. # dockerfile was previously built.
associated_repository = model.get_repository_for_resource(dockerfile_id) associated_repository = model.get_repository_for_resource(dockerfile_id)
if associated_repository: if associated_repository:
if not ModifyRepositoryPermission(associated_repository.namespace_user.username, if not ModifyRepositoryPermission(associated_repository.namespace_user.username,
@ -201,11 +201,16 @@ class RepositoryBuildList(RepositoryParamResource):
# Start the build. # Start the build.
repo = model.get_repository(namespace, repository) repo = model.get_repository(namespace, repository)
display_name = user_files.get_file_checksum(dockerfile_id)
build_request = start_build(repo, dockerfile_id, tags, display_name, subdir, True, prepared = PreparedBuild()
pull_robot_name=pull_robot_name) prepared.build_name = user_files.get_file_checksum(dockerfile_id)
prepared.dockerfile_id = dockerfile_id
prepared.tags = tags
prepared.subdirectory = subdir
prepared.is_manual = True
prepared.metadata = {}
build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
resp = build_status_view(build_request, can_write=True) resp = build_status_view(build_request, can_write=True)
repo_string = '%s/%s' % (namespace, repository) repo_string = '%s/%s' % (namespace, repository)
headers = { headers = {

View file

@ -12,7 +12,7 @@ from endpoints.api import (RepositoryParamResource, nickname, resource, require_
path_param) path_param)
from endpoints.api.build import (build_status_view, trigger_view, RepositoryBuildStatus, from endpoints.api.build import (build_status_view, trigger_view, RepositoryBuildStatus,
get_trigger_config) get_trigger_config)
from endpoints.common import start_build from endpoints.building import start_build
from endpoints.trigger import (BuildTriggerHandler, TriggerDeactivationException, from endpoints.trigger import (BuildTriggerHandler, TriggerDeactivationException,
TriggerActivationException, EmptyRepositoryException, TriggerActivationException, EmptyRepositoryException,
RepositoryReadException, TriggerStartException) RepositoryReadException, TriggerStartException)
@ -423,16 +423,12 @@ class ActivateBuildTrigger(RepositoryParamResource):
raise InvalidRequest('Trigger is not active.') raise InvalidRequest('Trigger is not active.')
try: try:
run_parameters = request.get_json()
specs = handler.manual_start(run_parameters=run_parameters)
dockerfile_id, tags, name, subdir, metadata = specs
repo = model.get_repository(namespace, repository) repo = model.get_repository(namespace, repository)
pull_robot_name = model.get_pull_robot_name(trigger) pull_robot_name = model.get_pull_robot_name(trigger)
build_request = start_build(repo, dockerfile_id, tags, name, subdir, True, run_parameters = request.get_json()
trigger=trigger, pull_robot_name=pull_robot_name, prepared = handler.manual_start(run_parameters=run_parameters)
trigger_metadata=metadata) build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
except TriggerStartException as tse: except TriggerStartException as tse:
raise InvalidRequest(tse.message) raise InvalidRequest(tse.message)

186
endpoints/building.py Normal file
View file

@ -0,0 +1,186 @@
import logging
import json
from app import app, dockerfile_build_queue
from data import model
from data.database import db
from auth.auth_context import get_authenticated_user
from endpoints.notificationhelper import spawn_notification
from flask import request
logger = logging.getLogger(__name__)
def start_build(repository, prepared_build, pull_robot_name=None):
host = app.config['SERVER_HOSTNAME']
repo_path = '%s/%s/%s' % (host, repository.namespace_user.username, repository.name)
token = model.create_access_token(repository, 'write', kind='build-worker',
friendly_name='Repository Build Token')
logger.debug('Creating build %s with repo %s tags %s',
prepared_build.build_name, repo_path, prepared_build.tags)
job_config = {
'docker_tags': prepared_build.tags,
'registry': host,
'build_subdir': prepared_build.subdirectory,
'trigger_metadata': prepared_build.metadata or {},
'is_manual': prepared_build.is_manual,
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
}
with app.config['DB_TRANSACTION_FACTORY'](db):
build_request = model.create_repository_build(repository, token, job_config,
prepared_build.dockerfile_id,
prepared_build.build_name,
prepared_build.trigger,
pull_robot_name=pull_robot_name)
json_data = json.dumps({
'build_uuid': build_request.uuid,
'pull_credentials': model.get_pull_credentials(pull_robot_name) if pull_robot_name else None
})
queue_id = dockerfile_build_queue.put([repository.namespace_user.username, repository.name],
json_data,
retries_remaining=3)
build_request.queue_id = queue_id
build_request.save()
# Add the build to the repo's log and spawn the build_queued notification.
event_log_metadata = {
'build_uuid': build_request.uuid,
'docker_tags': prepared_build.tags,
'repo': repository.name,
'namespace': repository.namespace_user.username,
'is_manual': prepared_build.is_manual,
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
}
if prepared_build.trigger:
event_log_metadata['trigger_id'] = prepared_build.trigger.uuid
event_log_metadata['trigger_kind'] = prepared_build.trigger.service.name
model.log_action('build_dockerfile', repository.namespace_user.username, ip=request.remote_addr,
metadata=event_log_metadata, repository=repository)
spawn_notification(repository, 'build_queued', event_log_metadata,
subpage='build?current=%s' % build_request.uuid,
pathargs=['build', build_request.uuid])
return build_request
class PreparedBuild(object):
""" Class which holds all the information about a prepared build. The build queuing service
will use this result to actually invoke the build.
"""
def __init__(self, trigger=None):
self._dockerfile_id = None
self._tags = None
self._build_name = None
self._subdirectory = None
self._metadata = None
self._trigger = trigger
self._is_manual = None
@staticmethod
def get_display_name(sha):
return sha[0:7]
def tags_from_ref(self, ref, default_branch='master'):
branch = ref.split('/')[-1]
tags = {branch}
if branch == default_branch:
tags.add('latest')
self.tags = tags
def name_from_sha(self, sha):
self.build_name = PreparedBuild.get_display_name(sha)
@property
def is_manual(self):
if self._is_manual is None:
raise Exception('Property is_manual not set')
return self._is_manual
@is_manual.setter
def is_manual(self, value):
if self._is_manual is not None:
raise Exception('Property is_manual already set')
self._is_manual = value
@property
def trigger(self):
return self._trigger
@property
def dockerfile_id(self):
return self._dockerfile_id
@dockerfile_id.setter
def dockerfile_id(self, value):
if self._dockerfile_id:
raise Exception('Property dockerfile_id already set')
self._dockerfile_id = value
@property
def tags(self):
if not self._tags:
raise Exception('Missing property tags')
return self._tags
@tags.setter
def tags(self, value):
if self._tags:
raise Exception('Property tags already set')
self._tags = list(value)
@property
def build_name(self):
if not self._build_name:
raise Exception('Missing property build_name')
return self._build_name
@build_name.setter
def build_name(self, value):
if self._build_name:
raise Exception('Property build_name already set')
self._build_name = value
@property
def subdirectory(self):
if self._subdirectory is None:
raise Exception('Missing property subdirectory')
return self._subdirectory
@subdirectory.setter
def subdirectory(self, value):
if self._subdirectory:
raise Exception('Property subdirectory already set')
self._subdirectory = value
@property
def metadata(self):
if self._metadata is None:
raise Exception('Missing property metadata')
return self._metadata
@metadata.setter
def metadata(self, value):
if self._metadata:
raise Exception('Property metadata already set')
self._metadata = value

View file

@ -1,5 +1,4 @@
import logging import logging
import urlparse
import json import json
import string import string
import datetime import datetime
@ -14,18 +13,15 @@ from flask.ext.principal import identity_changed
from random import SystemRandom from random import SystemRandom
from data import model from data import model
from data.database import db from app import app, oauth_apps, LoginWrappedDBUser
from app import app, oauth_apps, dockerfile_build_queue, LoginWrappedDBUser
from auth.permissions import QuayDeferredPermissionUser from auth.permissions import QuayDeferredPermissionUser
from auth import scopes from auth import scopes
from auth.auth_context import get_authenticated_user
from endpoints.api.discovery import swagger_route_data from endpoints.api.discovery import swagger_route_data
from werkzeug.routing import BaseConverter from werkzeug.routing import BaseConverter
from functools import wraps from functools import wraps
from config import getFrontendVisibleConfig from config import getFrontendVisibleConfig
from external_libraries import get_external_javascript, get_external_css from external_libraries import get_external_javascript, get_external_css
from endpoints.notificationhelper import spawn_notification
import features import features
@ -210,75 +206,3 @@ def check_repository_usage(user_or_org, plan_found):
else: else:
model.delete_notifications_by_kind(user_or_org, 'over_private_usage') model.delete_notifications_by_kind(user_or_org, 'over_private_usage')
def start_build(repository, dockerfile_id, tags, build_name, subdir, manual,
trigger=None, pull_robot_name=None, trigger_metadata=None):
host = urlparse.urlparse(request.url).netloc
repo_path = '%s/%s/%s' % (host, repository.namespace_user.username, repository.name)
token = model.create_access_token(repository, 'write', kind='build-worker',
friendly_name='Repository Build Token')
logger.debug('Creating build %s with repo %s tags %s and dockerfile_id %s',
build_name, repo_path, tags, dockerfile_id)
job_config = {
'docker_tags': tags,
'registry': host,
'build_subdir': subdir,
'trigger_metadata': trigger_metadata or {},
'is_manual': manual,
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
}
with app.config['DB_TRANSACTION_FACTORY'](db):
build_request = model.create_repository_build(repository, token, job_config,
dockerfile_id, build_name,
trigger, pull_robot_name=pull_robot_name)
json_data = json.dumps({
'build_uuid': build_request.uuid,
'pull_credentials': model.get_pull_credentials(pull_robot_name) if pull_robot_name else None
})
queue_id = dockerfile_build_queue.put([repository.namespace_user.username, repository.name],
json_data,
retries_remaining=3)
build_request.queue_id = queue_id
build_request.save()
# Add the build to the repo's log.
metadata = {
'repo': repository.name,
'namespace': repository.namespace_user.username,
'fileid': dockerfile_id,
'is_manual': manual,
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
}
if trigger:
metadata['trigger_id'] = trigger.uuid
metadata['config'] = json.loads(trigger.config)
metadata['service'] = trigger.service.name
model.log_action('build_dockerfile', repository.namespace_user.username, ip=request.remote_addr,
metadata=metadata, repository=repository)
# Add notifications for the build queue.
logger.debug('Adding notifications for repository')
event_data = {
'build_id': build_request.uuid,
'build_name': build_name,
'docker_tags': tags,
'is_manual': manual,
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
}
if trigger:
event_data['trigger_id'] = trigger.uuid
event_data['trigger_kind'] = trigger.service.name
spawn_notification(repository, 'build_queued', event_data,
subpage='build?current=%s' % build_request.uuid,
pathargs=['build', build_request.uuid])
return build_request

View file

@ -6,6 +6,7 @@ import base64
import re import re
import json import json
from endpoints.building import PreparedBuild
from github import Github, UnknownObjectException, GithubException from github import Github, UnknownObjectException, GithubException
from bitbucket import BitBucket from bitbucket import BitBucket
from tempfile import SpooledTemporaryFile from tempfile import SpooledTemporaryFile
@ -27,9 +28,6 @@ TARBALL_MIME = 'application/gzip'
CHUNK_SIZE = 512 * 1024 CHUNK_SIZE = 512 * 1024
def should_skip_commit(message):
return '[skip build]' in message or '[build skip]' in message
class InvalidPayloadException(Exception): class InvalidPayloadException(Exception):
pass pass
@ -64,6 +62,42 @@ class TriggerProviderException(Exception):
pass pass
def find_matching_branches(config, branches):
if 'branchtag_regex' in config:
try:
regex = re.compile(config['branchtag_regex'])
return [branch for branch in branches
if matches_ref('refs/heads/' + branch, regex)]
except:
pass
return branches
def raise_if_skipped(config, ref):
""" Raises a SkipRequestException if the given ref should be skipped. """
if 'branchtag_regex' in config:
try:
regex = re.compile(config['branchtag_regex'])
except:
regex = re.compile('.*')
if not matches_ref(ref, regex):
raise SkipRequestException()
def matches_ref(ref, regex):
match_string = ref.split('/', 1)[1]
if not regex:
return False
m = regex.match(match_string)
if not m:
return False
return len(m.group(0)) == len(match_string)
def should_skip_commit(message):
return '[skip build]' in message or '[build skip]' in message
def raise_unsupported(): def raise_unsupported():
raise io.UnsupportedOperation raise io.UnsupportedOperation
@ -113,8 +147,7 @@ class BuildTriggerHandler(object):
def handle_trigger_request(self): def handle_trigger_request(self):
""" """
Transform the incoming request data into a set of actions. Returns a tuple Transform the incoming request data into a set of actions. Returns a PreparedBuild.
of usefiles resource id, docker tags, build name, and resource subdir.
""" """
raise NotImplementedError raise NotImplementedError
@ -142,7 +175,7 @@ class BuildTriggerHandler(object):
def manual_start(self, run_parameters=None): def manual_start(self, run_parameters=None):
""" """
Manually creates a repository build for this trigger. Manually creates a repository build for this trigger. Returns a PreparedBuild.
""" """
raise NotImplementedError raise NotImplementedError
@ -166,7 +199,7 @@ class BuildTriggerHandler(object):
if subc.service_name() == trigger.service.name: if subc.service_name() == trigger.service.name:
return subc(trigger, override_config) return subc(trigger, override_config)
raise InvalidServiceException('Unable to find service: %s' % service) raise InvalidServiceException('Unable to find service: %s' % trigger.service.name)
def put_config_key(self, key, value): def put_config_key(self, key, value):
""" Updates a config key in the trigger, saving it to the DB. """ """ Updates a config key in the trigger, saving it to the DB. """
@ -272,7 +305,6 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
config['hook_id'] = data['id'] config['hook_id'] = data['id']
return config, {'private_key': private_key} return config, {'private_key': private_key}
def deactivate(self): def deactivate(self):
config = self.config config = self.config
repository = self._get_repository_client() repository = self._get_repository_client()
@ -294,7 +326,6 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
return config return config
def list_build_sources(self): def list_build_sources(self):
bitbucket_client = self._get_authorized_client() bitbucket_client = self._get_authorized_client()
(result, data, err_msg) = bitbucket_client.get_visible_repositories() (result, data, err_msg) = bitbucket_client.get_visible_repositories()
@ -321,12 +352,16 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
return namespaces.values() return namespaces.values()
def list_build_subdirs(self): def list_build_subdirs(self):
config = self.config
repository = self._get_repository_client() repository = self._get_repository_client()
(result, data, err_msg) = repository.get_path_contents('', revision='master')
# Find the first matching branch.
repo_branches = self.list_field_values('branch_name') or []
branches = find_matching_branches(config, repo_branches)
(result, data, err_msg) = repository.get_path_contents('', revision=branches[0])
if not result: if not result:
raise RepositoryReadException(err_msg) raise RepositoryReadException(err_msg)
files = set([f['path'] for f in data['files']]) files = set([f['path'] for f in data['files']])
if 'Dockerfile' in files: if 'Dockerfile' in files:
return ['/'] return ['/']
@ -392,29 +427,114 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
return None return None
def _prepare_build(self, commit_sha, ref, is_manual):
def handle_trigger_request(self, request):
return
def manual_start(self, run_parameters=None):
config = self.config config = self.config
repository = self._get_repository_client() repository = self._get_repository_client()
source = config['build_source'] # Lookup the default branch associated with the repository. We use this when building
run_parameters = run_parameters or {} # the tags.
default_branch = ''
# Lookup the branch to build.
master_branch = 'master'
(result, data, _) = repository.get_main_branch() (result, data, _) = repository.get_main_branch()
if result: if result:
master_branch = data['name'] default_branch = data['name']
branch_name = run_parameters.get('branch_name') or master_branch # Lookup the commit sha.
(result, data, _) = repository.changesets().get(commit_sha)
if not result:
raise TriggerStartException('Could not lookup commit SHA')
# Find the SHA for the branch. namespace = repository.namespace
# TODO name = repository.repository_name
return None
commit_info = {
'url': 'https://bitbucket.org/%s/%s/commits/%s' % (namespace, name, commit_sha),
'message': data['message'],
'date': data['timestamp']
}
# Try to lookup the author by email address. The raw_author field (if it exists) is returned
# in the form: "Joseph Schorr <joseph.schorr@coreos.com>"
if data.get('raw_author'):
match = re.compile(r'.*<(.+)>').match(data['raw_author'])
if match:
email_address = match.group(1)
bitbucket_client = self._get_authorized_client()
(result, data, _) = bitbucket_client.accounts().get_profile(email_address)
if result:
commit_info['author'] = {
'username': data['user']['username'],
'url': 'https://bitbucket.org/%s/' % data['user']['username'],
'avatar_url': data['user']['avatar']
}
metadata = {
'commit_sha': commit_sha,
'ref': ref,
'default_branch': default_branch,
'git_url': 'git@bitbucket.org:%s/%s.git' % (namespace, name),
'commit_info': commit_info
}
prepared = PreparedBuild(self.trigger)
prepared.tags_from_ref(ref, default_branch)
prepared.name_from_sha(commit_sha)
prepared.subdirectory = config['subdir']
prepared.metadata = metadata
prepared.is_manual = is_manual
return prepared
def handle_trigger_request(self, request):
# Parse the JSON payload.
payload_json = request.form.get('payload')
if not payload_json:
raise SkipRequestException()
try:
payload = json.loads(payload_json)
except ValueError:
raise SkipRequestException()
logger.debug('BitBucket trigger payload %s', payload)
# Make sure we have a commit in the payload.
if not payload.get('commits'):
raise SkipRequestException()
# Check if this build should be skipped by commit message.
commit = payload['commits'][0]
commit_message = commit['message']
if should_skip_commit(commit_message):
raise SkipRequestException()
# Check to see if this build should be skipped by ref.
ref = 'refs/heads/' + commit['branch'] if commit.get('branch') else 'refs/tags/' + commit['tag']
raise_if_skipped(self.config, ref)
commit_sha = commit['node']
return self._prepare_build(commit_sha, ref, False)
def manual_start(self, run_parameters=None):
run_parameters = run_parameters or {}
repository = self._get_repository_client()
# Find the branch to build.
branch_name = run_parameters.get('branch_name')
(result, data, _) = repository.get_main_branch()
if result:
branch_name = data['name'] or branch_name
# Lookup the commit SHA for the branch.
(result, data, _) = repository.get_branches()
if not result or not branch_name in data:
raise TriggerStartException('Could not find branch commit SHA')
commit_sha = data[branch_name]['node']
ref = 'refs/heads/%s' % (branch_name)
return self._prepare_build(commit_sha, ref, True)
class GithubBuildTrigger(BuildTriggerHandler): class GithubBuildTrigger(BuildTriggerHandler):
@ -543,18 +663,6 @@ class GithubBuildTrigger(BuildTriggerHandler):
return repos_by_org return repos_by_org
@staticmethod
def matches_ref(ref, regex):
match_string = ref.split('/', 1)[1]
if not regex:
return False
m = regex.match(match_string)
if not m:
return False
return len(m.group(0)) == len(match_string)
def list_build_subdirs(self): def list_build_subdirs(self):
config = self.config config = self.config
gh_client = self._get_client() gh_client = self._get_client()
@ -564,15 +672,8 @@ class GithubBuildTrigger(BuildTriggerHandler):
repo = gh_client.get_repo(source) repo = gh_client.get_repo(source)
# Find the first matching branch. # Find the first matching branch.
branches = None repo_branches = self.list_field_values('branch_name') or []
if 'branchtag_regex' in config: branches = find_matching_branches(config, repo_branches)
try:
regex = re.compile(config['branchtag_regex'])
branches = [branch.name for branch in repo.get_branches()
if GithubBuildTrigger.matches_ref('refs/heads/' + branch.name, regex)]
except:
pass
branches = branches or [repo.default_branch or 'master'] branches = branches or [repo.default_branch or 'master']
default_commit = repo.get_branch(branches[0]).commit default_commit = repo.get_branch(branches[0]).commit
commit_tree = repo.get_git_tree(default_commit.sha, recursive=True) commit_tree = repo.get_git_tree(default_commit.sha, recursive=True)
@ -691,37 +792,31 @@ class GithubBuildTrigger(BuildTriggerHandler):
return tarball_subdir, dockerfile_id return tarball_subdir, dockerfile_id
@staticmethod def _prepare_build(self, repo, ref, commit_sha, is_manual):
def _prepare_build(trigger, config, repo, commit_sha, build_name, ref, git_url): config = self.config
repo_subdir = config['subdir'] prepared = PreparedBuild(self.trigger)
joined_subdir = repo_subdir
dockerfile_id = None
if trigger.private_key is None: # If the trigger isn't using git, prepare the buildpack.
# If the trigger isn't using git, prepare the buildpack. if self.trigger.private_key is None:
tarball_subdir, dockerfile_id = GithubBuildTrigger._prepare_tarball(repo, commit_sha) tarball_subdir, dockerfile_id = GithubBuildTrigger._prepare_tarball(repo, commit_sha)
logger.debug('Successfully prepared job')
# Join provided subdir with the tarball subdir. prepared.subdirectory = os.path.join(tarball_subdir, config['subdir'])
joined_subdir = os.path.join(tarball_subdir, repo_subdir) prepared.dockerfile_id = dockerfile_id
else:
prepared.subdirectory = config['subdir']
logger.debug('Final subdir: %s', joined_subdir) # Set the name.
prepared.name_from_sha(commit_sha)
# compute the tag(s) # Set the tag(s).
branch = ref.split('/')[-1] prepared.tags_from_ref(ref, repo.default_branch)
tags = {branch}
if branch == repo.default_branch: # Build and set the metadata.
tags.add('latest')
logger.debug('Pushing to tags: %s', tags)
# compute the metadata
metadata = { metadata = {
'commit_sha': commit_sha, 'commit_sha': commit_sha,
'ref': ref, 'ref': ref,
'default_branch': repo.default_branch, 'default_branch': repo.default_branch,
'git_url': git_url, 'git_url': repo.git_url,
} }
# add the commit info. # add the commit info.
@ -729,71 +824,63 @@ class GithubBuildTrigger(BuildTriggerHandler):
if commit_info is not None: if commit_info is not None:
metadata['commit_info'] = commit_info metadata['commit_info'] = commit_info
return dockerfile_id, list(tags), build_name, joined_subdir, metadata prepared.metadata = metadata
prepared.is_manual = is_manual
return prepared
@staticmethod
def get_display_name(sha):
return sha[0:7]
def handle_trigger_request(self, request): def handle_trigger_request(self, request):
# Check the payload to see if we should skip it based on the lack of a head_commit.
payload = request.get_json() payload = request.get_json()
if not payload or payload.get('head_commit') is None: if not payload or payload.get('head_commit') is None:
raise SkipRequestException() raise SkipRequestException()
# This is for GitHub's probing/testing.
if 'zen' in payload: if 'zen' in payload:
raise ValidationRequestException() raise ValidationRequestException()
logger.debug('Payload %s', payload) logger.debug('GitHub trigger payload %s', payload)
ref = payload['ref'] ref = payload['ref']
commit_sha = payload['head_commit']['id'] commit_sha = payload['head_commit']['id']
commit_message = payload['head_commit'].get('message', '') commit_message = payload['head_commit'].get('message', '')
git_url = payload['repository']['git_url']
config = self.config
if 'branchtag_regex' in config:
try:
regex = re.compile(config['branchtag_regex'])
except:
regex = re.compile('.*')
if not GithubBuildTrigger.matches_ref(ref, regex):
raise SkipRequestException()
# Check if this build should be skipped by commit message.
if should_skip_commit(commit_message): if should_skip_commit(commit_message):
raise SkipRequestException() raise SkipRequestException()
short_sha = GithubBuildTrigger.get_display_name(commit_sha) # Check to see if this build should be skipped by ref.
raise_if_skipped(self.config, ref)
gh_client = self._get_client()
repo_full_name = '%s/%s' % (payload['repository']['owner']['name'],
payload['repository']['name'])
repo = gh_client.get_repo(repo_full_name)
logger.debug('Github repo: %s', repo)
return GithubBuildTrigger._prepare_build(self.trigger, config, repo, commit_sha, short_sha,
ref, git_url)
def manual_start(self, run_parameters=None):
config = self.config
try: try:
source = config['build_source'] repo_full_name = '%s/%s' % (payload['repository']['owner']['name'],
run_parameters = run_parameters or {} payload['repository']['name'])
gh_client = self._get_client() gh_client = self._get_client()
repo = gh_client.get_repo(source) repo = gh_client.get_repo(repo_full_name)
branch_name = run_parameters.get('branch_name') or repo.default_branch
branch = repo.get_branch(branch_name)
branch_sha = branch.commit.sha
short_sha = GithubBuildTrigger.get_display_name(branch_sha)
ref = 'refs/heads/%s' % (branch_name)
git_url = repo.git_url
return self._prepare_build(self.trigger, config, repo, branch_sha, short_sha, ref, git_url) return self._prepare_build(repo, ref, commit_sha, False)
except GithubException as ghe: except GithubException as ghe:
raise TriggerStartException(ghe.data['message']) raise TriggerStartException(ghe.data['message'])
def manual_start(self, run_parameters=None):
config = self.config
source = config['build_source']
run_parameters = run_parameters or {}
try:
gh_client = self._get_client()
# Lookup the branch and its associated current SHA.
repo = gh_client.get_repo(source)
branch_name = run_parameters.get('branch_name') or repo.default_branch
branch = repo.get_branch(branch_name)
commit_sha = branch.commit.sha
ref = 'refs/heads/%s' % (branch_name)
return self._prepare_build(repo, ref, commit_sha, True)
except GithubException as ghe:
raise TriggerStartException(ghe.data['message'])
def list_field_values(self, field_name): def list_field_values(self, field_name):
if field_name == 'refs': if field_name == 'refs':
@ -922,24 +1009,50 @@ class CustomBuildTrigger(BuildTriggerHandler):
return metadata return metadata
def handle_trigger_request(self, request): def handle_trigger_request(self, request):
# Skip if there is no payload.
payload = request.get_json() payload = request.get_json()
if not payload: if not payload:
raise SkipRequestException() raise SkipRequestException()
logger.debug('Payload %s', payload) logger.debug('Payload %s', payload)
# Skip if the commit message matches.
metadata = self._metadata_from_payload(payload) metadata = self._metadata_from_payload(payload)
if should_skip_commit(metadata.get('commit_info', {}).get('message', '')):
raise SkipRequestException()
# The build source is the canonical git URL used to clone. # The build source is the canonical git URL used to clone.
config = self.config config = self.config
metadata['git_url'] = config['build_source'] metadata['git_url'] = config['build_source']
branch = metadata['ref'].split('/')[-1] prepared = PreparedBuild(self.trigger)
tags = {branch} prepared.tags_from_ref(metadata['ref'])
prepared.name_from_sha(metadata['commit_sha'])
prepared.subdirectory = config['subdir']
prepared.metadata = metadata
build_name = metadata['commit_sha'][:6] return prepared
dockerfile_id = None
return dockerfile_id, tags, build_name, config['subdir'], metadata def manual_start(self, run_parameters=None):
# commit_sha is the only required parameter
commit_sha = run_parameters.get('commit_sha')
if commit_sha is None:
raise TriggerStartException('missing required parameter')
config = self.config
metadata = {
'commit_sha': commit_sha,
'git_url': config['build_source'],
}
prepared = PreparedBuild(self.trigger)
prepared.tags = [commit_sha]
prepared.name_from_sha(commit_sha)
prepared.subdirectory = config['subdir']
prepared.metadata = metadata
prepared.is_manual = True
return prepared
def activate(self, standard_webhook_url): def activate(self, standard_webhook_url):
config = self.config config = self.config
@ -962,19 +1075,3 @@ class CustomBuildTrigger(BuildTriggerHandler):
config.pop('credentials', None) config.pop('credentials', None)
self.config = config self.config = config
return config return config
def manual_start(self, run_parameters=None):
# commit_sha is the only required parameter
if 'commit_sha' not in run_parameters:
raise TriggerStartException('missing required parameter')
config = self.config
dockerfile_id = None
tags = {run_parameters['commit_sha']}
build_name = run_parameters['commit_sha']
metadata = {
'commit_sha': run_parameters['commit_sha'],
'git_url': config['build_source'],
}
return dockerfile_id, list(tags), build_name, config['subdir'], metadata

View file

@ -11,7 +11,7 @@ from util.useremails import send_invoice_email, send_subscription_change, send_p
from util.http import abort from util.http import abort
from endpoints.trigger import (BuildTriggerHandler, ValidationRequestException, from endpoints.trigger import (BuildTriggerHandler, ValidationRequestException,
SkipRequestException, InvalidPayloadException) SkipRequestException, InvalidPayloadException)
from endpoints.common import start_build from endpoints.building import start_build
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -87,8 +87,7 @@ def build_trigger_webhook(trigger_uuid, **kwargs):
logger.debug('Passing webhook request to handler %s', handler) logger.debug('Passing webhook request to handler %s', handler)
try: try:
specs = handler.handle_trigger_request(request) prepared = handler.handle_trigger_request(request)
dockerfile_id, tags, name, subdir, metadata = specs
except ValidationRequestException: except ValidationRequestException:
# This was just a validation request, we don't need to build anything # This was just a validation request, we don't need to build anything
return make_response('Okay') return make_response('Okay')
@ -101,8 +100,7 @@ def build_trigger_webhook(trigger_uuid, **kwargs):
pull_robot_name = model.get_pull_robot_name(trigger) pull_robot_name = model.get_pull_robot_name(trigger)
repo = model.get_repository(namespace, repository) repo = model.get_repository(namespace, repository)
start_build(repo, dockerfile_id, tags, name, subdir, False, trigger, start_build(repo, prepared, pull_robot_name=pull_robot_name)
pull_robot_name=pull_robot_name, trigger_metadata=metadata)
return make_response('Okay') return make_response('Okay')