check_on_pr.py
Go to the documentation of this file.
1 # Copyright 2018 The gRPC Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 from __future__ import print_function
16 
17 import datetime
18 import json
19 import os
20 import sys
21 import time
22 import traceback
23 
24 import jwt
25 import requests
26 
27 _GITHUB_API_PREFIX = 'https://api.github.com'
28 _GITHUB_REPO = 'grpc/grpc'
29 _GITHUB_APP_ID = 22338
30 _INSTALLATION_ID = 519109
31 
32 _ACCESS_TOKEN_CACHE = None
33 _ACCESS_TOKEN_FETCH_RETRIES = 6
34 _ACCESS_TOKEN_FETCH_RETRIES_INTERVAL_S = 15
35 
36 _CHANGE_LABELS = {
37  -1: 'improvement',
38  0: 'none',
39  1: 'low',
40  2: 'medium',
41  3: 'high',
42 }
43 
44 _INCREASE_DECREASE = {
45  -1: 'decrease',
46  0: 'neutral',
47  1: 'increase',
48 }
49 
50 
51 def _jwt_token():
52  github_app_key = open(
53  os.path.join(os.environ['KOKORO_KEYSTORE_DIR'],
54  '73836_grpc_checks_private_key'), 'rb').read()
55  return jwt.encode(
56  {
57  'iat': int(time.time()),
58  'exp': int(time.time() + 60 * 10), # expire in 10 minutes
59  'iss': _GITHUB_APP_ID,
60  },
61  github_app_key,
62  algorithm='RS256')
63 
64 
66  global _ACCESS_TOKEN_CACHE
67  if _ACCESS_TOKEN_CACHE == None or _ACCESS_TOKEN_CACHE['exp'] < time.time():
68  for i in range(_ACCESS_TOKEN_FETCH_RETRIES):
69  resp = requests.post(
70  url='https://api.github.com/app/installations/%s/access_tokens'
71  % _INSTALLATION_ID,
72  headers={
73  'Authorization': 'Bearer %s' % _jwt_token(),
74  'Accept': 'application/vnd.github.machine-man-preview+json',
75  })
76 
77  try:
78  _ACCESS_TOKEN_CACHE = {
79  'token': resp.json()['token'],
80  'exp': time.time() + 60
81  }
82  break
83  except (KeyError, ValueError):
84  traceback.print_exc()
85  print('HTTP Status %d %s' % (resp.status_code, resp.reason))
86  print("Fetch access token from Github API failed:")
87  print(resp.text)
88  if i != _ACCESS_TOKEN_FETCH_RETRIES - 1:
89  print('Retrying after %.2f second.' %
90  _ACCESS_TOKEN_FETCH_RETRIES_INTERVAL_S)
91  time.sleep(_ACCESS_TOKEN_FETCH_RETRIES_INTERVAL_S)
92  else:
93  print("error: Unable to fetch access token, exiting...")
94  sys.exit(0)
95 
96  return _ACCESS_TOKEN_CACHE['token']
97 
98 
99 def _call(url, method='GET', json=None):
100  if not url.startswith('https://'):
101  url = _GITHUB_API_PREFIX + url
102  headers = {
103  'Authorization': 'Bearer %s' % _access_token(),
104  'Accept': 'application/vnd.github.antiope-preview+json',
105  }
106  return requests.request(method=method, url=url, headers=headers, json=json)
107 
108 
110  resp = _call(
111  '/repos/%s/pulls/%s/commits' %
112  (_GITHUB_REPO, os.environ['KOKORO_GITHUB_PULL_REQUEST_NUMBER']))
113  return resp.json()[-1]
114 
115 
116 def check_on_pr(name, summary, success=True):
117  """Create/Update a check on current pull request.
118 
119  The check runs are aggregated by their name, so newer check will update the
120  older check with the same name.
121 
122  Requires environment variable 'KOKORO_GITHUB_PULL_REQUEST_NUMBER' to indicate which pull request
123  should be updated.
124 
125  Args:
126  name: The name of the check.
127  summary: A str in Markdown to be used as the detail information of the check.
128  success: A bool indicates whether the check is succeed or not.
129  """
130  if 'KOKORO_GIT_COMMIT' not in os.environ:
131  print('Missing KOKORO_GIT_COMMIT env var: not checking')
132  return
133  if 'KOKORO_KEYSTORE_DIR' not in os.environ:
134  print('Missing KOKORO_KEYSTORE_DIR env var: not checking')
135  return
136  if 'KOKORO_GITHUB_PULL_REQUEST_NUMBER' not in os.environ:
137  print('Missing KOKORO_GITHUB_PULL_REQUEST_NUMBER env var: not checking')
138  return
139  MAX_SUMMARY_LEN = 65400
140  if len(summary) > MAX_SUMMARY_LEN:
141  # Drop some hints to the log should someone come looking for what really happened!
142  print('Clipping too long summary')
143  print(summary)
144  summary = summary[:MAX_SUMMARY_LEN] + '\n\n\n... CLIPPED (too long)'
145  completion_time = str(
146  datetime.datetime.utcnow().replace(microsecond=0).isoformat()) + 'Z'
147  resp = _call('/repos/%s/check-runs' % _GITHUB_REPO,
148  method='POST',
149  json={
150  'name': name,
151  'head_sha': os.environ['KOKORO_GIT_COMMIT'],
152  'status': 'completed',
153  'completed_at': completion_time,
154  'conclusion': 'success' if success else 'failure',
155  'output': {
156  'title': name,
157  'summary': summary,
158  }
159  })
160  print('Result of Creating/Updating Check on PR:',
161  json.dumps(resp.json(), indent=2))
162 
163 
164 def label_significance_on_pr(name, change, labels=_CHANGE_LABELS):
165  """Add a label to the PR indicating the significance of the check.
166 
167  Requires environment variable 'KOKORO_GITHUB_PULL_REQUEST_NUMBER' to indicate which pull request
168  should be updated.
169 
170  Args:
171  name: The name of the label.
172  value: A str in Markdown to be used as the detail information of the label.
173  """
174  if change < min(list(labels.keys())):
175  change = min(list(labels.keys()))
176  if change > max(list(labels.keys())):
177  change = max(list(labels.keys()))
178  value = labels[change]
179  if 'KOKORO_GIT_COMMIT' not in os.environ:
180  print('Missing KOKORO_GIT_COMMIT env var: not checking')
181  return
182  if 'KOKORO_KEYSTORE_DIR' not in os.environ:
183  print('Missing KOKORO_KEYSTORE_DIR env var: not checking')
184  return
185  if 'KOKORO_GITHUB_PULL_REQUEST_NUMBER' not in os.environ:
186  print('Missing KOKORO_GITHUB_PULL_REQUEST_NUMBER env var: not checking')
187  return
188  existing = _call(
189  '/repos/%s/issues/%s/labels' %
190  (_GITHUB_REPO, os.environ['KOKORO_GITHUB_PULL_REQUEST_NUMBER']),
191  method='GET').json()
192  print('Result of fetching labels on PR:', existing)
193  new = [x['name'] for x in existing if not x['name'].startswith(name + '/')]
194  new.append(name + '/' + value)
195  resp = _call(
196  '/repos/%s/issues/%s/labels' %
197  (_GITHUB_REPO, os.environ['KOKORO_GITHUB_PULL_REQUEST_NUMBER']),
198  method='PUT',
199  json=new)
200  print('Result of setting labels on PR:', resp.text)
201 
202 
203 def label_increase_decrease_on_pr(name, change, significant):
204  if change <= -significant:
205  label_significance_on_pr(name, -1, _INCREASE_DECREASE)
206  elif change >= significant:
207  label_significance_on_pr(name, 1, _INCREASE_DECREASE)
208  else:
209  label_significance_on_pr(name, 0, _INCREASE_DECREASE)
xds_interop_client.str
str
Definition: xds_interop_client.py:487
python_utils.check_on_pr.label_increase_decrease_on_pr
def label_increase_decrease_on_pr(name, change, significant)
Definition: check_on_pr.py:203
capstone.range
range
Definition: third_party/bloaty/third_party/capstone/bindings/python/capstone/__init__.py:6
python_utils.check_on_pr._access_token
def _access_token()
Definition: check_on_pr.py:65
xds_interop_client.int
int
Definition: xds_interop_client.py:113
max
int max
Definition: bloaty/third_party/zlib/examples/enough.c:170
python_utils.check_on_pr._latest_commit
def _latest_commit()
Definition: check_on_pr.py:109
min
#define min(a, b)
Definition: qsort.h:83
python_utils.check_on_pr.label_significance_on_pr
def label_significance_on_pr(name, change, labels=_CHANGE_LABELS)
Definition: check_on_pr.py:164
python_utils.check_on_pr.check_on_pr
def check_on_pr(name, summary, success=True)
Definition: check_on_pr.py:116
read
int read(izstream &zs, T *x, Items items)
Definition: bloaty/third_party/zlib/contrib/iostream2/zstream.h:115
open
#define open
Definition: test-fs.c:46
len
int len
Definition: abseil-cpp/absl/base/internal/low_level_alloc_test.cc:46
python_utils.check_on_pr._call
def _call(url, method='GET', json=None)
Definition: check_on_pr.py:99
python_utils.check_on_pr._jwt_token
def _jwt_token()
Definition: check_on_pr.py:51


grpc
Author(s):
autogenerated on Thu Mar 13 2025 02:58:45