feat: add script for building and pushing Docker images with tagging options
This commit is contained in:
15
README.md
15
README.md
@@ -79,6 +79,21 @@ Alle Counter werden nach jeder Änderung in `/data/counters.json` gespeichert. D
|
|||||||
- Non-root User `appuser`
|
- Non-root User `appuser`
|
||||||
- Kein Port-Expose (Long Polling)
|
- Kein Port-Expose (Long Polling)
|
||||||
|
|
||||||
|
## Docker Image veröffentlichen
|
||||||
|
Zum Bauen und Pushen des Images mit `latest`-Tag und einem datierten Tag (z. B. `2025-09-30`) steht das Skript `publish.py` bereit. Stelle vorher sicher, dass du in der Registry `git.beging.de` angemeldet bist.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python publish.py --date 2025-09-30
|
||||||
|
```
|
||||||
|
|
||||||
|
Ohne `--date` wird automatisch das heutige UTC-Datum verwendet. Mit `--dry-run` kannst du die Docker-Kommandos nur anzeigen lassen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python publish.py --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
Falls du den `latest` Tag nicht veröffentlichen möchtest, verwende `--no-latest`.
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
Standard: INFO. Anpassbar über `log_level` in der Config.
|
Standard: INFO. Anpassbar über `log_level` in der Config.
|
||||||
|
|
||||||
|
|||||||
93
publish.py
Normal file
93
publish.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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 <context>/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())
|
||||||
Reference in New Issue
Block a user