osbuild: replace --from and --save with --input and --output
The new arguments are passed to the first, respectively last, stage and are both directories. --input is read only and can be used to initialize the first stage. --output is r/w and is where the final stage should place the produced image. Signed-off-by: Tom Gundersen <teg@jklm.no>
This commit is contained in:
parent
01aa00837f
commit
40cf349f18
2 changed files with 50 additions and 40 deletions
22
README.md
22
README.md
|
|
@ -35,21 +35,21 @@ modifies a file system tree. Pipelines are defined as JSON files like this one:
|
|||
}
|
||||
```
|
||||
|
||||
`osbuild` runs each of the stages in turn, isolating them into mount and pid
|
||||
namespaces. It injects the `options` object with a `tree` key pointing to the
|
||||
file system tree and passes that to the stage via its `stdin`. Each stage has
|
||||
private `/tmp` and `/var/tmp` directories that are deleted after the stage is
|
||||
run.
|
||||
`osbuild` runs each of the stages in turn, isolating them from the host and
|
||||
from each other, with the exception that the first stage may be given an input
|
||||
directory, the last stage an output directory and all stages of a given
|
||||
pipeline are given the same filesystem tree to operate on.
|
||||
|
||||
Stages may have side effects: the `io.weldr.qcow2` stage in the above
|
||||
example packs the tree into a `qcow2` image.
|
||||
Each stage is passed the (appended) `options` object as JSON over stdin.
|
||||
|
||||
The above pipeline has no input and produces a qcow2 image.
|
||||
|
||||
## Running
|
||||
|
||||
```
|
||||
osbuild [--from ARCHIVE] [--save ARCHIVE] PIPELINE
|
||||
osbuild [--input DIRECTORY] [--output DIRECTORY] PIPELINE
|
||||
```
|
||||
|
||||
Runs `PIPELINE`. If `--from` is given, unpacks its contents (`.tar.gz`) into
|
||||
the tree before running the first stage. If `--save` is given, saves the
|
||||
contents of the tree in the given archive.
|
||||
Runs `PIPELINE`. If `--input` is given, the directory is available
|
||||
read-only in the first stage. If `--output` is given it, it must be empty
|
||||
and is avialble read-write in the final stage.
|
||||
|
|
|
|||
68
osbuild
68
osbuild
|
|
@ -15,20 +15,14 @@ RED = "\033[31m"
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def tmpfs(save=None):
|
||||
def tmpfs():
|
||||
"""A contextmanager that mounts a tmpfs and returns its location.
|
||||
|
||||
If `save` is given, it should contain the name of an .tar.gz archive to
|
||||
which the contents of the tmpfs will be written.
|
||||
"""
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="osbuild-tree-", dir=os.getcwd()) as path:
|
||||
subprocess.run(["mount", "-t", "tmpfs", "tmpfs", path], check=True)
|
||||
try:
|
||||
yield path
|
||||
if save:
|
||||
print(f"Saving tree to {save}...")
|
||||
subprocess.run(["tar", "-czf", save, "-C", path, "."], stdout=subprocess.DEVNULL, check=True)
|
||||
finally:
|
||||
subprocess.run(["umount", path], check=True)
|
||||
|
||||
|
|
@ -45,21 +39,47 @@ def bindmnt(src_path):
|
|||
subprocess.run(["umount", dst_path], check=True)
|
||||
|
||||
|
||||
def main(pipeline_path, from_archive, save, sit):
|
||||
def main(pipeline_path, input_dir, output_dir, sit):
|
||||
if output_dir and len(os.listdir(output_dir)) != 0:
|
||||
print()
|
||||
print(f"{RESET}{BOLD}{RED}Output directory {output_dir} is not empty{RESET}")
|
||||
return 1
|
||||
|
||||
with open(pipeline_path) as f:
|
||||
pipeline = json.load(f)
|
||||
|
||||
with tmpfs(save) as tree, bindmnt("/") as root:
|
||||
if from_archive:
|
||||
r = subprocess.run(["tar", "-xzf", from_archive, "-C", tree])
|
||||
if r.returncode != 0:
|
||||
return
|
||||
|
||||
with tmpfs() as tree, bindmnt("/") as root:
|
||||
for i, stage in enumerate(pipeline["stages"], start=1):
|
||||
name = stage["name"]
|
||||
options = stage.get("options", {})
|
||||
options["tree"] = "/tmp/tree"
|
||||
|
||||
argv = ["systemd-nspawn",
|
||||
"--as-pid2",
|
||||
"--link-journal=no",
|
||||
"--volatile=yes",
|
||||
f"--directory={root}",
|
||||
f"--bind={tree}:/tmp/tree",
|
||||
f"--bind={os.getcwd()}/run-stage:/tmp/run-stage",
|
||||
f"--bind={os.getcwd()}/stages/{name}:/tmp/stage",
|
||||
"--bind=/etc/pki"]
|
||||
|
||||
# Optionally pass the input dir to the first stage
|
||||
if i == 1 and input_dir:
|
||||
options["input_dir"] = "/tmp/input"
|
||||
argv.append(f"--bind-ro={os.getcwd()}/{input_dir}:/tmp/input")
|
||||
|
||||
# Pass the tree r/w to all stages but the last one.
|
||||
# The last stage gets the tree ro and optionally gets an output dir
|
||||
if i == len(pipeline["stages"]) and output_dir:
|
||||
options["output_dir"] = "/tmp/output"
|
||||
argv.append(f"--bind={os.getcwd()}/{output_dir}:/tmp/output")
|
||||
|
||||
argv.append("/tmp/run-stage")
|
||||
if sit:
|
||||
argv.append("--sit")
|
||||
argv.append("/tmp/stage")
|
||||
|
||||
options_str = json.dumps(options, indent=2)
|
||||
|
||||
print()
|
||||
|
|
@ -69,17 +89,7 @@ def main(pipeline_path, from_archive, save, sit):
|
|||
print()
|
||||
|
||||
try:
|
||||
opts = ["--sit"] if sit else []
|
||||
subprocess.run(["systemd-nspawn",
|
||||
"--as-pid2",
|
||||
"--link-journal=no",
|
||||
"--volatile=yes",
|
||||
f"--directory={root}",
|
||||
f"--bind={tree}:/tmp/tree",
|
||||
f"--bind={os.getcwd()}/run-stage:/tmp/run-stage",
|
||||
f"--bind={os.getcwd()}/stages/{name}:/tmp/stage",
|
||||
"--bind=/etc/pki",
|
||||
"/tmp/run-stage", *opts, "/tmp/stage"],
|
||||
subprocess.run(argv,
|
||||
input=options_str, encoding="utf-8",
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT,
|
||||
check=True)
|
||||
|
|
@ -97,10 +107,10 @@ if __name__ == "__main__":
|
|||
parser = argparse.ArgumentParser(description="Build operating system images")
|
||||
parser.add_argument("pipeline_path", metavar="PIPELINE",
|
||||
help="json file containing the pipeline that should be built")
|
||||
parser.add_argument("--save", metavar="ARCHIVE",
|
||||
help="save the resulting tree to ARCHIVE")
|
||||
parser.add_argument("--from", dest="from_archive", metavar="ARCHIVE",
|
||||
help="initialize the tree from ARCHIVE")
|
||||
parser.add_argument("--input", dest="input_dir", metavar="DIRECTORY",
|
||||
help="provide the contents of DIRECTORY to the first stage")
|
||||
parser.add_argument("--output", dest="output_dir", metavar="DIRECTORY",
|
||||
help="provide the empty DIRECTORY as output argument to the last stage")
|
||||
parser.add_argument("--sit", action="store_true",
|
||||
help="keep the build environment up when a stage failed")
|
||||
args = parser.parse_args()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue