diff --git a/Cargo.lock b/Cargo.lock index db97554c195..3d2cab7fc67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1421,6 +1421,7 @@ dependencies = [ "crates_io_docs_rs", "crates_io_env_vars", "crates_io_github", + "crates_io_heroku", "crates_io_index", "crates_io_linecount", "crates_io_markdown", @@ -1570,6 +1571,7 @@ version = "0.0.0" dependencies = [ "anyhow", "chrono", + "crates_io_heroku", "crates_io_test_db", "diesel", "diesel-async", @@ -1645,6 +1647,14 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "crates_io_heroku" +version = "0.0.0" +dependencies = [ + "anyhow", + "crates_io_env_vars", +] + [[package]] name = "crates_io_index" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index e5b0b550e1e..3f3756ab29c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ crates_io_diesel_helpers = { path = "crates/crates_io_diesel_helpers" } crates_io_docs_rs = { path = "crates/crates_io_docs_rs" } crates_io_env_vars = { path = "crates/crates_io_env_vars" } crates_io_github = { path = "crates/crates_io_github" } +crates_io_heroku = { path = "crates/crates_io_heroku" } crates_io_index = { path = "crates/crates_io_index" } crates_io_linecount = { path = "crates/crates_io_linecount" } crates_io_markdown = { path = "crates/crates_io_markdown" } diff --git a/crates/crates_io_database_dump/Cargo.toml b/crates/crates_io_database_dump/Cargo.toml index 984b8783a81..da0c229b65c 100644 --- a/crates/crates_io_database_dump/Cargo.toml +++ b/crates/crates_io_database_dump/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] anyhow = "=1.0.100" chrono = { version = "=0.4.42", default-features = false, features = ["clock", "serde"] } +crates_io_heroku = { path = "../crates_io_heroku" } flate2 = "=1.1.5" minijinja = "=2.12.0" serde = { version = "=1.0.228", features = ["derive"] } diff --git a/crates/crates_io_database_dump/src/lib.rs b/crates/crates_io_database_dump/src/lib.rs index c1f9e06c04a..7709278b992 100644 --- a/crates/crates_io_database_dump/src/lib.rs +++ b/crates/crates_io_database_dump/src/lib.rs @@ -70,8 +70,10 @@ impl DumpDirectory { } let metadata = Metadata { timestamp: &self.timestamp, - crates_io_commit: std::env::var("HEROKU_SLUG_COMMIT") - .unwrap_or_else(|_| "unknown".to_owned()), + crates_io_commit: crates_io_heroku::commit() + .ok() + .flatten() + .unwrap_or_else(|| "unknown".to_owned()), }; let path = self.path().join("metadata.json"); debug!(?path, "Writing metadata.json file…"); diff --git a/crates/crates_io_heroku/Cargo.toml b/crates/crates_io_heroku/Cargo.toml new file mode 100644 index 00000000000..8d330d0374a --- /dev/null +++ b/crates/crates_io_heroku/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "crates_io_heroku" +version = "0.0.0" +license = "MIT OR Apache-2.0" +edition = "2024" + +[lints] +workspace = true + +[dependencies] +anyhow = "=1.0.100" +crates_io_env_vars = { path = "../crates_io_env_vars" } diff --git a/crates/crates_io_heroku/README.md b/crates/crates_io_heroku/README.md new file mode 100644 index 00000000000..ee231d8ee63 --- /dev/null +++ b/crates/crates_io_heroku/README.md @@ -0,0 +1,11 @@ +# crates_io_heroku + +This package contains utilities for accessing Heroku-specific environment +variables. + +When the `runtime-dyno-metadata` Heroku Labs feature is enabled, Heroku +exposes application and environment information through environment variables. +This crate provides convenient functions to access these values. + +For more information, see: + diff --git a/crates/crates_io_heroku/src/lib.rs b/crates/crates_io_heroku/src/lib.rs new file mode 100644 index 00000000000..203b21803dc --- /dev/null +++ b/crates/crates_io_heroku/src/lib.rs @@ -0,0 +1,97 @@ +#![doc = include_str!("../README.md")] + +use crates_io_env_vars::var; + +/// Returns the Git SHA of the currently deployed commit. +/// +/// This function tries `HEROKU_BUILD_COMMIT` first (the current standard), +/// and falls back to `HEROKU_SLUG_COMMIT` (deprecated) if the former is not +/// set. This provides compatibility with both old and new Heroku deployments. +/// +/// Both environment variables are set by Heroku when the appropriate Labs +/// features are enabled (`runtime-dyno-build-metadata` for `HEROKU_BUILD_COMMIT`, +/// `runtime-dyno-metadata` for `HEROKU_SLUG_COMMIT`). +/// +/// Returns `None` if neither variable is set (e.g., in local development). +/// +/// See for more +/// information. +/// +/// # Examples +/// +/// ``` +/// use crates_io_heroku::commit; +/// +/// if let Ok(Some(commit)) = commit() { +/// println!("Running commit: {}", commit); +/// } else { +/// println!("Commit SHA unknown"); +/// } +/// ``` +pub fn commit() -> anyhow::Result> { + // Try the current standard first + if let Some(commit) = build_commit()? { + return Ok(Some(commit)); + } + + // Fall back to the deprecated variable for backward compatibility + slug_commit() +} + +/// Returns the Git SHA of the currently deployed commit. +/// +/// This value comes from the `HEROKU_SLUG_COMMIT` environment variable, +/// which is set by Heroku when the `runtime-dyno-metadata` Labs feature +/// is enabled. If the variable is not set (e.g., in local development +/// or when the feature is disabled), returns `None`. +/// +/// Note: `HEROKU_SLUG_COMMIT` is deprecated by Heroku in favor of +/// `HEROKU_BUILD_COMMIT`, but this function continues to use +/// `HEROKU_SLUG_COMMIT` for backward compatibility with existing +/// deployments. +/// +/// See for more +/// information. +/// +/// # Examples +/// +/// ``` +/// use crates_io_heroku::slug_commit; +/// +/// if let Ok(Some(commit)) = slug_commit() { +/// println!("Running commit: {}", commit); +/// } else { +/// println!("Commit SHA unknown"); +/// } +/// ``` +pub fn slug_commit() -> anyhow::Result> { + var("HEROKU_SLUG_COMMIT") +} + +/// Returns the Git SHA of the currently deployed commit. +/// +/// This value comes from the `HEROKU_BUILD_COMMIT` environment variable, +/// which is set by Heroku when the `runtime-dyno-build-metadata` Labs +/// feature is enabled. If the variable is not set (e.g., in local development +/// or when the feature is disabled), returns `None`. +/// +/// This is the recommended function to use, as `HEROKU_BUILD_COMMIT` is +/// the current standard while `HEROKU_SLUG_COMMIT` is deprecated. +/// +/// See for more +/// information. +/// +/// # Examples +/// +/// ``` +/// use crates_io_heroku::build_commit; +/// +/// if let Ok(Some(commit)) = build_commit() { +/// println!("Running commit: {}", commit); +/// } else { +/// println!("Commit SHA unknown"); +/// } +/// ``` +pub fn build_commit() -> anyhow::Result> { + var("HEROKU_BUILD_COMMIT") +} diff --git a/src/config/sentry.rs b/src/config/sentry.rs index 3b0645edf05..b429cf4d3b3 100644 --- a/src/config/sentry.rs +++ b/src/config/sentry.rs @@ -1,5 +1,6 @@ use anyhow::Context; use crates_io_env_vars::{required_var, var, var_parsed}; +use crates_io_heroku::commit; use sentry::IntoDsn; use sentry::types::Dsn; @@ -28,7 +29,7 @@ impl SentryConfig { Ok(Self { dsn, environment, - release: var("HEROKU_SLUG_COMMIT")?, + release: commit()?, traces_sample_rate: var_parsed("SENTRY_TRACES_SAMPLE_RATE")?.unwrap_or(0.0), }) } diff --git a/src/controllers/site_metadata.rs b/src/controllers/site_metadata.rs index 4b9467a5dfd..05f3de22063 100644 --- a/src/controllers/site_metadata.rs +++ b/src/controllers/site_metadata.rs @@ -1,6 +1,7 @@ use crate::app::AppState; use axum::Json; use axum::response::IntoResponse; +use crates_io_heroku::commit; use serde::Serialize; #[derive(Debug, Serialize, utoipa::ToSchema)] @@ -33,7 +34,7 @@ pub struct MetadataResponse<'a> { pub async fn get_site_metadata(state: AppState) -> impl IntoResponse { let read_only = state.config.db.are_all_read_only(); - let deployed_sha = dotenvy::var("HEROKU_SLUG_COMMIT"); + let deployed_sha = commit().ok().flatten(); let deployed_sha = deployed_sha.as_deref().unwrap_or("unknown"); Json(MetadataResponse { diff --git a/src/sentry/mod.rs b/src/sentry/mod.rs index 614a8cceaff..56bc19b1d83 100644 --- a/src/sentry/mod.rs +++ b/src/sentry/mod.rs @@ -12,7 +12,8 @@ use tracing::warn; /// be set if a DSN is provided. /// /// `HEROKU_SLUG_COMMIT`, if present, will be used as the `release` property -/// on all events. +/// on all events. This environment variable is provided by Heroku when the +/// `runtime-dyno-metadata` Labs feature is enabled. pub fn init() -> Option { let config = match SentryConfig::from_environment() { Ok(config) => config,