Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions bloom/commands/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@
from bloom.github import GithubException
from bloom.github import GitHubAuthException

from bloom.gitlab import Gitlab
from bloom.gitlab import GitlabException
from bloom.gitlab import GitlabAuthException

from bloom.logging import debug
from bloom.logging import error
from bloom.logging import fmt
Expand Down Expand Up @@ -683,13 +687,30 @@ def get_gh_info(url):
'path': '/'.join(url_paths[4:])}


def get_gl_info(url):
o = urlparse(url)
if 'gitlab' not in o.netloc:
return None
url_paths = o.path.split('/')
if len(url_paths) < 6:
return None
return {'server': '{}://{}'.format(o.scheme, o.netloc),
'org': url_paths[1],
'repo': url_paths[2],
'branch': url_paths[4],
'path': '/'.join(url_paths[5:])}


def get_repo_info(distro_url):
gh_info = get_gh_info(distro_url)
if gh_info:
return gh_info
else:
return get_gl_info(distro_url)


_gh = None
_gl = None


def get_github_interface(quiet=False):
Expand Down Expand Up @@ -769,6 +790,52 @@ def mfa_prompt(oauth_config_path, username):
return gh


def get_gitlab_interface(server, quiet=False):
global _gl
if _gl is not None:
return _gl

config, oauth_config_path = get_bloom_config_and_path()
if 'gitlab' in config:
_gl = Gitlab(server, token=config['gitlab'])
return _gl

if quiet:
return None

info("")
warning("Looks like bloom doesn't have a gitlab token for you yet.")
warning("Go to http://{}/profile/personal_access_tokens to create one.".format(server))
warning("Make sure you give it API access.")
warning("The token will be stored in `~/.config/bloom`.")
warning("You can delete the token from that file to have a new token generated.")
warning("Guard this token like a password, because it allows someone/something to act on your behalf.")
info("")
if not maybe_continue('y', "Would you like to input a token now"):
return None
token = None
while token is None:
try:
token = safe_input("Gitlab Token: ")
except (KeyboardInterrupt, EOFError):
return None
try:
gl = Gitlab(server, token=token)
gl.auth()
with open(oauth_config_path, 'w') as f:
config.update({'gitlab': token})
f.write(json.dumps(config))
info("The token was stored in the bloom config file")
_gl = gl
break
except GitlabAuthException:
error("Failed to authenticate your token.")
if not maybe_continue():
return None

return _gl


def get_changelog_summary(release_tag):
summary = u""
packages = dict([(p.name, p) for p in get_packages().values()])
Expand Down Expand Up @@ -948,6 +1015,27 @@ def _my_run(cmd, msg=None):
# Open the pull request
return gh.create_pull_request(base_info['org'], base_info['repo'], base_info['branch'],
head_org, new_branch, title, body)
else: # if base_info['server'] == 'github.com':
gl = get_gitlab_interface(server)
if gl is None:
return None

repo_obj = gl.get_repo(base_org, base_repo)

# Determine New Branch Name
branches = gl.list_branches(repo_obj)
new_branch = 'bloom-{repository}-{count}'
count = 0
while new_branch.format(repository=repository, count=count) in branches:
count += 1
new_branch = new_branch.format(repository=repository, count=count)

gl.create_branch(repo_obj, new_branch, base_branch)
gl.update_file(repo_obj, new_branch, title, base_path, updated_distro_file_yaml)

mr = gl.create_pull_request(repo_obj, new_branch, base_branch, title, body)
return mr['web_url']


_original_version = None

Expand Down
90 changes: 90 additions & 0 deletions bloom/gitlab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import requests


class GitlabException(Exception):
def __init__(self, msg, code=None):
if code:
msg = "{msg}: {code}".format(**locals())
super(GitlabException, self).__init__(msg)


class GitlabAuthException(GitlabException):
def __init__(self, msg, code=None):
super(GitlabAuthException, self).__init__(msg, code)


class Gitlab(object):
def __init__(self, server, token=None):
self.server = server
self.token = token
self.api_version = 4
self.base_api_url = 'http://{server}/api/v{api_version}'.format(server=server, api_version=self.api_version)

def update_params(self, params):
if self.token:
if params is None:
params = {}
params['private_token'] = self.token
return params

def api_get(self, query, params=None):
r = requests.get(self.base_api_url + query, params=self.update_params(params))
if r.status_code == 401:
raise GitlabAuthException('Authentication Failed', r.status_code)
elif r.status_code == 200:
return r.json()
else:
raise GitlabException('Query {} failed.'.format(query), r.status_code)

def api_post(self, query, params=None):
r = requests.post(self.base_api_url + query, params=self.update_params(params))
if r.status_code == 401:
raise GitlabAuthException('Authentication Failed', r.status_code)
elif r.status_code == 201:
return r.json()
else:
raise GitlabException('Query {} failed.'.format(query), r.status_code)

def auth(self):
""" Authenticate by trying to get the projects """
self.api_get('/projects')

def get_repo(self, owner, repo):
path = '{}/{}'.format(owner, repo)
for repo_d in self.api_get('/projects', {'search': repo}):
if repo_d.get('path_with_namespace', '') == path:
return repo_d

def list_branches(self, repo):
res = self.api_get('/projects/{}/repository/branches'.format(repo['id']))
return [d['name'] for d in res]

def create_branch(self, repo, new_branch, base_branch):
params = {'branch': new_branch, 'ref': base_branch}
return self.api_post('/projects/{}/repository/branches'.format(repo['id']), params)

def create_commit(self, repo, branch, commit_message, actions):
params = {
'branch': branch,
'commit_message': commit_message,
'actions': actions
}
return self.api_post('/projects/{}/repository/commits'.format(repo['id']), params)

def update_file(self, repo, branch, commit_message, file_path, new_contents):
actions = [{
'action': 'update',
'file_path': file_path,
'content': new_contents
}
]
return self.create_commit(repo, branch, commit_message, actions)

def create_pull_request(self, repo, source_branch, target_branch, title, body=''):
params = {
'source_branch': source_branch,
'target_branch': target_branch,
'title': title,
'description': body
}
return self.api_post('/projects/{}/merge_requests'.format(repo['id']), params)
5 changes: 3 additions & 2 deletions stdeb.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[DEFAULT]
Depends: python-yaml, python-empy, python-argparse, python-rosdep (>= 0.10.25), python-rosdistro (>= 0.7.0), python-vcstools (>= 0.1.22), python-setuptools, python-catkin-pkg (>= 0.4.3)
Depends3: python3-yaml, python3-empy, python3-rosdep (>= 0.10.25), python3-rosdistro (>= 0.7.0), python3-vcstools (>= 0.1.22), python3-setuptools, python3-catkin-pkg (>= 0.4.3)

Depends: python-yaml, python-empy, python-argparse, python-rosdep (>= 0.10.25), python-rosdistro (>= 0.7.0), python-vcstools (>= 0.1.22), python-setuptools, python-catkin-pkg (>= 0.4.3), python-requests (>= 2.2)
Depends3: python3-yaml, python3-empy, python3-rosdep (>= 0.10.25), python3-rosdistro (>= 0.7.0), python3-vcstools (>= 0.1.22), python3-setuptools, python3-catkin-pkg (>= 0.4.3), python3-requests (>= 2.2)
Conflicts: python3-bloom
Conflicts3: python-bloom
Copyright-File: LICENSE.txt
Expand Down