• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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