vauxite-build/ostree-engine

238 lines
8.0 KiB
Plaintext
Raw Normal View History

2024-02-02 02:25:46 +01:00
#!/usr/bin/env python3
2024-02-06 04:59:46 +01:00
"""ostree-engine
Usage:
ostree-engine ([--source-branch=BRANCH] [--source-url=URL] | --no-download-sources)
[--ostree-branch=REF] [--treefile=TREEFILE]
[--dest-repo=PATH] [--gpg-passfile=PATH]
[--no-deploy] [--no-clean]
ostree-engine --version
Options:
--no-deploy Do not deploy resulting ostree repo to web server root.
--no-clean Do not clean the working directory, i.e. .cache, .build-repo, .source-repo, .tmp
--no-download-sources Do not download source repo.
--source-url=URL URL for source repo. [default: https://pagure.io/workstation-ostree-config]
--source-branch=BRANCH Branch of the source repo.
--treefile=TREEFILE YAML or JSON treefile to use for rpm-ostree.
--ostree-branch=REF Name of the ref branch.
--dest-repo=PATH Local or remote filesystem destination for rsync-repos. Usually a web server root. [default: /srv/repo]
--gpg-passfile=PATH Path to JSON file containing GPG key-id and passphrase (for auto signing commits).
--version Print version information.
"""
2024-02-02 02:25:46 +01:00
import datetime as dt
2024-02-06 04:59:46 +01:00
import json
2024-02-02 02:25:46 +01:00
import os
import shlex
import shutil
import subprocess
import sys
2024-02-06 04:59:46 +01:00
from docopt import docopt
2024-02-02 02:25:46 +01:00
from glob import glob
from pathlib import Path
if os.geteuid() != 0:
exit("Please run this script with sudo")
2024-02-06 04:59:46 +01:00
BASE_DIR = Path("/var/local/vauxite")
2024-02-02 02:25:46 +01:00
OSTREE_FILES_DIR = BASE_DIR.joinpath("src")
CACHE_DIR = BASE_DIR.joinpath(".cache")
BUILD_REPO = BASE_DIR.joinpath(".build-repo")
SOURCE_REPO = BASE_DIR.joinpath(".source-repo")
DEPLOY_REPO = BASE_DIR.joinpath(".deploy-repo")
WK_DIR = BASE_DIR.joinpath(".tmp")
def print_log(msg: str):
if sys.stdout.isatty():
log_date = dt.datetime.now().isoformat(" ", "seconds")
print("%s: %s" % (log_date, msg))
else:
print(msg)
def handle_err():
print_log("ERROR:")
2024-02-06 04:59:46 +01:00
print(f"{sys.exc_info()[0]}")
print(f"{sys.exc_info()[1]}")
print(f"{sys.exc_info()[2]}")
2024-02-02 02:25:46 +01:00
exit(1)
def clean_wk_dir():
try:
print_log("Clean working directory")
shutil.rmtree(CACHE_DIR)
2024-02-06 04:59:46 +01:00
if not args.get("--no-download-sources"):
shutil.rmtree(SOURCE_REPO)
2024-02-02 02:25:46 +01:00
shutil.rmtree(WK_DIR)
for dir in glob("/tmp/rpmostree*", recursive=True):
shutil.rmtree(dir)
except FileNotFoundError as ferr:
pass
except Exception:
handle_err()
def run_proc(cmd: str, capture_output=False) -> subprocess.CompletedProcess:
try:
if capture_output:
return subprocess.run(shlex.split(cmd), capture_output=True, text=True)
return subprocess.run(shlex.split(cmd), check=True, text=True)
except subprocess.CalledProcessError:
handle_err()
def prepare_build_env():
2024-02-06 04:59:46 +01:00
if not args.get("--no-clean"):
clean_wk_dir()
2024-02-02 02:25:46 +01:00
print_log("Ensure CACHE_DIR exists")
CACHE_DIR.mkdir(exist_ok=True)
print_log("Ensure WK_DIR exists")
WK_DIR.mkdir(exist_ok=True)
print_log("Remove previous BUILD_REPO if it exists")
shutil.rmtree(BUILD_REPO, ignore_errors=True)
print_log("Initialize ostree repo in bare-user mode")
run_proc(f"ostree --repo={BUILD_REPO} init --mode=bare-user")
if not DEPLOY_REPO.exists():
print_log("Deploy repo not found; initialize new deploy repo in archive mode")
run_proc(f"ostree --repo={BUILD_REPO} init --mode=archive")
else:
print_log("Pull existing deploy repo into local build repo")
run_proc(
2024-02-06 04:59:46 +01:00
f"ostree --repo={BUILD_REPO} pull-local --depth=2 {DEPLOY_REPO} {args.get("--ostree-branch")}"
2024-02-02 02:25:46 +01:00
)
if not SOURCE_REPO.exists():
print_log(
2024-02-06 04:59:46 +01:00
f"Clone branch {args.get("--source-branch")} of {args.get("--source-url")} into SOURCE_REPO"
2024-02-02 02:25:46 +01:00
)
2024-02-06 04:59:46 +01:00
run_proc(f"git clone -b {args.get("--source-branch")} {args.get("--source-url")} {SOURCE_REPO}")
2024-02-02 02:25:46 +01:00
print_log("Copy SOURCE_REPO contents into WK_DIR")
run_proc(f"rsync -aAX {SOURCE_REPO}/ {WK_DIR}")
print_log("Remove upstream xfce-desktop-pkgs.yaml from WK_DIR")
WK_DIR.joinpath("xfce-desktop-pkgs.yaml").unlink(missing_ok=True)
print_log("Copy OSTREE_FILES_DIR contents into WK_DIR")
run_proc(f"rsync -aAX {OSTREE_FILES_DIR}/ {WK_DIR}")
def compose_ostree():
print_log("Compose ostree")
time_fmt = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H%M%S")
run_proc(
subprocess.list2cmdline(
[
"rpm-ostree",
"compose",
"tree",
"--unified-core",
f"--cachedir={CACHE_DIR}",
f"--repo={BUILD_REPO}",
f"--add-metadata-string=Build={time_fmt}",
2024-02-06 04:59:46 +01:00
WK_DIR.joinpath(args.get("--treefile")),
2024-02-02 02:25:46 +01:00
]
)
)
2024-02-06 04:59:46 +01:00
def sign_commit():
commit_id = subprocess.run(["ostree", f"--repo={DEPLOY_REPO}", "rev-parse", args.get("--ostree-branch")], capture_output=True, text=True)
with open(args.get("--gpg-passfile"), "r") as json_file:
gpg_data = json.loads(json_file.read())
print_log(f"Signing rpm-ostree commit {commit_id.stdout} with GPG key-id {gpg_data.get("gpg-id")}")
gpg_cmd = f"echo {gpg_data.get("passphrase")} | gpg --batch --always-trust --yes --passphrase-fd 0 --pinentry-mode=loopback -s $(mktemp)"
run_gpg_cmd = subprocess.Popen(gpg_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = run_gpg_cmd.communicate()[0]
if output:
print_log(f"Error unlocking GPG keyring: {output}")
exit(99)
run_proc(f"ostree --repo={DEPLOY_REPO} gpg-sign {commit_id.stdout} {gpg_data.get("gpg-id")}")
2024-02-02 02:25:46 +01:00
def prepare_deploy():
print_log("Prune refs older than 30 days")
run_proc(
f"ostree --repo={BUILD_REPO} prune --refs-only --keep-younger-than='30 days ago'"
)
print_log("Pull new ostree commit into DEPLOY_REPO")
run_proc(
2024-02-06 04:59:46 +01:00
f"ostree --repo={DEPLOY_REPO} pull-local --depth=1 {BUILD_REPO} {args.get("--ostree-branch")}"
2024-02-02 02:25:46 +01:00
)
print_log("Remove local build repo")
shutil.rmtree(BUILD_REPO, ignore_errors=True)
print_log("Check filesystem for errors")
2024-02-06 04:59:46 +01:00
run_proc(f"ostree --repo={DEPLOY_REPO} fsck {args.get("--ostree-branch")}")
2024-02-02 02:25:46 +01:00
def generate_deltas():
print_log("Check if main ref has parent")
check_parent = run_proc(
2024-02-06 04:59:46 +01:00
f"ostree --repo={DEPLOY_REPO} show {args.get("--ostree-branch")}", capture_output=True
2024-02-02 02:25:46 +01:00
)
if not check_parent.stderr:
print_log("Generate static delta from main ref's parent")
run_proc(
2024-02-06 04:59:46 +01:00
f"ostree --repo={DEPLOY_REPO} static-delta generate {args.get("--ostree-branch")}"
2024-02-02 02:25:46 +01:00
)
print_log("Check if main ref's parent has parent")
check_gparent = run_proc(
2024-02-06 04:59:46 +01:00
f"ostree --repo={DEPLOY_REPO} show {args.get("--ostree-branch")}^^",
2024-02-02 02:25:46 +01:00
capture_output=True,
)
if not check_gparent.stderr:
print_log("Generate static delta from parent of main ref's parent")
run_proc(
2024-02-06 04:59:46 +01:00
f"ostree --repo={DEPLOY_REPO} static-delta generate --from={args.get("--ostree-branch")}^^ --to={args.get("--ostree-branch")}"
2024-02-02 02:25:46 +01:00
)
else:
print_log("Main ref's parent has no parent. No deltas generated.")
else:
print_log("Main ref has no parent. No deltas generated.")
def update_summary():
print_log("Update summary file of DEPLOY_REPO")
run_proc(f"ostree --repo={DEPLOY_REPO} summary -u")
def deploy_repo():
print_log("Deploying repo to web server root")
if DEPLOY_REPO.exists():
2024-02-06 04:59:46 +01:00
run_proc(f"{BASE_DIR}/rsync-repos --src {DEPLOY_REPO} --dest {args.get("--dest-repo")}")
2024-02-02 02:25:46 +01:00
else:
print_log("DEPLOY_REPO not found. Not deploying to web server")
if __name__ == "__main__":
2024-02-06 04:59:46 +01:00
args = docopt(__doc__, help=True, version="ostree-engine 0.1.0")
prepare_build_env()
compose_ostree()
prepare_deploy()
generate_deltas()
if args.get("--gpg-passfile"):
sign_commit()
update_summary()
if not args.get("--no-deploy"):
2024-02-02 02:25:46 +01:00
deploy_repo()