forjar 1.4.2

Rust-native Infrastructure as Code — bare-metal first, BLAKE3 state, provenance tracing
Documentation
//! Apply notification dispatch helpers — sends apply results to notification channels.
use std::path::Path;
pub(super) fn send_webhook(url: &str, payload: &str) {
    match std::process::Command::new("curl")
        .args([
            "-sf",
            "-X",
            "POST",
            "-H",
            "Content-Type: application/json",
            "-d",
            payload,
            url,
        ])
        .output()
    {
        Ok(o) if !o.status.success() => {
            eprintln!(
                "warn: webhook to {} failed (exit {})",
                url,
                o.status.code().unwrap_or(-1)
            );
        }
        Err(e) => eprintln!("warn: webhook to {url} error: {e}"),
        _ => {}
    }
}
pub(super) fn send_webhook_with_header(url: &str, header: &str, payload: &str) {
    match std::process::Command::new("curl")
        .args([
            "-sf",
            "-X",
            "POST",
            "-H",
            "Content-Type: application/json",
            "-H",
            header,
            "-d",
            payload,
            url,
        ])
        .output()
    {
        Ok(o) if !o.status.success() => {
            eprintln!(
                "warn: webhook to {} failed (exit {})",
                url,
                o.status.code().unwrap_or(-1)
            );
        }
        Err(e) => eprintln!("warn: webhook to {url} error: {e}"),
        _ => {}
    }
}
pub(super) fn event_json(status: &str, config: &Path) -> String {
    super::apply_gates::format_event_json(status, &config.display().to_string())
}
pub(super) fn publish_stdin(cmd: &str, args: &[&str], message: &str) {
    let child = std::process::Command::new(cmd)
        .args(args)
        .stdin(std::process::Stdio::piped())
        .spawn();
    if let Ok(mut c) = child {
        if let Some(ref mut stdin) = c.stdin {
            use std::io::Write;
            let _ = stdin.write_all(message.as_bytes());
        }
        let _ = c.wait();
    }
}
pub(crate) struct NotifyOpts<'a> {
    pub slack: Option<&'a str>,
    pub email: Option<&'a str>,
    pub webhook: Option<&'a str>,
    pub teams: Option<&'a str>,
    pub discord: Option<&'a str>,
    pub opsgenie: Option<&'a str>,
    pub datadog: Option<&'a str>,
    pub newrelic: Option<&'a str>,
    pub grafana: Option<&'a str>,
    pub victorops: Option<&'a str>,
    pub msteams_adaptive: Option<&'a str>,
    pub incident: Option<&'a str>,
    pub sns: Option<&'a str>,
    pub pubsub: Option<&'a str>,
    pub eventbridge: Option<&'a str>,
    pub kafka: Option<&'a str>,
    pub azure_servicebus: Option<&'a str>,
    pub gcp_pubsub_v2: Option<&'a str>,
    pub rabbitmq: Option<&'a str>,
    pub nats: Option<&'a str>,
    pub mqtt: Option<&'a str>,
    pub redis: Option<&'a str>,
    pub amqp: Option<&'a str>,
    pub stomp: Option<&'a str>,
    pub zeromq: Option<&'a str>,
    pub grpc: Option<&'a str>,
    pub sqs: Option<&'a str>,
    pub mattermost: Option<&'a str>,
    pub ntfy: Option<&'a str>,
    pub pagerduty: Option<&'a str>,
    pub discord_webhook: Option<&'a str>,
    pub teams_webhook: Option<&'a str>,
    pub slack_blocks: Option<&'a str>,
    pub custom_template: Option<&'a str>,
    pub custom_webhook: Option<&'a str>,
    pub custom_headers: Option<&'a str>,
    pub custom_json: Option<&'a str>,
    pub custom_filter: Option<&'a str>,
    pub custom_retry: Option<&'a str>,
    pub custom_transform: Option<&'a str>,
    pub custom_batch: Option<&'a str>,
    pub custom_deduplicate: Option<&'a str>,
    pub custom_throttle: Option<&'a str>,
    pub custom_aggregate: Option<&'a str>,
    pub custom_priority: Option<&'a str>,
    pub custom_routing: Option<&'a str>,
    pub custom_dedup_window: Option<&'a str>,
    pub custom_rate_limit: Option<&'a str>,
    pub custom_backoff: Option<&'a str>,
    pub custom_circuit_breaker: Option<&'a str>,
    pub custom_dead_letter: Option<&'a str>,
    pub custom_escalation: Option<&'a str>,
    pub custom_correlation: Option<&'a str>,
    pub custom_sampling: Option<&'a str>,
    pub custom_digest: Option<&'a str>,
    pub custom_severity_filter: Option<&'a str>,
}
pub(crate) fn send_apply_notifications(
    opts: &NotifyOpts<'_>,
    result: &Result<(), String>,
    config_path: &Path,
) {
    let status = super::apply_gates::notify_status(result);
    let msg = event_json(status, config_path);
    send_webhook_notifications(opts, status, config_path);
    send_monitoring_notifications(opts, status, config_path);
    send_incident_notifications(opts, result, config_path);
    send_email_notification(opts.email, status, config_path);
    send_cloud_notifications(opts, &msg);
    send_broker_notifications(opts, &msg);
}
pub(super) fn send_webhook_notifications(opts: &NotifyOpts<'_>, status: &str, config: &Path) {
    if let Some(url) = opts.slack {
        send_webhook(
            url,
            &format!(
                r#"{{"text":"forjar apply {}: {}"}}"#,
                status,
                config.display()
            ),
        );
    }
    if let Some(url) = opts.webhook {
        send_webhook(
            url,
            &format!(
                r#"{{"event":"apply_complete","status":"{}","config":"{}"}}"#,
                status,
                config.display()
            ),
        );
    }
    if let Some(url) = opts.teams {
        send_webhook(
            url,
            &format!(
                r#"{{"@type":"MessageCard","summary":"forjar apply {}","text":"Apply {} for {}"}}"#,
                status,
                status,
                config.display()
            ),
        );
    }
    if let Some(url) = opts.discord {
        send_webhook(
            url,
            &format!(
                r#"{{"content":"forjar apply {}: {}"}}"#,
                status,
                config.display()
            ),
        );
    }
    if let Some(url) = opts.msteams_adaptive {
        send_webhook(
            url,
            &format!(
                r#"{{"type":"message","attachments":[{{"contentType":"application/vnd.microsoft.card.adaptive","content":{{"type":"AdaptiveCard","body":[{{"type":"TextBlock","text":"Forjar Apply: {}","weight":"bolder"}},{{"type":"TextBlock","text":"Config: {}"}}],"$schema":"http://adaptivecards.io/schemas/adaptive-card.json","version":"1.4"}}}}]}}"#,
                status,
                config.display()
            ),
        );
    }
    if let Some(url) = opts.mattermost {
        send_webhook(
            url,
            &format!(
                r#"{{"text":"forjar apply {}: {}"}}"#,
                status,
                config.display()
            ),
        );
    }
    if let Some(topic) = opts.ntfy {
        let url = format!("https://ntfy.sh/{topic}");
        let msg = format!("forjar apply {}: {}", status, config.display());
        if let Err(e) = std::process::Command::new("curl")
            .args(["-sf", "-d", &msg, &url])
            .output()
        {
            eprintln!("warning: ntfy notification error: {e}");
        }
    }
    if let Some(url) = opts.grafana {
        let ts = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        send_webhook(
            url,
            &format!(
                r#"{{"text":"forjar apply {status}","tags":["forjar","deploy"],"time":{ts}}}"#
            ),
        );
    }
}
pub(super) fn send_monitoring_notifications(opts: &NotifyOpts<'_>, status: &str, config: &Path) {
    if let Some(key) = opts.opsgenie {
        send_webhook_with_header(
            "https://api.opsgenie.com/v2/alerts",
            &format!("Authorization: GenieKey {key}"),
            &format!(
                r#"{{"message":"forjar apply {}","description":"Apply {} for {}","priority":"P3"}}"#,
                status,
                status,
                config.display()
            ),
        );
    }
    if let Some(key) = opts.datadog {
        send_webhook_with_header(
            "https://api.datadoghq.com/api/v1/events",
            &format!("DD-API-KEY: {key}"),
            &format!(
                r#"{{"title":"forjar apply {}","text":"Apply {} for {}","alert_type":"info"}}"#,
                status,
                status,
                config.display()
            ),
        );
    }
    if let Some(key) = opts.newrelic {
        send_webhook_with_header(
            "https://insights-collector.newrelic.com/v1/accounts/events",
            &format!("Api-Key: {key}"),
            &format!(
                r#"{{"eventType":"ForjarApply","status":"{}","config":"{}"}}"#,
                status,
                config.display()
            ),
        );
    }
}
pub(super) fn send_incident_notifications(
    opts: &NotifyOpts<'_>,
    result: &Result<(), String>,
    config: &Path,
) {
    if let Some(key) = opts.victorops {
        let (vo_status, verb) = super::apply_gates::victorops_status(result);
        send_webhook(
            &format!(
                "https://alert.victorops.com/integrations/generic/20131114/alert/{key}/forjar"
            ),
            &format!(
                r#"{{"message_type":"{}","entity_display_name":"forjar apply","state_message":"Apply {} for {}"}}"#,
                vo_status,
                verb,
                config.display()
            ),
        );
    }
    if let Some(key) = opts.incident {
        if result.is_err() {
            send_webhook(
                "https://events.pagerduty.com/v2/enqueue",
                &format!(
                    r#"{{"routing_key":"{}","event_action":"trigger","payload":{{"summary":"Forjar apply failed: {}","source":"forjar","severity":"critical","component":"infrastructure","group":"apply"}}}}"#,
                    key,
                    config.display()
                ),
            );
        }
    }
    send_pagerduty_notification(opts.pagerduty, result, config);
    send_discord_webhook_notification(opts.discord_webhook, result, config);
    send_teams_webhook_notification(opts.teams_webhook, result, config);
    send_slack_blocks_notification(opts.slack_blocks, result, config);
    send_custom_template_notification(opts.custom_template, result, config);
    send_custom_webhook_notification(opts.custom_webhook, result, config);
    send_custom_headers_notification(opts.custom_headers, result, config);
    send_custom_json_notification(opts.custom_json, result, config);
    send_custom_filter_notification(opts.custom_filter, result, config);
    send_custom_retry_notification(opts.custom_retry, result, config);
    super::dispatch_notify_custom::send_custom_transform_notification(
        opts.custom_transform,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_batch_notification(
        opts.custom_batch,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_deduplicate_notification(
        opts.custom_deduplicate,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_throttle_notification(
        opts.custom_throttle,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_aggregate_notification(
        opts.custom_aggregate,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_priority_notification(
        opts.custom_priority,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_routing_notification(
        opts.custom_routing,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_dedup_window_notification(
        opts.custom_dedup_window,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_rate_limit_notification(
        opts.custom_rate_limit,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_backoff_notification(
        opts.custom_backoff,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_circuit_breaker_notification(
        opts.custom_circuit_breaker,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_dead_letter_notification(
        opts.custom_dead_letter,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_escalation_notification(
        opts.custom_escalation,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_correlation_notification(
        opts.custom_correlation,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_sampling_notification(
        opts.custom_sampling,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_digest_notification(
        opts.custom_digest,
        result,
        config,
    );
    super::dispatch_notify_custom::send_custom_severity_filter_notification(
        opts.custom_severity_filter,
        result,
        config,
    );
}

pub(super) use super::dispatch_notify_b::*;