Track manifest apps in the Applications catalog; make CMM gate contextual
All checks were successful
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s

seed-applications: a flask geenforce seed-applications command + service that
reads the imaging-PC-type manifests and creates a core Application for every
installer entry (MSI/EXE/CMD/BAT), so shopdb tracks what GE-Enforce actually
deploys. Idempotent, deduped by appname; File/Registry/PS1/INF config entries
are skipped. Run against the West Jefferson reference: 27 apps tracked (PC-DMIS
2016/2019/2026, eDNC, Oracle Client, Adobe Reader, HostExplorer, the VC++ redist
matrix, Keyence VR-6000, PowerShell, Display Kiosk, ...). 2 tests.

Editor: the CMM version gate (_CmmVersion) now only shows for CMM scopes - it is
metrology-specific, so a printer/common entry form no longer carries the
irrelevant field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 21:21:49 -04:00
parent d9d40297b6
commit 796007bcca
4 changed files with 119 additions and 2 deletions

View File

@@ -94,6 +94,45 @@ def rollback_scope(scopename, phase, versionnumber):
return versionnumber
# Entry types that install actual applications (vs File/Registry/PS1/INF config).
INSTALLER_TYPES = {'MSI', 'EXE', 'CMD', 'BAT'}
def seed_applications_from_manifests(manifests):
"""Populate the core Applications catalog from what the manifests install.
For every installer entry (MSI/EXE/CMD/BAT) across the given manifests,
create an Application (idempotent, deduped by appname) so shopdb tracks the
apps GE-Enforce deploys. `manifests` is a list of (scopename, phase, dict).
Returns {'created': [...], 'existing': [...]} (uncommitted).
"""
from shopdb.api import Application
created, existing, seen = [], [], set()
for _scopename, _phase, manifest in manifests:
for entry in manifest.get('Applications', []):
if entry.get('Type') not in INSTALLER_TYPES:
continue
name = (entry.get('Name') or '').strip()
if not name or name.lower() in seen:
continue
seen.add(name.lower())
if Application.query.filter(Application.appname.ilike(name)).first():
existing.append(name)
continue
description = None
comment = entry.get('_comment')
if comment:
description = comment.split('.')[0].strip()[:255]
db.session.add(Application(
appname=name[:100],
appdescription=description,
installpath=(entry.get('Installer') or '')[:255] or None,
isinstallable=True))
created.append(name)
return {'created': created, 'existing': existing}
def record_enforcement_report(payload):
"""Record one PC's enforcement cycle (observed state). Upserts the latest
report per (hostname, scopename, phase) and keeps prior ones as history.