Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-06-13 08:42:16

0001 """corun job notification callback endpoint for PanDA bot Mattermost notices."""
0002 
0003 import json
0004 import logging
0005 
0006 from django.views.decorators.csrf import csrf_exempt
0007 from django.views.decorators.http import require_POST
0008 from django.http import JsonResponse
0009 from decouple import config
0010 from mattermostdriver import Driver
0011 
0012 logger = logging.getLogger('panda_bot')
0013 
0014 
0015 def _short_text(value, limit=200):
0016     if value is None:
0017         return ''
0018     text = str(value).strip()
0019     if len(text) <= limit:
0020         return text
0021     return text[:limit - 3].rstrip() + '...'
0022 
0023 
0024 def _format_duration(timing):
0025     if timing in (None, ''):
0026         return ''
0027     if isinstance(timing, dict):
0028         for key in ('duration_s', 'elapsed_s', 'seconds', 'total_seconds'):
0029             if key in timing:
0030                 timing = timing[key]
0031                 break
0032         else:
0033             return ''
0034     try:
0035         total_seconds = int(round(float(timing)))
0036     except (TypeError, ValueError):
0037         return ''
0038     if total_seconds < 0:
0039         return ''
0040 
0041     hours, remainder = divmod(total_seconds, 3600)
0042     minutes, seconds = divmod(remainder, 60)
0043     if hours:
0044         return f"{hours}h{minutes}m{seconds}s"
0045     if minutes:
0046         return f"{minutes}m{seconds}s"
0047     return f"{seconds}s"
0048 
0049 
0050 def _mattermost_driver():
0051     return Driver({
0052         'url': config('MATTERMOST_URL', default='chat.epic-eic.org'),
0053         'token': config('MATTERMOST_TOKEN'),
0054         'scheme': 'https',
0055         'port': 443,
0056     })
0057 
0058 
0059 def _message_from_payload(payload):
0060     status = payload.get('status', 'unknown')
0061     display_name = (
0062         payload.get('result_page_title')
0063         or payload.get('definition_name')
0064         or payload.get('definition_id')
0065         or 'corun job'
0066     )
0067     result_url = payload.get('result_page_url')
0068     submitted_by = _short_text(payload.get('submitted_by'), limit=80)
0069     duration = _format_duration(payload.get('timing'))
0070     error = payload.get('error')
0071 
0072     title = f"corun job {status}: {_short_text(display_name)}"
0073     lines = [f"**{title}**"]
0074     if result_url:
0075         lines.append(f"Result: {result_url}")
0076     if submitted_by:
0077         lines.append(f"Submitted by: {submitted_by}")
0078     if duration:
0079         lines.append(f"Duration: {duration}")
0080     if error:
0081         lines.append(f"Error: `{str(error)[:1000]}`")
0082     return "\n".join(lines)
0083 
0084 
0085 @csrf_exempt
0086 @require_POST
0087 def corun_callback(request):
0088     """Receive corun terminal-job callbacks and post them to the pandabot channel."""
0089     try:
0090         if int(request.META.get('CONTENT_LENGTH') or 0) > 8192:
0091             return JsonResponse({'error': 'payload too large'}, status=413)
0092     except (TypeError, ValueError):
0093         pass
0094 
0095     try:
0096         payload = json.loads(request.body.decode('utf-8'))
0097     except (UnicodeDecodeError, json.JSONDecodeError) as exc:
0098         return JsonResponse({'error': f'invalid JSON: {exc}'}, status=400)
0099 
0100     if not isinstance(payload, dict):
0101         return JsonResponse({'error': 'payload must be a JSON object'}, status=400)
0102 
0103     status = payload.get('status')
0104     if status not in {'completed', 'failed', 'cancelled'}:
0105         return JsonResponse({'error': 'ignored non-terminal status'}, status=400)
0106 
0107     try:
0108         driver = _mattermost_driver()
0109         driver.login()
0110         team = driver.teams.get_team_by_name(config('MATTERMOST_TEAM', default='main'))
0111         channel = driver.channels.get_channel_by_name(
0112             team['id'], config('MATTERMOST_CHANNEL', default='pandabot')
0113         )
0114         driver.posts.create_post(options={
0115             'channel_id': channel['id'],
0116             'message': _message_from_payload(payload),
0117         })
0118     except Exception as exc:
0119         logger.exception("Failed to post corun callback to Mattermost")
0120         return JsonResponse({'error': str(exc)}, status=502)
0121 
0122     logger.info(
0123         "Posted corun callback notice for job %s status=%s",
0124         payload.get('job_id'), status,
0125     )
0126     return JsonResponse({'ok': True})