WIP: shaking out TOML errors

This commit is contained in:
Reid 'arrdem' McKenzie 2022-09-13 01:24:20 -06:00
parent a19e89e5cc
commit 2bd82a7a32
2 changed files with 99 additions and 65 deletions

View file

@ -154,25 +154,25 @@ def load_packages(root: Path, roots) -> dict:
return packages return packages
def missing_require_fatal(r): def missing_require_fatal(r, e):
log.fatal(f"Unable to load package {r}") log.fatal(f"Unable to load package {r}")
_exit(1) _exit(1)
def missing_require_warn(r): def missing_require_warn(r, e):
log.warn(f"Unable to load package {r}") log.warn(f"Unable to load package {r}")
def missing_require_ignore(r): def missing_require_ignore(r, e):
pass return
def build_fs(root: Path, roots, dest: Path, prelude: List[str], missing_require_handler) -> Vfs: def build_fs(
root: Path, roots, dest: Path, prelude: List[str], missing_require_handler
) -> Vfs:
"""Build a VFS by configuring dest from the given config root.""" """Build a VFS by configuring dest from the given config root."""
packages = load_packages(root, roots) packages = load_packages(root, roots)
requirements = []
requirements.extend(prelude)
if packages: if packages:
for p in packages: for p in packages:
@ -180,21 +180,23 @@ def build_fs(root: Path, roots, dest: Path, prelude: List[str], missing_require_
else: else:
log.warning("Loaded no packages!") log.warning("Loaded no packages!")
requirements = list(prelude)
for r in list(requirements): for r in list(requirements):
try: try:
for d in packages[r].requires(): for d in packages[r].requires():
if d not in requirements: if d not in requirements:
requirements.append(d) requirements.append(d)
except KeyError: except KeyError as e:
missing_require_handler(r) missing_require_handler(r, e)
requirements.remove(r) requirements.remove(r)
log.debug(f"Mapped requirements {prelude} to {requirements}")
# Compute the topsort graph # Compute the topsort graph
requirements = {r: packages[r].requires() for r in requirements} requirements_graph = {r: packages[r].requires() for r in requirements}
fs = Vfs() fs = Vfs()
# Abstractly execute the current packages # Abstractly execute the current packages
for r in toposort_flatten(requirements): for r in toposort_flatten(requirements_graph):
r = packages[r] r = packages[r]
r.install(fs, dest) r.install(fs, dest)
@ -304,6 +306,8 @@ def configure(ctx, param, filename: Optional[Path]):
if error: if error:
exit(1) exit(1)
pprint(ctx.default_map)
@click.group(invoke_without_command=True) @click.group(invoke_without_command=True)
@click.option( @click.option(
@ -340,37 +344,72 @@ def cli():
pass pass
def arg_statefile(*args, **kwargs):
return click.option(
"--state-file", default=".cram.log", type=Path, callback=handle_path_vars
)(*args, **kwargs)
def arg_require(*args, **kwargs):
return click.option(
"--require",
type=str,
multiple=True,
default=["hosts.d/${HOSTNAME}", "profiles.d/default"],
callback=handle_requires,
)(*args, **kwargs)
def arg_require_root(*args, **kwargs):
return click.option(
"--require-root",
type=(str, click.Choice(["profile", "package"])),
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
),
)(*args, **kwargs)
def arg_missing_require(*args, **kwargs):
return click.option(
"--missing-require",
type=click.Choice(["error", "warn", "ignore"]),
callback=lambda ctx, param, it: {
"ignore": missing_require_ignore,
"warn": missing_require_warn,
"error": missing_require_fatal,
}[it],
)(*args, **kwargs)
def arg_optimize(*args, **kwargs):
return click.option("--optimize/--no-optimize", default=True)(*args, **kwargs)
@cli.command("apply") @cli.command("apply")
@click.option("--execute/--dry-run", default=False) @click.option("--execute/--dry-run", default=False)
@click.option("--force/--no-force", default=False) @click.option("--force/--no-force", default=False)
@click.option("--state-file", default=".cram.log", type=Path)
@click.option("--optimize/--no-optimize", default=True)
@click.option(
"--require",
type=str,
multiple=True,
default=[
"hosts.d/${HOSTNAME}",
"profiles.d/default"
],
callback=handle_requires,
)
@click.option(
"--require-root",
type=(str, click.Choice(["profile", "package"])),
multiple=True,
callback=lambda ctx, param, pairs: groupby([(y, expandvars(x)) for x, y in pairs], first, second)
)
@click.option(
"--missing-require",
type=click.Choice(["error", "warn", "ignore"]),
callback=lambda ctx, param, it: {"ignore": missing_require_ignore, "warn": missing_require_warn, "error": missing_require_fatal}[it]
)
@click.option("--exec-idempotent/--exec-always", "exec_idempotent", default=True) @click.option("--exec-idempotent/--exec-always", "exec_idempotent", default=True)
@arg_statefile
@arg_require
@arg_require_root
@arg_optimize
@arg_missing_require
@click.argument("confdir", type=Path, callback=handle_path_vars) @click.argument("confdir", type=Path, callback=handle_path_vars)
@click.argument("destdir", type=Path, callback=handle_path_vars) @click.argument("destdir", type=Path, callback=handle_path_vars)
def do_apply( def do_apply(
confdir, destdir, state_file, execute, optimize, force, require, require_root, missing_require, exec_idempotent, confdir,
destdir,
state_file,
execute,
optimize,
force,
require,
require_root,
missing_require,
exec_idempotent,
): ):
"""The entry point of cram.""" """The entry point of cram."""
@ -418,22 +457,15 @@ def do_apply(
@cli.command("list") @cli.command("list")
@click.option( @click.option(
"-1", "--oneline", "-1", "--oneline", is_flag=True, default=False, help="Only list names of resources"
is_flag=True,
default=False,
help="Only list names of resources"
)
@click.option(
"--require-root",
type=(str, click.Choice(["profile", "package"])),
multiple=True,
callback=lambda ctx, param, pairs: groupby([(y, expandvars(x)) for x, y in pairs], first, second)
) )
@arg_require_root
@click.argument("confdir", type=Path, callback=handle_path_vars) @click.argument("confdir", type=Path, callback=handle_path_vars)
@click.argument("requirements", nargs=-1, callback=handle_requires) @click.argument("requirements", nargs=-1, callback=handle_requires)
def do_list(confdir, requirements, require_root, oneline): def do_list(confdir, requirements, require_root, oneline):
"""List out packages, profiles, hosts and subpackages in the <confdir>.""" """List out packages, profiles, hosts and subpackages in the <confdir>."""
root = confdir.resolve() root = confdir.resolve()
log.debug(f"confdir: {root}, roots: {require_root}, requirements: {requirements}")
if not root.is_dir(): if not root.is_dir():
log.fatal(f"{confdir} does not exist!") log.fatal(f"{confdir} does not exist!")
@ -467,7 +499,7 @@ def do_list(confdir, requirements, require_root, oneline):
@cli.command("state") @cli.command("state")
@click.option("--state-file", default=".cram.log", type=Path) @arg_statefile
@click.argument("confdir", type=Path, callback=handle_path_vars) @click.argument("confdir", type=Path, callback=handle_path_vars)
def do_state(confdir, state_file): def do_state(confdir, state_file):
"""List out the last `apply` state in the <confdir>/.cram.log or --state-file.""" """List out the last `apply` state in the <confdir>/.cram.log or --state-file."""
@ -486,13 +518,8 @@ def do_state(confdir, state_file):
@cli.command("fmt") @cli.command("fmt")
@arg_require_root
@click.argument("confdir", type=Path, callback=handle_path_vars) @click.argument("confdir", type=Path, callback=handle_path_vars)
@click.option(
"--require-root",
type=(str, click.Choice(["profile", "package"])),
multiple=True,
callback=lambda ctx, param, pairs: groupby([(y, expandvars(x)) for x, y in pairs], first, second)
)
@click.argument("requirement", type=str, callback=handle_path_vars) @click.argument("requirement", type=str, callback=handle_path_vars)
def do_fmt(confdir, requirement, require_root): def do_fmt(confdir, requirement, require_root):
"""Format the specified requirement to a canonical-ish representation.""" """Format the specified requirement to a canonical-ish representation."""
@ -523,14 +550,15 @@ def do_init(confdir: Path, force: bool):
"""Initialize an empty Cram repo.""" """Initialize an empty Cram repo."""
confdir.mkdir(parents=True, exist_ok=True) confdir.mkdir(parents=True, exist_ok=True)
rootfile = confdir / 'cram.toml' rootfile = confdir / "cram.toml"
if rootfile.exists() and not force: if rootfile.exists() and not force:
log.fatal("cram.toml exists! Provide -f/--force to overwrite") log.fatal("cram.toml exists! Provide -f/--force to overwrite")
exit(1) _exit(1)
log.info("Writing initial cram.toml...") log.info("Writing initial cram.toml...")
with open(rootfile, 'w') as fp: with open(rootfile, "w") as fp:
fp.write("""\ fp.write(
"""\
[cram] [cram]
version = 1 version = 1
@ -540,22 +568,27 @@ destdir = "${HOME}"
state_file = "${PWD}/.cram.log" state_file = "${PWD}/.cram.log"
optimize = true optimize = true
exec_idempotent = true exec_idempotent = true
require_roots = { # Where to load requirements from, and the requirement type
"packages.d" = "package", # Types: package, profile
"profiles.d" = "profile" require_root = [
"hosts.d" = "profile", ["${PWD}/packages.d", "package"],
} ["${PWD}/profiles.d", "profile"],
["${PWD}/hosts.d", "profile"],
]
# Choice([error, warn, ignore])
missing_require = "error" missing_require = "error"
[cram.task.apply] [cram.task.apply]
missing_require = "ignore"
require = [ require = [
"hosts.d/${FQDN}", "hosts.d/${FQDN}",
"hosts.d/${HOSTNAME}", "hosts.d/${HOSTNAME}",
"profiles.d/default", "profiles.d/default",
] ]
""") """
)
for d in ['profiles.d', 'packages.d', 'hosts.d']: for d in ["profiles.d", "packages.d", "hosts.d"]:
(confdir / d).mkdir(parents=True, exist_ok=True) (confdir / d).mkdir(parents=True, exist_ok=True)

View file

@ -20,7 +20,8 @@ missing_require = "error"
[cram.task.apply] [cram.task.apply]
missing_require = "ignore" missing_require = "ignore"
require = [ require = [
"hosts.d/${FQDN}", # "hosts.d/${FQDN}",
"hosts.d/${HOSTNAME}", # "hosts.d/${HOSTNAME}",
"hosts.d/test",
"profiles.d/default", "profiles.d/default",
] ]