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
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import pickle
|
import pickle
|
||||||
import sys
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
|
@ -25,6 +24,11 @@ from vfs import Vfs
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _exit(val):
|
||||||
|
logging.shutdown()
|
||||||
|
exit(val)
|
||||||
|
|
||||||
|
|
||||||
def load(root: Path, name: str, clss):
|
def load(root: Path, name: str, clss):
|
||||||
for c in clss:
|
for c in clss:
|
||||||
i = c(root, name)
|
i = c(root, name)
|
||||||
|
@ -33,10 +37,12 @@ def load(root: Path, name: str, clss):
|
||||||
|
|
||||||
|
|
||||||
def load_package(root, name):
|
def load_package(root, name):
|
||||||
|
log.debug(f"Attempting to load package {name} from {root}")
|
||||||
return load(root, name, [PackageV1, PackageV0])
|
return load(root, name, [PackageV1, PackageV0])
|
||||||
|
|
||||||
|
|
||||||
def load_profile(root, name):
|
def load_profile(root, name):
|
||||||
|
log.debug(f"Attempting to load profile {name} from {root}")
|
||||||
return load(root, name, [ProfileV1, ProfileV0])
|
return load(root, name, [ProfileV1, ProfileV0])
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,6 +50,7 @@ def load_packages(root: Path) -> dict:
|
||||||
"""Load the configured packages."""
|
"""Load the configured packages."""
|
||||||
|
|
||||||
packages = {}
|
packages = {}
|
||||||
|
log.debug(f"Trying to load packages from {root}...")
|
||||||
for p in (root / "packages.d").glob("*"):
|
for p in (root / "packages.d").glob("*"):
|
||||||
name = str(p.relative_to(root))
|
name = str(p.relative_to(root))
|
||||||
packages[name] = load_package(p, name)
|
packages[name] = load_package(p, name)
|
||||||
|
@ -71,14 +78,20 @@ def build_fs(root: Path, dest: Path, prelude: List[str]) -> Vfs:
|
||||||
requirements = []
|
requirements = []
|
||||||
requirements.extend(prelude)
|
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:
|
for r in 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:
|
||||||
print(f"Error: Unable to load package {r}", file=sys.stderr)
|
log.fatal(f"Error: Unable to load package {r}")
|
||||||
exit(1)
|
_exit(1)
|
||||||
|
|
||||||
# Compute the topsort graph
|
# Compute the topsort graph
|
||||||
requirements = {r: packages[r].requires() for r in requirements}
|
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()
|
old_fs = old_fs.copy()
|
||||||
new_fs = new_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
|
# Look for files in the old log which are no longer present in the new log
|
||||||
for txn in old_fs._log:
|
for txn in old_fs._log:
|
||||||
if txn[0] == "link" and txn not in new_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:
|
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()
|
@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
|
# Resolve the two input paths to absolutes
|
||||||
root = confdir.resolve()
|
root = confdir.resolve()
|
||||||
dest = destdir.resolve()
|
dest = destdir.resolve()
|
||||||
|
|
||||||
|
if not root.is_dir():
|
||||||
|
log.fatal(f"{confdir} does not exist!")
|
||||||
|
_exit(1)
|
||||||
|
|
||||||
if not state_file.is_absolute():
|
if not state_file.is_absolute():
|
||||||
state_file = root / state_file
|
state_file = root / state_file
|
||||||
|
|
||||||
new_fs = build_fs(root, dest, require)
|
|
||||||
old_fs = load_state(state_file)
|
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)
|
# Middleware processing of the resulting filesystem(s)
|
||||||
executable_fs = scrub(old_fs, new_fs)
|
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)
|
@click.argument("list_packages", nargs=-1)
|
||||||
def do_list(confdir, list_packages):
|
def do_list(confdir, list_packages):
|
||||||
"""List out packages, profiles, hosts and subpackages in the <confdir>."""
|
"""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:
|
if list_packages:
|
||||||
dest = Path("~/")
|
dest = Path("~/")
|
||||||
|
@ -249,8 +281,14 @@ def do_list(confdir, list_packages):
|
||||||
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."""
|
||||||
root = confdir.resolve()
|
root = confdir.resolve()
|
||||||
|
|
||||||
|
if not root.is_dir():
|
||||||
|
log.fatal(f"{confdir} does not exist!")
|
||||||
|
_exit(1)
|
||||||
|
|
||||||
if not state_file.is_absolute():
|
if not state_file.is_absolute():
|
||||||
state_file = root / state_file
|
state_file = root / state_file
|
||||||
|
|
||||||
fs = load_state(state_file)
|
fs = load_state(state_file)
|
||||||
for e in fs._log:
|
for e in fs._log:
|
||||||
print(*e)
|
print(*e)
|
||||||
|
|
|
@ -38,7 +38,10 @@ def stow(fs: Vfs, src_dir: Path, dest_dir: Path, skip=[]):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dest = dest_root / src.relative_to(src_root)
|
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.mkdir(dest)
|
||||||
fs.chmod(dest, src.stat().st_mode)
|
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)
|
return any(self.do_sh_or_script(c, fs, dest) for c in content)
|
||||||
|
|
||||||
elif isinstance(content, dict):
|
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):
|
elif isinstance(content, str):
|
||||||
sum = sha256()
|
sum = sha256()
|
||||||
|
|
|
@ -79,3 +79,6 @@ class Vfs(object):
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Vfs(list(self._log))
|
return Vfs(list(self._log))
|
||||||
|
|
||||||
|
def merge(self, other: "Vfs"):
|
||||||
|
self._log.extend(other._log)
|
||||||
|
|
Loading…
Reference in a new issue