detached ea2fb16

fix(cli): clarify route diagnose cleanup output

commit
ea2fb16968ac711b60027cbac3ea9d6f621872c5
author
brice · · 2026-05-22 07:13 UTC
parent
6507981
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +49 -2
M internal/adapters/in/cli/routes.go +6 -2
@@ -1191,7 +1191,7 @@ func finalizeRouteDiagnosis(diag *routeDiagnosis) {
11911191 diag.Hints = append(diag.Hints, "persistent volumes are preserved by default")
11921192 }
11931193 if len(diag.OrphanedAttachments) > 0 {
1194- diag.Hints = append(diag.Hints, "run 'gordon attachments prune --stop' to stop orphaned attachment containers while preserving volumes")
1194+ diag.Hints = append(diag.Hints, fmt.Sprintf("run 'gordon routes purge %s --attachments' to review orphaned attachment cleanup for this route", diag.Domain))
11951195 }
11961196 }
11971197
@@ -1323,8 +1323,12 @@ func writeRouteDiagnosisSummary(out io.Writer, diag *routeDiagnosis) error {
13231323 }
13241324
13251325 func writeRouteDiagnosisLeftovers(out io.Writer, diag *routeDiagnosis) error {
1326+ volumeLabel := "Volume:"
1327+ if !diag.Configured {
1328+ volumeLabel = "Preserved volume:"
1329+ }
13261330 for _, volume := range diag.Volumes {
1327- if err := cliWriteLine(out, cliRenderMeta("Preserved volume:", volume.Name)); err != nil {
1331+ if err := cliWriteLine(out, cliRenderMeta(volumeLabel, volume.Name)); err != nil {
13281332 return err
13291333 }
13301334 }
@@ -1191,7 +1191,7 @@ func finalizeRouteDiagnosis(diag *routeDiagnosis) {
1191 diag.Hints = append(diag.Hints, "persistent volumes are preserved by default")1191 diag.Hints = append(diag.Hints, "persistent volumes are preserved by default")
1192 }1192 }
1193 if len(diag.OrphanedAttachments) > 0 {1193 if len(diag.OrphanedAttachments) > 0 {
1194- diag.Hints = append(diag.Hints, "run 'gordon attachments prune --stop' to stop orphaned attachment containers while preserving volumes")1194+ diag.Hints = append(diag.Hints, fmt.Sprintf("run 'gordon routes purge %s --attachments' to review orphaned attachment cleanup for this route", diag.Domain))
1195 }1195 }
1196 }1196 }
1197 1197
@@ -1323,8 +1323,12 @@ func writeRouteDiagnosisSummary(out io.Writer, diag *routeDiagnosis) error {
1323 }1323 }
1324 1324
1325 func writeRouteDiagnosisLeftovers(out io.Writer, diag *routeDiagnosis) error {1325 func writeRouteDiagnosisLeftovers(out io.Writer, diag *routeDiagnosis) error {
1326+ volumeLabel := "Volume:"
1327+ if !diag.Configured {
1328+ volumeLabel = "Preserved volume:"
1329+ }
1326 for _, volume := range diag.Volumes {1330 for _, volume := range diag.Volumes {
1327- if err := cliWriteLine(out, cliRenderMeta("Preserved volume:", volume.Name)); err != nil {1331+ if err := cliWriteLine(out, cliRenderMeta(volumeLabel, volume.Name)); err != nil {
1328 return err1332 return err
1329 }1333 }
1330 }1334 }
M internal/adapters/in/cli/routes_test.go +43 -0
@@ -980,6 +980,49 @@ func TestBuildRouteDiagnosis_RemoteMissingRouteDoesNotWarnOnExpectedNotFound(t *
980980 assert.Empty(t, diag.Warnings)
981981 }
982982
983+func TestBuildRouteDiagnosis_UsesRouteScopedReviewHintForOrphanedAttachments(t *testing.T) {
984+ cpMock := climocks.NewMockControlPlane(t)
985+ cpMock.EXPECT().GetRoute(context.Background(), "app.example.test").Return(&domain.Route{Domain: "app.example.test", Image: "app:latest"}, nil).Once()
986+ cpMock.EXPECT().ListRoutesWithDetails(context.Background()).Return([]remote.RouteInfo{{
987+ Domain: "app.example.test",
988+ ContainerID: "route-1",
989+ ContainerStatus: "running",
990+ }}, nil).Once()
991+ cpMock.EXPECT().GetHealth(context.Background()).Return(nil, nil).Once()
992+ cpMock.EXPECT().ListVolumes(context.Background()).Return(nil, nil).Once()
993+ cpMock.EXPECT().ListOrphanedAttachments(context.Background()).Return([]domain.CleanupAttachment{{
994+ Name: "old-postgres",
995+ ContainerID: "attachment-1",
996+ Owner: "app.example.test",
997+ }}, nil).Once()
998+
999+ diag, err := buildRouteDiagnosis(context.Background(), cpMock, "app.example.test")
1000+
1001+ require.NoError(t, err)
1002+ assert.Contains(t, diag.Hints, "run 'gordon routes purge app.example.test --attachments' to review orphaned attachment cleanup for this route")
1003+}
1004+
1005+func TestWriteRouteDiagnosisText_UsesNeutralVolumeLabelForConfiguredRoutes(t *testing.T) {
1006+ var out bytes.Buffer
1007+ diag := &routeDiagnosis{
1008+ Domain: "app.example.test",
1009+ Configured: true,
1010+ Route: &domain.Route{Domain: "app.example.test", Image: "app:latest"},
1011+ Runtime: &remote.RouteInfo{
1012+ Domain: "app.example.test",
1013+ ContainerID: "route-1",
1014+ ContainerStatus: "running",
1015+ },
1016+ Volumes: []dto.Volume{{Name: "gordon-app-example-test-data"}},
1017+ }
1018+
1019+ err := writeRouteDiagnosisText(&out, diag)
1020+ require.NoError(t, err)
1021+ rendered := stripANSI(out.String())
1022+ assert.Contains(t, rendered, "Volume: gordon-app-example-test-data")
1023+ assert.NotContains(t, rendered, "Preserved volume:")
1024+}
1025+
9831026 func TestWriteRouteDiagnosisText_ShowsActiveAttachmentsSeparatelyFromOrphans(t *testing.T) {
9841027 var out bytes.Buffer
9851028 diag := &routeDiagnosis{
@@ -980,6 +980,49 @@ func TestBuildRouteDiagnosis_RemoteMissingRouteDoesNotWarnOnExpectedNotFound(t *
980 assert.Empty(t, diag.Warnings)980 assert.Empty(t, diag.Warnings)
981 }981 }
982 982
983+func TestBuildRouteDiagnosis_UsesRouteScopedReviewHintForOrphanedAttachments(t *testing.T) {
984+ cpMock := climocks.NewMockControlPlane(t)
985+ cpMock.EXPECT().GetRoute(context.Background(), "app.example.test").Return(&domain.Route{Domain: "app.example.test", Image: "app:latest"}, nil).Once()
986+ cpMock.EXPECT().ListRoutesWithDetails(context.Background()).Return([]remote.RouteInfo{{
987+ Domain: "app.example.test",
988+ ContainerID: "route-1",
989+ ContainerStatus: "running",
990+ }}, nil).Once()
991+ cpMock.EXPECT().GetHealth(context.Background()).Return(nil, nil).Once()
992+ cpMock.EXPECT().ListVolumes(context.Background()).Return(nil, nil).Once()
993+ cpMock.EXPECT().ListOrphanedAttachments(context.Background()).Return([]domain.CleanupAttachment{{
994+ Name: "old-postgres",
995+ ContainerID: "attachment-1",
996+ Owner: "app.example.test",
997+ }}, nil).Once()
998+
999+ diag, err := buildRouteDiagnosis(context.Background(), cpMock, "app.example.test")
1000+
1001+ require.NoError(t, err)
1002+ assert.Contains(t, diag.Hints, "run 'gordon routes purge app.example.test --attachments' to review orphaned attachment cleanup for this route")
1003+}
1004+
1005+func TestWriteRouteDiagnosisText_UsesNeutralVolumeLabelForConfiguredRoutes(t *testing.T) {
1006+ var out bytes.Buffer
1007+ diag := &routeDiagnosis{
1008+ Domain: "app.example.test",
1009+ Configured: true,
1010+ Route: &domain.Route{Domain: "app.example.test", Image: "app:latest"},
1011+ Runtime: &remote.RouteInfo{
1012+ Domain: "app.example.test",
1013+ ContainerID: "route-1",
1014+ ContainerStatus: "running",
1015+ },
1016+ Volumes: []dto.Volume{{Name: "gordon-app-example-test-data"}},
1017+ }
1018+
1019+ err := writeRouteDiagnosisText(&out, diag)
1020+ require.NoError(t, err)
1021+ rendered := stripANSI(out.String())
1022+ assert.Contains(t, rendered, "Volume: gordon-app-example-test-data")
1023+ assert.NotContains(t, rendered, "Preserved volume:")
1024+}
1025+
983 func TestWriteRouteDiagnosisText_ShowsActiveAttachmentsSeparatelyFromOrphans(t *testing.T) {1026 func TestWriteRouteDiagnosisText_ShowsActiveAttachmentsSeparatelyFromOrphans(t *testing.T) {
984 var out bytes.Buffer1027 var out bytes.Buffer
985 diag := &routeDiagnosis{1028 diag := &routeDiagnosis{

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close