use http::StatusCode;
use serde_json::json;
use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError};
use super::{extract_log_group_from_arn, require_str, LogsService};
use crate::state::SyslogConfiguration;
use chrono::Utc;
fn identifier_to_group_name(identifier: &str) -> Result<String, AwsServiceError> {
if identifier.starts_with("arn:") {
extract_log_group_from_arn(identifier).ok_or_else(|| {
AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterException",
format!("Invalid ARN: {identifier}"),
)
})
} else {
Ok(identifier.to_string())
}
}
fn validate_vpc_endpoint_id(value: &str) -> Result<(), AwsServiceError> {
let rest = value.strip_prefix("vpce-").filter(|r| {
!r.is_empty()
&& r.len() <= 64
&& r.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
});
if rest.is_some() {
Ok(())
} else {
Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"InvalidParameterException",
format!("vpcEndpointId does not match required pattern: {value}"),
))
}
}
impl LogsService {
pub(crate) fn put_syslog_configuration(
&self,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let body = req.json_body();
let identifier = require_str(&body, "logGroupIdentifier")?.to_string();
let group_name = identifier_to_group_name(&identifier)?;
let vpc_endpoint_id = body["vpcEndpointId"].as_str().map(|s| s.to_string());
if let Some(id) = &vpc_endpoint_id {
validate_vpc_endpoint_id(id)?;
}
let mut accounts = self.state.write();
let state = accounts.get_or_create(&req.account_id);
let group = state.log_groups.get(&group_name).ok_or_else(|| {
AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"ResourceNotFoundException",
format!("The specified log group does not exist: {group_name}"),
)
})?;
let log_group_arn = group.arn.clone();
let created_at = state
.syslog_configurations
.get(&group_name)
.map(|c| c.created_at)
.unwrap_or_else(|| Utc::now().timestamp_millis());
state.syslog_configurations.insert(
group_name,
SyslogConfiguration {
log_group_arn,
source_type: "VPCE".to_string(),
vpc_endpoint_id,
created_at,
},
);
Ok(AwsResponse::json(StatusCode::OK, "{}"))
}
pub(crate) fn list_syslog_configurations(
&self,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let body = req.json_body();
let group_filter = body["logGroupIdentifier"]
.as_str()
.map(identifier_to_group_name)
.transpose()?;
let vpc_filter = body["vpcEndpointId"].as_str();
let accounts = self.state.read();
let empty = crate::state::LogsState::new(&req.account_id, &req.region);
let state = accounts.get(&req.account_id).unwrap_or(&empty);
let configs: Vec<_> = state
.syslog_configurations
.iter()
.filter(|(group_name, _)| {
group_filter
.as_ref()
.is_none_or(|g| g == group_name.as_str())
})
.filter(|(_, c)| vpc_filter.is_none_or(|v| c.vpc_endpoint_id.as_deref() == Some(v)))
.map(|(_, c)| {
json!({
"logGroupArn": c.log_group_arn,
"sourceType": c.source_type,
"vpcEndpointId": c.vpc_endpoint_id,
"createdAt": c.created_at,
})
})
.collect();
Ok(AwsResponse::json(
StatusCode::OK,
serde_json::to_string(&json!({ "syslogConfigurations": configs })).unwrap(),
))
}
pub(crate) fn delete_syslog_configuration(
&self,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let body = req.json_body();
let identifier = require_str(&body, "logGroupIdentifier")?.to_string();
let group_name = identifier_to_group_name(&identifier)?;
let mut accounts = self.state.write();
let state = accounts.get_or_create(&req.account_id);
if state.syslog_configurations.remove(&group_name).is_none() {
return Err(AwsServiceError::aws_error(
StatusCode::BAD_REQUEST,
"ResourceNotFoundException",
format!("No syslog configuration found for log group: {group_name}"),
));
}
Ok(AwsResponse::json(StatusCode::OK, "{}"))
}
}