Support tweaking the shell in config

This commit is contained in:
Reid 'arrdem' McKenzie 2022-11-24 16:13:03 -07:00
parent 0dcb4118e2
commit 82d3b0c97c
4 changed files with 40 additions and 31 deletions

View file

@ -99,30 +99,30 @@ def handle_requires(_ctx, _params, requires):
return expand_requires(requires)
def load(root: Path, name: str, clss):
def load(config, root: Path, name: str, clss):
for c in clss:
i = c(root, name)
i = c(config, root, name)
if i.test():
return i
def load_package(root, name):
def load_package(config, root, name):
log.debug(f"Attempting to load package {name} from {root}")
return load(root, name, [LightPackageV1, PackageV1, PackageV0])
return load(config, root, name, [LightPackageV1, PackageV1, PackageV0])
def load_profile(root, name):
def load_profile(config, root, name):
log.debug(f"Attempting to load profile {name} from {root}")
return load(root, name, [ProfileV1, ProfileV0])
return load(config, root, name, [ProfileV1, ProfileV0])
def load_packages(root: Path, roots) -> dict:
def load_packages(config, root: Path, roots) -> dict:
"""Load the configured packages."""
def _load_package(p):
name = str(p.relative_to(root)).replace(".toml", "")
try:
packages[name] = load_package(p, name)
packages[name] = load_package(config, p, name)
except Exception as e:
log.exception(f"Unable to load {p} due to exception")
@ -146,7 +146,7 @@ def load_packages(root: Path, roots) -> dict:
# Register the metapackages themselves using the profile type
mp_name = str(mp_root.relative_to(root))
packages[mp_name] = load_profile(mp_root, mp_name)
packages[mp_name] = load_profile(config, mp_root, mp_name)
log.debug(f"Loaded {len(packages)} packages...")
return packages
@ -166,11 +166,11 @@ def missing_require_ignore(r, e):
def build_fs(
root: Path, roots, dest: Path, prelude: List[str], missing_require_handler
config, 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)
packages = load_packages(config, root, roots)
if not packages:
log.warning("Loaded no packages!")
@ -276,7 +276,7 @@ def configure(ctx, param, filename: Optional[Path]):
if filename and filename.exists():
ctx.default_map = {}
log.debug(f"Loading config from {filename}")
cfg = toml.load(filename)
ctx.obj = cfg = toml.load(filename)
assert cfg["cram"]["version"] >= 1
task_cfg = cfg.get("cram", {}).get("task", {})
@ -349,6 +349,7 @@ Features
- 0.2.0 initial release
- 0.2.1 TOML root config
- 0.2.3 Single-file packages (ex. packages.d/foo.toml)
- 0.2.4 Support tweaking the shell
About
{__copyright__}, {__author__}.
@ -410,6 +411,7 @@ def arg_optimize(*args, **kwargs):
@cli.command("apply")
@click.pass_obj
@click.option("--execute/--dry-run", default=False)
@click.option("--force/--no-force", default=False)
@click.option("--exec-idempotent/--exec-always", "exec_idempotent", default=True)
@ -421,6 +423,7 @@ def arg_optimize(*args, **kwargs):
@click.argument("confdir", type=Path, callback=handle_path_vars)
@click.argument("destdir", type=Path, callback=handle_path_vars)
def do_apply(
obj,
confdir,
destdir,
state_file,
@ -453,7 +456,7 @@ def do_apply(
# Force an empty state
old_fs = Vfs([])
new_fs = build_fs(root, require_root, dest, require, missing_require)
new_fs = build_fs(obj, root, require_root, dest, require, missing_require)
log.debug(f"Built new state consisting of {len(new_fs._log)} steps")
# Middleware processing of the resulting filesystem(s)
@ -478,13 +481,14 @@ def do_apply(
@cli.command("list")
@click.pass_obj
@click.option(
"-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):
def do_list(obj, confdir, requirements, require_root, oneline):
"""List out packages, profiles, hosts and subpackages in the <confdir>."""
root = confdir.resolve().absolute()
@ -495,7 +499,7 @@ def do_list(confdir, requirements, require_root, oneline):
log.fatal(f"{confdir} does not exist!")
_exit(1)
packages = load_packages(root, require_root)
packages = load_packages(obj, root, require_root)
if requirements:
dest = Path("~/")
@ -544,10 +548,11 @@ def do_state(confdir, state_file):
@cli.command("fmt")
@click.pass_obj
@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: Path, requirement, require_root):
def do_fmt(obj, confdir: Path, requirement, require_root):
"""Format the specified requirement to a canonical-ish representation."""
root = confdir.resolve().absolute()
@ -557,7 +562,7 @@ def do_fmt(confdir: Path, requirement, require_root):
log.fatal(f"{confdir} does not exist!")
_exit(1)
packages = load_packages(root, require_root)
packages = load_packages(obj, root, require_root)
pkg = packages[requirement]
json = pkg.json()
@ -606,6 +611,7 @@ require_root = [
missing_require = "error"
[cram.task.apply]
shell = ["/bin/sh"]
missing_require = "warn"
require = [
"hosts.d/${FQDN}",

View file

@ -8,17 +8,19 @@ from typing import List, Optional
from vfs import Vfs
# FIXME: This should be a config somewhere
SHELL = "/bin/sh"
SHELL = ["/bin/sh"]
# Light monkeypatching because macos ships a "stable" a py
if sys.version_info <= (3, 9, 0):
Path.readlink = lambda p: Path(os.readlink(str(p)))
def sh(cmd: List[str], /,
def sh(global_config,
cmd: List[str], /,
env: Optional[dict] = None):
shell = (global_config or {}).get("cram", {}).get("task", {}).get("apply", {}).get("shell", SHELL)
prefix = []
if env:
prefix.append("/usr/bin/env")
@ -26,7 +28,7 @@ def sh(cmd: List[str], /,
v = sh_quote(str(v))
prefix.append(f"{k}={v}")
return tuple(prefix + [SHELL, *cmd])
return tuple(prefix + [*shell, *cmd])
def stow(fs: Vfs, src_dir: Path, dest_dir: Path, skip=[]):
@ -56,7 +58,8 @@ def stow(fs: Vfs, src_dir: Path, dest_dir: Path, skip=[]):
class Package(object):
def __init__(self, root: Path, name: str):
def __init__(self, global_config, root: Path, name: str):
self.global_config = global_config
self.root = root
self.name = name

View file

@ -36,21 +36,21 @@ class PackageV0(Package):
"""Install this package."""
buildf = self.root / "BUILD"
if buildf.exists():
fs.exec(self.root, sh([str(buildf)]))
fs.exec(self.root, sh(self.global_config, [str(buildf)]))
pref = self.root / "PRE_INSTALL"
if pref.exists():
fs.exec(self.root, sh([str(pref)]))
fs.exec(self.root, sh(self.global_config, [str(pref)]))
installf = self.root / "INSTALL"
if installf.exists():
fs.exec(self.root, sh([str(installf)]))
fs.exec(self.root, sh(self.global_config, [str(installf)]))
else:
stow(fs, self.root, dest, self.SPECIAL_FILES)
postf = self.root / "POST_INSTALL"
if postf.exists():
fs.exec(self.root, sh([str(postf)]))
fs.exec(self.root, sh(self.global_config, [str(postf)]))
def _read(self, p: Path):
if p.exists():
@ -94,16 +94,16 @@ class ProfileV0(PackageV0):
buildf = self.root / "BUILD"
if buildf.exists():
fs.exec(self.root, sh([str(buildf)]))
fs.exec(self.root, sh(self.global_config, [str(buildf)]))
pref = self.root / "PRE_INSTALL"
if pref.exists():
fs.exec(self.root, sh([str(pref)]))
fs.exec(self.root, sh(self.global_config, [str(pref)]))
installf = self.root / "INSTALL"
if installf.exists():
fs.exec(self.root, sh([str(installf)]))
fs.exec(self.root, sh(self.global_config, [str(installf)]))
postf = self.root / "POST_INSTALL"
if postf.exists():
fs.exec(self.root, sh([str(postf)]))
fs.exec(self.root, sh(self.global_config, [str(postf)]))

View file

@ -79,7 +79,7 @@ class PackageV1(Package):
f = tempf(f"{sum}.sh")
with open(f, "w") as fp:
fp.write(content)
fs.exec(cwd, sh([f]))
fs.exec(cwd, sh(self.global_config, [f]))
def do_build(self, fs: Vfs, dest: Path):
self.do_sh_or_script(self.config().get("package", {}).get("build"), fs, dest)