#!/usr/bin/python3 """Fetch OSTree commits from an repository Uses ostree to pull specific commits from (remote) repositories at the provided `url`. Can verify the commit, if one or more gpg keys are provided via `gpgkeys`. """ import json import os import sys import subprocess import uuid SCHEMA = """ "additionalProperties": false, "properties": { "commits": { "description": "The commits to fetch indexed their checksum", "type": "object", "additionalProperties": false, "patternProperties": { "[0-9a-f]{5,64}": { "type": "object", "additionalProperties": false, "required": ["remote"], "properties": { "remote": { "type": "object", "additionalProperties": false, "required": ["url"], "properties": { "url": { "type": "string", "description": "URL of the repository." }, "gpgkeys": { "type": "array", "items": { "type": "string", "description": "GPG keys to verify the commits" } } } } } } } } } """ def ostree(*args, _input=None, **kwargs): args = list(args) + [f'--{k}={v}' for k, v in kwargs.items()] print("ostree " + " ".join(args), file=sys.stderr) subprocess.run(["ostree"] + args, encoding="utf-8", stdout=sys.stderr, input=_input, check=True) def main(options, checksums, cache, output): commits = options["commits"] os.makedirs(output, exist_ok=True) os.makedirs(cache, exist_ok=True) # Prepare the cache and the output repo repo_cache = os.path.join(cache, "repo") ostree("init", mode="archive", repo=repo_cache) # Make sure the cache repository uses locks to protect the metadata during # shared access. This is the default since `2018.5`, but lets document this # explicitly here. ostree("config", "set", "repo.locking", "true", repo=repo_cache) repo_out = os.path.join(output, "repo") ostree("init", mode="archive", repo=repo_out) for commit in checksums: remote = commits[commit]["remote"] url = remote["url"] gpg = remote.get("gpgkeys", []) uid = str(uuid.uuid4()) ostree("remote", "add", "--no-gpg-verify", uid, url, repo=repo_cache) # this temporary remote is needed to verify # the commit signatures via gpg below ostree("remote", "add", uid, repo_cache, repo=repo_out) for key in gpg: ostree("remote", "gpg-import", "--stdin", uid, repo=repo_out, _input=key) # Transfer the commit: remote → cache print(f"pulling {commit}", file=sys.stderr) ostree("pull", uid, commit, repo=repo_cache) # Transfer the commit: cache → output verify_args = [] if gpg: verify_args = ["--gpg-verify", "--remote", uid] ostree("pull-local", repo_cache, commit, *verify_args, repo=repo_out) # Remove the temporary remotes again ostree("remote", "delete", uid, repo=repo_cache) ostree("remote", "delete", uid, repo=repo_out) json.dump({}, sys.stdout) return 0 if __name__ == '__main__': source_args = json.load(sys.stdin) r = main(source_args["options"], source_args["checksums"], source_args["cache"], source_args["output"]) sys.exit(r)