If a contenturl is specified, the url is used only for metadata. This is useful when the actual content is hosted separately.
165 lines
4.6 KiB
Python
Executable file
165 lines
4.6 KiB
Python
Executable file
#!/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 os
|
|
import subprocess
|
|
import sys
|
|
import uuid
|
|
|
|
from osbuild import sources
|
|
from osbuild.util.ostree import show
|
|
from osbuild.util.rhsm import Subscriptions
|
|
|
|
SCHEMA = """
|
|
"additionalProperties": false,
|
|
"definitions": {
|
|
"item": {
|
|
"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."
|
|
},
|
|
"contenturl": {
|
|
"type": "string",
|
|
"description": "content URL of the repository."
|
|
},
|
|
"gpgkeys": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"description": "GPG keys to verify the commits"
|
|
}
|
|
},
|
|
"secrets": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": [
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Name of the secrets provider."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"properties": {
|
|
"items": {"$ref": "#/definitions/item"},
|
|
"commits": {"$ref": "#/definitions/item"}
|
|
},
|
|
"oneOf": [{
|
|
"required": ["items"]
|
|
}, {
|
|
"required": ["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=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
input=_input,
|
|
check=True)
|
|
|
|
|
|
class OSTreeSource(sources.SourceService):
|
|
|
|
content_type = "org.osbuild.ostree"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.repo = None
|
|
|
|
def fetch_one(self, checksum, desc):
|
|
commit = checksum
|
|
remote = desc["remote"]
|
|
url = remote["url"]
|
|
gpg = remote.get("gpgkeys", [])
|
|
uid = str(uuid.uuid4())
|
|
|
|
remote_add_args = []
|
|
if not gpg:
|
|
remote_add_args = ["--no-gpg-verify"]
|
|
|
|
if "contenturl" in remote:
|
|
remote_add_args.append(f"--contenturl={remote['contenturl']}")
|
|
|
|
if remote.get("secrets", {}).get("name") == "org.osbuild.rhsm.consumer":
|
|
secrets = Subscriptions.get_consumer_secrets()
|
|
remote_add_args.append(f"--set=\"tls-client-key-path={secrets['consumer_key']}\"")
|
|
remote_add_args.append(f"--set=\"tls-client-cert-path={secrets['consumer_cert']}\"")
|
|
|
|
ostree("remote", "add",
|
|
uid, url,
|
|
*remote_add_args,
|
|
repo=self.repo)
|
|
|
|
for key in gpg:
|
|
ostree("remote", "gpg-import", "--stdin", uid,
|
|
repo=self.repo, _input=key)
|
|
|
|
# Transfer the commit: remote → cache
|
|
print(f"pulling {commit}", file=sys.stderr)
|
|
ostree("pull", uid, commit, repo=self.repo)
|
|
|
|
# Remove the temporary remotes again
|
|
ostree("remote", "delete", uid,
|
|
repo=self.repo)
|
|
|
|
def setup(self, args):
|
|
super().setup(args)
|
|
# Prepare the cache and the output repo
|
|
self.repo = os.path.join(self.cache, "repo")
|
|
ostree("init", mode="archive", repo=self.repo)
|
|
|
|
# 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=self.repo)
|
|
|
|
# pylint: disable=[no-self-use]
|
|
def exists(self, checksum, _desc):
|
|
try:
|
|
show(self.repo, checksum)
|
|
except RuntimeError:
|
|
return False
|
|
return True
|
|
|
|
|
|
def main():
|
|
service = OSTreeSource.from_args(sys.argv[1:])
|
|
service.main()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|