1# Copyright 2021 Google LLC 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"""Module for dealing with the GitHub API. This is different from 15github_actions_toolkit which only deals with the actions API. We need to use 16both.""" 17import logging 18import os 19import sys 20 21import requests 22 23import filestore 24 25# pylint: disable=wrong-import-position,import-error 26 27sys.path.append( 28 os.path.join(__file__, os.path.pardir, os.path.pardir, os.path.pardir, 29 os.path.pardir)) 30import retry 31 32_MAX_ITEMS_PER_PAGE = 100 33 34_GET_ATTEMPTS = 3 35_GET_BACKOFF = 1 36 37 38def get_http_auth_headers(config): 39 """Returns HTTP headers for authentication to the API.""" 40 authorization = f'token {config.token}' 41 return { 42 'Authorization': authorization, 43 'Accept': 'application/vnd.github.v3+json' 44 } 45 46 47def _get_artifacts_list_api_url(repo_owner, repo_name): 48 """Returns the artifacts_api_url for |repo_name| owned by |repo_owner|.""" 49 return (f'https://api.github.com/repos/{repo_owner}/' 50 f'{repo_name}/actions/artifacts') 51 52 53@retry.wrap(_GET_ATTEMPTS, _GET_BACKOFF) 54def _do_get_request(*args, **kwargs): 55 """Wrapped version of requests.get that does retries.""" 56 return requests.get(*args, **kwargs) 57 58 59def _get_items(url, headers): 60 """Generator that gets and yields items from a GitHub API endpoint (specified 61 by |URL|) sending |headers| with the get request.""" 62 # Github API response pages are 1-indexed. 63 page_counter = 1 64 65 # Set to infinity so we run loop at least once. 66 total_num_items = float('inf') 67 68 item_num = 0 69 while item_num < total_num_items: 70 params = {'per_page': _MAX_ITEMS_PER_PAGE, 'page': str(page_counter)} 71 response = _do_get_request(url, params=params, headers=headers) 72 response_json = response.json() 73 if not response.status_code == 200: 74 # Check that request was successful. 75 logging.error('Request to %s failed. Code: %d. Response: %s', 76 response.request.url, response.status_code, response_json) 77 raise filestore.FilestoreError('Github API request failed.') 78 79 if total_num_items == float('inf'): 80 # Set proper total_num_items 81 total_num_items = response_json['total_count'] 82 83 # Get the key for the items we are after. 84 keys = [key for key in response_json.keys() if key != 'total_count'] 85 assert len(keys) == 1, keys 86 items_key = keys[0] 87 88 for item in response_json[items_key]: 89 yield item 90 item_num += 1 91 92 page_counter += 1 93 94 95def find_artifact(artifact_name, artifacts): 96 """Find the artifact with the name |artifact_name| in |artifacts|.""" 97 for artifact in artifacts: 98 # TODO(metzman): Handle multiple by making sure we download the latest. 99 if artifact['name'] == artifact_name and not artifact['expired']: 100 return artifact 101 return None 102 103 104def list_artifacts(owner, repo, headers): 105 """Returns a generator of all the artifacts for |owner|/|repo|.""" 106 url = _get_artifacts_list_api_url(owner, repo) 107 logging.debug('Getting artifacts from: %s', url) 108 return _get_items(url, headers) 109