Skip to content

Commit 12b404f

Browse files
authored
Merge pull request #15151 from ethereum/gh-token-download-benchmarks-script
Add authentication header to circleci api
2 parents 53278ea + fea0c75 commit 12b404f

File tree

4 files changed

+89
-25
lines changed

4 files changed

+89
-25
lines changed

.circleci/config.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,19 @@ jobs:
15331533
- run:
15341534
name: Summarize reports
15351535
command: cat reports/externalTests/all-benchmarks.json | scripts/externalTests/summarize_benchmarks.sh > reports/externalTests/summarized-benchmarks.json
1536+
- store_artifacts:
1537+
path: reports/externalTests/all-benchmarks.json
1538+
- store_artifacts:
1539+
path: reports/externalTests/summarized-benchmarks.json
1540+
- run:
1541+
name: Check CircleCI token presence; Skip remaining steps if the token is not present.
1542+
command: |
1543+
# NOTE: download_benchmarks.py requires CIRCLECI_TOKEN environment variable to be set to
1544+
# a valid CircleCI API token to download the benchmark artifacts.
1545+
if [[ -z "$CIRCLECI_TOKEN" ]]; then
1546+
echo "Skipping download benchmarks..."
1547+
circleci-agent step halt
1548+
fi
15361549
- run:
15371550
name: Download reports from base branch
15381551
command: |
@@ -1577,10 +1590,6 @@ jobs:
15771590
base-branch/all-benchmarks-*.json \
15781591
all-benchmarks.json > diff/benchmark-diff-all-table-inplace-absolute.md
15791592
fi
1580-
- store_artifacts:
1581-
path: reports/externalTests/all-benchmarks.json
1582-
- store_artifacts:
1583-
path: reports/externalTests/summarized-benchmarks.json
15841593
- store_artifacts:
15851594
path: reports/externalTests/diff/
15861595
- store_artifacts:

scripts/common/rest_api_helpers.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from os import environ
12
from pathlib import Path
23
from typing import List, Mapping, Optional
34
import functools
@@ -42,13 +43,13 @@ class FileAlreadyExists(APIHelperError):
4243
pass
4344

4445

45-
def query_api(url: str, params: Mapping[str, str], debug_requests=False) -> dict:
46+
def query_api(url: str, params: Mapping[str, str], headers: dict, debug_requests=False) -> dict:
4647
if debug_requests:
4748
print(f'REQUEST URL: {url}')
4849
if len(params) > 0:
4950
print(f'QUERY: {params}')
5051

51-
response = requests.get(url, params=params, timeout=60)
52+
response = requests.get(url, params=params, headers=headers, timeout=60)
5253
response.raise_for_status()
5354

5455
if debug_requests:
@@ -63,11 +64,11 @@ def query_api(url: str, params: Mapping[str, str], debug_requests=False) -> dict
6364
return response.json()
6465

6566

66-
def download_file(url: str, target_path: Path, overwrite=False):
67+
def download_file(url: str, target_path: Path, headers: dict, overwrite=False):
6768
if not overwrite and target_path.exists():
6869
raise FileAlreadyExists(f"Refusing to overwrite existing file: '{target_path}'.")
6970

70-
with requests.get(url, stream=True, timeout=60) as request:
71+
with requests.get(url, headers, stream=True, timeout=60) as request:
7172
with open(target_path, 'wb') as target_file:
7273
shutil.copyfileobj(request.raw, target_file)
7374

@@ -86,6 +87,7 @@ def pull_request(self, pr_id: int) -> dict:
8687
return query_api(
8788
f'{self.BASE_URL}/repos/{self.project_slug}/pulls/{pr_id}',
8889
{},
90+
{},
8991
self.debug_requests
9092
)
9193

@@ -108,11 +110,12 @@ def paginated_query_api_iterator(self, url: str, params: Mapping[str, str], max_
108110

109111
page_count = 0
110112
next_page_token = None
113+
headers = {'Circle-Token': str(environ.get('CIRCLECI_TOKEN'))} if 'CIRCLECI_TOKEN' in environ else {}
111114
while max_pages is None or page_count < max_pages:
112115
if next_page_token is not None:
113116
params = {**params, 'page-token': next_page_token}
114117

115-
json_response = query_api(url, params, self.debug_requests)
118+
json_response = query_api(url, params, headers, self.debug_requests)
116119

117120
yield json_response['items']
118121
next_page_token = json_response['next_page_token']

scripts/externalTests/download_benchmarks.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from argparse import ArgumentParser, Namespace
44
from enum import Enum, unique
5+
from os import environ
56
from pathlib import Path
7+
from textwrap import dedent
68
from typing import Mapping, Optional
79
import sys
810

@@ -28,8 +30,13 @@ class Status(Enum):
2830

2931
def process_commandline() -> Namespace:
3032
script_description = (
31-
"Downloads benchmark results attached as artifacts to the c_ext_benchmarks job on CircleCI. "
32-
"If no options are specified, downloads results for the currently checked out git branch."
33+
"""
34+
Downloads benchmark results attached as artifacts to the c_ext_benchmarks job on CircleCI.
35+
If no options are specified, downloads results for the currently checked out git branch.
36+
37+
The script requires the CIRCLECI_TOKEN environment variable to be set with a valid CircleCI API token.
38+
You can generate a new token at https://app.circleci.com/settings/user/tokens.
39+
"""
3340
)
3441

3542
parser = ArgumentParser(description=script_description)
@@ -96,9 +103,11 @@ def download_benchmark_artifact(
96103
print(f"Missing artifact: {artifact_path}.")
97104
return False
98105

106+
headers = {'Circle-Token': str(environ.get('CIRCLECI_TOKEN'))} if 'CIRCLECI_TOKEN' in environ else {}
99107
download_file(
100108
artifacts[artifact_path]['url'],
101109
Path(f'{benchmark_name}-{branch}-{commit_hash[:8]}.json'),
110+
headers,
102111
overwrite,
103112
)
104113

@@ -162,6 +171,13 @@ def download_benchmarks(
162171

163172
def main():
164173
try:
174+
if 'CIRCLECI_TOKEN' not in environ:
175+
raise RuntimeError(
176+
dedent(""" \
177+
CIRCLECI_TOKEN environment variable required but not set.
178+
Please generate a new token at https://app.circleci.com/settings/user/tokens and set CIRCLECI_TOKEN.
179+
""")
180+
)
165181
options = process_commandline()
166182
return download_benchmarks(
167183
options.branch,

test/scripts/test_externalTests_benchmark_downloader.py

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from pathlib import Path
44
from unittest import TestCase
55
from unittest.mock import call, Mock, patch
6+
import os
7+
import requests
68

79
# NOTE: This test file file only works with scripts/ added to PYTHONPATH so pylint can't find the imports
810
# pragma pylint: disable=import-error
@@ -31,7 +33,7 @@ def _git_run_command_mock(command):
3133
"If you have updated the code, please remember to add matching command fixtures above."
3234
)
3335

34-
def _requests_get_mock(url, params, timeout):
36+
def _requests_get_mock(url, params, headers, timeout):
3537
response_mock = Mock()
3638

3739
if url == 'https://api.github.com/repos/ethereum/solidity/pulls/12818':
@@ -155,25 +157,37 @@ def _requests_get_mock(url, params, timeout):
155157
return response_mock
156158

157159
if url == 'https://circleci.com/api/v2/project/gh/ethereum/solidity/1018023/artifacts':
158-
response_mock.json.return_value = {
159-
"next_page_token": None,
160-
"items": [
161-
{
162-
"path": "reports/externalTests/all-benchmarks.json",
163-
"url": "https://circle-artifacts.com/0/reports/externalTests/all-benchmarks.json"
164-
},
165-
{
166-
"path": "reports/externalTests/summarized-benchmarks.json",
167-
"url": "https://circle-artifacts.com/0/reports/externalTests/summarized-benchmarks.json"
168-
}
169-
]
170-
}
160+
if (
161+
os.environ.get('CIRCLECI_TOKEN') == 'valid_token' and
162+
headers.get('Circle-Token') == os.environ.get('CIRCLECI_TOKEN')
163+
):
164+
response_mock.json.return_value = {
165+
"next_page_token": None,
166+
"items": [
167+
{
168+
"path": "reports/externalTests/all-benchmarks.json",
169+
"url": "https://circle-artifacts.com/0/reports/externalTests/all-benchmarks.json"
170+
},
171+
{
172+
"path": "reports/externalTests/summarized-benchmarks.json",
173+
"url": "https://circle-artifacts.com/0/reports/externalTests/summarized-benchmarks.json"
174+
}
175+
]
176+
}
177+
else:
178+
response_mock.status_code = 401
179+
response_mock.json.return_value = {
180+
"message": "Unauthorized"
181+
}
182+
error = requests.exceptions.HTTPError(f"401 Client Error: Unauthorized for url: {url}")
183+
response_mock.raise_for_status.side_effect = error
171184
return response_mock
172185

173186
raise RuntimeError(
174187
"The test tried to perform an unexpected GET request.\n"
175188
f"URL: {url}\n" +
176189
(f"query: {params}\n" if len(params) > 0 else "") +
190+
(f"headers: {headers}\n" if len(headers) > 0 else "") +
177191
f"timeout: {timeout}\n" +
178192
"If you have updated the code, please remember to add matching response fixtures above."
179193
)
@@ -186,17 +200,20 @@ def setUp(self):
186200
@patch('externalTests.download_benchmarks.download_file')
187201
@patch('requests.get', _requests_get_mock)
188202
@patch('common.git_helpers.run_git_command',_git_run_command_mock)
203+
@patch.dict(os.environ, {'CIRCLECI_TOKEN': 'valid_token'})
189204
def test_download_benchmarks(download_file_mock):
190205
download_benchmarks(None, None, None, silent=True)
191206
download_file_mock.assert_has_calls([
192207
call(
193208
'https://circle-artifacts.com/0/reports/externalTests/summarized-benchmarks.json',
194209
Path('summarized-benchmarks-benchmark-downloader-fa1ddc6f.json'),
210+
{'Circle-Token': 'valid_token'},
195211
False
196212
),
197213
call(
198214
'https://circle-artifacts.com/0/reports/externalTests/all-benchmarks.json',
199215
Path('all-benchmarks-benchmark-downloader-fa1ddc6f.json'),
216+
{'Circle-Token': 'valid_token'},
200217
False
201218
),
202219
])
@@ -205,17 +222,20 @@ def test_download_benchmarks(download_file_mock):
205222
@patch('externalTests.download_benchmarks.download_file')
206223
@patch('requests.get', _requests_get_mock)
207224
@patch('common.git_helpers.run_git_command',_git_run_command_mock)
225+
@patch.dict(os.environ, {'CIRCLECI_TOKEN': 'valid_token'})
208226
def test_download_benchmarks_branch(download_file_mock):
209227
download_benchmarks('develop', None, None, silent=True)
210228
download_file_mock.assert_has_calls([
211229
call(
212230
'https://circle-artifacts.com/0/reports/externalTests/summarized-benchmarks.json',
213231
Path('summarized-benchmarks-develop-43f29c00.json'),
232+
{'Circle-Token': 'valid_token'},
214233
False
215234
),
216235
call(
217236
'https://circle-artifacts.com/0/reports/externalTests/all-benchmarks.json',
218237
Path('all-benchmarks-develop-43f29c00.json'),
238+
{'Circle-Token': 'valid_token'},
219239
False
220240
),
221241
])
@@ -224,17 +244,20 @@ def test_download_benchmarks_branch(download_file_mock):
224244
@patch('externalTests.download_benchmarks.download_file')
225245
@patch('requests.get', _requests_get_mock)
226246
@patch('common.git_helpers.run_git_command',_git_run_command_mock)
247+
@patch.dict(os.environ, {'CIRCLECI_TOKEN': 'valid_token'})
227248
def test_download_benchmarks_pr(download_file_mock):
228249
download_benchmarks(None, 12818, None, silent=True)
229250
download_file_mock.assert_has_calls([
230251
call(
231252
'https://circle-artifacts.com/0/reports/externalTests/summarized-benchmarks.json',
232253
Path('summarized-benchmarks-benchmark-downloader-fa1ddc6f.json'),
254+
{'Circle-Token': 'valid_token'},
233255
False
234256
),
235257
call(
236258
'https://circle-artifacts.com/0/reports/externalTests/all-benchmarks.json',
237259
Path('all-benchmarks-benchmark-downloader-fa1ddc6f.json'),
260+
{'Circle-Token': 'valid_token'},
238261
False
239262
),
240263
])
@@ -243,17 +266,30 @@ def test_download_benchmarks_pr(download_file_mock):
243266
@patch('externalTests.download_benchmarks.download_file')
244267
@patch('requests.get', _requests_get_mock)
245268
@patch('common.git_helpers.run_git_command',_git_run_command_mock)
269+
@patch.dict(os.environ, {'CIRCLECI_TOKEN': 'valid_token'})
246270
def test_download_benchmarks_base_of_pr(download_file_mock):
247271
download_benchmarks(None, None, 12818, silent=True)
248272
download_file_mock.assert_has_calls([
249273
call(
250274
'https://circle-artifacts.com/0/reports/externalTests/summarized-benchmarks.json',
251275
Path('summarized-benchmarks-develop-43f29c00.json'),
276+
{'Circle-Token': 'valid_token'},
252277
False
253278
),
254279
call(
255280
'https://circle-artifacts.com/0/reports/externalTests/all-benchmarks.json',
256281
Path('all-benchmarks-develop-43f29c00.json'),
282+
{'Circle-Token': 'valid_token'},
257283
False
258284
),
259285
])
286+
287+
# NOTE: No circleci token is set in the environment
288+
@patch('externalTests.download_benchmarks.download_file')
289+
@patch('requests.get', _requests_get_mock)
290+
@patch('common.git_helpers.run_git_command',_git_run_command_mock)
291+
def test_download_benchmarks_unauthorized_request(self, _):
292+
with self.assertRaises(requests.exceptions.HTTPError) as manager:
293+
download_benchmarks(None, None, None, silent=True)
294+
295+
self.assertIn('401 Client Error: Unauthorized', str(manager.exception))

0 commit comments

Comments
 (0)