And get TOML configs working

This commit is contained in:
Reid 'arrdem' McKenzie 2022-09-13 23:21:18 -06:00
parent 2bd82a7a32
commit 027f8c24fc
3 changed files with 92 additions and 66 deletions

View file

@ -4,93 +4,116 @@ set -ex
dest=$(mktemp -d)
./cram --help
PATH="$PATH:$(realpath .)"
PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
root="test/integration/"
cram --help
root="test/integration"
function ,scrub() {
rm -rf "${dest}"/* .cram.log
}
# Should be able to list all packages
./cram list "$root" | grep "packages.d/p1"
cram list "${root}" | grep "packages.d/p1"
,scrub
# P3 depends on P1, should show up in the listing
./cram list "$root" packages.d/p3 | grep "packages.d/p1"
cram list "${root}" packages.d/p3 | grep "packages.d/p1"
,scrub
# P4 depends on P3, should show up in the listing
./cram list "$root" packages.d/p4 | grep "packages.d/p3"
cram list "${root}" packages.d/p4 | grep "packages.d/p3"
,scrub
# The default profile should depend on its subpackage
./cram list "$root" profiles.d/default | grep "profiles.d/default/subpackage"
cram list "${root}" profiles.d/default | grep "profiles.d/default/subpackage"
,scrub
# And the subpackage has a dep
./cram list "$root" profiles.d/default/subpackage | grep "packages.d/p3"
cram list "${root}" profiles.d/default/subpackage | grep "packages.d/p3"
,scrub
# Install one package
./cram apply --no-optimize --require packages.d/p1 --execute "$root" "${dest}"
cram apply --no-optimize --require packages.d/p1 --execute "${root}" "${dest}"
[ -L "${dest}"/foo ]
./cram state "$root" | grep "${dest}/foo"
rm -r "${dest}"/*
cram state "$root" | grep "${dest}/foo"
,scrub
# Install two transitively (legacy)
./cram apply --no-optimize --require packages.d/p3 --execute "$root" "${dest}"
cram apply --no-optimize --require packages.d/p3 --execute "${root}" "${dest}"
[ -L "${dest}"/foo ]
[ -L "${dest}"/bar ]
./cram state "$root" | grep "${dest}/foo"
./cram state "$root" | grep "${dest}/bar"
rm -r "${dest}"/*
cram state "$root" | grep "${dest}/foo"
cram state "$root" | grep "${dest}/bar"
,scrub
# Install two transitively (current)
./cram apply --no-optimize --require packages.d/p4 --execute "$root" "${dest}"
cram apply --no-optimize --require packages.d/p4 --execute "${root}" "${dest}"
[ -L "${dest}"/foo ]
[ -L "${dest}"/bar ]
rm -r "${dest}"/*
,scrub
# Install two transitively (current)
./cram apply --no-optimize --require packages.d/p4 --execute "$root" "${dest}"
cram apply --no-optimize --require packages.d/p4 --execute "${root}" "${dest}"
[ -L "${dest}"/foo ]
[ -L "${dest}"/bar ]
rm -r "${dest}"/*
,scrub
# Install two transitively (current)
./cram apply --no-optimize --require hosts.d/test --require profiles.d/default --execute "$root" "${dest}"
cram apply --no-optimize --require hosts.d/test --require profiles.d/default --execute "${root}" "${dest}"
tree "${dest}"
[ -L "${dest}"/foo ]
[ -L "${dest}"/bar ]
rm -r "${dest}"/*
,scrub
# INSTALL scripts get run as-is
./cram list "$root" packages.d/p5 | grep "packages.d/p5/INSTALL"
cram list "${root}" packages.d/p5 | grep "packages.d/p5/INSTALL"
,scrub
# Inline scripts get pulled out repeatably
./cram list "$root" packages.d/p6 | grep "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b"
cram list "${root}" packages.d/p6 | grep "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b"
,scrub
# Inline scripts get pulled out repeatably, even from the list format
./cram list "$root" packages.d/p7 | grep "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b"
cram list "${root}" packages.d/p7 | grep "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b"
,scrub
# Test log-based optimization
./cram apply --no-optimize --require packages.d/p4 --execute "$root" "${dest}"
cram apply --no-optimize --require packages.d/p4 --execute "${root}" "${dest}"
[ -L "${dest}"/foo ]
[ -L "${dest}"/bar ]
# These paths were already linked, they shouldn't be re-linked when optimizing.
! ./cram apply --require packages.d/p4 --optimize --execute "$root" "${dest}" | grep "${dest}/foo"
! ./cram apply --require packages.d/p4 --optimize --execute "$root" "${dest}" | grep "${dest}/bar"
rm -r "${dest}"/*
! cram apply --require packages.d/p4 --optimize --execute "${root}" "${dest}" | grep "${dest}/foo"
! cram apply --require packages.d/p4 --optimize --execute "${root}" "${dest}" | grep "${dest}/bar"
,scrub
# Likewise, if we've exec'd this once we shouldn't do it again
./cram apply --no-optimize --require packages.d/p5 --execute "$root" "${dest}"
! ./cram apply --require packages.d/p5 --execute "$root" "${dest}" | grep "exec"
cram apply --no-optimize --require packages.d/p5 --execute "${root}" "${dest}"
! cram apply --require packages.d/p5 --execute "${root}" "${dest}" | grep "exec"
,scrub
# ... unless the user tells us to
./cram apply --no-optimize --require packages.d/p5 --execute "$root" "${dest}"
./cram apply --exec-always --require packages.d/p5 --execute "$root" "${dest}" | grep "exec"
cram apply --no-optimize --require packages.d/p5 --execute "${root}" "${dest}"
cram apply --exec-always --require packages.d/p5 --execute "${root}" "${dest}" | grep "exec"
,scrub
# If multiple packages provide the same _effective_ script, do it once
./cram apply --require packages.d/p6 --require packages.d/p7 --execute "$root" "${dest}" | sort | uniq -c | grep "/tmp/stow/b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b.sh" | grep "1 - exec"
cram apply --require packages.d/p6 --require packages.d/p7 --execute "${root}" "${dest}" | sort | uniq -c | grep "/tmp/stow/b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b.sh" | grep "1 - exec"
,scrub
# Test log-based cleanup
./cram apply --require packages.d/p1 --require packages.d/p2 --execute "$root" "${dest}"
cram apply --require packages.d/p1 --require packages.d/p2 --execute "${root}" "${dest}"
cram apply --require packages.d/p1 --require packages.d/p2 --execute "${root}" "${dest}"
[ -L "${dest}"/foo ]
[ -L "${dest}"/bar ]
,scrub
# And how bar shouldn't be installed...
./cram state test/
./cram apply --require packages.d/p1 --execute "$root" "${dest}"
./cram state test/
cram state "${root}"
cram apply --require packages.d/p1 --execute "${root}" "${dest}"
cram state "${root}"
[ -L "${dest}"/foo ]
[ ! -L "${dest}"/bar ]
,scrub

View file

@ -1,11 +1,9 @@
"""Cram's entry point."""
from itertools import chain
import logging
import os
from pathlib import Path
import pickle
from pprint import pprint
from typing import List, Optional, Union
import re
import platform
@ -128,7 +126,7 @@ def load_packages(root: Path, roots) -> dict:
packages = {}
for r in roots.get("package", []):
r = Path(r)
r = root / r
log.debug(f"Trying to load packages from {r}...")
for p in r.glob("*"):
name = str(p.relative_to(root))
@ -136,7 +134,7 @@ def load_packages(root: Path, roots) -> dict:
for r in roots.get("profile", []):
# Add profiles, hosts which contain subpackages.
r = Path(r)
r = root / r
log.debug(f"Trying to load profiles from {r}...")
for mp_root in r.glob("*"):
# First find all subpackages
@ -181,7 +179,7 @@ def build_fs(
log.warning("Loaded no packages!")
requirements = list(prelude)
for r in list(requirements):
for r in requirements:
try:
for d in packages[r].requires():
if d not in requirements:
@ -190,7 +188,7 @@ def build_fs(
missing_require_handler(r, e)
requirements.remove(r)
log.debug(f"Mapped requirements {prelude} to {requirements}")
log.info(f"Mapped requirements {prelude} to {requirements}")
# Compute the topsort graph
requirements_graph = {r: packages[r].requires() for r in requirements}
fs = Vfs()
@ -273,7 +271,6 @@ def scrub(old_fs: Vfs, new_fs: Vfs) -> Vfs:
def configure(ctx, param, filename: Optional[Path]):
if filename and filename.exists():
ctx.default_map = {}
os.chdir(filename.parent)
log.debug(f"Loading config from {filename}")
cfg = toml.load(filename)
assert cfg["cram"]["version"] >= 1
@ -306,8 +303,6 @@ def configure(ctx, param, filename: Optional[Path]):
if error:
exit(1)
pprint(ctx.default_map)
@click.group(invoke_without_command=True)
@click.option(
@ -363,12 +358,14 @@ def arg_require(*args, **kwargs):
def arg_require_root(*args, **kwargs):
return click.option(
"--require-root",
type=(str, click.Choice(["profile", "package"])),
type=(click.Choice(["profile", "package"]), str),
multiple=True,
default=[("profiles.d", "profile"), ("hosts.d", "profile"), ("packages.d", "package")],
callback=lambda ctx, param, pairs: groupby(
[(y, expandvars(x)) for x, y in pairs], first, second
),
default=[
("profile", "profiles.d"),
("profile", "hosts.d"),
("package", "packages.d"),
],
callback=lambda ctx, param, pairs: groupby(pairs, first, second),
)(*args, **kwargs)
@ -376,6 +373,7 @@ def arg_missing_require(*args, **kwargs):
return click.option(
"--missing-require",
type=click.Choice(["error", "warn", "ignore"]),
default="error",
callback=lambda ctx, param, it: {
"ignore": missing_require_ignore,
"warn": missing_require_warn,
@ -414,7 +412,8 @@ def do_apply(
"""The entry point of cram."""
# Resolve the two input paths to absolutes
root = confdir.resolve()
root = confdir.resolve().absolute()
os.chdir(root)
dest = destdir.resolve()
if not root.is_dir():
@ -464,7 +463,9 @@ def do_apply(
@click.argument("requirements", nargs=-1, callback=handle_requires)
def do_list(confdir, requirements, require_root, oneline):
"""List out packages, profiles, hosts and subpackages in the <confdir>."""
root = confdir.resolve()
root = confdir.resolve().absolute()
os.chdir(root)
log.debug(f"confdir: {root}, roots: {require_root}, requirements: {requirements}")
if not root.is_dir():
@ -503,7 +504,9 @@ def do_list(confdir, requirements, require_root, oneline):
@click.argument("confdir", type=Path, callback=handle_path_vars)
def do_state(confdir, state_file):
"""List out the last `apply` state in the <confdir>/.cram.log or --state-file."""
root = confdir.resolve()
root = confdir.resolve().absolute()
os.chdir(root)
if not root.is_dir():
log.fatal(f"{confdir} does not exist!")
@ -521,10 +524,11 @@ def do_state(confdir, state_file):
@arg_require_root
@click.argument("confdir", type=Path, callback=handle_path_vars)
@click.argument("requirement", type=str, callback=handle_path_vars)
def do_fmt(confdir, requirement, require_root):
def do_fmt(confdir: Path, requirement, require_root):
"""Format the specified requirement to a canonical-ish representation."""
root = confdir.resolve()
root = confdir.resolve().absolute()
os.chdir(root)
if not root.is_dir():
log.fatal(f"{confdir} does not exist!")
@ -571,9 +575,9 @@ exec_idempotent = true
# Where to load requirements from, and the requirement type
# Types: package, profile
require_root = [
["${PWD}/packages.d", "package"],
["${PWD}/profiles.d", "profile"],
["${PWD}/hosts.d", "profile"],
["package", "${PWD}/packages.d"],
["profile", "${PWD}/profiles.d"],
["profile", "${PWD}/hosts.d"],
]
# Choice([error, warn, ignore])
missing_require = "error"
@ -594,7 +598,7 @@ require = [
if __name__ == "__main__" or 1:
logging.basicConfig(
level=logging.DEBUG,
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)

View file

@ -7,12 +7,12 @@ destdir = "${HOME}"
state_file = "${PWD}/.cram.log"
optimize = true
exec_idempotent = true
# Where to load requirements from
# Where to load requirements from, and the requirement type
# Types: package, profile
require_root = [
["${PWD}/packages.d", "package"],
["${PWD}/profiles.d", "profile"],
["${PWD}/hosts.d", "profile"],
["package", "${PWD}/packages.d"],
["profile", "${PWD}/profiles.d"],
["profile", "${PWD}/hosts.d"],
]
# Choice([error, warn, ignore])
missing_require = "error"
@ -20,8 +20,7 @@ missing_require = "error"
[cram.task.apply]
missing_require = "ignore"
require = [
# "hosts.d/${FQDN}",
# "hosts.d/${HOSTNAME}",
"hosts.d/test",
"hosts.d/${FQDN}",
"hosts.d/${HOSTNAME}",
"profiles.d/default",
]