From ad4ba4da79362539a03024cc48f645d21be9abd3 Mon Sep 17 00:00:00 2001 From: Adrien Cacciaguerra Date: Wed, 29 May 2024 15:29:06 -0400 Subject: [PATCH] feat(runner): add local provider --- src/run/ci_provider/interfaces.rs | 1 + src/run/ci_provider/local/mod.rs | 3 + src/run/ci_provider/local/provider.rs | 165 ++++++++++++++++++ ...l__provider__tests__provider_metadata.snap | 12 ++ src/run/ci_provider/mod.rs | 14 +- src/run/ci_provider/provider.rs | 3 +- 6 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 src/run/ci_provider/local/mod.rs create mode 100644 src/run/ci_provider/local/provider.rs create mode 100644 src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap diff --git a/src/run/ci_provider/interfaces.rs b/src/run/ci_provider/interfaces.rs index d9fb729..e802da8 100644 --- a/src/run/ci_provider/interfaces.rs +++ b/src/run/ci_provider/interfaces.rs @@ -21,6 +21,7 @@ pub enum RunEvent { PullRequest, WorkflowDispatch, Schedule, + Local, } #[derive(Deserialize, Serialize, Debug, Clone)] diff --git a/src/run/ci_provider/local/mod.rs b/src/run/ci_provider/local/mod.rs new file mode 100644 index 0000000..7822295 --- /dev/null +++ b/src/run/ci_provider/local/mod.rs @@ -0,0 +1,3 @@ +mod provider; + +pub use provider::LocalProvider; diff --git a/src/run/ci_provider/local/provider.rs b/src/run/ci_provider/local/provider.rs new file mode 100644 index 0000000..088bd49 --- /dev/null +++ b/src/run/ci_provider/local/provider.rs @@ -0,0 +1,165 @@ +use git2::Repository; +use lazy_static::lazy_static; +use simplelog::SharedLogger; + +use crate::logger::get_local_logger; +use crate::prelude::*; +use crate::run::{ + ci_provider::{ + interfaces::{ProviderMetadata, RunEvent}, + provider::{CIProvider, CIProviderDetector}, + }, + config::Config, + helpers::find_repository_root, +}; + +#[derive(Debug)] +pub struct LocalProvider { + pub ref_: String, + pub owner: String, + pub repository: String, + pub head_ref: Option, + pub base_ref: Option, + pub event: RunEvent, + pub repository_root_path: String, +} + +impl LocalProvider {} + +lazy_static! { + static ref REMOTE_REGEX: regex::Regex = + regex::Regex::new(r"[:/](?P[^/]+)/(?P[^/]+)\.git").unwrap(); +} + +fn extract_owner_and_repository_from_remote_url(remote_url: &str) -> Result<(String, String)> { + let captures = REMOTE_REGEX.captures(remote_url).ok_or_else(|| { + anyhow!( + "Could not extract owner and repository from remote url: {}", + remote_url + ) + })?; + + let owner = captures.name("owner").unwrap().as_str(); + let repository = captures.name("repository").unwrap().as_str(); + + Ok((owner.to_string(), repository.to_string())) +} + +impl TryFrom<&Config> for LocalProvider { + type Error = Error; + fn try_from(_config: &Config) -> Result { + 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 => bail!("Could not find repository root, please make sure you are running the command from inside a git repository"), + }; + + let git_repository = Repository::open(repository_root_path.clone()).context(format!( + "Failed to open repository at path: {}", + repository_root_path + ))?; + + let remote = git_repository.find_remote("origin")?; + let (owner, repository) = + extract_owner_and_repository_from_remote_url(remote.url().unwrap())?; + + let head = git_repository.head().context("Failed to get HEAD")?; + let ref_ = head + .peel_to_commit() + .context("Failed to get HEAD commit")? + .id() + .to_string(); + let head_ref = if head.is_branch() { + let branch = head.shorthand().context("Failed to get HEAD branch name")?; + Some(branch.to_string()) + } else { + None + }; + + Ok(Self { + ref_, + head_ref, + base_ref: None, + owner, + repository, + event: RunEvent::Local, + repository_root_path, + }) + } +} + +impl CIProviderDetector for LocalProvider { + fn detect() -> bool { + true + } +} + +impl CIProvider for LocalProvider { + fn get_logger(&self) -> Box { + get_local_logger() + } + + fn get_provider_name(&self) -> &'static str { + "Local" + } + + fn get_provider_slug(&self) -> &'static str { + "local" + } + + fn get_provider_metadata(&self) -> Result { + Ok(ProviderMetadata { + base_ref: self.base_ref.clone(), + head_ref: self.head_ref.clone(), + event: self.event.clone(), + gh_data: None, + owner: self.owner.clone(), + repository: self.repository.clone(), + ref_: self.ref_.clone(), + repository_root_path: self.repository_root_path.clone(), + }) + } +} + +#[cfg(test)] +mod tests { + // use crate::VERSION; + // use insta::assert_json_snapshot; + + use super::*; + + #[test] + fn test_extract_owner_and_repository_from_remote_url() { + let remote_urls = [ + "git@github.com:CodSpeedHQ/runner.git", + "/~https://github.com/CodSpeedHQ/runner.git", + ]; + for remote_url in remote_urls.iter() { + let (owner, repository) = + extract_owner_and_repository_from_remote_url(remote_url).unwrap(); + assert_eq!(owner, "CodSpeedHQ"); + assert_eq!(repository, "runner"); + } + } + + // TODO: uncomment later when we have a way to mock git repository + // #[test] + // fn test_provider_metadata() { + // let config = Config { + // token: Some("token".into()), + // ..Config::test() + // }; + // let local_provider = LocalProvider::try_from(&config).unwrap(); + // let provider_metadata = local_provider.get_provider_metadata().unwrap(); + + // assert_json_snapshot!(provider_metadata, { + // ".runner.version" => insta::dynamic_redaction(|value,_path| { + // assert_eq!(value.as_str().unwrap(), VERSION.to_string()); + // "[version]" + // }), + // }); + // } +} diff --git a/src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap b/src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap new file mode 100644 index 0000000..7388234 --- /dev/null +++ b/src/run/ci_provider/local/snapshots/codspeed_runner__run__ci_provider__local__provider__tests__provider_metadata.snap @@ -0,0 +1,12 @@ +--- +source: src/run/ci_provider/local/provider.rs +expression: provider_metadata +--- +{ + "ref": "18ec1d64b5f25fb27451d89eee03cc569bd6bbb1", + "headRef": "feat/branch", + "owner": "my-org", + "repository": "adrien-python-test", + "event": "local", + "repositoryRootPath": "/Users/adrien/projects/my-org/adrien-python-test" +} diff --git a/src/run/ci_provider/mod.rs b/src/run/ci_provider/mod.rs index 7ebe1c2..ae06ed8 100644 --- a/src/run/ci_provider/mod.rs +++ b/src/run/ci_provider/mod.rs @@ -2,10 +2,12 @@ pub mod interfaces; pub mod logger; mod provider; +use buildkite::BuildkiteProvider; +use github_actions::GitHubActionsProvider; +use local::LocalProvider; +use provider::CIProviderDetector; + use crate::prelude::*; -use crate::run::ci_provider::buildkite::BuildkiteProvider; -use crate::run::ci_provider::github_actions::GitHubActionsProvider; -use crate::run::ci_provider::provider::CIProviderDetector; use crate::run::config::Config; pub use self::provider::CIProvider; @@ -13,6 +15,7 @@ pub use self::provider::CIProvider; // Provider implementations mod buildkite; mod github_actions; +mod local; pub fn get_provider(config: &Config) -> Result> { if BuildkiteProvider::detect() { @@ -25,5 +28,10 @@ pub fn get_provider(config: &Config) -> Result> { return Ok(Box::new(provider)); } + if LocalProvider::detect() { + let provider = LocalProvider::try_from(config)?; + return Ok(Box::new(provider)); + } + bail!("No CI provider detected") } diff --git a/src/run/ci_provider/provider.rs b/src/run/ci_provider/provider.rs index 4de9a23..4e5c769 100644 --- a/src/run/ci_provider/provider.rs +++ b/src/run/ci_provider/provider.rs @@ -19,7 +19,8 @@ fn get_commit_hash(repository_root_path: &str) -> Result { ))?; let commit_hash = repo - .revparse_single("HEAD") + .head() + .and_then(|head| head.peel_to_commit()) .context("Failed to get HEAD commit")? .id() .to_string();