Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/uv-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[
),
// Trim end-of-line whitespaces, to allow removing them on save.
(r"([^\s])[ \t]+(\r?\n)", "$1$2"),
// Certificate overrides and their contents depend on the host environment.
(
r"(?ms)^([ \t]*custom_certificates: )(?:None|Some\(\n.*?^[ \t]*\),\n[ \t]*\)),",
"${1}[CERTIFICATES],",
),
// Filter SSL certificate loading debug messages (environment-dependent)
(r"DEBUG Loaded \d+ certificate\(s\) from [^\n]+\n", ""),
];
Expand Down
16 changes: 3 additions & 13 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ use uv_types::SourceTreeEditablePolicy;
use uv_warnings::warn_user;
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache, WorkspaceErrorKind};

use crate::base_client_builder;
use crate::child::run_to_completion;

/// GitHub Gist API response structure
Expand Down Expand Up @@ -1472,19 +1473,8 @@ impl ParsedRunCommand {
Ok((script, run_command))
}
Self::PendingRemote(remote_command) => {
let settings = GlobalSettings::resolve(global_args, filesystem, environment)?;
let client_builder = BaseClientBuilder::new(
settings.network_settings.connectivity,
settings.network_settings.system_certs,
settings.network_settings.allow_insecure_host,
settings.preview,
settings.network_settings.read_timeout,
settings.network_settings.connect_timeout,
settings.network_settings.retries,
)
.http_proxy(settings.network_settings.http_proxy)
.https_proxy(settings.network_settings.https_proxy)
.no_proxy(settings.network_settings.no_proxy);
let settings = GlobalSettings::resolve(global_args, filesystem, environment, None)?;
let client_builder = base_client_builder(&settings);

let (url, downloaded_script, args) =
remote_command.download(&client_builder).await?;
Expand Down
62 changes: 31 additions & 31 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use uv_cli::{
PythonNamespace, SelfCommand, SelfNamespace, ToolCommand, ToolNamespace, TopLevelArgs,
WorkspaceCommand, WorkspaceNamespace, compat::CompatArgs, options::ArgumentError,
};
use uv_client::{BaseClientBuilder, Certificates};
use uv_client::BaseClientBuilder;
use uv_configuration::min_stack_size;
use uv_flags::EnvironmentFlags;
use uv_fs::{CWD, Simplified, normalize_path};
Expand Down Expand Up @@ -68,6 +68,29 @@ mod logging;
pub(crate) mod printer;
pub(crate) mod settings;

/// Construct the shared HTTP client builder from the resolved global settings.
pub(crate) fn base_client_builder<'a>(globals: &GlobalSettings) -> BaseClientBuilder<'a> {
let client_builder = BaseClientBuilder::new(
globals.network_settings.connectivity,
globals.network_settings.system_certs,
globals.network_settings.allow_insecure_host.clone(),
globals.preview,
globals.network_settings.read_timeout,
globals.network_settings.connect_timeout,
globals.network_settings.retries,
)
.cache_read_concurrency(globals.concurrency.cache_reads)
.http_proxy(globals.network_settings.http_proxy.clone())
.https_proxy(globals.network_settings.https_proxy.clone())
.no_proxy(globals.network_settings.no_proxy.clone());

if let Some(certificates) = &globals.network_settings.custom_certificates {
client_builder.custom_certificates(certificates.clone())
} else {
client_builder
}
}

/// Whether to initialize process-global state.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[doc(hidden)]
Expand Down Expand Up @@ -461,11 +484,17 @@ pub async fn run(cli: Cli, global_initialization: GlobalInitialization) -> Resul
.map(FilesystemOptions::from)
.combine(filesystem);

let custom_certificate_file = match &*cli.command {
Commands::Pip(PipNamespace { cert, .. }) => cert.as_deref(),
_ => None,
};

// Resolve the global settings.
let globals = GlobalSettings::resolve(
&cli.top_level.global_args,
filesystem.as_ref(),
&environment,
custom_certificate_file,
)?;

if global_initialization.needs_initialization() {
Expand Down Expand Up @@ -607,37 +636,8 @@ pub async fn run(cli: Cli, global_initialization: GlobalInitialization) -> Resul
WorkspaceCache::default()
};

// Configure custom CA certificates from `--cert` or from the environment (`SSL_CERT_FILE` and
// `SSL_CERT_DIR`).
// Like pip, an explicit certificate bundle overrides the environment certificate sources.
let custom_certificates = if let Commands::Pip(PipNamespace {
cert: Some(cert), ..
}) = &*cli.command
{
Some(Certificates::from_file(cert)?)
} else {
Certificates::from_env()
};

// Configure the global network settings.
let client_builder = BaseClientBuilder::new(
globals.network_settings.connectivity,
globals.network_settings.system_certs,
globals.network_settings.allow_insecure_host.clone(),
globals.preview,
globals.network_settings.read_timeout,
globals.network_settings.connect_timeout,
globals.network_settings.retries,
)
.cache_read_concurrency(globals.concurrency.cache_reads)
.http_proxy(globals.network_settings.http_proxy.clone())
.https_proxy(globals.network_settings.https_proxy.clone())
.no_proxy(globals.network_settings.no_proxy.clone());
let client_builder = if let Some(certificates) = custom_certificates {
client_builder.custom_certificates(certificates)
} else {
client_builder
};
let client_builder = base_client_builder(&globals);

match *cli.command {
Commands::Auth(AuthNamespace {
Expand Down
16 changes: 13 additions & 3 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::env::VarError;
use std::fmt;
use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process;
use std::str::FromStr;
use std::time::Duration;
Expand Down Expand Up @@ -32,7 +32,7 @@ use uv_cli::{
resolver_installer_options, resolver_options,
},
};
use uv_client::Connectivity;
use uv_client::{Certificates, Connectivity};
use uv_configuration::{
BuildIsolation, BuildOptions, Concurrency, DependencyGroups, DevMode, DryRun, EditableMode,
EnvFile, ExcludeDependency, ExportFormat, ExtrasSpecification, GitLfsSetting, HashCheckingMode,
Expand Down Expand Up @@ -98,8 +98,10 @@ impl GlobalSettings {
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
custom_certificate_file: Option<&Path>,
) -> anyhow::Result<Self> {
let network_settings = NetworkSettings::resolve(args, workspace, environment)?;
let network_settings =
NetworkSettings::resolve(args, workspace, environment, custom_certificate_file)?;
let python_preference = resolve_python_preference(args, workspace, environment)?;
let color = resolve_color(args);
Ok(Self {
Expand Down Expand Up @@ -273,6 +275,7 @@ pub(crate) struct NetworkSettings {
pub(super) connectivity: Connectivity,
pub(super) offline: Flag,
pub(super) system_certs: bool,
pub(super) custom_certificates: Option<Certificates>,
pub(super) http_proxy: Option<ProxyUrl>,
pub(super) https_proxy: Option<ProxyUrl>,
pub(super) no_proxy: Option<Vec<String>>,
Expand All @@ -288,6 +291,7 @@ impl NetworkSettings {
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
custom_certificate_file: Option<&Path>,
) -> anyhow::Result<Self> {
// Resolve offline flag from CLI, environment variable, and workspace config.
// Precedence: CLI > Env var > Workspace config > default (false).
Expand Down Expand Up @@ -388,10 +392,16 @@ impl NetworkSettings {
let https_proxy = workspace.and_then(|workspace| workspace.globals.https_proxy.clone());
let no_proxy = workspace.and_then(|workspace| workspace.globals.no_proxy.clone());

let custom_certificates = custom_certificate_file
.map(Certificates::from_file)
.transpose()?
.or_else(Certificates::from_env);

Ok(Self {
connectivity,
offline,
system_certs,
custom_certificates,
http_proxy,
https_proxy,
no_proxy,
Expand Down
21 changes: 21 additions & 0 deletions crates/uv/tests/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4641,6 +4641,27 @@ fn run_remote_pep723_script() {
");
}

#[test]
fn run_remote_pep723_script_with_nonexistent_ssl_cert_file() {
let context = uv_test::test_context!("3.12");

uv_snapshot!(context.filters(), context.run()
.arg("https://raw.githubusercontent.com/astral-sh/uv/df45b9ac2584824309ff29a6a09421055ad730f6/scripts/uv-run-remote-script-test.py")
.arg(EnvVars::CI)
.env(EnvVars::SSL_CERT_FILE, context.temp_dir.join("missing.pem"))
.env(EnvVars::UV_HTTP_RETRIES, "0")
.env_remove(EnvVars::SSL_CERT_DIR)
.env_remove(EnvVars::UV_NATIVE_TLS)
.env_remove(EnvVars::UV_SYSTEM_CERTS), @"
exit_code: 2 (failure)
----- stderr -----
warning: Invalid `SSL_CERT_FILE`. Path does not exist: [TEMP_DIR]/missing.pem. No default certificates will be trusted.
error: error sending request for url (https://raw.githubusercontent.com/astral-sh/uv/df45b9ac2584824309ff29a6a09421055ad730f6/scripts/uv-run-remote-script-test.py)
Caused by: client error (Connect)
Caused by: invalid peer certificate: UnknownIssuer
");
}

#[test]
fn run_remote_requirements_offline_redacts_credentials() -> Result<()> {
let context = uv_test::test_context!("3.12");
Expand Down
8 changes: 7 additions & 1 deletion crates/uv/tests/sync/show_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ fn pip_compile_baseline() {
connectivity: Online,
offline: Disabled,
system_certs: false,
custom_certificates: [CERTIFICATES],
http_proxy: None,
https_proxy: None,
no_proxy: None,
Expand Down Expand Up @@ -247,6 +248,7 @@ fn publish_resolved_settings() -> anyhow::Result<()> {
connectivity: Online,
offline: Disabled,
system_certs: false,
custom_certificates: [CERTIFICATES],
http_proxy: None,
https_proxy: None,
no_proxy: None,
Expand Down Expand Up @@ -413,6 +415,7 @@ fn pip_install_baseline() {
connectivity: Online,
offline: Disabled,
system_certs: false,
custom_certificates: [CERTIFICATES],
http_proxy: None,
https_proxy: None,
no_proxy: None,
Expand Down Expand Up @@ -594,6 +597,7 @@ fn lock_baseline() {
connectivity: Online,
offline: Disabled,
system_certs: false,
custom_certificates: [CERTIFICATES],
http_proxy: None,
https_proxy: None,
no_proxy: None,
Expand Down Expand Up @@ -714,6 +718,7 @@ fn version_baseline() {
connectivity: Online,
offline: Disabled,
system_certs: false,
custom_certificates: [CERTIFICATES],
http_proxy: None,
https_proxy: None,
no_proxy: None,
Expand Down Expand Up @@ -849,6 +854,7 @@ fn tool_install_baseline() {
connectivity: Online,
offline: Disabled,
system_certs: false,
custom_certificates: [CERTIFICATES],
http_proxy: None,
https_proxy: None,
no_proxy: None,
Expand Down Expand Up @@ -4307,9 +4313,9 @@ fn system_certs_config_aliases() -> anyhow::Result<()> {
offline: Disabled,
- system_certs: false,
+ system_certs: true,
custom_certificates: [CERTIFICATES],
http_proxy: None,
https_proxy: None,
no_proxy: None,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this dropped just because the diff is abbreviated, so the addition of custom_certificates bumped it off

...
"
);
Expand Down
Loading