Skip to content

fix(snapshot ecs): handle clusters with zero services#966

Merged
dangrondahl merged 1 commit into
mainfrom
fix-ecs-empty-services-965
Jun 22, 2026
Merged

fix(snapshot ecs): handle clusters with zero services#966
dangrondahl merged 1 commit into
mainfrom
fix-ecs-empty-services-965

Conversation

@dangrondahl

Copy link
Copy Markdown
Contributor

Summary

kosli snapshot ecs aborted when any scanned cluster had no ECS services, failing with:

Error: failed to filter ECS services in cluster <cluster>: operation error ECS: DescribeServices, ... InvalidParameterException: Services cannot be empty

This is a regression introduced in #556 (first in v2.11.26, present in v2.28.0). getFilteredECSServicesInCluster called DescribeServices with the service ARNs from ListServices; when a cluster has no services that list is empty, and the AWS ECS API rejects an empty Services list. The pre-#556 code guarded the equivalent describe call with a len(...) > 0 check; the rewrite dropped it.

Fix

Guard the empty case so an empty cluster contributes zero services instead of erroring.

Testability

The repo had no mock layer for ECS, so this follows the existing Lambda pattern (LambdaAPI interface + fake_lambda.go):

  • ECSServicesAPI interface — the real *ecs.Client satisfies it implicitly; getFilteredECSServicesInCluster now takes the interface (no production behaviour change).
  • fake_ecs.goFakeECSClient whose DescribeServices reproduces the real AWS contract (errors on an empty Services list).
  • ecs_services_test.go — empty-cluster regression test (reproduces the exact bug-report error) plus a happy-path test.

These run via go test ./internal/aws/ with no Docker server and no AWS credentials.

Verification

  • Regression test fails before the fix with InvalidParameterException: Services cannot be empty, passes after.
  • internal/aws package green, go vet clean, golangci-lint 0 issues, go build ./... succeeds.

Closes #965

kosli snapshot ecs aborted when any scanned cluster had no services,
because getFilteredECSServicesInCluster called DescribeServices with an
empty Services list, which AWS rejects with InvalidParameterException:
Services cannot be empty.

Guard the empty case so an empty cluster contributes zero services
instead of erroring. Introduce an ECSServicesAPI interface and a
FakeECSClient (mirroring the existing Lambda pattern) so the behaviour
is covered by unit tests without live AWS.

Fixes #965
@claude

claude Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dangrondahl's task in 1m 41s —— View job


Review: PR #966 — handle clusters with zero services

  • Read PR changes and gather context
  • Run static analysis (go build, go vet, go mod tidy)
  • Review code quality, edge cases, and tests
  • Post review feedback

Verdict: LGTM. This is a tight, well-scoped regression fix with the right test coverage. Recommend merging.

Static analysis

Check Result
go build ./... ✅ clean
go vet ./internal/aws/... ✅ clean
go mod tidy + git diff go.mod go.sum ✅ no changes

(Note: go test ./internal/aws/ requires interactive approval in this environment, so I reviewed the tests statically rather than executing them. The package compiles and vets cleanly.)

Correctness

The fix is exactly right. getFilteredECSServicesInCluster was calling DescribeServices with the ARNs from ListServices; for an empty cluster that list is empty and AWS rejects it with InvalidParameterException: Services cannot be empty. The new len(listServicesOutput.ServiceArns) == 0 guard (aws.go:651–653) returns early so an empty cluster contributes zero services — matching the pre-#556 behaviour. The explanatory comment is clear and explains why, not just what. 👍

Testing — nicely done

  • Introducing the ECSServicesAPI interface so the real *ecs.Client satisfies it implicitly means no production behaviour change while making the function testable — and it follows the existing LambdaAPI/fake_lambda.go pattern rather than inventing a new one. Good consistency with the codebase.
  • FakeECSClient.DescribeServices deliberately reproduces the real AWS contract (errors on empty Services), so the regression test genuinely fails before the fix and passes after — not a test that merely asserts the new code path.
  • Both the empty-cluster regression case and the happy path are covered, and the tests need no Docker server or AWS creds.

Minor / non-blocking

  • One inline note (aws.go:651–653): the early return also short-circuits the pagination recursion. This is fine because ECS ListServices doesn't filter server-side, so an empty page with a non-nil NextToken shouldn't occur — flagged only so the intent is documented for future readers.
  • Optional: a sibling concern is getFilteredECSClusters (aws.go:541) calling DescribeClusters with ClusterArns — but DescribeClusters tolerates an empty list, so no equivalent bug exists there. No action needed.

Comment thread internal/aws/aws.go
@dangrondahl

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Both notes are non-blocking and need no code change:

  • Early return short-circuiting pagination (aws.go:653): intentional, and your reasoning is right — ECS ListServices doesn't filter server-side, so an empty ServiceArns page paired with a non-nil NextToken can't occur. The guard only triggers for genuinely empty clusters, so no later pages are dropped.
  • Sibling getFilteredECSClustersDescribeClusters: confirmed safe — DescribeClusters tolerates an empty list, so there's no equivalent bug. No action needed.

Leaving the code as-is. Ready to merge.

@dangrondahl dangrondahl merged commit c26e9b3 into main Jun 22, 2026
20 checks passed
@dangrondahl dangrondahl deleted the fix-ecs-empty-services-965 branch June 22, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kosli snapshot ecs fails when a scanned cluster has zero services

2 participants