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 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/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..f666e1431 --- /dev/null +++ b/src/run_environment/circleci/provider.rs @@ -0,0 +1,580 @@ +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(), + 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..8eeeff771 --- /dev/null +++ b/src/run_environment/circleci/snapshots/codspeed_runner__run_environment__circleci__provider__tests__pull_request_run_environment_metadata.snap @@ -0,0 +1,15 @@ +--- +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, + "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 2d2e83383..3dbf15b6f 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, } @@ -43,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, } @@ -59,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/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/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); } 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" }