spack reproduce-build: accept URLs from web interface (#42261)

Sometimes the logs are too long and the copy & paste command is not
shown. In that case I'd like to just copy the failing GitLab job URL in
my browser to `spack reproduce-build <url>`.
This commit is contained in:
Harmen Stoppels 2024-01-31 15:58:51 +01:00 committed by GitHub
parent 5c49bb45c7
commit f27bff81ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 7 deletions

View File

@ -6,6 +6,7 @@
import json
import os
import shutil
from urllib.parse import urlparse, urlunparse
import llnl.util.filesystem as fs
import llnl.util.tty as tty
@ -157,7 +158,9 @@ def setup_parser(subparser):
description=deindent(ci_reproduce.__doc__),
help=spack.cmd.first_line(ci_reproduce.__doc__),
)
reproduce.add_argument("job_url", help="URL of job artifacts bundle")
reproduce.add_argument(
"job_url", help="URL of GitLab job web page or artifact", type=_gitlab_artifacts_url
)
reproduce.add_argument(
"--runtime",
help="Container runtime to use.",
@ -792,11 +795,6 @@ def ci_reproduce(args):
artifacts of the provided gitlab pipeline rebuild job's URL will be used to derive
instructions for reproducing the build locally
"""
job_url = args.job_url
work_dir = args.working_dir
autostart = args.autostart
runtime = args.runtime
# Allow passing GPG key for reprocuding protected CI jobs
if args.gpg_file:
gpg_key_url = url_util.path_to_file_url(args.gpg_file)
@ -805,7 +803,47 @@ def ci_reproduce(args):
else:
gpg_key_url = None
return spack_ci.reproduce_ci_job(job_url, work_dir, autostart, gpg_key_url, runtime)
return spack_ci.reproduce_ci_job(
args.job_url, args.working_dir, args.autostart, gpg_key_url, args.runtime
)
def _gitlab_artifacts_url(url: str) -> str:
"""Take a URL either to the URL of the job in the GitLab UI, or to the artifacts zip file,
and output the URL to the artifacts zip file."""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(url)
parts = parsed.path.split("/")
if len(parts) < 2:
raise ValueError(url)
# Just use API endpoints verbatim, they're probably generated by Spack.
if parts[1] == "api":
return url
# If it's a URL to the job in the Gitlab UI, we may need to append the artifacts path.
minus_idx = parts.index("-")
# Remove repeated slashes in the remainder
rest = [p for p in parts[minus_idx + 1 :] if p]
# Now the format is jobs/X or jobs/X/artifacts/download
if len(rest) < 2 or rest[0] != "jobs":
raise ValueError(url)
if len(rest) == 2:
# replace jobs/X with jobs/X/artifacts/download
rest.extend(("artifacts", "download"))
# Replace the parts and unparse.
parts[minus_idx + 1 :] = rest
# Don't allow fragments / queries
return urlunparse(parsed._replace(path="/".join(parts), fragment="", query=""))
def ci(parser, args):

View File

@ -16,6 +16,7 @@
import spack
import spack.binary_distribution
import spack.ci as ci
import spack.cmd.ci
import spack.config
import spack.environment as ev
import spack.hash_types as ht
@ -2028,6 +2029,43 @@ def fake_download_and_extract_artifacts(url, work_dir):
assert expect_out in rep_out
@pytest.mark.parametrize(
"url_in,url_out",
[
(
"https://example.com/api/v4/projects/1/jobs/2/artifacts",
"https://example.com/api/v4/projects/1/jobs/2/artifacts",
),
(
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
),
(
"https://example.com/spack/spack/-/jobs/123456",
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
),
(
"https://example.com/spack/spack/-/jobs/////123456////?x=y#z",
"https://example.com/spack/spack/-/jobs/123456/artifacts/download",
),
],
)
def test_reproduce_build_url_validation(url_in, url_out):
assert spack.cmd.ci._gitlab_artifacts_url(url_in) == url_out
def test_reproduce_build_url_validation_fails():
"""Wrong URLs should cause an exception"""
with pytest.raises(SystemExit):
ci_cmd("reproduce-build", "example.com/spack/spack/-/jobs/123456/artifacts/download")
with pytest.raises(SystemExit):
ci_cmd("reproduce-build", "https://example.com/spack/spack/-/issues")
with pytest.raises(SystemExit):
ci_cmd("reproduce-build", "https://example.com/spack/spack/-")
@pytest.mark.parametrize(
"subcmd", [(""), ("generate"), ("rebuild-index"), ("rebuild"), ("reproduce-build")]
)