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