154 lines
4.4 KiB
Python
154 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Release helper: git commit/push + docker build/push.
|
|
|
|
Flow:
|
|
1) Prompt for version
|
|
2) git add -A
|
|
3) git commit -m "Version <version>" (skips if nothing to commit)
|
|
4) git push -u origin main (ensures origin points to target repo)
|
|
5) docker build + push tags: <version> and latest
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Config:
|
|
target_git_url: str = "https://git.alphen.cloud/bramval/PsalmbordOnlineCE.git"
|
|
docker_image: str = "git.alphen.cloud/bramval/psalmbordonlinece"
|
|
branch: str = "main"
|
|
|
|
|
|
VERSION_RE = re.compile(r"^[0-9A-Za-z][0-9A-Za-z._-]{0,127}$")
|
|
|
|
|
|
def run(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]:
|
|
"""Run command and stream output; return CompletedProcess."""
|
|
print(f"\n$ {' '.join(cmd)}")
|
|
return subprocess.run(cmd, text=True, check=check)
|
|
|
|
|
|
def capture(cmd: list[str]) -> str:
|
|
"""Run command and capture stdout."""
|
|
return subprocess.check_output(cmd, text=True).strip()
|
|
|
|
|
|
def ensure_tools() -> None:
|
|
for tool in ("git", "docker"):
|
|
if shutil.which(tool) is None:
|
|
raise SystemExit(f"Error: '{tool}' is not available in PATH")
|
|
|
|
|
|
def prompt_version() -> str:
|
|
version = input("Enter version (e.g. 1.2.3): ").strip()
|
|
if not version:
|
|
raise SystemExit("Error: version cannot be empty")
|
|
if not VERSION_RE.match(version):
|
|
raise SystemExit(
|
|
"Error: invalid version. Use only letters/numbers and . _ - (max 128 chars)"
|
|
)
|
|
return version
|
|
|
|
|
|
def prompt_yes_no(question: str, default_no: bool = True) -> bool:
|
|
prompt = "(y/N)" if default_no else "(Y/n)"
|
|
ans = input(f"{question} {prompt}: ").strip().lower()
|
|
if not ans:
|
|
return not default_no
|
|
return ans in ("y", "yes")
|
|
|
|
|
|
def ensure_origin(config: Config) -> None:
|
|
try:
|
|
origin_url = capture(["git", "remote", "get-url", "origin"])
|
|
except subprocess.CalledProcessError:
|
|
origin_url = ""
|
|
|
|
if not origin_url:
|
|
print(f"Remote 'origin' not found; adding {config.target_git_url}")
|
|
run(["git", "remote", "add", "origin", config.target_git_url])
|
|
return
|
|
|
|
if origin_url != config.target_git_url:
|
|
print(
|
|
f"Remote 'origin' is '{origin_url}' -> updating to '{config.target_git_url}'"
|
|
)
|
|
run(["git", "remote", "set-url", "origin", config.target_git_url])
|
|
|
|
|
|
def main() -> int:
|
|
config = Config()
|
|
|
|
ensure_tools()
|
|
version = prompt_version()
|
|
commit_message = f"Version {version}"
|
|
|
|
# Branch warning
|
|
try:
|
|
current_branch = capture(["git", "branch", "--show-current"])
|
|
except subprocess.CalledProcessError:
|
|
current_branch = ""
|
|
if current_branch and current_branch != config.branch:
|
|
print(
|
|
f"Warning: you are on branch '{current_branch}' (expected '{config.branch}')."
|
|
)
|
|
if not prompt_yes_no(f"Continue and push '{config.branch}' anyway?", default_no=True):
|
|
return 1
|
|
|
|
ensure_origin(config)
|
|
|
|
print("\n== Git: status ==")
|
|
run(["git", "status", "-sb"], check=False)
|
|
|
|
if not prompt_yes_no("Proceed with commit+push and docker push?", default_no=True):
|
|
print("Aborted.")
|
|
return 1
|
|
|
|
print("\n== Git: add/commit/push ==")
|
|
run(["git", "add", "-A"])
|
|
|
|
# commit may fail if nothing to commit
|
|
try:
|
|
run(["git", "commit", "-m", commit_message])
|
|
print(f"Committed: {commit_message}")
|
|
except subprocess.CalledProcessError:
|
|
print("No changes to commit (or commit failed). Continuing...")
|
|
|
|
run(["git", "push", "-u", "origin", config.branch])
|
|
|
|
print("\n== Docker: build/tag/push ==")
|
|
print(f"Docker image: {config.docker_image}")
|
|
print(f"Tags: {version}, latest")
|
|
|
|
run(
|
|
[
|
|
"docker",
|
|
"build",
|
|
"-t",
|
|
f"{config.docker_image}:{version}",
|
|
"-t",
|
|
f"{config.docker_image}:latest",
|
|
".",
|
|
]
|
|
)
|
|
run(["docker", "push", f"{config.docker_image}:{version}"])
|
|
run(["docker", "push", f"{config.docker_image}:latest"])
|
|
|
|
print("\nDone.")
|
|
print("If docker push failed due to auth, run: docker login git.alphen.cloud")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Make Ctrl+C exit nicely
|
|
try:
|
|
raise SystemExit(main())
|
|
except KeyboardInterrupt:
|
|
print("\nAborted (Ctrl+C).")
|
|
raise SystemExit(130)
|