use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use awsim_core::{AwsError, RequestContext};
use serde_json::{Value, json};
use tracing::info;
use crate::state::{Cluster, EcsState};
pub fn now_epoch_str() -> String {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
.to_string()
}
fn cluster_to_json(cluster: &Cluster) -> Value {
json!({
"clusterArn": cluster.arn,
"clusterName": cluster.name,
"status": cluster.status,
"registeredContainerInstancesCount": 0,
"runningTasksCount": cluster.tasks.values().filter(|t| t.status == "RUNNING").count(),
"pendingTasksCount": 0,
"activeServicesCount": cluster.services.len(),
"statistics": [],
"tags": [],
"capacityProviders": cluster.capacity_providers,
"defaultCapacityProviderStrategy": cluster.default_capacity_provider_strategy,
})
}
pub fn resolve_cluster_name(input_cluster: &str) -> &str {
if input_cluster.starts_with("arn:") {
input_cluster
.split('/')
.next_back()
.unwrap_or(input_cluster)
} else {
input_cluster
}
}
pub fn create_cluster(
state: &EcsState,
input: &Value,
ctx: &RequestContext,
) -> Result<Value, AwsError> {
let name = input["clusterName"]
.as_str()
.unwrap_or("default")
.to_string();
let arn = format!(
"arn:aws:ecs:{}:{}:cluster/{}",
ctx.region, ctx.account_id, name
);
if state.clusters.contains_key(&name) {
let cluster = state.clusters.get(&name).unwrap();
return Ok(json!({ "cluster": cluster_to_json(&cluster) }));
}
let cluster = Cluster {
name: name.clone(),
arn,
status: "ACTIVE".to_string(),
services: HashMap::new(),
tasks: HashMap::new(),
created_at: now_epoch_str(),
capacity_providers: Vec::new(),
default_capacity_provider_strategy: Vec::new(),
};
info!(cluster = %name, "Created ECS cluster");
let json = cluster_to_json(&cluster);
state.clusters.insert(name, cluster);
Ok(json!({ "cluster": json }))
}
pub fn delete_cluster(
state: &EcsState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let cluster_id = input["cluster"]
.as_str()
.ok_or_else(|| AwsError::bad_request("InvalidParameterException", "cluster is required"))?;
let name = resolve_cluster_name(cluster_id);
let cluster = state.clusters.get(name).ok_or_else(|| {
AwsError::bad_request(
"ClusterNotFoundException",
format!("The specified cluster '{name}' does not exist"),
)
})?;
let json = cluster_to_json(&cluster);
drop(cluster);
state.clusters.remove(name);
info!(cluster = %name, "Deleted ECS cluster");
Ok(json!({ "cluster": json }))
}
pub fn describe_clusters(
state: &EcsState,
input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let clusters_input = input["clusters"].as_array();
let (clusters, failures): (Vec<Value>, Vec<Value>) = if let Some(ids) = clusters_input {
let mut found = Vec::new();
let mut missing = Vec::new();
for id_val in ids {
let id = id_val.as_str().unwrap_or("");
let name = resolve_cluster_name(id);
match state.clusters.get(name) {
Some(c) => found.push(cluster_to_json(&c)),
None => missing.push(json!({
"arn": id,
"reason": "MISSING",
"detail": format!("Cluster '{name}' not found"),
})),
}
}
(found, missing)
} else {
let all: Vec<Value> = state
.clusters
.iter()
.map(|e| cluster_to_json(e.value()))
.collect();
(all, vec![])
};
Ok(json!({ "clusters": clusters, "failures": failures }))
}
pub fn list_clusters(
state: &EcsState,
_input: &Value,
_ctx: &RequestContext,
) -> Result<Value, AwsError> {
let arns: Vec<Value> = state
.clusters
.iter()
.map(|e| json!(e.value().arn))
.collect();
Ok(json!({ "clusterArns": arns }))
}