From 66ad9c75476e88aa705d5e2b0545ac7a7ddf335d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 15 Apr 2021 13:47:28 +0200 Subject: [PATCH 1/7] Refactor release modules Update the release module to be more precise about which parameters the commands prepare, release and sign require. This will simplify using pontos release. --- pontos/release/release.py | 437 +++++++++++++++++++------------------- 1 file changed, 220 insertions(+), 217 deletions(-) diff --git a/pontos/release/release.py b/pontos/release/release.py index b90893be..97fba162 100644 --- a/pontos/release/release.py +++ b/pontos/release/release.py @@ -25,40 +25,15 @@ import shutil from pathlib import Path -from typing import Callable, Dict, List, Union, Tuple, Optional -from dataclasses import dataclass +from typing import Callable, Dict, List, Union, Tuple import requests +from requests.api import request from pontos import version from pontos import changelog -@dataclass -class ReleaseInformation: - release_version: str - nextversion: str - project: str - space: str - git_signing_key: str - git_tag_prefix: str - git_remote_name: str - username: str - token: str - signing_key: Optional[str] - path: Path - requests_module: requests - version_module: version - changelog_module: changelog - shell_cmd_runner: Callable - - def git_version(self): - return "{}{}".format(self.git_tag_prefix, self.release_version) - - def auth(self): - return (self.username, self.token) - - def build_release_dict( release_version: str, release_changelog: str, @@ -92,16 +67,6 @@ def initialize_default_parser() -> argparse.ArgumentParser: description='Release handling utility.', prog='pontos-release', ) - parser.add_argument( - '--release-version', - help='Will release changelog as version. Must be PEP 440 compliant', - required=True, - ) - parser.add_argument( - '--next-release-version', - help='Sets the next PEP 440 compliant version in project definition.', - required=True, - ) parser.add_argument( '--project', help='The github project', @@ -112,34 +77,75 @@ def initialize_default_parser() -> argparse.ArgumentParser: default='greenbone', help='user/team name in github', ) - parser.add_argument( - '--signing-key', - default='0ED1E580', - help='The key to sign zip, tarballs of a release. Default %(default)s.', + + subparsers = parser.add_subparsers( + title='subcommands', + description='valid subcommands', + help='additional help', + dest='command', ) - parser.add_argument( + + prepare_parser = subparsers.add_parser('prepare') + prepare_parser.set_defaults(func=prepare) + prepare_parser.add_argument( + '--release-version', + help='Will release changelog as version. Must be PEP 440 compliant', + required=True, + ) + prepare_parser.add_argument( + '--next-version', + help='Sets the next PEP 440 compliant version in project definition ' + 'after the release', + required=True, + ) + prepare_parser.add_argument( '--git-signing-key', help='The key to sign the commits and tag for a release', ) - parser.add_argument( + prepare_parser.add_argument( + '--git-tag-prefix', + default='v', + help='Prefix for git tag versions. Default: %(default)s', + ) + + release_parser = subparsers.add_parser('release') + release_parser.set_defaults(func=release) + release_parser.add_argument( + '--release-version', + help='Will release changelog as version. Must be PEP 440 compliant', + required=True, + ) + release_parser.add_argument( '--git-remote-name', help='The git remote name to push the commits and tag to', ) - - subparsers = parser.add_subparsers( - title='subcommands', - description='valid subcommands', - help='additional help', - dest='command', + release_parser.add_argument( + '--git-tag-prefix', + default='v', + help='Prefix for git tag versions. Default: %(default)s', ) - subparsers.add_parser('prepare') - subparsers.add_parser('release') - subparsers.add_parser('sign') + sign_parser = subparsers.add_parser('sign') + sign_parser.set_defaults(func=sign) + sign_parser.add_argument( + '--signing-key', + default='0ED1E580', + help='The key to sign zip, tarballs of a release. Default %(default)s.', + ) + sign_parser.add_argument( + '--release-version', + help='Will release changelog as version. Must be PEP 440 compliant', + required=True, + ) + sign_parser.add_argument( + '--git-tag-prefix', + default='v', + help='Prefix for git tag versions. Default: %(default)s', + ) return parser -def parse(args=None): +def parse(args=None) -> Tuple[str, str, argparse.Namespace]: parser = initialize_default_parser() commandline_arguments = parser.parse_args(args) token = ( @@ -152,18 +158,7 @@ def parse(args=None): if not args or not 'testcases' in args else 'USER' ) - return ( - commandline_arguments.command, - commandline_arguments.release_version, - commandline_arguments.next_release_version, - commandline_arguments.project, - commandline_arguments.space, - commandline_arguments.signing_key, - commandline_arguments.git_signing_key, - commandline_arguments.git_remote_name, - user, - token, - ) + return (user, token, commandline_arguments) def update_version( @@ -199,35 +194,85 @@ def commit_files( ) +def upload_assets( + username: str, + token: str, + pathnames: List[str], + github_json: Dict, + path: Path, + requests_module: request, +) -> bool: + print("Uploading assets: {}".format(pathnames)) + + asset_url = github_json['upload_url'].replace('{?name,label}', '') + paths = [path('{}.asc'.format(p)) for p in pathnames] + + headers = { + 'Accept': 'application/vnd.github.v3+json', + 'content-type': 'application/octet-stream', + } + auth = (username, token) + + for path in paths: + to_upload = path.read_bytes() + resp = requests_module.post( + "{}?name={}".format(asset_url, path.name), + headers=headers, + auth=auth, + data=to_upload, + ) + + if resp.status_code != 201: + print( + "Wrong response status {} while uploading {}".format( + resp.status_code, path.name + ) + ) + print(json.dumps(resp.text, indent=4, sort_keys=True)) + return False + else: + print("uploaded: {}".format(path.name)) + + return True + + def prepare( - release_info: ReleaseInformation, + shell_cmd_runner: Callable, + args: argparse.Namespace, + *, + path: Path, + version_module: version, + changelog_module: changelog, + **_kwargs, ): + project: str = args.project + space: str = args.space + git_tag_prefix: str = args.git_tag_prefix + git_signing_key: str = args.git_signing_key + release_version: str = args.release_version + next_version: str = args.next_version + print("in prepare") + # guardian - git_tags = release_info.shell_cmd_runner('git tag -l') - git_version = "{}{}".format( - release_info.git_tag_prefix, release_info.release_version - ) + git_tags = shell_cmd_runner('git tag -l') + git_version = "{}{}".format(git_tag_prefix, release_version) if git_version.encode() in git_tags.stdout.splitlines(): raise ValueError('git tag {} is already taken.'.format(git_version)) executed, filename = update_version( - release_info.release_version, release_info.version_module, develop=False + release_version, version_module, develop=False ) if not executed: return False - print( - "updated version {} to {}".format( - filename, release_info.release_version - ) - ) + print("updated version {} to {}".format(filename, release_version)) - change_log_path = release_info.path.cwd() / 'CHANGELOG.md' - updated, changelog_text = release_info.changelog_module.update( + change_log_path = path.cwd() / 'CHANGELOG.md' + updated, changelog_text = changelog_module.update( change_log_path.read_text(), - release_info.release_version, - git_tag_prefix=release_info.git_tag_prefix, + release_version, + git_tag_prefix=git_tag_prefix, ) if not updated: @@ -235,62 +280,62 @@ def prepare( change_log_path.write_text(updated) - print("updated CHANGELOG.md") + print("Updated CHANGELOG.md") print("Committing changes") - commit_msg = 'automatic release to {}'.format(release_info.release_version) + commit_msg = 'automatic release to {}'.format(release_version) commit_files( filename, commit_msg, - release_info.git_signing_key, - release_info.shell_cmd_runner, + git_signing_key, + shell_cmd_runner, ) - if release_info.git_signing_key: - release_info.shell_cmd_runner( + if git_signing_key: + shell_cmd_runner( "git tag -u {} {} -m '{}'".format( - release_info.git_signing_key, git_version, commit_msg + git_signing_key, git_version, commit_msg ), ) else: - release_info.shell_cmd_runner( + shell_cmd_runner( "git tag -s {} -m '{}'".format(git_version, commit_msg), ) - release_text = release_info.path(".release.txt.md") + release_text = path(".release.txt.md") release_text.write_text(changelog_text) # set to new version add skeleton executed, filename = update_version( - release_info.nextversion, release_info.version_module, develop=True + next_version, version_module, develop=True ) if not executed: return False - updated = release_info.changelog_module.add_skeleton( + updated = changelog_module.add_skeleton( change_log_path.read_text(), - release_info.release_version, - release_info.project, - git_tag_prefix=release_info.git_tag_prefix, - git_space=release_info.space, + release_version, + project, + git_tag_prefix=git_tag_prefix, + git_space=space, ) change_log_path.write_text(updated) - commit_msg = 'set version {}, add empty changelog after {}'.format( - release_info.nextversion, release_info.release_version + commit_msg = ( + f'* Update to version {next_version}\n' + f'* Add empty changelog after {release_version}' ) commit_files( filename, commit_msg, - release_info.git_signing_key, - release_info.shell_cmd_runner, + git_signing_key, + shell_cmd_runner, ) print( - "Please verify git tag {}, commit and release text in {}".format( - git_version, release_text - ) + f"Please verify git tag {git_version}, " + f"commit and release text in {str(release_text)}" ) print("Afterwards please execute release") @@ -298,36 +343,46 @@ def prepare( def release( - release_info: ReleaseInformation, + shell_cmd_runner: Callable, + args: argparse.Namespace, + *, + path: Path, + username: str, + token: str, + requests_module: requests, + **_kwargs, ): - changelog_text = release_info.path(".release.txt.md").read_text() + project: str = args.project + space: str = args.space + git_remote_name: str = args.git_remote_name + git_tag_prefix: str = args.git_tag_prefix + release_version: str = args.release_version + + changelog_text: str = path(".release.txt.md").read_text() print("Pushing changes") - if release_info.git_remote_name: - release_info.shell_cmd_runner( - "git push --follow-tags {}".format(release_info.git_remote_name) - ) + if git_remote_name: + shell_cmd_runner("git push --follow-tags {}".format(git_remote_name)) else: - release_info.shell_cmd_runner("git push --follow-tags") + shell_cmd_runner("git push --follow-tags") print("Creating release") headers = {'Accept': 'application/vnd.github.v3+json'} base_url = "https://api.github.com/repos/{}/{}/releases".format( - release_info.space, release_info.project + space, project ) - response = release_info.requests_module.post( + git_version = f'{git_tag_prefix}{release_version}' + response = requests_module.post( base_url, headers=headers, - auth=release_info.auth(), + auth=(username, token), json=build_release_dict( - release_info.git_version(), + git_version, changelog_text, - name="{} {}".format( - release_info.project, release_info.release_version - ), + name="{} {}".format(project, release_version), ), ) if response.status_code != 201: @@ -335,61 +390,46 @@ def release( print(json.dumps(response.text, indent=4, sort_keys=True)) return False - release_info.path(".release.txt.md").unlink() + path(".release.txt.md").unlink() return True -def sign(release_info: ReleaseInformation): - def upload_assets(pathnames: List[str], github_json: Dict) -> bool: - print("Uploading assets: {}".format(pathnames)) - - asset_url = github_json['upload_url'].replace('{?name,label}', '') - paths = [release_info.path('{}.asc'.format(p)) for p in pathnames] - - headers = { - 'Accept': 'application/vnd.github.v3+json', - 'content-type': 'application/octet-stream', - } - - for path in paths: - to_upload = path.read_bytes() - resp = release_info.requests_module.post( - "{}?name={}".format(asset_url, path.name), - headers=headers, - auth=release_info.auth(), - data=to_upload, - ) - - if resp.status_code != 201: - print( - "Wrong response status {} while uploading {}".format( - resp.status_code, path.name - ) - ) - print(json.dumps(resp.text, indent=4, sort_keys=True)) - return False - else: - print("uploaded: {}".format(path.name)) - - return True - +def sign( + shell_cmd_runner: Callable, + args: argparse.Namespace, + *, + path: Path, + username: str, + token: str, + requests_module: requests, + **_kwargs, +): def download(url, filename): - file_path = release_info.path("/tmp/{}".format(filename)) + file_path = path(f"/tmp/{filename}") - with release_info.requests_module.get( - url, stream=True - ) as resp, file_path.open(mode='wb') as download_file: + with requests_module.get(url, stream=True) as resp, file_path.open( + mode='wb' + ) as download_file: shutil.copyfileobj(resp.raw, download_file) return file_path + project: str = args.project + space: str = args.space + git_tag_prefix: str = args.git_tag_prefix + release_version: str = args.release_version + signing_key: str = args.signing_key + headers = {'Accept': 'application/vnd.github.v3+json'} - base_url = "https://api.github.com/repos/{}/{}/releases/tags/{}".format( - release_info.space, release_info.project, release_info.git_version() + git_version: str = f'{git_tag_prefix}{release_version}' + + base_url = ( + f"https://api.github.com/repos/{space}/{project}" + f"/releases/tags/{git_version}" ) - response = release_info.requests_module.get( + response = requests_module.get( base_url, headers=headers, ) @@ -399,28 +439,26 @@ def download(url, filename): return False github_json = json.loads(response.text) - zip_path = download( - github_json['zipball_url'], release_info.git_version() + ".zip" - ) - tar_path = download( - github_json['tarball_url'], release_info.git_version() + ".tar.gz" - ) + zip_path = download(github_json['zipball_url'], git_version + ".zip") + tar_path = download(github_json['tarball_url'], git_version + ".tar.gz") print("Signing {}".format([zip_path, tar_path])) gpg_cmd = "gpg --default-key {} --detach-sign --armor {}" - release_info.shell_cmd_runner( - gpg_cmd.format(release_info.signing_key, zip_path) - ) - release_info.shell_cmd_runner( - gpg_cmd.format(release_info.signing_key, tar_path) - ) + shell_cmd_runner(gpg_cmd.format(signing_key, zip_path)) + shell_cmd_runner(gpg_cmd.format(signing_key, tar_path)) - return upload_assets([zip_path, tar_path], github_json) + return upload_assets( + username, + token, + [zip_path, tar_path], + github_json, + path=path, + requests_module=requests_module, + ) def main( - git_tag_prefix: str = "v", shell_cmd_runner=lambda x: subprocess.run( x, shell=True, @@ -436,54 +474,19 @@ def main( leave: bool = True, args=None, ): - def execute( - command: str, - release_version: str, - next_release_version: str, - project: str, - space: str, - signing_key: str, - git_signing_key: str, - git_remote_name: str, - user: str, - token: str, - ): - info = ReleaseInformation( - release_version=release_version, - nextversion=next_release_version, - project=project, - space=space, - git_signing_key=git_signing_key, - git_tag_prefix=git_tag_prefix, - git_remote_name=git_remote_name, - username=user, - token=token, - signing_key=signing_key, + username, token, parsed_args = parse(args) + + try: + if not parsed_args.func( + shell_cmd_runner, + parsed_args, path=_path, + username=username, + token=token, + changelog_module=_changelog, requests_module=_requests, version_module=_version, - changelog_module=_changelog, - shell_cmd_runner=shell_cmd_runner, - ) - - if command == 'prepare': - return prepare(info) - if command == 'release': - print("command release") - return release( - info, - ) - if command == 'sign': - return sign(info) - - raise ValueError("Unknown command: {}".format(command)) - - values = parse(args) - if not values: - return sys.exit(1) if leave else False - - try: - if not execute(*values): + ): return sys.exit(1) if leave else False except subprocess.CalledProcessError as e: print( From 0da7803f9dd987969716355011c61d76ed5a903d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 15 Apr 2021 13:50:31 +0200 Subject: [PATCH 2/7] Update tests for release command changes Re-arange tests into three different testcase classes reflecting the commands prepare, release and sign. --- tests/release/test_release.py | 245 ++++++++++++++++------------------ 1 file changed, 112 insertions(+), 133 deletions(-) diff --git a/tests/release/test_release.py b/tests/release/test_release.py index 5a57dbeb..85c1a94b 100644 --- a/tests/release/test_release.py +++ b/tests/release/test_release.py @@ -38,166 +38,139 @@ class StdOutput: @patch("pontos.release.release.shutil", new=_shutil_mock) -class ReleaseTestCase(unittest.TestCase): - - valid_gh_release_response = ( - '{"zipball_url": "zip", "tarball_url": "tar", "upload_url":"upload"}' - ) - - def test_use_git_signing_key_on_prepare(self): +class PrepareTestCase(unittest.TestCase): + def test_prepare_successfully(self): fake_path_class = MagicMock(spec=Path) - fake_requests = MagicMock(spec=requests) - fake_post = MagicMock(spec=requests.Response).return_value - fake_post.status_code = 201 - fake_post.text = self.valid_gh_release_response - fake_requests.post.return_value = fake_post fake_version = MagicMock(spec=version) fake_version.main.return_value = (True, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--git-signing-key', - '0815', - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', 'prepare', + '--release-version', + '0.0.1', + '--next-version', + '0.0.2dev', ] - called = [] - - def runner(cmd): - called.append(cmd) - return StdOutput('') - + runner = lambda x: StdOutput('') released = release.main( shell_cmd_runner=runner, _path=fake_path_class, - _requests=fake_requests, _version=fake_version, _changelog=fake_changelog, leave=False, args=args, ) self.assertTrue(released) - self.assertIn( - "git commit -S0815 -m 'automatic release to 0.0.1'", called - ) - self.assertIn( - "git tag -u 0815 v0.0.1 -m 'automatic release to 0.0.1'", called - ) - def test_prepare_successfully(self): + def test_use_git_signing_key_on_prepare(self): fake_path_class = MagicMock(spec=Path) - fake_requests = MagicMock(spec=requests) - fake_post = MagicMock(spec=requests.Response).return_value - fake_post.status_code = 201 - fake_post.text = self.valid_gh_release_response - fake_requests.post.return_value = fake_post fake_version = MagicMock(spec=version) fake_version.main.return_value = (True, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', 'prepare', + '--git-signing-key', + '0815', + '--release-version', + '0.0.1', + '--next-version', + '0.0.2dev', ] - runner = lambda x: StdOutput('') + called = [] + + def runner(cmd): + called.append(cmd) + return StdOutput('') + released = release.main( shell_cmd_runner=runner, _path=fake_path_class, - _requests=fake_requests, _version=fake_version, _changelog=fake_changelog, leave=False, args=args, ) self.assertTrue(released) + self.assertIn( + "git commit -S0815 -m 'automatic release to 0.0.1'", called + ) + self.assertIn( + "git tag -u 0815 v0.0.1 -m 'automatic release to 0.0.1'", called + ) def test_fail_if_tag_is_already_taken(self): fake_path_class = MagicMock(spec=Path) - fake_requests = MagicMock(spec=requests) fake_version = MagicMock(spec=version) fake_changelog = MagicMock(spec=changelog) args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', 'prepare', + '--release-version', + '0.0.1', + '--next-version', + '0.0.2dev', ] runner = lambda x: StdOutput('v0.0.1'.encode()) - with self.assertRaises(ValueError): + with self.assertRaises( + ValueError, msg='git tag v0.0.1 is already taken' + ): release.main( shell_cmd_runner=runner, _path=fake_path_class, - _requests=fake_requests, _version=fake_version, _changelog=fake_changelog, leave=False, args=args, ) - def test_release_successfully(self): + def test_not_release_when_no_project_found(self): fake_path_class = MagicMock(spec=Path) - fake_requests = MagicMock(spec=requests) - fake_post = MagicMock(spec=requests.Response).return_value - fake_post.status_code = 201 - fake_post.text = self.valid_gh_release_response - fake_requests.post.return_value = fake_post fake_version = MagicMock(spec=version) - fake_version.main.return_value = (True, 'MyProject.conf') + fake_version.main.return_value = (False, '') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ + '--project', + 'testcases', + 'prepare', '--release-version', '0.0.1', - '--next-release-version', + '--next-version', '0.0.2dev', - '--project', - 'testcases', - 'release', ] runner = lambda x: StdOutput('') released = release.main( shell_cmd_runner=runner, _path=fake_path_class, - _requests=fake_requests, _version=fake_version, _changelog=fake_changelog, leave=False, args=args, ) - self.assertTrue(released) + self.assertFalse(released) - def test_not_release_successfully_when_github_create_release_fails(self): + def test_not_release_when_updating_version_fails(self): fake_path_class = MagicMock(spec=Path) fake_requests = MagicMock(spec=requests) - fake_post = MagicMock(spec=requests.Response).return_value - fake_post.status_code = 401 - fake_post.text = self.valid_gh_release_response - fake_requests.post.return_value = fake_post fake_version = MagicMock(spec=version) - fake_version.main.return_value = (True, 'MyProject.conf') + fake_version.main.return_value = (False, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ + '--project', + 'testcases', + 'prepare', '--release-version', '0.0.1', - '--next-release-version', + '--next-version', '0.0.2dev', - '--project', - 'testcases', - 'release', ] runner = lambda x: StdOutput('') released = release.main( @@ -211,21 +184,31 @@ def test_not_release_successfully_when_github_create_release_fails(self): ) self.assertFalse(released) - def test_not_release_when_no_project_found(self): + +@patch("pontos.release.release.shutil", new=_shutil_mock) +class ReleaseTestCase(unittest.TestCase): + + valid_gh_release_response = ( + '{"zipball_url": "zip", "tarball_url": "tar", "upload_url":"upload"}' + ) + + def test_release_successfully(self): fake_path_class = MagicMock(spec=Path) fake_requests = MagicMock(spec=requests) + fake_post = MagicMock(spec=requests.Response).return_value + fake_post.status_code = 201 + fake_post.text = self.valid_gh_release_response + fake_requests.post.return_value = fake_post fake_version = MagicMock(spec=version) - fake_version.main.return_value = (False, '') + fake_version.main.return_value = (True, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', - 'prepare', + 'release', + '--release-version', + '0.0.1', ] runner = lambda x: StdOutput('') released = release.main( @@ -237,23 +220,25 @@ def test_not_release_when_no_project_found(self): leave=False, args=args, ) - self.assertFalse(released) + self.assertTrue(released) - def test_not_release_when_updating_version_fails(self): + def test_not_release_successfully_when_github_create_release_fails(self): fake_path_class = MagicMock(spec=Path) fake_requests = MagicMock(spec=requests) + fake_post = MagicMock(spec=requests.Response).return_value + fake_post.status_code = 401 + fake_post.text = self.valid_gh_release_response + fake_requests.post.return_value = fake_post fake_version = MagicMock(spec=version) - fake_version.main.return_value = (False, 'MyProject.conf') + fake_version.main.return_value = (True, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', - 'prepare', + 'release', + '--release-version', + '0.0.1', ] runner = lambda x: StdOutput('') released = release.main( @@ -267,30 +252,30 @@ def test_not_release_when_updating_version_fails(self): ) self.assertFalse(released) - def test_fail_sign_on_invalid_get_response(self): + def test_release_to_specific_git_remote(self): fake_path_class = MagicMock(spec=Path) fake_requests = MagicMock(spec=requests) - fake_get = MagicMock(spec=requests.Response).return_value - fake_get.status_code = 404 - fake_get.text = self.valid_gh_release_response - fake_requests.get.return_value = fake_get + fake_post = MagicMock(spec=requests.Response).return_value + fake_post.status_code = 201 + fake_post.text = self.valid_gh_release_response + fake_requests.post.return_value = fake_post fake_version = MagicMock(spec=version) fake_version.main.return_value = (True, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', + 'release', + '--release-version', + '0.0.1', '--git-remote-name', 'testremote', - 'sign', ] - def runner(_: str): + def runner(cmd: str): + if not 'testremote' in cmd: + raise ValueError('unexpected cmd: {}'.format(cmd)) return StdOutput('') released = release.main( @@ -302,33 +287,33 @@ def runner(_: str): leave=False, args=args, ) - self.assertFalse(released) + self.assertTrue(released) - def test_fail_sign_on_upload_fail(self): + +@patch("pontos.release.release.shutil", new=_shutil_mock) +class SignTestCase(unittest.TestCase): + + valid_gh_release_response = ( + '{"zipball_url": "zip", "tarball_url": "tar", "upload_url":"upload"}' + ) + + def test_fail_sign_on_invalid_get_response(self): fake_path_class = MagicMock(spec=Path) fake_requests = MagicMock(spec=requests) fake_get = MagicMock(spec=requests.Response).return_value - fake_get.status_code = 200 + fake_get.status_code = 404 fake_get.text = self.valid_gh_release_response - fake_post = MagicMock(spec=requests.Response).return_value - fake_post.status_code = 500 - fake_post.text = self.valid_gh_release_response - fake_requests.post.return_value = fake_post fake_requests.get.return_value = fake_get fake_version = MagicMock(spec=version) fake_version.main.return_value = (True, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', - '--git-remote-name', - 'testremote', 'sign', + '--release-version', + '0.0.1', ] def runner(_: str): @@ -345,31 +330,27 @@ def runner(_: str): ) self.assertFalse(released) - def test_successfully_sign(self): + def test_fail_sign_on_upload_fail(self): fake_path_class = MagicMock(spec=Path) fake_requests = MagicMock(spec=requests) fake_get = MagicMock(spec=requests.Response).return_value fake_get.status_code = 200 fake_get.text = self.valid_gh_release_response - fake_requests.get.return_value = fake_get fake_post = MagicMock(spec=requests.Response).return_value - fake_post.status_code = 201 + fake_post.status_code = 500 fake_post.text = self.valid_gh_release_response fake_requests.post.return_value = fake_post + fake_requests.get.return_value = fake_get fake_version = MagicMock(spec=version) fake_version.main.return_value = (True, 'MyProject.conf') fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', - '--git-remote-name', - 'testremote', 'sign', + '--release-version', + '0.0.1', ] def runner(_: str): @@ -384,11 +365,15 @@ def runner(_: str): leave=False, args=args, ) - self.assertTrue(released) + self.assertFalse(released) - def test_release_to_specific_git_remote(self): + def test_successfully_sign(self): fake_path_class = MagicMock(spec=Path) fake_requests = MagicMock(spec=requests) + fake_get = MagicMock(spec=requests.Response).return_value + fake_get.status_code = 200 + fake_get.text = self.valid_gh_release_response + fake_requests.get.return_value = fake_get fake_post = MagicMock(spec=requests.Response).return_value fake_post.status_code = 201 fake_post.text = self.valid_gh_release_response @@ -398,20 +383,14 @@ def test_release_to_specific_git_remote(self): fake_changelog = MagicMock(spec=changelog) fake_changelog.update.return_value = ('updated', 'changelog') args = [ - '--release-version', - '0.0.1', - '--next-release-version', - '0.0.2dev', '--project', 'testcases', - '--git-remote-name', - 'testremote', - 'release', + 'sign', + '--release-version', + '0.0.1', ] - def runner(cmd: str): - if not 'testremote' in cmd: - raise ValueError('unexpected cmd: {}'.format(cmd)) + def runner(_: str): return StdOutput('') released = release.main( From ad38e8b41e129070c3efe55de905a9de5224e4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 15 Apr 2021 13:53:08 +0200 Subject: [PATCH 3/7] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 005e45b2..5f547297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Calendar Versioning](https://calver.org). ## [Unreleased] ### Added + ### Changed +- Refactored release module and changed the arguments of release, prepare and + sign commands [#80](https://github.com/greenbone/pontos/pull/80) + ### Deprecated ### Removed ### Fixed From 972cc6bb5af425ef3ea32cb5af53d23cf107b1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 16 Apr 2021 13:56:10 +0200 Subject: [PATCH 4/7] Remove unnecessary import The main requests module already provides the request api. --- pontos/release/release.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pontos/release/release.py b/pontos/release/release.py index 97fba162..5d6bbc87 100644 --- a/pontos/release/release.py +++ b/pontos/release/release.py @@ -28,7 +28,6 @@ from typing import Callable, Dict, List, Union, Tuple import requests -from requests.api import request from pontos import version from pontos import changelog @@ -200,7 +199,7 @@ def upload_assets( pathnames: List[str], github_json: Dict, path: Path, - requests_module: request, + requests_module: requests, ) -> bool: print("Uploading assets: {}".format(pathnames)) From fc3e20a997e191b584c0bc8f61ed47b520e83fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 16 Apr 2021 13:57:10 +0200 Subject: [PATCH 5/7] Add return value types for prepare, release and sign funcs Add missing typings for the return values of prepare, release and sign functions. --- pontos/release/release.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pontos/release/release.py b/pontos/release/release.py index 5d6bbc87..1b021de0 100644 --- a/pontos/release/release.py +++ b/pontos/release/release.py @@ -243,7 +243,7 @@ def prepare( version_module: version, changelog_module: changelog, **_kwargs, -): +) -> bool: project: str = args.project space: str = args.space git_tag_prefix: str = args.git_tag_prefix @@ -350,7 +350,7 @@ def release( token: str, requests_module: requests, **_kwargs, -): +) -> bool: project: str = args.project space: str = args.space git_remote_name: str = args.git_remote_name @@ -402,7 +402,7 @@ def sign( token: str, requests_module: requests, **_kwargs, -): +) -> bool: def download(url, filename): file_path = path(f"/tmp/{filename}") From 3b6530eb237d4e706b331ca9f3bce2801179d48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 16 Apr 2021 13:58:13 +0200 Subject: [PATCH 6/7] Extract release file name into a constant Use a single constant variable to the release file name. This allows for easier changing the file name and avoids typos. --- pontos/release/release.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pontos/release/release.py b/pontos/release/release.py index 1b021de0..5d75e8fe 100644 --- a/pontos/release/release.py +++ b/pontos/release/release.py @@ -32,6 +32,8 @@ from pontos import version from pontos import changelog +RELEASE_TEXT_FILE = ".release.txt.md" + def build_release_dict( release_version: str, @@ -302,7 +304,7 @@ def prepare( "git tag -s {} -m '{}'".format(git_version, commit_msg), ) - release_text = path(".release.txt.md") + release_text = path(RELEASE_TEXT_FILE) release_text.write_text(changelog_text) # set to new version add skeleton @@ -357,7 +359,7 @@ def release( git_tag_prefix: str = args.git_tag_prefix release_version: str = args.release_version - changelog_text: str = path(".release.txt.md").read_text() + changelog_text: str = path(RELEASE_TEXT_FILE).read_text() print("Pushing changes") @@ -389,7 +391,7 @@ def release( print(json.dumps(response.text, indent=4, sort_keys=True)) return False - path(".release.txt.md").unlink() + path(RELEASE_TEXT_FILE).unlink() return True From e52b66e4f2da1811ea995ff73a024f46346d687c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 16 Apr 2021 13:59:10 +0200 Subject: [PATCH 7/7] Pass parameters as keyword arguments --- pontos/release/release.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pontos/release/release.py b/pontos/release/release.py index 5d75e8fe..739ddfd8 100644 --- a/pontos/release/release.py +++ b/pontos/release/release.py @@ -315,9 +315,9 @@ def prepare( return False updated = changelog_module.add_skeleton( - change_log_path.read_text(), - release_version, - project, + markdown=change_log_path.read_text(), + new_version=release_version, + project_name=project, git_tag_prefix=git_tag_prefix, git_space=space, )