diff --git a/crates/contractor/src/main.rs b/crates/contractor/src/main.rs index f93c5a5..cc133d7 100644 --- a/crates/contractor/src/main.rs +++ b/crates/contractor/src/main.rs @@ -22,7 +22,7 @@ enum Commands { #[arg(long)] user: Option, #[arg(long)] - orgs: Option>, + org: Option>, }, } @@ -61,12 +61,12 @@ async fn main() -> anyhow::Result<()> { result?? } } - Some(Commands::Reconcile { user, orgs }) => { + Some(Commands::Reconcile { user, org }) => { tracing::info!("running reconcile"); let state = SharedState::from(Arc::new(State::new().await?)); - state.reconciler().reconcile(user, orgs).await?; + state.reconciler().reconcile(user, org).await?; } None => {} } diff --git a/crates/contractor/src/services/gitea.rs b/crates/contractor/src/services/gitea.rs index 25bac0b..6c6bb0b 100644 --- a/crates/contractor/src/services/gitea.rs +++ b/crates/contractor/src/services/gitea.rs @@ -72,6 +72,7 @@ impl Default for DefaultGiteaClient { impl DefaultGiteaClient { pub async fn fetch_user_repos(&self) -> anyhow::Result> { + //FIXME: We should collect the pages for these queries let client = reqwest::Client::new(); let url = format!("{}/api/v1/user/repos", self.url); @@ -114,6 +115,32 @@ impl DefaultGiteaClient { .flat_map(Repository::try_from) .collect()) } + + async fn fetch_renovate(&self, repo: &Repository) -> anyhow::Result> { + let client = reqwest::Client::new(); + + let url = format!( + "{}/api/v1/repos/{}/{}/contents/renovate.json", + self.url, &repo.owner, &repo.name + ); + + tracing::trace!("calling url: {}", &url); + + let response = client + .get(&url) + .header("Content-Type", "application/json") + .header("Authorization", format!("token {}", self.token)) + .send() + .await?; + + match response.error_for_status() { + Ok(_) => Ok(Some(())), + Err(e) => match e.status() { + Some(StatusCode::NOT_FOUND) => Ok(None), + _ => anyhow::bail!(e), + }, + } + } } impl traits::GiteaClient for DefaultGiteaClient { @@ -136,6 +163,15 @@ impl traits::GiteaClient for DefaultGiteaClient { Box::pin(async move { self.fetch_org_repos(org).await }) } + + fn renovate_enabled<'a>( + &'a self, + repo: &'a Repository, + ) -> Pin> + Send + 'a>> { + tracing::trace!("checking whether renovate is enabled for: {:?}", repo); + + Box::pin(async { self.fetch_renovate(repo).await.map(|s| s.is_some()) }) + } } mod extensions; @@ -143,4 +179,5 @@ pub mod traits; use anyhow::Context; pub use extensions::*; +use reqwest::StatusCode; use serde::Deserialize; diff --git a/crates/contractor/src/services/gitea/traits.rs b/crates/contractor/src/services/gitea/traits.rs index 27daf5a..84efdc8 100644 --- a/crates/contractor/src/services/gitea/traits.rs +++ b/crates/contractor/src/services/gitea/traits.rs @@ -14,4 +14,9 @@ pub trait GiteaClient { &'a self, org: &'a str, ) -> Pin>> + Send + 'a>>; + + fn renovate_enabled<'a>( + &'a self, + repo: &'a Repository, + ) -> Pin> + Send + 'a>>; } diff --git a/crates/contractor/src/services/reconciler.rs b/crates/contractor/src/services/reconciler.rs index 63d8ab1..77fba4a 100644 --- a/crates/contractor/src/services/reconciler.rs +++ b/crates/contractor/src/services/reconciler.rs @@ -1,3 +1,4 @@ +use futures::{stream::FuturesUnordered, StreamExt}; use itertools::Itertools; use crate::SharedState; @@ -19,8 +20,13 @@ impl Reconciler { orgs: Option>, ) -> anyhow::Result<()> { let repos = self.get_repos(user, orgs).await?; + tracing::debug!("found repositories: {}", repos.len()); - tracing::info!("found repositories: {}", repos.len()); + let renovate_enabled = self.get_renovate_enabled(&repos).await?; + tracing::debug!( + "found repositories with renovate enabled: {}", + renovate_enabled.len() + ); Ok(()) } @@ -47,6 +53,34 @@ impl Reconciler { Ok(repos.into_iter().unique().collect()) } + + async fn get_renovate_enabled(&self, repos: &[Repository]) -> anyhow::Result> { + let mut futures = FuturesUnordered::new(); + + for repo in repos { + futures.push(async move { + let enabled = self.gitea_client.renovate_enabled(repo).await?; + + if enabled { + Ok::, anyhow::Error>(Some(repo.to_owned())) + } else { + tracing::trace!("repository: {:?}, doesn't have renovate enabled", repo); + Ok(None) + } + }) + } + + let mut enabled = Vec::new(); + while let Some(res) = futures.next().await { + let res = res?; + + if let Some(repo) = res { + enabled.push(repo) + } + } + + Ok(enabled) + } } pub trait ReconcilerState {