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

@ -148,15 +148,16 @@ this can expose you to attacks. Use at your own risk.
``ssl_certs`` ``ssl_certs``
-------------------- --------------------
Path to custom certificats for SSL verification. The value can be a Path to custom certificats for SSL verification. The value can be a
filesytem path, or an environment variable that expands to an absolute file path. filesytem path, or an environment variable that expands to an absolute file path.
The default value is set to the environment variable ``SSL_CERT_FILE`` The default value is set to the environment variable ``SSL_CERT_FILE``
to use the same syntax used by many other applications that automatically 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,
If ``url_fetch_method:urllib`` then files and directories are supported i.e. 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.
``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.
In all cases the expanded path must be absolute for Spack to use the certificates. In all cases the expanded path must be absolute for Spack to use the certificates.

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)