1use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
2use aws_sdk_cloudformation::types::{Capability, StackStatus, Tag};
3use rusty_cdk_core::stack::Stack;
4use serde::Deserialize;
5use std::collections::HashMap;
6use std::process::exit;
7use std::sync::Arc;
8use std::time::Duration;
9use tokio::time::sleep;
10use rusty_cdk_core::wrappers::StringWithOnlyAlphaNumericsAndHyphens;
11
12#[derive(Deserialize)]
13struct StackOnlyMetadata {
14 #[serde(rename = "Metadata")]
15 pub(crate) metadata: HashMap<String, String>,
16}
17
18async fn get_existing_template(client: &aws_sdk_cloudformation::Client, stack_name: &str) -> Option<String> {
19 match client.describe_stacks().stack_name(stack_name).send().await {
20 Ok(_) => {
21 let template = client.get_template()
22 .stack_name(stack_name)
23 .send()
24 .await;
25 template.unwrap().template_body
26 }
27 Err(_) => {
28 None
29 }
30 }
31}
32
33pub async fn deploy(name: StringWithOnlyAlphaNumericsAndHyphens, mut stack: Stack) {
89 let name = name.0;
90 let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
91 .stalled_stream_protection(StalledStreamProtectionConfig::disabled())
93 .load()
94 .await;
95 let s3_client = Arc::new(aws_sdk_s3::Client::new(&config));
96
97 let tasks: Vec<_> = stack
98 .get_assets()
99 .into_iter()
100 .map(|a| {
101 println!("uploading asset {} to {}/{}", a.path, a.s3_bucket, a.s3_key);
102 let s3_client = s3_client.clone();
103 tokio::spawn(async move {
104 let body = aws_sdk_s3::primitives::ByteStream::from_path(a.path).await;
105 s3_client
106 .put_object()
107 .bucket(a.s3_bucket)
108 .key(a.s3_key)
109 .body(body.unwrap()) .send()
111 .await
112 .unwrap();
113 })
114 })
115 .collect();
116
117 for task in tasks {
118 task.await.unwrap();
119 }
120
121 let cloudformation_client = aws_sdk_cloudformation::Client::new(&config);
122 let existing_template = get_existing_template(&cloudformation_client, &name).await;
123 let tags = stack.get_tags();
124 let tags = if tags.is_empty() {
125 None
126 } else {
127 Some(tags.into_iter()
128 .map(|v| Tag::builder().key(v.0).value(v.1).build())
129 .collect())
130 };
131
132 match existing_template {
133 Some(existing) => {
134 let meta: StackOnlyMetadata = serde_json::from_str(existing.as_str()).expect("an existing stack should have our 'id' metadata");
135 stack.update_resource_ids_for_existing_stack(meta.metadata);
136 let body = get_template_or_exit(&stack);
137
138 match cloudformation_client
139 .update_stack()
140 .stack_name(&name)
141 .template_body(body)
142 .capabilities(Capability::CapabilityNamedIam)
143 .set_tags(tags)
144 .send()
145 .await
146 {
147 Ok(_) => println!("stack {name} update started"),
148 Err(e) => eprintln!("an error occurred while creating the stack: {e:#?}"),
149 }
150 }
151 None => {
152 let body = get_template_or_exit(&stack);
153
154 match cloudformation_client
155 .create_stack()
156 .stack_name(&name)
157 .template_body(body)
158 .capabilities(Capability::CapabilityNamedIam)
159 .set_tags(tags)
160 .send()
161 .await
162 {
163 Ok(_) => println!("stack {name} creation started"),
164 Err(e) => {
165 eprintln!("an error occurred while creating the stack: {e:#?}");
166 exit(1);
167 }
168 }
169 }
170 }
171
172 loop {
173 let status = cloudformation_client.describe_stacks().stack_name(&name).send().await;
174 let mut stacks = status
175 .expect("to get a describe stacks result")
176 .stacks
177 .expect("to have a list of stacks");
178 let first_stack = stacks.get_mut(0).expect("to find our stack");
179 let status = first_stack.stack_status.take().expect("stack to have status");
180
181 match status {
182 StackStatus::CreateComplete => {
183 println!("creation completed successfully!");
184 exit(0);
185 }
186 StackStatus::CreateFailed => {
187 println!("creation failed");
188 exit(1);
189 }
190 StackStatus::CreateInProgress => {
191 println!("creating...");
192 }
193 StackStatus::UpdateComplete | StackStatus::UpdateCompleteCleanupInProgress => {
194 println!("update completed successfully!");
195 exit(0);
196 }
197 StackStatus::UpdateRollbackComplete
198 | StackStatus::UpdateRollbackCompleteCleanupInProgress
199 | StackStatus::UpdateRollbackFailed
200 | StackStatus::UpdateRollbackInProgress
201 | StackStatus::UpdateFailed => {
202 println!("update failed");
203 exit(1);
204 }
205 StackStatus::UpdateInProgress => {
206 println!("updating...");
207 }
208 _ => {
209 println!("encountered unexpected cloudformation status: {status}");
210 exit(1);
211 }
212 }
213
214 sleep(Duration::from_secs(10)).await;
215 }
216}
217
218fn get_template_or_exit(stack: &Stack) -> String {
219 match stack.synth() {
220 Ok(b) => b,
221 Err(e) => {
222 eprintln!("{e:#?}");
223 exit(1);
224 }
225 }
226}