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:
parent
6479f8ddc9
commit
d5c70878c5
6 changed files with 432 additions and 226 deletions
|
@ -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 = {
|
||||||
|
|
|
@ -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
186
endpoints/building.py
Normal 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
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
Reference in a new issue