Skip to content

Commit 22901c9

Browse files
authored
fix(ecs): reject phantom clusters, over-limit count, negative desiredCount, phantom task-set service (#2253)
2 parents a27eebf + 6c3a430 commit 22901c9

3 files changed

Lines changed: 84 additions & 12 deletions

File tree

crates/fakecloud-ecs/src/service_container_instances_etc.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,21 @@ impl EcsService {
760760
let accounts = self.state.read();
761761
let mut found = Vec::new();
762762
let mut failures = Vec::new();
763+
{
764+
// AWS validates the referenced cluster + service exist, returning
765+
// ClusterNotFoundException / ServiceNotFoundException rather than an
766+
// empty task-set list.
767+
let state = accounts
768+
.get(&request.account_id)
769+
.ok_or_else(|| service_not_found(&service_name))?;
770+
if !state.clusters.contains_key(&cluster_name) {
771+
return Err(cluster_not_found(&cluster_name));
772+
}
773+
let svc_key = EcsState::service_key(&cluster_name, &service_name);
774+
if !state.services.contains_key(&svc_key) {
775+
return Err(service_not_found(&service_name));
776+
}
777+
}
763778
if let Some(state) = accounts.get(&request.account_id) {
764779
if filter_refs.is_empty() {
765780
for ts in state.task_sets.values() {

crates/fakecloud-ecs/src/service_services_resource.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ impl EcsService {
2020
let td_ref = req_str(&body, "taskDefinition")?;
2121
let cluster_ref = opt_str(&body, "cluster");
2222
let cluster_name = EcsState::resolve_cluster_name(cluster_ref);
23-
let desired_count = body
24-
.get("desiredCount")
25-
.and_then(|v| v.as_i64())
26-
.filter(|n| *n >= 0)
27-
.unwrap_or(1) as i32;
23+
// AWS rejects a negative desiredCount with InvalidParameterException
24+
// rather than silently defaulting it to 1.
25+
let desired_count = match body.get("desiredCount").and_then(|v| v.as_i64()) {
26+
Some(n) if n >= 0 => n as i32,
27+
Some(n) => {
28+
return Err(invalid_parameter(format!(
29+
"desiredCount cannot be negative. desiredCount={n}"
30+
)))
31+
}
32+
None => 1,
33+
};
2834
let launch_type = opt_str(&body, "launchType")
2935
.unwrap_or("FARGATE")
3036
.to_string();
@@ -133,11 +139,13 @@ impl EcsService {
133139
let td_arn = td.task_definition_arn.clone();
134140
let td_family = td.family.clone();
135141
let td_revision = td.revision;
142+
// A phantom cluster is a ClusterNotFoundException on AWS, not an
143+
// auto-created cluster.
136144
let cluster_arn = state
137145
.clusters
138146
.get(&cluster_name)
139147
.map(|c| c.cluster_arn.clone())
140-
.unwrap_or_else(|| state.cluster_arn(&cluster_name));
148+
.ok_or_else(|| cluster_not_found(&cluster_name))?;
141149
let service_arn = state.service_arn(&cluster_name, &service_name);
142150
let key = EcsState::service_key(&cluster_name, &service_name);
143151
if let Some(existing) = state.services.get(&key) {

crates/fakecloud-ecs/src/service_tasks.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,17 @@ impl EcsService {
7676
.and_then(|v| v.as_array())
7777
.cloned()
7878
.unwrap_or_default();
79-
let count = body
80-
.get("count")
81-
.and_then(|v| v.as_i64())
82-
.filter(|n| (1..=10).contains(n))
83-
.unwrap_or(1) as usize;
79+
// AWS caps RunTask at 1..=10 tasks per call and rejects anything else
80+
// with InvalidParameterException — it does NOT silently clamp to 1.
81+
let count = match body.get("count").and_then(|v| v.as_i64()) {
82+
Some(n) if (1..=10).contains(&n) => n as usize,
83+
Some(n) => {
84+
return Err(invalid_parameter(format!(
85+
"count should be between 1 and 10, inclusive. count={n}"
86+
)))
87+
}
88+
None => 1,
89+
};
8490
let group = opt_str(&body, "group").map(String::from);
8591
let started_by = opt_str(&body, "startedBy").map(String::from);
8692
let enable_execute_command = body
@@ -124,11 +130,13 @@ impl EcsService {
124130
let runtime = self.runtime.clone();
125131
let mut accounts = self.state.write();
126132
let state = accounts.get_or_create(&account);
133+
// AWS rejects RunTask against a cluster that does not exist rather than
134+
// synthesizing one; a phantom cluster returns ClusterNotFoundException.
127135
let cluster_arn = state
128136
.clusters
129137
.get(&cluster_name)
130138
.map(|c| c.cluster_arn.clone())
131-
.unwrap_or_else(|| state.cluster_arn(&cluster_name));
139+
.ok_or_else(|| cluster_not_found(&cluster_name))?;
132140
let (_, family, rev) = resolve_task_definition_ref(td_ref)?;
133141
let revisions = state
134142
.task_definitions
@@ -579,6 +587,47 @@ mod multi_container_tests {
579587
}
580588
}
581589

590+
#[test]
591+
fn run_task_count_over_ten_is_invalid_parameter() {
592+
let svc = fresh_service();
593+
// count is validated before the cluster/task-def are dereferenced.
594+
let err = match svc.run_task(&make_request(
595+
"RunTask",
596+
json!({"taskDefinition": "x", "count": 50}),
597+
)) {
598+
Ok(_) => panic!("expected InvalidParameterException for count > 10"),
599+
Err(e) => e,
600+
};
601+
assert_eq!(err.code(), "InvalidParameterException");
602+
}
603+
604+
#[test]
605+
fn create_service_negative_desired_count_is_invalid_parameter() {
606+
let svc = fresh_service();
607+
// desiredCount is validated before the cluster/task-def are resolved.
608+
let err = match svc.create_service(&make_request(
609+
"CreateService",
610+
json!({"serviceName": "s", "taskDefinition": "x", "desiredCount": -3}),
611+
)) {
612+
Ok(_) => panic!("expected InvalidParameterException for negative desiredCount"),
613+
Err(e) => e,
614+
};
615+
assert_eq!(err.code(), "InvalidParameterException");
616+
}
617+
618+
#[test]
619+
fn run_task_on_missing_cluster_is_cluster_not_found() {
620+
let svc = fresh_service();
621+
let err = match svc.run_task(&make_request(
622+
"RunTask",
623+
json!({"taskDefinition": "x", "cluster": "ghost", "count": 1}),
624+
)) {
625+
Ok(_) => panic!("expected ClusterNotFoundException for a phantom cluster"),
626+
Err(e) => e,
627+
};
628+
assert_eq!(err.code(), "ClusterNotFoundException");
629+
}
630+
582631
#[test]
583632
fn register_task_def_with_two_containers_then_run_task_starts_both() {
584633
let svc = fresh_service();

0 commit comments

Comments
 (0)