Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
Commit b05c8580 authored by Dorian Turba's avatar Dorian Turba
Browse files

rework flow

parent 965700bc
No related branches found
No related tags found
No related merge requests found
import functools
import pathlib
import typing
import gitlab.v4.objects
import rich.panel
import rich.table
import typer
import release_by_changelog.exc
from release_by_changelog.app import app
from release_by_changelog.cmds._release import (
_changelog_file,
_last_changelog,
_project,
_release,
)
from release_by_changelog.services.changelog import find_changelog_file, last_changelog
from release_by_changelog.services.gitlab import get_project
from release_by_changelog.services.write import post_release
from release_by_changelog.typings_ import ChangelogEntry
err_console = rich.console.Console(stderr=True)
panel = functools.partial(
rich.panel.Panel,
title_align="left",
subtitle="release_by_changelog --help",
subtitle_align="left",
expand=False,
border_style="red",
)
@app.command()
def release(
......@@ -55,14 +64,9 @@ def release(
if not token and not ci_job_token:
err_console.print(
rich.panel.Panel(
panel(
"You need to provide a PRIVATE_TOKEN or a CI_JOB_TOKEN",
title="Error: Missing token",
title_align="left",
subtitle="release_by_changelog --help",
subtitle_align="left",
expand=False,
border_style="red",
)
)
raise typer.Exit(code=1)
......@@ -70,65 +74,117 @@ def release(
token = token or ci_job_token
try:
changelog_path = _changelog_file(
changelog_path = find_changelog_file(
changelog_path, token, host, project, ref, console
)
except release_by_changelog.exc.UnauthorizedError:
err_console.print("Error: Unauthorized", style="bold red")
err_console.print(
panel(
"Possible remediation:\n"
"\t- Check if the provided token is correct.\n"
"\t- Check if the provided token has the correct permissions and "
"scope, is not expired or revoked.",
title="Error: Unauthorized",
)
)
raise typer.Exit(code=1)
except release_by_changelog.exc.ProjectNotFoundError:
err_console.print(
"Error: Project not found.",
"Possible remediation: Provide the full path to the project.",
style="bold red",
panel(
"Possible remediation:\n"
"\t- Provide the project ID. To find the project ID, go to the project "
"page, click on the three vertical dot button on the top right corner "
"and press the 'Copy project ID: XXXX' button.\n"
"\t- Provide the full path to the project. To find it, go to the "
"project page, check at the url and copy the path after the host. If "
"the following link doesn't point to your project, you could have the "
f"namespace wrong: 'https://{host}/{project}'\n"
"\t- Check if the host is correct. If you are using a self-hosted "
"GitLab, you need to provide the correct host. Current host: "
"'https://{host}', is that where your project is hosted?\n",
title="Error: Project not found.",
)
)
raise typer.Exit(code=1)
console.print(f"Processing [bold cyan]{changelog_path}[/bold cyan]")
changelog_entry = _last_changelog(changelog_path)
changelog_entry = last_changelog(changelog_path)
console.print(
"[bold green]Found changelog entry:[/bold green] "
f"[bold cyan]{changelog_entry.version}[/bold cyan]"
)
try:
project_, project_name = _project(token, host, project, console)
project_, project_name = get_project(token, host, project, console)
except release_by_changelog.exc.UnauthorizedError:
err_console.print("Error: Unauthorized", style="bold red")
err_console.print(
panel(
"Possible remediation:\n"
"\t- Check if the provided token is correct.\n"
"\t- Check if the provided token has the correct permissions and "
"scope, is not expired or revoked.",
title="Error: Unauthorized",
)
)
raise typer.Exit(code=1)
except release_by_changelog.exc.ProjectNotFoundError:
err_console.print(
"Error: Project not found.",
"Possible remediation:",
"\t- Provide the project ID. To find the project ID, go to the project page"
", click on the three vertical dot button on the top right corner and press"
" the 'Copy project ID: XXXX' button.",
"\t- Provide the full path to the project. To find it, go to the project "
"page, check at the url and copy the path after the host. "
"If the following link doesn't point to your project, you got the "
f"namespace wrong: 'https://{host}/{project}'",
"\t- Check if the host is correct. If you are using a self-hosted GitLab, "
"you need to provide the correct host. Current host: "
f"'https://{host}', is that where your project is hosted?",
style="bold red",
sep="\n",
panel(
"Possible remediation:\n"
"\t- Provide the project ID. To find the project ID, go to the project "
"page, click on the three vertical dot button on the top right corner "
"and press the 'Copy project ID: XXXX' button.\n"
"\t- Provide the full path to the project. To find it, go to the "
"project page, check at the url and copy the path after the host. If "
"the following link doesn't point to your project, you could have the "
f"namespace wrong: 'https://{host}/{project}'\n"
"\t- Check if the host is correct. If you are using a self-hosted "
"GitLab, you need to provide the correct host. Current host: "
"'https://{host}', is that where your project is hosted?\n",
title="Error: Project not found.",
)
)
raise typer.Exit(code=1)
if interact:
typer.confirm("Do you confirm release?", default=True, abort=True)
publish_release(changelog_entry, console, project_, project_name, ref)
def publish_release(
changelog_entry: ChangelogEntry,
console: rich.console.Console,
project_: gitlab.v4.objects.Project,
project_name: str,
ref: str,
) -> None:
console.print(
f"Creating release [bold cyan]{changelog_entry.version}[/bold cyan] for "
f"project [bold cyan]{project_name}[/bold cyan]"
)
try:
result = _release(project_, changelog_entry, ref)
result = post_release(project_, changelog_entry, ref)
except release_by_changelog.exc.UnauthorizedError:
err_console.print("Error: Unauthorized", style="bold red")
err_console.print(
panel(
"Possible remediation:\n"
"\t- Check if the provided token is correct.\n"
"\t- Check if the provided token has the correct permissions and "
"scope, is not expired or revoked.",
title="Error: Unauthorized",
)
)
raise typer.Exit(code=1)
except release_by_changelog.exc.ReleaseAlreadyExistError:
err_console.print("Error: Release already exists", style="bold red")
err_console.print(
panel(
f"It looks like the release [bold cyan]{changelog_entry.version}"
f"[/bold cyan] already exists.\n"
"Possible remediation: Bump the version in the changelog file.",
title="Error: Release already exists",
)
)
raise typer.Exit(code=1)
console.print(f"Release created: {result}", style="bold green")
......
from __future__ import annotations
import contextlib
import functools
import http
import os
import pathlib
import re
import typing
import typer
if typing.TYPE_CHECKING:
import rich.console
import gitlab.v4.objects
import rich.console
import typer
import release_by_changelog.exc
regex: typing.Final = re.compile(
r"^## \[(?P<version>(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]"
r"\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*"
r"|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA"
r"-Z-]+)*))?)\]"
)
from release_by_changelog.services.gitlab import get_project
from release_by_changelog.typings_ import ChangelogEntry
def _extract_last_version(f: typing.TextIO) -> str:
......@@ -36,24 +26,7 @@ def _extract_last_version(f: typing.TextIO) -> str:
raise ValueError("No changelog entry found")
def _extract_body(f: typing.TextIO) -> str:
body = []
for lines in f:
matches = regex.finditer(lines)
with contextlib.suppress(StopIteration):
next(matches)
break
body.append(lines)
return "".join(body)
class ChangelogEntry(typing.NamedTuple):
version: str
body: str
def _last_changelog(changelog_path: pathlib.Path) -> ChangelogEntry:
def last_changelog(changelog_path: pathlib.Path) -> ChangelogEntry:
"""Extract the last changelog entry from the changelog file."""
with changelog_path.open() as f:
version = _extract_last_version(f)
......@@ -61,58 +34,63 @@ def _last_changelog(changelog_path: pathlib.Path) -> ChangelogEntry:
return ChangelogEntry(version=version, body=body)
@functools.cache
def _project(
def find_changelog_file(
changelog_path: pathlib.Path,
token: str,
host: str,
project: str,
ref: str,
console: rich.console.Console,
) -> tuple[gitlab.v4.objects.Project, str]:
) -> pathlib.Path:
"""
Find usable changelog file path.
If the changelog file is not found locally, it will look for it in the remote
project files.
:raises NotImplementedError: If the OS is not supported.
"""
console.print(f"Look for local [bold cyan]{changelog_path}[/bold cyan]")
if changelog_path.exists():
console.print(
f"[bold green]Found local[/bold green] "
f"[bold cyan]{changelog_path}[/bold cyan]"
)
return changelog_path
console.print(
f"Retrieving project [bold cyan]{project}[/bold cyan] from "
f"[bold cyan]{host}[/bold cyan]"
f"[yellow]Local [bold cyan]{changelog_path}[/bold cyan] file not found, looking"
" for file in the remote project files[/yellow]"
)
url = f"https://{host}"
gl = gitlab.Gitlab(url=url, oauth_token=token)
try:
project_ = gl.projects.get(project)
except gitlab.GitlabAuthenticationError as e:
if e.response_code == http.HTTPStatus.UNAUTHORIZED:
raise release_by_changelog.exc.UnauthorizedError from e
raise e
except gitlab.GitlabGetError as e:
if e.response_code == http.HTTPStatus.NOT_FOUND:
raise release_by_changelog.exc.ProjectNotFoundError from e
raise e
project_name = f"{project_.namespace.get('full_path')}/{project_.name}"
console.print(
f"[bold green]Project found:[/bold green] [bold cyan]{project_name}[bold cyan]"
project_, _ = get_project(
token,
host,
project,
console,
)
return project_, project_name
changelog_file = _get_remote_changelog_file(changelog_path, console, project_, ref)
tmp_file = _save_remote_changelog_file(changelog_file, changelog_path)
return tmp_file
def _release(
project: gitlab.v4.objects.Project,
changelog_entry: ChangelogEntry,
ref: str,
) -> str:
data = {
"ref": ref,
"name": changelog_entry.version,
"tag_name": changelog_entry.version,
"description": changelog_entry.body,
}
try:
project.releases.create(data)
except gitlab.GitlabAuthenticationError as e:
if e.response_code == http.HTTPStatus.UNAUTHORIZED:
raise release_by_changelog.exc.UnauthorizedError from e
raise e
except gitlab.GitlabCreateError as e:
if e.response_code == http.HTTPStatus.CONFLICT:
raise release_by_changelog.exc.ReleaseAlreadyExistError from e
raise e
return changelog_entry.version
def _save_remote_changelog_file(
changelog_file: gitlab.v4.objects.ProjectFile, changelog_path: pathlib.Path
) -> pathlib.Path:
"""
Save the remote changelog file to a temporary file.
:raises NotImplementedError: If the OS is not supported.
"""
if os.name == "posix":
tmp_file = pathlib.Path(f"/tmp/{changelog_path.name}")
elif os.name == "nt":
temp_path = pathlib.Path(os.environ["Temp"])
tmp_file = temp_path / changelog_path.name
else:
raise NotImplementedError(f"OS {os.name} not supported")
tmp_file.write_bytes(changelog_file.decode())
return tmp_file
def _get_remote_changelog_file(
......@@ -140,60 +118,21 @@ def _get_remote_changelog_file(
return changelog_file
def _save_remote_changelog_file(
changelog_file: gitlab.v4.objects.ProjectFile, changelog_path: pathlib.Path
) -> pathlib.Path:
"""
Save the remote changelog file to a temporary file.
:raises NotImplementedError: If the OS is not supported.
"""
if os.name == "posix":
tmp_file = pathlib.Path(f"/tmp/{changelog_path.name}")
elif os.name == "nt":
temp_path = pathlib.Path(os.environ["Temp"])
tmp_file = temp_path / changelog_path.name
else:
raise NotImplementedError(f"OS {os.name} not supported")
tmp_file.write_bytes(changelog_file.decode())
return tmp_file
def _changelog_file(
changelog_path: pathlib.Path,
token: str,
host: str,
project: str,
ref: str,
console: rich.console.Console,
) -> pathlib.Path:
"""
Find usable changelog file path.
If the changelog file is not found locally, it will look for it in the remote
project files.
def _extract_body(f: typing.TextIO) -> str:
body = []
for lines in f:
matches = regex.finditer(lines)
with contextlib.suppress(StopIteration):
next(matches)
break
:raises NotImplementedError: If the OS is not supported.
"""
console.print(f"Look for local [bold cyan]{changelog_path}[/bold cyan]")
if changelog_path.exists():
console.print(
f"[bold green]Found local[/bold green] "
f"[bold cyan]{changelog_path}[/bold cyan]"
)
return changelog_path
body.append(lines)
return "".join(body)
console.print(
f"[yellow]Local [bold cyan]{changelog_path}[/bold cyan] file not found, looking"
" for file in the remote project files[/yellow]"
)
project_, _ = _project(
token,
host,
project,
console,
)
changelog_file = _get_remote_changelog_file(changelog_path, console, project_, ref)
tmp_file = _save_remote_changelog_file(changelog_file, changelog_path)
return tmp_file
regex: typing.Final = re.compile(
r"^## \[(?P<version>(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]"
r"\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*"
r"|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA"
r"-Z-]+)*))?)\]"
)
from __future__ import annotations
import functools
import http
import gitlab.v4.objects
import rich.console
import release_by_changelog.exc
@functools.cache
def get_project(
token: str,
host: str,
project: str,
console: rich.console.Console,
) -> tuple[gitlab.v4.objects.Project, str]:
console.print(
f"Retrieving project [bold cyan]{project}[/bold cyan] from "
f"[bold cyan]{host}[/bold cyan]"
)
url = f"https://{host}"
gl = gitlab.Gitlab(url=url, oauth_token=token)
try:
project_ = gl.projects.get(project)
except gitlab.GitlabAuthenticationError as e:
if e.response_code == http.HTTPStatus.UNAUTHORIZED:
raise release_by_changelog.exc.UnauthorizedError from e
raise e
except gitlab.GitlabGetError as e:
if e.response_code == http.HTTPStatus.NOT_FOUND:
raise release_by_changelog.exc.ProjectNotFoundError from e
raise e
project_name = f"{project_.namespace.get('full_path')}/{project_.name}"
console.print(
f"[bold green]Project found:[/bold green] [bold cyan]{project_name}[bold cyan]"
)
return project_, project_name
from __future__ import annotations
import http
import gitlab.v4.objects
import release_by_changelog.exc
from release_by_changelog.typings_ import ChangelogEntry
def post_release(
project: gitlab.v4.objects.Project,
changelog_entry: ChangelogEntry,
ref: str,
) -> str:
data = {
"ref": ref,
"name": changelog_entry.version,
"tag_name": changelog_entry.version,
"description": changelog_entry.body,
}
try:
project.releases.create(data)
except gitlab.GitlabAuthenticationError as e:
if e.response_code == http.HTTPStatus.UNAUTHORIZED:
raise release_by_changelog.exc.UnauthorizedError from e
raise e
except gitlab.GitlabCreateError as e:
if e.response_code == http.HTTPStatus.CONFLICT:
raise release_by_changelog.exc.ReleaseAlreadyExistError from e
raise e
return changelog_entry.version
from __future__ import annotations
import typing
class ChangelogEntry(typing.NamedTuple):
version: str
body: str
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment