import logging import io import os.path from github import Github, UnknownObjectException, GithubException from tempfile import SpooledTemporaryFile from app import app user_files = app.config['USERFILES'] client = app.config['HTTPCLIENT'] logger = logging.getLogger(__name__) ZIPBALL = 'application/zip' CHUNK_SIZE = 512 * 1024 class BuildArchiveException(Exception): pass class InvalidServiceException(Exception): pass class TriggerActivationException(Exception): pass class ValidationRequestException(Exception): pass class EmptyRepositoryException(Exception): pass class BuildTrigger(object): def __init__(self): pass def list_build_sources(self, auth_token): """ 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): """ 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, auth_token, config): """ Transform the incoming request data into a set of actions. """ raise NotImplementedError def is_active(self, config): """ 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): """ Activates the trigger for the service, with the given new configuration. """ raise NotImplementedError @classmethod def service_name(cls): """ Particular service implemented by subclasses. """ raise NotImplementedError @classmethod def get_trigger_for_service(cls, service): for subc in cls.__subclasses__(): if subc.service_name() == service: return subc() raise InvalidServiceException('Unable to find service: %s' % service) def raise_unsupported(): raise io.UnsupportedOperation class GithubBuildTrigger(BuildTrigger): @staticmethod def _get_client(auth_token): return Github(auth_token, client_id=app.config['GITHUB_CLIENT_ID'], client_secret=app.config['GITHUB_CLIENT_SECRET']) @classmethod def service_name(cls): return 'github' def is_active(self, config): return 'build_source' in config and len(config['build_source']) > 0 def activate(self, trigger_uuid, standard_webhook_url, auth_token, config): new_build_source = config['build_source'] gh_client = self._get_client(auth_token) try: to_add_webhook = gh_client.get_repo(new_build_source) except UnknownObjectException: msg = 'Unable to find GitHub repository for source: %s' raise TriggerActivationException(msg % new_build_source) webhook_config = { 'url': standard_webhook_url, 'content_type': 'json', } try: to_add_webhook.create_hook('web', webhook_config) except GithubException: msg = 'Unable to create webhook on repository: %s' raise TriggerActivationException(msg % new_build_source) def list_build_sources(self, auth_token): gh_client = self._get_client(auth_token) usr = gh_client.get_user() personal = { 'personal': True, 'repos': [repo.full_name for repo in usr.get_repos()], 'info': { 'name': usr.login, 'avatar_url': usr.avatar_url, } } repos_by_org = [personal] for org in usr.get_orgs(): repo_list = [] for repo in org.get_repos(type='member'): repo_list.append(repo.full_name) repos_by_org.append({ 'personal': False, 'repos': repo_list, 'info': { 'name': org.name, 'avatar_url': org.avatar_url } }) return repos_by_org def list_build_subdirs(self, auth_token, config): gh_client = self._get_client(auth_token) source = config['build_source'] try: repo = gh_client.get_repo(source) default_commit = repo.get_branch(repo.default_branch).commit commit_tree = repo.get_git_tree(default_commit.sha, recursive=True) return [os.path.dirname(elem.path) for elem in commit_tree.tree if (elem.type == u'blob' and os.path.basename(elem.path) == u'Dockerfile')] except GithubException: msg = 'Unable to list contents of repository: %s' % source raise EmptyRepositoryException(msg) def handle_trigger_request(self, request, auth_token, config): payload = request.get_json() if 'zen' in payload: raise ValidationRequestException() logger.debug('Payload %s', payload) ref = payload['ref'] commit_id = payload['head_commit']['id'][0:7] gh_client = self._get_client(auth_token) 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) # Prepare the download and upload URLs branch_name = ref.split('/')[-1] archive_link = repo.get_archive_link('zipball', branch_name) download_archive = client.get(archive_link, stream=True) with SpooledTemporaryFile(CHUNK_SIZE) as zipball: for chunk in download_archive.iter_content(CHUNK_SIZE): zipball.write(chunk) dockerfile_id = user_files.store_file(zipball, ZIPBALL) logger.debug('Successfully prepared job') return dockerfile_id, branch_name, commit_id