1use std::{
2 thread,
3 time::{Duration, Instant},
4};
5
6use crate::errors::{
7 Error::{Other, API},
8 Result,
9};
10use aws_sdk_cloudformation::{
11 error::{DeleteStackError, DescribeStacksError},
12 model::{Capability, OnFailure, Output, Parameter, StackStatus, Tag},
13 types::SdkError,
14 Client,
15};
16use aws_types::SdkConfig as AwsSdkConfig;
17use log::{info, warn};
18
19#[derive(Debug, Clone)]
21pub struct Manager {
22 #[allow(dead_code)]
23 shared_config: AwsSdkConfig,
24 cli: Client,
25}
26
27impl Manager {
28 pub fn new(shared_config: &AwsSdkConfig) -> Self {
29 let cloned = shared_config.clone();
30 let cli = Client::new(shared_config);
31 Self {
32 shared_config: cloned,
33 cli,
34 }
35 }
36
37 pub async fn create_stack(
40 &self,
41 stack_name: &str,
42 capabilities: Option<Vec<Capability>>,
43 on_failure: OnFailure,
44 template_body: &str,
45 tags: Option<Vec<Tag>>,
46 parameters: Option<Vec<Parameter>>,
47 ) -> Result<Stack> {
48 info!("creating stack '{}'", stack_name);
49 let ret = self
50 .cli
51 .create_stack()
52 .stack_name(stack_name)
53 .set_capabilities(capabilities)
54 .on_failure(on_failure)
55 .template_body(template_body)
56 .set_tags(tags)
57 .set_parameters(parameters)
58 .send()
59 .await;
60 let resp = match ret {
61 Ok(v) => v,
62 Err(e) => {
63 return Err(API {
64 message: format!("failed create_stack {:?}", e),
65 is_retryable: is_error_retryable(&e),
66 });
67 }
68 };
69
70 let stack_id = resp.stack_id().unwrap();
71 info!("created stack '{}' with '{}'", stack_name, stack_id);
72 Ok(Stack::new(
73 stack_name,
74 stack_id,
75 StackStatus::CreateInProgress,
76 None,
77 ))
78 }
79
80 pub async fn delete_stack(&self, stack_name: &str) -> Result<Stack> {
83 info!("deleting stack '{}'", stack_name);
84 let ret = self.cli.delete_stack().stack_name(stack_name).send().await;
85 match ret {
86 Ok(_) => {}
87 Err(e) => {
88 if !is_error_delete_stack_does_not_exist(&e) {
89 return Err(API {
90 message: format!("failed schedule_key_deletion {:?}", e),
91 is_retryable: is_error_retryable(&e),
92 });
93 }
94 warn!("stack already deleted ({})", e);
95 return Ok(Stack::new(
96 stack_name,
97 "",
98 StackStatus::DeleteComplete,
99 None,
100 ));
101 }
102 };
103
104 Ok(Stack::new(
105 stack_name,
106 "",
107 StackStatus::DeleteInProgress,
108 None,
109 ))
110 }
111
112 pub async fn poll_stack(
114 &self,
115 stack_name: &str,
116 desired_status: StackStatus,
117 timeout: Duration,
118 interval: Duration,
119 ) -> Result<Stack> {
120 info!(
121 "polling stack '{}' with desired status {:?} for timeout {:?} and interval {:?}",
122 stack_name, desired_status, timeout, interval,
123 );
124
125 let start = Instant::now();
126 let mut cnt: u128 = 0;
127 loop {
128 let elapsed = start.elapsed();
129 if elapsed.gt(&timeout) {
130 break;
131 }
132
133 let itv = {
134 if cnt == 0 {
135 Duration::from_secs(1)
137 } else {
138 interval
139 }
140 };
141 thread::sleep(itv);
142
143 let ret = self
144 .cli
145 .describe_stacks()
146 .stack_name(stack_name)
147 .send()
148 .await;
149 let stacks = match ret {
150 Ok(v) => v.stacks,
151 Err(e) => {
152 if is_error_describe_stacks_does_not_exist(&e)
154 && desired_status.eq(&StackStatus::DeleteComplete)
155 {
156 info!("stack already deleted as desired");
157 return Ok(Stack::new(stack_name, "", desired_status, None));
158 }
159 return Err(API {
160 message: format!("failed describe_stacks {:?}", e),
161 is_retryable: is_error_retryable(&e),
162 });
163 }
164 };
165 let stacks = stacks.unwrap();
166 if stacks.len() != 1 {
167 return Err(Other {
169 message: String::from("failed to find stack"),
170 is_retryable: false,
171 });
172 }
173
174 let stack = stacks.get(0).unwrap();
175 let current_id = stack.stack_id().unwrap();
176 let current_status = stack.stack_status().unwrap();
177 info!("poll (current {:?}, elapsed {:?})", current_status, elapsed);
178
179 if desired_status.ne(&StackStatus::DeleteComplete)
180 && current_status.eq(&StackStatus::DeleteComplete)
181 {
182 return Err(Other {
183 message: String::from("stack create/update failed thus deleted"),
184 is_retryable: false,
185 });
186 }
187
188 if desired_status.eq(&StackStatus::CreateComplete)
189 && current_status.eq(&StackStatus::CreateFailed)
190 {
191 return Err(Other {
192 message: String::from("stack create failed"),
193 is_retryable: false,
194 });
195 }
196
197 if desired_status.eq(&StackStatus::DeleteComplete)
198 && current_status.eq(&StackStatus::DeleteFailed)
199 {
200 return Err(Other {
201 message: String::from("stack delete failed"),
202 is_retryable: false,
203 });
204 }
205
206 if current_status.eq(&desired_status) {
207 let outputs = stack.outputs();
208 let outputs = outputs.unwrap();
209 let outputs = Vec::from(outputs);
210 let current_stack = Stack::new(
211 stack_name,
212 current_id,
213 current_status.clone(),
214 Some(outputs),
215 );
216 return Ok(current_stack);
217 }
218
219 cnt += 1;
220 }
221
222 return Err(Other {
223 message: format!("failed to poll stack {} in time", stack_name),
224 is_retryable: true,
225 });
226 }
227}
228
229#[derive(Debug)]
231pub struct Stack {
232 pub name: String,
233 pub id: String,
234 pub status: StackStatus,
235 pub outputs: Option<Vec<Output>>,
236}
237
238impl Stack {
239 pub fn new(name: &str, id: &str, status: StackStatus, outputs: Option<Vec<Output>>) -> Self {
240 Self {
242 name: String::from(name),
243 id: String::from(id),
244 status,
245 outputs,
246 }
247 }
248}
249
250#[inline]
251pub fn is_error_retryable<E>(e: &SdkError<E>) -> bool {
252 match e {
253 SdkError::TimeoutError(_) | SdkError::ResponseError { .. } => true,
254 SdkError::DispatchFailure(e) => e.is_timeout() || e.is_io(),
255 _ => false,
256 }
257}
258
259#[inline]
260fn is_error_delete_stack_does_not_exist(e: &SdkError<DeleteStackError>) -> bool {
261 match e {
262 SdkError::ServiceError { err, .. } => {
263 let msg = format!("{:?}", err);
264 msg.contains("does not exist")
265 }
266 _ => false,
267 }
268}
269
270#[inline]
271fn is_error_describe_stacks_does_not_exist(e: &SdkError<DescribeStacksError>) -> bool {
272 match e {
273 SdkError::ServiceError { err, .. } => {
274 let msg = format!("{:?}", err);
275 msg.contains("does not exist")
276 }
277 _ => false,
278 }
279}