Skip to content

Commit cd3ae91

Browse files
committed
Enable environment variable authentication for named indexes
1 parent e5c0b1e commit cd3ae91

20 files changed

Lines changed: 289 additions & 66 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/distribution-types/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pep440_rs = { workspace = true }
1919
pep508_rs = { workspace = true, features = ["serde"] }
2020
platform-tags = { workspace = true }
2121
pypi-types = { workspace = true }
22+
uv-auth = { workspace = true }
2223
uv-cache-info = { workspace = true }
2324
uv-fs = { workspace = true }
2425
uv-git = { workspace = true }

crates/distribution-types/src/index.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{IndexUrl, IndexUrlError};
22
use std::str::FromStr;
33
use thiserror::Error;
44
use url::Url;
5+
use uv_auth::Credentials;
56

67
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
78
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@@ -102,6 +103,19 @@ impl Index {
102103
pub fn raw_url(&self) -> &Url {
103104
self.url.url()
104105
}
106+
107+
/// Retrieve the credentials for the index, either from the environment, or from the URL itself.
108+
pub fn credentials(&self) -> Option<Credentials> {
109+
// If the index is named, and credentials are provided via the environment, prefer those.
110+
if let Some(name) = self.name.as_deref() {
111+
if let Some(credentials) = Credentials::from_env(name) {
112+
return Some(credentials);
113+
}
114+
}
115+
116+
// Otherwise, extract the credentials from the URL.
117+
Credentials::from_url(self.url.url())
118+
}
105119
}
106120

107121
impl FromStr for Index {

crates/distribution-types/src/index_url.rs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ impl<'a> IndexLocations {
407407
}
408408

409409
/// Return an iterator over the [`FlatIndexLocation`] entries.
410-
pub fn flat_index(&'a self) -> impl Iterator<Item = &'a FlatIndexLocation> + 'a {
410+
pub fn flat_indexes(&'a self) -> impl Iterator<Item = &'a FlatIndexLocation> + 'a {
411411
self.flat_index.iter()
412412
}
413413

@@ -424,9 +424,10 @@ impl<'a> IndexLocations {
424424
}
425425
}
426426

427-
/// Return an iterator over all allowed [`IndexUrl`] entries.
427+
/// Return an iterator over all allowed [`Index`] entries.
428428
///
429-
/// This includes both explicit and implicit indexes, as well as the default index.
429+
/// This includes both explicit and implicit indexes, as well as the default index (but _not_
430+
/// the flat indexes).
430431
///
431432
/// If `no_index` was enabled, then this always returns an empty
432433
/// iterator.
@@ -435,18 +436,6 @@ impl<'a> IndexLocations {
435436
.chain(self.implicit_indexes())
436437
.chain(self.default_index())
437438
}
438-
439-
/// Return an iterator over all allowed [`Url`] entries.
440-
///
441-
/// This includes both explicit and implicit index URLs, as well as the default index.
442-
///
443-
/// If `no_index` was enabled, then this always returns an empty
444-
/// iterator.
445-
pub fn allowed_urls(&'a self) -> impl Iterator<Item = &'a Url> + 'a {
446-
self.allowed_indexes()
447-
.map(Index::raw_url)
448-
.chain(self.flat_index().map(FlatIndexLocation::url))
449-
}
450439
}
451440

452441
/// The index URLs to use for fetching packages.

crates/uv-auth/src/credentials.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,21 @@ impl Credentials {
139139
})
140140
}
141141

142+
/// Extract the [`Credentials`] from the environment, given a named source.
143+
///
144+
/// For example, given a name of `"pytorch"`, search for `UV_HTTP_BASIC_PYTORCH_USERNAME` and
145+
/// `UV_HTTP_BASIC_PYTORCH_PASSWORD`.
146+
pub fn from_env(name: &str) -> Option<Self> {
147+
let name = name.to_uppercase();
148+
let username = std::env::var(format!("UV_HTTP_BASIC_{name}_USERNAME")).ok();
149+
let password = std::env::var(format!("UV_HTTP_BASIC_{name}_PASSWORD")).ok();
150+
if username.is_none() && password.is_none() {
151+
None
152+
} else {
153+
Some(Self::new(username, password))
154+
}
155+
}
156+
142157
/// Parse [`Credentials`] from an HTTP request, if any.
143158
///
144159
/// Only HTTP Basic Authentication is supported.

crates/uv-auth/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@ pub fn store_credentials_from_url(url: &Url) -> bool {
3535
false
3636
}
3737
}
38+
39+
/// Populate the global authentication store with credentials on a URL, if there are any.
40+
///
41+
/// Returns `true` if the store was updated.
42+
pub fn store_credentials(url: &Url, credentials: Credentials) {
43+
trace!("Caching credentials for {url}");
44+
CREDENTIALS_CACHE.insert(url, Arc::new(credentials));
45+
}

crates/uv-distribution/src/index/registry_wheel_index.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ impl<'a> RegistryWheelIndex<'a> {
8989

9090
// Collect into owned `IndexUrl`.
9191
let flat_index_urls: Vec<Index> = index_locations
92-
.flat_index()
92+
.flat_indexes()
9393
.map(|flat_index| Index::from_extra_index_url(IndexUrl::from(flat_index.clone())))
9494
.collect();
9595

crates/uv-resolver/src/lock/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@ impl Lock {
10551055
})
10561056
.chain(
10571057
locations
1058-
.flat_index()
1058+
.flat_indexes()
10591059
.filter_map(|index_url| match index_url {
10601060
FlatIndexLocation::Url(_) => {
10611061
Some(UrlString::from(index_url.redacted()))
@@ -1081,7 +1081,7 @@ impl Lock {
10811081
})
10821082
.chain(
10831083
locations
1084-
.flat_index()
1084+
.flat_indexes()
10851085
.filter_map(|index_url| match index_url {
10861086
FlatIndexLocation::Url(_) => None,
10871087
FlatIndexLocation::Path(index_url) => {

crates/uv-resolver/src/pubgrub/report.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ impl PubGrubReportFormatter<'_> {
621621
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
622622
hints: &mut IndexSet<PubGrubHint>,
623623
) {
624-
let no_find_links = index_locations.flat_index().peekable().peek().is_none();
624+
let no_find_links = index_locations.flat_indexes().peekable().peek().is_none();
625625

626626
// Add hints due to the package being entirely unavailable.
627627
match unavailable_packages.get(name) {

crates/uv/src/commands/build.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use distribution_types::{DependencyMetadata, IndexLocations};
88
use install_wheel_rs::linker::LinkMode;
99
use owo_colors::OwoColorize;
1010

11-
use uv_auth::store_credentials_from_url;
11+
use uv_auth::{store_credentials, store_credentials_from_url};
1212
use uv_cache::Cache;
1313
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
1414
use uv_configuration::{
@@ -391,8 +391,13 @@ async fn build_package(
391391
.into_interpreter();
392392

393393
// Add all authenticated sources to the cache.
394-
for url in index_locations.allowed_urls() {
395-
store_credentials_from_url(url);
394+
for index in index_locations.allowed_indexes() {
395+
if let Some(credentials) = index.credentials() {
396+
store_credentials(index.raw_url(), credentials);
397+
}
398+
}
399+
for index in index_locations.flat_indexes() {
400+
store_credentials_from_url(index.url());
396401
}
397402

398403
// Read build constraints.
@@ -445,7 +450,7 @@ async fn build_package(
445450
// Resolve the flat indexes from `--find-links`.
446451
let flat_index = {
447452
let client = FlatIndexClient::new(&client, cache);
448-
let entries = client.fetch(index_locations.flat_index()).await?;
453+
let entries = client.fetch(index_locations.flat_indexes()).await?;
449454
FlatIndex::from_entries(entries, None, &hasher, build_options)
450455
};
451456

0 commit comments

Comments
 (0)