From 14e75364f49a6de23d767f479b0115aeb5150c83 Mon Sep 17 00:00:00 2001 From: Ben Brown Date: Mon, 18 Apr 2016 15:47:16 +0100 Subject: Add support for mirroring to a GitLab server Change-Id: I74dc0265fb3c92259101317d655eb55ccb62c119 --- lorry-controller-webapp | 13 +++++-- lorrycontroller/__init__.py | 3 +- lorrycontroller/gitlab.py | 84 ++++++++++++++++++++++++++++++++++++++++++++ lorrycontroller/givemejob.py | 16 ++++++++- 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 lorrycontroller/gitlab.py diff --git a/lorry-controller-webapp b/lorry-controller-webapp index ee573a2..7d4479c 100755 --- a/lorry-controller-webapp +++ b/lorry-controller-webapp @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (C) 2014 Codethink Limited +# Copyright (C) 2014-2016 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -131,9 +131,13 @@ class WEBAPP(cliapp.Application): self.settings.choice( ['git-server-type'], - ['gitano', 'gerrit'], + ['gitano', 'gerrit', 'gitlab'], 'what API the local Git server speaks') + self.settings.string( + ['gitlab-private-token'], + 'private token for GitLab API access') + def find_routes(self): '''Return all classes that are API routes. @@ -158,6 +162,11 @@ class WEBAPP(cliapp.Application): def process_args(self, args): self.settings.require('statedb') + if (self.settings['git-server-type'] == 'gitlab' and + not self.settings['gitlab-private-token']): + logging.error('A private token must be provided to create ' + 'repositories on a GitLab instance.') + self.settings.require('gitlab-private-token') self.setup_proxy() diff --git a/lorrycontroller/__init__.py b/lorrycontroller/__init__.py index 2c4a1c2..72696fa 100644 --- a/lorrycontroller/__init__.py +++ b/lorrycontroller/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2014 Codethink Limited +# Copyright (C) 2014-2016 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -45,6 +45,7 @@ from gitano import ( from static import StaticFile from proxy import setup_proxy from gerrit import Gerrit +from gitlab import Gitlab __all__ = locals() diff --git a/lorrycontroller/gitlab.py b/lorrycontroller/gitlab.py new file mode 100644 index 0000000..5c7e579 --- /dev/null +++ b/lorrycontroller/gitlab.py @@ -0,0 +1,84 @@ +# Copyright (C) 2016 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import absolute_import +import itertools +try: + import gitlab +except ImportError: + gitlab = None + + +class MissingGitlabModuleError(Exception): + pass + + +class Gitlab(object): + + '''Run commands on a GitLab instance. + + This uses the python wrapper around the GitLab API. + Use of the API requires the private token of a user with master access + to the targetted group. + + ''' + + def __init__(self, host, token): + if gitlab: + url = "http://" + host + self.gl = gitlab.Gitlab(url, token) + else: + raise MissingGitlabModuleError('gitlab module missing\n' + '\tpython-gitlab is required with GitLab as the git server') + + def first(self, predicate, iterable): + return next(itertools.ifilter(predicate, iterable)) + + def split_path(self, path): + return path.rsplit('/', 1) + + def find_project(self, group, project): + predicate = lambda x: x.namespace.name == group and x.name == project + + return self.first(predicate, self.gl.projects.search(project)) + + def has_project(self, repo_path): + group, project = self.split_path(repo_path) + + try: + return bool(self.find_project(group, project)) + except StopIteration: + return False + + def create_project(self, repo_path): + group_name, project_name = self.split_path(repo_path) + group = None + try: + group = self.gl.groups.get(group_name) + except gitlab.GitlabGetError as e: + if e.error_message == '404 Not found': + group = self.gl.groups.create( + {'name': group_name, 'path': group_name}) + else: + raise + + project = { + 'name': project_name, + 'public': True, + 'merge_requests_enabled': False, + 'namespace_id': group.id + } + self.gl.projects.create(project) + diff --git a/lorrycontroller/givemejob.py b/lorrycontroller/givemejob.py index 0d9e6ab..dd4de87 100644 --- a/lorrycontroller/givemejob.py +++ b/lorrycontroller/givemejob.py @@ -1,4 +1,4 @@ -# Copyright (C) 2014-2015 Codethink Limited +# Copyright (C) 2014-2016 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -68,6 +68,8 @@ class GiveMeJob(lorrycontroller.LorryControllerRoute): self.create_repository_in_local_trove(statedb, lorry_info) elif api == 'gerrit': self.create_gerrit_project(statedb, lorry_info) + elif api == 'gitlab': + self.create_gitlab_project(statedb, lorry_info) def create_repository_in_local_trove(self, statedb, lorry_info): # Create repository on local Trove. If it fails, assume @@ -101,6 +103,18 @@ class GiveMeJob(lorrycontroller.LorryControllerRoute): gerrit.create_project(project_name) logging.info('Created %s project in local Gerrit.', project_name) + def create_gitlab_project(self, statedb, lorry_info): + gitlab = lorrycontroller.Gitlab( + 'localhost', self.app_settings['gitlab-private-token']) + project_name = lorry_info['path'] + + if gitlab.has_project(project_name): + logging.info('Project %s exists in local GitLab already.', + project_name) + else: + gitlab.create_project(lorry_info['path']) + logging.info('Created %s project in local GitLab.', project_name) + def copy_repository_metadata(self, statedb, lorry_info): '''Copy project.head and project.description to the local Trove.''' -- cgit v1.2.1