# Copyright (C) 2016-2019 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. ''' 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. ''' import logging import re import urllib.parse try: import gitlab except ImportError: gitlab = None from . import hosts class MissingGitlabModuleError(Exception): pass def _init_gitlab(host, token): if gitlab: url = "http://" + host return gitlab.Gitlab(url, token) else: raise MissingGitlabModuleError('gitlab module missing\n' '\tpython-gitlab is required with GitLab as the git server') class GitlabDownstream(hosts.DownstreamHost): @staticmethod def add_app_settings(app_settings): app_settings.string( ['gitlab-private-token'], 'private token for GitLab API access') @staticmethod def check_app_settings(app_settings): if not app_settings['gitlab-private-token']: logging.error('A private token must be provided to create ' 'repositories on a GitLab instance.') app_settings.require('gitlab-private-token') def __init__(self, app_settings): # XXX This needs to be configurable host = 'localhost' self.gl = _init_gitlab(host, app_settings['gitlab-private-token']) def _has_project(self, repo_path): try: self.gl.projects.get(repo_path) return True except gitlab.GitlabGetError: return False def prepare_repo(self, repo_path, metadata): # TODO: set metadata if self._has_project(repo_path): logging.info('Project %s exists in local GitLab already.', repo_path) return path_comps = repo_path.split('/') if len(path_comps) < 2: raise ValueError('cannot create GitLab project outside a group') # Create hierarchy of groups as necessary parent_group = None for group_name in path_comps[:-1]: if parent_group is None: group_path = group_name else: group_path = parent_group.full_path + '/' + group_name try: group = self.gl.groups.get(group_path) except gitlab.GitlabGetError as e: if e.response_code != 404: raise data = {'name': group_name, 'path': group_name} if parent_group is not None: data['parent_id'] = parent_group.id group = self.gl.groups.create(data) parent_group = group project = { 'name': path_comps[-1], 'public': True, 'merge_requests_enabled': False, 'namespace_id': group.id, } self.gl.projects.create(project) logging.info('Created %s project in local GitLab.', repo_path) class GitlabUpstream(hosts.UpstreamHost): @staticmethod def check_host_type_params(validator, section): validator.check_has_required_fields(section, ['private-token']) @staticmethod def get_host_type_params(section): return {'private-token': section['private-token']} def __init__(self, host_info): self._protocol = host_info['protocol'] self.gl = _init_gitlab(host_info['host'], host_info['type_params']['private-token']) def list_repos(self): '''List projects on a GitLab instance.''' return [x.path_with_namespace for x in self.gl.projects.list()] def get_repo_url(self, repo_path): '''Return the clone url for a GitLab project. Depending on the protocol specified, will return a suitable clone url. If 'ssh', a url in the format 'git@host:group/project.git' will be returned. If 'http' or 'https', the http_url_to_repo from the GitLab API is split with urlparse into its constituent parts: the protocol (http by default), the host, and the path ('group/project.git'). This is then rejoined, replacing the protocol with what is specified. The resulting format matching 'http(s)://host/group/project.git'. ''' project = self.gl.projects.get(repo_path) if self._protocol == 'ssh': return project.ssh_url_to_repo elif self._protocol in ('http', 'https'): split = urllib.parse.urlsplit(project.http_url_to_repo) return urllib.parse.urlunsplit(( self._protocol, split.netloc, split.path, '', '')) def get_repo_metadata(self, repo_path): # TODO return {}