From d6217eeb4b82e2c3a93f869c6a49fbafb1e70f0c Mon Sep 17 00:00:00 2001 From: "Antoine R. Dumont (@ardumont)" <ardumont@softwareheritage.org> Date: Wed, 18 Jan 2023 15:21:41 +0100 Subject: [PATCH] scripts/app_manager: Add subcommand to manipulate tags ``` $ app_manager tag --help Usage: app_manager.py tag [OPTIONS] COMMAND [ARGS]... Manipulate application tag, for example either determine the last tag or compute the next one. Without any parameters this lists the current application tags. Options: --help Show this message and exit. Commands: latest list next ``` This will allow to: - retrieve the last known tag for an application - determine the next tag for an application (be it known or not) - current list of tags of an application This will raise when: - providing both --next-tag and --last-tag for an application - listing tags for an unknown application - retrieving the last tag on an unknown application Refs. swh/infra/sysadm-environment#4724 --- scripts/Dockerfile | 1 + scripts/app_manager.py | 107 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/scripts/Dockerfile b/scripts/Dockerfile index d52e9790a..963e70728 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get -y update && \ python3-click \ python3-venv \ python3-yaml \ + python3-dulwich \ && \ apt clean diff --git a/scripts/app_manager.py b/scripts/app_manager.py index 6edb05b55..fcef6e005 100755 --- a/scripts/app_manager.py +++ b/scripts/app_manager.py @@ -5,22 +5,28 @@ # License: GNU General Public License v3 or later # See top-level LICENSE file for more information -"""Cli to list applications (with/without filters) or generate the -requirements-frozen.txt file for app(s) provided as parameters. +"""Cli to manipulate applications (with/without filters), generate/update the +requirements-frozen.txt file for app(s) provided as parameters, list or generate tags, +... """ -import click +from __future__ import annotations + +from collections import defaultdict import os import pathlib import subprocess import sys -import yaml -from collections import defaultdict import tempfile +from typing import TYPE_CHECKING, Dict, Iterator, List, Set, Tuple from venv import EnvBuilder -from typing import Dict, Iterator, Set +import click +import yaml + +if TYPE_CHECKING: + from dulwich.repo import Repo APPS_DIR = pathlib.Path(__file__).absolute().parent.parent / "apps" @@ -215,6 +221,95 @@ def compute_yaml(updated_information: Dict[str, Dict[str, str]]) -> Dict[str, st return yaml_dict +def application_tags(repo: Repo, application: str) -> List[Tuple[str, str, str]]: + """Returns list of tuple application (tag, date, increment) (from most recent to + oldest).""" + import re + + pattern = re.compile( + fr"refs/tags/{re.escape(application)}-(?P<date>[0-9]+)\.(?P<inc>[0-9]+)" + ) + + tags = [] + for current_ref in repo.get_refs(): + ref = current_ref.decode() + is_match = pattern.fullmatch(ref) + if not is_match: + continue + mdata = is_match.groupdict() + tag = ref.replace("refs/tags/", "") + tags.append((tag, mdata["date"], mdata["inc"])) + + return sorted(tags, reverse=True) + + +@app.group("tag") +@click.pass_context +def tag(ctx): + """Manipulate application tag, for example either determine the last tag or compute + the next one. Without any parameters this lists the current application tags. + + """ + from dulwich.repo import Repo + repo_dirpath = ctx.obj["apps_dir"] / '..' + + ctx.obj["repo"] = Repo(repo_dirpath) + + +@tag.command("list") +@click.argument("application", required=True) +@click.pass_context +def tag_list(ctx, application: str): + """List all tags for the application provided.""" + repo = ctx.obj["repo"] + tags = application_tags(repo, application) + + if not tags: + raise ValueError(f"No tag to display for application '{application}'") + + # else, with no params, just print the application tags + for tag in tags: + print(tag[0]) + + +@tag.command("latest") +@click.argument("application", required=True) +@click.pass_context +def tag_latest(ctx, application: str): + """Determine the latest tag for the application provided.""" + repo = ctx.obj["repo"] + tags = application_tags(repo, application) + + if not tags: + raise ValueError(f"No tag to display for application '{application}'") + + # Determine the application's latest tag if any + print(tags[0][0]) + + +@tag.command("next") +@click.argument("application", required=True) +@click.pass_context +def tag_next(ctx, application: str): + """Compute the next tag for the application provided.""" + from datetime import datetime, timezone + + repo = ctx.obj["repo"] + tags = application_tags(repo, application) + + now = datetime.now(tz=timezone.utc) + current_date = now.strftime("%Y%m%d") + if tags: + _, previous_date, previous_tag_date_inc = tags[0] + inc = int(previous_tag_date_inc) + 1 if current_date == previous_date else 1 + else: + # First time we ask a tag for that application + inc = 1 + + tag = f"{application}-{current_date}.{inc}" + print(tag) + + @app.command("update-values") @click.option( "-v", -- GitLab