feat: receive gitlab push webhooks for instant git sync pull

This commit is contained in:
hugocasa
2026-09-02 13:24:16 +02:00
parent d11ac2dcb9
commit bb071efd7c
7 changed files with 38 additions and 14 deletions
+1 -1
View File
@@ -1 +1 @@
1a7e81206e8768722f0d0acfb3838036078e70be
9ac2ffdee130fcff7ed45d137d8606ae983da837
+1 -1
View File
@@ -4622,7 +4622,7 @@ const GIT_CREDENTIAL_LOCK_ID: i64 = 737_483_923;
/// expiry, so a token dies visibly (and usually not at all) rather than taking
/// sync down on its expiry date.
#[cfg(all(feature = "enterprise", feature = "private"))]
pub async fn maintain_git_credentials(db: &Pool<Postgres>) {
async fn maintain_git_credentials(db: &Pool<Postgres>) {
use windmill_common::ee_oss::{get_license_plan, LicensePlan};
if !matches!(get_license_plan().await, LicensePlan::Enterprise) {
@@ -4117,13 +4117,10 @@ async fn edit_git_sync_config(
// `sync_repo_webhook` writes back the webhook fields it changes itself:
// the remote hook and the record of it have to move together, so
// persisting them out here would let one land without the other.
if let Err(e) = windmill_common::git_sync_ee::sync_repo_webhook(&db, &w_id, repo).await
{
tracing::warn!("git auto-pull: webhook sync error: {}", e);
}
// Check the credential while the operator is still on the settings page,
// so a token that is short-lived or missing a scope is visible now rather
// than when it expires.
// Before the webhook reconcile, which decides whether this repo can have
// one from the credential this records. Also puts a short-lived or
// under-scoped token in front of the operator while they are still on the
// settings page, rather than when it expires.
if let Err(e) = windmill_common::git_sync_ee::refresh_git_credential_status(
&db,
&w_id,
@@ -4133,6 +4130,10 @@ async fn edit_git_sync_config(
{
tracing::warn!("git credential check error: {}", e);
}
if let Err(e) = windmill_common::git_sync_ee::sync_repo_webhook(&db, &w_id, repo).await
{
tracing::warn!("git auto-pull: webhook sync error: {}", e);
}
}
for (path, hook_id) in removed_webhooks {
if let Ok(url) =
@@ -4365,9 +4366,6 @@ async fn edit_git_sync_repository(
.iter_mut()
.find(|r| r.git_repo_resource_path == new_config.git_repo_resource_path)
{
if let Err(e) = windmill_common::git_sync_ee::sync_repo_webhook(&db, &w_id, repo).await {
tracing::warn!("git auto-pull: webhook sync error: {}", e);
}
if let Err(e) = windmill_common::git_sync_ee::refresh_git_credential_status(
&db,
&w_id,
@@ -4377,6 +4375,9 @@ async fn edit_git_sync_repository(
{
tracing::warn!("git credential check error: {}", e);
}
if let Err(e) = windmill_common::git_sync_ee::sync_repo_webhook(&db, &w_id, repo).await {
tracing::warn!("git auto-pull: webhook sync error: {}", e);
}
}
// Trigger git sync for individual repository update/add
+3
View File
@@ -34005,6 +34005,9 @@ components:
type: array
items:
type: string
token_fingerprint:
type: string
description: one-way digest identifying which credential this status describes
rotatable:
type: boolean
checked_at:
+9
View File
@@ -958,6 +958,15 @@ pub async fn run_server(
#[cfg(not(feature = "enterprise"))]
Router::new()
})
.nest("/w/{workspace_id}/git_sync", {
#[cfg(feature = "enterprise")]
{
git_sync_oss::workspaced_git_sync_service()
}
#[cfg(not(feature = "enterprise"))]
Router::new()
})
.nest(
"/w/{workspace_id}/resources_u",
public_service().layer(cors.clone()),
+6 -1
View File
@@ -427,8 +427,13 @@ pub struct GitCredentialStatus {
pub expires_at: Option<chrono::NaiveDate>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scopes: Vec<String>,
/// Which credential this status describes, as a one-way digest. Lets a later
/// check tell "the token I know about stopped working" from "someone put a
/// different credential here", which look identical at the API otherwise.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub token_fingerprint: Option<String>,
/// Rotation needs both a scope that permits it (`api` or `self_rotate`) and a
/// URL held in a variable we can write back to.
/// URL Windmill can write back to. `scopes` says which of the two is missing.
pub rotatable: bool,
/// Unix timestamp (seconds) of the last check.
pub checked_at: i64,
@@ -204,10 +204,16 @@
}
}
if (days > 30) return undefined
// Two different things stop a renewal, and they need opposite advice: a
// token that may not rotate itself, or a token Windmill cannot store the
// replacement for. Scopes say which.
const canSelfRotate = (credential.scopes ?? []).some((s) => s === 'api' || s === 'self_rotate')
return {
type: days <= 7 ? ('error' as const) : days <= 14 ? ('warning' as const) : ('info' as const),
title: `Repository token ${when}`,
body: 'Give the token the api or self_rotate scope and Windmill will renew it on its own. Otherwise, replace it before it expires to keep sync running.'
body: canSelfRotate
? 'Windmill cannot renew it because it cannot write the new token back to where this URL is stored. Move the URL into a Windmill variable, or replace the token before it expires.'
: 'Give the token the api or self_rotate scope and Windmill will renew it on its own. Otherwise, replace it before it expires to keep sync running.'
}
})