diff --git a/bin/core/src/api/write/build.rs b/bin/core/src/api/write/build.rs index 71bf3bc36..7ae8cf27e 100644 --- a/bin/core/src/api/write/build.rs +++ b/bin/core/src/api/write/build.rs @@ -216,6 +216,12 @@ impl Resolve for State { .. } = core_config(); + let webhook_secret = if build.config.webhook_secret.is_empty() { + webhook_secret + } else { + &build.config.webhook_secret + }; + let host = webhook_base_url.as_ref().unwrap_or(host); let url = format!("{host}/listener/github/build/{}", build.id); diff --git a/bin/core/src/api/write/repo.rs b/bin/core/src/api/write/repo.rs index 0013da4c8..904969776 100644 --- a/bin/core/src/api/write/repo.rs +++ b/bin/core/src/api/write/repo.rs @@ -217,6 +217,12 @@ impl Resolve for State { .. } = core_config(); + let webhook_secret = if repo.config.webhook_secret.is_empty() { + webhook_secret + } else { + &repo.config.webhook_secret + }; + let host = webhook_base_url.as_ref().unwrap_or(host); let url = match action { RepoWebhookAction::Clone => { diff --git a/bin/core/src/api/write/stack.rs b/bin/core/src/api/write/stack.rs index 8288da9ce..04ddbb8a5 100644 --- a/bin/core/src/api/write/stack.rs +++ b/bin/core/src/api/write/stack.rs @@ -348,6 +348,12 @@ impl Resolve for State { .. } = core_config(); + let webhook_secret = if stack.config.webhook_secret.is_empty() { + webhook_secret + } else { + &stack.config.webhook_secret + }; + let host = webhook_base_url.as_ref().unwrap_or(host); let url = match action { StackWebhookAction::Refresh => { diff --git a/bin/core/src/api/write/sync.rs b/bin/core/src/api/write/sync.rs index a6c4a82a0..5caf71106 100644 --- a/bin/core/src/api/write/sync.rs +++ b/bin/core/src/api/write/sync.rs @@ -105,7 +105,11 @@ impl Resolve for State { } impl Resolve for State { - #[instrument(name = "RefreshResourceSyncPending", level = "debug", skip(self, user))] + #[instrument( + name = "RefreshResourceSyncPending", + level = "debug", + skip(self, user) + )] async fn resolve( &self, RefreshResourceSyncPending { sync }: RefreshResourceSyncPending, @@ -420,6 +424,12 @@ impl Resolve for State { .. } = core_config(); + let webhook_secret = if sync.config.webhook_secret.is_empty() { + webhook_secret + } else { + &sync.config.webhook_secret + }; + let host = webhook_base_url.as_ref().unwrap_or(host); let url = match action { SyncWebhookAction::Refresh => { diff --git a/bin/core/src/listener/github/build.rs b/bin/core/src/listener/github/build.rs index b215a4764..1962dcae4 100644 --- a/bin/core/src/listener/github/build.rs +++ b/bin/core/src/listener/github/build.rs @@ -31,15 +31,20 @@ pub async fn handle_build_webhook( let lock = build_locks().get_or_insert_default(&build_id).await; let _lock = lock.lock().await; - verify_gh_signature(headers, &body).await?; - let request_branch = extract_branch(&body)?; let build = resource::get::(&build_id).await?; + + verify_gh_signature(headers, &body, &build.config.webhook_secret) + .await?; + if !build.config.webhook_enabled { return Err(anyhow!("build does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; if request_branch != build.config.branch { return Err(anyhow!("request branch does not match expected")); } + let user = git_webhook_user().to_owned(); let req = ExecuteRequest::RunBuild(RunBuild { build: build_id }); let update = init_execution_update(&req, &user).await?; diff --git a/bin/core/src/listener/github/mod.rs b/bin/core/src/listener/github/mod.rs index 02ceac07a..f6099260a 100644 --- a/bin/core/src/listener/github/mod.rs +++ b/bin/core/src/listener/github/mod.rs @@ -216,6 +216,7 @@ pub fn router() -> Router { async fn verify_gh_signature( headers: HeaderMap, body: &str, + custom_secret: &str, ) -> anyhow::Result<()> { // wait random amount of time tokio::time::sleep(random_duration(0, 500)).await; @@ -229,10 +230,13 @@ async fn verify_gh_signature( return Err(anyhow!("failed to unwrap signature")); } let signature = signature.unwrap().replace("sha256=", ""); - let mut mac = HmacSha256::new_from_slice( - core_config().webhook_secret.as_bytes(), - ) - .expect("github webhook | failed to create hmac sha256"); + let secret_bytes = if custom_secret.is_empty() { + core_config().webhook_secret.as_bytes() + } else { + custom_secret.as_bytes() + }; + let mut mac = HmacSha256::new_from_slice(secret_bytes) + .expect("github webhook | failed to create hmac sha256"); mac.update(body.as_bytes()); let expected = mac.finalize().into_bytes().encode_hex::(); if signature == expected { diff --git a/bin/core/src/listener/github/procedure.rs b/bin/core/src/listener/github/procedure.rs index 705183716..a49c68aa6 100644 --- a/bin/core/src/listener/github/procedure.rs +++ b/bin/core/src/listener/github/procedure.rs @@ -33,15 +33,24 @@ pub async fn handle_procedure_webhook( procedure_locks().get_or_insert_default(&procedure_id).await; let _lock = lock.lock().await; - verify_gh_signature(headers, &body).await?; + let procedure = resource::get::(&procedure_id).await?; + + verify_gh_signature( + headers, + &body, + &procedure.config.webhook_secret, + ) + .await?; + + if !procedure.config.webhook_enabled { + return Err(anyhow!("procedure does not have webhook enabled")); + } + let request_branch = extract_branch(&body)?; if request_branch != target_branch { return Err(anyhow!("request branch does not match expected")); } - let procedure = resource::get::(&procedure_id).await?; - if !procedure.config.webhook_enabled { - return Err(anyhow!("procedure does not have webhook enabled")); - } + let user = git_webhook_user().to_owned(); let req = ExecuteRequest::RunProcedure(RunProcedure { procedure: procedure_id, diff --git a/bin/core/src/listener/github/repo.rs b/bin/core/src/listener/github/repo.rs index f4a9c9a15..2ea280507 100644 --- a/bin/core/src/listener/github/repo.rs +++ b/bin/core/src/listener/github/repo.rs @@ -30,15 +30,20 @@ pub async fn handle_repo_clone_webhook( 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?; + + verify_gh_signature(headers, &body, &repo.config.webhook_secret) + .await?; + if !repo.config.webhook_enabled { return Err(anyhow!("repo does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; 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::CloneRepo(CloneRepo { @@ -64,15 +69,20 @@ pub async fn handle_repo_pull_webhook( 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?; + + verify_gh_signature(headers, &body, &repo.config.webhook_secret) + .await?; + if !repo.config.webhook_enabled { return Err(anyhow!("repo does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; 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::PullRepo(PullRepo { repo: repo_id, @@ -96,23 +106,30 @@ pub async fn handle_repo_build_webhook( 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?; + + verify_gh_signature(headers, &body, &repo.config.webhook_secret) + .await?; + if !repo.config.webhook_enabled { return Err(anyhow!("repo does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; 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 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 { + 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/bin/core/src/listener/github/stack.rs b/bin/core/src/listener/github/stack.rs index 59c818570..c37d15d8b 100644 --- a/bin/core/src/listener/github/stack.rs +++ b/bin/core/src/listener/github/stack.rs @@ -31,15 +31,20 @@ pub async fn handle_stack_refresh_webhook( let lock = stack_locks().get_or_insert_default(&stack_id).await; let _lock = lock.lock().await; - verify_gh_signature(headers, &body).await?; - let request_branch = extract_branch(&body)?; let stack = resource::get::(&stack_id).await?; + + verify_gh_signature(headers, &body, &stack.config.webhook_secret) + .await?; + if !stack.config.webhook_enabled { return Err(anyhow!("stack does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; if request_branch != stack.config.branch { return Err(anyhow!("request branch does not match expected")); } + let user = git_webhook_user().to_owned(); State .resolve(RefreshStackCache { stack: stack.id }, user) @@ -58,15 +63,20 @@ pub async fn handle_stack_deploy_webhook( let lock = stack_locks().get_or_insert_default(&stack_id).await; let _lock = lock.lock().await; - verify_gh_signature(headers, &body).await?; - let request_branch = extract_branch(&body)?; let stack = resource::get::(&stack_id).await?; + + verify_gh_signature(headers, &body, &stack.config.webhook_secret) + .await?; + if !stack.config.webhook_enabled { return Err(anyhow!("stack does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; if request_branch != stack.config.branch { return Err(anyhow!("request branch does not match expected")); } + let user = git_webhook_user().to_owned(); let req = ExecuteRequest::DeployStack(DeployStack { stack: stack_id, diff --git a/bin/core/src/listener/github/sync.rs b/bin/core/src/listener/github/sync.rs index d6693b533..b9220b7d0 100644 --- a/bin/core/src/listener/github/sync.rs +++ b/bin/core/src/listener/github/sync.rs @@ -31,15 +31,20 @@ pub async fn handle_sync_refresh_webhook( let lock = sync_locks().get_or_insert_default(&sync_id).await; let _lock = lock.lock().await; - verify_gh_signature(headers, &body).await?; - let request_branch = extract_branch(&body)?; let sync = resource::get::(&sync_id).await?; + + verify_gh_signature(headers, &body, &sync.config.webhook_secret) + .await?; + if !sync.config.webhook_enabled { return Err(anyhow!("sync does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; if request_branch != sync.config.branch { return Err(anyhow!("request branch does not match expected")); } + let user = git_webhook_user().to_owned(); State .resolve(RefreshResourceSyncPending { sync: sync_id }, user) @@ -58,15 +63,20 @@ pub async fn handle_sync_execute_webhook( let lock = sync_locks().get_or_insert_default(&sync_id).await; let _lock = lock.lock().await; - verify_gh_signature(headers, &body).await?; - let request_branch = extract_branch(&body)?; let sync = resource::get::(&sync_id).await?; + + verify_gh_signature(headers, &body, &sync.config.webhook_secret) + .await?; + if !sync.config.webhook_enabled { return Err(anyhow!("sync does not have webhook enabled")); } + + let request_branch = extract_branch(&body)?; if request_branch != sync.config.branch { return Err(anyhow!("request branch does not match expected")); } + let user = git_webhook_user().to_owned(); let req = ExecuteRequest::RunSync(RunSync { sync: sync_id }); let update = init_execution_update(&req, &user).await?; diff --git a/bin/migrator/src/legacy/v0/build.rs b/bin/migrator/src/legacy/v0/build.rs index 5b2531045..0cc993c7f 100644 --- a/bin/migrator/src/legacy/v0/build.rs +++ b/bin/migrator/src/legacy/v0/build.rs @@ -243,6 +243,7 @@ impl TryFrom for monitor_client::entities::build::Build { use_buildx, labels: Default::default(), webhook_enabled: true, + webhook_secret: Default::default(), commit: Default::default(), }, }; diff --git a/bin/migrator/src/legacy/v1_11/build.rs b/bin/migrator/src/legacy/v1_11/build.rs index 3c9fd9382..320f09d03 100644 --- a/bin/migrator/src/legacy/v1_11/build.rs +++ b/bin/migrator/src/legacy/v1_11/build.rs @@ -163,6 +163,7 @@ impl From extra_args: value.extra_args, use_buildx: value.use_buildx, webhook_enabled: value.webhook_enabled, + webhook_secret: Default::default(), image_registry: value.image_registry.into(), } } diff --git a/bin/migrator/src/legacy/v1_6/build.rs b/bin/migrator/src/legacy/v1_6/build.rs index 3ec98d94b..61142c288 100644 --- a/bin/migrator/src/legacy/v1_6/build.rs +++ b/bin/migrator/src/legacy/v1_6/build.rs @@ -157,6 +157,7 @@ impl From }) }, webhook_enabled: value.webhook_enabled, + webhook_secret: Default::default(), } } } diff --git a/changelog.md b/changelog.md index 3d7af18ab..cded784e5 100644 --- a/changelog.md +++ b/changelog.md @@ -33,7 +33,7 @@ Users will have to manage their own versioning though. - **Sync**: Sync resources declared in toml files in Github repos. - Manage resources declaratively, with git history for configuration rollbacks. - See the actions which will be performed in the UI, and execute them upon manual confirmation. - - Use a Github webhook to automatically execute syncs on git push. + - Use a Git webhook to automatically execute syncs on git push. - **Resource Tagging** - Attach multiple *tags* to resources, which can be used to group related resources together. These can be used to filter resources in the UI. diff --git a/client/core/rs/src/entities/build.rs b/client/core/rs/src/entities/build.rs index 67c115a21..146079a90 100644 --- a/client/core/rs/src/entities/build.rs +++ b/client/core/rs/src/entities/build.rs @@ -127,6 +127,15 @@ pub struct BuildConfig { #[partial_default(default_git_https())] pub git_https: bool, + /// The git account used to access private repos. + /// Passing empty string can only clone public repos. + /// + /// Note. A token for the account must be available in the core config or the builder server's periphery config + /// for the configured git provider. + #[serde(default)] + #[builder(default)] + pub git_account: String, + /// The repo used as the source of the build. #[serde(default)] #[builder(default)] @@ -143,14 +152,17 @@ pub struct BuildConfig { #[builder(default)] pub commit: String, - /// The git account used to access private repos. - /// Passing empty string can only clone public repos. - /// - /// Note. A token for the account must be available in the core config or the builder server's periphery config - /// for the configured git provider. + /// Whether incoming webhooks actually trigger action. + #[serde(default = "default_webhook_enabled")] + #[builder(default = "default_webhook_enabled()")] + #[partial_default(default_webhook_enabled())] + pub webhook_enabled: bool, + + /// Optionally provide an alternate webhook secret for this build. + /// If its an empty string, use the default secret from the config. #[serde(default)] #[builder(default)] - pub git_account: String, + pub webhook_secret: String, /// The optional command run after repo clone and before docker build. #[serde(default)] @@ -185,12 +197,6 @@ pub struct BuildConfig { #[builder(default)] pub use_buildx: bool, - /// Whether incoming webhooks actually trigger action. - #[serde(default = "default_webhook_enabled")] - #[builder(default = "default_webhook_enabled()")] - #[partial_default(default_webhook_enabled())] - pub webhook_enabled: bool, - /// Any extra docker cli arguments to be included in the build command #[serde(default)] #[builder(default)] @@ -298,6 +304,7 @@ impl Default for BuildConfig { use_buildx: Default::default(), image_registry: Default::default(), webhook_enabled: default_webhook_enabled(), + webhook_secret: Default::default(), } } } diff --git a/client/core/rs/src/entities/procedure.rs b/client/core/rs/src/entities/procedure.rs index da9d65e0d..d270d7c6e 100644 --- a/client/core/rs/src/entities/procedure.rs +++ b/client/core/rs/src/entities/procedure.rs @@ -66,6 +66,12 @@ pub struct ProcedureConfig { #[builder(default = "default_webhook_enabled()")] #[partial_default(default_webhook_enabled())] pub webhook_enabled: bool, + + /// Optionally provide an alternate webhook secret for this procedure. + /// If its an empty string, use the default secret from the config. + #[serde(default)] + #[builder(default)] + pub webhook_secret: String, } impl ProcedureConfig { @@ -83,6 +89,7 @@ impl Default for ProcedureConfig { Self { stages: Default::default(), webhook_enabled: default_webhook_enabled(), + webhook_secret: Default::default() } } } diff --git a/client/core/rs/src/entities/repo.rs b/client/core/rs/src/entities/repo.rs index 86a5a0efb..968fb69dd 100644 --- a/client/core/rs/src/entities/repo.rs +++ b/client/core/rs/src/entities/repo.rs @@ -111,6 +111,23 @@ pub struct RepoConfig { #[partial_default(default_git_provider())] pub git_provider: String, + /// Whether to use https to clone the repo (versus http). Default: true + /// + /// Note. Monitor does not currently support cloning repos via ssh. + #[serde(default = "default_git_https")] + #[builder(default = "default_git_https()")] + #[partial_default(default_git_https())] + pub git_https: bool, + + /// The git account used to access private repos. + /// Passing empty string can only clone public repos. + /// + /// Note. A token for the account must be available in the core config or the builder server's periphery config + /// for the configured git provider. + #[serde(default)] + #[builder(default)] + pub git_account: String, + /// The github repo to clone. #[serde(default)] #[builder(default)] @@ -127,28 +144,23 @@ pub struct RepoConfig { #[builder(default)] pub commit: String, - /// The git account used to access private repos. - /// Passing empty string can only clone public repos. - /// - /// Note. A token for the account must be available in the core config or the builder server's periphery config - /// for the configured git provider. - #[serde(default)] - #[builder(default)] - pub git_account: String, - - /// Whether to use https to clone the repo (versus http). Default: true - /// - /// Note. Monitor does not currently support cloning repos via ssh. - #[serde(default = "default_git_https")] - #[builder(default = "default_git_https()")] - #[partial_default(default_git_https())] - pub git_https: bool, - /// Explicitly specify the folder to clone the repo in. #[serde(default)] #[builder(default)] pub path: String, + /// Whether incoming webhooks actually trigger action. + #[serde(default = "default_webhook_enabled")] + #[builder(default = "default_webhook_enabled()")] + #[partial_default(default_webhook_enabled())] + pub webhook_enabled: bool, + + /// Optionally provide an alternate webhook secret for this repo. + /// If its an empty string, use the default secret from the config. + #[serde(default)] + #[builder(default)] + pub webhook_secret: String, + /// Command to be run after the repo is cloned. /// The path is relative to the root of the repo. #[serde(default)] @@ -189,12 +201,6 @@ pub struct RepoConfig { #[serde(default)] #[builder(default)] pub skip_secret_interp: bool, - - /// Whether incoming webhooks actually trigger action. - #[serde(default = "default_webhook_enabled")] - #[builder(default = "default_webhook_enabled()")] - #[partial_default(default_webhook_enabled())] - pub webhook_enabled: bool, } impl RepoConfig { @@ -241,6 +247,7 @@ impl Default for RepoConfig { env_file_path: default_env_file_path(), skip_secret_interp: Default::default(), webhook_enabled: default_webhook_enabled(), + webhook_secret: Default::default(), } } } diff --git a/client/core/rs/src/entities/stack.rs b/client/core/rs/src/entities/stack.rs index c8bffa69e..e308815da 100644 --- a/client/core/rs/src/entities/stack.rs +++ b/client/core/rs/src/entities/stack.rs @@ -358,6 +358,12 @@ pub struct StackConfig { #[partial_default(default_webhook_enabled())] pub webhook_enabled: bool, + /// Optionally provide an alternate webhook secret for this stack. + /// If its an empty string, use the default secret from the config. + #[serde(default)] + #[builder(default)] + pub webhook_secret: String, + /// Whether to send StackStateChange alerts for this stack. #[serde(default = "default_send_alerts")] #[builder(default = "default_send_alerts()")] @@ -421,6 +427,7 @@ impl Default for StackConfig { commit: Default::default(), git_account: Default::default(), webhook_enabled: default_webhook_enabled(), + webhook_secret: Default::default(), send_alerts: default_send_alerts(), } } diff --git a/client/core/rs/src/entities/sync.rs b/client/core/rs/src/entities/sync.rs index 6e21598c5..407113a52 100644 --- a/client/core/rs/src/entities/sync.rs +++ b/client/core/rs/src/entities/sync.rs @@ -240,6 +240,12 @@ pub struct ResourceSyncConfig { #[builder(default = "default_webhook_enabled()")] #[partial_default(default_webhook_enabled())] pub webhook_enabled: bool, + + /// Optionally provide an alternate webhook secret for this sync. + /// If its an empty string, use the default secret from the config. + #[serde(default)] + #[builder(default)] + pub webhook_secret: String, } impl ResourceSyncConfig { @@ -280,6 +286,7 @@ impl Default for ResourceSyncConfig { resource_path: default_resource_path(), delete: Default::default(), webhook_enabled: default_webhook_enabled(), + webhook_secret: Default::default(), } } } diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 808627f16..ebc0c767f 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -401,12 +401,6 @@ export interface BuildConfig { * Note. Monitor does not currently support cloning repos via ssh. */ git_https: boolean; - /** The repo used as the source of the build. */ - repo?: string; - /** The branch of the repo. */ - branch: string; - /** Optionally set a specific commit hash. */ - commit?: string; /** * The git account used to access private repos. * Passing empty string can only clone public repos. @@ -415,6 +409,19 @@ export interface BuildConfig { * for the configured git provider. */ git_account?: string; + /** The repo used as the source of the build. */ + repo?: string; + /** The branch of the repo. */ + branch: string; + /** Optionally set a specific commit hash. */ + commit?: string; + /** Whether incoming webhooks actually trigger action. */ + webhook_enabled: boolean; + /** + * Optionally provide an alternate webhook secret for this build. + * If its an empty string, use the default secret from the config. + */ + webhook_secret?: string; /** The optional command run after repo clone and before docker build. */ pre_build?: SystemCommand; /** Configuration for the registry to push the built image to. */ @@ -430,8 +437,6 @@ export interface BuildConfig { skip_secret_interp?: boolean; /** Whether to use buildx to build (eg `docker buildx build ...`) */ use_buildx?: boolean; - /** Whether incoming webhooks actually trigger action. */ - webhook_enabled: boolean; /** Any extra docker cli arguments to be included in the build command */ extra_args?: string[]; /** @@ -871,6 +876,11 @@ export interface ProcedureConfig { stages?: ProcedureStage[]; /** Whether incoming webhooks actually trigger action. */ webhook_enabled: boolean; + /** + * Optionally provide an alternate webhook secret for this procedure. + * If its an empty string, use the default secret from the config. + */ + webhook_secret?: string; } /** @@ -980,12 +990,12 @@ export interface RepoConfig { builder_id?: string; /** The git provider domain. Default: github.com */ git_provider: string; - /** The github repo to clone. */ - repo?: string; - /** The repo branch. */ - branch: string; - /** Optionally set a specific commit hash. */ - commit?: string; + /** + * Whether to use https to clone the repo (versus http). Default: true + * + * Note. Monitor does not currently support cloning repos via ssh. + */ + git_https: boolean; /** * The git account used to access private repos. * Passing empty string can only clone public repos. @@ -994,14 +1004,21 @@ export interface RepoConfig { * for the configured git provider. */ git_account?: string; - /** - * Whether to use https to clone the repo (versus http). Default: true - * - * Note. Monitor does not currently support cloning repos via ssh. - */ - git_https: boolean; + /** The github repo to clone. */ + repo?: string; + /** The repo branch. */ + branch: string; + /** Optionally set a specific commit hash. */ + commit?: string; /** Explicitly specify the folder to clone the repo in. */ path?: string; + /** Whether incoming webhooks actually trigger action. */ + webhook_enabled: boolean; + /** + * Optionally provide an alternate webhook secret for this repo. + * If its an empty string, use the default secret from the config. + */ + webhook_secret?: string; /** * Command to be run after the repo is cloned. * The path is relative to the root of the repo. @@ -1028,8 +1045,6 @@ export interface RepoConfig { env_file_path: string; /** Whether to skip secret interpolation into the repo environment variable file. */ skip_secret_interp?: boolean; - /** Whether incoming webhooks actually trigger action. */ - webhook_enabled: boolean; } export interface RepoInfo { @@ -1522,6 +1537,11 @@ export interface StackConfig { commit?: string; /** Whether incoming webhooks actually trigger action. */ webhook_enabled: boolean; + /** + * Optionally provide an alternate webhook secret for this stack. + * If its an empty string, use the default secret from the config. + */ + webhook_secret?: string; /** Whether to send StackStateChange alerts for this stack. */ send_alerts: boolean; } @@ -1732,6 +1752,11 @@ export interface ResourceSyncConfig { delete?: boolean; /** Whether incoming webhooks actually trigger action. */ webhook_enabled: boolean; + /** + * Optionally provide an alternate webhook secret for this sync. + * If its an empty string, use the default secret from the config. + */ + webhook_secret?: string; } export type PendingSyncUpdatesData = diff --git a/frontend/src/components/resources/build/config.tsx b/frontend/src/components/resources/build/config.tsx index 9208efc56..15ea74a5a 100644 --- a/frontend/src/components/resources/build/config.tsx +++ b/frontend/src/components/resources/build/config.tsx @@ -287,16 +287,31 @@ export const BuildConfig = ({ }, }, { - label: "Github Webhook", + label: "Git Webhook", description: "Configure your repo provider to send webhooks to Monitor", components: { + ["Guard" as any]: () => { + if (update.branch ?? config.branch) { + return null; + } + return ( + +
Must configure Branch before webhooks will work.
+
+ ); + }, ["build" as any]: () => ( ), webhook_enabled: webhook !== undefined && !webhook.managed, + webhook_secret: { + description: + "Provide a custom webhook secret for this resource, or use the global default.", + placeholder: "Input custom secret", + }, ["managed" as any]: () => { const inv = useInvalidate(); const { toast } = useToast(); diff --git a/frontend/src/components/resources/procedure/config.tsx b/frontend/src/components/resources/procedure/config.tsx index a3feedd9d..5402bd454 100644 --- a/frontend/src/components/resources/procedure/config.tsx +++ b/frontend/src/components/resources/procedure/config.tsx @@ -192,7 +192,7 @@ const ProcedureConfigInner = ({
- +
@@ -223,6 +223,19 @@ const ProcedureConfigInner = ({ disabled={disabled} />
+
+
Custom Secret:
+ + setConfig({ ...config, webhook_secret: e.target.value }) + } + disabled={disabled} + className="w-[400px] max-w-full" + /> +
diff --git a/frontend/src/components/resources/repo/config.tsx b/frontend/src/components/resources/repo/config.tsx index f41ec2f57..ee7e30fc6 100644 --- a/frontend/src/components/resources/repo/config.tsx +++ b/frontend/src/components/resources/repo/config.tsx @@ -150,10 +150,20 @@ export const RepoConfig = ({ id }: { id: string }) => { }, }, { - label: "Github Webhooks", + label: "Git Webhooks", description: "Configure your repo provider to send webhooks to Monitor", components: { + ["Guard" as any]: () => { + if (update.branch ?? config.branch) { + return null; + } + return ( + +
Must configure Branch before webhooks will work.
+
+ ); + }, ["pull" as any]: () => ( @@ -170,6 +180,11 @@ export const RepoConfig = ({ id }: { id: string }) => { ), webhook_enabled: webhooks !== undefined && !webhooks.managed, + webhook_secret: { + description: + "Provide a custom webhook secret for this resource, or use the global default.", + placeholder: "Input custom secret", + }, ["managed" as any]: () => { const inv = useInvalidate(); const { toast } = useToast(); diff --git a/frontend/src/components/resources/resource-sync/config.tsx b/frontend/src/components/resources/resource-sync/config.tsx index 4f7665f02..2eff18200 100644 --- a/frontend/src/components/resources/resource-sync/config.tsx +++ b/frontend/src/components/resources/resource-sync/config.tsx @@ -101,10 +101,20 @@ export const ResourceSyncConfig = ({ }, }, { - label: "Github Webhooks", + label: "Git Webhooks", description: "Configure your repo provider to send webhooks to Monitor", components: { + ["Guard" as any]: () => { + if (update.branch ?? config.branch) { + return null; + } + return ( + +
Must configure Branch before webhooks will work.
+
+ ); + }, ["refresh" as any]: () => ( ), webhook_enabled: webhooks !== undefined && !webhooks.managed, + webhook_secret: { + description: + "Provide a custom webhook secret for this resource, or use the global default.", + placeholder: "Input custom secret", + }, ["managed" as any]: () => { const inv = useInvalidate(); const { toast } = useToast(); diff --git a/frontend/src/components/resources/stack/config.tsx b/frontend/src/components/resources/stack/config.tsx index 50e58bd4a..1965babd0 100644 --- a/frontend/src/components/resources/stack/config.tsx +++ b/frontend/src/components/resources/stack/config.tsx @@ -380,6 +380,11 @@ export const StackConfig = ({ !!(update.branch ?? config.branch) && webhooks !== undefined && !webhooks.managed, + webhook_secret: { + description: + "Provide a custom webhook secret for this resource, or use the global default.", + placeholder: "Input custom secret", + }, ["managed" as any]: () => { const inv = useInvalidate(); const { toast } = useToast();