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> {
131 state: PhantomData<T>,
132 id: Id,
133 architecture: Architecture,
134 memory: u16,
135 timeout: u16,
136 code: Option<Code>,
137 handler: Option<String>,
138 runtime: Option<Runtime>,
139 additional_policies: Vec<Policy>,
140 aws_services_in_dependencies: Vec<String>,
141 env_vars: Vec<(String, Value)>,
142 function_name: Option<String>,
143 sqs_event_source_mapping: Option<EventSourceMappingInfo>,
144 reserved_concurrent_executions: Option<u32>,
145 log_group: Option<LogGroupRef>,
146}
147
148impl<T: FunctionBuilderState> FunctionBuilder<T> {
149 pub fn function_name(self, name: StringWithOnlyAlphaNumericsUnderscoresAndHyphens) -> FunctionBuilder<T> {
157 Self {
158 function_name: Some(name.0),
159 ..self
160 }
161 }
162
163 pub fn add_permission(mut self, permission: IamPermission) -> FunctionBuilder<T> {
169 self.additional_policies.push(permission.into_policy());
170 Self { ..self }
171 }
172
173 pub fn check_permissions_against_dependencies(self, cargo_toml: TomlFile) -> Self {
178 let services = map_toml_dependencies_to_services(cargo_toml.0.as_ref());
179
180 Self {
181 aws_services_in_dependencies: services,
182 ..self
183 }
184 }
185
186 pub fn env_var(mut self, key: EnvVarKey, value: Value) -> FunctionBuilder<T> {
194 self.env_vars.push((key.0, value));
195 Self { ..self }
196 }
197
198 pub fn env_var_string<V: Into<String>>(mut self, key: EnvVarKey, value: V) -> FunctionBuilder<T> {
205 self.env_vars.push((key.0, Value::String(value.into())));
206 Self { ..self }
207 }
208
209 pub fn reserved_concurrent_executions(self, executions: u32) -> FunctionBuilder<T> {
215 Self {
216 reserved_concurrent_executions: Some(executions),
217 ..self
218 }
219 }
220
221 pub fn log_group(self, log_group: &LogGroupRef) -> Self {
224 Self {
225 log_group: Some(log_group.clone()),
226 ..self
227 }
228 }
229
230 fn build_internal(self, stack_builder: &mut StackBuilder) -> (FunctionRef, RoleRef, LogGroupRef) {
231 let function_resource_id = Resource::generate_id("LambdaFunction");
232
233 let code = match self.code.expect("code to be present, enforced by builder") {
234 Code::Zip(z) => {
235 let asset_id = Resource::generate_id("Asset");
236 let asset_id = format!("{asset_id}.zip");
237
238 let asset = Asset {
239 s3_bucket: z.bucket.clone(),
240 s3_key: asset_id.clone(),
241 path: z.file.0.to_string(),
242 };
243
244 let code = LambdaCode {
245 s3_bucket: Some(z.bucket),
246 s3_key: Some(asset_id),
247 zipfile: None,
248 };
249
250 (Some(asset), code)
251 }
252 Code::Inline(inline_code) => {
253 let code = LambdaCode {
254 s3_bucket: None,
255 s3_key: None,
256 zipfile: Some(inline_code),
257 };
258 (None, code)
259 }
260 };
261
262 if let Some(mapping) = self.sqs_event_source_mapping {
263 let event_id = Id::generate_id(&self.id, "ESM");
264 let event_resource_id = format!("EventSourceMapping{}", function_resource_id);
265 let event_source_mapping = EventSourceMapping {
266 id: event_id,
267 resource_id: event_resource_id.clone(),
268 r#type: EventSourceMappingType::EventSourceMappingType,
269 properties: EventSourceProperties {
270 event_source_arn: Some(get_arn(&mapping.id)),
271 function_name: Some(get_ref(&function_resource_id)),
272 scaling_config: mapping.max_concurrency.map(|c| ScalingConfig { max_concurrency: c }),
273 },
274 };
275 stack_builder.add_resource(event_source_mapping);
276 };
277
278 let assume_role_statement = StatementBuilder::internal_new(vec!["sts:AssumeRole".to_string()], Effect::Allow)
279 .principal(PrincipalBuilder::new().service("lambda.amazonaws.com").build())
280 .build();
281 let assumed_role_policy_document = AssumeRolePolicyDocumentBuilder::new(vec![assume_role_statement]).build();
282 let managed_policy_arns = vec![join(
283 "",
284 vec![
285 Value::String("arn:".to_string()),
286 get_ref(AWS_PARTITION_PSEUDO_PARAM),
287 Value::String(":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole".to_string()),
288 ],
289 )];
290 let potentially_missing = find_missing_services(&self.aws_services_in_dependencies, &self.additional_policies);
291 let props = RolePropertiesBuilder::new(assumed_role_policy_document, managed_policy_arns)
292 .policies(self.additional_policies)
293 .build();
294
295 let role_id = Id::generate_id(&self.id, "Role");
296 let role_resource_id = Resource::generate_id("LambdaFunctionRole");
297 let role_ref = get_arn(&role_resource_id);
298 let role = RoleBuilder::new_with_info_on_missing(&role_id, &role_resource_id, props, potentially_missing).build(stack_builder);
299
300 let environment = if self.env_vars.is_empty() {
301 None
302 } else {
303 Some(Environment {
304 variables: self.env_vars.into_iter().collect(),
305 })
306 };
307
308 let log_group = if let Some(log_group) = self.log_group {
309 log_group
310 } else {
311 let log_group_id = Id::generate_id(&self.id, "LogGroup");
312 let log_group_name = self.function_name.clone().map(|fun_name| format!("/aws/lambda/{fun_name}"));
313 let base_builder = LogGroupBuilder::new(&log_group_id).log_group_retention(RetentionInDays(731));
314 let log_group = if let Some(name) = log_group_name {
315 base_builder.log_group_name_string(LogGroupName(name)).build(stack_builder)
316 } else {
317 base_builder.build(stack_builder)
318 };
319 log_group
320 };
321
322 let logging_info = LoggingInfo {
323 log_group: Some(log_group.get_ref()),
324 };
325
326 let properties = LambdaFunctionProperties {
327 code: code.1,
328 architectures: vec![self.architecture.into()],
329 memory_size: self.memory,
330 timeout: self.timeout,
331 handler: self.handler,
332 runtime: self.runtime.map(Into::into),
333 role: role_ref,
334 function_name: self.function_name,
335 environment,
336 reserved_concurrent_executions: self.reserved_concurrent_executions,
337 logging_info,
338 };
339
340 stack_builder.add_resource(Function {
341 id: self.id.clone(),
342 resource_id: function_resource_id.clone(),
343 asset: code.0,
344 r#type: FunctionType::FunctionType,
345 properties,
346 });
347
348 let function = FunctionRef::internal_new(self.id, function_resource_id);
349
350 (function, role, log_group)
351 }
352}
353
354impl FunctionBuilder<StartState> {
356 pub fn new(id: &str, architecture: Architecture, memory: Memory, timeout: Timeout) -> FunctionBuilder<StartState> {
365 FunctionBuilder {
366 state: Default::default(),
367 id: Id(id.to_string()),
368 architecture,
369 memory: memory.0,
370 timeout: timeout.0,
371 code: None,
372 handler: None,
373 runtime: None,
374 additional_policies: vec![],
375 aws_services_in_dependencies: vec![],
376 env_vars: vec![],
377 function_name: None,
378 sqs_event_source_mapping: None,
379 reserved_concurrent_executions: None,
380 log_group: None,
381 }
382 }
383
384 pub fn code(self, code: Code) -> FunctionBuilder<CodeState> {
392 FunctionBuilder {
393 code: Some(code),
394 state: Default::default(),
395 id: self.id,
396 architecture: self.architecture,
397 memory: self.memory,
398 timeout: self.timeout,
399 handler: self.handler,
400 runtime: self.runtime,
401 additional_policies: self.additional_policies,
402 aws_services_in_dependencies: self.aws_services_in_dependencies,
403 env_vars: self.env_vars,
404 function_name: self.function_name,
405 sqs_event_source_mapping: self.sqs_event_source_mapping,
406 reserved_concurrent_executions: self.reserved_concurrent_executions,
407 log_group: self.log_group,
408 }
409 }
410}
411
412impl FunctionBuilder<CodeState> {
413 pub fn handler<T: Into<String>>(self, handler: T) -> FunctionBuilder<ZipStateWithHandler> {
421 FunctionBuilder {
422 id: self.id,
423 handler: Some(handler.into()),
424 state: Default::default(),
425 architecture: self.architecture,
426 memory: self.memory,
427 timeout: self.timeout,
428 code: self.code,
429 runtime: self.runtime,
430 additional_policies: self.additional_policies,
431 aws_services_in_dependencies: self.aws_services_in_dependencies,
432 env_vars: self.env_vars,
433 function_name: self.function_name,
434 sqs_event_source_mapping: self.sqs_event_source_mapping,
435 reserved_concurrent_executions: self.reserved_concurrent_executions,
436 log_group: self.log_group,
437 }
438 }
439}
440
441impl FunctionBuilder<ZipStateWithHandler> {
442 pub fn runtime(self, runtime: Runtime) -> FunctionBuilder<ZipStateWithHandlerAndRuntime> {
448 FunctionBuilder {
449 id: self.id,
450 runtime: Some(runtime),
451 state: Default::default(),
452 architecture: self.architecture,
453 memory: self.memory,
454 timeout: self.timeout,
455 code: self.code,
456 handler: self.handler,
457 additional_policies: self.additional_policies,
458 aws_services_in_dependencies: self.aws_services_in_dependencies,
459 env_vars: self.env_vars,
460 function_name: self.function_name,
461 sqs_event_source_mapping: self.sqs_event_source_mapping,
462 reserved_concurrent_executions: self.reserved_concurrent_executions,
463 log_group: self.log_group,
464 }
465 }
466}
467
468impl FunctionBuilder<ZipStateWithHandlerAndRuntime> {
469 pub fn sqs_event_source_mapping(
473 mut self,
474 sqs_queue: &QueueRef,
475 max_concurrency: Option<SqsEventSourceMaxConcurrency>,
476 ) -> FunctionBuilder<EventSourceMappingState> {
477 self.additional_policies.push(IamPermission::SqsRead(sqs_queue).into_policy());
478
479 let mapping = EventSourceMappingInfo {
480 id: sqs_queue.get_resource_id().to_string(),
481 max_concurrency: max_concurrency.map(|c| c.0),
482 };
483
484 FunctionBuilder {
485 id: self.id,
486 sqs_event_source_mapping: Some(mapping),
487 state: Default::default(),
488 runtime: self.runtime,
489 architecture: self.architecture,
490 memory: self.memory,
491 timeout: self.timeout,
492 code: self.code,
493 handler: self.handler,
494 additional_policies: self.additional_policies,
495 aws_services_in_dependencies: self.aws_services_in_dependencies,
496 env_vars: self.env_vars,
497 function_name: self.function_name,
498 reserved_concurrent_executions: self.reserved_concurrent_executions,
499 log_group: self.log_group,
500 }
501 }
502
503 pub fn build(self, stack_builder: &mut StackBuilder) -> (FunctionRef, RoleRef, LogGroupRef) {
508 self.build_internal(stack_builder)
509 }
510}
511
512impl FunctionBuilder<EventSourceMappingState> {
513 pub fn build(self, stack_builder: &mut StackBuilder) -> (FunctionRef, RoleRef, LogGroupRef) {
518 self.build_internal(stack_builder)
519 }
520}
521
522pub struct PermissionBuilder {
527 id: Id,
528 action: String,
529 function_name: Value,
530 principal: String,
531 source_arn: Option<Value>,
532 source_account: Option<Value>,
533}
534
535impl PermissionBuilder {
536 pub fn new<R: Into<String>>(id: &str, action: LambdaPermissionAction, function_name: Value, principal: R) -> Self {
544 Self {
545 id: Id(id.to_string()),
546 action: action.0,
547 function_name,
548 principal: principal.into(),
549 source_arn: None,
550 source_account: None,
551 }
552 }
553
554 pub fn source_arn(self, arn: Value) -> Self {
555 Self {
556 source_arn: Some(arn),
557 ..self
558 }
559 }
560
561 pub fn current_account(self) -> Self {
562 Self {
563 source_account: Some(get_ref("AWS::AccountId")),
564 ..self
565 }
566 }
567
568 pub fn build(self, stack_builder: &mut StackBuilder) -> PermissionRef {
569 let permission_resource_id = Resource::generate_id("LambdaPermission");
570
571 stack_builder.add_resource(Permission {
572 id: self.id.clone(),
573 resource_id: permission_resource_id.clone(),
574 r#type: PermissionType::PermissionType,
575 properties: LambdaPermissionProperties {
576 action: self.action,
577 function_name: self.function_name,
578 principal: self.principal,
579 source_arn: self.source_arn,
580 source_account: self.source_account,
581 },
582 });
583
584 PermissionRef::internal_new(self.id, permission_resource_id)
585 }
586}