Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recommend --native-tls on SSL errors #10605

Merged
merged 1 commit into from
Jan 14, 2025
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
46 changes: 37 additions & 9 deletions crates/uv-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ impl Error {
matches!(err.kind(), std::io::ErrorKind::NotFound)
}

/// Returns `true` if the error is due to an SSL error.
pub fn is_ssl(&self) -> bool {
matches!(&*self.kind, ErrorKind::WrappedReqwestError(.., err) if err.is_ssl())
}

/// Returns `true` if the error is due to the server not supporting HTTP range requests.
pub fn is_http_range_requests_unsupported(&self) -> bool {
match &*self.kind {
Expand Down Expand Up @@ -260,13 +265,9 @@ impl ErrorKind {
pub struct WrappedReqwestError(reqwest_middleware::Error);

impl WrappedReqwestError {
/// Check if the error chain contains a reqwest error that looks like this:
/// * error sending request for url (...)
/// * client error (Connect)
/// * dns error: failed to lookup address information: Name or service not known
/// * failed to lookup address information: Name or service not known
fn is_likely_offline(&self) -> bool {
let reqwest_err = match &self.0 {
/// Return the inner [`reqwest::Error`] from the error chain, if it exists.
fn inner(&self) -> Option<&reqwest::Error> {
match &self.0 {
reqwest_middleware::Error::Reqwest(err) => Some(err),
reqwest_middleware::Error::Middleware(err) => err.chain().find_map(|err| {
if let Some(err) = err.downcast_ref::<reqwest::Error>() {
Expand All @@ -279,9 +280,16 @@ impl WrappedReqwestError {
None
}
}),
};
}
}

if let Some(reqwest_err) = reqwest_err {
/// Check if the error chain contains a `reqwest` error that looks like this:
/// * error sending request for url (...)
/// * client error (Connect)
/// * dns error: failed to lookup address information: Name or service not known
/// * failed to lookup address information: Name or service not known
fn is_likely_offline(&self) -> bool {
if let Some(reqwest_err) = self.inner() {
if !reqwest_err.is_connect() {
return false;
}
Expand All @@ -297,6 +305,26 @@ impl WrappedReqwestError {
}
false
}

/// Check if the error chain contains a `reqwest` error that looks like this:
/// * invalid peer certificate: `UnknownIssuer`
fn is_ssl(&self) -> bool {
if let Some(reqwest_err) = self.inner() {
if !reqwest_err.is_connect() {
return false;
}
// Self is "error sending request for url", the first source is "error trying to connect",
// the second source is "dns error". We have to check for the string because hyper errors
// are opaque.
if std::error::Error::source(&reqwest_err)
.and_then(|err| err.source())
.is_some_and(|err| err.to_string().starts_with("invalid peer certificate: "))
{
return true;
}
}
false
}
}

impl From<reqwest::Error> for WrappedReqwestError {
Expand Down
60 changes: 56 additions & 4 deletions crates/uv/src/commands/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,37 @@ static SUGGESTIONS: LazyLock<FxHashMap<PackageName, PackageName>> = LazyLock::ne
pub(crate) struct OperationDiagnostic {
/// The hint to display to the user upon resolution failure.
pub(crate) hint: Option<String>,
/// Whether native TLS is enabled.
pub(crate) native_tls: bool,
/// The context to display to the user upon resolution failure.
pub(crate) context: Option<&'static str>,
}

impl OperationDiagnostic {
/// Create an [`OperationDiagnostic`] with the given native TLS setting.
#[must_use]
pub(crate) fn native_tls(native_tls: bool) -> Self {
Self {
native_tls,
..Default::default()
}
}

/// Set the hint to display to the user upon resolution failure.
#[must_use]
pub(crate) fn with_hint(hint: String) -> Self {
pub(crate) fn with_hint(self, hint: String) -> Self {
Self {
hint: Some(hint),
..Default::default()
..self
}
}

/// Set the context to display to the user upon resolution failure.
#[must_use]
pub(crate) fn with_context(context: &'static str) -> Self {
pub(crate) fn with_context(self, context: &'static str) -> Self {
Self {
context: Some(context),
..Default::default()
..self
}
}

Expand Down Expand Up @@ -106,6 +117,12 @@ impl OperationDiagnostic {
Some(pip::operations::Error::Requirements(err))
}
}
pip::operations::Error::Resolve(uv_resolver::ResolveError::Client(err))
if !self.native_tls && err.is_ssl() =>
{
native_tls_hint(err);
None
}
err => Some(err),
}
}
Expand Down Expand Up @@ -236,6 +253,41 @@ pub(crate) fn no_solution_hint(err: uv_resolver::NoSolutionError, help: String)
anstream::eprint!("{report:?}");
}

/// Render a [`uv_resolver::NoSolutionError`] with a help message.
pub(crate) fn native_tls_hint(err: uv_client::Error) {
#[derive(Debug, miette::Diagnostic)]
#[diagnostic()]
struct Error {
/// The underlying error.
err: uv_client::Error,

/// The help message to display.
#[help]
help: String,
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.err)
}
}

impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.err.source()
}
}

let report = miette::Report::new(Error {
err,
help: format!(
"Consider enabling use of system TLS certificates with the `{}` command-line flag",
"--native-tls".green()
),
});
anstream::eprint!("{report:?}");
}

/// Format a [`DerivationChain`] as a human-readable error message.
fn format_chain(name: &PackageName, version: Option<&Version>, chain: &DerivationChain) -> String {
/// Format a step in the [`DerivationChain`] as a human-readable error message.
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ pub(crate) async fn pip_compile(
{
Ok(resolution) => resolution,
Err(err) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ pub(crate) async fn pip_install(
{
Ok(graph) => Resolution::from(graph),
Err(err) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -462,7 +462,7 @@ pub(crate) async fn pip_install(
{
Ok(_) => {}
Err(err) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ pub(crate) async fn pip_sync(
{
Ok(resolution) => Resolution::from(resolution),
Err(err) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -407,7 +407,7 @@ pub(crate) async fn pip_sync(
{
Ok(_) => {}
Err(err) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ pub(crate) async fn add(
let _ = snapshot.revert();
}
match err {
ProjectError::Operation(err) => diagnostics::OperationDiagnostic::with_hint(format!("If you want to add the package regardless of the failed resolution, provide the `{}` flag to skip locking and syncing.", "--frozen".green()))
ProjectError::Operation(err) => diagnostics::OperationDiagnostic::native_tls(native_tls).with_hint(format!("If you want to add the package regardless of the failed resolution, provide the `{}` flag to skip locking and syncing.", "--frozen".green()))
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
err => Err(err.into()),
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ pub(crate) async fn export(
{
Ok(result) => result.into_lock(),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
8 changes: 5 additions & 3 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,11 @@ pub(crate) async fn lock(

Ok(ExitStatus::Success)
}
Err(ProjectError::Operation(err)) => diagnostics::OperationDiagnostic::default()
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
Err(ProjectError::Operation(err)) => {
diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Err(err) => Err(err.into()),
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ pub(crate) async fn remove(
{
Ok(result) => result.into_lock(),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -349,7 +349,7 @@ pub(crate) async fn remove(
{
Ok(()) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
13 changes: 8 additions & 5 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ pub(crate) async fn run(
let environment = match result {
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::with_context("script")
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("script")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -415,7 +416,8 @@ pub(crate) async fn run(
let environment = match result {
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::with_context("script")
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("script")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -738,7 +740,7 @@ pub(crate) async fn run(
{
Ok(result) => result,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -819,7 +821,7 @@ pub(crate) async fn run(
{
Ok(()) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -972,7 +974,8 @@ pub(crate) async fn run(
let environment = match result {
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::with_context("`--with`")
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("`--with`")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pub(crate) async fn sync(
{
Ok(result) => result.into_lock(),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -236,7 +236,7 @@ pub(crate) async fn sync(
{
Ok(()) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ pub(crate) async fn tree(
{
Ok(result) => result.into_lock(),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
8 changes: 4 additions & 4 deletions crates/uv/src/commands/tool/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ pub(crate) async fn install(
{
Ok(update) => update.into_environment(),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down Expand Up @@ -491,7 +491,7 @@ pub(crate) async fn install(
.await
.ok()
.flatten() else {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
};
Expand Down Expand Up @@ -520,7 +520,7 @@ pub(crate) async fn install(
{
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
Expand Down Expand Up @@ -563,7 +563,7 @@ pub(crate) async fn install(
}) {
Ok(environment) => environment,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::default()
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/tool/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ pub(crate) async fn run(
let (from, environment) = match result {
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::with_context("tool")
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("tool")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Expand Down
Loading