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