Skip to content

Commit

Permalink
Add: Introduce pontos-release show CLI
Browse files Browse the repository at this point in the history
Add a new CLI for displaying the current release and possible next
release.
  • Loading branch information
bjoernricks committed Aug 8, 2023
1 parent d6043a0 commit 7fcc5f1
Show file tree
Hide file tree
Showing 4 changed files with 716 additions and 2 deletions.
51 changes: 50 additions & 1 deletion pontos/release/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from typing import Callable, Optional, Tuple, Type

from pontos.release.helper import ReleaseType
from pontos.release.show import OutputFormat, show
from pontos.version.schemes import (
VERSIONING_SCHEMES,
PEP440VersioningScheme,
Expand Down Expand Up @@ -231,13 +232,61 @@ def parse_args(args) -> Tuple[Optional[str], Optional[str], Namespace]:
"--dry-run", action="store_true", help="Do not upload signed files."
)

show_parser = subparsers.add_parser(
"show",
help="Show release information about the current release version and "
"determine the next release version",
)
show_parser.set_defaults(func=show)
show_parser.add_argument(
"--versioning-scheme",
help="Versioning scheme to use for parsing and handling version "
f"information. Choices are {', '.join(VERSIONING_SCHEMES.keys())}. "
"Default: %(default)s",
default="pep440",
type=versioning_scheme_argument_type,
)
show_parser.add_argument(
"--release-type",
help="Select the release type for calculating the release version. "
f"Possible choices are: {to_choices(ReleaseType)}.",
type=enum_type(ReleaseType),
)
show_parser.add_argument(
"--release-version",
help=(
"Will release changelog as version. "
"Default: lookup version in project definition."
),
action=ReleaseVersionAction,
)
show_parser.add_argument(
"--release-series",
help="Create a release for a release series. Setting a release series "
"is required if the latest tag version is newer then the to be "
'released version. Examples: "1.2", "2", "22.4"',
)
show_parser.add_argument(
"--git-tag-prefix",
default="v",
const="",
nargs="?",
help="Prefix for git tag versions. Default: %(default)s",
)
show_parser.add_argument(
"--output-format",
help="Print in the desired output format. "
f"Possible choices are: {to_choices(OutputFormat)}.",
type=enum_type(OutputFormat),
)

parsed_args = parser.parse_args(args)

scheme: type[VersioningScheme] = getattr(
parsed_args, "versioning_scheme", PEP440VersioningScheme
)

if parsed_args.func in (create_release,):
if parsed_args.func in (create_release, show):
# check for release-type
if not getattr(parsed_args, "release_type", None):
parser.error("--release-type is required.")
Expand Down
177 changes: 177 additions & 0 deletions pontos/release/show.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# SPDX-FileCopyrightText: 2023 Greenbone AG
#
# SPDX-License-Identifier: GPL-3.0-or-later

import json
from argparse import Namespace
from enum import Enum, IntEnum, auto
from typing import Optional

from pontos.errors import PontosError
from pontos.git import Git
from pontos.github.actions import ActionIO
from pontos.release.command import Command
from pontos.release.helper import ReleaseType, get_next_release_version
from pontos.terminal import Terminal
from pontos.typing import SupportsStr
from pontos.version import Version, VersionError
from pontos.version.helper import get_last_release_version
from pontos.version.schemes import VersioningScheme


class ShowReleaseReturnValue(IntEnum):
"""
Possible return values of ReleaseCommand
"""

SUCCESS = 0
NO_LAST_RELEASE_VERSION = auto()
NO_RELEASE_VERSION = auto()


class OutputFormat(Enum):
ENV = "env"
JSON = "json"
GITHUB_ACTION = "github-action"


class ShowReleaseCommand(Command):
def __init__(self, *, terminal: Terminal, error_terminal: Terminal) -> None:
super().__init__(terminal=terminal, error_terminal=error_terminal)
self.git = Git()

def run( # type: ignore[override]
self,
*,
output_format: OutputFormat = OutputFormat.ENV,
versioning_scheme: VersioningScheme,
release_type: ReleaseType,
release_version: Optional[Version],
release_series: Optional[str] = None,
git_tag_prefix: Optional[str] = None,
) -> int:
git_tag_prefix = git_tag_prefix or ""

try:
last_release_version = get_last_release_version(
parse_version=versioning_scheme.parse_version,
git_tag_prefix=git_tag_prefix,
tag_name=f"{git_tag_prefix}{release_series}.*"
if release_series
else None,
)
except PontosError as e:
last_release_version = None
self.print_warning(f"Could not determine last release version. {e}")

Check warning on line 65 in pontos/release/show.py

View check run for this annotation

Codecov / codecov/patch

pontos/release/show.py#L63-L65

Added lines #L63 - L65 were not covered by tests

if not last_release_version and not release_version:
self.print_error("Unable to determine last release version.")
return ShowReleaseReturnValue.NO_LAST_RELEASE_VERSION

Check warning on line 69 in pontos/release/show.py

View check run for this annotation

Codecov / codecov/patch

pontos/release/show.py#L68-L69

Added lines #L68 - L69 were not covered by tests

calculator = versioning_scheme.calculator()

try:
release_version = get_next_release_version(
last_release_version=last_release_version,
calculator=calculator,
release_type=release_type,
release_version=release_version,
)
except VersionError as e:
self.print_error(f"Unable to determine release version. {e}")
return ShowReleaseReturnValue.NO_RELEASE_VERSION

Check warning on line 82 in pontos/release/show.py

View check run for this annotation

Codecov / codecov/patch

pontos/release/show.py#L80-L82

Added lines #L80 - L82 were not covered by tests

if last_release_version:
last_release_version_dict = {
"last_release_version": str(last_release_version),
"last_release_version_major": last_release_version.major,
"last_release_version_minor": last_release_version.minor,
"last_release_version_patch": last_release_version.patch,
}
else:
last_release_version_dict = {
"last_release_version": "",
"last_release_version_major": "",
"last_release_version_minor": "",
"last_release_version_patch": "",
}

if output_format == OutputFormat.JSON:
release_dict = {
"release_version": str(release_version),
"release_version_major": release_version.major,
"release_version_minor": release_version.minor,
"release_version_patch": release_version.patch,
}
release_dict.update(last_release_version_dict)
self.terminal.print(json.dumps(release_dict, indent=2))
elif output_format == OutputFormat.GITHUB_ACTION:
with ActionIO.out() as output:
output.write(
"last_release_version",
last_release_version_dict["last_release_version"],
)
output.write(
"last_release_version_major",
last_release_version_dict["last_release_version_major"],
)
output.write(
"last_release_version_minor",
last_release_version_dict["last_release_version_minor"],
)
output.write(
"last_release_version_patch",
last_release_version_dict["last_release_version_patch"],
)
output.write("release_version_major", release_version.major)
output.write("release_version_minor", release_version.minor)
output.write("release_version_patch", release_version.patch)
output.write("release_version", release_version)
else:
self.terminal.print(
"LAST_RELEASE_VERSION="
f"{last_release_version_dict['last_release_version']}"
)
self.terminal.print(
"LAST_RELEASE_VERSION_MAJOR="
f"{last_release_version_dict['last_release_version_major']}"
)
self.terminal.print(
"LAST_RELEASE_VERSION_MINOR="
f"{last_release_version_dict['last_release_version_minor']}"
)
self.terminal.print(
"LAST_RELEASE_VERSION_PATCH="
f"{last_release_version_dict['last_release_version_patch']}"
)
self.terminal.print(f"RELEASE_VERSION={release_version}")
self.terminal.print(
f"RELEASE_VERSION_MAJOR={release_version.major}"
)
self.terminal.print(
f"RELEASE_VERSION_MINOR={release_version.minor}"
)
self.terminal.print(
f"RELEASE_VERSION_PATCH={release_version.patch}"
)

return ShowReleaseReturnValue.SUCCESS


def show(
args: Namespace,
terminal: Terminal,
error_terminal: Terminal,
**_kwargs,
) -> SupportsStr:
return ShowReleaseCommand(

Check warning on line 167 in pontos/release/show.py

View check run for this annotation

Codecov / codecov/patch

pontos/release/show.py#L167

Added line #L167 was not covered by tests
terminal=terminal,
error_terminal=error_terminal,
).run(
versioning_scheme=args.versioning_scheme,
release_type=args.release_type,
release_version=args.release_version,
git_tag_prefix=args.git_tag_prefix,
release_series=args.release_series,
output_format=args.output_format,
)
130 changes: 129 additions & 1 deletion tests/release/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
from pontos.release.create import create_release
from pontos.release.helper import ReleaseType
from pontos.release.parser import DEFAULT_SIGNING_KEY, parse_args
from pontos.release.show import OutputFormat, show
from pontos.release.sign import sign
from pontos.version.schemes._pep440 import PEP440Version
from pontos.version.schemes._pep440 import PEP440Version, PEP440VersioningScheme


class ParseArgsTestCase(unittest.TestCase):
Expand Down Expand Up @@ -290,3 +291,130 @@ def test_release_series(self):
_, _, args = parse_args(["sign", "--release-series", "22.4"])

self.assertEqual(args.release_series, "22.4")


class ShowParseArgsTestCase(unittest.TestCase):
def test_show_func(self):
_, _, args = parse_args(["show", "--release-type", "patch"])

self.assertEqual(args.func, show)

def test_defaults(self):
_, _, args = parse_args(["show", "--release-type", "patch"])

self.assertEqual(args.git_tag_prefix, "v")
self.assertEqual(args.versioning_scheme, PEP440VersioningScheme)

def test_release_series(self):
_, _, args = parse_args(
["show", "--release-type", "patch", "--release-series", "1.2"]
)

self.assertEqual(args.release_series, "1.2")

def test_release_type(self):
_, _, args = parse_args(["show", "--release-type", "patch"])

self.assertEqual(args.release_type, ReleaseType.PATCH)

_, _, args = parse_args(["show", "--release-type", "calendar"])

self.assertEqual(args.release_type, ReleaseType.CALENDAR)

_, _, args = parse_args(["show", "--release-type", "minor"])

self.assertEqual(args.release_type, ReleaseType.MINOR)

_, _, args = parse_args(["show", "--release-type", "major"])

self.assertEqual(args.release_type, ReleaseType.MAJOR)

_, _, args = parse_args(["show", "--release-type", "alpha"])

self.assertEqual(args.release_type, ReleaseType.ALPHA)

_, _, args = parse_args(["show", "--release-type", "beta"])

self.assertEqual(args.release_type, ReleaseType.BETA)

_, _, args = parse_args(["show", "--release-type", "release-candidate"])

self.assertEqual(args.release_type, ReleaseType.RELEASE_CANDIDATE)

with self.assertRaises(SystemExit), redirect_stderr(StringIO()):
parse_args(["show", "--release-type", "foo"])

def test_git_tag_prefix(self):
_, _, args = parse_args(
["show", "--git-tag-prefix", "a", "--release-type", "patch"]
)

self.assertEqual(args.git_tag_prefix, "a")

_, _, args = parse_args(
["show", "--git-tag-prefix", "", "--release-type", "patch"]
)

self.assertEqual(args.git_tag_prefix, "")

_, _, args = parse_args(
["show", "--git-tag-prefix", "--release-type", "patch"]
)

self.assertEqual(args.git_tag_prefix, "")

def test_release_version(self):
_, _, args = parse_args(["show", "--release-version", "1.2.3"])

self.assertEqual(args.release_version, PEP440Version("1.2.3"))
self.assertEqual(args.release_type, ReleaseType.VERSION)

with self.assertRaises(SystemExit), redirect_stderr(StringIO()):
parse_args(
[
"show",
"--release-version",
"1.2.3",
"--release-type",
"patch",
]
)

with self.assertRaises(SystemExit), redirect_stderr(StringIO()):
parse_args(
[
"show",
"--release-version",
"1.2.3",
"--release-type",
"calendar",
]
)

def test_output_format(self):
_, _, args = parse_args(
["show", "--release-type", "patch", "--output-format", "env"]
)

self.assertEqual(args.output_format, OutputFormat.ENV)

_, _, args = parse_args(
["show", "--release-type", "patch", "--output-format", "json"]
)

self.assertEqual(args.output_format, OutputFormat.JSON)

_, _, args = parse_args(
[
"show",
"--release-type",
"patch",
"--output-format",
"github-action",
]
)

with patch.dict(
"os.environ", {"GITHUB_OUTPUT": "/tmp/output"}, clear=True
):
self.assertEqual(args.output_format, OutputFormat.GITHUB_ACTION)
Loading

0 comments on commit 7fcc5f1

Please sign in to comment.