Skip to content

Commit 3af32a7

Browse files
Toilalrelekang
authored andcommitted
fix(netrc): prefer using token defined in GH_TOKEN instead of .netrc file
.netrc file will only be used when available and no GH_TOKEN environment variable is defined. This also add a test to make sure .netrc is used properly when no GH_TOKEN is defined.
1 parent 93e48c9 commit 3af32a7

File tree

3 files changed

+103
-26
lines changed

3 files changed

+103
-26
lines changed

semantic_release/hvcs.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import gitlab
99
import requests
10+
from requests.auth import AuthBase
1011

1112
from .errors import ImproperConfigurationError
1213
from .helpers import LoggedFunction
@@ -59,6 +60,26 @@ def _fix_mime_types():
5960
mimetypes.add_type("text/markdown", ".md")
6061

6162

63+
class TokenAuth(AuthBase):
64+
"""
65+
requests Authentication for token based authorization
66+
"""
67+
def __init__(self, token):
68+
self.token = token
69+
70+
def __eq__(self, other):
71+
return all([
72+
self.token == getattr(other, 'token', None),
73+
])
74+
75+
def __ne__(self, other):
76+
return not self == other
77+
78+
def __call__(self, r):
79+
r.headers['Authorization'] = f'token {self.token}'
80+
return r
81+
82+
6283
class Github(Base):
6384
"""Github helper class"""
6485

@@ -81,6 +102,17 @@ def token() -> Optional[str]:
81102
"""
82103
return os.environ.get("GH_TOKEN")
83104

105+
@staticmethod
106+
def auth() -> Optional[TokenAuth]:
107+
"""Github token property
108+
109+
:return: The Github token environment variable (GH_TOKEN) value
110+
"""
111+
token = Github.token()
112+
if not token:
113+
return None
114+
return TokenAuth(token)
115+
84116
@staticmethod
85117
@LoggedFunction(logger)
86118
def check_build_status(owner: str, repo: str, ref: str) -> bool:
@@ -123,7 +155,7 @@ def create_release(cls, owner: str, repo: str, tag: str, changelog: str) -> bool
123155
"draft": False,
124156
"prerelease": False,
125157
},
126-
headers={"Authorization": "token {}".format(Github.token())},
158+
auth=Github.auth()
127159
)
128160
logger.debug(f"Release creation status code: {response.status_code}")
129161

@@ -144,7 +176,7 @@ def get_release(cls, owner: str, repo: str, tag: str) -> int:
144176
"""
145177
response = requests.get(
146178
f"{Github.API_URL}/repos/{owner}/{repo}/releases/tags/{tag}",
147-
headers={"Authorization": "token {}".format(Github.token())},
179+
auth=Github.auth()
148180
)
149181
logger.debug(f"Get release by tag status code: {response.status_code}")
150182

@@ -167,7 +199,7 @@ def edit_release(cls, owner: str, repo: str, id: int, changelog: str) -> bool:
167199
response = requests.post(
168200
f"{Github.API_URL}/repos/{owner}/{repo}/releases/{id}",
169201
json={"body": changelog},
170-
headers={"Authorization": "token {}".format(Github.token())},
202+
auth=Github.auth()
171203
)
172204
logger.debug(f"Edit release status code: {response.status_code}")
173205

@@ -222,9 +254,9 @@ def upload_asset(
222254
url.format(owner=owner, repo=repo, id=release_id),
223255
params={"name": os.path.basename(file), "label": label},
224256
headers={
225-
"Authorization": "token {}".format(Github.token()),
226257
"Content-Type": mimetypes.guess_type(file, strict=False)[0],
227258
},
259+
auth=Github.auth(),
228260
data=open(file, "rb").read(),
229261
)
230262
logger.debug(

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def _read_long_description():
4040
"invoke>=1.4.1,<2",
4141
"semver>=2.8,<3",
4242
"twine>=3,<4",
43-
"requests>=2.21,<3",
43+
"requests>=2.25,<3",
4444
"wheel",
4545
"toml~=0.10.0",
4646
"python-gitlab>=1.10,<2",

tests/test_hvcs.py

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import base64
12
import json
23
import os
34
import platform
4-
from unittest import TestCase
5+
from tempfile import NamedTemporaryFile
6+
from unittest import TestCase, mock
57

68
import pytest
79
import responses
@@ -155,26 +157,69 @@ class GithubReleaseTests(TestCase):
155157
)
156158

157159
@responses.activate
158-
@mock.patch("semantic_release.hvcs.Github.token", return_value="super-token")
159-
def test_should_post_changelog(self, mock_token):
160-
def request_callback(request):
161-
payload = json.loads(request.body)
162-
self.assertEqual(payload["tag_name"], "v1.0.0")
163-
self.assertEqual(payload["body"], "text")
164-
self.assertEqual(payload["draft"], False)
165-
self.assertEqual(payload["prerelease"], False)
166-
self.assertEqual("token super-token", request.headers["Authorization"])
160+
@mock.patch("semantic_release.hvcs.Github.token", return_value='super-token')
161+
def test_should_post_changelog_using_github_token(self, mock_token):
162+
with NamedTemporaryFile('w') as netrc_file:
163+
netrc_file.write('machine api.github.com\n')
164+
netrc_file.write('login username\n')
165+
netrc_file.write('password password\n')
166+
167+
netrc_file.flush()
168+
169+
def request_callback(request):
170+
payload = json.loads(request.body)
171+
self.assertEqual(payload["tag_name"], "v1.0.0")
172+
self.assertEqual(payload["body"], "text")
173+
self.assertEqual(payload["draft"], False)
174+
self.assertEqual(payload["prerelease"], False)
175+
self.assertEqual('token super-token', request.headers.get("Authorization"))
176+
177+
return 201, {}, json.dumps({})
178+
179+
responses.add_callback(
180+
responses.POST,
181+
self.url,
182+
callback=request_callback,
183+
content_type="application/json",
184+
)
167185

168-
return 201, {}, json.dumps({})
186+
with mock.patch.dict(os.environ, {'NETRC': netrc_file.name}):
187+
status = Github.post_release_changelog("relekang", "rmoq", "1.0.0", "text")
188+
self.assertTrue(status)
169189

170-
responses.add_callback(
171-
responses.POST,
172-
self.url,
173-
callback=request_callback,
174-
content_type="application/json",
175-
)
176-
status = Github.post_release_changelog("relekang", "rmoq", "1.0.0", "text")
177-
self.assertTrue(status)
190+
@responses.activate
191+
@mock.patch("semantic_release.hvcs.Github.token", return_value=None)
192+
def test_should_post_changelog_using_netrc(self, mock_token):
193+
with NamedTemporaryFile('w') as netrc_file:
194+
netrc_file.write('machine api.github.com\n')
195+
netrc_file.write('login username\n')
196+
netrc_file.write('password password\n')
197+
198+
netrc_file.flush()
199+
200+
def request_callback(request):
201+
payload = json.loads(request.body)
202+
self.assertEqual(payload["tag_name"], "v1.0.0")
203+
self.assertEqual(payload["body"], "text")
204+
self.assertEqual(payload["draft"], False)
205+
self.assertEqual(payload["prerelease"], False)
206+
self.assertEqual(
207+
"Basic " + base64.encodebytes(b"username:password").decode('ascii').strip(),
208+
request.headers.get("Authorization")
209+
)
210+
211+
return 201, {}, json.dumps({})
212+
213+
responses.add_callback(
214+
responses.POST,
215+
self.url,
216+
callback=request_callback,
217+
content_type="application/json",
218+
)
219+
220+
with mock.patch.dict(os.environ, {'NETRC': netrc_file.name}):
221+
status = Github.post_release_changelog("relekang", "rmoq", "1.0.0", "text")
222+
self.assertTrue(status)
178223

179224
@responses.activate
180225
def test_should_return_false_status_if_it_failed(self):
@@ -217,7 +262,7 @@ def request_callback(request):
217262
self.assertEqual(request.body.decode().replace("\r\n", "\n"), dummy_content)
218263
self.assertEqual(request.url, self.asset_url_params)
219264
self.assertEqual(request.headers["Content-Type"], "text/markdown")
220-
self.assertEqual("token super-token", request.headers["Authorization"])
265+
self.assertEqual("token super-token", request.headers.get("Authorization"))
221266

222267
return 201, {}, json.dumps({})
223268

@@ -246,7 +291,7 @@ def request_callback(request):
246291
self.assertEqual(request.body.decode().replace("\r\n", "\n"), dummy_content)
247292
self.assertEqual(request.url, self.dist_asset_url_params)
248293
self.assertEqual(request.headers["Content-Type"], "text/markdown")
249-
self.assertEqual("token super-token", request.headers["Authorization"])
294+
self.assertEqual("token super-token", request.headers.get("Authorization"))
250295

251296
return 201, {}, json.dumps({})
252297

0 commit comments

Comments
 (0)