fix(framework): detect installed tool drift (#1194)
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare deployed Mosaic framework tools with the shipped framework source.
|
||||
|
||||
The framework ownership manifest declares tools/** framework-owned. Consequently every
|
||||
regular file shipped below source tools/ is expected below MOSAIC_HOME/tools/, except
|
||||
the explicit operator credential carve-out. Files that exist only in the deployed tree
|
||||
are operator/unknown state and are reported but never treated as framework drift.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
OPERATOR_CARVE_OUTS = {"_lib/credentials.json"}
|
||||
|
||||
|
||||
def digest(path: Path) -> str:
|
||||
value = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
value.update(chunk)
|
||||
return value.hexdigest()
|
||||
|
||||
|
||||
def default_source_tools() -> Path:
|
||||
# .../tools/quality/scripts/framework-drift-check.py -> .../tools
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def normalize_source(path: Path) -> Path:
|
||||
candidate = path.resolve()
|
||||
if (candidate / "tools").is_dir():
|
||||
candidate = candidate / "tools"
|
||||
return candidate
|
||||
|
||||
|
||||
def files_below(root: Path) -> dict[str, Path]:
|
||||
return {
|
||||
path.relative_to(root).as_posix(): path
|
||||
for path in root.rglob("*")
|
||||
if path.is_file()
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Detect deployed Mosaic framework-tool drift")
|
||||
parser.add_argument(
|
||||
"--source-root",
|
||||
type=Path,
|
||||
default=Path(os.environ["MOSAIC_FRAMEWORK_SOURCE_ROOT"])
|
||||
if os.environ.get("MOSAIC_FRAMEWORK_SOURCE_ROOT")
|
||||
else default_source_tools(),
|
||||
help="shipped framework root or tools root (default: this script's shipped tools tree)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--installed-root",
|
||||
type=Path,
|
||||
default=Path(os.environ.get("MOSAIC_HOME", Path.home() / ".config/mosaic")) / "tools",
|
||||
help="deployed tools root (default: $MOSAIC_HOME/tools)",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="list in-sync paths too")
|
||||
args = parser.parse_args()
|
||||
|
||||
source = normalize_source(args.source_root)
|
||||
installed = args.installed_root.resolve()
|
||||
if not source.is_dir():
|
||||
print(f"[framework-drift] CANNOT_ASSERT source tools missing: {source}", file=sys.stderr)
|
||||
return 2
|
||||
if not installed.is_dir():
|
||||
print(f"[framework-drift] CANNOT_ASSERT installed tools missing: {installed}", file=sys.stderr)
|
||||
return 2
|
||||
if source == installed:
|
||||
print(
|
||||
"[framework-drift] CANNOT_ASSERT source and installed roots are identical; "
|
||||
"run the checker from the bundled package or pass --source-root",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
source_files = files_below(source)
|
||||
installed_files = files_below(installed)
|
||||
required = sorted(set(source_files) - OPERATOR_CARVE_OUTS)
|
||||
in_sync: list[str] = []
|
||||
stale: list[str] = []
|
||||
not_installed: list[str] = []
|
||||
for relative in required:
|
||||
deployed = installed / relative
|
||||
if not deployed.is_file():
|
||||
not_installed.append(relative)
|
||||
elif digest(source_files[relative]) == digest(deployed):
|
||||
in_sync.append(relative)
|
||||
else:
|
||||
stale.append(relative)
|
||||
|
||||
installed_only = sorted(set(installed_files) - set(source_files) - OPERATOR_CARVE_OUTS)
|
||||
if args.verbose:
|
||||
for relative in in_sync:
|
||||
print(f"[framework-drift] IN_SYNC {relative}")
|
||||
for relative in stale:
|
||||
print(f"[framework-drift] STALE {relative}")
|
||||
for relative in not_installed:
|
||||
print(f"[framework-drift] NOT_INSTALLED {relative}")
|
||||
if args.verbose:
|
||||
for relative in installed_only:
|
||||
print(f"[framework-drift] INSTALLED_ONLY operator-or-unknown {relative}")
|
||||
|
||||
print(
|
||||
"[framework-drift] summary "
|
||||
f"in-sync={len(in_sync)} stale={len(stale)} not-installed={len(not_installed)} "
|
||||
f"installed-only={len(installed_only)}"
|
||||
)
|
||||
print(
|
||||
"[framework-drift] classification tools/**=framework-owned-required; "
|
||||
"tools/_lib/credentials.json=operator-owned-excluded; "
|
||||
"installed-only=operator-or-unknown-preserved"
|
||||
)
|
||||
if stale or not_installed:
|
||||
print(
|
||||
"[framework-drift] FAIL deployed framework tools do not match shipped source; "
|
||||
"schedule a reviewed framework reseed",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
CHECKER = Path(__file__).with_name("framework-drift-check.py")
|
||||
|
||||
|
||||
class FrameworkDriftCheckTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
root = Path(self.temp.name)
|
||||
self.source = root / "framework" / "tools"
|
||||
self.installed = root / "home" / "tools"
|
||||
for directory in (self.source / "git", self.source / "_lib", self.installed / "git", self.installed / "_lib"):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
(self.source / "git" / "guard.sh").write_text("fixed\n")
|
||||
(self.source / "git" / "new-wrapper.sh").write_text("new\n")
|
||||
(self.source / "_lib" / "credentials.json").write_text("source-placeholder\n")
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.temp.cleanup()
|
||||
|
||||
def run_check(self, *extra: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(CHECKER), "--source-root", str(self.source.parent), "--installed-root", str(self.installed), *extra],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"},
|
||||
)
|
||||
|
||||
def test_fails_loudly_and_classifies_stale_missing_and_installed_only(self) -> None:
|
||||
(self.installed / "git" / "guard.sh").write_text("broken\n")
|
||||
(self.installed / "local-helper.sh").write_text("operator\n")
|
||||
(self.installed / "_lib" / "credentials.json").write_text("secret\n")
|
||||
|
||||
result = self.run_check("--verbose")
|
||||
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertIn("STALE git/guard.sh", result.stdout)
|
||||
self.assertIn("NOT_INSTALLED git/new-wrapper.sh", result.stdout)
|
||||
self.assertIn("INSTALLED_ONLY operator-or-unknown local-helper.sh", result.stdout)
|
||||
self.assertNotIn("STALE _lib/credentials.json", result.stdout)
|
||||
self.assertNotIn("NOT_INSTALLED _lib/credentials.json", result.stdout)
|
||||
self.assertIn("in-sync=0 stale=1 not-installed=1 installed-only=1", result.stdout)
|
||||
self.assertIn("FAIL deployed framework tools", result.stderr)
|
||||
|
||||
def test_passes_only_when_every_framework_owned_source_file_matches(self) -> None:
|
||||
(self.installed / "git" / "guard.sh").write_text("fixed\n")
|
||||
(self.installed / "git" / "new-wrapper.sh").write_text("new\n")
|
||||
(self.installed / "_lib" / "credentials.json").write_text("different-operator-secret\n")
|
||||
|
||||
result = self.run_check()
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("in-sync=2 stale=0 not-installed=0 installed-only=0", result.stdout)
|
||||
|
||||
def test_refuses_self_comparison_that_would_make_drift_unobservable(self) -> None:
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(CHECKER), "--source-root", str(self.source), "--installed-root", str(self.source)],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
self.assertEqual(result.returncode, 2)
|
||||
self.assertIn("source and installed roots are identical", result.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user