From 5cc91ed20220b31b19a325444b0ca0c48338ac86 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 24 Apr 2015 18:36:48 -0400 Subject: [PATCH] Work in progress: bitbucket support --- endpoints/api/build.py | 9 +- endpoints/api/trigger.py | 54 ++-- endpoints/bitbuckettrigger.py | 14 +- endpoints/trigger.py | 299 +++++++++++------- endpoints/web.py | 5 +- endpoints/webhooks.py | 7 +- .../directives/ui/trigger-setup-githost.css | 88 ++++++ static/css/quay.css | 89 ------ static/directives/setup-trigger-dialog.html | 9 +- ...github.html => trigger-setup-githost.html} | 30 +- ...tup-github.js => trigger-setup-githost.js} | 7 +- 11 files changed, 352 insertions(+), 259 deletions(-) create mode 100644 static/css/directives/ui/trigger-setup-githost.css rename static/directives/{trigger-setup-github.html => trigger-setup-githost.html} (89%) rename static/js/directives/ui/{trigger-setup-github.js => trigger-setup-githost.js} (95%) diff --git a/endpoints/api/build.py b/endpoints/api/build.py index c5e548785..f6a4b05fb 100644 --- a/endpoints/api/build.py +++ b/endpoints/api/build.py @@ -11,7 +11,7 @@ from endpoints.api import (RepositoryParamResource, parse_args, query_param, nic ApiResource, internal_only, format_date, api, Unauthorized, NotFound, path_param, InvalidRequest, require_repo_admin) from endpoints.common import start_build -from endpoints.trigger import BuildTrigger +from endpoints.trigger import BuildTriggerHandler from data import model, database from auth.auth_context import get_authenticated_user from auth.permissions import ModifyRepositoryPermission, AdministerOrganizationPermission @@ -45,14 +45,13 @@ def user_view(user): def trigger_view(trigger, can_admin=False): if trigger and trigger.uuid: - config_dict = get_trigger_config(trigger) - build_trigger = BuildTrigger.get_trigger_for_service(trigger.service.name) + build_trigger = BuildTriggerHandler.get_handler(trigger) return { 'service': trigger.service.name, - 'config': config_dict if can_admin else {}, + 'config': build_trigger.config if can_admin else {}, 'id': trigger.uuid, 'connected_user': trigger.connected_user.username, - 'is_active': build_trigger.is_active(config_dict), + 'is_active': build_trigger.is_active(), 'pull_robot': user_view(trigger.pull_robot) if trigger.pull_robot else None } diff --git a/endpoints/api/trigger.py b/endpoints/api/trigger.py index 72a04fd0b..dc7ae3bba 100644 --- a/endpoints/api/trigger.py +++ b/endpoints/api/trigger.py @@ -13,7 +13,7 @@ from endpoints.api import (RepositoryParamResource, nickname, resource, require_ from endpoints.api.build import (build_status_view, trigger_view, RepositoryBuildStatus, get_trigger_config) from endpoints.common import start_build -from endpoints.trigger import (BuildTrigger as BuildTriggerBase, TriggerDeactivationException, +from endpoints.trigger import (BuildTriggerHandler, TriggerDeactivationException, TriggerActivationException, EmptyRepositoryException, RepositoryReadException, TriggerStartException) from data import model @@ -71,18 +71,17 @@ class BuildTrigger(RepositoryParamResource): except model.InvalidBuildTriggerException: raise NotFound() - handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name) - config_dict = get_trigger_config(trigger) - if handler.is_active(config_dict): + handler = BuildTriggerHandler.get_handler(trigger) + if handler.is_active(): try: - handler.deactivate(trigger.auth_token, config_dict) + handler.deactivate() except TriggerDeactivationException as ex: # We are just going to eat this error logger.warning('Trigger deactivation problem: %s', ex) log_action('delete_repo_trigger', namespace, {'repo': repository, 'trigger_id': trigger_uuid, - 'service': trigger.service.name, 'config': config_dict}, + 'service': trigger.service.name}, repo=model.get_repository(namespace, repository)) trigger.delete_instance(recursive=True) @@ -117,13 +116,13 @@ class BuildTriggerSubdirs(RepositoryParamResource): except model.InvalidBuildTriggerException: raise NotFound() - handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name) user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): new_config_dict = request.get_json() + handler = BuildTriggerHandler.get_handler(trigger, new_config_dict) try: - subdirs = handler.list_build_subdirs(trigger.auth_token, new_config_dict) + subdirs = handler.list_build_subdirs() return { 'subdir': subdirs, 'status': 'success' @@ -178,9 +177,8 @@ class BuildTriggerActivate(RepositoryParamResource): except model.InvalidBuildTriggerException: raise NotFound() - handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name) - existing_config_dict = get_trigger_config(trigger) - if handler.is_active(existing_config_dict): + handler = BuildTriggerHandler.get_handler(trigger) + if handler.is_active(): raise InvalidRequest('Trigger config is not sufficient for activation.') user_permission = UserAdminPermission(trigger.connected_user.username) @@ -217,8 +215,8 @@ class BuildTriggerActivate(RepositoryParamResource): '$token', write_token.code, app.config['SERVER_HOSTNAME'], path) - final_config, private_config = handler.activate(trigger.uuid, authed_url, - trigger.auth_token, new_config_dict) + handler = BuildTriggerHandler.get_handler(trigger, new_config_dict) + final_config, private_config = handler.activate(authed_url) if 'private_key' in private_config: trigger.private_key = private_config['private_key'] @@ -279,12 +277,12 @@ class BuildTriggerAnalyze(RepositoryParamResource): except model.InvalidBuildTriggerException: raise NotFound() - handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name) new_config_dict = request.get_json()['config'] + handler = BuildTriggerHandler.get_handler(trigger, new_config_dict) try: # Load the contents of the Dockerfile. - contents = handler.load_dockerfile_contents(trigger.auth_token, new_config_dict) + contents = handler.load_dockerfile_contents() if not contents: return { 'status': 'error', @@ -370,7 +368,7 @@ class BuildTriggerAnalyze(RepositoryParamResource): 'is_public': found_repository.visibility.name == 'public', 'robots': read_robots, 'status': 'analyzed', - 'dockerfile_url': handler.dockerfile_url(trigger.auth_token, new_config_dict) + 'dockerfile_url': handler.dockerfile_url() } except RepositoryReadException as rre: @@ -420,14 +418,13 @@ class ActivateBuildTrigger(RepositoryParamResource): except model.InvalidBuildTriggerException: raise NotFound() - handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name) - config_dict = get_trigger_config(trigger) - if not handler.is_active(config_dict): + handler = BuildTriggerHandler.get_handler(trigger) + if not handler.is_active(): raise InvalidRequest('Trigger is not active.') try: run_parameters = request.get_json() - specs = handler.manual_start(trigger, run_parameters=run_parameters) + specs = handler.manual_start(run_parameters=run_parameters) dockerfile_id, tags, name, subdir, metadata = specs repo = model.get_repository(namespace, repository) @@ -481,11 +478,11 @@ class BuildTriggerFieldValues(RepositoryParamResource): except model.InvalidBuildTriggerException: raise NotFound() - config = request.get_json() or json.loads(trigger.config) + config = request.get_json() or None user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): - trigger_handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name) - values = trigger_handler.list_field_values(trigger.auth_token, config, field_name) + handler = BuildTriggerHandler.get_handler(trigger, config) + values = handler.list_field_values(field_name) if values is None: raise NotFound() @@ -514,10 +511,13 @@ class BuildTriggerSources(RepositoryParamResource): user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): - trigger_handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name) + handler = BuildTriggerHandler.get_handler(trigger) - return { - 'sources': trigger_handler.list_build_sources(trigger.auth_token) - } + try: + return { + 'sources': handler.list_build_sources() + } + except RepositoryReadException as rre: + raise InvalidRequest(rre.message) else: raise Unauthorized() diff --git a/endpoints/bitbuckettrigger.py b/endpoints/bitbuckettrigger.py index 1d4a21a98..2cfdda91e 100644 --- a/endpoints/bitbuckettrigger.py +++ b/endpoints/bitbuckettrigger.py @@ -3,7 +3,7 @@ import logging from flask import request, redirect, url_for, Blueprint from flask.ext.login import current_user -from endpoints.trigger import BitbucketBuildTrigger +from endpoints.trigger import BitbucketBuildTrigger, BuildTriggerHandler from endpoints.common import route_show_if from app import app from data import model @@ -30,14 +30,18 @@ def attach_bitbucket_build_trigger(trigger_uuid): abort(404) verifier = request.args.get('oauth_verifier') - result = BitbucketBuildTrigger.exchange_verifier(trigger, verifier) - print result - return 'hello' + handler = BuildTriggerHandler.get_handler(trigger) + result = handler.exchange_verifier(verifier) + if not result: + trigger.delete_instance() + return 'Token has expired' + + namespace = trigger.repository.namespace_user.username + repository = trigger.repository.name repo_path = '%s/%s' % (namespace, repository) full_url = '%s%s%s' % (url_for('web.repository', path=repo_path), '?tab=builds&newtrigger=', trigger.uuid) - logger.debug('Redirecting to full url: %s', full_url) return redirect(full_url) \ No newline at end of file diff --git a/endpoints/trigger.py b/endpoints/trigger.py index 1368bded8..8f00d4674 100644 --- a/endpoints/trigger.py +++ b/endpoints/trigger.py @@ -10,6 +10,7 @@ from github import Github, UnknownObjectException, GithubException from bitbucket.bitbucket import Bitbucket from tempfile import SpooledTemporaryFile from jsonschema import validate +from data import model from app import app, userfiles as user_files, github_trigger, get_app_url from util.tarfileappender import TarfileAppender @@ -63,59 +64,75 @@ class TriggerProviderException(Exception): pass -class BuildTrigger(object): - def __init__(self): - pass +def raise_unsupported(): + raise io.UnsupportedOperation - def dockerfile_url(self, auth_token, config): +def get_trigger_config(trigger): + try: + return json.loads(trigger.config) + except: + return {} + + +class BuildTriggerHandler(object): + def __init__(self, trigger, override_config=None): + self.trigger = trigger + self.config = override_config or get_trigger_config(trigger) + + @property + def auth_token(self): + """ Returns the auth token for the trigger. """ + return self.trigger.auth_token + + def dockerfile_url(self): """ Returns the URL at which the Dockerfile for the trigger is found or None if none/not applicable. """ raise NotImplementedError - def load_dockerfile_contents(self, auth_token, config): + def load_dockerfile_contents(self): """ Loads the Dockerfile found for the trigger's config and returns them or None if none could be found/loaded. """ raise NotImplementedError - def list_build_sources(self, auth_token): + def list_build_sources(self): """ Take the auth information for the specific trigger type and load the list of build sources(repositories). """ raise NotImplementedError - def list_build_subdirs(self, auth_token, config): + def list_build_subdirs(self): """ Take the auth information and the specified config so far and list all of the possible subdirs containing dockerfiles. """ raise NotImplementedError - def handle_trigger_request(self, request, trigger): + def handle_trigger_request(self): """ Transform the incoming request data into a set of actions. Returns a tuple of usefiles resource id, docker tags, build name, and resource subdir. """ raise NotImplementedError - def is_active(self, config): + def is_active(self): """ Returns True if the current build trigger is active. Inactive means further setup is needed. """ raise NotImplementedError - def activate(self, trigger_uuid, standard_webhook_url, auth_token, config): + def activate(self, standard_webhook_url): """ Activates the trigger for the service, with the given new configuration. Returns new public and private config that should be stored if successful. """ raise NotImplementedError - def deactivate(self, auth_token, config): + def deactivate(self): """ Deactivates the trigger for the service, removing any hooks installed in the remote service. Returns the new config that should be stored if this @@ -123,13 +140,13 @@ class BuildTrigger(object): """ raise NotImplementedError - def manual_start(self, trigger, run_parameters=None): + def manual_start(self, run_parameters=None): """ Manually creates a repository build for this trigger. """ raise NotImplementedError - def list_field_values(self, auth_token, config, field_name): + def list_field_values(self, field_name): """ Lists all values for the given custom trigger field. For example, a trigger might have a field named "branches", and this method would return all branches. @@ -144,25 +161,24 @@ class BuildTrigger(object): raise NotImplementedError @classmethod - def get_trigger_for_service(cls, service): + def get_handler(cls, trigger, override_config=None): for subc in cls.__subclasses__(): - if subc.service_name() == service: - return subc() + if subc.service_name() == trigger.service.name: + return subc(trigger, override_config) raise InvalidServiceException('Unable to find service: %s' % service) + def put_config_key(self, key, value): + """ Updates a config key in the trigger, saving it to the DB. """ + self.config[key] = value + model.update_build_trigger(self.trigger, self.config) -def raise_unsupported(): - raise io.UnsupportedOperation - -def get_trigger_config(trigger): - try: - return json.loads(trigger.config) - except: - return {} + def set_auth_token(self, auth_token): + """ Sets the auth token for the trigger, saving it to the DB. """ + model.update_build_trigger(self.trigger, self.config, auth_token=auth_token) -class BitbucketBuildTrigger(BuildTrigger): +class BitbucketBuildTrigger(BuildTriggerHandler): """ BuildTrigger for Bitbucket. """ @@ -170,23 +186,25 @@ class BitbucketBuildTrigger(BuildTrigger): def service_name(cls): return 'bitbucket' - @staticmethod - def _get_authorized_client(trigger_uuid): + def _get_authorized_client(self, namespace=None): key = app.config.get('BITBUCKET_TRIGGER_CONFIG', {}).get('CONSUMER_KEY', '') secret = app.config.get('BITBUCKET_TRIGGER_CONFIG', {}).get('CONSUMER_SECRET', '') + trigger_uuid = self.trigger.uuid callback_url = '%s/oauth1/bitbucket/callback/trigger/%s' % (get_app_url(), trigger_uuid) - bitbucket_client = Bitbucket() - (result, err_message) = bitbucket_client.authorize(key, secret, callback_url) + + bitbucket_client = Bitbucket(username=namespace or self.config.get('username', '')) + + (result, err_message) = bitbucket_client.authorize(key, secret, callback_url, + access_token=self.config.get('access_token'), + access_token_secret=self.auth_token) if not result: raise TriggerProviderException(err_message) return bitbucket_client - - @staticmethod - def get_oauth_url(trigger_uuid): - bitbucket_client = BitbucketBuildTrigger._get_authorized_client(trigger_uuid) + def get_oauth_url(self): + bitbucket_client = self._get_authorized_client() url = bitbucket_client.url('AUTHENTICATE', token=bitbucket_client.access_token) return { 'access_token': bitbucket_client.access_token, @@ -194,62 +212,104 @@ class BitbucketBuildTrigger(BuildTrigger): 'url': url } - @staticmethod - def exchange_verifier(trigger, verifier): - trigger_config = get_trigger_config(trigger.config) - bitbucket_client = BitbucketBuildTrigger._get_authorized_client(trigger.uuid) - print trigger.config - print trigger.auth_token - print bitbucket_client.verify(verifier, access_token=trigger_config.get('access_token', ''), - access_token_secret=trigger.auth_token) - return None - #(result, _) = bitbucket_client.verify(verifier) + def exchange_verifier(self, verifier): + bitbucket_client = self._get_authorized_client() + (result, data) = bitbucket_client.verify(verifier, + access_token=self.config.get('access_token', ''), + access_token_secret=self.auth_token) - #if not result: - # return None + if not result: + return False - #return (bitbucket_client.access_token, bitbucket_client.access_token_secret) + # Request the user's information and save it and the access token to the config. + user_url = bitbucket_client.URLS['BASE'] % 'user' + (result, data) = bitbucket_client.dispatch('GET', user_url, auth=bitbucket_client.auth) + if not result: + return False - def is_active(self, config): + username = data['user']['username'] + new_access_token = bitbucket_client.access_token + + self.put_config_key('username', username) + self.put_config_key('access_token', new_access_token) + self.set_auth_token(bitbucket_client.access_token_secret) + + return True + + + def is_active(self): return False - def activate(self, trigger_uuid, standard_webhook_url, auth_token, config): + def activate(self, standard_webhook_url): return {} - def deactivate(self, auth_token, config): - return config + def deactivate(self): + return self.config - def list_build_sources(self, auth_token): + def list_build_sources(self): + bitbucket_client = self._get_authorized_client() + success, repositories = bitbucket_client.repository.all() + if not success: + raise RepositoryReadException('Could not read repository list') + + namespaces = {} + + for repo in repositories: + if not repo['scm'] == 'git': + continue + + owner = repo['owner'] + if not owner in namespaces: + namespaces[owner] = { + 'personal': owner == self.config.get('username'), + 'repos': [], + 'info': { + 'name': owner + } + } + + namespaces[owner]['repos'].append(owner + '/' + repo['slug']) + + return namespaces.values() + + + def list_build_subdirs(self): + source = self.config['build_source'] + (namespace, name) = source.split('/') + (result, data) = self._get_authorized_client(namespace=namespace).repository.get(name) + + print result + print data + return [] + + def dockerfile_url(self): + return None + + def load_dockerfile_contents(self): + raise RepositoryReadException('Not supported') + + def handle_trigger_request(self, request): + return + + def manual_start(self, run_parameters=None): + return None + + def list_field_values(self, field_name): + source = self.config['build_source'] + (namespace, name) = source.split('/') + (result, data) = self._get_authorized_client(namespace=namespace).repository.get(name) + + print result + print data return [] - def list_build_subdirs(self, auth_token, config): - raise RepositoryReadException('Not supported') - - def dockerfile_url(self, auth_token, config): - return None - - def load_dockerfile_contents(self, auth_token, config): - raise RepositoryReadException('Not supported') - - @staticmethod - def _build_commit_info(repo, commit_sha): - return {} - - def handle_trigger_request(self, request, trigger): - return - - def manual_start(self, trigger, run_parameters=None): - return None - - -class GithubBuildTrigger(BuildTrigger): +class GithubBuildTrigger(BuildTriggerHandler): """ BuildTrigger for GitHub that uses the archive API and buildpacks. """ - @staticmethod - def _get_client(auth_token): - return Github(auth_token, + def _get_client(self): + return Github(self.auth_token, base_url=github_trigger.api_endpoint(), client_id=github_trigger.client_id(), client_secret=github_trigger.client_secret()) @@ -258,12 +318,13 @@ class GithubBuildTrigger(BuildTrigger): def service_name(cls): return 'github' - def is_active(self, config): - return 'hook_id' in config + def is_active(self): + return 'hook_id' in self.config - def activate(self, trigger_uuid, standard_webhook_url, auth_token, config): + def activate(self, standard_webhook_url): + config = self.config new_build_source = config['build_source'] - gh_client = self._get_client(auth_token) + gh_client = self._get_client() # Find the GitHub repository. try: @@ -303,8 +364,9 @@ class GithubBuildTrigger(BuildTrigger): return config, {'private_key': private_key} - def deactivate(self, auth_token, config): - gh_client = self._get_client(auth_token) + def deactivate(self): + config = self.config + gh_client = self._get_client() # Find the GitHub repository. try: @@ -334,11 +396,11 @@ class GithubBuildTrigger(BuildTrigger): raise TriggerDeactivationException(msg) config.pop('hook_id', None) - + self.config = config return config - def list_build_sources(self, auth_token): - gh_client = self._get_client(auth_token) + def list_build_sources(self): + gh_client = self._get_client() usr = gh_client.get_user() personal = { @@ -380,8 +442,9 @@ class GithubBuildTrigger(BuildTrigger): return len(m.group(0)) == len(match_string) - def list_build_subdirs(self, auth_token, config): - gh_client = self._get_client(auth_token) + def list_build_subdirs(self): + config = self.config + gh_client = self._get_client() source = config['build_source'] try: @@ -411,11 +474,13 @@ class GithubBuildTrigger(BuildTrigger): raise RepositoryReadException(message) - def dockerfile_url(self, auth_token, config): + def dockerfile_url(self): + config = self.config + source = config['build_source'] subdirectory = config.get('subdir', '') path = subdirectory + '/Dockerfile' if subdirectory else 'Dockerfile' - gh_client = self._get_client(auth_token) + gh_client = self._get_client() try: repo = gh_client.get_repo(source) @@ -425,8 +490,9 @@ class GithubBuildTrigger(BuildTrigger): logger.exception('Could not load repository for Dockerfile.') return None - def load_dockerfile_contents(self, auth_token, config): - gh_client = self._get_client(auth_token) + def load_dockerfile_contents(self): + config = self.config + gh_client = self._get_client() source = config['build_source'] subdirectory = config.get('subdir', '') @@ -556,7 +622,7 @@ class GithubBuildTrigger(BuildTrigger): def get_display_name(sha): return sha[0:7] - def handle_trigger_request(self, request, trigger): + def handle_trigger_request(self, request): payload = request.get_json() if not payload or payload.get('head_commit') is None: raise SkipRequestException() @@ -570,7 +636,7 @@ class GithubBuildTrigger(BuildTrigger): commit_message = payload['head_commit'].get('message', '') git_url = payload['repository']['git_url'] - config = get_trigger_config(trigger) + config = self.config if 'branchtag_regex' in config: try: regex = re.compile(config['branchtag_regex']) @@ -585,7 +651,7 @@ class GithubBuildTrigger(BuildTrigger): short_sha = GithubBuildTrigger.get_display_name(commit_sha) - gh_client = self._get_client(trigger.auth_token) + gh_client = self._get_client() repo_full_name = '%s/%s' % (payload['repository']['owner']['name'], payload['repository']['name']) @@ -593,16 +659,16 @@ class GithubBuildTrigger(BuildTrigger): logger.debug('Github repo: %s', repo) - return GithubBuildTrigger._prepare_build(trigger, config, repo, commit_sha, - short_sha, ref, git_url) + return GithubBuildTrigger._prepare_build(self.trigger, config, repo, commit_sha, short_sha, + ref, git_url) - def manual_start(self, trigger, run_parameters=None): - config = get_trigger_config(trigger) + def manual_start(self, run_parameters=None): + config = self.config try: source = config['build_source'] run_parameters = run_parameters or {} - gh_client = self._get_client(trigger.auth_token) + gh_client = self._get_client() repo = gh_client.get_repo(source) branch_name = run_parameters.get('branch_name') or repo.default_branch branch = repo.get_branch(branch_name) @@ -611,27 +677,28 @@ class GithubBuildTrigger(BuildTrigger): ref = 'refs/heads/%s' % (branch_name) git_url = repo.git_url - return self._prepare_build(trigger, config, repo, branch_sha, short_sha, ref, git_url) + return self._prepare_build(self.trigger, config, repo, branch_sha, short_sha, ref, git_url) except GithubException as ghe: raise TriggerStartException(ghe.data['message']) - def list_field_values(self, auth_token, config, field_name): + def list_field_values(self, field_name): if field_name == 'refs': - branches = self.list_field_values(auth_token, config, 'branch_name') - tags = self.list_field_values(auth_token, config, 'tag_name') + branches = self.list_field_values('branch_name') + tags = self.list_field_values('tag_name') return ([{'kind': 'branch', 'name': b} for b in branches] + [{'kind': 'tag', 'name': tag} for tag in tags]) + config = self.config if field_name == 'tag_name': - gh_client = self._get_client(auth_token) + gh_client = self._get_client() source = config['build_source'] repo = gh_client.get_repo(source) return [tag.name for tag in repo.get_tags()] if field_name == 'branch_name': - gh_client = self._get_client(auth_token) + gh_client = self._get_client() source = config['build_source'] repo = gh_client.get_repo(source) branches = [branch.name for branch in repo.get_branches()] @@ -647,7 +714,7 @@ class GithubBuildTrigger(BuildTrigger): return None -class CustomBuildTrigger(BuildTrigger): +class CustomBuildTrigger(BuildTriggerHandler): payload_schema = { 'type': 'object', 'properties': { @@ -730,8 +797,8 @@ class CustomBuildTrigger(BuildTrigger): def service_name(cls): return 'custom-git' - def is_active(self, config): - return config.has_key('credentials') + def is_active(self): + return self.config.has_key('credentials') def _metadata_from_payload(self, payload): try: @@ -741,7 +808,7 @@ class CustomBuildTrigger(BuildTrigger): raise InvalidPayloadException() return metadata - def handle_trigger_request(self, request, trigger): + def handle_trigger_request(self, request): payload = request.get_json() if not payload: raise SkipRequestException() @@ -750,7 +817,7 @@ class CustomBuildTrigger(BuildTrigger): metadata = self._metadata_from_payload(payload) # The build source is the canonical git URL used to clone. - config = get_trigger_config(trigger) + config = self.config metadata['git_url'] = config['build_source'] branch = metadata['ref'].split('/')[-1] @@ -759,9 +826,10 @@ class CustomBuildTrigger(BuildTrigger): build_name = metadata['commit_sha'][:6] dockerfile_id = None - return dockerfile_id, tags, build_name, trigger.config['subdir'], metadata + return dockerfile_id, tags, build_name, config['subdir'], metadata - def activate(self, trigger_uuid, standard_webhook_url, auth_token, config): + def activate(self, standard_webhook_url): + config = self.config public_key, private_key = generate_ssh_keypair() config['credentials'] = [ { @@ -773,18 +841,21 @@ class CustomBuildTrigger(BuildTrigger): 'value': standard_webhook_url, }, ] + self.config = config return config, {'private_key': private_key} - def deactivate(self, auth_token, config): + def deactivate(self): + config = self.config config.pop('credentials', None) + self.config = config return config - def manual_start(self, trigger, run_parameters=None): + 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 = get_trigger_config(trigger) + config = self.config dockerfile_id = None tags = {run_parameters['commit_sha']} build_name = run_parameters['commit_sha'] diff --git a/endpoints/web.py b/endpoints/web.py index b90e8ea1e..3dda9998d 100644 --- a/endpoints/web.py +++ b/endpoints/web.py @@ -20,7 +20,8 @@ from util.cache import no_cache from endpoints.common import common_login, render_page_template, route_show_if, param_required from endpoints.csrf import csrf_protect, generate_csrf_token, verify_csrf from endpoints.registry import set_cache_headers -from endpoints.trigger import CustomBuildTrigger, BitbucketBuildTrigger, TriggerProviderException +from endpoints.trigger import (CustomBuildTrigger, BitbucketBuildTrigger, TriggerProviderException, + BuildTriggerHandler) from util.names import parse_repository_name, parse_repository_name_and_tag from util.useremails import send_email_changed from util.systemlogs import build_logs_archive @@ -513,7 +514,7 @@ def attach_bitbucket_trigger(namespace, repository_name): current_user.db_user()) try: - oauth_info = BitbucketBuildTrigger.get_oauth_url(trigger.uuid) + oauth_info = BuildTriggerHandler.get_handler(trigger).get_oauth_url() config = { 'access_token': oauth_info['access_token'] diff --git a/endpoints/webhooks.py b/endpoints/webhooks.py index 0ab4f0bc5..8804e8ef1 100644 --- a/endpoints/webhooks.py +++ b/endpoints/webhooks.py @@ -9,7 +9,8 @@ from auth.permissions import ModifyRepositoryPermission from util.invoice import renderInvoiceToHtml from util.useremails import send_invoice_email, send_subscription_change, send_payment_failed from util.http import abort -from endpoints.trigger import BuildTrigger, ValidationRequestException, SkipRequestException, InvalidPayloadException +from endpoints.trigger import (BuildTriggerHandler, ValidationRequestException, + SkipRequestException, InvalidPayloadException) from endpoints.common import start_build @@ -82,11 +83,11 @@ def build_trigger_webhook(trigger_uuid, **kwargs): repository = trigger.repository.name permission = ModifyRepositoryPermission(namespace, repository) if permission.can(): - handler = BuildTrigger.get_trigger_for_service(trigger.service.name) + handler = BuildTriggerHandler.get_handler(trigger) logger.debug('Passing webhook request to handler %s', handler) try: - specs = handler.handle_trigger_request(request, trigger) + specs = handler.handle_trigger_request(request) dockerfile_id, tags, name, subdir, metadata = specs except ValidationRequestException: # This was just a validation request, we don't need to build anything diff --git a/static/css/directives/ui/trigger-setup-githost.css b/static/css/directives/ui/trigger-setup-githost.css new file mode 100644 index 000000000..edaf63c86 --- /dev/null +++ b/static/css/directives/ui/trigger-setup-githost.css @@ -0,0 +1,88 @@ +.trigger-setup-githost-element .ref-reference { + color: #ccc; +} + +.trigger-setup-githost-element .ref-reference span { + cursor: pointer; + text-decoration: line-through; +} + +.trigger-setup-githost-element .ref-reference:hover { + color: #3276b1; +} + +.trigger-setup-githost-element .ref-reference:hover span { + text-decoration: none; +} + +.trigger-setup-githost-element .ref-reference.match { + color: black; +} + +.trigger-setup-githost-element .ref-reference.match span { + text-decoration: none; + cursor: default; +} + +.trigger-setup-githost-element .ref-filter { + white-space: nowrap; +} + +.trigger-setup-githost-element .ref-filter span { + display: inline-block; +} + +.trigger-setup-githost-element .selected-info { + margin-bottom: 20px; +} + +.trigger-setup-githost-element .org-icon { + width: 20px; + margin-right: 8px; + vertical-align: middle; +} + +.trigger-setup-githost-element li.repo-listing i { + margin-right: 10px; + margin-left: 6px; +} + +.trigger-setup-githost-element li.org-header { + padding-left: 6px; +} + +.trigger-setup-githost-element .matching-refs { + margin: 0px; + padding: 0px; + margin-left: 10px; + display: inline-block; +} + +.trigger-setup-githost-element .ref-matches { + padding-left: 70px; + position: relative; + margin-bottom: 10px; +} + +.trigger-setup-githost-element .ref-matches .kind { + font-weight: bold; + position: absolute; + top: 0px; + left: 0px; +} + +.trigger-setup-githost-element .matching-refs.tags li:before { + content: "\f02b"; + font-family: FontAwesome; +} + +.trigger-setup-githost-element .matching-refs.branches li:before { + content: "\f126"; + font-family: FontAwesome; +} + +.trigger-setup-githost-element .matching-refs li { + list-style: none; + display: inline-block; + margin-left: 10px; +} \ No newline at end of file diff --git a/static/css/quay.css b/static/css/quay.css index 35bb92671..02409c42a 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -3768,95 +3768,6 @@ pre.command:before { border-bottom-left-radius: 0px; } -.trigger-setup-github-element .ref-reference { - color: #ccc; -} - -.trigger-setup-github-element .ref-reference span { - cursor: pointer; - text-decoration: line-through; -} - -.trigger-setup-github-element .ref-reference:hover { - color: #3276b1; -} - -.trigger-setup-github-element .ref-reference:hover span { - text-decoration: none; -} - -.trigger-setup-github-element .ref-reference.match { - color: black; -} - -.trigger-setup-github-element .ref-reference.match span { - text-decoration: none; - cursor: default; -} - -.trigger-setup-github-element .ref-filter { - white-space: nowrap; -} - -.trigger-setup-github-element .ref-filter span { - display: inline-block; -} - -.trigger-setup-github-element .selected-info { - margin-bottom: 20px; -} - -.trigger-setup-github-element .github-org-icon { - width: 20px; - margin-right: 8px; - vertical-align: middle; -} - -.trigger-setup-github-element li.github-repo-listing i { - margin-right: 10px; - margin-left: 6px; -} - -.trigger-setup-github-element li.github-org-header { - padding-left: 6px; -} - -.trigger-setup-github-element .matching-refs { - margin: 0px; - padding: 0px; - margin-left: 10px; - display: inline-block; -} - -.trigger-setup-github-element .ref-matches { - padding-left: 70px; - position: relative; - margin-bottom: 10px; -} - -.trigger-setup-github-element .ref-matches .kind { - font-weight: bold; - position: absolute; - top: 0px; - left: 0px; -} - -.trigger-setup-github-element .matching-refs.tags li:before { - content: "\f02b"; - font-family: FontAwesome; -} - -.trigger-setup-github-element .matching-refs.branches li:before { - content: "\f126"; - font-family: FontAwesome; -} - -.trigger-setup-github-element .matching-refs li { - list-style: none; - display: inline-block; - margin-left: 10px; -} - .setup-trigger-directive-element .dockerfile-found-content { margin-left: 32px; } diff --git a/static/directives/setup-trigger-dialog.html b/static/directives/setup-trigger-dialog.html index 19123d4a9..66a1c52b7 100644 --- a/static/directives/setup-trigger-dialog.html +++ b/static/directives/setup-trigger-dialog.html @@ -15,7 +15,14 @@
-
+
+
+
diff --git a/static/directives/trigger-setup-github.html b/static/directives/trigger-setup-githost.html similarity index 89% rename from static/directives/trigger-setup-github.html rename to static/directives/trigger-setup-githost.html index c6af1f957..67e44a5be 100644 --- a/static/directives/trigger-setup-github.html +++ b/static/directives/trigger-setup-githost.html @@ -1,4 +1,4 @@ -
+
@@ -8,9 +8,17 @@ @@ -47,21 +55,23 @@
-
Please choose the GitHub repository that will trigger the build:
+
Please choose the repository that will trigger the build: