Import the wilm endpoint-monitor and channel-distribution work
Everything on origin/wilm that is not on the published main, applied as one
change because the published history has rewritten hashes: per-commit
cherry-pick conflicts against our main even though the squashed diff applies
clean.
Contents: endpoint monitor catalog + policy attachment + warn-only drift
reconciler, operator issue reports from the tray, agent launch-path allowlist,
and the GHCR channel-image distribution path for dev workstations.
NOT READY FOR main. Open items:
- 10 new routes are absent from docs/api-inventory.json, so
test_api_inventory_parity fails. That gate SKIPS on the published mirror
(the test ships, its data file is scrubbed), which is why wilm never saw it.
- dev-bootstrap.ps1 and Start-ShopDBDev.ps1 hardcode the ge-aero GitHub org.
- plugins/endpoints/plugin.py carries a whitespace-only re-indent.
Scope: product
This commit is contained in:
@@ -40,6 +40,23 @@ def _known_actions():
|
||||
return set(COMMAND_ALLOWLIST.keys())
|
||||
|
||||
|
||||
def _is_valid_action(name):
|
||||
"""True when an action name is either an allowlisted command or a monitor
|
||||
attachment (monitor:<id>) pointing at an existing monitor definition.
|
||||
|
||||
Monitor actions are intentionally kept out of the command allowlist so they
|
||||
are never enqueued as commands; they are only valid as policy attachments.
|
||||
"""
|
||||
if name in _known_actions():
|
||||
return True
|
||||
from plugins.endpoints.commands import monitor_id_from_action
|
||||
from plugins.endpoints.models import EndpointMonitor
|
||||
monitorid = monitor_id_from_action(name)
|
||||
if monitorid is None:
|
||||
return False
|
||||
return db.session.get(EndpointMonitor, monitorid) is not None
|
||||
|
||||
|
||||
def _configpolicy_dict(policy):
|
||||
data = policy.to_dict()
|
||||
data['actions'] = [a.to_dict() for a in policy.actions]
|
||||
@@ -89,11 +106,15 @@ def _apply_actions(policy, actions):
|
||||
|
||||
Returns an error_response on failure, else None.
|
||||
"""
|
||||
known = _known_actions()
|
||||
policy.actions.clear()
|
||||
# Flush the deletes before re-appending. The (configurationpolicyid, action)
|
||||
# unique constraint collides if SQLAlchemy orders a re-added action's INSERT
|
||||
# before the old row's DELETE in the same flush, which surfaces as a generic
|
||||
# "value already in use" IntegrityError on an otherwise valid edit.
|
||||
db.session.flush()
|
||||
for row in actions or []:
|
||||
name = (row.get('action') or '').strip()
|
||||
if name not in known:
|
||||
if not _is_valid_action(name):
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
f'Unknown action: {name!r}')
|
||||
mode = (row.get('mode') or ACTION_MODE_GATE).strip()
|
||||
@@ -259,6 +280,10 @@ def get_business_policy(policy_id: int):
|
||||
def _apply_items(policy, items):
|
||||
"""Replace a business policy's configuration-policy items, validating each."""
|
||||
policy.items.clear()
|
||||
# Flush the deletes before re-appending so the (businesspolicyid,
|
||||
# configurationpolicyid) unique constraint does not collide with a re-added
|
||||
# item whose INSERT would otherwise race ahead of the old row's DELETE.
|
||||
db.session.flush()
|
||||
seen = set()
|
||||
for order, row in enumerate(items or []):
|
||||
cpid = row.get('configurationpolicyid')
|
||||
@@ -375,14 +400,19 @@ def delete_business_policy(policy_id: int):
|
||||
@computers_bp.route('/policies/actions', methods=['GET'])
|
||||
@require_permission('computers.view')
|
||||
def list_policy_actions():
|
||||
"""The allowlisted action names + labels a policy may reference."""
|
||||
"""The allowlisted action names + labels a policy may reference.
|
||||
|
||||
Returns command actions tagged kind='command'. Monitor attachments are not
|
||||
listed here: the UI builds that branch from the endpoints monitor catalog
|
||||
(endpointsApi.monitors) so newly created monitors appear without a reload.
|
||||
"""
|
||||
gate = _feature_gate()
|
||||
if gate:
|
||||
return gate
|
||||
from plugins.endpoints.commands import COMMAND_ALLOWLIST
|
||||
return success_response([
|
||||
{'action': name, 'label': meta.get('label'),
|
||||
'description': meta.get('description')}
|
||||
'description': meta.get('description'), 'kind': 'command'}
|
||||
for name, meta in COMMAND_ALLOWLIST.items()
|
||||
])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user