diff options
author | Harmen Stoppels <me@harmenstoppels.nl> | 2024-01-31 15:58:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-31 15:58:51 +0100 |
commit | f27bff81ba9cd53416ceda109aa9a6708dd13760 (patch) | |
tree | 9fb0c7a2b1f76cc3dc50792c72d1e4c9907c7ed9 /lib | |
parent | 5c49bb45c7955490319f229ffbe72cfdf3102b22 (diff) | |
download | spack-f27bff81ba9cd53416ceda109aa9a6708dd13760.tar.gz spack-f27bff81ba9cd53416ceda109aa9a6708dd13760.tar.bz2 spack-f27bff81ba9cd53416ceda109aa9a6708dd13760.tar.xz spack-f27bff81ba9cd53416ceda109aa9a6708dd13760.zip |
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>`.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/cmd/ci.py | 52 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/ci.py | 38 |
2 files changed, 83 insertions, 7 deletions
diff --git a/lib/spack/spack/cmd/ci.py b/lib/spack/spack/cmd/ci.py index 1b0eab888c..80aa1634e3 100644 --- a/lib/spack/spack/cmd/ci.py +++ b/lib/spack/spack/cmd/ci.py @@ -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): diff --git a/lib/spack/spack/test/cmd/ci.py b/lib/spack/spack/test/cmd/ci.py index 6fabd16cd5..8209040d87 100644 --- a/lib/spack/spack/test/cmd/ci.py +++ b/lib/spack/spack/test/cmd/ci.py @@ -16,6 +16,7 @@ from llnl.util.filesystem import mkdirp, working_dir 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 @@ -2029,6 +2030,43 @@ spack: @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")] ) def test_ci_help(subcmd, capsys): |