mirror of https://github.com/astral-sh/uv
Read index credentials from env for `uv publish` (#15545)
We were previously missing the `index_locations.cache_index_credentials()` call in `uv publish` to load index credentials from the env. See https://github.com/astral-sh/uv/issues/11836#issuecomment-3022735011 Fixes #11836
This commit is contained in:
parent
bce30be3a5
commit
0bde9e4b8f
|
|
@ -5026,6 +5026,7 @@ dependencies = [
|
|||
"self-replace",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"similar",
|
||||
"tar",
|
||||
"tempfile",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use std::sync::{Arc, LazyLock, RwLock};
|
|||
use itertools::Either;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use thiserror::Error;
|
||||
use tracing::trace;
|
||||
use url::{ParseError, Url};
|
||||
|
||||
use uv_pep508::{Scheme, VerbatimUrl, VerbatimUrlError, split_scheme};
|
||||
|
|
@ -457,6 +458,14 @@ impl<'a> IndexLocations {
|
|||
pub fn cache_index_credentials(&self) {
|
||||
for index in self.known_indexes() {
|
||||
if let Some(credentials) = index.credentials() {
|
||||
trace!(
|
||||
"Read credentials for index {}",
|
||||
index
|
||||
.name
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(|| index.url.to_string())
|
||||
);
|
||||
let credentials = Arc::new(credentials);
|
||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
||||
if let Some(root_url) = index.root_url() {
|
||||
|
|
|
|||
|
|
@ -406,7 +406,9 @@ doc_comment::doctest!("../README.md", readme);
|
|||
/// Instead, it contains generics that each keystore invokes in their tests,
|
||||
/// passing their store-specific parameters for the generic ones.
|
||||
mod tests {
|
||||
use super::{Entry, Error, Result, credential::CredentialApi};
|
||||
use super::{Entry, Error};
|
||||
#[cfg(feature = "keyring-tests")]
|
||||
use super::{Result, credential::CredentialApi};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Create a platform-specific credential given the constructor, service, and user
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ insta = { workspace = true }
|
|||
predicates = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["blocking"], default-features = false }
|
||||
sha2 = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
tar = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use uv_auth::Credentials;
|
|||
use uv_cache::Cache;
|
||||
use uv_client::{AuthIntegration, BaseClient, BaseClientBuilder, RegistryClientBuilder};
|
||||
use uv_configuration::{KeyringProviderType, TrustedPublishing};
|
||||
use uv_distribution_types::{Index, IndexCapabilities, IndexLocations, IndexUrl};
|
||||
use uv_distribution_types::{IndexCapabilities, IndexLocations, IndexUrl};
|
||||
use uv_publish::{
|
||||
CheckUrlClient, TrustedPublishResult, check_trusted_publishing, files_for_publishing, upload,
|
||||
};
|
||||
|
|
@ -32,6 +32,7 @@ pub(crate) async fn publish(
|
|||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
check_url: Option<IndexUrl>,
|
||||
index_locations: IndexLocations,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
|
|
@ -88,11 +89,7 @@ pub(crate) async fn publish(
|
|||
|
||||
// Initialize the registry client.
|
||||
let check_url_client = if let Some(index_url) = &check_url {
|
||||
let index_locations = IndexLocations::new(
|
||||
vec![Index::from_index_url(index_url.clone())],
|
||||
Vec::new(),
|
||||
false,
|
||||
);
|
||||
index_locations.cache_index_credentials();
|
||||
let registry_client_builder = RegistryClientBuilder::new(cache.clone())
|
||||
.retries_from_env()?
|
||||
.native_tls(network_settings.native_tls)
|
||||
|
|
|
|||
|
|
@ -1626,6 +1626,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
username,
|
||||
password,
|
||||
check_url,
|
||||
index_locations,
|
||||
&cache,
|
||||
printer,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ use indoc::formatdoc;
|
|||
use itertools::Itertools;
|
||||
use predicates::prelude::predicate;
|
||||
use regex::Regex;
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
use uv_cache::{Cache, CacheBucket};
|
||||
use uv_fs::Simplified;
|
||||
use uv_preview::Preview;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
use crate::common::{TestContext, uv_snapshot, venv_bin_path};
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::fixture::{FileTouch, FileWriteStr, PathChild};
|
||||
use indoc::indoc;
|
||||
use fs_err::OpenOptions;
|
||||
use indoc::{formatdoc, indoc};
|
||||
use serde_json::json;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::env;
|
||||
use std::env::current_dir;
|
||||
use std::io::Write;
|
||||
use uv_static::EnvVars;
|
||||
use wiremock::matchers::{basic_auth, method, path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
#[test]
|
||||
fn username_password_no_longer_supported() {
|
||||
|
|
@ -387,3 +393,110 @@ fn invalid_index() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure that we read index credentials from the environment when publishing.
|
||||
///
|
||||
/// <https://github.com/astral-sh/uv/issues/11836#issuecomment-3022735011>
|
||||
#[tokio::test]
|
||||
async fn read_index_credential_env_vars_for_check_url() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let server = MockServer::start().await;
|
||||
|
||||
context
|
||||
.init()
|
||||
.arg("--name")
|
||||
.arg("astral-test-private")
|
||||
.arg(".")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
context.build().arg("--wheel").assert().success();
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.create(false)
|
||||
.open(context.temp_dir.join("pyproject.toml"))
|
||||
.unwrap();
|
||||
file.write_all(
|
||||
formatdoc! {
|
||||
r#"
|
||||
[[tool.uv.index]]
|
||||
name = "private-index"
|
||||
url = "{index_uri}/simple/"
|
||||
publish-url = "{index_uri}/upload"
|
||||
"#,
|
||||
index_uri = server.uri()
|
||||
}
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let filename = "astral_test_private-0.1.0-py3-none-any.whl";
|
||||
let wheel = context.temp_dir.join("dist").join(filename);
|
||||
let sha256 = format!("{:x}", Sha256::digest(fs_err::read(&wheel).unwrap()));
|
||||
|
||||
let simple_index = json! ({
|
||||
"files": [
|
||||
{
|
||||
"filename": filename,
|
||||
"hashes": {
|
||||
"sha256": sha256
|
||||
},
|
||||
"url": format!("{}/{}", server.uri(), filename),
|
||||
}
|
||||
]
|
||||
});
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/simple/astral-test-private/"))
|
||||
.and(basic_auth("username", "secret"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_raw(
|
||||
simple_index.to_string().into_bytes(),
|
||||
"application/vnd.pypi.simple.v1+json",
|
||||
))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
// Test that we fail without credentials
|
||||
uv_snapshot!(context.filters(), context.publish()
|
||||
.current_dir(&context.temp_dir)
|
||||
.arg(&wheel)
|
||||
.arg("--index")
|
||||
.arg("private-index")
|
||||
.arg("--trusted-publishing")
|
||||
.arg("never"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Publishing 1 file to http://[LOCALHOST]/upload
|
||||
Uploading astral_test_private-0.1.0-py3-none-any.whl ([SIZE])
|
||||
error: Failed to publish `dist/astral_test_private-0.1.0-py3-none-any.whl` to http://[LOCALHOST]/upload
|
||||
Caused by: Failed to send POST request
|
||||
Caused by: Missing credentials for http://[LOCALHOST]/upload
|
||||
"
|
||||
);
|
||||
// Test that it works with credentials
|
||||
uv_snapshot!(context.filters(), context.publish()
|
||||
.current_dir(&context.temp_dir)
|
||||
.arg(&wheel)
|
||||
.arg("--index")
|
||||
.arg("private-index")
|
||||
.env("UV_INDEX_PRIVATE_INDEX_USERNAME", "username")
|
||||
.env("UV_INDEX_PRIVATE_INDEX_PASSWORD", "secret")
|
||||
.arg("--trusted-publishing")
|
||||
.arg("never"),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Publishing 1 file to http://[LOCALHOST]/upload
|
||||
File astral_test_private-0.1.0-py3-none-any.whl already exists, skipping
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue