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
def missing_require_fatal(r):
def missing_require_fatal(r, e):
log.fatal(f"Unable to load package {r}")
_exit(1)
def missing_require_warn(r):
def missing_require_warn(r, e):
log.warn(f"Unable to load package {r}")
def missing_require_ignore(r):
pass
def missing_require_ignore(r, e):
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."""
packages = load_packages(root, roots)
requirements = []
requirements.extend(prelude)
if packages:
for p in packages:
@ -180,21 +180,23 @@ def build_fs(root: Path, roots, dest: Path, prelude: List[str], missing_require_
else:
log.warning("Loaded no packages!")
requirements = list(prelude)
for r in list(requirements):
try:
for d in packages[r].requires():
if d not in requirements:
requirements.append(d)
except KeyError:
missing_require_handler(r)
except KeyError as e:
missing_require_handler(r, e)
requirements.remove(r)
log.debug(f"Mapped requirements {prelude} to {requirements}")
# 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()
# Abstractly execute the current packages
for r in toposort_flatten(requirements):
for r in toposort_flatten(requirements_graph):
r = packages[r]
r.install(fs, dest)
@ -304,6 +306,8 @@ def configure(ctx, param, filename: Optional[Path]):
if error:
exit(1)
pprint(ctx.default_map)
@click.group(invoke_without_command=True)
@click.option(
@ -340,37 +344,72 @@ def cli():
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")
@click.option("--execute/--dry-run", 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)
@arg_statefile
@arg_require
@arg_require_root
@arg_optimize
@arg_missing_require
@click.argument("confdir", type=Path, callback=handle_path_vars)
@click.argument("destdir", type=Path, callback=handle_path_vars)
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."""
@ -418,22 +457,15 @@ def do_apply(
@cli.command("list")
@click.option(
"-1", "--oneline",
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)
"-1", "--oneline", is_flag=True, default=False, help="Only list names of resources"
)
@arg_require_root
@click.argument("confdir", type=Path, callback=handle_path_vars)
@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()
log.debug(f"confdir: {root}, roots: {require_root}, requirements: {requirements}")
if not root.is_dir():
log.fatal(f"{confdir} does not exist!")
@ -467,7 +499,7 @@ def do_list(confdir, requirements, require_root, oneline):
@cli.command("state")
@click.option("--state-file", default=".cram.log", type=Path)
@arg_statefile
@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."""
@ -486,13 +518,8 @@ def do_state(confdir, state_file):
@cli.command("fmt")
@arg_require_root
@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)
def do_fmt(confdir, requirement, require_root):
"""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."""
confdir.mkdir(parents=True, exist_ok=True)
rootfile = confdir / 'cram.toml'
rootfile = confdir / "cram.toml"
if rootfile.exists() and not force:
log.fatal("cram.toml exists! Provide -f/--force to overwrite")
exit(1)
_exit(1)
log.info("Writing initial cram.toml...")
with open(rootfile, 'w') as fp:
fp.write("""\
with open(rootfile, "w") as fp:
fp.write(
"""\
[cram]
version = 1
@ -540,22 +568,27 @@ destdir = "${HOME}"
state_file = "${PWD}/.cram.log"
optimize = true
exec_idempotent = true
require_roots = {
"packages.d" = "package",
"profiles.d" = "profile"
"hosts.d" = "profile",
}
# 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"],
]
# Choice([error, warn, ignore])
missing_require = "error"
[cram.task.apply]
missing_require = "ignore"
require = [
"hosts.d/${FQDN}",
"hosts.d/${HOSTNAME}",
"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)

View file

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