refactor: Split CLI into sub parsers

Misc refactoring
This commit is contained in:
Nikolai Røed Kristiansen
2024-10-08 15:59:53 +02:00
committed by Nikolai Røed Kristiansen
parent 9d4f069960
commit 2dbe44771b
7 changed files with 78 additions and 59 deletions

View File

@@ -23,7 +23,7 @@ jobs:
- name: Generate build matrix
run: |
FORCE=$(if git log --pretty=format:"%s" HEAD^..HEAD | grep -q '\[force\]'; then echo "--force"; else echo ""; fi)
uv run dpn --ci-matrix $FORCE --ci-event ${{ github.event_name }}
uv run dpn $FORCE build-matrix --event ${{ github.event_name }}
id: set-matrix
deploy:
@@ -39,7 +39,7 @@ jobs:
with:
enable-cache: true
- name: Generate Dockerfile from config
run: uv run dpn --dockerfile-with-context '${{ toJSON(matrix) }}'
run: uv run dpn dockerfile --context '${{ toJSON(matrix) }}'
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
@@ -79,7 +79,7 @@ jobs:
enable-cache: true
- name: Update versions.json and README.md, then commit and push changes (if any)
run: |
uv run dpn --verbose --release
uv run dpn --verbose release
clean_checkout=$(git status --porcelain)
if [[ -n "${clean_checkout}" ]]; then
git config --global user.name "Nikolai Kristiansen" > /dev/null 2>&1

3
.gitignore vendored
View File

@@ -1,10 +1,9 @@
debug-*.Dockerfile
.idea/
dockerfiles/
.circleci/config_generated.yml
version_config.json
__pycache__
.coverage
coverage.xml
.vscode/
htmlcov/
tmp/

View File

@@ -44,7 +44,7 @@ ignore = [
"FBT001", # Allow boolean function args
"FIX001" # Allow fixme's
]
target-version = "py311"
target-version = "py312"
[tool.ruff.per-file-ignores]
"**/test*.py" = ["S101"]
@@ -54,6 +54,5 @@ strict = true
disallow_any_unimported = true
no_implicit_optional = true
[tool.pytest.ini_options]
addopts = "--disable-socket"

View File

@@ -2,6 +2,7 @@ import dataclasses
import json
import logging
import os
import sys
from pathlib import Path
from typing import TYPE_CHECKING
@@ -19,11 +20,15 @@ def _github_action_set_output(key: str, value: str) -> None:
"""Write
https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter
"""
if not GITHUB_OUTPUT:
print("GITHUB_OUTPUT not set", file=sys.stderr)
sys.exit(1)
with Path(GITHUB_OUTPUT).open("a") as fp:
fp.write(f"{key}={value}")
def generate_matrix(new_or_updated: "list[BuildVersion]", ci_event: str) -> None:
def build_matrix(new_or_updated: "list[BuildVersion]", ci_event: str) -> None:
if not new_or_updated and ci_event == CI_EVENT_SCHEDULED:
logger.info("\n# Scheduled run with no new or updated versions. Doing nothing.")
return

View File

@@ -1,18 +1,16 @@
import argparse
import logging
from typing import cast
from typing import Literal, cast
from .ci_matrix import generate_matrix
from .build_matrix import build_matrix
from .dockerfiles import render_dockerfile_with_context
from .readme import format_supported_versions, update_dynamic_readme
from .readme import update_dynamic_readme
from .settings import DISTROS
from .versions import (
decide_version_combinations,
fetch_supported_nodejs_versions,
find_new_or_updated,
load_versions,
persist_versions,
scrape_supported_python_versions,
supported_versions,
)
logger = logging.getLogger("dpn")
@@ -21,41 +19,48 @@ logger = logging.getLogger("dpn")
class CLIArgs(argparse.Namespace):
dry_run: bool
distros: list[str]
ci_matrix: bool
dockerfile_with_context: str
ci_event: str
release: bool
force: bool
verbose: bool
command: Literal["dockerfile", "build-matrix", "release"]
force: bool # build-matrix and release command arg
context: str # dockerfile command arg
event: str # build-matrix command arg
def run_dockerfile(args: CLIArgs) -> None:
render_dockerfile_with_context(args.context, args.dry_run)
def run_build_matrix(args: CLIArgs) -> None:
suported_python_versions, suported_nodejs_versions = supported_versions()
versions = decide_version_combinations(args.distros, suported_python_versions, suported_nodejs_versions)
new_or_updated = find_new_or_updated(versions, args.force)
build_matrix(new_or_updated, args.event)
def run_release(args: CLIArgs) -> None:
suported_python_versions, suported_nodejs_versions = supported_versions()
versions = decide_version_combinations(args.distros, suported_python_versions, suported_nodejs_versions)
new_or_updated = find_new_or_updated(versions, args.force)
if not new_or_updated:
logger.info("No new or updated versions")
return
persist_versions(versions, args.dry_run)
update_dynamic_readme(versions, suported_python_versions, suported_nodejs_versions, args.dry_run)
def main(args: CLIArgs) -> None:
if args.dry_run:
logger.debug("Dry run, outputing only.")
if args.dockerfile_with_context:
render_dockerfile_with_context(args.dockerfile_with_context, args.dry_run)
return
current_versions = load_versions()
suported_python_versions = scrape_supported_python_versions()
suported_nodejs_versions = fetch_supported_nodejs_versions()
supported_versions = format_supported_versions(suported_python_versions, suported_nodejs_versions)
logger.debug(f"Found the following supported versions:\n{supported_versions}")
versions = decide_version_combinations(args.distros, suported_python_versions, suported_nodejs_versions)
new_or_updated = find_new_or_updated(current_versions, versions, args.force)
if args.ci_matrix:
generate_matrix(new_or_updated, args.ci_event)
if not new_or_updated and not args.ci_matrix:
logger.info("No new or updated versions")
return
if args.release:
persist_versions(versions, args.dry_run)
update_dynamic_readme(versions, suported_python_versions, suported_nodejs_versions, args.dry_run)
if args.command == "dockerfile":
run_dockerfile(args)
elif args.command == "build-matrix":
run_build_matrix(args)
elif args.command == "release":
run_release(args)
def parse_args() -> CLIArgs:
@@ -75,16 +80,21 @@ def parse_args() -> CLIArgs:
dest="dry_run",
help="Skip persisting, README update, and pushing of builds",
)
parser.add_argument("--ci-matrix", action="store_true", help="Generate CI build matrix")
parser.add_argument(
"--ci-event",
parser.add_argument("--force", action="store_true", help="Force build all versions (even old)")
parser.add_argument("--verbose", action="store_true", help="Enable debug logging")
subparsers = parser.add_subparsers(dest="command", help="Sub-commands")
# Dockerfile command
parser_dockerfile = subparsers.add_parser("dockerfile", help="Render a dockerfile based on version config")
parser_dockerfile.add_argument("context", default="", help="Dockerfile version config")
# Build matrix command
parser_build_matrix = subparsers.add_parser("build-matrix", help="Generate CI build matrix")
parser_build_matrix.add_argument(
"--event",
default="webhook",
# https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
help="GitHub Action event name (github.event_name)",
)
parser.add_argument("--release", action="store_true", help="Persist versions and make a release")
parser.add_argument("--dockerfile-with-context", default="", help="Render a dockerfile based on version config")
parser.add_argument("--force", action="store_true", help="Force build all versions (even old)")
parser.add_argument("--verbose", action="store_true", help="Enable debug logging")
# Release command
subparsers.add_parser("release", help="Persist versions and make a release")
return cast(CLIArgs, parser.parse_args())

View File

@@ -67,14 +67,10 @@ def format_supported_versions(
python_versions: "list[SupportedVersion]",
node_versions: "list[SupportedVersion]",
) -> str:
headings = ["Python version", "Start", "End"]
rows = [[ver.version, ver.start, ver.end] for ver in sorted(python_versions, key=lambda x: x.start, reverse=True)]
python_table = _format_md_table(headings, rows)
def _as_rows(versions: "list[SupportedVersion]") -> list[list[str]]:
return [[ver.version, ver.start, ver.end] for ver in sorted(versions, key=lambda x: x.start, reverse=True)]
headings_node = ["Node.js version", "Start", "End"]
rows_node = [
[ver.version, ver.start, ver.end] for ver in sorted(node_versions, key=lambda x: x.start, reverse=True)
]
node_table = _format_md_table(headings_node, rows_node)
python_table = _format_md_table(["Python version", "Start", "End"], _as_rows(python_versions))
node_table = _format_md_table(["Node.js version", "Start", "End"], _as_rows(node_versions))
return f"{python_table}\n{node_table}"

View File

@@ -9,6 +9,8 @@ import requests
from bs4 import BeautifulSoup
from semver.version import Version
from docker_python_nodejs.readme import format_supported_versions
from .docker_hub import DockerImageDict, DockerTagDict, fetch_tags
from .nodejs_versions import (
fetch_node_releases,
@@ -162,6 +164,14 @@ def fetch_supported_nodejs_versions() -> list[SupportedVersion]:
return versions
def supported_versions() -> tuple[list[SupportedVersion], list[SupportedVersion]]:
suported_python_versions = scrape_supported_python_versions()
suported_nodejs_versions = fetch_supported_nodejs_versions()
supported_versions = format_supported_versions(suported_python_versions, suported_nodejs_versions)
logger.debug(f"Found the following supported versions:\n{supported_versions}")
return suported_python_versions, suported_nodejs_versions
def _has_arch_files(files: list[str], distro: str) -> bool:
if distro == "alpine":
return {"linux-x64-musl"}.issubset(files)
@@ -257,13 +267,13 @@ def load_versions() -> list[BuildVersion]:
def find_new_or_updated(
current_versions: list[BuildVersion],
versions: list[BuildVersion],
force: bool = False,
) -> list[BuildVersion]:
if force:
logger.warning("Generating full build matrix because --force is set")
current_versions = load_versions()
current_versions_dict = {ver.key: ver for ver in current_versions}
versions_dict = {ver.key: ver for ver in versions}
new_or_updated: list[BuildVersion] = []