diff --git a/bin/core/src/api/read/repo.rs b/bin/core/src/api/read/repo.rs index e8819d829..ff1347416 100644 --- a/bin/core/src/api/read/repo.rs +++ b/bin/core/src/api/read/repo.rs @@ -137,6 +137,7 @@ impl Resolve for State { managed: false, clone_enabled: false, pull_enabled: false, + build_enabled: false, }); }; @@ -154,6 +155,7 @@ impl Resolve for State { managed: false, clone_enabled: false, pull_enabled: false, + build_enabled: false, }); } @@ -165,6 +167,7 @@ impl Resolve for State { managed: false, clone_enabled: false, pull_enabled: false, + build_enabled: false, }); }; @@ -190,23 +193,33 @@ impl Resolve for State { format!("{host}/listener/github/repo/{}/clone", repo.id); let pull_url = format!("{host}/listener/github/repo/{}/pull", repo.id); + let build_url = + format!("{host}/listener/github/repo/{}/build", repo.id); let mut clone_enabled = false; let mut pull_enabled = false; + let mut build_enabled = false; for webhook in webhooks { - if webhook.active && webhook.config.url == clone_url { + if !webhook.active { + continue; + } + if webhook.config.url == clone_url { clone_enabled = true } - if webhook.active && webhook.config.url == pull_url { + if webhook.config.url == pull_url { pull_enabled = true } + if webhook.config.url == build_url { + build_enabled = true + } } Ok(GetRepoWebhooksEnabledResponse { managed: true, clone_enabled, pull_enabled, + build_enabled, }) } } diff --git a/bin/core/src/api/write/repo.rs b/bin/core/src/api/write/repo.rs index e75bf5330..0013da4c8 100644 --- a/bin/core/src/api/write/repo.rs +++ b/bin/core/src/api/write/repo.rs @@ -225,6 +225,9 @@ impl Resolve for State { RepoWebhookAction::Pull => { format!("{host}/listener/github/repo/{}/pull", repo.id) } + RepoWebhookAction::Build => { + format!("{host}/listener/github/repo/{}/build", repo.id) + } }; for webhook in webhooks { @@ -339,6 +342,9 @@ impl Resolve for State { RepoWebhookAction::Pull => { format!("{host}/listener/github/repo/{}/pull", repo.id) } + RepoWebhookAction::Build => { + format!("{host}/listener/github/repo/{}/build", repo.id) + } }; for webhook in webhooks { diff --git a/bin/core/src/listener/github/mod.rs b/bin/core/src/listener/github/mod.rs index d028450c4..02ceac07a 100644 --- a/bin/core/src/listener/github/mod.rs +++ b/bin/core/src/listener/github/mod.rs @@ -89,6 +89,24 @@ pub fn router() -> Router { }, ) ) + .route( + "/repo/:id/build", + post( + |Path(Id { id }), headers: HeaderMap, body: String| async move { + tokio::spawn(async move { + let span = info_span!("repo_build_webhook", id); + async { + let res = repo::handle_repo_build_webhook(id.clone(), headers, body).await; + if let Err(e) = res { + warn!("failed to run repo build webook for repo {id} | {e:#}"); + } + } + .instrument(span) + .await + }); + }, + ) + ) .route( "/stack/:id/refresh", post( diff --git a/bin/core/src/listener/github/repo.rs b/bin/core/src/listener/github/repo.rs index df6676787..f4a9c9a15 100644 --- a/bin/core/src/listener/github/repo.rs +++ b/bin/core/src/listener/github/repo.rs @@ -3,7 +3,7 @@ use std::sync::OnceLock; use anyhow::anyhow; use axum::http::HeaderMap; use monitor_client::{ - api::execute::{CloneRepo, PullRepo}, + api::execute::{BuildRepo, CloneRepo, PullRepo}, entities::{repo::Repo, user::git_webhook_user}, }; use resolver_api::Resolve; @@ -84,3 +84,35 @@ pub async fn handle_repo_pull_webhook( State.resolve(req, (user, update)).await?; Ok(()) } + +pub async fn handle_repo_build_webhook( + repo_id: String, + headers: HeaderMap, + body: String, +) -> anyhow::Result<()> { + // Acquire and hold lock to make a task queue for + // subsequent listener calls on same resource. + // It would fail if we let it go through from action state busy. + let lock = repo_locks().get_or_insert_default(&repo_id).await; + let _lock = lock.lock().await; + + verify_gh_signature(headers, &body).await?; + let request_branch = extract_branch(&body)?; + let repo = resource::get::(&repo_id).await?; + if !repo.config.webhook_enabled { + return Err(anyhow!("repo does not have webhook enabled")); + } + if request_branch != repo.config.branch { + return Err(anyhow!("request branch does not match expected")); + } + let user = git_webhook_user().to_owned(); + let req = crate::api::execute::ExecuteRequest::BuildRepo(BuildRepo { + repo: repo_id, + }); + let update = init_execution_update(&req, &user).await?; + let crate::api::execute::ExecuteRequest::BuildRepo(req) = req else { + unreachable!() + }; + State.resolve(req, (user, update)).await?; + Ok(()) +} \ No newline at end of file diff --git a/client/core/rs/src/api/read/repo.rs b/client/core/rs/src/api/read/repo.rs index ea1b16cd0..7363b353f 100644 --- a/client/core/rs/src/api/read/repo.rs +++ b/client/core/rs/src/api/read/repo.rs @@ -139,4 +139,6 @@ pub struct GetRepoWebhooksEnabledResponse { pub clone_enabled: bool, /// Whether pushes to branch trigger pull. Will always be false if managed is false. pub pull_enabled: bool, + /// Whether pushes to branch trigger build. Will always be false if managed is false. + pub build_enabled: bool, } diff --git a/client/core/rs/src/api/write/repo.rs b/client/core/rs/src/api/write/repo.rs index 67fd32d28..903a5035d 100644 --- a/client/core/rs/src/api/write/repo.rs +++ b/client/core/rs/src/api/write/repo.rs @@ -105,6 +105,7 @@ pub struct RefreshRepoCache { pub enum RepoWebhookAction { Clone, Pull, + Build, } /// Create a webhook on the github repo attached to the (monitor) repo diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 3513dad9f..1b78505ea 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -3262,6 +3262,8 @@ export interface GetRepoWebhooksEnabledResponse { clone_enabled: boolean; /** Whether pushes to branch trigger pull. Will always be false if managed is false. */ pull_enabled: boolean; + /** Whether pushes to branch trigger build. Will always be false if managed is false. */ + build_enabled: boolean; } /** Find resources matching a common query. Response: [FindResourcesResponse]. */ @@ -4389,6 +4391,7 @@ export interface RefreshRepoCache { export enum RepoWebhookAction { Clone = "Clone", Pull = "Pull", + Build = "Build", } /** diff --git a/frontend/src/components/resources/repo/config.tsx b/frontend/src/components/resources/repo/config.tsx index ab43822c6..f41ec2f57 100644 --- a/frontend/src/components/resources/repo/config.tsx +++ b/frontend/src/components/resources/repo/config.tsx @@ -164,6 +164,11 @@ export const RepoConfig = ({ id }: { id: string }) => { ), + ["build" as any]: () => ( + + + + ), webhook_enabled: webhooks !== undefined && !webhooks.managed, ["managed" as any]: () => { const inv = useInvalidate(); @@ -247,44 +252,95 @@ export const RepoConfig = ({ id }: { id: string }) => { /> )} - {!webhooks.clone_enabled && !webhooks.pull_enabled && ( + {webhooks.build_enabled && (
Incoming webhook is{" "}
- DISABLED + ENABLED +
+ and will trigger +
+ BUILD
} + title="Disable" + icon={} + variant="destructive" onClick={() => - createWebhook({ + deleteWebhook({ repo: id, - action: Types.RepoWebhookAction.Clone, + action: Types.RepoWebhookAction.Build, }) } - loading={createPending} - disabled={disabled || createPending} - /> - } - onClick={() => - createWebhook({ - repo: id, - action: Types.RepoWebhookAction.Pull, - }) - } - loading={createPending} - disabled={disabled || createPending} + loading={deletePending} + disabled={disabled || deletePending} />
)} + {!webhooks.clone_enabled && + !webhooks.pull_enabled && + !webhooks.build_enabled && ( +
+
+ Incoming webhook is{" "} +
+ DISABLED +
+
+ {(update.server_id ?? config.server_id) && ( + } + onClick={() => + createWebhook({ + repo: id, + action: Types.RepoWebhookAction.Clone, + }) + } + loading={createPending} + disabled={disabled || createPending} + /> + )} + {(update.server_id ?? config.server_id) && ( + } + onClick={() => + createWebhook({ + repo: id, + action: Types.RepoWebhookAction.Pull, + }) + } + loading={createPending} + disabled={disabled || createPending} + /> + )} + {(update.builder_id ?? config.builder_id) && ( + } + onClick={() => + createWebhook({ + repo: id, + action: Types.RepoWebhookAction.Build, + }) + } + loading={createPending} + disabled={disabled || createPending} + /> + )} +
+ )} ); },