From 7a4a80a4dd7ccf84ef0b680a8e3dbbc8a39ed9d2 Mon Sep 17 00:00:00 2001 From: fargito Date: Thu, 30 Jul 2026 15:35:15 +0200 Subject: [PATCH 1/3] feat(run-environment): support CircleCI for GitHub repositories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build runs on CircleCI while commits and pull requests live on GitHub, so a run is reported against the GitHub commit and pull request. Detection is `CIRCLECI=true`, and the repository comes from `CIRCLE_REPOSITORY_URL`. CircleCI also builds Bitbucket and GitLab repositories, so the remote's domain is checked and anything but `github.com` is refused with an error naming the domain found. That domain is the only signal available at runtime: `pipeline.project.type` is a pipeline value, interpolated when the config is compiled, so it never reaches the job as an environment variable. `runId` is `CIRCLE_WORKFLOW_ID`, shared by every job and every parallel container of a workflow; the per-job `CIRCLE_WORKFLOW_JOB_ID` would split one workflow into unrelated runs. `runPartId` is `{CIRCLE_JOB}-{CIRCLE_NODE_INDEX}`, to stay unique along both fan-out axes. No commit hash override is needed, as CircleCI checks out the associated commit rather than a synthetic merge ref. Uploads authenticate with an OIDC token the job mints for itself, so they need no `CODSPEED_TOKEN` secret. The token CircleCI exposes in `CIRCLE_OIDC_TOKEN` and `CIRCLE_OIDC_TOKEN_V2` cannot be used: its audience is fixed to the id of the CircleCI organization, while CodSpeed requires its own. Only the `circleci` CLI can request a custom audience — CircleCI publishes no endpoint for it, unlike the `ACTIONS_ID_TOKEN_REQUEST_URL` GitHub Actions provides — so the runner shells out to `circleci run oidc get`, once per upload, as a token expires an hour after it is minted. Whether a job can mint at all is settled by minting a token and throwing it away, before the benchmarks run: a CLI can be installed and still predate `run oidc get`, so probing for the binary would pass and the mint would fail once the benchmarks had run for nothing. A static `CODSPEED_TOKEN` is used as is when one is set, and stays required for a pull request opened from a fork, whose token names the fork. `baseRef` and `sender` are left empty: CircleCI exposes neither a base-branch variable nor the id the repository provider gives the user who triggered a build. Closes COD-2995 Closes COD-3257 --- src/run_environment/circleci/logger.rs | 105 ++++ src/run_environment/circleci/mod.rs | 5 + src/run_environment/circleci/oidc.rs | 47 ++ src/run_environment/circleci/provider.rs | 582 ++++++++++++++++++ ...ll_request_run_environment_metadata-2.snap | 13 + ...pull_request_run_environment_metadata.snap | 17 + src/run_environment/interfaces.rs | 1 + src/run_environment/mod.rs | 5 + src/upload/uploader.rs | 3 + 9 files changed, 778 insertions(+) create mode 100644 src/run_environment/circleci/logger.rs create mode 100644 src/run_environment/circleci/mod.rs create mode 100644 src/run_environment/circleci/oidc.rs create mode 100644 src/run_environment/circleci/provider.rs create mode 100644 src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata-2.snap create mode 100644 src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap diff --git a/src/run_environment/circleci/logger.rs b/src/run_environment/circleci/logger.rs new file mode 100644 index 000000000..95c62d1f4 --- /dev/null +++ b/src/run_environment/circleci/logger.rs @@ -0,0 +1,105 @@ +use console::style; +use log::*; +use simplelog::SharedLogger; +use std::{env, io::Write}; + +use crate::{ + logger::{GroupEvent, get_announcement_event, get_group_event, get_json_event}, + run_environment::logger::should_provider_logger_handle_record, +}; + +/// A logger that prints logs in the format expected by CircleCI +/// +/// CircleCI has no collapsible section markers, so groups are printed as plain +/// headers. +pub struct CircleCILogger { + log_level: LevelFilter, +} + +impl CircleCILogger { + pub fn new() -> Self { + // force activation of colors: CircleCI renders ANSI sequences in its UI, but + // the output is not a TTY so colors would be disabled by default. + console::set_colors_enabled(true); + + let log_level = env::var("CODSPEED_LOG") + .ok() + .and_then(|log_level| log_level.parse::().ok()) + .unwrap_or(log::LevelFilter::Info); + Self { log_level } + } +} + +impl Log for CircleCILogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if !should_provider_logger_handle_record(record) { + return; + } + + let level = record.level(); + let message = record.args(); + + if let Some(group_event) = get_group_event(record) { + match group_event { + GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { + println!("{}", style(name).cyan().bold()); + } + GroupEvent::End => {} + } + return; + } + + if get_json_event(record).is_some() { + return; + } + + if let Some(announcement) = get_announcement_event(record) { + println!("{}", style(announcement).green()); + return; + } + + if level > self.log_level { + return; + } + + match level { + Level::Error => { + println!("{}", style(message).red()); + } + Level::Warn => { + println!("{}", style(message).yellow()); + } + Level::Info => { + println!("{message}"); + } + Level::Debug => { + println!("{}", style(message).cyan()); + } + Level::Trace => { + println!("{}", style(message).magenta()); + } + } + } + + fn flush(&self) { + std::io::stdout().flush().unwrap(); + } +} + +impl SharedLogger for CircleCILogger { + fn level(&self) -> LevelFilter { + self.log_level + } + + fn config(&self) -> Option<&simplelog::Config> { + None + } + + fn as_log(self: Box) -> Box { + Box::new(*self) + } +} diff --git a/src/run_environment/circleci/mod.rs b/src/run_environment/circleci/mod.rs new file mode 100644 index 000000000..4e6609f91 --- /dev/null +++ b/src/run_environment/circleci/mod.rs @@ -0,0 +1,5 @@ +mod logger; +mod oidc; +mod provider; + +pub use provider::CircleCIProvider; diff --git a/src/run_environment/circleci/oidc.rs b/src/run_environment/circleci/oidc.rs new file mode 100644 index 000000000..2d994ec5f --- /dev/null +++ b/src/run_environment/circleci/oidc.rs @@ -0,0 +1,47 @@ +use std::process::Command; + +use crate::prelude::*; + +/// The CLI CircleCI makes available inside jobs. +const CIRCLECI_CLI: &str = "circleci"; + +/// Mints an OIDC token for `audience`. +/// +/// The token CircleCI puts in `CIRCLE_OIDC_TOKEN` and `CIRCLE_OIDC_TOKEN_V2` cannot +/// be used instead: its audience is the id of the CircleCI organization, while +/// CodSpeed requires its own. Requesting the audience is what makes a token minted +/// for another integration unusable against CodSpeed, and vice versa. +/// +/// Errors carry what the CLI itself reported, as only the first error of a chain is +/// shown outside of debug logging: callers should add their advice to that message +/// rather than wrap it. +/// +/// +pub fn mint_token(audience: &str) -> Result { + let claims = serde_json::json!({ "aud": audience }).to_string(); + + let output = Command::new(CIRCLECI_CLI) + .args(["run", "oidc", "get", "--claims", &claims]) + .output() + .map_err(|error| anyhow!("Failed to run the `circleci` CLI: {error}"))?; + + if !output.status.success() { + bail!( + "`circleci run oidc get` failed: {}", + String::from_utf8_lossy(&output.stderr).trim() + ); + } + + // CircleCI does not mask tokens minted this way in the job output, so the token + // must not reach the logs, here or in the callers. + let token = String::from_utf8(output.stdout) + .map_err(|_| anyhow!("The OIDC token minted by CircleCI is not valid UTF-8"))? + .trim() + .to_string(); + + if token.is_empty() { + bail!("`circleci run oidc get` returned an empty token"); + } + + Ok(token) +} diff --git a/src/run_environment/circleci/provider.rs b/src/run_environment/circleci/provider.rs new file mode 100644 index 000000000..8abde5458 --- /dev/null +++ b/src/run_environment/circleci/provider.rs @@ -0,0 +1,582 @@ +use std::collections::BTreeMap; +use std::env; + +use async_trait::async_trait; +use serde_json::Value; +use simplelog::SharedLogger; + +use crate::api_client::CodSpeedAPIClient; +use crate::cli::run::helpers::{ + GitRemote, find_repository_root, get_env_variable, parse_git_remote, +}; +use crate::executor::config::OrchestratorConfig; +use crate::prelude::*; +use crate::run_environment::interfaces::{RepositoryProvider, RunEnvironmentMetadata, RunEvent}; +use crate::run_environment::provider::{RunEnvironmentDetector, RunEnvironmentProvider}; +use crate::run_environment::{RunEnvironment, RunPart}; + +use super::logger::CircleCILogger; +use super::oidc; + +#[derive(Debug)] +pub struct CircleCIProvider { + owner: String, + repository: String, + ref_: String, + head_ref: Option, + event: RunEvent, + repository_root_path: String, + workflow_id: String, + job_name: String, + /// Index of this container within a job running with `parallelism`, `0` when unset. + node_index: u32, + /// Number of containers the job runs on, `1` when unset. + node_total: u32, + + /// Whether the build runs on a pull request opened from a fork. + is_forked_pull_request: bool, + + /// Whether uploads authenticate with an OIDC token minted by CircleCI. + /// + /// Decided in [`RunEnvironmentProvider::check_oidc_configuration`], acted on in + /// [`RunEnvironmentProvider::set_oidc_token`]. + uses_oidc: bool, +} + +/// Returns the number of the pull request the build runs on, if any. +/// +/// `CIRCLE_PULL_REQUEST` holds the URL of the pull request (`.../pull/22`) and is +/// only set for pull request builds. `CIRCLE_PR_NUMBER` is a fallback that is only +/// populated for pull requests opened from a fork. +fn get_pr_number() -> Option { + let from_url = env::var("CIRCLE_PULL_REQUEST") + .ok() + .and_then(|url| url.rsplit('/').next()?.parse().ok()); + + from_url.or_else(|| { + env::var("CIRCLE_PR_NUMBER") + .ok() + .and_then(|number| number.parse().ok()) + }) +} + +fn get_ref(pr_number: Option) -> Result { + match pr_number { + Some(pr_number) => Ok(format!("refs/pull/{pr_number}/merge")), + None => Ok(format!("refs/heads/{}", get_env_variable("CIRCLE_BRANCH")?)), + } +} + +/// Returns whether the build runs on a pull request opened from a fork. +/// +/// `CIRCLE_PR_NUMBER` is "only available on forked PRs", so its presence is the signal. +/// +/// +fn is_forked_pull_request() -> bool { + env::var("CIRCLE_PR_NUMBER").is_ok() +} + +fn get_env_number(name: &str, default: u32) -> u32 { + env::var(name) + .ok() + .and_then(|value| value.parse().ok()) + .unwrap_or(default) +} + +impl TryFrom<&OrchestratorConfig> for CircleCIProvider { + type Error = Error; + fn try_from(config: &OrchestratorConfig) -> Result { + if config.repository_override.is_some() { + bail!("Specifying owner and repository from CLI is not supported for CircleCI"); + } + + let pr_number = get_pr_number(); + let repository_url = get_env_variable("CIRCLE_REPOSITORY_URL")?; + let GitRemote { + owner, + repository, + domain, + } = parse_git_remote(&repository_url)?; + + // CircleCI also builds Bitbucket and GitLab repositories, which CodSpeed does not + // support here. The domain of the remote is the only signal available at runtime: + // the explicit `pipeline.project.type` is a pipeline value, interpolated when the + // config is compiled, so it never reaches the job as an environment variable. + // https://circleci.com/docs/reference/variables/ + if domain != "github.com" { + bail!( + "CodSpeed only supports GitHub repositories on CircleCI, but \ + CIRCLE_REPOSITORY_URL points to {domain} ({repository_url})" + ); + } + + let repository_root_path = match find_repository_root(&std::env::current_dir()?) { + Some(mut path) => { + // Add a trailing slash to the path + path.push(""); + path.to_string_lossy().to_string() + } + None => { + // Fallback to the working directory of the job, where CircleCI checks the + // repository out. Its value mirrors the `working_directory` key of the job, + // so it can start with a `~`. + // https://circleci.com/docs/variables/#built-in-environment-variables + let working_directory = get_env_variable("CIRCLE_WORKING_DIRECTORY")?; + format!("{}/", shellexpand::tilde(&working_directory)) + } + }; + + Ok(Self { + owner, + repository, + ref_: get_ref(pr_number)?, + head_ref: if pr_number.is_some() { + Some(get_env_variable("CIRCLE_BRANCH")?) + } else { + None + }, + event: if pr_number.is_some() { + RunEvent::PullRequest + } else { + RunEvent::Push + }, + repository_root_path, + workflow_id: get_env_variable("CIRCLE_WORKFLOW_ID")?, + job_name: get_env_variable("CIRCLE_JOB")?, + node_index: get_env_number("CIRCLE_NODE_INDEX", 0), + node_total: get_env_number("CIRCLE_NODE_TOTAL", 1), + is_forked_pull_request: is_forked_pull_request(), + uses_oidc: false, + }) + } +} + +impl RunEnvironmentDetector for CircleCIProvider { + fn detect() -> bool { + env::var("CIRCLECI") == Ok("true".into()) + } +} + +#[async_trait(?Send)] +impl RunEnvironmentProvider for CircleCIProvider { + fn get_repository_provider(&self) -> RepositoryProvider { + RepositoryProvider::GitHub + } + + fn get_logger(&self) -> Box { + Box::new(CircleCILogger::new()) + } + + fn get_run_environment(&self) -> RunEnvironment { + RunEnvironment::Circleci + } + + fn get_run_environment_metadata(&self) -> Result { + Ok(RunEnvironmentMetadata { + // CircleCI exposes no base branch variable. + base_ref: None, + head_ref: self.head_ref.clone(), + event: self.event.clone(), + owner: self.owner.clone(), + repository: self.repository.clone(), + ref_: self.ref_.clone(), + repository_root_path: self.repository_root_path.clone(), + gh_data: None, + gl_data: None, + local_data: None, + // CircleCI does not provide the sender information. + sender: None, + }) + } + + /// `CIRCLE_WORKFLOW_ID` is shared by every job and every parallel container of a + /// workflow, which makes it the key that groups run parts together. The per-job + /// `CIRCLE_WORKFLOW_JOB_ID` would instead split one workflow into unrelated runs. + /// + /// A workflow fans out along two axes, so the part id has to cover both: its jobs + /// (`CIRCLE_JOB`) and, within a job declaring `parallelism`, its containers + /// (`CIRCLE_NODE_INDEX`). + fn get_run_provider_run_part(&self) -> Option { + Some(RunPart { + run_id: self.workflow_id.clone(), + run_part_id: format!("{}-{}", self.job_name, self.node_index), + job_name: self.job_name.clone(), + metadata: BTreeMap::from([ + ("node-index".to_string(), Value::from(self.node_index)), + ("node-total".to_string(), Value::from(self.node_total)), + ]), + }) + } + + /// Decide how the uploads of this job authenticate. + /// + /// A static `CODSPEED_TOKEN` is used as is when there is one. Otherwise the runner + /// mints an OIDC token itself, which rules out a forked pull request: CircleCI + /// issues a forked build a token that names the fork, and CodSpeed only accepts + /// uploads to the repository its token names. + /// + /// Whether the job can mint at all is settled here by minting a token and throwing + /// it away. Nothing cheaper answers the question: an image may carry no `circleci` + /// CLI, or one too old to know `run oidc get`, and only the CLI knows whether the + /// job is allowed to issue tokens. Asking now rather than at upload time is what + /// keeps an unauthenticated job from being told so only once its benchmarks have + /// run. + fn check_oidc_configuration(&mut self, api_client: &CodSpeedAPIClient) -> Result<()> { + if api_client.token().is_some() { + if !self.is_forked_pull_request { + announcement!( + "You can now authenticate your CircleCI jobs using OpenID Connect (OIDC) tokens instead of `CODSPEED_TOKEN` secrets.\n\ + This makes integrating and authenticating jobs safer and simpler.\n\ + Learn more at https://codspeed.io/docs/integrations/ci/circleci/configuration#oidc-recommended\n" + ); + } + + return Ok(()); + } + + if self.is_forked_pull_request { + bail!( + "Pull requests opened from a fork cannot authenticate with OIDC on CircleCI.\n\ + Set `CODSPEED_TOKEN` for this job instead.\n\ + See https://codspeed.io/docs/integrations/ci/circleci/configuration#authentication" + ); + } + + if let Err(error) = oidc::mint_token(self.get_oidc_audience()) { + bail!( + "{error}\n\ + Unable to mint an OIDC token for authentication. \ + Set `CODSPEED_TOKEN` for this job instead.\n\ + See https://codspeed.io/docs/integrations/ci/circleci/configuration#oidc-recommended" + ); + } + + self.uses_oidc = true; + + Ok(()) + } + + /// Mint the OIDC token authenticating the upload that follows. + /// + /// A token expires an hour after it is minted, which a run can outlast, so each + /// upload gets its own rather than reusing the one of a previous call. + async fn set_oidc_token(&self, api_client: &mut CodSpeedAPIClient) -> Result<()> { + if !self.uses_oidc { + return Ok(()); + } + + let token = oidc::mint_token(self.get_oidc_audience())?; + + debug!("Minted an OIDC token to authenticate the upload"); + api_client.set_token(Some(token)); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use insta::assert_json_snapshot; + use temp_env::{with_var, with_vars}; + + use super::*; + + #[test] + fn test_detect() { + with_var("CIRCLECI", Some("true"), || { + assert!(CircleCIProvider::detect()); + }); + } + + #[test] + fn test_try_from_push_main() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("main")), + ( + "CIRCLE_REPOSITORY_URL", + Some("git@github.com:my-org/adrien-python-test.git"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let provider = CircleCIProvider::try_from(&config).unwrap(); + + assert_eq!(provider.owner, "my-org"); + assert_eq!(provider.repository, "adrien-python-test"); + assert_eq!(provider.ref_, "refs/heads/main"); + assert_eq!(provider.head_ref, None); + assert_eq!(provider.event, RunEvent::Push); + assert_eq!(provider.repository_root_path, "/home/circleci/project/"); + }, + ); + } + + #[test] + fn test_try_from_pull_request() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("feat/codspeed-runner")), + ( + "CIRCLE_PULL_REQUEST", + Some("https://github.com/my-org/adrien-python-test/pull/22"), + ), + ( + "CIRCLE_REPOSITORY_URL", + Some("https://github.com/my-org/adrien-python-test"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let provider = CircleCIProvider::try_from(&config).unwrap(); + + assert_eq!(provider.owner, "my-org"); + assert_eq!(provider.repository, "adrien-python-test"); + assert_eq!(provider.ref_, "refs/pull/22/merge"); + assert_eq!(provider.head_ref, Some("feat/codspeed-runner".into())); + assert_eq!(provider.event, RunEvent::PullRequest); + }, + ); + } + + /// On pull requests opened from a fork, `CIRCLE_PULL_REQUEST` is not set and + /// `CIRCLE_BRANCH` is the `pull/{number}` ref CircleCI checks out. + #[test] + fn test_try_from_fork_pull_request() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("pull/22")), + ("CIRCLE_PR_NUMBER", Some("22")), + ( + "CIRCLE_REPOSITORY_URL", + Some("git@github.com:my-org/adrien-python-test.git"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let provider = CircleCIProvider::try_from(&config).unwrap(); + + assert_eq!(provider.ref_, "refs/pull/22/merge"); + assert_eq!(provider.event, RunEvent::PullRequest); + }, + ); + } + + #[test] + fn test_try_from_non_github_repository() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("main")), + ( + "CIRCLE_REPOSITORY_URL", + Some("git@bitbucket.org:my-org/adrien-python-test.git"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let error = CircleCIProvider::try_from(&config).unwrap_err(); + + assert_eq!( + error.to_string(), + "CodSpeed only supports GitHub repositories on CircleCI, but \ + CIRCLE_REPOSITORY_URL points to bitbucket.org \ + (git@bitbucket.org:my-org/adrien-python-test.git)" + ); + }, + ); + } + + #[test] + fn test_pull_request_run_environment_metadata() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("feat/codspeed-runner")), + ( + "CIRCLE_PULL_REQUEST", + Some("https://github.com/my-org/adrien-python-test/pull/22"), + ), + ( + "CIRCLE_REPOSITORY_URL", + Some("git@github.com:my-org/adrien-python-test.git"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let provider = CircleCIProvider::try_from(&config).unwrap(); + let run_environment_metadata = provider.get_run_environment_metadata().unwrap(); + let run_part = provider.get_run_provider_run_part().unwrap(); + + assert_json_snapshot!(run_environment_metadata); + assert_json_snapshot!(run_part); + }, + ); + } + + /// A job declaring `parallelism` runs on several containers that share + /// `CIRCLE_JOB`, so the node index is what keeps their part ids apart. + #[test] + fn test_run_part_of_parallel_job() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("main")), + ( + "CIRCLE_REPOSITORY_URL", + Some("git@github.com:my-org/adrien-python-test.git"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ("CIRCLE_NODE_INDEX", Some("2")), + ("CIRCLE_NODE_TOTAL", Some("4")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let provider = CircleCIProvider::try_from(&config).unwrap(); + let run_part = provider.get_run_provider_run_part().unwrap(); + + assert_eq!(run_part.run_id, "8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"); + assert_eq!(run_part.job_name, "benchmarks"); + assert_eq!(run_part.run_part_id, "benchmarks-2"); + assert_json_snapshot!(run_part.metadata, @r#" + { + "node-index": 2, + "node-total": 4 + } + "#); + }, + ); + } + + fn api_client(token: Option<&str>) -> CodSpeedAPIClient { + CodSpeedAPIClient::new( + token.map(str::to_string), + "https://gql.codspeed.io/".to_string(), + ) + } + + /// Whether the runner mints a token is not decided from the environment alone: it is + /// decided by trying to mint one, which only a CircleCI job can do. Only the cases + /// settled before that attempt are asserted here. + #[test] + fn test_static_token_is_used_as_is() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("main")), + ( + "CIRCLE_REPOSITORY_URL", + Some("git@github.com:my-org/adrien-python-test.git"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let mut provider = CircleCIProvider::try_from(&config).unwrap(); + + provider + .check_oidc_configuration(&api_client(Some("a-static-token"))) + .unwrap(); + + assert!(!provider.uses_oidc); + }, + ); + } + + #[test] + fn test_forked_pull_request_requires_a_static_token() { + with_vars( + [ + ("CIRCLECI", Some("true")), + ("CIRCLE_BRANCH", Some("pull/22")), + ("CIRCLE_PR_NUMBER", Some("22")), + ( + "CIRCLE_REPOSITORY_URL", + Some("git@github.com:my-org/adrien-python-test.git"), + ), + ("CIRCLE_WORKING_DIRECTORY", Some("/home/circleci/project")), + ( + "CIRCLE_WORKFLOW_ID", + Some("8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d"), + ), + ("CIRCLE_JOB", Some("benchmarks")), + ], + || { + let config = OrchestratorConfig { + ..OrchestratorConfig::test() + }; + let mut provider = CircleCIProvider::try_from(&config).unwrap(); + + let error = provider + .check_oidc_configuration(&api_client(None)) + .unwrap_err(); + + assert_eq!( + error.to_string(), + "Pull requests opened from a fork cannot authenticate with OIDC on CircleCI.\n\ + Set `CODSPEED_TOKEN` for this job instead.\n\ + See https://codspeed.io/docs/integrations/ci/circleci/configuration#authentication" + ); + + assert!(!provider.uses_oidc); + }, + ); + } +} diff --git a/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata-2.snap b/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata-2.snap new file mode 100644 index 000000000..58144a2c1 --- /dev/null +++ b/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata-2.snap @@ -0,0 +1,13 @@ +--- +source: src/run_environment/circleci/provider.rs +expression: run_part +--- +{ + "runId": "8d8f0b2a-1f3e-4b6a-9c2d-0f1e2a3b4c5d", + "runPartId": "benchmarks-0", + "jobName": "benchmarks", + "metadata": { + "node-index": 0, + "node-total": 1 + } +} diff --git a/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap b/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap new file mode 100644 index 000000000..ab99e3aad --- /dev/null +++ b/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap @@ -0,0 +1,17 @@ +--- +source: src/run_environment/circleci/provider.rs +expression: run_environment_metadata +--- +{ + "ref": "refs/pull/22/merge", + "headRef": "feat/codspeed-runner", + "baseRef": null, + "owner": "my-org", + "repository": "adrien-python-test", + "event": "pull_request", + "sender": null, + "ghData": null, + "glData": null, + "localData": null, + "repositoryRootPath": "/home/circleci/project/" +} diff --git a/src/run_environment/interfaces.rs b/src/run_environment/interfaces.rs index 2d2e83383..a3c8d9289 100644 --- a/src/run_environment/interfaces.rs +++ b/src/run_environment/interfaces.rs @@ -29,6 +29,7 @@ pub enum RunEnvironment { GithubActions, GitlabCi, Buildkite, + Circleci, Local, } diff --git a/src/run_environment/mod.rs b/src/run_environment/mod.rs index ddebd46fe..77ba7faa0 100644 --- a/src/run_environment/mod.rs +++ b/src/run_environment/mod.rs @@ -3,6 +3,7 @@ pub mod logger; mod provider; use buildkite::BuildkiteProvider; +use circleci::CircleCIProvider; use github_actions::GitHubActionsProvider; use gitlab_ci::GitLabCIProvider; use local::LocalProvider; @@ -17,6 +18,7 @@ pub use self::provider::RunEnvironmentProvider; // RunEnvironment Provider implementations mod buildkite; +mod circleci; mod github_actions; mod gitlab_ci; mod local; @@ -29,6 +31,9 @@ pub async fn get_provider( if BuildkiteProvider::detect() { let provider = BuildkiteProvider::try_from(config)?; Box::new(provider) + } else if CircleCIProvider::detect() { + let provider = CircleCIProvider::try_from(config)?; + Box::new(provider) } else if GitHubActionsProvider::detect() { let provider = GitHubActionsProvider::try_from(config)?; Box::new(provider) diff --git a/src/upload/uploader.rs b/src/upload/uploader.rs index c24bafe3a..63348dd70 100644 --- a/src/upload/uploader.rs +++ b/src/upload/uploader.rs @@ -155,6 +155,9 @@ async fn retrieve_upload_data( RunEnvironment::GitlabCi => { "Check that the CI job is correctly authenticated. View more at https://codspeed.io/docs/integrations/ci/gitlab-ci/configuration#authentication" } + RunEnvironment::Circleci => { + "Check that the CI job is correctly authenticated. View more at https://codspeed.io/docs/integrations/ci/circleci/configuration#authentication" + } RunEnvironment::Buildkite => { "Check that CODSPEED_TOKEN is set and has the correct value" } From 21b4ee9bb91c09647832c9da86e50a9c8c6461d7 Mon Sep 17 00:00:00 2001 From: fargito Date: Thu, 30 Jul 2026 14:03:22 +0200 Subject: [PATCH 2/3] refactor(upload): drop ghData and glData from the run environment metadata Both carried a run id and a job name that `runPart` already carries for every provider, so the payload named the same job twice, and only ever for GitHub Actions and GitLab CI. Every other provider filled both fields with `None` to say it was neither of them. GitHub Actions and GitLab CI keep the two values as plain fields read when the provider is built, the way CircleCI holds the ones its own run part needs. The payload no longer has the shape version 10 describes, so it becomes version 11. The backend accepts both: it reads either field only when `runPart` is missing, which no version since 6 has been. Refs COD-3257 Co-Authored-By: Claude Opus 5 (1M context) --- src/run_environment/buildkite/provider.rs | 2 - ...pull_request_run_environment_metadata.snap | 2 - src/run_environment/circleci/provider.rs | 2 - ...pull_request_run_environment_metadata.snap | 2 - .../github_actions/provider.rs | 49 +++++++------------ ...pull_request_run_environment_metadata.snap | 5 -- ...__matrix_job_run_environment_metadata.snap | 5 -- ...pull_request_run_environment_metadata.snap | 5 -- src/run_environment/gitlab_ci/provider.rs | 19 ++++--- ...erge_request_run_environment_metadata.snap | 5 -- ...erge_request_run_environment_metadata.snap | 5 -- ...s__push_main_run_environment_metadata.snap | 5 -- src/run_environment/interfaces.rs | 16 ------ src/run_environment/local/provider.rs | 2 - ..._new_falls_back_to_project_repository.snap | 2 - ..._with_github_remote_found_on_codspeed.snap | 2 - ...er__tests__new_without_git_repository.snap | 2 - src/upload/interfaces.rs | 2 +- ...ata__tests__get_local_metadata_hash-2.snap | 4 +- ..._metadata__tests__get_metadata_hash-2.snap | 7 +-- src/upload/upload_metadata.rs | 15 ++---- 21 files changed, 35 insertions(+), 123 deletions(-) diff --git a/src/run_environment/buildkite/provider.rs b/src/run_environment/buildkite/provider.rs index d7c16d611..4b4840248 100644 --- a/src/run_environment/buildkite/provider.rs +++ b/src/run_environment/buildkite/provider.rs @@ -137,8 +137,6 @@ impl RunEnvironmentProvider for BuildkiteProvider { repository: self.repository.clone(), ref_: self.ref_.clone(), repository_root_path: self.repository_root_path.clone(), - gh_data: None, - gl_data: None, local_data: None, sender: None, }) diff --git a/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap b/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap index 95c04c63a..9db641b6c 100644 --- a/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap +++ b/src/run_environment/buildkite/snapshots/codspeed_runner__run_environment__buildkite__provider__tests__pull_request_run_environment_metadata.snap @@ -10,8 +10,6 @@ expression: run_environment_metadata "repository": "adrien-python-test", "event": "pull_request", "sender": null, - "ghData": null, - "glData": null, "localData": null, "repositoryRootPath": "/buildkite/builds/7b10eca7600b-1/my-org/buildkite-test/" } diff --git a/src/run_environment/circleci/provider.rs b/src/run_environment/circleci/provider.rs index 8abde5458..f666e1431 100644 --- a/src/run_environment/circleci/provider.rs +++ b/src/run_environment/circleci/provider.rs @@ -181,8 +181,6 @@ impl RunEnvironmentProvider for CircleCIProvider { repository: self.repository.clone(), ref_: self.ref_.clone(), repository_root_path: self.repository_root_path.clone(), - gh_data: None, - gl_data: None, local_data: None, // CircleCI does not provide the sender information. sender: None, diff --git a/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap b/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap index ab99e3aad..8eeeff771 100644 --- a/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap +++ b/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap @@ -10,8 +10,6 @@ expression: run_environment_metadata "repository": "adrien-python-test", "event": "pull_request", "sender": null, - "ghData": null, - "glData": null, "localData": null, "repositoryRootPath": "/home/circleci/project/" } diff --git a/src/run_environment/github_actions/provider.rs b/src/run_environment/github_actions/provider.rs index 27afc683d..81667f65b 100644 --- a/src/run_environment/github_actions/provider.rs +++ b/src/run_environment/github_actions/provider.rs @@ -15,7 +15,7 @@ use crate::executor::config::OrchestratorConfig; use crate::prelude::*; use crate::request_client::OIDC_CLIENT; use crate::run_environment::interfaces::{ - GhData, RepositoryProvider, RunEnvironmentMetadata, RunEvent, Sender, + RepositoryProvider, RunEnvironmentMetadata, RunEvent, Sender, }; use crate::run_environment::provider::{RunEnvironmentDetector, RunEnvironmentProvider}; use crate::run_environment::{RunEnvironment, RunPart}; @@ -29,7 +29,8 @@ pub struct GitHubActionsProvider { pub head_ref: Option, pub base_ref: Option, pub sender: Option, - pub gh_data: GhData, + pub run_id: String, + pub job_name: String, pub event: RunEvent, pub repository_root_path: String, @@ -135,10 +136,8 @@ impl TryFrom<&OrchestratorConfig> for GitHubActionsProvider { ref_, head_ref, event, - gh_data: GhData { - job: get_env_variable("GITHUB_JOB")?, - run_id: get_env_variable("GITHUB_RUN_ID")?, - }, + run_id: get_env_variable("GITHUB_RUN_ID")?, + job_name: get_env_variable("GITHUB_JOB")?, sender: Some(Sender { login: get_env_variable("GITHUB_ACTOR")?, id: get_env_variable("GITHUB_ACTOR_ID")?, @@ -178,8 +177,6 @@ impl RunEnvironmentProvider for GitHubActionsProvider { base_ref: self.base_ref.clone(), head_ref: self.head_ref.clone(), event: self.event.clone(), - gh_data: Some(self.gh_data.clone()), - gl_data: None, local_data: None, sender: self.sender.clone(), owner: self.owner.clone(), @@ -211,7 +208,7 @@ impl RunEnvironmentProvider for GitHubActionsProvider { /// Plus we are interested in the content of these objects, /// so it makes sense to parse and re-serialize them. fn get_run_provider_run_part(&self) -> Option { - let job_name = self.gh_data.job.clone(); + let job_name = self.job_name.clone(); let mut metadata = BTreeMap::new(); @@ -240,13 +237,13 @@ impl RunEnvironmentProvider for GitHubActionsProvider { format!("{job_name}-{matrix_str}-{strategy_str}") } else { - job_name + job_name.clone() }; Some(RunPart { - run_id: self.gh_data.run_id.clone(), + run_id: self.run_id.clone(), run_part_id, - job_name: self.gh_data.job.clone(), + job_name, metadata, }) } @@ -446,8 +443,8 @@ mod tests { assert_eq!(github_actions_provider.base_ref, Some("main".into())); assert_eq!(github_actions_provider.head_ref, None); assert_eq!(github_actions_provider.event, RunEvent::Push); - assert_eq!(github_actions_provider.gh_data.job, "job"); - assert_eq!(github_actions_provider.gh_data.run_id, "1234567890"); + assert_eq!(github_actions_provider.job_name, "job"); + assert_eq!(github_actions_provider.run_id, "1234567890"); assert_eq!( github_actions_provider.sender.as_ref().unwrap().login, "actor" @@ -663,10 +660,8 @@ mod tests { head_ref: Some("my-branch".into()), base_ref: None, sender: None, - gh_data: GhData { - job: "my_job".into(), - run_id: "123789".into(), - }, + run_id: "123789".into(), + job_name: "my_job".into(), event: RunEvent::Push, repository_root_path: "/home/work/my-repo".into(), is_head_repo_fork: false, @@ -708,10 +703,8 @@ mod tests { head_ref: Some("my-branch".into()), base_ref: None, sender: None, - gh_data: GhData { - job: "my_job".into(), - run_id: "123789".into(), - }, + run_id: "123789".into(), + job_name: "my_job".into(), event: RunEvent::Push, repository_root_path: "/home/work/my-repo".into(), is_head_repo_fork: false, @@ -762,10 +755,8 @@ mod tests { head_ref: Some("my-branch".into()), base_ref: None, sender: None, - gh_data: GhData { - job: "my_job".into(), - run_id: "123789".into(), - }, + run_id: "123789".into(), + job_name: "my_job".into(), event: RunEvent::Push, repository_root_path: "/home/work/my-repo".into(), is_head_repo_fork: false, @@ -814,10 +805,8 @@ mod tests { head_ref: Some("my-branch".into()), base_ref: None, sender: None, - gh_data: GhData { - job: "my_job".into(), - run_id: "123789".into(), - }, + run_id: "123789".into(), + job_name: "my_job".into(), event: RunEvent::Push, repository_root_path: "/home/work/my-repo".into(), is_head_repo_fork: false, diff --git a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap index c721764f9..785a65ded 100644 --- a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap +++ b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__fork_pull_request_run_environment_metadata.snap @@ -13,11 +13,6 @@ expression: run_environment_metadata "id": "19605940", "login": "adriencaccia" }, - "ghData": { - "runId": "6957110437", - "job": "log-env" - }, - "glData": null, "localData": null, "repositoryRootPath": "/home/runner/work/adrien-python-test/adrien-python-test/" } diff --git a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap index b6a8868b4..f49c107f6 100644 --- a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap +++ b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__matrix_job_run_environment_metadata.snap @@ -13,11 +13,6 @@ expression: run_environment_metadata "id": "19605940", "login": "adriencaccia" }, - "ghData": { - "runId": "6957110437", - "job": "log-env" - }, - "glData": null, "localData": null, "repositoryRootPath": "/home/runner/work/adrien-python-test/adrien-python-test/" } diff --git a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap index b6a8868b4..f49c107f6 100644 --- a/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap +++ b/src/run_environment/github_actions/snapshots/codspeed_runner__run_environment__github_actions__provider__tests__pull_request_run_environment_metadata.snap @@ -13,11 +13,6 @@ expression: run_environment_metadata "id": "19605940", "login": "adriencaccia" }, - "ghData": { - "runId": "6957110437", - "job": "log-env" - }, - "glData": null, "localData": null, "repositoryRootPath": "/home/runner/work/adrien-python-test/adrien-python-test/" } diff --git a/src/run_environment/gitlab_ci/provider.rs b/src/run_environment/gitlab_ci/provider.rs index 57271fc36..e93439a91 100644 --- a/src/run_environment/gitlab_ci/provider.rs +++ b/src/run_environment/gitlab_ci/provider.rs @@ -7,7 +7,7 @@ use crate::cli::run::helpers::get_env_variable; use crate::executor::config::OrchestratorConfig; use crate::prelude::*; use crate::run_environment::interfaces::{ - GlData, RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunEvent, Sender, + RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunEvent, Sender, }; use crate::run_environment::provider::RunEnvironmentDetector; use crate::run_environment::{RunEnvironmentProvider, RunPart}; @@ -21,7 +21,8 @@ pub struct GitLabCIProvider { ref_: String, head_ref: Option, base_ref: Option, - gl_data: GlData, + run_id: String, + job_name: String, sender: Sender, event: RunEvent, repository_root_path: String, @@ -106,12 +107,11 @@ impl TryFrom<&OrchestratorConfig> for GitLabCIProvider { }; let run_id = get_env_variable("CI_JOB_ID")?; - let job = get_env_variable("CI_JOB_NAME")?; + let job_name = get_env_variable("CI_JOB_NAME")?; let gitlab_user_id = get_env_variable("GITLAB_USER_ID")?; let gitlab_user_login = get_env_variable("GITLAB_USER_LOGIN")?; - let gl_data = GlData { run_id, job }; let sender = Sender { id: gitlab_user_id, login: gitlab_user_login, @@ -125,7 +125,8 @@ impl TryFrom<&OrchestratorConfig> for GitLabCIProvider { ref_, head_ref, base_ref, - gl_data, + run_id, + job_name, sender, event, repository_root_path, @@ -159,8 +160,6 @@ impl RunEnvironmentProvider for GitLabCIProvider { base_ref: self.base_ref.clone(), head_ref: self.head_ref.clone(), event: self.event.clone(), - gh_data: None, - gl_data: Some(self.gl_data.clone()), local_data: None, sender: Some(self.sender.clone()), owner: self.owner.clone(), @@ -172,9 +171,9 @@ impl RunEnvironmentProvider for GitLabCIProvider { fn get_run_provider_run_part(&self) -> Option { Some(RunPart { - run_id: self.gl_data.run_id.clone(), - run_part_id: self.gl_data.job.clone(), - job_name: self.gl_data.job.clone(), + run_id: self.run_id.clone(), + run_part_id: self.job_name.clone(), + job_name: self.job_name.clone(), metadata: BTreeMap::new(), }) } diff --git a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap index 371fe9f2d..a4854326f 100644 --- a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap +++ b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__fork_merge_request_run_environment_metadata.snap @@ -13,11 +13,6 @@ expression: run_environment_metadata "id": "19605940", "login": "actor" }, - "ghData": null, - "glData": { - "runId": "6957110437", - "job": "build-job" - }, "localData": null, "repositoryRootPath": "/builds/owner/repository" } diff --git a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap index 4a277013c..e10285e72 100644 --- a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap +++ b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__merge_request_run_environment_metadata.snap @@ -13,11 +13,6 @@ expression: run_environment_metadata "id": "19605940", "login": "actor" }, - "ghData": null, - "glData": { - "runId": "6957110437", - "job": "build-job" - }, "localData": null, "repositoryRootPath": "/builds/owner/repository" } diff --git a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap index b2a3c4d7e..28eb8b35f 100644 --- a/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap +++ b/src/run_environment/gitlab_ci/snapshots/codspeed_runner__run_environment__gitlab_ci__provider__tests__push_main_run_environment_metadata.snap @@ -13,11 +13,6 @@ expression: run_environment_metadata "id": "1234567890", "login": "actor" }, - "ghData": null, - "glData": { - "runId": "1234567890", - "job": "job" - }, "localData": null, "repositoryRootPath": "/builds/owner/repository" } diff --git a/src/run_environment/interfaces.rs b/src/run_environment/interfaces.rs index a3c8d9289..3dbf15b6f 100644 --- a/src/run_environment/interfaces.rs +++ b/src/run_environment/interfaces.rs @@ -44,8 +44,6 @@ pub struct RunEnvironmentMetadata { pub repository: String, pub event: RunEvent, pub sender: Option, - pub gh_data: Option, - pub gl_data: Option, pub local_data: Option, pub repository_root_path: String, } @@ -60,20 +58,6 @@ pub enum RunEvent { Local, } -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct GhData { - pub run_id: String, - pub job: String, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct GlData { - pub run_id: String, - pub job: String, -} - #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct LocalData { diff --git a/src/run_environment/local/provider.rs b/src/run_environment/local/provider.rs index 2097a30f8..ab67f69b5 100644 --- a/src/run_environment/local/provider.rs +++ b/src/run_environment/local/provider.rs @@ -351,8 +351,6 @@ impl RunEnvironmentProvider for LocalProvider { base_ref: None, head_ref: self.head_ref.clone(), event: self.event.clone(), - gh_data: None, - gl_data: None, local_data: Some(LocalData { expected_run_parts_count: self.expected_run_parts_count, }), diff --git a/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_falls_back_to_project_repository.snap b/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_falls_back_to_project_repository.snap index 9404b76df..a0124d5e3 100644 --- a/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_falls_back_to_project_repository.snap +++ b/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_falls_back_to_project_repository.snap @@ -10,8 +10,6 @@ expression: run_environment_metadata "repository": "local-runs", "event": "local", "sender": null, - "ghData": null, - "glData": null, "localData": { "expectedRunPartsCount": 1 }, diff --git a/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_with_github_remote_found_on_codspeed.snap b/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_with_github_remote_found_on_codspeed.snap index 3e5f442df..de6637869 100644 --- a/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_with_github_remote_found_on_codspeed.snap +++ b/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_with_github_remote_found_on_codspeed.snap @@ -10,8 +10,6 @@ expression: run_environment_metadata "repository": "my-owner", "event": "local", "sender": null, - "ghData": null, - "glData": null, "localData": { "expectedRunPartsCount": 1 }, diff --git a/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_without_git_repository.snap b/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_without_git_repository.snap index 9404b76df..a0124d5e3 100644 --- a/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_without_git_repository.snap +++ b/src/run_environment/local/snapshots/codspeed_runner__run_environment__local__provider__tests__new_without_git_repository.snap @@ -10,8 +10,6 @@ expression: run_environment_metadata "repository": "local-runs", "event": "local", "sender": null, - "ghData": null, - "glData": null, "localData": { "expectedRunPartsCount": 1 }, diff --git a/src/upload/interfaces.rs b/src/upload/interfaces.rs index f834995d4..fbf6abad7 100644 --- a/src/upload/interfaces.rs +++ b/src/upload/interfaces.rs @@ -5,7 +5,7 @@ use crate::instruments::InstrumentName; use crate::run_environment::{RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunPart}; use crate::system::SystemInfo; -pub const LATEST_UPLOAD_METADATA_VERSION: u32 = 10; +pub const LATEST_UPLOAD_METADATA_VERSION: u32 = 11; #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] diff --git a/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap index 4b9af9b3d..1679aa850 100644 --- a/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap +++ b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_local_metadata_hash-2.snap @@ -4,7 +4,7 @@ expression: upload_metadata --- { "repositoryProvider": "PROJECT", - "version": 10, + "version": 11, "tokenless": false, "profileMd5": "tfC4VxYiYdJcTWpHpv4Ouw==", "profileEncoding": "gzip", @@ -46,8 +46,6 @@ expression: upload_metadata "repository": "local-runs", "event": "local", "sender": null, - "ghData": null, - "glData": null, "localData": { "expectedRunPartsCount": 1 }, diff --git a/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap index 36f97410d..fd695b09f 100644 --- a/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap +++ b/src/upload/snapshots/codspeed_runner__upload__upload_metadata__tests__get_metadata_hash-2.snap @@ -4,7 +4,7 @@ expression: upload_metadata --- { "repositoryProvider": "GITHUB", - "version": 10, + "version": 11, "tokenless": true, "profileMd5": "jp/k05RKuqP3ERQuIIvx4Q==", "profileEncoding": "gzip", @@ -54,11 +54,6 @@ expression: upload_metadata "id": "19605940", "login": "adriencaccia" }, - "ghData": { - "runId": "7044765741", - "job": "codspeed" - }, - "glData": null, "localData": null, "repositoryRootPath": "/home/runner/work/codspeed-node/codspeed-node/" } diff --git a/src/upload/upload_metadata.rs b/src/upload/upload_metadata.rs index 81bdd9b75..9f0dc02d3 100644 --- a/src/upload/upload_metadata.rs +++ b/src/upload/upload_metadata.rs @@ -18,8 +18,8 @@ mod tests { use crate::executor::ExecutorName; use crate::instruments::InstrumentName; use crate::run_environment::{ - GhData, LocalData, RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunEvent, - RunPart, Sender, + LocalData, RepositoryProvider, RunEnvironment, RunEnvironmentMetadata, RunEvent, RunPart, + Sender, }; use crate::system::SystemInfo; use crate::upload::{LATEST_UPLOAD_METADATA_VERSION, Runner, UploadMetadata}; @@ -50,15 +50,10 @@ mod tests { owner: "CodSpeedHQ".into(), repository: "codspeed-node".into(), event: RunEvent::PullRequest, - gh_data: Some(GhData { - run_id: "7044765741".into(), - job: "codspeed".into(), - }), sender: Some(Sender { id: "19605940".into(), login: "adriencaccia".into(), }), - gl_data: None, local_data: None, repository_root_path: "/home/runner/work/codspeed-node/codspeed-node/".into(), }, @@ -78,7 +73,7 @@ mod tests { hash, // Caution: when changing this value, we need to ensure that // the related backend snapshot remains the same - @"0afc09ee58a610d400aa6b3fbdddf628608ed2e11aed39585a50abe96e1c9284" + @"b6e221583869b0a49498d71538432a5396b08fec7783081dcdf23c9e037a9365" ); assert_json_snapshot!(upload_metadata); } @@ -130,9 +125,7 @@ mod tests { owner: "GuillaumeLagrange".into(), repository: "local-runs".into(), event: RunEvent::Local, - gh_data: None, sender: None, - gl_data: None, local_data: Some(LocalData { expected_run_parts_count: 1, }), @@ -151,7 +144,7 @@ mod tests { hash, // Caution: when changing this value, we need to ensure that // the related backend snapshot remains the same - @"26c83ef306f189fe5b725043577dbc09a204bbd1c973dd7d1e974ff88235dd84" + @"5c960260ea5b5ceaafa20ea220e566743e20b65106d0fd672a844a63bba3835a" ); assert_json_snapshot!(upload_metadata); } From 916a1bd80eb1bc36093773b35a39f68942c00313 Mon Sep 17 00:00:00 2001 From: fargito Date: Thu, 30 Jul 2026 15:48:22 +0200 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20Release=20codspeed-runner=20versio?= =?UTF-8?q?n=205.0.2-alpha.1=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 350a9e81f..2e5770a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,7 +691,7 @@ dependencies = [ [[package]] name = "codspeed-runner" -version = "5.0.1" +version = "5.0.2-alpha.1" dependencies = [ "addr2line", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 6d5ebe701..54b9c9a55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codspeed-runner" -version = "5.0.1" +version = "5.0.2-alpha.1" edition = "2024" repository = "https://github.com/CodSpeedHQ/codspeed" publish = false