56 lines
2.2 KiB
Python
56 lines
2.2 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Render a validated S3 report as Prometheus text; no listener or delivery."""
|
||
|
|
import argparse
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
import sys
|
||
|
|
|
||
|
|
from platform_event import translate
|
||
|
|
from receiver import STATES, contract_check, instant, read_json
|
||
|
|
|
||
|
|
|
||
|
|
def labels(values):
|
||
|
|
def escape(value):
|
||
|
|
return value.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
|
||
|
|
return '{' + ','.join(f'{key}="{escape(value)}"' for key, value in values.items()) + '}'
|
||
|
|
|
||
|
|
|
||
|
|
def render(report, contract, now):
|
||
|
|
contract_check(contract)
|
||
|
|
event = translate(report, contract)
|
||
|
|
observed = instant(event['observed_at']).timestamp()
|
||
|
|
if not 0 <= now.timestamp() - observed <= contract['max_event_age_seconds']:
|
||
|
|
raise ValueError('stale or future report')
|
||
|
|
identity = dict(stream=contract['stream'], producer=contract['producer'])
|
||
|
|
lines = ['# HELP railiance_assurance_state Producer classification, one hot per signal.',
|
||
|
|
'# TYPE railiance_assurance_state gauge']
|
||
|
|
for signal, state in sorted(event['states'].items()):
|
||
|
|
for candidate in sorted(STATES):
|
||
|
|
key = labels(dict(identity, signal=signal, state=candidate))
|
||
|
|
lines.append(f'railiance_assurance_state{key} {int(state == candidate)}')
|
||
|
|
lines.extend([
|
||
|
|
'# HELP railiance_assurance_observed_timestamp_seconds Original producer evaluation time.',
|
||
|
|
'# TYPE railiance_assurance_observed_timestamp_seconds gauge',
|
||
|
|
f'railiance_assurance_observed_timestamp_seconds{labels(identity)} {observed}',
|
||
|
|
])
|
||
|
|
return '\n'.join(lines) + '\n'
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
||
|
|
parser.add_argument('--contract', required=True, type=Path)
|
||
|
|
parser.add_argument('report', type=Path)
|
||
|
|
args = parser.parse_args()
|
||
|
|
try:
|
||
|
|
output = render(read_json(args.report), read_json(args.contract), datetime.now(timezone.utc))
|
||
|
|
except (OSError, ValueError, KeyError, TypeError, AttributeError):
|
||
|
|
print(json.dumps({'status': 'rejected', 'error': 'invalid-or-expired-report'}), file=sys.stderr)
|
||
|
|
return 2
|
||
|
|
sys.stdout.write(output)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
sys.exit(main())
|