add repo build webhook

This commit is contained in:
mbecker20
2024-08-11 18:26:51 -07:00
parent 495e208ccd
commit 392e691f92
8 changed files with 157 additions and 26 deletions

View File

@@ -137,6 +137,7 @@ impl Resolve<GetRepoWebhooksEnabled, User> for State {
managed: false,
clone_enabled: false,
pull_enabled: false,
build_enabled: false,
});
};
@@ -154,6 +155,7 @@ impl Resolve<GetRepoWebhooksEnabled, User> for State {
managed: false,
clone_enabled: false,
pull_enabled: false,
build_enabled: false,
});
}
@@ -165,6 +167,7 @@ impl Resolve<GetRepoWebhooksEnabled, User> for State {
managed: false,
clone_enabled: false,
pull_enabled: false,
build_enabled: false,
});
};
@@ -190,23 +193,33 @@ impl Resolve<GetRepoWebhooksEnabled, User> 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,
})
}
}

View File

@@ -225,6 +225,9 @@ impl Resolve<CreateRepoWebhook, User> 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<DeleteRepoWebhook, User> 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 {

View File

@@ -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(

View File

@@ -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>(&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(())
}

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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",
}
/**

View File

@@ -164,6 +164,11 @@ export const RepoConfig = ({ id }: { id: string }) => {
<CopyGithubWebhook path={`/repo/${id}/clone`} />
</ConfigItem>
),
["build" as any]: () => (
<ConfigItem label="Build">
<CopyGithubWebhook path={`/repo/${id}/build`} />
</ConfigItem>
),
webhook_enabled: webhooks !== undefined && !webhooks.managed,
["managed" as any]: () => {
const inv = useInvalidate();
@@ -247,44 +252,95 @@ export const RepoConfig = ({ id }: { id: string }) => {
/>
</div>
)}
{!webhooks.clone_enabled && !webhooks.pull_enabled && (
{webhooks.build_enabled && (
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
Incoming webhook is{" "}
<div
className={text_color_class_by_intention(
"Critical"
)}
className={text_color_class_by_intention("Good")}
>
DISABLED
ENABLED
</div>
and will trigger
<div
className={text_color_class_by_intention("Neutral")}
>
BUILD
</div>
</div>
<ConfirmButton
title="Enable Clone"
icon={<CirclePlus className="w-4 h-4" />}
title="Disable"
icon={<Ban className="w-4 h-4" />}
variant="destructive"
onClick={() =>
createWebhook({
deleteWebhook({
repo: id,
action: Types.RepoWebhookAction.Clone,
action: Types.RepoWebhookAction.Build,
})
}
loading={createPending}
disabled={disabled || createPending}
/>
<ConfirmButton
title="Enable Pull"
icon={<CirclePlus className="w-4 h-4" />}
onClick={() =>
createWebhook({
repo: id,
action: Types.RepoWebhookAction.Pull,
})
}
loading={createPending}
disabled={disabled || createPending}
loading={deletePending}
disabled={disabled || deletePending}
/>
</div>
)}
{!webhooks.clone_enabled &&
!webhooks.pull_enabled &&
!webhooks.build_enabled && (
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
Incoming webhook is{" "}
<div
className={text_color_class_by_intention(
"Critical"
)}
>
DISABLED
</div>
</div>
{(update.server_id ?? config.server_id) && (
<ConfirmButton
title="Enable Clone"
icon={<CirclePlus className="w-4 h-4" />}
onClick={() =>
createWebhook({
repo: id,
action: Types.RepoWebhookAction.Clone,
})
}
loading={createPending}
disabled={disabled || createPending}
/>
)}
{(update.server_id ?? config.server_id) && (
<ConfirmButton
title="Enable Pull"
icon={<CirclePlus className="w-4 h-4" />}
onClick={() =>
createWebhook({
repo: id,
action: Types.RepoWebhookAction.Pull,
})
}
loading={createPending}
disabled={disabled || createPending}
/>
)}
{(update.builder_id ?? config.builder_id) && (
<ConfirmButton
title="Enable Build"
icon={<CirclePlus className="w-4 h-4" />}
onClick={() =>
createWebhook({
repo: id,
action: Types.RepoWebhookAction.Build,
})
}
loading={createPending}
disabled={disabled || createPending}
/>
)}
</div>
)}
</ConfigItem>
);
},