Hardening cram, stratify uninstalls
This commit is contained in:
parent
0fb3cfa595
commit
547396b76e
4 changed files with 60 additions and 11 deletions
|
@ -5,7 +5,6 @@ import logging
|
|||
import os
|
||||
from pathlib import Path
|
||||
import pickle
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from . import (
|
||||
|
@ -25,6 +24,11 @@ from vfs import Vfs
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _exit(val):
|
||||
logging.shutdown()
|
||||
exit(val)
|
||||
|
||||
|
||||
def load(root: Path, name: str, clss):
|
||||
for c in clss:
|
||||
i = c(root, name)
|
||||
|
@ -33,10 +37,12 @@ def load(root: Path, name: str, clss):
|
|||
|
||||
|
||||
def load_package(root, name):
|
||||
log.debug(f"Attempting to load package {name} from {root}")
|
||||
return load(root, name, [PackageV1, PackageV0])
|
||||
|
||||
|
||||
def load_profile(root, name):
|
||||
log.debug(f"Attempting to load profile {name} from {root}")
|
||||
return load(root, name, [ProfileV1, ProfileV0])
|
||||
|
||||
|
||||
|
@ -44,6 +50,7 @@ def load_packages(root: Path) -> dict:
|
|||
"""Load the configured packages."""
|
||||
|
||||
packages = {}
|
||||
log.debug(f"Trying to load packages from {root}...")
|
||||
for p in (root / "packages.d").glob("*"):
|
||||
name = str(p.relative_to(root))
|
||||
packages[name] = load_package(p, name)
|
||||
|
@ -71,14 +78,20 @@ def build_fs(root: Path, dest: Path, prelude: List[str]) -> Vfs:
|
|||
requirements = []
|
||||
requirements.extend(prelude)
|
||||
|
||||
if packages:
|
||||
for p in packages:
|
||||
log.debug(f"Loaded package {p}")
|
||||
else:
|
||||
log.warning("Loaded no packages!")
|
||||
|
||||
for r in requirements:
|
||||
try:
|
||||
for d in packages[r].requires():
|
||||
if d not in requirements:
|
||||
requirements.append(d)
|
||||
except KeyError:
|
||||
print(f"Error: Unable to load package {r}", file=sys.stderr)
|
||||
exit(1)
|
||||
log.fatal(f"Error: Unable to load package {r}")
|
||||
_exit(1)
|
||||
|
||||
# Compute the topsort graph
|
||||
requirements = {r: packages[r].requires() for r in requirements}
|
||||
|
@ -140,16 +153,21 @@ def scrub(old_fs: Vfs, new_fs: Vfs) -> Vfs:
|
|||
|
||||
old_fs = old_fs.copy()
|
||||
new_fs = new_fs.copy()
|
||||
cleanup_fs = Vfs([])
|
||||
|
||||
# Look for files in the old log which are no longer present in the new log
|
||||
for txn in old_fs._log:
|
||||
if txn[0] == "link" and txn not in new_fs._log:
|
||||
new_fs.unlink(txn[2])
|
||||
cleanup_fs.unlink(txn[2])
|
||||
|
||||
elif txn[0] == "mkdir" and txn not in new_fs._log:
|
||||
new_fs.unlink(txn[1])
|
||||
cleanup_fs.unlink(txn[1])
|
||||
|
||||
return new_fs
|
||||
# Do unlink operations before we do install operations.
|
||||
# This works around being unable to finely straify uninstall operations over their source packages.
|
||||
cleanup_fs.merge(new_fs)
|
||||
|
||||
return cleanup_fs
|
||||
|
||||
|
||||
@click.group()
|
||||
|
@ -186,11 +204,19 @@ def do_apply(confdir, destdir, state_file, execute, optimize, require, exec_idem
|
|||
# Resolve the two input paths to absolutes
|
||||
root = confdir.resolve()
|
||||
dest = destdir.resolve()
|
||||
|
||||
if not root.is_dir():
|
||||
log.fatal(f"{confdir} does not exist!")
|
||||
_exit(1)
|
||||
|
||||
if not state_file.is_absolute():
|
||||
state_file = root / state_file
|
||||
|
||||
new_fs = build_fs(root, dest, require)
|
||||
old_fs = load_state(state_file)
|
||||
log.debug(f"Loaded old state consisting of {len(old_fs._log)} steps")
|
||||
|
||||
new_fs = build_fs(root, dest, require)
|
||||
log.debug(f"Built new state consisting of {len(new_fs._log)} steps")
|
||||
|
||||
# Middleware processing of the resulting filesystem(s)
|
||||
executable_fs = scrub(old_fs, new_fs)
|
||||
|
@ -219,7 +245,13 @@ def do_apply(confdir, destdir, state_file, execute, optimize, require, exec_idem
|
|||
@click.argument("list_packages", nargs=-1)
|
||||
def do_list(confdir, list_packages):
|
||||
"""List out packages, profiles, hosts and subpackages in the <confdir>."""
|
||||
packages = load_packages(confdir)
|
||||
root = confdir.resolve()
|
||||
|
||||
if not root.is_dir():
|
||||
log.fatal(f"{confdir} does not exist!")
|
||||
_exit(1)
|
||||
|
||||
packages = load_packages(root)
|
||||
|
||||
if list_packages:
|
||||
dest = Path("~/")
|
||||
|
@ -249,8 +281,14 @@ def do_list(confdir, list_packages):
|
|||
def do_state(confdir, state_file):
|
||||
"""List out the last `apply` state in the <confdir>/.cram.log or --state-file."""
|
||||
root = confdir.resolve()
|
||||
|
||||
if not root.is_dir():
|
||||
log.fatal(f"{confdir} does not exist!")
|
||||
_exit(1)
|
||||
|
||||
if not state_file.is_absolute():
|
||||
state_file = root / state_file
|
||||
|
||||
fs = load_state(state_file)
|
||||
for e in fs._log:
|
||||
print(*e)
|
||||
|
|
|
@ -38,7 +38,10 @@ def stow(fs: Vfs, src_dir: Path, dest_dir: Path, skip=[]):
|
|||
continue
|
||||
|
||||
dest = dest_root / src.relative_to(src_root)
|
||||
if src.is_dir():
|
||||
if src.is_symlink():
|
||||
|
||||
fs.link(src.readlink().resolve(), dest)
|
||||
elif src.is_dir():
|
||||
fs.mkdir(dest)
|
||||
fs.chmod(dest, src.stat().st_mode)
|
||||
|
||||
|
|
|
@ -51,7 +51,12 @@ class PackageV1(Package):
|
|||
return any(self.do_sh_or_script(c, fs, dest) for c in content)
|
||||
|
||||
elif isinstance(content, dict):
|
||||
return self.do_sh_or_script(content["run"], fs, dest, {"cwd": self.root}.get(content.get("root"), "/tmp"))
|
||||
return self.do_sh_or_script(
|
||||
content["run"],
|
||||
fs,
|
||||
dest,
|
||||
{"cwd": self.root}.get(content.get("root"), "/tmp")
|
||||
)
|
||||
|
||||
elif isinstance(content, str):
|
||||
sum = sha256()
|
||||
|
|
|
@ -52,7 +52,7 @@ class Vfs(object):
|
|||
elif e[0] == "unlink":
|
||||
_, dest = e
|
||||
if dest.is_dir():
|
||||
rmtree(dest)
|
||||
rmtree(dest)
|
||||
elif dest.is_file():
|
||||
dest.unlink()
|
||||
|
||||
|
@ -79,3 +79,6 @@ class Vfs(object):
|
|||
|
||||
def copy(self):
|
||||
return Vfs(list(self._log))
|
||||
|
||||
def merge(self, other: "Vfs"):
|
||||
self._log.extend(other._log)
|
||||
|
|
Loading…
Reference in a new issue