config:url_fetch_method: allow curl args (#49712)

Signed-off-by: Gregory Becker <becker33@llnl.gov>
This commit is contained in:
Greg Becker 2025-04-01 13:23:28 -07:00 committed by GitHub
parent 91b3afac88
commit ca64050f6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 35 additions and 14 deletions

View File

@ -155,7 +155,8 @@ to use the same syntax used by many other applications that automatically
detect custom certificates. detect custom certificates.
When ``url_fetch_method:curl`` the ``config:ssl_certs`` should resolve to When ``url_fetch_method:curl`` the ``config:ssl_certs`` should resolve to
a single file. Spack will then set the environment variable ``CURL_CA_BUNDLE`` a single file. Spack will then set the environment variable ``CURL_CA_BUNDLE``
in the subprocess calling ``curl``. in the subprocess calling ``curl``. If additional ``curl`` arguments are required,
they can be set in the config, e.g. ``url_fetch_method:'curl -k -q'``.
If ``url_fetch_method:urllib`` then files and directories are supported i.e. If ``url_fetch_method:urllib`` then files and directories are supported i.e.
``config:ssl_certs:$SSL_CERT_FILE`` or ``config:ssl_certs:$SSL_CERT_DIR`` ``config:ssl_certs:$SSL_CERT_FILE`` or ``config:ssl_certs:$SSL_CERT_DIR``
will work. will work.

View File

@ -295,8 +295,9 @@ def fetch(self):
) )
def _fetch_from_url(self, url): def _fetch_from_url(self, url):
if spack.config.get("config:url_fetch_method") == "curl": fetch_method = spack.config.get("config:url_fetch_method", "urllib")
return self._fetch_curl(url) if fetch_method.startswith("curl"):
return self._fetch_curl(url, config_args=fetch_method.split()[1:])
else: else:
return self._fetch_urllib(url) return self._fetch_urllib(url)
@ -345,7 +346,7 @@ def _fetch_urllib(self, url):
self._check_headers(str(response.headers)) self._check_headers(str(response.headers))
@_needs_stage @_needs_stage
def _fetch_curl(self, url): def _fetch_curl(self, url, config_args=[]):
save_file = None save_file = None
partial_file = None partial_file = None
if self.stage.save_filename: if self.stage.save_filename:
@ -374,7 +375,7 @@ def _fetch_curl(self, url):
timeout = self.extra_options.get("timeout") timeout = self.extra_options.get("timeout")
base_args = web_util.base_curl_fetch_args(url, timeout) base_args = web_util.base_curl_fetch_args(url, timeout)
curl_args = save_args + base_args + cookie_args curl_args = config_args + save_args + base_args + cookie_args
# Run curl but grab the mime type from the http headers # Run curl but grab the mime type from the http headers
curl = self.curl curl = self.curl

View File

@ -100,7 +100,7 @@
"allow_sgid": {"type": "boolean"}, "allow_sgid": {"type": "boolean"},
"install_status": {"type": "boolean"}, "install_status": {"type": "boolean"},
"binary_index_root": {"type": "string"}, "binary_index_root": {"type": "string"},
"url_fetch_method": {"type": "string", "enum": ["urllib", "curl"]}, "url_fetch_method": {"type": "string", "pattern": r"^urllib$|^curl( .*)*"},
"additional_external_search_paths": {"type": "array", "items": {"type": "string"}}, "additional_external_search_paths": {"type": "array", "items": {"type": "string"}},
"binary_index_ttl": {"type": "integer", "minimum": 0}, "binary_index_ttl": {"type": "integer", "minimum": 0},
"aliases": {"type": "object", "patternProperties": {r"\w[\w-]*": {"type": "string"}}}, "aliases": {"type": "object", "patternProperties": {r"\w[\w-]*": {"type": "string"}}},

View File

@ -109,6 +109,26 @@ def test_fetch_options(tmp_path, mock_archive):
assert filecmp.cmp(fetcher.archive_file, mock_archive.archive_file) assert filecmp.cmp(fetcher.archive_file, mock_archive.archive_file)
def test_fetch_curl_options(tmp_path, mock_archive, monkeypatch):
with spack.config.override("config:url_fetch_method", "curl -k -q"):
fetcher = fs.URLFetchStrategy(
url=mock_archive.url, fetch_options={"cookie": "True", "timeout": 10}
)
def check_args(*args, **kwargs):
# Raise StopIteration to avoid running the rest of the fetch method
# args[0] is `which curl`, next two are our config options
assert args[1:3] == ("-k", "-q")
raise StopIteration
monkeypatch.setattr(type(fetcher.curl), "__call__", check_args)
with Stage(fetcher, path=str(tmp_path)):
assert fetcher.archive_file is None
with pytest.raises(StopIteration):
fetcher.fetch()
@pytest.mark.parametrize("_fetch_method", ["curl", "urllib"]) @pytest.mark.parametrize("_fetch_method", ["curl", "urllib"])
def test_archive_file_errors(tmp_path, mutable_config, mock_archive, _fetch_method): def test_archive_file_errors(tmp_path, mutable_config, mock_archive, _fetch_method):
"""Ensure FetchStrategy commands may only be used as intended""" """Ensure FetchStrategy commands may only be used as intended"""

View File

@ -388,9 +388,9 @@ def fetch_url_text(url, curl: Optional[Executable] = None, dest_dir="."):
fetch_method = spack.config.get("config:url_fetch_method") fetch_method = spack.config.get("config:url_fetch_method")
tty.debug("Using '{0}' to fetch {1} into {2}".format(fetch_method, url, path)) tty.debug("Using '{0}' to fetch {1} into {2}".format(fetch_method, url, path))
if fetch_method == "curl": if fetch_method.startswith("curl"):
curl_exe = curl or require_curl() curl_exe = curl or require_curl()
curl_args = ["-O"] curl_args = fetch_method.split()[1:] + ["-O"]
curl_args.extend(base_curl_fetch_args(url)) curl_args.extend(base_curl_fetch_args(url))
# Curl automatically downloads file contents as filename # Curl automatically downloads file contents as filename
@ -436,15 +436,14 @@ def url_exists(url, curl=None):
url_result = urllib.parse.urlparse(url) url_result = urllib.parse.urlparse(url)
# Use curl if configured to do so # Use curl if configured to do so
use_curl = spack.config.get( fetch_method = spack.config.get("config:url_fetch_method", "urllib")
"config:url_fetch_method", "urllib" use_curl = fetch_method.startswith("curl") and url_result.scheme not in ("gs", "s3")
) == "curl" and url_result.scheme not in ("gs", "s3")
if use_curl: if use_curl:
curl_exe = curl or require_curl() curl_exe = curl or require_curl()
# Telling curl to fetch the first byte (-r 0-0) is supposed to be # Telling curl to fetch the first byte (-r 0-0) is supposed to be
# portable. # portable.
curl_args = ["--stderr", "-", "-s", "-f", "-r", "0-0", url] curl_args = fetch_method.split()[1:] + ["--stderr", "-", "-s", "-f", "-r", "0-0", url]
if not spack.config.get("config:verify_ssl"): if not spack.config.get("config:verify_ssl"):
curl_args.append("-k") curl_args.append("-k")
_ = curl_exe(*curl_args, fail_on_error=False, output=os.devnull) _ = curl_exe(*curl_args, fail_on_error=False, output=os.devnull)