Work in progress: bitbucket support
This commit is contained in:
parent
d180524b23
commit
c480fb2105
12 changed files with 321 additions and 86 deletions
|
@ -113,6 +113,9 @@ class DefaultConfig(object):
|
||||||
# Google Config.
|
# Google Config.
|
||||||
GOOGLE_LOGIN_CONFIG = None
|
GOOGLE_LOGIN_CONFIG = None
|
||||||
|
|
||||||
|
# Bitbucket Config.
|
||||||
|
BITBUCKET_TRIGGER_CONFIG = None
|
||||||
|
|
||||||
# Requests based HTTP client with a large request pool
|
# Requests based HTTP client with a large request pool
|
||||||
HTTPCLIENT = build_requests_session()
|
HTTPCLIENT = build_requests_session()
|
||||||
|
|
||||||
|
@ -151,6 +154,9 @@ class DefaultConfig(object):
|
||||||
# Feature Flag: Whether to support GitHub build triggers.
|
# Feature Flag: Whether to support GitHub build triggers.
|
||||||
FEATURE_GITHUB_BUILD = False
|
FEATURE_GITHUB_BUILD = False
|
||||||
|
|
||||||
|
# Feature Flag: Whether to support Bitbucket build triggers.
|
||||||
|
FEATURE_BITBUCKET_BUILD = False
|
||||||
|
|
||||||
# Feature Flag: Dockerfile build support.
|
# Feature Flag: Dockerfile build support.
|
||||||
FEATURE_BUILD_SUPPORT = True
|
FEATURE_BUILD_SUPPORT = True
|
||||||
|
|
||||||
|
|
|
@ -2433,12 +2433,21 @@ def log_action(kind_name, user_or_organization_name, performer=None,
|
||||||
datetime=timestamp)
|
datetime=timestamp)
|
||||||
|
|
||||||
|
|
||||||
def create_build_trigger(repo, service_name, auth_token, user, pull_robot=None):
|
def update_build_trigger(trigger, config, auth_token=None):
|
||||||
|
trigger.config = json.dumps(config or {})
|
||||||
|
if auth_token is not None:
|
||||||
|
trigger.auth_token = auth_token
|
||||||
|
trigger.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_build_trigger(repo, service_name, auth_token, user, pull_robot=None, config=None):
|
||||||
|
config = config or {}
|
||||||
service = BuildTriggerService.get(name=service_name)
|
service = BuildTriggerService.get(name=service_name)
|
||||||
trigger = RepositoryBuildTrigger.create(repository=repo, service=service,
|
trigger = RepositoryBuildTrigger.create(repository=repo, service=service,
|
||||||
auth_token=auth_token,
|
auth_token=auth_token,
|
||||||
connected_user=user,
|
connected_user=user,
|
||||||
pull_robot=pull_robot)
|
pull_robot=pull_robot,
|
||||||
|
config=json.dumps(config))
|
||||||
return trigger
|
return trigger
|
||||||
|
|
||||||
|
|
||||||
|
|
43
endpoints/bitbuckettrigger.py
Normal file
43
endpoints/bitbuckettrigger.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from flask import request, redirect, url_for, Blueprint
|
||||||
|
from flask.ext.login import current_user
|
||||||
|
|
||||||
|
from endpoints.trigger import BitbucketBuildTrigger
|
||||||
|
from endpoints.common import route_show_if
|
||||||
|
from app import app
|
||||||
|
from data import model
|
||||||
|
from util.names import parse_repository_name
|
||||||
|
from util.http import abort
|
||||||
|
from auth.auth import require_session_login
|
||||||
|
|
||||||
|
import features
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
client = app.config['HTTPCLIENT']
|
||||||
|
bitbuckettrigger = Blueprint('bitbuckettrigger', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@bitbuckettrigger.route('/bitbucket/callback/trigger/<trigger_uuid>', methods=['GET'])
|
||||||
|
@route_show_if(features.BITBUCKET_BUILD)
|
||||||
|
@require_session_login
|
||||||
|
def attach_bitbucket_build_trigger(trigger_uuid):
|
||||||
|
trigger = model.get_build_trigger(trigger_uuid)
|
||||||
|
if not trigger or trigger.service.name != BitbucketBuildTrigger.service_name():
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
if trigger.connected_user != current_user.db_user():
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
verifier = request.args.get('oauth_verifier')
|
||||||
|
result = BitbucketBuildTrigger.exchange_verifier(trigger, verifier)
|
||||||
|
print result
|
||||||
|
return 'hello'
|
||||||
|
|
||||||
|
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)
|
51
endpoints/githubtrigger.py
Normal file
51
endpoints/githubtrigger.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from flask import request, redirect, url_for, Blueprint
|
||||||
|
from flask.ext.login import current_user
|
||||||
|
|
||||||
|
from endpoints.common import route_show_if
|
||||||
|
from app import app, github_trigger
|
||||||
|
from data import model
|
||||||
|
from util.names import parse_repository_name
|
||||||
|
from util.http import abort
|
||||||
|
from auth.permissions import AdministerRepositoryPermission
|
||||||
|
from auth.auth import require_session_login
|
||||||
|
|
||||||
|
import features
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
client = app.config['HTTPCLIENT']
|
||||||
|
githubtrigger = Blueprint('callback', __name__)
|
||||||
|
|
||||||
|
@githubtrigger.route('/github/callback/trigger/<path:repository>', methods=['GET'])
|
||||||
|
@githubtrigger.route('/github/callback/trigger/<path:repository>/__new', methods=['GET'])
|
||||||
|
@route_show_if(features.GITHUB_BUILD)
|
||||||
|
@require_session_login
|
||||||
|
@parse_repository_name
|
||||||
|
def attach_github_build_trigger(namespace, repository):
|
||||||
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
|
if permission.can():
|
||||||
|
code = request.args.get('code')
|
||||||
|
token = github_trigger.exchange_code_for_token(app.config, client, code)
|
||||||
|
repo = model.get_repository(namespace, repository)
|
||||||
|
if not repo:
|
||||||
|
msg = 'Invalid repository: %s/%s' % (namespace, repository)
|
||||||
|
abort(404, message=msg)
|
||||||
|
|
||||||
|
trigger = model.create_build_trigger(repo, 'github', token, current_user.db_user())
|
||||||
|
|
||||||
|
# TODO(jschorr): Remove once the new layout is in place.
|
||||||
|
admin_path = '%s/%s/%s' % (namespace, repository, 'admin')
|
||||||
|
full_url = '%s%s%s' % (url_for('web.repository', path=admin_path), '?tab=trigger&new_trigger=',
|
||||||
|
trigger.uuid)
|
||||||
|
|
||||||
|
if '__new' in request.url:
|
||||||
|
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)
|
||||||
|
|
||||||
|
abort(403)
|
|
@ -5,23 +5,19 @@ from flask import request, redirect, url_for, Blueprint
|
||||||
from flask.ext.login import current_user
|
from flask.ext.login import current_user
|
||||||
|
|
||||||
from endpoints.common import render_page_template, common_login, route_show_if
|
from endpoints.common import render_page_template, common_login, route_show_if
|
||||||
from app import app, analytics, get_app_url, github_login, google_login, github_trigger
|
from app import app, analytics, get_app_url, github_login, google_login
|
||||||
from data import model
|
from data import model
|
||||||
from util.names import parse_repository_name
|
from util.names import parse_repository_name
|
||||||
from util.validation import generate_valid_usernames
|
from util.validation import generate_valid_usernames
|
||||||
from util.http import abort
|
from util.http import abort
|
||||||
from auth.permissions import AdministerRepositoryPermission
|
|
||||||
from auth.auth import require_session_login
|
from auth.auth import require_session_login
|
||||||
from peewee import IntegrityError
|
from peewee import IntegrityError
|
||||||
|
|
||||||
import features
|
import features
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
client = app.config['HTTPCLIENT']
|
client = app.config['HTTPCLIENT']
|
||||||
|
oauthlogin = Blueprint('oauthlogin', __name__)
|
||||||
|
|
||||||
callback = Blueprint('callback', __name__)
|
|
||||||
|
|
||||||
def render_ologin_error(service_name,
|
def render_ologin_error(service_name,
|
||||||
error_message='Could not load user data. The token may have expired.'):
|
error_message='Could not load user data. The token may have expired.'):
|
||||||
|
@ -30,36 +26,6 @@ def render_ologin_error(service_name,
|
||||||
service_url=get_app_url(),
|
service_url=get_app_url(),
|
||||||
user_creation=features.USER_CREATION)
|
user_creation=features.USER_CREATION)
|
||||||
|
|
||||||
def exchange_code_for_token(code, service, form_encode=False, redirect_suffix=''):
|
|
||||||
code = request.args.get('code')
|
|
||||||
payload = {
|
|
||||||
'client_id': service.client_id(),
|
|
||||||
'client_secret': service.client_secret(),
|
|
||||||
'code': code,
|
|
||||||
'grant_type': 'authorization_code',
|
|
||||||
'redirect_uri': '%s://%s/oauth2/%s/callback%s' % (app.config['PREFERRED_URL_SCHEME'],
|
|
||||||
app.config['SERVER_HOSTNAME'],
|
|
||||||
service.service_name().lower(),
|
|
||||||
redirect_suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
token_url = service.token_endpoint()
|
|
||||||
if form_encode:
|
|
||||||
get_access_token = client.post(token_url, data=payload, headers=headers)
|
|
||||||
else:
|
|
||||||
get_access_token = client.post(token_url, params=payload, headers=headers)
|
|
||||||
|
|
||||||
json_data = get_access_token.json()
|
|
||||||
if not json_data:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
token = json_data.get('access_token', '')
|
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
def get_user(service, token):
|
def get_user(service, token):
|
||||||
token_param = {
|
token_param = {
|
||||||
|
@ -129,14 +95,15 @@ def get_google_username(user_data):
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
|
||||||
@callback.route('/google/callback', methods=['GET'])
|
@oauthlogin.route('/google/callback', methods=['GET'])
|
||||||
@route_show_if(features.GOOGLE_LOGIN)
|
@route_show_if(features.GOOGLE_LOGIN)
|
||||||
def google_oauth_callback():
|
def google_oauth_callback():
|
||||||
error = request.args.get('error', None)
|
error = request.args.get('error', None)
|
||||||
if error:
|
if error:
|
||||||
return render_ologin_error('Google', error)
|
return render_ologin_error('Google', error)
|
||||||
|
|
||||||
token = exchange_code_for_token(request.args.get('code'), google_login, form_encode=True)
|
code = request.args.get('code')
|
||||||
|
token = google_login.exchange_code_for_token(app.config, client, code, form_encode=True)
|
||||||
user_data = get_user(google_login, token)
|
user_data = get_user(google_login, token)
|
||||||
if not user_data or not user_data.get('id', None) or not user_data.get('email', None):
|
if not user_data or not user_data.get('id', None) or not user_data.get('email', None):
|
||||||
return render_ologin_error('Google')
|
return render_ologin_error('Google')
|
||||||
|
@ -150,7 +117,7 @@ def google_oauth_callback():
|
||||||
metadata=metadata)
|
metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
@callback.route('/github/callback', methods=['GET'])
|
@oauthlogin.route('/github/callback', methods=['GET'])
|
||||||
@route_show_if(features.GITHUB_LOGIN)
|
@route_show_if(features.GITHUB_LOGIN)
|
||||||
def github_oauth_callback():
|
def github_oauth_callback():
|
||||||
error = request.args.get('error', None)
|
error = request.args.get('error', None)
|
||||||
|
@ -158,7 +125,8 @@ def github_oauth_callback():
|
||||||
return render_ologin_error('GitHub', error)
|
return render_ologin_error('GitHub', error)
|
||||||
|
|
||||||
# Exchange the OAuth code.
|
# Exchange the OAuth code.
|
||||||
token = exchange_code_for_token(request.args.get('code'), github_login)
|
code = request.args.get('code')
|
||||||
|
token = google_login.exchange_code_for_token(app.config, client, code)
|
||||||
|
|
||||||
# Retrieve the user's information.
|
# Retrieve the user's information.
|
||||||
user_data = get_user(github_login, token)
|
user_data = get_user(github_login, token)
|
||||||
|
@ -211,11 +179,12 @@ def github_oauth_callback():
|
||||||
return conduct_oauth_login(github_login, github_id, username, found_email, metadata=metadata)
|
return conduct_oauth_login(github_login, github_id, username, found_email, metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
@callback.route('/google/callback/attach', methods=['GET'])
|
@oauthlogin.route('/google/callback/attach', methods=['GET'])
|
||||||
@route_show_if(features.GOOGLE_LOGIN)
|
@route_show_if(features.GOOGLE_LOGIN)
|
||||||
@require_session_login
|
@require_session_login
|
||||||
def google_oauth_attach():
|
def google_oauth_attach():
|
||||||
token = exchange_code_for_token(request.args.get('code'), google_login,
|
code = request.args.get('code')
|
||||||
|
token = google_login.exchange_code_for_token(app.config, client, code,
|
||||||
redirect_suffix='/attach', form_encode=True)
|
redirect_suffix='/attach', form_encode=True)
|
||||||
|
|
||||||
user_data = get_user(google_login, token)
|
user_data = get_user(google_login, token)
|
||||||
|
@ -240,11 +209,12 @@ def google_oauth_attach():
|
||||||
return redirect(url_for('web.user'))
|
return redirect(url_for('web.user'))
|
||||||
|
|
||||||
|
|
||||||
@callback.route('/github/callback/attach', methods=['GET'])
|
@oauthlogin.route('/github/callback/attach', methods=['GET'])
|
||||||
@route_show_if(features.GITHUB_LOGIN)
|
@route_show_if(features.GITHUB_LOGIN)
|
||||||
@require_session_login
|
@require_session_login
|
||||||
def github_oauth_attach():
|
def github_oauth_attach():
|
||||||
token = exchange_code_for_token(request.args.get('code'), github_login)
|
code = request.args.get('code')
|
||||||
|
token = google_login.exchange_code_for_token(app.config, client, code)
|
||||||
user_data = get_user(github_login, token)
|
user_data = get_user(github_login, token)
|
||||||
if not user_data:
|
if not user_data:
|
||||||
return render_ologin_error('GitHub')
|
return render_ologin_error('GitHub')
|
||||||
|
@ -266,36 +236,3 @@ def github_oauth_attach():
|
||||||
return render_ologin_error('GitHub', err)
|
return render_ologin_error('GitHub', err)
|
||||||
|
|
||||||
return redirect(url_for('web.user'))
|
return redirect(url_for('web.user'))
|
||||||
|
|
||||||
|
|
||||||
@callback.route('/github/callback/trigger/<path:repository>', methods=['GET'])
|
|
||||||
@callback.route('/github/callback/trigger/<path:repository>/__new', methods=['GET'])
|
|
||||||
@route_show_if(features.GITHUB_BUILD)
|
|
||||||
@require_session_login
|
|
||||||
@parse_repository_name
|
|
||||||
def attach_github_build_trigger(namespace, repository):
|
|
||||||
permission = AdministerRepositoryPermission(namespace, repository)
|
|
||||||
if permission.can():
|
|
||||||
token = exchange_code_for_token(request.args.get('code'), github_trigger)
|
|
||||||
repo = model.get_repository(namespace, repository)
|
|
||||||
if not repo:
|
|
||||||
msg = 'Invalid repository: %s/%s' % (namespace, repository)
|
|
||||||
abort(404, message=msg)
|
|
||||||
|
|
||||||
trigger = model.create_build_trigger(repo, 'github', token, current_user.db_user())
|
|
||||||
|
|
||||||
# TODO(jschorr): Remove once the new layout is in place.
|
|
||||||
admin_path = '%s/%s/%s' % (namespace, repository, 'admin')
|
|
||||||
full_url = '%s%s%s' % (url_for('web.repository', path=admin_path), '?tab=trigger&new_trigger=',
|
|
||||||
trigger.uuid)
|
|
||||||
|
|
||||||
if '__new' in request.url:
|
|
||||||
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)
|
|
||||||
|
|
||||||
abort(403)
|
|
|
@ -7,10 +7,11 @@ import re
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from github import Github, UnknownObjectException, GithubException
|
from github import Github, UnknownObjectException, GithubException
|
||||||
|
from bitbucket.bitbucket import Bitbucket
|
||||||
from tempfile import SpooledTemporaryFile
|
from tempfile import SpooledTemporaryFile
|
||||||
from jsonschema import validate
|
from jsonschema import validate
|
||||||
|
|
||||||
from app import app, userfiles as user_files, github_trigger
|
from app import app, userfiles as user_files, github_trigger, get_app_url
|
||||||
from util.tarfileappender import TarfileAppender
|
from util.tarfileappender import TarfileAppender
|
||||||
from util.ssh import generate_ssh_keypair
|
from util.ssh import generate_ssh_keypair
|
||||||
|
|
||||||
|
@ -58,6 +59,9 @@ class EmptyRepositoryException(Exception):
|
||||||
class RepositoryReadException(Exception):
|
class RepositoryReadException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class TriggerProviderException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BuildTrigger(object):
|
class BuildTrigger(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -158,6 +162,87 @@ def get_trigger_config(trigger):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class BitbucketBuildTrigger(BuildTrigger):
|
||||||
|
"""
|
||||||
|
BuildTrigger for Bitbucket.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def service_name(cls):
|
||||||
|
return 'bitbucket'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_authorized_client(trigger_uuid):
|
||||||
|
key = app.config.get('BITBUCKET_TRIGGER_CONFIG', {}).get('CONSUMER_KEY', '')
|
||||||
|
secret = app.config.get('BITBUCKET_TRIGGER_CONFIG', {}).get('CONSUMER_SECRET', '')
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
url = bitbucket_client.url('AUTHENTICATE', token=bitbucket_client.access_token)
|
||||||
|
return {
|
||||||
|
'access_token': bitbucket_client.access_token,
|
||||||
|
'access_token_secret': bitbucket_client.access_token_secret,
|
||||||
|
'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)
|
||||||
|
|
||||||
|
#if not result:
|
||||||
|
# return None
|
||||||
|
|
||||||
|
#return (bitbucket_client.access_token, bitbucket_client.access_token_secret)
|
||||||
|
|
||||||
|
def is_active(self, config):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def activate(self, trigger_uuid, standard_webhook_url, auth_token, config):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def deactivate(self, auth_token, config):
|
||||||
|
return config
|
||||||
|
|
||||||
|
def list_build_sources(self, auth_token):
|
||||||
|
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(BuildTrigger):
|
||||||
"""
|
"""
|
||||||
BuildTrigger for GitHub that uses the archive API and buildpacks.
|
BuildTrigger for GitHub that uses the archive API and buildpacks.
|
||||||
|
|
|
@ -20,7 +20,7 @@ from util.cache import no_cache
|
||||||
from endpoints.common import common_login, render_page_template, route_show_if, param_required
|
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.csrf import csrf_protect, generate_csrf_token, verify_csrf
|
||||||
from endpoints.registry import set_cache_headers
|
from endpoints.registry import set_cache_headers
|
||||||
from endpoints.trigger import CustomBuildTrigger
|
from endpoints.trigger import CustomBuildTrigger, BitbucketBuildTrigger, TriggerProviderException
|
||||||
from util.names import parse_repository_name, parse_repository_name_and_tag
|
from util.names import parse_repository_name, parse_repository_name_and_tag
|
||||||
from util.useremails import send_email_changed
|
from util.useremails import send_email_changed
|
||||||
from util.systemlogs import build_logs_archive
|
from util.systemlogs import build_logs_archive
|
||||||
|
@ -495,6 +495,41 @@ def download_logs_archive():
|
||||||
|
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
|
@web.route('/bitbucket/setup/<path:repository>', methods=['GET'])
|
||||||
|
@require_session_login
|
||||||
|
@parse_repository_name
|
||||||
|
@route_show_if(features.BITBUCKET_BUILD)
|
||||||
|
def attach_bitbucket_trigger(namespace, repository_name):
|
||||||
|
permission = AdministerRepositoryPermission(namespace, repository_name)
|
||||||
|
if permission.can():
|
||||||
|
repo = model.get_repository(namespace, repository_name)
|
||||||
|
if not repo:
|
||||||
|
msg = 'Invalid repository: %s/%s' % (namespace, repository_name)
|
||||||
|
abort(404, message=msg)
|
||||||
|
|
||||||
|
trigger = model.create_build_trigger(repo, BitbucketBuildTrigger.service_name(),
|
||||||
|
None,
|
||||||
|
current_user.db_user())
|
||||||
|
|
||||||
|
try:
|
||||||
|
oauth_info = BitbucketBuildTrigger.get_oauth_url(trigger.uuid)
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'access_token': oauth_info['access_token']
|
||||||
|
}
|
||||||
|
|
||||||
|
access_token_secret = oauth_info['access_token_secret']
|
||||||
|
model.update_build_trigger(trigger, config, auth_token=access_token_secret)
|
||||||
|
|
||||||
|
return redirect(oauth_info['url'])
|
||||||
|
except TriggerProviderException:
|
||||||
|
trigger.delete_instance()
|
||||||
|
abort(400, message='Could not retrieve OAuth URL from Bitbucket')
|
||||||
|
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
@web.route('/customtrigger/setup/<path:repository>', methods=['GET'])
|
@web.route('/customtrigger/setup/<path:repository>', methods=['GET'])
|
||||||
@require_session_login
|
@require_session_login
|
||||||
@parse_repository_name
|
@parse_repository_name
|
||||||
|
|
|
@ -204,6 +204,7 @@ def initialize_database():
|
||||||
|
|
||||||
BuildTriggerService.create(name='github')
|
BuildTriggerService.create(name='github')
|
||||||
BuildTriggerService.create(name='custom-git')
|
BuildTriggerService.create(name='custom-git')
|
||||||
|
BuildTriggerService.create(name='bitbucket')
|
||||||
|
|
||||||
AccessTokenKind.create(name='build-worker')
|
AccessTokenKind.create(name='build-worker')
|
||||||
AccessTokenKind.create(name='pushpull-token')
|
AccessTokenKind.create(name='pushpull-token')
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
<b class="caret"></b>
|
<b class="caret"></b>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right pull-right">
|
<ul class="dropdown-menu dropdown-menu-right pull-right">
|
||||||
<li ng-repeat="type in TriggerService.getTypes()">
|
<li ng-repeat="type in TriggerService.getTypes()" ng-if="TriggerService.isEnabled(type)">
|
||||||
<a href="{{ TriggerService.getRedirectUrl(type, repository.namespace, repository.name) }}" target="{{ TriggerService.getMetadata(type).is_external ? '' : '_self' }}">
|
<a href="{{ TriggerService.getRedirectUrl(type, repository.namespace, repository.name) }}" target="{{ TriggerService.getMetadata(type).is_external ? '' : '_self' }}">
|
||||||
<i class="fa fa-lg" ng-class="TriggerService.getMetadata(type).icon"></i>
|
<i class="fa fa-lg" ng-class="TriggerService.getMetadata(type).icon"></i>
|
||||||
{{ TriggerService.getTitle(type) }}
|
{{ TriggerService.getTitle(type) }}
|
||||||
|
|
|
@ -52,6 +52,32 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'bitbucket': {
|
||||||
|
'description': function(config) {
|
||||||
|
var source = UtilService.textToSafeHtml(config['build_source']);
|
||||||
|
var desc = '<i class="fa fa-bitbucket fa-lg" style="margin-left: 2px; margin-right: 2px"></i> Push to Bitbucket Repository ';
|
||||||
|
desc += '<a href="https://bitbucket.org/' + source + '" target="_blank">' + source + '</a>';
|
||||||
|
desc += '<br>Dockerfile folder: //' + UtilService.textToSafeHtml(config['subdir']);
|
||||||
|
return desc;
|
||||||
|
},
|
||||||
|
'run_parameters': [
|
||||||
|
{
|
||||||
|
'title': 'Branch',
|
||||||
|
'type': 'option',
|
||||||
|
'name': 'branch_name'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'get_redirect_url': function(namespace, repository) {
|
||||||
|
return Config.getUrl('/bitbucket/setup/' + namespace + '/' + repository);
|
||||||
|
},
|
||||||
|
'is_external': false,
|
||||||
|
'is_enabled': function() {
|
||||||
|
return Features.BITBUCKET_BUILD;
|
||||||
|
},
|
||||||
|
'icon': 'fa-bitbucket',
|
||||||
|
'title': function() { return 'Bitbucket Repository Push'; }
|
||||||
|
},
|
||||||
|
|
||||||
'custom-git': {
|
'custom-git': {
|
||||||
'description': function(config) {
|
'description': function(config) {
|
||||||
var source = UtilService.textToSafeHtml(config['build_source']);
|
var source = UtilService.textToSafeHtml(config['build_source']);
|
||||||
|
@ -104,6 +130,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
||||||
return '//' + trigger.config.subdir.replace(new RegExp('(^\/+|\/+$)'), '') + '/Dockerfile';
|
return '//' + trigger.config.subdir.replace(new RegExp('(^\/+|\/+$)'), '') + '/Dockerfile';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
triggerService.isEnabled = function(name) {
|
||||||
|
var type = triggerTypes[name];
|
||||||
|
if (!type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return type['is_enabled']();
|
||||||
|
};
|
||||||
|
|
||||||
triggerService.getTitle = function(name) {
|
triggerService.getTitle = function(name) {
|
||||||
var type = triggerTypes[name];
|
var type = triggerTypes[name];
|
||||||
if (!type) {
|
if (!type) {
|
||||||
|
|
|
@ -33,6 +33,36 @@ class OAuthConfig(object):
|
||||||
|
|
||||||
return endpoint
|
return endpoint
|
||||||
|
|
||||||
|
def exchange_code_for_token(self, app_config, http_client, code, form_encode=False,
|
||||||
|
redirect_suffix=''):
|
||||||
|
payload = {
|
||||||
|
'client_id': self.client_id(),
|
||||||
|
'client_secret': self.client_secret(),
|
||||||
|
'code': code,
|
||||||
|
'grant_type': 'authorization_code',
|
||||||
|
'redirect_uri': '%s://%s/oauth2/%s/callback%s' % (app_config['PREFERRED_URL_SCHEME'],
|
||||||
|
app_config['SERVER_HOSTNAME'],
|
||||||
|
self.service_name().lower(),
|
||||||
|
redirect_suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
token_url = self.token_endpoint()
|
||||||
|
if form_encode:
|
||||||
|
get_access_token = http_client.post(token_url, data=payload, headers=headers)
|
||||||
|
else:
|
||||||
|
get_access_token = http_client.post(token_url, params=payload, headers=headers)
|
||||||
|
|
||||||
|
json_data = get_access_token.json()
|
||||||
|
if not json_data:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
token = json_data.get('access_token', '')
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
class GithubOAuthConfig(OAuthConfig):
|
class GithubOAuthConfig(OAuthConfig):
|
||||||
def __init__(self, config, key_name):
|
def __init__(self, config, key_name):
|
||||||
|
|
8
web.py
8
web.py
|
@ -7,10 +7,14 @@ from endpoints.api import api_bp
|
||||||
from endpoints.web import web
|
from endpoints.web import web
|
||||||
from endpoints.webhooks import webhooks
|
from endpoints.webhooks import webhooks
|
||||||
from endpoints.realtime import realtime
|
from endpoints.realtime import realtime
|
||||||
from endpoints.callbacks import callback
|
from endpoints.oauthlogin import oauthlogin
|
||||||
|
from endpoints.githubtrigger import githubtrigger
|
||||||
|
from endpoints.bitbuckettrigger import bitbuckettrigger
|
||||||
|
|
||||||
application.register_blueprint(web)
|
application.register_blueprint(web)
|
||||||
application.register_blueprint(callback, url_prefix='/oauth2')
|
application.register_blueprint(githubtrigger, url_prefix='/oauth2')
|
||||||
|
application.register_blueprint(oauthlogin, url_prefix='/oauth2')
|
||||||
|
application.register_blueprint(bitbuckettrigger, url_prefix='/oauth1')
|
||||||
application.register_blueprint(api_bp, url_prefix='/api')
|
application.register_blueprint(api_bp, url_prefix='/api')
|
||||||
application.register_blueprint(webhooks, url_prefix='/webhooks')
|
application.register_blueprint(webhooks, url_prefix='/webhooks')
|
||||||
application.register_blueprint(realtime, url_prefix='/realtime')
|
application.register_blueprint(realtime, url_prefix='/realtime')
|
||||||
|
|
Reference in a new issue