import argparse import datetime as dt import shlex import subprocess import sys from pathlib import Path REPOSITORY = "git.beging.de/troogs/gigalativbot" DEFAULT_CONTEXT = Path(__file__).parent def run_command(cmd: list[str], /, *, dry_run: bool) -> None: """Print and optionally execute a shell command.""" print("$", shlex.join(cmd)) if dry_run: return subprocess.run(cmd, check=True) def build_and_push(*, include_latest: bool, date_tag: str, context: Path, dockerfile: Path | None, dry_run: bool) -> None: tags: list[str] = [] if include_latest: tags.append(f"{REPOSITORY}:latest") tags.append(f"{REPOSITORY}:{date_tag}") build_cmd = ["docker", "build"] for tag in tags: build_cmd.extend(["-t", tag]) if dockerfile is not None: build_cmd.extend(["-f", str(dockerfile)]) build_cmd.append(str(context)) run_command(build_cmd, dry_run=dry_run) for tag in tags: run_command(["docker", "push", tag], dry_run=dry_run) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Build and push docker images with latest and YYYYMMDD tags.") parser.add_argument("--context", default=DEFAULT_CONTEXT, type=Path, help="Build context directory (default: project root)") parser.add_argument("--dockerfile", type=Path, default=None, help="Path to Dockerfile (defaults to /Dockerfile)") parser.add_argument("--date", help="Custom date string for tag (YYYY-MM-DD). Defaults to today (UTC).") parser.add_argument("--no-latest", action="store_true", help="Do not tag/push the 'latest' tag.") parser.add_argument("--dry-run", action="store_true", help="Print the docker commands without executing them.") return parser.parse_args() def ensure_date_tag(raw: str | None) -> str: if raw is None: return dt.datetime.utcnow().strftime("%Y-%m-%d") try: parsed = dt.datetime.strptime(raw, "%Y-%m-%d") except ValueError as exc: raise ValueError("Date tag must be in YYYY-MM-DD format.") from exc return parsed.strftime("%Y-%m-%d") def main() -> int: args = parse_args() try: date_tag = ensure_date_tag(args.date) except ValueError as exc: print(f"Error: {exc}", file=sys.stderr) return 1 context = args.context.resolve() dockerfile = args.dockerfile.resolve() if args.dockerfile else None if dockerfile and not dockerfile.exists(): print(f"Error: Dockerfile {dockerfile} does not exist.", file=sys.stderr) return 2 if not context.exists(): print(f"Error: context directory {context} does not exist.", file=sys.stderr) return 3 try: build_and_push( include_latest=not args.no_latest, date_tag=date_tag, context=context, dockerfile=dockerfile, dry_run=args.dry_run, ) except subprocess.CalledProcessError as exc: print(f"Command failed with exit code {exc.returncode}: {exc.cmd}", file=sys.stderr) return exc.returncode return 0 if __name__ == "__main__": sys.exit(main())