1#!/usr/bin/env python3 2# Copyright 2021 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Utilities to file bugs.""" 7 8import base64 9import datetime 10import enum 11import json 12import os 13from typing import Any, Dict, List, Optional 14 15X20_PATH = '/google/data/rw/teams/c-compiler-chrome/prod_bugs' 16 17 18class WellKnownComponents(enum.IntEnum): 19 """A listing of "well-known" components recognized by our infra.""" 20 CrOSToolchainPublic = -1 21 CrOSToolchainPrivate = -2 22 23 24def _WriteBugJSONFile(object_type: str, json_object: Dict[str, Any]): 25 """Writes a JSON file to X20_PATH with the given bug-ish object.""" 26 final_object = { 27 'type': object_type, 28 'value': json_object, 29 } 30 31 # The name of this has two parts: 32 # - An easily sortable time, to provide uniqueness and let our service send 33 # things in the order they were put into the outbox. 34 # - 64 bits of entropy, so two racing bug writes don't clobber the same file. 35 now = datetime.datetime.utcnow().isoformat('T', 'seconds') + 'Z' 36 entropy = base64.urlsafe_b64encode(os.getrandom(8)) 37 entropy_str = entropy.rstrip(b'=').decode('utf-8') 38 file_path = os.path.join(X20_PATH, f'{now}_{entropy_str}.json') 39 40 temp_path = file_path + '.in_progress' 41 try: 42 with open(temp_path, 'w') as f: 43 json.dump(final_object, f) 44 os.rename(temp_path, file_path) 45 except: 46 os.remove(temp_path) 47 raise 48 return file_path 49 50 51def AppendToExistingBug(bug_id: int, body: str): 52 """Sends a reply to an existing bug.""" 53 _WriteBugJSONFile('AppendToExistingBugRequest', { 54 'body': body, 55 'bug_id': bug_id, 56 }) 57 58 59def CreateNewBug(component_id: int, 60 title: str, 61 body: str, 62 assignee: Optional[str] = None, 63 cc: Optional[List[str]] = None): 64 """Sends a request to create a new bug. 65 66 Args: 67 component_id: The component ID to add. Anything from WellKnownComponents 68 also works. 69 title: Title of the bug. Must be nonempty. 70 body: Body of the bug. Must be nonempty. 71 assignee: Assignee of the bug. Must be either an email address, or a 72 "well-known" assignee (detective, mage). 73 cc: A list of emails to add to the CC list. Must either be an email 74 address, or a "well-known" individual (detective, mage). 75 """ 76 obj = { 77 'component_id': component_id, 78 'subject': title, 79 'body': body, 80 } 81 82 if assignee: 83 obj['assignee'] = assignee 84 85 if cc: 86 obj['cc'] = cc 87 88 _WriteBugJSONFile('FileNewBugRequest', obj) 89 90 91def SendCronjobLog(cronjob_name: str, failed: bool, message: str): 92 """Sends the record of a cronjob to our bug infra. 93 94 cronjob_name: The name of the cronjob. Expected to remain consistent over 95 time. 96 failed: Whether the job failed or not. 97 message: Any seemingly relevant context. This is pasted verbatim in a bug, if 98 the cronjob infra deems it worthy. 99 """ 100 _WriteBugJSONFile('ChrotomationCronjobUpdate', { 101 'name': cronjob_name, 102 'message': message, 103 'failed': failed, 104 }) 105