Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
197 lines (165 sloc) 6.3 KB
import re
import git
from concurrent.futures import ThreadPoolExecutor
from tornado import gen
import os
import os.path
class GitMixin:
STATE_VARS = ['_repo_url', '_repo_dir', '_repo_pointer', '_processed_repo_url',
'_protocol', '_service', '_owner', '_repo', '_token', '_repo_sha', '_branch_name']
def parse_url(self, repo_url, tmp_dir=None):
"""parse repo_url to parts:
_processed: url to clone from
_repo_pointer: position to reset"""
self._repo_url = repo_url
self._repo_dir = tmp_dir
self._repo_pointer = None
if re.search(r'@[\w/]+?$', repo_url):
self._processed_repo_url, self._repo_pointer = repo_url.rsplit('@', 1)
if self._repo_pointer.endswith('/'):
self._repo_pointer = self._repo_pointer[:-1]
else:
parts = re.match(
r'(^.+?://[^/]+/[^/]+/.+?)(?:/|$)(tree|commits?)?(/[^/]+)?', # github and bitbucket
repo_url
)
if not parts:
raise ValueError('Incorrect repository url')
self._processed_repo_url = parts.group(1)
if parts.group(3):
self._repo_pointer = parts.group(3)[1:]
if self._processed_repo_url.endswith('.git'):
self._processed_repo_url = self._processed_repo_url[:-4]
if not self._repo_pointer:
self._repo_pointer = 'HEAD'
url_parts = re.match(r'^(.+?)://([^/]+)/([^/]+)/(.+)/?$', self._processed_repo_url)
if not url_parts:
raise ValueError('Incorrect repository url')
self._protocol, self._service, self._owner, self._repo = url_parts.groups()
self._token = None
for not_supported_protocol in ('ssh',):
if not_supported_protocol in self._protocol.lower():
raise ValueError("%s isn't supported yet" % not_supported_protocol)
if '@' in self._service:
token, self._service = self._service.split('@')
if re.match(r'\w+:x-oauth-basic', token):
token, _ = token.split(':')
self._token = token
self._processed_repo_url = '{proto}://{service}/{owner}/{repo}'.format(
proto=self._protocol,
service=self._service,
owner=self._owner,
repo=self._repo
)
_git_executor = None
@property
def git_executor(self):
"""single global git executor"""
cls = self.__class__
if cls._git_executor is None:
cls._git_executor = ThreadPoolExecutor(20)
return cls._git_executor
_git_client = None
@property
def git_client(self):
"""single global git client instance"""
cls = self.__class__
if cls._git_client is None:
cls._git_client = git.Git()
return cls._git_client
def _git(self, method, *args, **kwargs):
"""wrapper for calling git methods
to be passed to ThreadPoolExecutor
"""
m = getattr(self.git_client, method)
return m(*args, **kwargs)
def git(self, method, *args, **kwargs):
"""Call a git method in a background thread
returns a Future
"""
return self.git_executor.submit(self._git, method, *args, **kwargs)
@gen.coroutine
def prepare_local_repo(self):
"""Returns False if there is no Dockerfile in repo
True otherwise
"""
clone_url = self.repo_url_with_token
yield self.git('clone', clone_url, self._repo_dir)
repo = git.Repo(self._repo_dir)
repo.git.reset('--hard', self._repo_pointer)
self._repo_sha = repo.rev_parse('HEAD').hexsha
self._branch_name = repo.active_branch.name
dockerfile_path = os.path.join(self._repo_dir, 'Dockerfile')
if not os.path.isfile(dockerfile_path):
if self.config_files_exist():
parent_image = 'everware/base:latest'
elif not os.environ.get('DEFAULT_DOCKER_IMAGE'):
raise Exception('No dockerfile in repository')
else:
parent_image = os.environ['DEFAULT_DOCKER_IMAGE']
with open(dockerfile_path, 'w') as fout:
fout.writelines([
'FROM %s\n' % parent_image
])
return False
else:
return True
def config_files_exist(self):
# uncomment when this functionality will be implemented
# for conf in ('everware.yml', 'requirements.txt', 'environment.yml'):
# cur_path = os.path.join(self._repo_dir, conf)
# if os.path.isfile(cur_path):
# return True
return False
@property
def escaped_repo_url(self):
repo_url = re.sub(r'^.+?://', '', self._processed_repo_url)
if repo_url.endswith('.git'):
repo_url = repo_url[:-4]
trans = str.maketrans(':/-.', "____")
repo_url = repo_url.translate(trans).lower()
return re.sub(r'_+', '_', repo_url)
@property
def repo_url(self):
return self._processed_repo_url
@property
def commit_sha(self):
return self._repo_sha
@property
def branch_name(self):
return self._branch_name
@property
def repo(self):
return self._repo
@property
def owner(self):
return self._owner
@property
def service(self):
return self._service
@property
def token(self):
return self._token
@property
def repo_url_with_token(self):
cur_service = self._service
token = None
if hasattr(self.user, 'token'):
token = self.user.token
elif hasattr(self.authenticator, 'test_token'):
token = self.authenticator.test_token
if token and self.user.escaped_name:
cur_service = self.user.escaped_name + ':' + token + '@' + self._service
return '{proto}://{service}/{owner}/{repo}'.format(
proto=self._protocol,
service=cur_service,
owner=self._owner,
repo=self._repo
)
def get_state(self):
result = {key: getattr(self, key, '') for key in self.STATE_VARS}
return result
def load_state(self, state):
for key in self.STATE_VARS:
if key in state:
setattr(self, key, state[key])