Implement Bazel Auto Sheriff (#953) (Second try)
This reverts commit 689368dcb43aa76f844e227c2c680a82d16ea0d3.
diff --git a/buildkite/bazelci.py b/buildkite/bazelci.py
index 1eade27..7d27414 100755
--- a/buildkite/bazelci.py
+++ b/buildkite/bazelci.py
@@ -25,6 +25,7 @@
import os.path
import random
import re
+import requests
from shutil import copyfile
import shutil
import stat
@@ -565,11 +566,20 @@
"https://api.buildkite.com/v2/organizations/{}/pipelines/{}/builds/{}"
)
+ _NEW_BUILD_URL_TEMPLATE = (
+ "https://api.buildkite.com/v2/organizations/{}/pipelines/{}/builds"
+ )
+
+ _RETRY_JOB_URL_TEMPLATE = (
+ "https://api.buildkite.com/v2/organizations/{}/pipelines/{}/builds/{}/jobs/{}/retry"
+ )
+
def __init__(self, org, pipeline):
self._org = org
self._pipeline = pipeline
self._token = self._get_buildkite_token()
+
def _get_buildkite_token(self):
return decrypt_token(
encrypted_token=self._ENCRYPTED_BUILDKITE_API_TESTING_TOKEN
@@ -580,25 +590,174 @@
else "buildkite-untrusted-api-token",
)
- def _open_url(self, url):
+
+ def _open_url(self, url, params = []):
try:
+ params_str = "".join("&{}={}".format(k, v) for k, v in params)
return (
- urllib.request.urlopen("{}?access_token={}".format(url, self._token))
+ urllib.request.urlopen("{}?access_token={}{}".format(url, self._token, params_str))
.read()
.decode("utf-8")
)
except urllib.error.HTTPError as ex:
raise BuildkiteException("Failed to open {}: {} - {}".format(url, ex.code, ex.reason))
+
def get_build_info(self, build_number):
+ """Get build info for a pipeline with a given build number
+ See https://buildkite.com/docs/apis/rest-api/builds#get-a-build
+
+ Parameters
+ ----------
+ build_number : the build number
+
+ Returns
+ -------
+ dict
+ the metadata for the build
+ """
url = self._BUILD_STATUS_URL_TEMPLATE.format(self._org, self._pipeline, build_number)
output = self._open_url(url)
return json.loads(output)
+
+ def get_build_info_list(self, params):
+ """Get a list of build infos for this pipeline
+ See https://buildkite.com/docs/apis/rest-api/builds#list-builds-for-a-pipeline
+
+ Parameters
+ ----------
+ params : the parameters to filter the result
+
+ Returns
+ -------
+ list of dict
+ the metadata for a list of builds
+ """
+ url = self._BUILD_STATUS_URL_TEMPLATE.format(self._org, self._pipeline, "")
+ output = self._open_url(url, params)
+ return json.loads(output)
+
+
def get_build_log(self, job):
return self._open_url(job["raw_log_url"])
+ @staticmethod
+ def _check_response(response, expected_status_code):
+ if response.status_code != expected_status_code:
+ eprint("Exit code:", response.status_code)
+ eprint("Response:\n", response.text)
+ response.raise_for_status()
+
+
+ def trigger_new_build(self, commit, message = None, env = {}):
+ """Trigger a new build at a given commit and return the build metadata.
+ See https://buildkite.com/docs/apis/rest-api/builds#create-a-build
+
+ Parameters
+ ----------
+ commit : the commit we want to build at
+ message : the message we should as the build titile
+ env : (optional) the environment variables to set
+
+ Returns
+ -------
+ dict
+ the metadata for the build
+ """
+ url = self._NEW_BUILD_URL_TEMPLATE.format(self._org, self._pipeline)
+ data = {
+ "commit": commit,
+ "branch": "master",
+ "message": message if message else f"Trigger build at {commit}",
+ "env": env,
+ }
+ response = requests.post(url + "?access_token=" + self._token, json = data)
+ BuildkiteClient._check_response(response, requests.codes.created)
+ return json.loads(response.text)
+
+
+ def trigger_job_retry(self, build_number, job_id):
+ """Trigger a job retry and return the job metadata.
+ See https://buildkite.com/docs/apis/rest-api/jobs#retry-a-job
+
+ Parameters
+ ----------
+ build_number : the number of the build we want to retry
+ job_id : the id of the job we want to retry
+
+ Returns
+ -------
+ dict
+ the metadata for the job
+ """
+ url = self._RETRY_JOB_URL_TEMPLATE.format(self._org, self._pipeline, build_number, job_id)
+ response = requests.put(url + "?access_token=" + self._token)
+ BuildkiteClient._check_response(response, requests.codes.ok)
+ return json.loads(response.text)
+
+
+ def wait_job_to_finish(self, build_number, job_id, interval_time=30, logger=None):
+ """Wait a job to finish and return the job metadata
+
+ Parameters
+ ----------
+ build_number : the number of the build we want to wait
+ job_id : the id of the job we want to wait
+ interval_time : (optional) the interval time to check the build status, default to 30s
+ logger : (optional) a logger to report progress
+
+ Returns
+ -------
+ dict
+ the latest metadata for the job
+ """
+ t = 0
+ build_info = self.get_build_info(build_number)
+ while True:
+ for job in build_info["jobs"]:
+ if job["id"] == job_id:
+ state = job["state"]
+ if state != "scheduled" and state != "running" and state != "assigned":
+ return job
+ break
+ else:
+ raise BuildkiteException(f"job id {job_id} doesn't exist in build " + build_info["web_url"])
+ url = build_info["web_url"]
+ if logger:
+ logger.log(f"Waiting for {url}, waited {t} seconds...")
+ time.sleep(interval_time)
+ t += interval_time
+ build_info = self.get_build_info(build_number)
+
+
+ def wait_build_to_finish(self, build_number, interval_time=30, logger=None):
+ """Wait a build to finish and return the build metadata
+
+ Parameters
+ ----------
+ build_number : the number of the build we want to wait
+ interval_time : (optional) the interval time to check the build status, default to 30s
+ logger : (optional) a logger to report progress
+
+ Returns
+ -------
+ dict
+ the latest metadata for the build
+ """
+ t = 0
+ build_info = self.get_build_info(build_number)
+ while build_info["state"] == "scheduled" or build_info["state"] == "running":
+ url = build_info["web_url"]
+ if logger:
+ logger.log(f"Waiting for {url}, waited {t} seconds...")
+ time.sleep(interval_time)
+ t += interval_time
+ build_info = self.get_build_info(build_number)
+ return build_info
+
+
def decrypt_token(encrypted_token, kms_key):
return (
subprocess.check_output(
@@ -1719,8 +1878,9 @@
return process.stdout
-def execute_command(args, shell=False, fail_if_nonzero=True, cwd=None):
- eprint(" ".join(args))
+def execute_command(args, shell=False, fail_if_nonzero=True, cwd=None, print_output=True):
+ if print_output:
+ eprint(" ".join(args))
return subprocess.run(
args, shell=shell, check=fail_if_nonzero, env=os.environ, cwd=cwd
).returncode