Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix empty ostree commit object in deploy stage #4

Open
wants to merge 17 commits into
base: dusty-fedora-coreos
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions inputs/org.osbuild.ostree
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ contain `ref` it was specified.

import json
import os
import subprocess
import sys

from osbuild import inputs
from osbuild.util import ostree

SCHEMA = """
"definitions": {
Expand Down Expand Up @@ -84,34 +84,24 @@ SCHEMA = """
"""


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 export(checksums, cache, output):
repo_cache = os.path.join(cache, "repo")

repo_out = os.path.join(output, "repo")
ostree("init", mode="archive", repo=repo_out)
ostree.cli("init", mode="archive", repo=repo_out)

refs = {}
for commit, options in checksums.items():
# Transfer the commit: remote → cache
print(f"exporting {commit}", file=sys.stderr)

ostree("pull-local", repo_cache, commit,
repo=repo_out)
ostree.cli("pull-local", repo_cache, commit,
repo=repo_out)

ref = options.get("ref")
if ref:
ostree("refs", "--create", ref, commit,
repo=repo_out)
ostree.cli("refs", "--create", ref, commit,
repo=repo_out)

refs[commit] = options

Expand Down
2 changes: 1 addition & 1 deletion osbuild/buildroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def run(self, argv, monitor, timeout=None, binds=None, readonly_binds=None, extr

# Import directories from the caller-provided root.
imports = ["usr"]
if self.mount_boot:
if True:
imports.insert(0, "boot")

for p in imports:
Expand Down
6 changes: 6 additions & 0 deletions osbuild/objectstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,16 @@ def init(self):
# with just /usr mounted from the host
usr = os.path.join(root, "usr")
os.makedirs(usr)
boot = os.path.join(root, "boot")
os.makedirs(boot)
etc = os.path.join(root, "etc")
os.makedirs(etc)

# ensure / is read-only
mount(root, root)
mount("/etc", etc)
mount("/usr", usr)
mount("/boot", boot)

@property
def tree(self) -> os.PathLike:
Expand Down
28 changes: 28 additions & 0 deletions osbuild/util/ostree.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# pylint doesn't understand the string-annotation below
from typing import Any, List # pylint: disable=unused-import

from osbuild.util.rhsm import Subscriptions

from .types import PathLike


Expand Down Expand Up @@ -111,6 +113,32 @@ def as_tmp_file(self):
os.unlink(name)


def setup_remote(repo, name, remote):
"""Configure an OSTree remote in a given repo"""

url = remote["url"]
gpg = remote.get("gpgkeys", [])

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']}")

cli("remote", "add", name, url,
*remote_add_args, repo=repo)

for key in gpg:
cli("remote", "gpg-import", "--stdin",
name, repo=repo, _input=key)


def rev_parse(repo: PathLike, ref: str) -> str:
"""Resolve an OSTree reference `ref` in the repository at `repo`"""

Expand Down
54 changes: 10 additions & 44 deletions sources/org.osbuild.ostree
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ 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
from osbuild.util import ostree

SCHEMA = """
"additionalProperties": false,
Expand Down Expand Up @@ -81,17 +79,6 @@ SCHEMA = """
"""


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"
Expand All @@ -103,54 +90,33 @@ class OSTreeSource(sources.SourceService):
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)
# This is a temporary remote so we'll just use a random name
name = str(uuid.uuid4())

for key in gpg:
ostree("remote", "gpg-import", "--stdin", uid,
repo=self.repo, _input=key)
ostree.setup_remote(self.repo, name, remote)

# Transfer the commit: remote → cache
print(f"pulling {commit}", file=sys.stderr)
ostree("pull", uid, commit, repo=self.repo)
ostree.cli("pull", name, commit, repo=self.repo)

# Remove the temporary remotes again
ostree("remote", "delete", uid,
repo=self.repo)
# Remove the temporary remote again
ostree.cli("remote", "delete", name, 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)
ostree.cli("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)
ostree.cli("config", "set", "repo.locking", "true", repo=self.repo)

# pylint: disable=[no-self-use]
def exists(self, checksum, _desc):
try:
show(self.repo, checksum)
ostree.show(self.repo, checksum)
except RuntimeError:
return False
return True
Expand Down
102 changes: 79 additions & 23 deletions stages/org.osbuild.ostree.deploy
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import os
import sys

import osbuild.api
from osbuild.util import ostree
from osbuild.util import containers, ostree
from osbuild.util.mnt import MountGuard

CAPABILITIES = ["CAP_MAC_ADMIN"]

SCHEMA_2 = """
"options": {
"additionalProperties": false,
"required": ["osname", "ref"],
"required": ["osname"],
"properties": {
"mounts": {
"description": "Mount points of the final file system",
Expand Down Expand Up @@ -84,12 +84,33 @@ SCHEMA_2 = """
},
"inputs": {
"type": "object",
"required": ["commits"],
"additionalProperties": false,
"oneOf": [{
"required": ["commits"]
}, {
"required": ["images"]
}, {
"not": {
"anyOf": [{
"required": ["commits"]
}, {
"required": ["images"]
}]
}
}],
"properties": {
"commits": {
"type": "object",
"additionalProperties": true
},
"images": {
"type": "object",
"additionalProperties": true
},
"manifest-lists": {
"type": "object",
"description": "Optional manifest lists to merge into images. The metadata must specify an image ID to merge to.",
"additionalProperties": true
}
}
}
Expand All @@ -104,44 +125,79 @@ def make_fs_identifier(desc):
raise ValueError("unknown rootfs type")


def main(tree, inputs, options):
osname = options["osname"]
rootfs = options.get("rootfs")
mounts = options.get("mounts", [])
kopts = options.get("kernel_opts", [])
ref = options["ref"]
remote = options.get("remote")
def ostree_commit_deploy(tree, inputs, osname, remote, ref, kopts):
if len(inputs) == 0:
if not ref:
raise ValueError("ref should be specified in options")
elif len(inputs) == 1:
if ref:
raise ValueError("Should not specify ref if input was specified")

# If provided an input then do the pull into the tree
if len(inputs) != 0:
# If we have an input then we need to pull_local() from the input
# first before we deploy.
source_repo, commits = ostree.parse_input_commits(inputs["commits"])
target_repo = f"{tree}/ostree/repo"
for commit, data in commits.items():
loopref = data.get("ref", commit)
ostree.pull_local(source_repo, target_repo, remote, loopref)
ref = data.get("ref", commit)
ostree.pull_local(source_repo, target_repo, remote, ref)

if remote:
ref = f"{remote}:{ref}"

kargs = []
kargs = [f'--karg-append={v}' for v in kopts]
ostree.cli("admin", "deploy", ref,
*kargs, sysroot=tree, os=osname)


def ostree_container_deploy(tree, inputs, osname, kopts):
images = containers.parse_containers_input(inputs)
for image in images.values():
with containers.container_source(image) as (image_name, image_source):
extra_args = []
imgref = f"ostree-unverified-image:{image_source}"

extra_args.append(f'--imgref={imgref}')
extra_args.append(f'--stateroot={osname}')
extra_args.append(f'--target-imgref=ostree-unverified-registry:{image_name}')

kargs = [f'--karg={v}' for v in kopts]

ostree.cli("container", "image", "deploy",
*extra_args, sysroot=tree, *kargs)


def main(tree, inputs, options):
osname = options["osname"]
rootfs = options.get("rootfs")
mounts = options.get("mounts", [])
kopts = options.get("kernel_opts", [])
ref = options.get("ref", "")
remote = options.get("remote")

# schema should catch the case in which there are more
# than one input but this adds a second layer of security
if len(inputs) > 1:
raise ValueError("Only one input accepted")

# distingush between ostree commit or container image input
input_type = None
for key in inputs:
input_type = key

if rootfs:
rootfs_id = make_fs_identifier(rootfs)
kargs += [f"--karg=root={rootfs_id}"]

for opt in kopts:
kargs += [f"--karg-append={opt}"]
kopts += [f"root={rootfs_id}"]

with MountGuard() as mounter:
for mount in mounts:
path = mount.lstrip("/")
path = os.path.join(tree, path)
mounter.mount(path, path)

ostree.cli("admin", "deploy", ref,
*kargs,
sysroot=tree,
os=osname)
if input_type == 'images':
ostree_container_deploy(tree, inputs, osname, kopts)
else:
ostree_commit_deploy(tree, inputs, osname, remote, ref, kopts)


if __name__ == '__main__':
Expand Down
Loading