Skip to content

Commit

Permalink
fix(netrc): prefer using token defined in GH_TOKEN instead of .netrc …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
Toilal authored and relekang committed Dec 18, 2020
1 parent 93e48c9 commit 3af32a7
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 26 deletions.
40 changes: 36 additions & 4 deletions semantic_release/hvcs.py
Expand Up @@ -7,6 +7,7 @@

import gitlab
import requests
from requests.auth import AuthBase

from .errors import ImproperConfigurationError
from .helpers import LoggedFunction
Expand Down Expand Up @@ -59,6 +60,26 @@ def _fix_mime_types():
mimetypes.add_type("text/markdown", ".md")


class TokenAuth(AuthBase):
"""
requests Authentication for token based authorization
"""
def __init__(self, token):
self.token = token

def __eq__(self, other):
return all([
self.token == getattr(other, 'token', None),
])

def __ne__(self, other):
return not self == other

def __call__(self, r):
r.headers['Authorization'] = f'token {self.token}'
return r


class Github(Base):
"""Github helper class"""

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

@staticmethod
def auth() -> Optional[TokenAuth]:
"""Github token property
:return: The Github token environment variable (GH_TOKEN) value
"""
token = Github.token()
if not token:
return None
return TokenAuth(token)

@staticmethod
@LoggedFunction(logger)
def check_build_status(owner: str, repo: str, ref: str) -> bool:
Expand Down Expand Up @@ -123,7 +155,7 @@ def create_release(cls, owner: str, repo: str, tag: str, changelog: str) -> bool
"draft": False,
"prerelease": False,
},
headers={"Authorization": "token {}".format(Github.token())},
auth=Github.auth()
)
logger.debug(f"Release creation status code: {response.status_code}")

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

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

Expand Down Expand Up @@ -222,9 +254,9 @@ def upload_asset(
url.format(owner=owner, repo=repo, id=release_id),
params={"name": os.path.basename(file), "label": label},
headers={
"Authorization": "token {}".format(Github.token()),
"Content-Type": mimetypes.guess_type(file, strict=False)[0],
},
auth=Github.auth(),
data=open(file, "rb").read(),
)
logger.debug(
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -40,7 +40,7 @@ def _read_long_description():
"invoke>=1.4.1,<2",
"semver>=2.8,<3",
"twine>=3,<4",
"requests>=2.21,<3",
"requests>=2.25,<3",
"wheel",
"toml~=0.10.0",
"python-gitlab>=1.10,<2",
Expand Down
87 changes: 66 additions & 21 deletions tests/test_hvcs.py
@@ -1,7 +1,9 @@
import base64
import json
import os
import platform
from unittest import TestCase
from tempfile import NamedTemporaryFile
from unittest import TestCase, mock

import pytest
import responses
Expand Down Expand Up @@ -155,26 +157,69 @@ class GithubReleaseTests(TestCase):
)

@responses.activate
@mock.patch("semantic_release.hvcs.Github.token", return_value="super-token")
def test_should_post_changelog(self, mock_token):
def request_callback(request):
payload = json.loads(request.body)
self.assertEqual(payload["tag_name"], "v1.0.0")
self.assertEqual(payload["body"], "text")
self.assertEqual(payload["draft"], False)
self.assertEqual(payload["prerelease"], False)
self.assertEqual("token super-token", request.headers["Authorization"])
@mock.patch("semantic_release.hvcs.Github.token", return_value='super-token')
def test_should_post_changelog_using_github_token(self, mock_token):
with NamedTemporaryFile('w') as netrc_file:
netrc_file.write('machine api.github.com\n')
netrc_file.write('login username\n')
netrc_file.write('password password\n')

netrc_file.flush()

def request_callback(request):
payload = json.loads(request.body)
self.assertEqual(payload["tag_name"], "v1.0.0")
self.assertEqual(payload["body"], "text")
self.assertEqual(payload["draft"], False)
self.assertEqual(payload["prerelease"], False)
self.assertEqual('token super-token', request.headers.get("Authorization"))

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

responses.add_callback(
responses.POST,
self.url,
callback=request_callback,
content_type="application/json",
)

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

responses.add_callback(
responses.POST,
self.url,
callback=request_callback,
content_type="application/json",
)
status = Github.post_release_changelog("relekang", "rmoq", "1.0.0", "text")
self.assertTrue(status)
@responses.activate
@mock.patch("semantic_release.hvcs.Github.token", return_value=None)
def test_should_post_changelog_using_netrc(self, mock_token):
with NamedTemporaryFile('w') as netrc_file:
netrc_file.write('machine api.github.com\n')
netrc_file.write('login username\n')
netrc_file.write('password password\n')

netrc_file.flush()

def request_callback(request):
payload = json.loads(request.body)
self.assertEqual(payload["tag_name"], "v1.0.0")
self.assertEqual(payload["body"], "text")
self.assertEqual(payload["draft"], False)
self.assertEqual(payload["prerelease"], False)
self.assertEqual(
"Basic " + base64.encodebytes(b"username:password").decode('ascii').strip(),
request.headers.get("Authorization")
)

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

responses.add_callback(
responses.POST,
self.url,
callback=request_callback,
content_type="application/json",
)

with mock.patch.dict(os.environ, {'NETRC': netrc_file.name}):
status = Github.post_release_changelog("relekang", "rmoq", "1.0.0", "text")
self.assertTrue(status)

@responses.activate
def test_should_return_false_status_if_it_failed(self):
Expand Down Expand Up @@ -217,7 +262,7 @@ def request_callback(request):
self.assertEqual(request.body.decode().replace("\r\n", "\n"), dummy_content)
self.assertEqual(request.url, self.asset_url_params)
self.assertEqual(request.headers["Content-Type"], "text/markdown")
self.assertEqual("token super-token", request.headers["Authorization"])
self.assertEqual("token super-token", request.headers.get("Authorization"))

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

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

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

Expand Down

0 comments on commit 3af32a7

Please sign in to comment.