Skip to content

Commit 25f7635

Browse files
authored
Merge pull request #17 from lewagon/git-repos
add git and gh helper classes
2 parents 5da9bc8 + 787c4f5 commit 25f7635

22 files changed

Lines changed: 748 additions & 38 deletions

.env.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GH_API_DELETE_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

.github/workflows/run-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@ jobs:
2323
pip install -e .
2424
2525
- name: Install package and test
26+
env:
27+
GH_API_DELETE_TOKEN: ${{ secrets.AUTOBOT_QA_ADMIN_TOKEN }}
2628
run: |
2729
make pytest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ __pycache__
33
*.egg-info
44
.DS_Store
55
dist
6+
.env

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11

2-
# 0.2.9 (2022-11-25)
2+
# 0.2.10 (2022-11-25)
3+
4+
### Added
5+
6+
- Adds `GitRepo`, `GhOrg`, `GhOrgSecret` and `GhRepo` helper classes
7+
- Adds tests for the `GhRepo` class
8+
- Adds `subblack`, `subred`, `subgreen`, `subyellow`, `subblue`, `submagenta`, `subcyan`, and `subwhite` helpers for `colorama`
39

410
### Added
511

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ pylint:
55

66
pytest:
77
PYTHONDONTWRITEBYTECODE=1 pytest -v --color=yes
8+
9+
pytest_output:
10+
PYTHONDONTWRITEBYTECODE=1 pytest -v --color=yes -s

doc/drafts/gh_repo.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
import requests
3+
4+
from wagon_common.helpers.output import red
5+
6+
7+
class GhRepo:
8+
"""
9+
helper class for gh api calls
10+
"""
11+
12+
base_url = "https://api.github.com"
13+
default_path = f"/repos/{self.owner}/{self.repo}"
14+
15+
def __init__(self, name, token=None, is_org=True, verbose=False):
16+
"""
17+
required gh token scopes:
18+
- repo: push commits TBC
19+
- admin:org: create repos TBC
20+
- workflow: push commits containing `.github/workflows/*.yml` files
21+
- delete_repo: delete `lewagon-test` and `lewagon-qa` repositories
22+
"""
23+
24+
self.name, self.owner, self.repo = self.__identify(name)
25+
self.token = token
26+
self.is_org = is_org
27+
self.verbose = verbose
28+
29+
def __identify(self, name):
30+
"""
31+
identify owner and repo from provided name
32+
(gh cli nomenclatura)
33+
"""
34+
35+
parts = name.split("/", maxsplit=1)
36+
37+
if len(parts) == 2:
38+
owner = parts[0]
39+
repo = parts[1]
40+
else:
41+
owner = "lewagon"
42+
repo = name
43+
name = f"{owner}/{repo}"
44+
45+
return name, owner, repo
46+
47+
def __call(self, path=None, verb="get", headers={}, params={}, context="", status_code=200, decode_response=True):
48+
"""
49+
resolve api call
50+
"""
51+
52+
# set path
53+
if path is None:
54+
path = self.default_path
55+
56+
# resolve http verb call method
57+
call_method = dict(
58+
get=requests.get,
59+
put=requests.put,
60+
patch=requests.patch,
61+
post=requests.post,
62+
delete=requests.delete)[verb]
63+
64+
# add auth
65+
headers["Authorization"] = f"token {self.token}"
66+
67+
# list repo params
68+
response = call_method(self.base_url + path,
69+
headers=headers,
70+
json=params)
71+
72+
if response.status_code != status_code:
73+
74+
red("\nGH api error 🤕",
75+
f"\n- context {context}"
76+
+ f"\n- path: {path}"
77+
+ f"\n- verb: {verb}"
78+
+ f"\n- params: {params}"
79+
+ f"\n- expected status code: {status_code}"
80+
+ f"\n- status code: {response.status_code}"
81+
+ f"\n- response: {response.content}")
82+
83+
raise ValueError("GH api error")
84+
85+
if decode_response:
86+
return response.json()
87+
88+
def create(self, params={}):
89+
"""
90+
create repo
91+
"""
92+
93+
params["org"] = self.owner
94+
params["name"] = self.repo
95+
96+
return self.__call(path=f"/orgs/{self.owner}/repos",
97+
verb="post",
98+
params=params,
99+
status_code=201)
100+
101+
def get(self):
102+
"""
103+
get repo
104+
"""
105+
106+
return self.__call()
107+
108+
def update(self, params):
109+
"""
110+
update repo
111+
"""
112+
113+
return self.__call(verb="patch", params=params)
114+
115+
def delete(self, dry_run=True):
116+
"""
117+
delete repo
118+
"""
119+
120+
# protect production repositories
121+
if self.owner.lower() not in ["lewagon-test", "lewagon-qa"]:
122+
raise NameError("cannot delete repo in production organisation")
123+
124+
if dry_run:
125+
return {}
126+
127+
# delete repo
128+
return self.__call(verb="delete", status_code=204, decode_response=False)

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
colorama
22
pylint
33
pytest
4+
python-dotenv
5+
requests

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
requirements = [c.strip() for c in f.readlines()]
66

77
setup(name="wagon_common",
8-
version="0.2.9",
8+
version="0.2.10",
99
description="Le Wagon common packages",
1010
url="https://github.com/lewagon/python-utilities/",
1111
author="Sébastien Saunier",

tests/gh/__init__.py

Whitespace-only changes.

tests/gh/test_gh_repo.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
2+
from wagon_common.gh.gh_repo import GhRepo
3+
4+
import os
5+
6+
import pytest
7+
8+
from dotenv import load_dotenv, find_dotenv
9+
10+
11+
class TestGhRepo():
12+
13+
@pytest.fixture
14+
def token(self):
15+
"""
16+
fetch gh token to perform the tests
17+
"""
18+
19+
# Arrange
20+
load_dotenv(find_dotenv())
21+
token = os.environ.get("GH_API_DELETE_TOKEN")
22+
23+
# Act & Assert
24+
yield token
25+
26+
# Cleanup
27+
28+
def test_gh_repo_naming(self):
29+
30+
# Arrange
31+
32+
# Act
33+
repo = GhRepo("lewagon-test/data-solutions", token=None)
34+
35+
lw_repo = GhRepo("myriad", token=None)
36+
37+
# Assert
38+
assert repo.owner == "lewagon-test"
39+
assert repo.repo == "data-solutions"
40+
assert repo.name == "lewagon-test/data-solutions"
41+
42+
assert repo.ssh_url == f"git@github.com:{repo.owner}/{repo.repo}.git"
43+
assert repo.https_url == f"https://None@github.com/{repo.owner}/{repo.repo}.git"
44+
45+
assert lw_repo.owner == "lewagon"
46+
assert lw_repo.repo == "myriad"
47+
assert lw_repo.name == "lewagon/myriad"
48+
49+
# Cleanup
50+
51+
def test_gh_repo_delete_prod(self, token):
52+
"""
53+
verify that a production repo cannot be deleted
54+
"""
55+
56+
exception_catched = False
57+
58+
try:
59+
GhRepo("lewagon/data-solutions", token=token).delete()
60+
except NameError as e:
61+
exception_catched = True
62+
assert "cannot delete repo in" in str(e)
63+
64+
assert exception_catched
65+
66+
def test_gh_repo_delete_test_qa(self, token):
67+
"""
68+
verify that a test or QA repo can be deleted
69+
"""
70+
71+
# test gh repo can be deleted
72+
exception_catched = False
73+
74+
try:
75+
GhRepo("lewagon-test/data-solutions", token=token).delete()
76+
except NameError:
77+
exception_catched = True
78+
79+
assert not exception_catched
80+
81+
# QA gh repo can be deleted
82+
exception_catched = False
83+
84+
try:
85+
GhRepo("lewagon-qa/data-solutions", token=token).delete()
86+
except NameError:
87+
exception_catched = True
88+
89+
assert not exception_catched
90+
91+
def test_gh_repo_crud(self, token):
92+
"""
93+
verifies repo crud
94+
"""
95+
96+
# Arrange
97+
98+
# Act
99+
repo = GhRepo("lewagon-qa/automated-test", token=token)
100+
create_response = repo.create()
101+
get_response = repo.get()
102+
update_response = repo.update(dict(description="automated update test"))
103+
repo.delete(dry_run=False)
104+
105+
# Assert
106+
assert "node_id" in create_response
107+
assert "node_id" in get_response
108+
assert "node_id" in update_response
109+
110+
create_node_id = create_response["node_id"]
111+
get_node_id = get_response["node_id"]
112+
update_node_id = update_response["node_id"]
113+
114+
assert create_node_id == get_node_id
115+
assert create_node_id == update_node_id
116+
117+
# Cleanup

0 commit comments

Comments
 (0)