2020-06-03 13:40:22 +02:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import subprocess
|
2020-11-03 15:51:59 +01:00
|
|
|
import urllib.parse
|
2020-06-03 13:40:22 +02:00
|
|
|
from typing import Optional
|
2020-11-25 15:29:50 +01:00
|
|
|
from benedict import benedict
|
2020-10-12 16:25:23 +02:00
|
|
|
|
|
|
|
import backoff
|
2020-10-08 14:32:40 +02:00
|
|
|
import requests
|
2020-06-03 13:40:22 +02:00
|
|
|
|
2020-11-03 15:51:59 +01:00
|
|
|
context_style = {}
|
2020-11-25 15:29:50 +01:00
|
|
|
previous_context = 'default'
|
2020-11-03 15:51:59 +01:00
|
|
|
styles = ['default', 'info', 'success', 'warning', 'error']
|
|
|
|
|
2020-07-23 09:50:06 +02:00
|
|
|
|
2020-06-03 13:40:22 +02:00
|
|
|
def upload_file(base_dir: str, file: str):
|
|
|
|
"""
|
|
|
|
Uploads artifact to buildkite and returns URL to it
|
|
|
|
"""
|
|
|
|
r = subprocess.run(f'buildkite-agent artifact upload "{file}"', shell=True, capture_output=True, cwd=base_dir)
|
|
|
|
logging.debug(f'upload-artifact {r}')
|
|
|
|
match = re.search('Uploading artifact ([^ ]*) ', r.stderr.decode())
|
|
|
|
logging.debug(f'match {match}')
|
|
|
|
if match:
|
|
|
|
url = f'https://buildkite.com/organizations/llvm-project/pipelines/premerge-checks/builds/{os.getenv("BUILDKITE_BUILD_NUMBER")}/jobs/{os.getenv("BUILDKITE_JOB_ID")}/artifacts/{match.group(1)}'
|
|
|
|
logging.info(f'uploaded {file} to {url}')
|
|
|
|
return url
|
|
|
|
else:
|
|
|
|
logging.warning(f'could not find artifact {base_dir}/{file}')
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2021-05-03 21:07:00 +02:00
|
|
|
def set_metadata(key: str, value: str):
|
|
|
|
r = subprocess.run(f'buildkite-agent meta-data set "{key}" "{value}"', shell=True, capture_output=True)
|
|
|
|
if r.returncode != 0:
|
|
|
|
logging.warning(r)
|
|
|
|
|
|
|
|
|
2020-11-25 15:29:50 +01:00
|
|
|
def annotate(message: str, style: str = 'default', context: Optional[str] = None, append: bool = True):
|
2020-11-03 15:51:59 +01:00
|
|
|
"""
|
|
|
|
Adds an annotation for that currently running build.
|
|
|
|
Note that last `style` applied to the same `context` takes precedence.
|
|
|
|
"""
|
2020-11-25 15:29:50 +01:00
|
|
|
global previous_context, styles, context_style
|
2020-11-03 15:51:59 +01:00
|
|
|
if style not in styles:
|
|
|
|
style = 'default'
|
2020-11-25 15:29:50 +01:00
|
|
|
if context is None:
|
|
|
|
context = previous_context
|
|
|
|
previous_context = context
|
2020-11-03 15:51:59 +01:00
|
|
|
# Pick most severe style so far.
|
|
|
|
context_style.setdefault(context, 0)
|
|
|
|
context_style[context] = max(styles.index(style), context_style[context])
|
|
|
|
style = styles[context_style[context]]
|
|
|
|
if append:
|
|
|
|
message += '\n\n'
|
2020-11-25 15:29:50 +01:00
|
|
|
cmd = ['buildkite-agent', 'annotate', message, '--style', style, '--context', context]
|
|
|
|
if append:
|
|
|
|
cmd.append('--append')
|
|
|
|
r = subprocess.run(cmd, capture_output=True)
|
2020-11-03 15:51:59 +01:00
|
|
|
logging.debug(f'annotate call {r}')
|
|
|
|
if r.returncode != 0:
|
2020-11-25 15:29:50 +01:00
|
|
|
logging.warning(r)
|
2020-11-03 15:51:59 +01:00
|
|
|
|
|
|
|
|
|
|
|
def feedback_url():
|
|
|
|
title = f"buildkite build {os.getenv('BUILDKITE_PIPELINE_SLUG')} {os.getenv('BUILDKITE_BUILD_NUMBER')}"
|
|
|
|
return f'https://github.com/google/llvm-premerge-checks/issues/new?assignees=&labels=bug' \
|
|
|
|
f'&template=bug_report.md&title={urllib.parse.quote(title)}'
|
|
|
|
|
|
|
|
|
2020-10-08 14:32:40 +02:00
|
|
|
class BuildkiteApi:
|
|
|
|
def __init__(self, token: str, organization: str):
|
|
|
|
self.token = token
|
|
|
|
self.organization = organization
|
|
|
|
|
|
|
|
def get_build(self, pipeline: str, build_number: str):
|
|
|
|
# https://buildkite.com/docs/apis/rest-api/builds#get-a-build
|
2020-11-25 15:29:50 +01:00
|
|
|
return benedict(self.get(f'https://api.buildkite.com/v2/organizations/{self.organization}/pipelines/{pipeline}/builds/{build_number}').json())
|
|
|
|
|
2021-05-04 11:24:08 +02:00
|
|
|
def list_running_revision_builds(self, pipeline: str, rev: str):
|
|
|
|
return self.get(f'https://api.buildkite.com/v2/organizations/{self.organization}/pipelines/{pipeline}/builds?state[]=scheduled&state[]=running&meta_data[ph_buildable_revision]={rev}').json()
|
|
|
|
|
2020-11-25 15:29:50 +01:00
|
|
|
@backoff.on_exception(backoff.expo, Exception, max_tries=3, logger='', factor=3)
|
|
|
|
def get(self, url: str):
|
|
|
|
authorization = f'Bearer {self.token}'
|
|
|
|
response = requests.get(url, allow_redirects=True, headers={'Authorization': authorization})
|
2020-10-08 14:32:40 +02:00
|
|
|
if response.status_code != 200:
|
2020-11-25 15:29:50 +01:00
|
|
|
raise Exception(f'Buildkite responded with non-OK status: {response.status_code}')
|
|
|
|
return response
|
2020-10-08 14:32:40 +02:00
|
|
|
|
2021-05-04 11:24:08 +02:00
|
|
|
# cancel a build. 'build' is a json object returned by API.
|
|
|
|
def cancel_build(self, build):
|
|
|
|
build = benedict(build)
|
|
|
|
url = f'https://api.buildkite.com/v2/organizations/{self.organization}/pipelines/{build.get("pipeline.slug")}/builds/{build.get("number")}/cancel'
|
|
|
|
authorization = f'Bearer {self.token}'
|
|
|
|
response = requests.put(url, headers={'Authorization': authorization})
|
|
|
|
if response.status_code != 200:
|
|
|
|
raise Exception(f'Buildkite responded with non-OK status: {response.status_code}')
|
|
|
|
|
2020-10-08 14:32:40 +02:00
|
|
|
|
2020-06-03 13:40:22 +02:00
|
|
|
def format_url(url: str, name: Optional[str] = None):
|
|
|
|
if name is None:
|
|
|
|
name = url
|
|
|
|
return f"\033]1339;url='{url}';content='{name}'\a\n"
|
2020-11-25 15:29:50 +01:00
|
|
|
|
|
|
|
|
|
|
|
def strip_emojis(s: str) -> str:
|
|
|
|
return re.sub(r':[^:]+:', '', s).strip()
|