Skip to content

Commit b92fbdf

Browse files
committed
SERVER-42144 Remove use of evergreen rest API in bypass_compile_and_fetch_binaries.py
1 parent a9c7a1b commit b92fbdf

File tree

3 files changed

+122
-147
lines changed

3 files changed

+122
-147
lines changed

buildscripts/bypass_compile_and_fetch_binaries.py

Lines changed: 81 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
#!/usr/bin/env python3
22
"""Bypass compile and fetch binaries."""
33

4-
import argparse
54
import json
65
import logging
76
import os
8-
import re
97
import sys
108
import tarfile
119
from tempfile import TemporaryDirectory
1210
import urllib.error
1311
import urllib.parse
1412
import urllib.request
1513

16-
# pylint: disable=ungrouped-imports
17-
try:
18-
from urllib.parse import urlparse
19-
except ImportError:
20-
from urllib.parse import urlparse # type: ignore
21-
# pylint: enable=ungrouped-imports
14+
import click
2215

16+
from evergreen.api import RetryingEvergreenApi
2317
from git.repo import Repo
2418
import requests
2519
import structlog
2620
from structlog.stdlib import LoggerFactory
2721
import yaml
2822

23+
EVG_CONFIG_FILE = ".evergreen.yml"
24+
2925
# Get relative imports to work when the package is not installed on the PYTHONPATH.
3026
if __name__ == "__main__" and __package__ is None:
3127
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -40,8 +36,8 @@
4036
_IS_WINDOWS = (sys.platform == "win32" or sys.platform == "cygwin")
4137

4238
# If changes are only from files in the bypass_files list or the bypass_directories list, then
43-
# bypass compile, unless they are also found in the requires_compile_directories lists. All
44-
# other file changes lead to compile.
39+
# bypass compile, unless they are also found in the BYPASS_EXTRA_CHECKS_REQUIRED lists. All other
40+
# file changes lead to compile.
4541
BYPASS_WHITELIST = {
4642
"files": {
4743
"etc/evergreen.yml",
@@ -114,19 +110,6 @@ def requests_get_json(url):
114110
raise
115111

116112

117-
def read_evg_config():
118-
"""Attempt to parse the Evergreen configuration from its home location.
119-
120-
Return None if the configuration file wasn't found.
121-
"""
122-
evg_file = os.path.expanduser("~/.evergreen.yml")
123-
if os.path.isfile(evg_file):
124-
with open(evg_file, "r") as fstream:
125-
return yaml.safe_load(fstream)
126-
127-
return None
128-
129-
130113
def write_out_bypass_compile_expansions(patch_file, **expansions):
131114
"""Write out the macro expansions to given file."""
132115
with open(patch_file, "w") as out_file:
@@ -251,16 +234,17 @@ def _file_in_group(filename, group):
251234
return False
252235

253236

254-
def should_bypass_compile(args):
237+
def should_bypass_compile(patch_file, build_variant):
255238
"""
256239
Determine whether the compile stage should be bypassed based on the modified patch files.
257240
258241
We use lists of files and directories to more precisely control which modified patch files will
259242
lead to compile bypass.
260-
:param args: Command line arguments.
243+
:param patch_file: A list of all files modified in patch build.
244+
:param build_variant: Build variant where compile is running.
261245
:returns: True if compile should be bypassed.
262246
"""
263-
with open(args.patchFile, "r") as pch:
247+
with open(patch_file, "r") as pch:
264248
for filename in pch:
265249
filename = filename.rstrip()
266250
# Skip directories that show up in 'git diff HEAD --name-only'.
@@ -277,116 +261,100 @@ def should_bypass_compile(args):
277261
return False
278262

279263
if filename in BYPASS_EXTRA_CHECKS_REQUIRED:
280-
if not _check_file_for_bypass(filename, args.buildVariant):
264+
if not _check_file_for_bypass(filename, build_variant):
281265
log.warning("Compile bypass disabled due to extra checks for file.")
282266
return False
283267

284268
return True
285269

286270

287-
def parse_args():
288-
"""Parse the program arguments."""
289-
parser = argparse.ArgumentParser()
290-
parser.add_argument("--project", required=True,
291-
help="The Evergreen project. e.g mongodb-mongo-master")
292-
293-
parser.add_argument("--buildVariant", required=True,
294-
help="The build variant. e.g enterprise-rhel-62-64-bit")
295-
296-
parser.add_argument("--revision", required=True, help="The base commit hash.")
297-
298-
parser.add_argument("--patchFile", required=True,
299-
help="A list of all files modified in patch build.")
300-
301-
parser.add_argument("--outFile", required=True,
302-
help="The YAML file to write out the macro expansions.")
303-
304-
parser.add_argument("--jsonArtifact", required=True,
305-
help="The JSON file to write out the metadata of files to attach to task.")
306-
307-
return parser.parse_args()
308-
309-
310-
def find_suitable_build_id(builds, args):
271+
def find_build_for_previous_compile_task(evergreen_api, revision, project, build_variant):
311272
"""
312-
Find a build_id that fits the given parameters.
273+
Find build_id of the base revision.
313274
314-
:param builds: List of builds.
315-
:param args: The parameters a build must meet, including project, buildVariant, and revision.
316-
:return: Build_id that matches the parameters.
275+
:param evergreen_api: Evergreen.py object.
276+
:param revision: The base revision being run against.
277+
:param project: The evergreen project.
278+
:param build_variant: The build variant whose artifacts we want to use.
279+
:return: build_id of the base revision.
317280
"""
318-
prefix = "{}_{}_{}_".format(args.project, args.buildVariant, args.revision)
319-
# The "project" and "buildVariant" passed in may contain "-", but the "builds" listed from
320-
# Evergreen only contain "_". Replace the hyphens before searching for the build.
321-
prefix = prefix.replace("-", "_")
322-
build_id_pattern = re.compile(prefix)
323-
for build_id in builds:
324-
if build_id_pattern.search(build_id):
325-
return build_id
326-
return None
327-
281+
project_prefix = project.replace("-", "_")
282+
version_of_base_revision = "{}_{}".format(project_prefix, revision)
283+
version = evergreen_api.version_by_id(version_of_base_revision)
284+
build_id = version.build_by_variant(build_variant).id
285+
return build_id
328286

329-
def main(): # pylint: disable=too-many-locals,too-many-statements
330-
"""Execute Main entry.
331287

332-
From the /rest/v1/projects/{project}/revisions/{revision} endpoint find an existing build id
333-
to generate the compile task id to use for retrieving artifacts when bypassing compile.
334-
335-
We retrieve the URLs to the artifacts from the task info endpoint at
336-
/rest/v1/tasks/{build_id}. We only download the artifacts.tgz and extract certain files
337-
in order to retain any modified patch files.
288+
def find_previous_compile_task(evergreen_api, build_id, revision):
289+
"""
290+
Find compile task that should be used for skip compile..
338291
339-
If for any reason bypass compile is false, we do not write out the macro expansion. Only if we
340-
determine to bypass compile do we write out the macro expansions.
292+
:param evergreen_api: Evergreen.py object.
293+
:param build_id: Build id of the desired compile task.
294+
:param revision: The base revision being run against.
295+
:return: Evergreen.py object containing data about the desired compile task.
296+
"""
297+
index = build_id.find(revision)
298+
compile_task_id = "{}compile_{}".format(build_id[:index], build_id[index:])
299+
task = evergreen_api.task_by_id(compile_task_id)
300+
return task
301+
302+
303+
@click.command()
304+
@click.option("--project", required=True, help="The evergreen project.")
305+
@click.option("--build-variant", required=True,
306+
help="The build variant whose artifacts we want to use.")
307+
@click.option("--revision", required=True, help="Base revision of the build.")
308+
@click.option("--patch-file", required=True, help="A list of all files modified in patch build.")
309+
@click.option("--out-file", required=True, help="File to write expansions to.")
310+
@click.option("--json-artifact", required=True,
311+
help="The JSON file to write out the metadata of files to attach to task.")
312+
def main( # pylint: disable=too-many-arguments,too-many-locals,too-many-statements
313+
project, build_variant, revision, patch_file, out_file, json_artifact):
314+
"""
315+
Create a file with expansions that can be used to bypass compile.
316+
317+
If for any reason bypass compile is false, we do not write out the expansion. Only if we
318+
determine to bypass compile do we write out the expansions.
319+
\f
320+
321+
:param project: The evergreen project.
322+
:param build_variant: The build variant whose artifacts we want to use.
323+
:param revision: Base revision of the build.
324+
:param patch_file: A list of all files modified in patch build.
325+
:param out_file: File to write expansions to.
326+
:param json_artifact: The JSON file to write out the metadata of files to attach to task.
341327
"""
342-
args = parse_args()
343328
logging.basicConfig(
344329
format="[%(asctime)s - %(name)s - %(levelname)s] %(message)s",
345330
level=logging.DEBUG,
346331
stream=sys.stdout,
347332
)
348333

349334
# Determine if we should bypass compile based on modified patch files.
350-
if should_bypass_compile(args):
351-
evg_config = read_evg_config()
352-
if evg_config is None:
353-
LOGGER.warning(
354-
"Could not find ~/.evergreen.yml config file. Default compile bypass to false.")
355-
return
356-
357-
api_server = "{url.scheme}://{url.netloc}".format(
358-
url=urlparse(evg_config.get("api_server_host")))
359-
revision_url = "{}/rest/v1/projects/{}/revisions/{}".format(api_server, args.project,
360-
args.revision)
361-
revisions = requests_get_json(revision_url)
362-
build_id = find_suitable_build_id(revisions["builds"], args)
335+
if should_bypass_compile(patch_file, build_variant):
336+
evergreen_api = RetryingEvergreenApi.get_api(config_file=EVG_CONFIG_FILE)
337+
build_id = find_build_for_previous_compile_task(evergreen_api, revision, project,
338+
build_variant)
363339
if not build_id:
364340
LOGGER.warning("Could not find build id. Default compile bypass to false.",
365-
revision=args.revision, project=args.project)
341+
revision=revision, project=project)
366342
return
367-
368-
# Generate the compile task id.
369-
index = build_id.find(args.revision)
370-
compile_task_id = "{}compile_{}".format(build_id[:index], build_id[index:])
371-
task_url = "{}/rest/v1/tasks/{}".format(api_server, compile_task_id)
372-
# Get info on compile task of base commit.
373-
task = requests_get_json(task_url)
374-
if task is None or task["status"] != "success":
343+
task = find_previous_compile_task(evergreen_api, build_id, revision)
344+
if task is None or not task.is_success():
375345
LOGGER.warning(
376346
"Could not retrieve artifacts because the compile task for base commit"
377-
" was not available. Default compile bypass to false.", task_id=compile_task_id)
347+
" was not available. Default compile bypass to false.", task_id=task.task_id)
378348
return
379-
380-
# Get the compile task artifacts from REST API
381-
LOGGER.info("Fetching pre-existing artifacts from compile task", task_id=compile_task_id)
349+
LOGGER.info("Fetching pre-existing artifacts from compile task", task_id=task.task_id)
382350
artifacts = []
383-
for artifact in task["files"]:
384-
filename = os.path.basename(artifact["url"])
351+
for artifact in task.artifacts:
352+
filename = os.path.basename(artifact.url)
385353
if filename.startswith(build_id):
386354
LOGGER.info("Retrieving archive", filename=filename)
387355
# This is the artifacts.tgz as referenced in evergreen.yml.
388356
try:
389-
urllib.request.urlretrieve(artifact["url"], filename)
357+
urllib.request.urlretrieve(artifact.url, filename)
390358
except urllib.error.ContentTooShortError:
391359
LOGGER.warning(
392360
"The artifact could not be completely downloaded. Default"
@@ -415,7 +383,7 @@ def main(): # pylint: disable=too-many-locals,too-many-statements
415383
LOGGER.info("Retrieving mongo source", filename=filename)
416384
# This is the distsrc.[tgz|zip] as referenced in evergreen.yml.
417385
try:
418-
urllib.request.urlretrieve(artifact["url"], filename)
386+
urllib.request.urlretrieve(artifact.url, filename)
419387
except urllib.error.ContentTooShortError:
420388
LOGGER.warn(
421389
"The artifact could not be completely downloaded. Default"
@@ -429,12 +397,12 @@ def main(): # pylint: disable=too-many-locals,too-many-statements
429397
LOGGER.info("Linking base artifact to this patch build", filename=filename)
430398
# For other artifacts we just add their URLs to the JSON file to upload.
431399
files = {
432-
"name": artifact["name"],
433-
"link": artifact["url"],
400+
"name": artifact.name,
401+
"link": artifact.url,
434402
"visibility": "private",
435403
}
436404
# Check the link exists, else raise an exception. Compile bypass is disabled.
437-
requests.head(artifact["url"]).raise_for_status()
405+
requests.head(artifact.url).raise_for_status()
438406
artifacts.append(files)
439407

440408
# SERVER-21492 related issue where without running scons the jstests/libs/key1
@@ -445,13 +413,12 @@ def main(): # pylint: disable=too-many-locals,too-many-statements
445413
os.chmod("jstests/libs/keyForRollover", 0o600)
446414

447415
# This is the artifacts.json file.
448-
write_out_artifacts(args.jsonArtifact, artifacts)
416+
write_out_artifacts(json_artifact, artifacts)
449417

450418
# Need to apply these expansions for bypassing SCons.
451-
expansions = generate_bypass_expansions(args.project, args.buildVariant, args.revision,
452-
build_id)
453-
write_out_bypass_compile_expansions(args.outFile, **expansions)
419+
expansions = generate_bypass_expansions(project, build_variant, revision, build_id)
420+
write_out_bypass_compile_expansions(out_file, **expansions)
454421

455422

456423
if __name__ == "__main__":
457-
main()
424+
main() # pylint: disable=no-value-for-parameter

0 commit comments

Comments
 (0)