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