Skip to content

Commit 5f2871e

Browse files
Support Gitlab CI/CD as a trusted publisher (#15583)
Co-authored-by: William Woodruff <[email protected]>
1 parent cbb713f commit 5f2871e

File tree

15 files changed

+296
-152
lines changed

15 files changed

+296
-152
lines changed

Cargo.lock

Lines changed: 28 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ uv-virtualenv = { path = "crates/uv-virtualenv" }
7777
uv-warnings = { path = "crates/uv-warnings" }
7878
uv-workspace = { path = "crates/uv-workspace" }
7979

80+
ambient-id = { version = "0.0.5" }
8081
anstream = { version = "0.6.15" }
8182
anyhow = { version = "1.0.89" }
8283
arcstr = { version = "1.2.0" }

crates/uv-cli/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6635,11 +6635,12 @@ pub struct PublishArgs {
66356635
)]
66366636
pub token: Option<String>,
66376637

6638-
/// Configure using trusted publishing through GitHub Actions.
6638+
/// Configure trusted publishing.
66396639
///
6640-
/// By default, uv checks for trusted publishing when running in GitHub Actions, but ignores it
6641-
/// if it isn't configured or the workflow doesn't have enough permissions (e.g., a pull request
6642-
/// from a fork).
6640+
/// By default, uv checks for trusted publishing when running in a supported environment, but
6641+
/// ignores it if it isn't configured.
6642+
///
6643+
/// uv's supported environments for trusted publishing include GitHub Actions and GitLab CI/CD.
66436644
#[arg(long)]
66446645
pub trusted_publishing: Option<TrustedPublishing>,
66456646

crates/uv-configuration/src/trusted_publishing.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use serde::{Deserialize, Serialize};
55
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
66
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
77
pub enum TrustedPublishing {
8-
/// Try trusted publishing when we're already in GitHub Actions, continue if that fails.
8+
/// Attempt trusted publishing when we're in a supported environment, continue if that fails.
9+
///
10+
/// Supported environments include GitHub Actions and GitLab CI/CD.
911
#[default]
1012
Automatic,
1113
// Force trusted publishing.

crates/uv-publish/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ uv-redacted = { workspace = true }
2727
uv-static = { workspace = true }
2828
uv-warnings = { workspace = true }
2929

30+
ambient-id = { workspace = true }
3031
astral-tokio-tar = { workspace = true }
3132
async-compression = { workspace = true }
3233
base64 = { workspace = true }
@@ -42,12 +43,17 @@ serde = { workspace = true, features = ["derive"] }
4243
serde_json = { workspace = true }
4344
thiserror = { workspace = true }
4445
tokio = { workspace = true }
45-
tokio-util = { workspace = true , features = ["io"] }
46+
tokio-util = { workspace = true, features = ["io"] }
4647
tracing = { workspace = true }
4748
url = { workspace = true }
4849

4950
[dev-dependencies]
5051
insta = { workspace = true }
5152

53+
[features]
54+
# Test only feature to enable non-HTTPS URL handling
55+
# in unit tests.
56+
test = []
57+
5258
[lints]
5359
workspace = true

crates/uv-publish/src/lib.rs

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod trusted_publishing;
33
use std::path::{Path, PathBuf};
44
use std::sync::Arc;
55
use std::time::{Duration, SystemTime};
6-
use std::{env, fmt, io};
6+
use std::{fmt, io};
77

88
use fs_err::tokio::File;
99
use futures::TryStreamExt;
@@ -21,7 +21,6 @@ use tokio::io::{AsyncReadExt, BufReader};
2121
use tokio::sync::Semaphore;
2222
use tokio_util::io::ReaderStream;
2323
use tracing::{Level, debug, enabled, trace, warn};
24-
use trusted_publishing::TrustedPublishingToken;
2524
use url::Url;
2625

2726
use uv_auth::{Credentials, PyxTokenStore};
@@ -38,10 +37,9 @@ use uv_fs::{ProgressReader, Simplified};
3837
use uv_metadata::read_metadata_async_seek;
3938
use uv_pypi_types::{HashAlgorithm, HashDigest, Metadata23, MetadataError};
4039
use uv_redacted::DisplaySafeUrl;
41-
use uv_static::EnvVars;
42-
use uv_warnings::{warn_user, warn_user_once};
40+
use uv_warnings::warn_user;
4341

44-
use crate::trusted_publishing::TrustedPublishingError;
42+
use crate::trusted_publishing::{TrustedPublishingError, TrustedPublishingToken};
4543

4644
#[derive(Error, Debug)]
4745
pub enum PublishError {
@@ -324,26 +322,20 @@ pub async fn check_trusted_publishing(
324322
{
325323
return Ok(TrustedPublishResult::Skipped);
326324
}
327-
// If we aren't in GitHub Actions, we can't use trusted publishing.
328-
if env::var(EnvVars::GITHUB_ACTIONS) != Ok("true".to_string()) {
329-
return Ok(TrustedPublishResult::Skipped);
330-
}
331-
// We could check for credentials from the keyring or netrc the auth middleware first, but
332-
// given that we are in GitHub Actions we check for trusted publishing first.
333-
debug!(
334-
"Running on GitHub Actions without explicit credentials, checking for trusted publishing"
335-
);
325+
326+
debug!("Attempting to get a token for trusted publishing");
327+
// Attempt to get a token for trusted publishing.
336328
match trusted_publishing::get_token(registry, client.for_host(registry).raw_client())
337329
.await
338330
{
339-
Ok(token) => Ok(TrustedPublishResult::Configured(token)),
340-
Err(err) => {
341-
// TODO(konsti): It would be useful if we could differentiate between actual errors
342-
// such as connection errors and warn for them while ignoring errors from trusted
343-
// publishing not being configured.
344-
debug!("Could not obtain trusted publishing credentials, skipping: {err}");
345-
Ok(TrustedPublishResult::Ignored(err))
346-
}
331+
// Success: we have a token for trusted publishing.
332+
Ok(Some(token)) => Ok(TrustedPublishResult::Configured(token)),
333+
// Failed to discover an ambient OIDC token.
334+
Ok(None) => Ok(TrustedPublishResult::Ignored(
335+
TrustedPublishingError::NoToken,
336+
)),
337+
// Hard failure during OIDC discovery or token exchange.
338+
Err(err) => Ok(TrustedPublishResult::Ignored(err)),
347339
}
348340
}
349341
TrustedPublishing::Always => {
@@ -363,15 +355,15 @@ pub async fn check_trusted_publishing(
363355
return Err(PublishError::MixedCredentials(conflicts.join(" and ")));
364356
}
365357

366-
if env::var(EnvVars::GITHUB_ACTIONS) != Ok("true".to_string()) {
367-
warn_user_once!(
368-
"Trusted publishing was requested, but you're not in GitHub Actions."
369-
);
370-
}
371-
372-
let token =
358+
let Some(token) =
373359
trusted_publishing::get_token(registry, client.for_host(registry).raw_client())
374-
.await?;
360+
.await?
361+
else {
362+
return Err(PublishError::TrustedPublishing(
363+
TrustedPublishingError::NoToken,
364+
));
365+
};
366+
375367
Ok(TrustedPublishResult::Configured(token))
376368
}
377369
TrustedPublishing::Never => Ok(TrustedPublishResult::Skipped),

0 commit comments

Comments
 (0)