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 15from __future__ import print_function 16import os 17import sys 18import json 19import time 20import datetime 21import traceback 22 23import requests 24import jwt 25 26_GITHUB_API_PREFIX = 'https://api.github.com' 27_GITHUB_REPO = 'grpc/grpc' 28_GITHUB_APP_ID = 22338 29_INSTALLATION_ID = 519109 30 31_ACCESS_TOKEN_CACHE = None 32_ACCESS_TOKEN_FETCH_RETRIES = 6 33_ACCESS_TOKEN_FETCH_RETRIES_INTERVAL_S = 15 34 35 36def _jwt_token(): 37 github_app_key = open( 38 os.path.join(os.environ['KOKORO_KEYSTORE_DIR'], 39 '73836_grpc_checks_private_key'), 'rb').read() 40 return jwt.encode( 41 { 42 'iat': int(time.time()), 43 'exp': int(time.time() + 60 * 10), # expire in 10 minutes 44 'iss': _GITHUB_APP_ID, 45 }, 46 github_app_key, 47 algorithm='RS256') 48 49 50def _access_token(): 51 global _ACCESS_TOKEN_CACHE 52 if _ACCESS_TOKEN_CACHE == None or _ACCESS_TOKEN_CACHE['exp'] < time.time(): 53 for i in range(_ACCESS_TOKEN_FETCH_RETRIES): 54 resp = requests.post( 55 url='https://api.github.com/app/installations/%s/access_tokens' 56 % _INSTALLATION_ID, 57 headers={ 58 'Authorization': 'Bearer %s' % _jwt_token().decode('ASCII'), 59 'Accept': 'application/vnd.github.machine-man-preview+json', 60 }) 61 62 try: 63 _ACCESS_TOKEN_CACHE = { 64 'token': resp.json()['token'], 65 'exp': time.time() + 60 66 } 67 break 68 except (KeyError, ValueError) as e: 69 traceback.print_exc(e) 70 print('HTTP Status %d %s' % (resp.status_code, resp.reason)) 71 print("Fetch access token from Github API failed:") 72 print(resp.text) 73 if i != _ACCESS_TOKEN_FETCH_RETRIES - 1: 74 print('Retrying after %.2f second.' % 75 _ACCESS_TOKEN_FETCH_RETRIES_INTERVAL_S) 76 time.sleep(_ACCESS_TOKEN_FETCH_RETRIES_INTERVAL_S) 77 else: 78 print("error: Unable to fetch access token, exiting...") 79 sys.exit(0) 80 81 return _ACCESS_TOKEN_CACHE['token'] 82 83 84def _call(url, method='GET', json=None): 85 if not url.startswith('https://'): 86 url = _GITHUB_API_PREFIX + url 87 headers = { 88 'Authorization': 'Bearer %s' % _access_token(), 89 'Accept': 'application/vnd.github.antiope-preview+json', 90 } 91 return requests.request(method=method, url=url, headers=headers, json=json) 92 93 94def _latest_commit(): 95 resp = _call( 96 '/repos/%s/pulls/%s/commits' % 97 (_GITHUB_REPO, os.environ['KOKORO_GITHUB_PULL_REQUEST_NUMBER'])) 98 return resp.json()[-1] 99 100 101def check_on_pr(name, summary, success=True): 102 """Create/Update a check on current pull request. 103 104 The check runs are aggregated by their name, so newer check will update the 105 older check with the same name. 106 107 Requires environment variable 'KOKORO_GITHUB_PULL_REQUEST_NUMBER' to indicate which pull request 108 should be updated. 109 110 Args: 111 name: The name of the check. 112 summary: A str in Markdown to be used as the detail information of the check. 113 success: A bool indicates whether the check is succeed or not. 114 """ 115 if 'KOKORO_GIT_COMMIT' not in os.environ: 116 print('Missing KOKORO_GIT_COMMIT env var: not checking') 117 return 118 if 'KOKORO_KEYSTORE_DIR' not in os.environ: 119 print('Missing KOKORO_KEYSTORE_DIR env var: not checking') 120 return 121 if 'KOKORO_GITHUB_PULL_REQUEST_NUMBER' not in os.environ: 122 print('Missing KOKORO_GITHUB_PULL_REQUEST_NUMBER env var: not checking') 123 return 124 completion_time = str( 125 datetime.datetime.utcnow().replace(microsecond=0).isoformat()) + 'Z' 126 resp = _call('/repos/%s/check-runs' % _GITHUB_REPO, 127 method='POST', 128 json={ 129 'name': name, 130 'head_sha': os.environ['KOKORO_GIT_COMMIT'], 131 'status': 'completed', 132 'completed_at': completion_time, 133 'conclusion': 'success' if success else 'failure', 134 'output': { 135 'title': name, 136 'summary': summary, 137 } 138 }) 139 print('Result of Creating/Updating Check on PR:', 140 json.dumps(resp.json(), indent=2)) 141