mirror of https://github.com/astral-sh/uv
Respect credentials from all defined indexes (#14858)
## Summary The core problem here is that `allowed_indexes` only includes at most one "default" index. This is problematic for tool upgrades, since the index in the receipt will be marked as default, but credentials will be omitted; if credentials are then defined in a `uv.toml`, we'll never look at those, since that will _also_ be marked as default, and we only look at the first default. Instead, we should consider all defined indexes in priority order. Closes https://github.com/astral-sh/uv/issues/14806.
This commit is contained in:
parent
4dd0392086
commit
faa12f50ce
|
|
@ -400,8 +400,8 @@ impl<'a> IndexLocations {
|
||||||
///
|
///
|
||||||
/// This includes explicit indexes, implicit indexes, flat indexes, and the default index.
|
/// This includes explicit indexes, implicit indexes, flat indexes, and the default index.
|
||||||
///
|
///
|
||||||
/// The indexes will be returned in the order in which they were defined, such that the
|
/// The indexes will be returned in the reverse of the order in which they were defined, such
|
||||||
/// last-defined index is the last item in the vector.
|
/// that the last-defined index is the first item in the vector.
|
||||||
pub fn allowed_indexes(&'a self) -> Vec<&'a Index> {
|
pub fn allowed_indexes(&'a self) -> Vec<&'a Index> {
|
||||||
if self.no_index {
|
if self.no_index {
|
||||||
self.flat_index.iter().rev().collect()
|
self.flat_index.iter().rev().collect()
|
||||||
|
|
@ -436,9 +436,29 @@ impl<'a> IndexLocations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a vector containing all known [`Index`] entries.
|
||||||
|
///
|
||||||
|
/// This includes explicit indexes, implicit indexes, flat indexes, and default indexes;
|
||||||
|
/// in short, it includes all defined indexes, even if they're overridden by some other index
|
||||||
|
/// definition.
|
||||||
|
///
|
||||||
|
/// The indexes will be returned in the reverse of the order in which they were defined, such
|
||||||
|
/// that the last-defined index is the first item in the vector.
|
||||||
|
pub fn known_indexes(&'a self) -> impl Iterator<Item = &'a Index> {
|
||||||
|
if self.no_index {
|
||||||
|
Either::Left(self.flat_index.iter().rev())
|
||||||
|
} else {
|
||||||
|
Either::Right(
|
||||||
|
std::iter::once(&*DEFAULT_INDEX)
|
||||||
|
.chain(self.flat_index.iter().rev())
|
||||||
|
.chain(self.indexes.iter().rev()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add all authenticated sources to the cache.
|
/// Add all authenticated sources to the cache.
|
||||||
pub fn cache_index_credentials(&self) {
|
pub fn cache_index_credentials(&self) {
|
||||||
for index in self.allowed_indexes() {
|
for index in self.known_indexes() {
|
||||||
if let Some(credentials) = index.credentials() {
|
if let Some(credentials) = index.credentials() {
|
||||||
let credentials = Arc::new(credentials);
|
let credentials = Arc::new(credentials);
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
||||||
|
|
|
||||||
|
|
@ -3709,3 +3709,126 @@ fn tool_install_credentials() {
|
||||||
"#);
|
"#);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When installing from an authenticated index, the credentials should be omitted from the receipt.
|
||||||
|
#[test]
|
||||||
|
fn tool_install_default_credentials() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12")
|
||||||
|
.with_exclude_newer("2025-01-18T00:00:00Z")
|
||||||
|
.with_filtered_counts()
|
||||||
|
.with_filtered_exe_suffix();
|
||||||
|
let tool_dir = context.temp_dir.child("tools");
|
||||||
|
let bin_dir = context.temp_dir.child("bin");
|
||||||
|
|
||||||
|
// Write a `uv.toml` with a default index that has credentials.
|
||||||
|
let uv_toml = context.temp_dir.child("uv.toml");
|
||||||
|
uv_toml.write_str(indoc::indoc! {r#"
|
||||||
|
[[index]]
|
||||||
|
url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple"
|
||||||
|
default = true
|
||||||
|
authenticate = "always"
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Install `executable-application`
|
||||||
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
.arg("executable-application")
|
||||||
|
.arg("--config-file")
|
||||||
|
.arg(uv_toml.as_os_str())
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ executable-application==0.3.0
|
||||||
|
Installed 1 executable: app
|
||||||
|
"###);
|
||||||
|
|
||||||
|
tool_dir
|
||||||
|
.child("executable-application")
|
||||||
|
.assert(predicate::path::is_dir());
|
||||||
|
tool_dir
|
||||||
|
.child("executable-application")
|
||||||
|
.child("uv-receipt.toml")
|
||||||
|
.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
let executable = bin_dir.child(format!("app{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
assert!(executable.exists());
|
||||||
|
|
||||||
|
// On Windows, we can't snapshot an executable file.
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// Should run black in the virtual environment
|
||||||
|
assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###"
|
||||||
|
#![TEMP_DIR]/tools/executable-application/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
from executable_application import main
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if sys.argv[0].endswith("-script.pyw"):
|
||||||
|
sys.argv[0] = sys.argv[0][:-11]
|
||||||
|
elif sys.argv[0].endswith(".exe"):
|
||||||
|
sys.argv[0] = sys.argv[0][:-4]
|
||||||
|
sys.exit(main())
|
||||||
|
"###);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
// We should have a tool receipt
|
||||||
|
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
|
||||||
|
[tool]
|
||||||
|
requirements = [{ name = "executable-application" }]
|
||||||
|
entrypoints = [
|
||||||
|
{ name = "app", install-path = "[TEMP_DIR]/bin/app" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.options]
|
||||||
|
index = [{ url = "https://pypi-proxy.fly.dev/basic-auth/simple", explicit = false, default = true, format = "simple", authenticate = "always" }]
|
||||||
|
exclude-newer = "2025-01-18T00:00:00Z"
|
||||||
|
"#);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attempt to upgrade without providing the credentials (from the config file).
|
||||||
|
uv_snapshot!(context.filters(), context.tool_upgrade()
|
||||||
|
.arg("executable-application")
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: Failed to upgrade executable-application
|
||||||
|
Caused by: Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth/simple/executable-application/`
|
||||||
|
Caused by: Missing credentials for https://pypi-proxy.fly.dev/basic-auth/simple/executable-application/
|
||||||
|
");
|
||||||
|
|
||||||
|
// Attempt to upgrade.
|
||||||
|
uv_snapshot!(context.filters(), context.tool_upgrade()
|
||||||
|
.arg("executable-application")
|
||||||
|
.arg("--config-file")
|
||||||
|
.arg(uv_toml.as_os_str())
|
||||||
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Nothing to upgrade
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue