Add GE-Enforce P2 admin CRUD API: scopes, entries, reorder, simulate, publish
Full HTTP admin surface behind the manifest editor (geenforce.manage for edits, geenforce.publish for shipping): - Scopes: POST/GET/PUT/DELETE /scopes[/<id>] (create imaging PC types, edit the ComputerType/MeasuringToolType mapping + metadata, delete). - Entries: POST /scopes/<id>/entries, PUT/DELETE /entries/<id>. Payloads use the manifest Applications[] shape; populate_entry (refactored out of build_entry) updates an entry in place, resetting omitted fields and replacing children. - Reorder: PUT /scopes/<id>/entries/reorder enforces the ordering contract (body must list exactly the scope's entry ids). - Simulate: GET /scopes/<id>/simulate?pctype&subtype&hostname&machinenumber& cmmversion returns which entries apply and which filter excluded the rest, reusing the engine-mirror filters. The "what would this PC get" tool. - Publish lifecycle: POST /scopes/<id>/publish (records publishedby from JWT), GET /scopes/<id>/versions, GET .../versions/<n> (frozen manifest), POST /scopes/<id>/rollback. Entry type validated against ENTRY_TYPES; 8 CRUD tests. JWT+permission gated so the authz sweep covers them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,33 +24,40 @@ _KEY_TO_ATTR = {mk: attr for attr, mk in _SCALAR_FIELDS}
|
||||
_FLAG_TO_ATTR = {mk: attr for attr, mk in _BOOL_FLAGS}
|
||||
|
||||
|
||||
def build_entry(entry_dict, sortorder):
|
||||
"""Build a ManifestEntry (+ children) from one Applications[] entry."""
|
||||
entry = ManifestEntry(sortorder=sortorder,
|
||||
name=entry_dict.get('Name'),
|
||||
entrytype=entry_dict.get('Type'))
|
||||
if '_comment' in entry_dict:
|
||||
entry.comment = entry_dict['_comment']
|
||||
# Scalar model attributes that a populate resets (so an update clears fields the
|
||||
# new payload omits). name/entrytype are required and set explicitly.
|
||||
_RESETTABLE_ATTRS = [attr for _, attr in _SCALAR_FIELDS
|
||||
if attr not in ('name', 'entrytype')] + ['comment', 'regvalue']
|
||||
|
||||
|
||||
def populate_entry(entry, entry_dict):
|
||||
"""Set every field + child on an existing (or new) ManifestEntry from one
|
||||
Applications[] entry dict. Existing children are replaced. Returns entry."""
|
||||
entry.name = entry_dict.get('Name')
|
||||
entry.entrytype = entry_dict.get('Type')
|
||||
entry.comment = entry_dict.get('_comment')
|
||||
for attr in _RESETTABLE_ATTRS:
|
||||
if attr in ('comment', 'regvalue'):
|
||||
continue
|
||||
setattr(entry, attr, None)
|
||||
for key, attr in _KEY_TO_ATTR.items():
|
||||
if attr in ('name', 'entrytype'):
|
||||
continue
|
||||
if key in entry_dict and entry_dict[key] is not None:
|
||||
setattr(entry, attr, entry_dict[key])
|
||||
# RegValue stored as its raw JSON literal so DWord vs string typing survives.
|
||||
if 'RegValue' in entry_dict:
|
||||
entry.regvalue = json.dumps(entry_dict['RegValue'])
|
||||
entry.regvalue = (json.dumps(entry_dict['RegValue'])
|
||||
if 'RegValue' in entry_dict else None)
|
||||
for key, attr in _FLAG_TO_ATTR.items():
|
||||
if entry_dict.get(key):
|
||||
setattr(entry, attr, True)
|
||||
# Multi-value filters -> child rows (preserve order).
|
||||
for i, value in enumerate(entry_dict.get('PCTypes') or []):
|
||||
entry.pctypes.append(ManifestEntryPcType(sortorder=i, pctypevalue=value))
|
||||
for i, value in enumerate(entry_dict.get('TargetHostnames') or []):
|
||||
entry.hostnames.append(
|
||||
ManifestEntryHostname(sortorder=i, hostnamepattern=value))
|
||||
for i, value in enumerate(entry_dict.get('TargetMachineNumbers') or []):
|
||||
entry.machinenumbers.append(
|
||||
ManifestEntryMachineNumber(sortorder=i, machinenumber=str(value)))
|
||||
setattr(entry, attr, bool(entry_dict.get(key)))
|
||||
# Multi-value filters -> child rows (replace, preserve order).
|
||||
entry.pctypes = [ManifestEntryPcType(sortorder=i, pctypevalue=value)
|
||||
for i, value in enumerate(entry_dict.get('PCTypes') or [])]
|
||||
entry.hostnames = [ManifestEntryHostname(sortorder=i, hostnamepattern=value)
|
||||
for i, value in enumerate(entry_dict.get('TargetHostnames') or [])]
|
||||
entry.machinenumbers = [
|
||||
ManifestEntryMachineNumber(sortorder=i, machinenumber=str(value))
|
||||
for i, value in enumerate(entry_dict.get('TargetMachineNumbers') or [])]
|
||||
# InUseCheck object + Processes[].
|
||||
inuse = entry_dict.get('InUseCheck')
|
||||
if inuse:
|
||||
@@ -62,9 +69,17 @@ def build_entry(entry_dict, sortorder):
|
||||
exepath=proc.get('ExePath'),
|
||||
gracefulclosetimeoutsec=proc.get('GracefulCloseTimeoutSec')))
|
||||
entry.inusecheck = check
|
||||
else:
|
||||
entry.inusecheck = None
|
||||
return entry
|
||||
|
||||
|
||||
def build_entry(entry_dict, sortorder):
|
||||
"""Build a new ManifestEntry (+ children) from one Applications[] entry."""
|
||||
entry = ManifestEntry(sortorder=sortorder)
|
||||
return populate_entry(entry, entry_dict)
|
||||
|
||||
|
||||
def build_scope(scopename, phase, manifest_dict, iscommon=False):
|
||||
"""Build a ManifestScope (+ entries) from a parsed manifest dict."""
|
||||
scope = ManifestScope(
|
||||
|
||||
Reference in New Issue
Block a user