1use crate::cloudwatch::{LogGroupBuilder, LogGroupRef};
2use crate::iam::{
3 find_missing_services, map_toml_dependencies_to_services, AssumeRolePolicyDocumentBuilder, Effect, Permission as IamPermission, Policy, PrincipalBuilder,
4 RoleBuilder, RolePropertiesBuilder, RoleRef, StatementBuilder,
5};
6use crate::intrinsic::{get_arn, get_ref, join, AWS_PARTITION_PSEUDO_PARAM};
7use crate::lambda::{Environment, EventSourceMapping, EventSourceMappingType, EventSourceProperties, Function, FunctionRef, FunctionType, LambdaCode, LambdaFunctionProperties, LambdaPermissionProperties, LoggingInfo, Permission, PermissionRef, PermissionType, ScalingConfig};
8use crate::shared::Id;
9use crate::sqs::QueueRef;
10use crate::stack::{Asset, Resource, StackBuilder};
11use crate::type_state;
12use crate::wrappers::{
13 Bucket, EnvVarKey, LambdaPermissionAction, LogGroupName, Memory, RetentionInDays, SqsEventSourceMaxConcurrency,
14 StringWithOnlyAlphaNumericsUnderscoresAndHyphens, Timeout, TomlFile, ZipFile,
15};
16use serde_json::Value;
17use std::marker::PhantomData;
18use std::vec;
19
20#[derive(Debug, Clone)]
21pub enum Runtime {
22 NodeJs22,
23 Java21,
24 Python313,
25 ProvidedAl2023,
26}
27
28impl From<Runtime> for String {
29 fn from(value: Runtime) -> Self {
30 match value {
31 Runtime::NodeJs22 => "nodejs22.x".to_string(),
32 Runtime::Java21 => "java21".to_string(),
33 Runtime::Python313 => "python3.13".to_string(),
34 Runtime::ProvidedAl2023 => "provided.al2023".to_string(),
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
40pub enum Architecture {
41 X86_64,
42 ARM64,
43}
44
45impl From<Architecture> for String {
46 fn from(value: Architecture) -> Self {
47 match value {
48 Architecture::X86_64 => "x86_64".to_string(),
49 Architecture::ARM64 => "arm64".to_string(),
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
55pub enum PackageType {
56 Image,
57 Zip,
58}
59
60impl From<PackageType> for String {
61 fn from(value: PackageType) -> Self {
62 match value {
63 PackageType::Image => "Image".to_string(),
64 PackageType::Zip => "Zip".to_string(),
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
70pub struct Zip {
71 bucket: String,
72 file: ZipFile,
73}
74
75impl Zip {
76 pub fn new(bucket: Bucket, file: ZipFile) -> Self {
77 Zip { bucket: bucket.0, file }
78 }
79}
80
81#[derive(Debug, Clone)]
82pub enum Code {
83 Zip(Zip),
84 Inline(String),
85}
86
87type_state!(
88 FunctionBuilderState,
89 StartState,
90 CodeState,
91 ZipStateWithHandler,
92 ZipStateWithHandlerAndRuntime,
93 EventSourceMappingState,
94);
95
96struct EventSourceMappingInfo {
97 id: String,
98 max_concurrency: Option<u16>,
99}
100
101pub struct FunctionBuilder<T: FunctionBuilderState> {
130 state: PhantomData<T>,
131 id: Id,
132 architecture: Architecture,
133 memory: u16,
134 timeout: u16,
135 code: Option<Code>,
136 handler: Option<String>,
137 runtime: Option<Runtime>,
138 additional_policies: Vec<Policy>,
139 aws_services_in_dependencies: Vec<String>,
140 env_vars: Vec<(String, Value)>,
141 function_name: Option<String>,
142 sqs_event_source_mapping: Option<EventSourceMappingInfo>,
143 reserved_concurrent_executions: Option<u32>,
144 log_group: Option<LogGroupRef>,
145}
146
147impl<T: FunctionBuilderState> FunctionBuilder<T> {
148 pub fn function_name(self, name: StringWithOnlyAlphaNumericsUnderscoresAndHyphens) -> FunctionBuilder<T> {
149 Self {
150 function_name: Some(name.0),
151 ..self
152 }
153 }
154
155 pub fn add_permission(mut self, permission: IamPermission) -> FunctionBuilder<T> {
156 self.additional_policies.push(permission.into_policy());
157 Self { ..self }
158 }
159
160 pub fn check_permissions_against_dependencies(self, cargo_toml: TomlFile) -> Self {
165 let services = map_toml_dependencies_to_services(cargo_toml.0.as_ref());
166
167 Self {
168 aws_services_in_dependencies: services,
169 ..self
170 }
171 }
172
173 pub fn env_var(mut self, key: EnvVarKey, value: Value) -> FunctionBuilder<T> {
174 self.env_vars.push((key.0, value));
175 Self { ..self }
176 }
177
178 pub fn env_var_string<V: Into<String>>(mut self, key: EnvVarKey, value: V) -> FunctionBuilder<T> {
179 self.env_vars.push((key.0, Value::String(value.into())));
180 Self { ..self }
181 }
182
183 pub fn reserved_concurrent_executions(self, executions: u32) -> FunctionBuilder<T> {
184 Self {
185 reserved_concurrent_executions: Some(executions),
186 ..self
187 }
188 }
189
190 pub fn log_group(self, log_group: &LogGroupRef) -> Self {
193 Self {
194 log_group: Some(log_group.clone()),
195 ..self
196 }
197 }
198
199 fn build_internal(self, stack_builder: &mut StackBuilder) -> (FunctionRef, RoleRef, LogGroupRef) {
200 let function_resource_id = Resource::generate_id("LambdaFunction");
201
202 let code = match self.code.expect("code to be present, enforced by builder") {
203 Code::Zip(z) => {
204 let asset_id = Resource::generate_id("Asset");
205 let asset_id = format!("{asset_id}.zip");
206
207 let asset = Asset {
208 s3_bucket: z.bucket.clone(),
209 s3_key: asset_id.clone(),
210 path: z.file.0.to_string(),
211 };
212
213 let code = LambdaCode {
214 s3_bucket: Some(z.bucket),
215 s3_key: Some(asset_id),
216 zipfile: None,
217 };
218
219 (Some(asset), code)
220 }
221 Code::Inline(inline_code) => {
222 let code = LambdaCode {
223 s3_bucket: None,
224 s3_key: None,
225 zipfile: Some(inline_code),
226 };
227 (None, code)
228 }
229 };
230
231 if let Some(mapping) = self.sqs_event_source_mapping {
232 let event_id = Id::generate_id(&self.id, "ESM");
233 let event_resource_id = format!("EventSourceMapping{}", function_resource_id);
234 let event_source_mapping = EventSourceMapping {
235 id: event_id,
236 resource_id: event_resource_id.clone(),
237 r#type: EventSourceMappingType::EventSourceMappingType,
238 properties: EventSourceProperties {
239 event_source_arn: Some(get_arn(&mapping.id)),
240 function_name: Some(get_ref(&function_resource_id)),
241 scaling_config: mapping.max_concurrency.map(|c| ScalingConfig { max_concurrency: c }),
242 },
243 };
244 stack_builder.add_resource(event_source_mapping);
245 };
246
247 let assume_role_statement = StatementBuilder::internal_new(vec!["sts:AssumeRole".to_string()], Effect::Allow)
248 .principal(PrincipalBuilder::new().service("lambda.amazonaws.com").build())
249 .build();
250 let assumed_role_policy_document = AssumeRolePolicyDocumentBuilder::new(vec![assume_role_statement]).build();
251 let managed_policy_arns = vec![join(
252 "",
253 vec![
254 Value::String("arn:".to_string()),
255 get_ref(AWS_PARTITION_PSEUDO_PARAM),
256 Value::String(":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole".to_string()),
257 ],
258 )];
259 let potentially_missing = find_missing_services(&self.aws_services_in_dependencies, &self.additional_policies);
260 let props = RolePropertiesBuilder::new(assumed_role_policy_document, managed_policy_arns)
261 .policies(self.additional_policies)
262 .build();
263
264 let role_id = Id::generate_id(&self.id, "Role");
265 let role_resource_id = Resource::generate_id("LambdaFunctionRole");
266 let role_ref = get_arn(&role_resource_id);
267 let role = RoleBuilder::new_with_info_on_missing(&role_id, &role_resource_id, props, potentially_missing).build(stack_builder);
268
269 let environment = if self.env_vars.is_empty() {
270 None
271 } else {
272 Some(Environment {
273 variables: self.env_vars.into_iter().collect(),
274 })
275 };
276
277 let log_group = if let Some(log_group) = self.log_group {
278 log_group
279 } else {
280 let log_group_id = Id::generate_id(&self.id, "LogGroup");
281 let log_group_name = self.function_name.clone().map(|fun_name| format!("/aws/lambda/{fun_name}"));
282 let base_builder = LogGroupBuilder::new(&log_group_id).log_group_retention(RetentionInDays(731));
283 let log_group = if let Some(name) = log_group_name {
284 base_builder.log_group_name_string(LogGroupName(name)).build(stack_builder)
285 } else {
286 base_builder.build(stack_builder)
287 };
288 log_group
289 };
290
291 let logging_info = LoggingInfo {
292 log_group: Some(log_group.get_ref()),
293 };
294
295 let properties = LambdaFunctionProperties {
296 code: code.1,
297 architectures: vec![self.architecture.into()],
298 memory_size: self.memory,
299 timeout: self.timeout,
300 handler: self.handler,
301 runtime: self.runtime.map(Into::into),
302 role: role_ref,
303 function_name: self.function_name,
304 environment,
305 reserved_concurrent_executions: self.reserved_concurrent_executions,
306 logging_info,
307 };
308
309 stack_builder.add_resource(Function {
310 id: self.id.clone(),
311 resource_id: function_resource_id.clone(),
312 asset: code.0,
313 r#type: FunctionType::FunctionType,
314 properties,
315 });
316
317 let function = FunctionRef::internal_new(self.id, function_resource_id);
318
319 (function, role, log_group)
320 }
321}
322
323impl FunctionBuilder<StartState> {
325 pub fn new(id: &str, architecture: Architecture, memory: Memory, timeout: Timeout) -> FunctionBuilder<StartState> {
334 FunctionBuilder {
335 state: Default::default(),
336 id: Id(id.to_string()),
337 architecture,
338 memory: memory.0,
339 timeout: timeout.0,
340 code: None,
341 handler: None,
342 runtime: None,
343 additional_policies: vec![],
344 aws_services_in_dependencies: vec![],
345 env_vars: vec![],
346 function_name: None,
347 sqs_event_source_mapping: None,
348 reserved_concurrent_executions: None,
349 log_group: None,
350 }
351 }
352
353 pub fn code(self, code: Code) -> FunctionBuilder<CodeState> {
354 FunctionBuilder {
355 code: Some(code),
356 state: Default::default(),
357 id: self.id,
358 architecture: self.architecture,
359 memory: self.memory,
360 timeout: self.timeout,
361 handler: self.handler,
362 runtime: self.runtime,
363 additional_policies: self.additional_policies,
364 aws_services_in_dependencies: self.aws_services_in_dependencies,
365 env_vars: self.env_vars,
366 function_name: self.function_name,
367 sqs_event_source_mapping: self.sqs_event_source_mapping,
368 reserved_concurrent_executions: self.reserved_concurrent_executions,
369 log_group: self.log_group,
370 }
371 }
372}
373
374impl FunctionBuilder<CodeState> {
375 pub fn handler<T: Into<String>>(self, handler: T) -> FunctionBuilder<ZipStateWithHandler> {
376 FunctionBuilder {
377 id: self.id,
378 handler: Some(handler.into()),
379 state: Default::default(),
380 architecture: self.architecture,
381 memory: self.memory,
382 timeout: self.timeout,
383 code: self.code,
384 runtime: self.runtime,
385 additional_policies: self.additional_policies,
386 aws_services_in_dependencies: self.aws_services_in_dependencies,
387 env_vars: self.env_vars,
388 function_name: self.function_name,
389 sqs_event_source_mapping: self.sqs_event_source_mapping,
390 reserved_concurrent_executions: self.reserved_concurrent_executions,
391 log_group: self.log_group,
392 }
393 }
394}
395
396impl FunctionBuilder<ZipStateWithHandler> {
397 pub fn runtime(self, runtime: Runtime) -> FunctionBuilder<ZipStateWithHandlerAndRuntime> {
398 FunctionBuilder {
399 id: self.id,
400 runtime: Some(runtime),
401 state: Default::default(),
402 architecture: self.architecture,
403 memory: self.memory,
404 timeout: self.timeout,
405 code: self.code,
406 handler: self.handler,
407 additional_policies: self.additional_policies,
408 aws_services_in_dependencies: self.aws_services_in_dependencies,
409 env_vars: self.env_vars,
410 function_name: self.function_name,
411 sqs_event_source_mapping: self.sqs_event_source_mapping,
412 reserved_concurrent_executions: self.reserved_concurrent_executions,
413 log_group: self.log_group,
414 }
415 }
416}
417
418impl FunctionBuilder<ZipStateWithHandlerAndRuntime> {
419 pub fn sqs_event_source_mapping(
423 mut self,
424 sqs_queue: &QueueRef,
425 max_concurrency: Option<SqsEventSourceMaxConcurrency>,
426 ) -> FunctionBuilder<EventSourceMappingState> {
427 self.additional_policies.push(IamPermission::SqsRead(sqs_queue).into_policy());
428
429 let mapping = EventSourceMappingInfo {
430 id: sqs_queue.get_resource_id().to_string(),
431 max_concurrency: max_concurrency.map(|c| c.0),
432 };
433
434 FunctionBuilder {
435 id: self.id,
436 sqs_event_source_mapping: Some(mapping),
437 state: Default::default(),
438 runtime: self.runtime,
439 architecture: self.architecture,
440 memory: self.memory,
441 timeout: self.timeout,
442 code: self.code,
443 handler: self.handler,
444 additional_policies: self.additional_policies,
445 aws_services_in_dependencies: self.aws_services_in_dependencies,
446 env_vars: self.env_vars,
447 function_name: self.function_name,
448 reserved_concurrent_executions: self.reserved_concurrent_executions,
449 log_group: self.log_group,
450 }
451 }
452
453 pub fn build(self, stack_builder: &mut StackBuilder) -> (FunctionRef, RoleRef, LogGroupRef) {
458 self.build_internal(stack_builder)
459 }
460}
461
462impl FunctionBuilder<EventSourceMappingState> {
463 pub fn build(self, stack_builder: &mut StackBuilder) -> (FunctionRef, RoleRef, LogGroupRef) {
468 self.build_internal(stack_builder)
469 }
470}
471
472pub struct PermissionBuilder {
476 id: Id,
477 action: String,
478 function_name: Value,
479 principal: String,
480 source_arn: Option<Value>,
481 source_account: Option<Value>,
482}
483
484impl PermissionBuilder {
485 pub fn new<R: Into<String>>(id: &str, action: LambdaPermissionAction, function_name: Value, principal: R) -> Self {
493 Self {
494 id: Id(id.to_string()),
495 action: action.0,
496 function_name,
497 principal: principal.into(),
498 source_arn: None,
499 source_account: None,
500 }
501 }
502
503 pub fn source_arn(self, arn: Value) -> Self {
504 Self {
505 source_arn: Some(arn),
506 ..self
507 }
508 }
509
510 pub fn current_account(self) -> Self {
511 Self {
512 source_account: Some(get_ref("AWS::AccountId")),
513 ..self
514 }
515 }
516
517 pub fn build(self, stack_builder: &mut StackBuilder) -> PermissionRef {
518 let permission_resource_id = Resource::generate_id("LambdaPermission");
519
520 stack_builder.add_resource(Permission {
521 id: self.id.clone(),
522 resource_id: permission_resource_id.clone(),
523 r#type: PermissionType::PermissionType,
524 properties: LambdaPermissionProperties {
525 action: self.action,
526 function_name: self.function_name,
527 principal: self.principal,
528 source_arn: self.source_arn,
529 source_account: self.source_account,
530 },
531 });
532
533 PermissionRef::internal_new(self.id, permission_resource_id)
534 }
535}