From 60ba328469b83698bc1d8cf995c05fe6c6ef5522 Mon Sep 17 00:00:00 2001 From: Loic Dachary Date: Thu, 28 Jan 2021 00:05:19 +0100 Subject: [PATCH] implement gitlab project get/create/delete --- fedeproxy/common/gitlab.py | 62 +++++++++++++++++++-------- fedeproxy/common/retry.py | 29 +++++++++++++ tests/conftest.py | 2 + tests/fedeproxy/common/test_gitlab.py | 7 ++- tests/fedeproxy/common/test_retry.py | 36 ++++++++++++++++ 5 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 fedeproxy/common/retry.py create mode 100644 tests/conftest.py create mode 100644 tests/fedeproxy/common/test_retry.py diff --git a/fedeproxy/common/gitlab.py b/fedeproxy/common/gitlab.py index ef621e0..4f227af 100644 --- a/fedeproxy/common/gitlab.py +++ b/fedeproxy/common/gitlab.py @@ -4,6 +4,8 @@ import os import requests import time +from fedeproxy.common.retry import retry + logger = logging.getLogger(__name__) @@ -38,8 +40,8 @@ class GitLab(object): def set_token(self, token): self.s.headers['Authorization'] = f'Bearer {token}' - def get_namespace(self, user): - r = self.s.get(self.s.api + '/namespaces?search=' + user) + def get_namespace_id(self, name): + r = self.s.get(self.s.api + '/namespaces?search=' + name) r.raise_for_status() return r.json()[0]['id'] @@ -57,23 +59,47 @@ class GitLab(object): user = r.json() return self.is_member_of_group(group, user['username']) - def recreate_project(self, user, project): - namespace_id = self.get_namespace(user) - r = self.s.get(self.s.api + '/projects/' + user + '%2F' + project) - if r.status_code == requests.codes.ok: - r = self.s.delete(self.s.api + '/projects/' + user + '%2F' + project) - r.raise_for_status() - for _ in range(10): - r = self.s.post(self.s.api + '/projects', data={ - "name": project, - "namespace_id": int(namespace_id), - "visibility": "public", - }) - time.sleep(5) - if r.status_code == 201: - break - print(str(r.text)) + def project_delete(self, namespace, project): + info = self.project_get(namespace, project) + if info is None: + return False + r = self.s.delete(f'{self.s.api}/projects/{info["id"]}') r.raise_for_status() + while self.project_get(namespace, project) is not None: + time.sleep(1) + return True + + def project_get(self, namespace, project): + r = self.s.get(f'{self.s.api}/projects/{namespace}%2F{project}') + if r.status_code == requests.codes.ok: + return r.json() + else: + return None + + class DeletionInProgress(Exception): + pass + + @retry(DeletionInProgress, tries=5) + def _project_create(self, namespace, project): + namespace_id = self.get_namespace_id(namespace) + data = { + "name": project, + "namespace_id": int(namespace_id), + "visibility": "public", + } + r = self.s.post(f'{self.s.api}/projects', data=data) + if r.status_code == 201: + return r.json() + if r.status_code == 400 and 'still being deleted' in r.text: + raise GitLab.DeletionInProgress() + r.raise_for_status() + + def project_create(self, namespace, project): + info = self.project_get(namespace, project) + if info is None: + return self._project_create(namespace, project) + else: + return info def create_api_application(self, domain): callbacks = [ diff --git a/fedeproxy/common/retry.py b/fedeproxy/common/retry.py new file mode 100644 index 0000000..0eaa94e --- /dev/null +++ b/fedeproxy/common/retry.py @@ -0,0 +1,29 @@ +import logging +import time +from functools import wraps + +logger = logging.getLogger(__name__) + + +class RetryException(Exception): + pass + + +def retry(exceptions, tries=2, delay=1): + def deco_retry(f): + @wraps(f) + def f_retry(*args, **kwargs): + mtries, mdelay = tries + 1, delay + while mtries > 0: + try: + return f(*args, **kwargs) + except exceptions as e: + logger.info('%s: %s, Retrying in %s seconds...', f.__qualname__, e, mdelay) + if mtries == tries + 1: + logger.debug("", exc_info=True) + time.sleep(mdelay) + mtries -= 1 + mdelay *= 2 + raise RetryException("Number of retries exceeded for function " + f.__name__) + return f_retry + return deco_retry diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..f03e673 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,2 @@ +def pytest_configure(config): + config.addinivalue_line("markers", "gitlab: mark tests which require live GitLab instances") diff --git a/tests/fedeproxy/common/test_gitlab.py b/tests/fedeproxy/common/test_gitlab.py index ac60e60..df83d2a 100644 --- a/tests/fedeproxy/common/test_gitlab.py +++ b/tests/fedeproxy/common/test_gitlab.py @@ -5,8 +5,11 @@ from fedeproxy.common.gitlab import GitLab @pytest.mark.gitlab -def test_recreate_project(tmpdir): +def test_create_project(tmpdir): ip = os.environ.get('FEDEPROXY_IP', '0.0.0.0') gitlab = GitLab(f'http://{ip}:8181') gitlab.login('root', 'Wrobyak4') - gitlab.recreate_project('root', 'testproject') + gitlab.project_delete('root', 'testproject') + assert gitlab.project_get('root', 'testproject') is None + p = gitlab.project_create('root', 'testproject') + assert p['id'] == gitlab.project_create('root', 'testproject')['id'] diff --git a/tests/fedeproxy/common/test_retry.py b/tests/fedeproxy/common/test_retry.py new file mode 100644 index 0000000..ab78264 --- /dev/null +++ b/tests/fedeproxy/common/test_retry.py @@ -0,0 +1,36 @@ +import pytest +from enough.common import retry + + +def test_that_retry_works_on_simple_function(): + + @retry.retry(Exception) + def f(): + return True + + assert f() + + +def test_that_retry_fails(): + + @retry.retry(AssertionError) + def f(): + assert 0 + + with pytest.raises(retry.RetryException): + f() + + +def test_that_retry_works_on_complex_function(): + class C(): + fail = 2 + + @retry.retry(Exception, tries=3) + def f(self): + self.fail -= 1 + assert self.fail == 0 + return True + + c = C() + assert c.f() + assert c.fail == 0