use super::*;
impl LambdaService {
pub(crate) fn create_function(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
let input = CreateFunctionInput::from_body(&body)?;
let raw = input.function_name.as_str();
if raw.is_empty() || raw.chars().count() > 140 {
return Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterValueException",
format!(
"1 validation error detected: Value '{}' at 'functionName' failed to \
satisfy constraint: Member must have length less than or equal to 140",
raw
),
));
}
if let Some(ref validator) = self.role_trust_validator {
if let Err(err) =
validator.validate(&req.account_id, &input.role, "lambda.amazonaws.com")
{
return Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterValueException",
err.to_string(),
));
}
}
let mut accounts = self.state.write();
let layer_attachments =
crate::extras::resolve_layer_attachments(&accounts, input.layer_arns.clone());
let state = accounts.get_or_create(&req.account_id);
if state.functions.contains_key(&input.function_name) {
return Err(AwsServiceError::aws_error(
StatusCode::CONFLICT,
"ResourceConflictException",
format!("Function already exist: {}", input.function_name),
));
}
let code_bytes = input.code_zip.as_deref().unwrap_or(&input.code_fallback);
let mut hasher = Sha256::new();
hasher.update(code_bytes);
let hash = hasher.finalize();
let code_sha256 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, hash);
let code_size = code_bytes.len() as i64;
let function_arn = format!(
"arn:aws:lambda:{}:{}:function:{}",
state.region, state.account_id, input.function_name
);
let now = Utc::now();
let func = LambdaFunction {
function_name: input.function_name.clone(),
function_arn,
runtime: input.runtime,
role: input.role,
handler: input.handler,
description: input.description,
timeout: input.timeout,
memory_size: input.memory_size,
code_sha256,
code_size,
version: "$LATEST".to_string(),
last_modified: now,
tags: input.tags,
environment: input.environment,
architectures: input.architectures,
package_type: input.package_type,
code_zip: input.code_zip,
image_uri: input.image_uri,
policy: None,
layers: layer_attachments,
revision_id: uuid::Uuid::new_v4().to_string(),
tracing_mode: input.tracing_mode,
kms_key_arn: input.kms_key_arn,
ephemeral_storage_size: input.ephemeral_storage_size,
vpc_config: input.vpc_config,
snap_start: input.snap_start,
dead_letter_config_arn: input.dead_letter_config_arn,
file_system_configs: input.file_system_configs,
logging_config: input.logging_config,
image_config: input.image_config,
durable_config: input.durable_config,
signing_profile_version_arn: None,
signing_job_arn: None,
runtime_version_config: None,
master_arn: None,
state_reason: None,
state_reason_code: None,
last_update_status_reason: None,
last_update_status_reason_code: None,
};
let response = self.function_config_json(&func);
state.functions.insert(input.function_name, func);
Ok(AwsResponse::json(StatusCode::CREATED, response.to_string()))
}
pub(crate) fn get_function(
&self,
req: &AwsRequest,
function_name: &str,
account_id: &str,
region: &str,
qualifier: Option<&str>,
) -> Result<AwsResponse, AwsServiceError> {
if function_name.is_empty() {
return Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterValueException",
"FunctionName is required",
));
}
let accounts = self.state.read();
let empty = LambdaState::new(account_id, region);
let state = accounts.get(account_id).unwrap_or(&empty);
let live = state.functions.get(function_name).ok_or_else(|| {
AwsServiceError::aws_error(
StatusCode::NOT_FOUND,
"ResourceNotFoundException",
format!(
"Function not found: arn:aws:lambda:{}:{}:function:{}",
state.region, state.account_id, function_name
),
)
})?;
let resolved_version = resolve_qualifier_to_version(state, function_name, qualifier);
let (func, version_label) = match resolved_version {
None => (live, "$LATEST".to_string()),
Some(v) => {
let snap = state
.function_version_snapshots
.get(function_name)
.and_then(|m| m.get(&v))
.ok_or_else(|| {
AwsServiceError::aws_error(
StatusCode::NOT_FOUND,
"ResourceNotFoundException",
format!(
"Function not found: arn:aws:lambda:{}:{}:function:{}:{v}",
state.region, state.account_id, function_name
),
)
})?;
(snap, v)
}
};
let mut config = self.function_config_json(func);
config["Version"] = json!(version_label);
if version_label != "$LATEST" {
config["FunctionArn"] = json!(format!("{}:{version_label}", live.function_arn));
config["MasterArn"] = json!(live.function_arn);
}
let code = if let Some(ref uri) = func.image_uri {
json!({
"ImageUri": uri,
"ResolvedImageUri": uri,
"RepositoryType": "ECR",
})
} else {
json!({
"Location": crate::extras::function_code_url(
req,
&state.account_id,
function_name,
&version_label,
),
"RepositoryType": "S3",
})
};
let response = json!({
"Code": code,
"Configuration": config,
"Tags": live.tags,
});
Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
}
pub(crate) fn delete_function(
&self,
function_name: &str,
account_id: &str,
qualifier: Option<&str>,
) -> Result<AwsResponse, AwsServiceError> {
let mut accounts = self.state.write();
let state = accounts.get_or_create(account_id);
let region = state.region.clone();
let account_id_owned = state.account_id.clone();
if let Some(q) = qualifier {
if q == "$LATEST" {
return Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterValueException",
"$LATEST version cannot be deleted without deleting the function.",
));
}
if !q.chars().all(|c| c.is_ascii_digit()) {
return Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterValueException",
format!(
"Value '{q}' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)"
),
));
}
if !state.functions.contains_key(function_name) {
return Err(AwsServiceError::aws_error(
StatusCode::NOT_FOUND,
"ResourceNotFoundException",
format!(
"Function not found: arn:aws:lambda:{region}:{account_id_owned}:function:{function_name}:{q}"
),
));
}
let alias_targets: Vec<String> = state
.aliases
.iter()
.filter_map(|(k, a)| {
let prefix = format!("{function_name}:");
if k.starts_with(&prefix) && a.function_version == *q {
Some(a.name.clone())
} else {
None
}
})
.collect();
if !alias_targets.is_empty() {
return Err(AwsServiceError::aws_error(
StatusCode::CONFLICT,
"ResourceConflictException",
format!(
"Cannot delete version {q} of function {function_name}: alias(es) reference it ({})",
alias_targets.join(", ")
),
));
}
let snap_existed = state
.function_version_snapshots
.get_mut(function_name)
.map(|m| m.remove(q).is_some())
.unwrap_or(false);
if !snap_existed {
return Err(AwsServiceError::aws_error(
StatusCode::NOT_FOUND,
"ResourceNotFoundException",
format!(
"Function not found: arn:aws:lambda:{region}:{account_id_owned}:function:{function_name}:{q}"
),
));
}
if let Some(list) = state.function_versions.get_mut(function_name) {
list.retain(|v| v != q);
}
return Ok(AwsResponse::json(StatusCode::NO_CONTENT, ""));
}
if state.functions.remove(function_name).is_none() {
return Err(AwsServiceError::aws_error(
StatusCode::NOT_FOUND,
"ResourceNotFoundException",
format!(
"Function not found: arn:aws:lambda:{region}:{account_id_owned}:function:{function_name}"
),
));
}
state.function_versions.remove(function_name);
state.function_version_snapshots.remove(function_name);
let prefix = format!("{function_name}:");
state.aliases.retain(|k, _| !k.starts_with(&prefix));
if let Some(ref runtime) = self.runtime {
let rt = runtime.clone();
let name = function_name.to_string();
tokio::spawn(async move { rt.stop_container(&name).await });
}
Ok(AwsResponse::json(StatusCode::NO_CONTENT, ""))
}
pub(crate) fn list_functions(
&self,
account_id: &str,
function_version: Option<&str>,
) -> Result<AwsResponse, AwsServiceError> {
if let Some(fv) = function_version {
if fv != "ALL" {
return Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterValueException",
format!("Invalid FunctionVersion value '{}'; expected 'ALL'", fv),
));
}
}
let accounts = self.state.read();
let empty = LambdaState::new(account_id, "");
let state = accounts.get(account_id).unwrap_or(&empty);
let functions: Vec<Value> = state
.functions
.values()
.map(|f| self.function_config_json(f))
.collect();
let response = json!({
"Functions": functions,
"NextMarker": "",
});
Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
}
}