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