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 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}