66 lines
2.8 KiB
Python
66 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Post bounded failure/recovery events, or wrap an explicitly authorized local check."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
from control import Client
|
|
from worker import atomic_json
|
|
|
|
|
|
def main():
|
|
p=argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument('--config',required=True)
|
|
p.add_argument('--source',required=True)
|
|
p.add_argument('--event-id',required=True)
|
|
p.add_argument('--severity',choices=['info','warning','critical'],default='warning')
|
|
p.add_argument('--summary',required=True)
|
|
p.add_argument('--evidence',default='')
|
|
p.add_argument('--task-id',default='')
|
|
p.add_argument('--status',choices=['firing','resolved'],default='firing')
|
|
p.add_argument('--timeout',type=int,default=300)
|
|
p.add_argument('command',nargs=argparse.REMAINDER)
|
|
a=p.parse_args()
|
|
cfg=json.loads(Path(a.config).read_text(encoding='utf-8-sig'))
|
|
client=Client(cfg.get('control',cfg))
|
|
command=a.command[1:] if a.command[:1]==['--'] else a.command
|
|
status=a.status
|
|
code=0
|
|
if command:
|
|
if not 1<=a.timeout<=86400:raise ValueError('Timeout must be bounded')
|
|
# Inherit output for the operator, never send raw logs or command arguments.
|
|
# No shell and no command is ever accepted by the HTTP service.
|
|
try:code=subprocess.run(command,timeout=a.timeout).returncode
|
|
except subprocess.TimeoutExpired:code=124
|
|
except OSError:code=127
|
|
status='resolved' if code==0 else 'firing'
|
|
payload={'source':a.source,'event_id':a.event_id,'severity':a.severity,
|
|
'status':status,'summary':a.summary,'evidence':[a.evidence] if a.evidence else []}
|
|
if a.task_id:payload['task_id']=a.task_id
|
|
state_path=Path(a.config).resolve().with_suffix('.hook-check-state.json')
|
|
state=json.loads(state_path.read_text(encoding='utf-8')) if state_path.exists() else {}
|
|
key=a.source+':'+a.event_id
|
|
previous=state.get(key)
|
|
if command and code==0 and not previous:
|
|
print(json.dumps({'status':'ok','recovery_required':False}))
|
|
return 0
|
|
if status=='firing':
|
|
state[key]={'payload':payload,'accepted':False}
|
|
atomic_json(state_path,state)
|
|
try:
|
|
if status=='resolved' and previous and not previous.get('accepted'):
|
|
client.call('/api/hooks',previous['payload'])
|
|
result=client.call('/api/hooks',payload)
|
|
except Exception:
|
|
print('Hook delivery failed; original command exit='+str(code)+'. Inspect event delivery before retry.',file=sys.stderr)
|
|
return code or 75
|
|
state[key]={'payload':payload,'accepted':True}
|
|
atomic_json(state_path,state)
|
|
print(json.dumps(result,ensure_ascii=True))
|
|
return code
|
|
|
|
|
|
if __name__=='__main__':sys.exit(main())
|