detached cc460f4

fix(cli): preserve route show health errors

Keep unknown container status when probes return no container state, and surface per-route probe failures in routes show output.
commit
cc460f41bef6847915a24ae918ea89c8d9be0d7e
author
brice · · 2026-04-15 05:40 UTC
parent
6577047
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +68 -1
M internal/adapters/in/cli/routes.go +6 -1
@@ -975,8 +975,13 @@ func runRoutesShow(ctx context.Context, cp ControlPlane, out io.Writer, routeDom
975975 containerStatus := "unknown"
976976 httpStatus := 0
977977 if routeHealth != nil {
978- containerStatus = routeHealth.ContainerStatus
978+ if routeHealth.ContainerStatus != "" {
979+ containerStatus = routeHealth.ContainerStatus
980+ }
979981 httpStatus = routeHealth.HTTPStatus
982+ if healthErr == "" && routeHealth.Error != "" {
983+ healthErr = routeHealth.Error
984+ }
980985 }
981986
982987 if jsonOut {
@@ -975,8 +975,13 @@ func runRoutesShow(ctx context.Context, cp ControlPlane, out io.Writer, routeDom
975 containerStatus := "unknown"975 containerStatus := "unknown"
976 httpStatus := 0976 httpStatus := 0
977 if routeHealth != nil {977 if routeHealth != nil {
978- containerStatus = routeHealth.ContainerStatus978+ if routeHealth.ContainerStatus != "" {
979+ containerStatus = routeHealth.ContainerStatus
980+ }
979 httpStatus = routeHealth.HTTPStatus981 httpStatus = routeHealth.HTTPStatus
982+ if healthErr == "" && routeHealth.Error != "" {
983+ healthErr = routeHealth.Error
984+ }
980 }985 }
981 986
982 if jsonOut {987 if jsonOut {
M internal/adapters/in/cli/routes_test.go +62 -0
@@ -353,6 +353,68 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) {
353353 assert.Contains(t, text, "probe failed")
354354 }
355355
356+func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) {
357+ fake := &routesShowTestControlPlane{
358+ resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{
359+ getRoute: func(context.Context, string) (*domain.Route, error) {
360+ return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil
361+ },
362+ },
363+ getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) {
364+ return map[string]*remote.RouteHealth{
365+ "app.example.com": {
366+ HTTPStatus: 503,
367+ ContainerStatus: "",
368+ Error: "probe failed",
369+ },
370+ }, nil
371+ },
372+ }
373+
374+ var out bytes.Buffer
375+ err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true)
376+
377+ require.NoError(t, err)
378+
379+ var got map[string]any
380+ require.NoError(t, json.Unmarshal(out.Bytes(), &got))
381+ assert.Equal(t, "app.example.com", got["domain"])
382+ assert.Equal(t, "app:latest", got["image"])
383+ assert.Equal(t, "unknown", got["container_status"])
384+ assert.Equal(t, float64(503), got["http_status"])
385+ assert.Equal(t, "probe failed", got["health_error"])
386+}
387+
388+func TestRunRoutesShow_TextIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) {
389+ fake := &routesShowTestControlPlane{
390+ resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{
391+ getRoute: func(context.Context, string) (*domain.Route, error) {
392+ return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil
393+ },
394+ },
395+ getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) {
396+ return map[string]*remote.RouteHealth{
397+ "app.example.com": {
398+ HTTPStatus: 503,
399+ ContainerStatus: "",
400+ Error: "probe failed",
401+ },
402+ }, nil
403+ },
404+ }
405+
406+ var out bytes.Buffer
407+ err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false)
408+
409+ require.NoError(t, err)
410+
411+ text := stripANSI(out.String())
412+ assert.Contains(t, text, "Route: app.example.com")
413+ assert.Contains(t, text, "Container:")
414+ assert.Contains(t, text, "unknown")
415+ assert.Contains(t, text, "probe failed")
416+}
417+
356418 func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) {
357419 testsDeps := routesListDeps{
358420 explicitRemote: func() (*remote.ResolvedRemote, bool, error) {
@@ -353,6 +353,68 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) {
353 assert.Contains(t, text, "probe failed")353 assert.Contains(t, text, "probe failed")
354 }354 }
355 355
356+func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) {
357+ fake := &routesShowTestControlPlane{
358+ resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{
359+ getRoute: func(context.Context, string) (*domain.Route, error) {
360+ return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil
361+ },
362+ },
363+ getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) {
364+ return map[string]*remote.RouteHealth{
365+ "app.example.com": {
366+ HTTPStatus: 503,
367+ ContainerStatus: "",
368+ Error: "probe failed",
369+ },
370+ }, nil
371+ },
372+ }
373+
374+ var out bytes.Buffer
375+ err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true)
376+
377+ require.NoError(t, err)
378+
379+ var got map[string]any
380+ require.NoError(t, json.Unmarshal(out.Bytes(), &got))
381+ assert.Equal(t, "app.example.com", got["domain"])
382+ assert.Equal(t, "app:latest", got["image"])
383+ assert.Equal(t, "unknown", got["container_status"])
384+ assert.Equal(t, float64(503), got["http_status"])
385+ assert.Equal(t, "probe failed", got["health_error"])
386+}
387+
388+func TestRunRoutesShow_TextIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) {
389+ fake := &routesShowTestControlPlane{
390+ resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{
391+ getRoute: func(context.Context, string) (*domain.Route, error) {
392+ return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil
393+ },
394+ },
395+ getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) {
396+ return map[string]*remote.RouteHealth{
397+ "app.example.com": {
398+ HTTPStatus: 503,
399+ ContainerStatus: "",
400+ Error: "probe failed",
401+ },
402+ }, nil
403+ },
404+ }
405+
406+ var out bytes.Buffer
407+ err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false)
408+
409+ require.NoError(t, err)
410+
411+ text := stripANSI(out.String())
412+ assert.Contains(t, text, "Route: app.example.com")
413+ assert.Contains(t, text, "Container:")
414+ assert.Contains(t, text, "unknown")
415+ assert.Contains(t, text, "probe failed")
416+}
417+
356 func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) {418 func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) {
357 testsDeps := routesListDeps{419 testsDeps := routesListDeps{
358 explicitRemote: func() (*remote.ResolvedRemote, bool, error) {420 explicitRemote: func() (*remote.ResolvedRemote, bool, error) {

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