Skip to content

Commit c4877d6

Browse files
committed
feat: add ECS Exec debug sessions browser (M3.11)
- Add ECS service layer: ListClusters, ListServices, ListTasks, DescribeTaskContainers - Add ECSClientAPI interface and ECSClient field to AwsRepository - Add BuildECSExecCommand wrapping `aws ecs execute-command --interactive` - Add TUI drill-down: Cluster → Service → Task → Container - Show exec readiness (exec:✓/✗) per container; surface actionable error if disabled - Launch exec session via tea.ExecProcess, return cleanly to TUI on exit - Add unit tests with mockECSClient (5 test cases) - Update README features table and key bindings
1 parent cc20ad0 commit c4877d6

13 files changed

Lines changed: 1131 additions & 1 deletion

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ contexts:
128128
| IAM | IAM User Browser (lightweight username pages, background filter expansion, detail drill-down) | ✅ Implemented |
129129
| IAM | Access Key Browser (list keys with status, age, last used) | ✅ Implemented |
130130
| IAM | Access Key Rotation (create → verify/apply → deactivate → delete) | ✅ Implemented |
131+
| ECS | ECS Exec Sessions (Clusters → Services → Tasks → Containers, exec session via `aws ecs execute-command`) | ✅ Implemented |
131132

132133
## TUI Key Bindings
133134

@@ -202,6 +203,16 @@ contexts:
202203
| `n` | Load more (older events) | Viewer |
203204
| `PgUp`/`PgDn` | Page scroll | Viewer |
204205

206+
### ECS Exec Sessions
207+
208+
| Key | Action | Screen |
209+
|-----|--------|--------|
210+
| `/` | Filter clusters/services | Cluster / Service list |
211+
| `r` | Refresh list | Cluster / Service / Task list |
212+
| `Enter` | Drill down (Cluster → Service → Task → Container) | Any list |
213+
| `Enter` | Start exec session (`/bin/sh`) | Container list |
214+
| `Esc` | Go back one level | Any screen |
215+
205216
### Context Switcher
206217

207218
| Key | Action |

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ require (
2626
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
2727
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
2828
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.68.0 // indirect
29+
github.com/aws/aws-sdk-go-v2/service/ecs v1.76.0 // indirect
2930
github.com/aws/aws-sdk-go-v2/service/iam v1.53.7 // indirect
3031
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
3132
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.68.0 h1:+/lmB/+i2oqkzbmlQ
2424
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.68.0/go.mod h1:PobeppEnIjw4pcgjFryNDZCTH7AiqZw0yb5r98Gvf9c=
2525
github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0 h1:98Miqj16un1WLNyM1RjVDhXYumhqZrQfAeG8i4jPG6o=
2626
github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0/go.mod h1:T6ndRfdhnXLIY5oKBHjYZDVj706los2zGdpThppquvA=
27+
github.com/aws/aws-sdk-go-v2/service/ecs v1.76.0 h1:a5G/TgJNrpuCjZBTf8/PTN0C2B0do/ylaYVynxPSbUQ=
28+
github.com/aws/aws-sdk-go-v2/service/ecs v1.76.0/go.mod h1:QkWmubOYmjj3cHn7A4CoUU7BKJhVeo39Gp6NH7IyhZw=
2729
github.com/aws/aws-sdk-go-v2/service/iam v1.53.7 h1:n9YLiWtX3+6pTLZWvRJmtq5JIB9NA/KFelyCg5fOlTU=
2830
github.com/aws/aws-sdk-go-v2/service/iam v1.53.7/go.mod h1:sP46Vo6MeJcM4s0ZXcG2PFmfiSyixhIuC/74W52yKuk=
2931
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=

internal/app/app.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ const (
4848
screenCWLogGroupList
4949
screenCWLogStreamList
5050
screenCWLogViewer
51+
screenECSClusterList
52+
screenECSServiceList
53+
screenECSTaskList
54+
screenECSContainerList
5155
screenContextPicker
5256
screenContextAdd
5357
screenLoading
@@ -176,6 +180,29 @@ type Model struct {
176180
sgAddInput string // current field text input
177181
sgAddSelectIdx int // index for select-type fields (direction, protocol)
178182

183+
// ECS browser state
184+
ecsClusters []awsservice.ECSCluster
185+
filteredECSClusters []awsservice.ECSCluster
186+
ecsClusterIdx int
187+
ecsClusterFilter string
188+
ecsClusterFilterActive bool
189+
selectedECSCluster *awsservice.ECSCluster
190+
191+
ecsServices []awsservice.ECSService
192+
filteredECSServices []awsservice.ECSService
193+
ecsServiceIdx int
194+
ecsServiceFilter string
195+
ecsServiceFilterActive bool
196+
selectedECSService *awsservice.ECSService
197+
198+
ecsTasks []awsservice.ECSTask
199+
filteredECSTasks []awsservice.ECSTask
200+
ecsTaskIdx int
201+
selectedECSTask *awsservice.ECSTask
202+
203+
ecsContainers []awsservice.ECSContainer
204+
ecsContainerIdx int
205+
179206
// CloudWatch Logs browser state
180207
cwLogGroups []awsservice.LogGroup
181208
filteredCWLogGroups []awsservice.LogGroup
@@ -310,6 +337,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
310337
m.handleIAMMsg,
311338
m.handleSecretMsg,
312339
m.handleCloudWatchLogsMsg,
340+
m.handleECSMsg,
313341
m.handleContextMsg,
314342
} {
315343
if newM, cmd, handled := h(msg); handled {
@@ -398,6 +426,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
398426
return m.updateIAMKeyRotateConfirm(msg)
399427
case screenIAMKeyRotateResult:
400428
return m.updateIAMKeyRotateResult(msg)
429+
case screenECSClusterList:
430+
return m.updateECSClusterList(msg)
431+
case screenECSServiceList:
432+
return m.updateECSServiceList(msg)
433+
case screenECSTaskList:
434+
return m.updateECSTaskList(msg)
435+
case screenECSContainerList:
436+
return m.updateECSContainerList(msg)
401437
case screenContextPicker:
402438
return m.updateContextPicker(msg)
403439
case screenContextAdd:
@@ -501,6 +537,9 @@ func (m Model) updateFeatureList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
501537
m.iamRotationEnabled = true
502538
m.screen = screenLoading
503539
return m, m.loadIAMKeys()
540+
case domain.FeatureECSExec:
541+
m.screen = screenLoading
542+
return m, m.loadECSClusters()
504543
}
505544
}
506545
}
@@ -586,6 +625,14 @@ func (m Model) View() string {
586625
v = m.viewIAMKeyRotateConfirm()
587626
case screenIAMKeyRotateResult:
588627
v = m.viewIAMKeyRotateResult()
628+
case screenECSClusterList:
629+
v = m.viewECSClusterList()
630+
case screenECSServiceList:
631+
v = m.viewECSServiceList()
632+
case screenECSTaskList:
633+
v = m.viewECSTaskList()
634+
case screenECSContainerList:
635+
v = m.viewECSContainerList()
589636
case screenContextPicker:
590637
v = m.viewContextPicker()
591638
case screenContextAdd:

internal/app/messages.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,23 @@ type iamKeyDeletedMsg struct {
152152
keyID string
153153
err error
154154
}
155+
156+
type ecsClustersLoadedMsg struct {
157+
clusters []awsservice.ECSCluster
158+
}
159+
160+
type ecsServicesLoadedMsg struct {
161+
services []awsservice.ECSService
162+
}
163+
164+
type ecsTasksLoadedMsg struct {
165+
tasks []awsservice.ECSTask
166+
}
167+
168+
type ecsContainersLoadedMsg struct {
169+
containers []awsservice.ECSContainer
170+
}
171+
172+
type ecsExecDoneMsg struct {
173+
err error
174+
}

0 commit comments

Comments
 (0)