rusticity_core/
cfn.rs

1use crate::config::AwsConfig;
2use anyhow::Result;
3
4#[derive(Clone, Debug)]
5pub struct Stack {
6    pub name: String,
7    pub stack_id: String,
8    pub status: String,
9    pub created_time: String,
10    pub updated_time: String,
11    pub deleted_time: String,
12    pub drift_status: String,
13    pub last_drift_check_time: String,
14    pub status_reason: String,
15    pub description: String,
16}
17
18pub struct CloudFormationClient {
19    config: AwsConfig,
20}
21
22impl CloudFormationClient {
23    pub fn new(config: AwsConfig) -> Self {
24        Self { config }
25    }
26
27    pub async fn list_stacks(&self, include_nested: bool) -> Result<Vec<Stack>> {
28        let client = self.config.cloudformation_client().await;
29
30        let mut stacks = Vec::new();
31        let mut next_token: Option<String> = None;
32
33        loop {
34            let mut request = client.list_stacks();
35            if let Some(token) = next_token {
36                request = request.next_token(token);
37            }
38
39            let response = request.send().await?;
40
41            if let Some(stack_summaries) = response.stack_summaries {
42                for stack in stack_summaries {
43                    // Skip nested stacks if not requested
44                    if !include_nested {
45                        if let Some(root_id) = &stack.root_id {
46                            if root_id != stack.stack_id.as_deref().unwrap_or("") {
47                                continue;
48                            }
49                        }
50                    }
51
52                    stacks.push(Stack {
53                        name: stack.stack_name.unwrap_or_default(),
54                        stack_id: stack.stack_id.unwrap_or_default(),
55                        status: stack
56                            .stack_status
57                            .map(|s| s.as_str().to_string())
58                            .unwrap_or_default(),
59                        created_time: stack
60                            .creation_time
61                            .map(|dt| {
62                                let timestamp = dt.secs();
63                                let datetime = chrono::DateTime::from_timestamp(timestamp, 0)
64                                    .unwrap_or_default();
65                                datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
66                            })
67                            .unwrap_or_default(),
68                        updated_time: stack
69                            .last_updated_time
70                            .map(|dt| {
71                                let timestamp = dt.secs();
72                                let datetime = chrono::DateTime::from_timestamp(timestamp, 0)
73                                    .unwrap_or_default();
74                                datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
75                            })
76                            .unwrap_or_default(),
77                        deleted_time: stack
78                            .deletion_time
79                            .map(|dt| {
80                                let timestamp = dt.secs();
81                                let datetime = chrono::DateTime::from_timestamp(timestamp, 0)
82                                    .unwrap_or_default();
83                                datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
84                            })
85                            .unwrap_or_default(),
86                        drift_status: stack
87                            .drift_information
88                            .as_ref()
89                            .and_then(|d| d.stack_drift_status.as_ref())
90                            .map(|s| format!("{:?}", s))
91                            .unwrap_or_default(),
92                        last_drift_check_time: stack
93                            .drift_information
94                            .and_then(|d| d.last_check_timestamp)
95                            .map(|dt| {
96                                let timestamp = dt.secs();
97                                let datetime = chrono::DateTime::from_timestamp(timestamp, 0)
98                                    .unwrap_or_default();
99                                datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
100                            })
101                            .unwrap_or_default(),
102                        status_reason: stack.stack_status_reason.unwrap_or_default(),
103                        description: stack.template_description.unwrap_or_default(),
104                    });
105                }
106            }
107
108            next_token = response.next_token;
109            if next_token.is_none() {
110                break;
111            }
112        }
113
114        Ok(stacks)
115    }
116
117    pub async fn describe_stack(&self, stack_name: &str) -> Result<StackDetails> {
118        let client = self.config.cloudformation_client().await;
119
120        let response = client
121            .describe_stacks()
122            .stack_name(stack_name)
123            .send()
124            .await?;
125
126        let stack = response
127            .stacks()
128            .first()
129            .ok_or_else(|| anyhow::anyhow!("Stack not found"))?;
130
131        Ok(StackDetails {
132            detailed_status: String::new(),
133            root_stack: stack.root_id().unwrap_or("").to_string(),
134            parent_stack: stack.parent_id().unwrap_or("").to_string(),
135            termination_protection: stack.enable_termination_protection().unwrap_or(false),
136            iam_role: stack.role_arn().unwrap_or("").to_string(),
137            tags: stack
138                .tags()
139                .iter()
140                .map(|t| {
141                    (
142                        t.key().unwrap_or("").to_string(),
143                        t.value().unwrap_or("").to_string(),
144                    )
145                })
146                .collect(),
147            stack_policy: String::new(),
148            rollback_monitoring_time: String::new(),
149            rollback_alarms: stack
150                .rollback_configuration()
151                .map(|rc| {
152                    rc.rollback_triggers()
153                        .iter()
154                        .map(|t| t.arn().unwrap_or("").to_string())
155                        .collect()
156                })
157                .unwrap_or_default(),
158            notification_arns: stack
159                .notification_arns()
160                .iter()
161                .map(|s| s.to_string())
162                .collect(),
163        })
164    }
165}
166
167#[derive(Debug, Clone)]
168pub struct StackDetails {
169    pub detailed_status: String,
170    pub root_stack: String,
171    pub parent_stack: String,
172    pub termination_protection: bool,
173    pub iam_role: String,
174    pub tags: Vec<(String, String)>,
175    pub stack_policy: String,
176    pub rollback_monitoring_time: String,
177    pub rollback_alarms: Vec<String>,
178    pub notification_arns: Vec<String>,
179}