forked from github-starred/komodo
add repo build webhook
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user