1+ #!/usr/bin/env python3
2+
3+ import argparse
4+ import asyncio
5+ import os
6+ import re
7+ import requests
8+
9+ _API_TOKEN = os .environ ["BUILDKITE_API_TOKEN" ]
10+ _BASE_URL = "https://api.buildkite.com/v2/organizations/"
11+ _BUILDKITE_AUTH_HEADERS = {"Authorization" : f"Bearer { _API_TOKEN } " }
12+
13+ ansi_escape_8bit = re .compile (
14+ r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])"
15+ )
16+
17+
18+ def _build_parser () -> argparse .ArgumentParser :
19+ parser = argparse .ArgumentParser (description = "Combine buildkite logs" )
20+ parser .add_argument (
21+ "pipeline" , help = "Pipeline to combine logs for, ex: bazelbuild/bazel"
22+ )
23+ parser .add_argument (
24+ "build_number" , help = "Build number to combine logs for"
25+ )
26+ return parser
27+
28+
29+ def _get_job_log (
30+ org : str , pipeline : str , build_number : str , job_id : str
31+ ) -> str :
32+ response = requests .get (
33+ f"{ _BASE_URL } /{ org } /pipelines/{ pipeline } /builds/{ build_number } /jobs/{ job_id } /log" ,
34+ headers = _BUILDKITE_AUTH_HEADERS ,
35+ )
36+ response .raise_for_status ()
37+ return ansi_escape_8bit .sub ("" , response .json ()["content" ]).replace (
38+ "\r " , ""
39+ )
40+
41+
42+ async def _main (org : str , pipeline : str , build_number : str ) -> None :
43+ response = requests .get (
44+ f"{ _BASE_URL } /{ org } /pipelines/{ pipeline } /builds/{ build_number } " ,
45+ headers = _BUILDKITE_AUTH_HEADERS ,
46+ )
47+ response .raise_for_status ()
48+
49+ combined_log = ""
50+ build = response .json ()
51+ loop = asyncio .get_event_loop ()
52+ futures = []
53+
54+ for job in build ["jobs" ]:
55+ job_id = str (job ["id" ])
56+ futures .append (
57+ loop .run_in_executor (
58+ None , _get_job_log , org , pipeline , build_number , job_id
59+ )
60+ )
61+
62+ for future in futures :
63+ combined_log += await future
64+ combined_log += "\n "
65+
66+ with open (f"buildkite-build-{ build_number } .log" , "w" ) as f :
67+ f .write (combined_log )
68+ f .write ("\n " )
69+
70+
71+ if __name__ == "__main__" :
72+ args = _build_parser ().parse_args ()
73+ org , pipeline = args .pipeline .split ("/" , 1 )
74+ loop = asyncio .get_event_loop ()
75+ loop .run_until_complete (_main (org , pipeline , args .build_number ))
0 commit comments