65 lines
3.3 KiB
Python
65 lines
3.3 KiB
Python
"""Run inside the already admitted State Hub dispatcher; no token leaves it."""
|
|
import json
|
|
import re
|
|
import sys
|
|
import urllib.request
|
|
import urllib.error
|
|
from api.services.memo_dispatch import Dispatcher, ORIGIN, NoRedirect, token_file
|
|
|
|
commands = json.loads(sys.stdin.read(256 * 1024))
|
|
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())
|
|
token_path = '/var/run/secrets/state-hub/informed-decision/lifecycle-token'
|
|
|
|
def call(method, path, body=None, key=None):
|
|
if not path.startswith('/v1/decisions'):
|
|
raise ValueError('route_refused')
|
|
raw = json.dumps(body).encode() if body is not None else None
|
|
if raw is not None and len(raw) > 256 * 1024:
|
|
raise ValueError('input_too_large')
|
|
headers = {'Authorization': 'Bearer ' + token_file(token_path),
|
|
'Content-Type': 'application/json'}
|
|
if key:
|
|
headers['Idempotency-Key'] = key
|
|
req = urllib.request.Request(ORIGIN + path, data=raw, headers=headers, method=method)
|
|
try:
|
|
response = opener.open(req, timeout=15)
|
|
except urllib.error.HTTPError as error:
|
|
response = error
|
|
with response:
|
|
result = response.read(1024 * 1024 + 1)
|
|
if len(result) > 1024 * 1024:
|
|
raise ValueError('response_unavailable')
|
|
value = json.loads(result)
|
|
if response.code not in (200, 201):
|
|
code = value.get('error', 'request_refused') if isinstance(value, dict) else 'request_refused'
|
|
code = code if isinstance(code, str) and re.fullmatch('[a-z0-9_]{1,64}', code) else 'request_refused'
|
|
print(json.dumps({'http_status': response.code, 'error': code}), flush=True)
|
|
raise ValueError('operation_refused')
|
|
return value
|
|
|
|
for command in commands:
|
|
ident = command['id']
|
|
if not re.fullmatch('[a-z0-9-]{3,63}', ident):
|
|
raise SystemExit('invalid_memo_identity')
|
|
try:
|
|
receipts = {}
|
|
for action, path in [('register', '/v1/decisions'),
|
|
('publish', '/v1/decisions/' + ident + '/publications'),
|
|
('review', '/v1/decisions/' + ident + '/review-requests')]:
|
|
receipts[action] = call('POST', path, command[action],
|
|
'security-consolidation.' + ident + '.' + action + '.v1')
|
|
handoff = receipts['review']
|
|
queued = Dispatcher('/data/private').enqueue(handoff['envelope'])
|
|
outcome = call('GET', '/v1/decisions/' + ident + '/outcomes?version=1')
|
|
print(json.dumps({'memo_id': ident, 'version': 1,
|
|
'register_event_id': receipts['register']['event_id'],
|
|
'publication_event_id': receipts['publish']['event_id'],
|
|
'review_event_id': handoff['event_id'],
|
|
'review_memo_id': handoff['review_memo_id'],
|
|
'queue_status': queued.status, 'queue_detail': queued.detail,
|
|
'workspace_url': 'https://decisions.coulomb.social/decisions/' + ident + '?version=1',
|
|
'outcome_review': outcome.get('review'),
|
|
'human_dispositions': len(outcome.get('dispositions', []))}), flush=True)
|
|
except Exception:
|
|
print(json.dumps({'memo_id': ident, 'status': 'operation_unverified'}), flush=True)
|
|
raise SystemExit(1) from None
|