use crate::packs::{DestructivePattern, Pack, SafePattern};
use crate::{destructive_pattern, safe_pattern};
#[must_use]
pub fn create_pack() -> Pack {
Pack {
id: "infrastructure.pulumi".to_string(),
name: "Pulumi",
description: "Protects against destructive Pulumi operations like destroy \
and up with -y (auto-approve)",
keywords: &["pulumi", "destroy", "state"],
safe_patterns: create_safe_patterns(),
destructive_patterns: create_destructive_patterns(),
keyword_matcher: None,
safe_regex_set: None,
safe_regex_set_is_complete: false,
}
}
fn create_safe_patterns() -> Vec<SafePattern> {
vec![
safe_pattern!(
"pulumi-preview",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+preview(?=\s|$)"
),
safe_pattern!(
"pulumi-stack-ls",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+stack\s+ls(?=\s|$)"
),
safe_pattern!(
"pulumi-stack-select",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+stack\s+select(?=\s|$)"
),
safe_pattern!(
"pulumi-stack-init",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+stack\s+init(?=\s|$)"
),
safe_pattern!(
"pulumi-config",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+config(?=\s|$)"
),
safe_pattern!(
"pulumi-whoami",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+whoami(?=\s|$)"
),
safe_pattern!(
"pulumi-version",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+version(?=\s|$)"
),
safe_pattern!(
"pulumi-about",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+about(?=\s|$)"
),
safe_pattern!(
"pulumi-logs",
r"pulumi\b(?:\s+--?\S+(?:\s+\S+)?)*\s+logs(?=\s|$)"
),
]
}
fn create_destructive_patterns() -> Vec<DestructivePattern> {
vec![
destructive_pattern!(
"destroy",
r"pulumi\b.*?\bdestroy(?=\s|$)",
"pulumi destroy removes ALL managed infrastructure. Use 'pulumi preview --diff' first.",
Critical,
"pulumi destroy removes ALL managed infrastructure:\n\n\
- Every resource in your stack is destroyed\n\
- Cloud resources (VMs, databases, networks) deleted\n\
- Cannot be undone without backups/recreation\n\
- Use --target to destroy specific resources only\n\n\
Preview first: pulumi preview --diff"
),
destructive_pattern!(
"up-yes",
r"pulumi\b.*?\bup\s+.*(?:-y\b|--yes\b)",
"pulumi up -y skips confirmation. Remove -y flag for safety.",
High,
"pulumi up -y skips confirmation:\n\n\
- No opportunity to review changes before applying\n\
- Intended for CI/CD, not interactive use\n\
- Changes may destroy or recreate resources\n\
- Replacements can cause downtime\n\n\
For safety: remove -y and review the preview"
),
destructive_pattern!(
"state-delete",
r"pulumi\b.*?\bstate\s+delete",
"pulumi state delete removes resource from state without destroying it.",
High,
"pulumi state delete orphans resources:\n\n\
- Resource removed from Pulumi state\n\
- Actual cloud resource still exists\n\
- Resource becomes 'unmanaged' (Pulumi ignores it)\n\
- May cause drift between state and reality\n\n\
Consider: pulumi refresh to sync state with reality"
),
destructive_pattern!(
"stack-rm",
r"pulumi\b.*?\bstack\s+rm",
"pulumi stack rm removes the stack. Use --force only if stack is empty.",
High,
"pulumi stack rm removes the entire stack:\n\n\
- Stack and its state deleted\n\
- Does NOT destroy actual infrastructure (unless empty)\n\
- --force required if resources still exist\n\
- Resources become unmanaged (orphaned)\n\n\
Destroy resources first: pulumi destroy, then rm stack"
),
destructive_pattern!(
"refresh-yes",
r"pulumi\b.*?\brefresh\s+.*(?:-y\b|--yes\b)",
"pulumi refresh -y auto-approves state changes. Review changes first.",
Medium,
"pulumi refresh -y auto-approves state sync:\n\n\
- Syncs Pulumi state with actual cloud resources\n\
- May delete resources from state if not found\n\
- May update state with drift from cloud\n\n\
Run without -y first to review detected changes"
),
destructive_pattern!(
"cancel",
r"pulumi\b.*?\bcancel\b",
"pulumi cancel terminates an in-progress update, which may leave resources in inconsistent state.",
High,
"pulumi cancel stops in-progress operations:\n\n\
- Terminates currently running update/destroy\n\
- Resources may be left in inconsistent state\n\
- Some resources created, others not\n\
- May require manual cleanup\n\n\
Use only when operation is stuck/hung"
),
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packs::test_helpers::*;
#[test]
fn pulumi_patterns_match_with_global_flags() {
let pack = create_pack();
assert_blocks(&pack, "pulumi --cwd ./prod destroy", "destroy");
assert_blocks(&pack, "pulumi --non-interactive --cwd ./prod up -y", "-y");
assert_blocks(
&pack,
"pulumi --cwd ./prod state delete urn:pulumi:prod::db::aws:rds/instance:Instance::main",
"state",
);
assert_blocks(
&pack,
"pulumi --verbose --cwd ./prod stack rm prod-old",
"stack rm",
);
}
}