Support tweaking the shell in config
This commit is contained in:
parent
0dcb4118e2
commit
82d3b0c97c
4 changed files with 40 additions and 31 deletions
|
@ -99,30 +99,30 @@ def handle_requires(_ctx, _params, requires):
|
||||||
return expand_requires(requires)
|
return expand_requires(requires)
|
||||||
|
|
||||||
|
|
||||||
def load(root: Path, name: str, clss):
|
def load(config, root: Path, name: str, clss):
|
||||||
for c in clss:
|
for c in clss:
|
||||||
i = c(root, name)
|
i = c(config, root, name)
|
||||||
if i.test():
|
if i.test():
|
||||||
return i
|
return i
|
||||||
|
|
||||||
|
|
||||||
def load_package(root, name):
|
def load_package(config, root, name):
|
||||||
log.debug(f"Attempting to load package {name} from {root}")
|
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}")
|
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."""
|
"""Load the configured packages."""
|
||||||
|
|
||||||
def _load_package(p):
|
def _load_package(p):
|
||||||
name = str(p.relative_to(root)).replace(".toml", "")
|
name = str(p.relative_to(root)).replace(".toml", "")
|
||||||
try:
|
try:
|
||||||
packages[name] = load_package(p, name)
|
packages[name] = load_package(config, p, name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(f"Unable to load {p} due to exception")
|
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
|
# Register the metapackages themselves using the profile type
|
||||||
mp_name = str(mp_root.relative_to(root))
|
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...")
|
log.debug(f"Loaded {len(packages)} packages...")
|
||||||
return packages
|
return packages
|
||||||
|
@ -166,11 +166,11 @@ def missing_require_ignore(r, e):
|
||||||
|
|
||||||
|
|
||||||
def build_fs(
|
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:
|
) -> 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(config, root, roots)
|
||||||
|
|
||||||
if not packages:
|
if not packages:
|
||||||
log.warning("Loaded no packages!")
|
log.warning("Loaded no packages!")
|
||||||
|
@ -276,7 +276,7 @@ def configure(ctx, param, filename: Optional[Path]):
|
||||||
if filename and filename.exists():
|
if filename and filename.exists():
|
||||||
ctx.default_map = {}
|
ctx.default_map = {}
|
||||||
log.debug(f"Loading config from {filename}")
|
log.debug(f"Loading config from {filename}")
|
||||||
cfg = toml.load(filename)
|
ctx.obj = cfg = toml.load(filename)
|
||||||
assert cfg["cram"]["version"] >= 1
|
assert cfg["cram"]["version"] >= 1
|
||||||
|
|
||||||
task_cfg = cfg.get("cram", {}).get("task", {})
|
task_cfg = cfg.get("cram", {}).get("task", {})
|
||||||
|
@ -349,6 +349,7 @@ Features
|
||||||
- 0.2.0 initial release
|
- 0.2.0 initial release
|
||||||
- 0.2.1 TOML root config
|
- 0.2.1 TOML root config
|
||||||
- 0.2.3 Single-file packages (ex. packages.d/foo.toml)
|
- 0.2.3 Single-file packages (ex. packages.d/foo.toml)
|
||||||
|
- 0.2.4 Support tweaking the shell
|
||||||
|
|
||||||
About
|
About
|
||||||
{__copyright__}, {__author__}.
|
{__copyright__}, {__author__}.
|
||||||
|
@ -410,6 +411,7 @@ def arg_optimize(*args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@cli.command("apply")
|
@cli.command("apply")
|
||||||
|
@click.pass_obj
|
||||||
@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("--exec-idempotent/--exec-always", "exec_idempotent", default=True)
|
@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("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(
|
||||||
|
obj,
|
||||||
confdir,
|
confdir,
|
||||||
destdir,
|
destdir,
|
||||||
state_file,
|
state_file,
|
||||||
|
@ -453,7 +456,7 @@ def do_apply(
|
||||||
# Force an empty state
|
# Force an empty state
|
||||||
old_fs = Vfs([])
|
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")
|
log.debug(f"Built new state consisting of {len(new_fs._log)} steps")
|
||||||
|
|
||||||
# Middleware processing of the resulting filesystem(s)
|
# Middleware processing of the resulting filesystem(s)
|
||||||
|
@ -478,13 +481,14 @@ def do_apply(
|
||||||
|
|
||||||
|
|
||||||
@cli.command("list")
|
@cli.command("list")
|
||||||
|
@click.pass_obj
|
||||||
@click.option(
|
@click.option(
|
||||||
"-1", "--oneline", is_flag=True, default=False, help="Only list names of resources"
|
"-1", "--oneline", is_flag=True, default=False, help="Only list names of resources"
|
||||||
)
|
)
|
||||||
@arg_require_root
|
@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(obj, 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().absolute()
|
root = confdir.resolve().absolute()
|
||||||
|
@ -495,7 +499,7 @@ def do_list(confdir, requirements, require_root, oneline):
|
||||||
log.fatal(f"{confdir} does not exist!")
|
log.fatal(f"{confdir} does not exist!")
|
||||||
_exit(1)
|
_exit(1)
|
||||||
|
|
||||||
packages = load_packages(root, require_root)
|
packages = load_packages(obj, root, require_root)
|
||||||
|
|
||||||
if requirements:
|
if requirements:
|
||||||
dest = Path("~/")
|
dest = Path("~/")
|
||||||
|
@ -544,10 +548,11 @@ def do_state(confdir, state_file):
|
||||||
|
|
||||||
|
|
||||||
@cli.command("fmt")
|
@cli.command("fmt")
|
||||||
|
@click.pass_obj
|
||||||
@arg_require_root
|
@arg_require_root
|
||||||
@click.argument("confdir", type=Path, callback=handle_path_vars)
|
@click.argument("confdir", type=Path, callback=handle_path_vars)
|
||||||
@click.argument("requirement", type=str, 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."""
|
"""Format the specified requirement to a canonical-ish representation."""
|
||||||
|
|
||||||
root = confdir.resolve().absolute()
|
root = confdir.resolve().absolute()
|
||||||
|
@ -557,7 +562,7 @@ def do_fmt(confdir: Path, requirement, require_root):
|
||||||
log.fatal(f"{confdir} does not exist!")
|
log.fatal(f"{confdir} does not exist!")
|
||||||
_exit(1)
|
_exit(1)
|
||||||
|
|
||||||
packages = load_packages(root, require_root)
|
packages = load_packages(obj, root, require_root)
|
||||||
pkg = packages[requirement]
|
pkg = packages[requirement]
|
||||||
json = pkg.json()
|
json = pkg.json()
|
||||||
|
|
||||||
|
@ -606,6 +611,7 @@ require_root = [
|
||||||
missing_require = "error"
|
missing_require = "error"
|
||||||
|
|
||||||
[cram.task.apply]
|
[cram.task.apply]
|
||||||
|
shell = ["/bin/sh"]
|
||||||
missing_require = "warn"
|
missing_require = "warn"
|
||||||
require = [
|
require = [
|
||||||
"hosts.d/${FQDN}",
|
"hosts.d/${FQDN}",
|
||||||
|
|
|
@ -8,17 +8,19 @@ from typing import List, Optional
|
||||||
|
|
||||||
from vfs import Vfs
|
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
|
# Light monkeypatching because macos ships a "stable" a py
|
||||||
if sys.version_info <= (3, 9, 0):
|
if sys.version_info <= (3, 9, 0):
|
||||||
Path.readlink = lambda p: Path(os.readlink(str(p)))
|
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):
|
env: Optional[dict] = None):
|
||||||
|
|
||||||
|
shell = (global_config or {}).get("cram", {}).get("task", {}).get("apply", {}).get("shell", SHELL)
|
||||||
|
|
||||||
prefix = []
|
prefix = []
|
||||||
if env:
|
if env:
|
||||||
prefix.append("/usr/bin/env")
|
prefix.append("/usr/bin/env")
|
||||||
|
@ -26,7 +28,7 @@ def sh(cmd: List[str], /,
|
||||||
v = sh_quote(str(v))
|
v = sh_quote(str(v))
|
||||||
prefix.append(f"{k}={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=[]):
|
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):
|
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.root = root
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|
|
@ -36,21 +36,21 @@ class PackageV0(Package):
|
||||||
"""Install this package."""
|
"""Install this package."""
|
||||||
buildf = self.root / "BUILD"
|
buildf = self.root / "BUILD"
|
||||||
if buildf.exists():
|
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"
|
pref = self.root / "PRE_INSTALL"
|
||||||
if pref.exists():
|
if pref.exists():
|
||||||
fs.exec(self.root, sh([str(pref)]))
|
fs.exec(self.root, sh(self.global_config, [str(pref)]))
|
||||||
|
|
||||||
installf = self.root / "INSTALL"
|
installf = self.root / "INSTALL"
|
||||||
if installf.exists():
|
if installf.exists():
|
||||||
fs.exec(self.root, sh([str(installf)]))
|
fs.exec(self.root, sh(self.global_config, [str(installf)]))
|
||||||
else:
|
else:
|
||||||
stow(fs, self.root, dest, self.SPECIAL_FILES)
|
stow(fs, self.root, dest, self.SPECIAL_FILES)
|
||||||
|
|
||||||
postf = self.root / "POST_INSTALL"
|
postf = self.root / "POST_INSTALL"
|
||||||
if postf.exists():
|
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):
|
def _read(self, p: Path):
|
||||||
if p.exists():
|
if p.exists():
|
||||||
|
@ -94,16 +94,16 @@ class ProfileV0(PackageV0):
|
||||||
|
|
||||||
buildf = self.root / "BUILD"
|
buildf = self.root / "BUILD"
|
||||||
if buildf.exists():
|
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"
|
pref = self.root / "PRE_INSTALL"
|
||||||
if pref.exists():
|
if pref.exists():
|
||||||
fs.exec(self.root, sh([str(pref)]))
|
fs.exec(self.root, sh(self.global_config, [str(pref)]))
|
||||||
|
|
||||||
installf = self.root / "INSTALL"
|
installf = self.root / "INSTALL"
|
||||||
if installf.exists():
|
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"
|
postf = self.root / "POST_INSTALL"
|
||||||
if postf.exists():
|
if postf.exists():
|
||||||
fs.exec(self.root, sh([str(postf)]))
|
fs.exec(self.root, sh(self.global_config, [str(postf)]))
|
||||||
|
|
|
@ -79,7 +79,7 @@ class PackageV1(Package):
|
||||||
f = tempf(f"{sum}.sh")
|
f = tempf(f"{sum}.sh")
|
||||||
with open(f, "w") as fp:
|
with open(f, "w") as fp:
|
||||||
fp.write(content)
|
fp.write(content)
|
||||||
fs.exec(cwd, sh([f]))
|
fs.exec(cwd, sh(self.global_config, [f]))
|
||||||
|
|
||||||
def do_build(self, fs: Vfs, dest: Path):
|
def do_build(self, fs: Vfs, dest: Path):
|
||||||
self.do_sh_or_script(self.config().get("package", {}).get("build"), fs, dest)
|
self.do_sh_or_script(self.config().get("package", {}).get("build"), fs, dest)
|
||||||
|
|
Loading…
Reference in a new issue