detached 494c50d

feat: show attachment network in routes list for debugging

Add Network field to Attachment struct to display each attachment's
actual network, allowing verification that attachments are correctly
connected to their parent route's network.
commit
494c50da9aa439117464e8196651c0534ef5489e
author
brice · · 2026-01-17 14:41 UTC
parent
210d0fa
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +26 -3
M internal/adapters/dto/admin_route.go +1 -0
@@ -24,6 +24,7 @@ type Attachment struct {
2424 Image string `json:"image"`
2525 ContainerID string `json:"container_id"`
2626 Status string `json:"status"`
27+ Network string `json:"network"`
2728 }
2829
2930 // RoutesResponse represents a list of routes.
@@ -24,6 +24,7 @@ type Attachment struct {
24 Image string `json:"image"`24 Image string `json:"image"`
25 ContainerID string `json:"container_id"`25 ContainerID string `json:"container_id"`
26 Status string `json:"status"`26 Status string `json:"status"`
27+ Network string `json:"network"`
27 }28 }
28 29
29 // RoutesResponse represents a list of routes.30 // RoutesResponse represents a list of routes.
M internal/adapters/in/cli/routes.go +3 -1
@@ -181,7 +181,9 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client) error {
181181 }
182182 attachmentStatus := components.ContainerStatusBadge(attachment.Status)
183183 attachmentImage := truncateImage(attachment.Image, imageColWidth)
184- rows = append(rows, []string{attachmentName, attachmentImage, "-", attachmentStatus, "-"})
184+ // Show attachment's actual network for debugging/verification
185+ attachmentNetwork := truncateNetwork(attachment.Network, networkColWidth)
186+ rows = append(rows, []string{attachmentName, attachmentImage, attachmentNetwork, attachmentStatus, "-"})
185187 }
186188 }
187189
@@ -181,7 +181,9 @@ func runRoutesListRemote(ctx context.Context, client *remote.Client) error {
181 }181 }
182 attachmentStatus := components.ContainerStatusBadge(attachment.Status)182 attachmentStatus := components.ContainerStatusBadge(attachment.Status)
183 attachmentImage := truncateImage(attachment.Image, imageColWidth)183 attachmentImage := truncateImage(attachment.Image, imageColWidth)
184- rows = append(rows, []string{attachmentName, attachmentImage, "-", attachmentStatus, "-"})184+ // Show attachment's actual network for debugging/verification
185+ attachmentNetwork := truncateNetwork(attachment.Network, networkColWidth)
186+ rows = append(rows, []string{attachmentName, attachmentImage, attachmentNetwork, attachmentStatus, "-"})
185 }187 }
186 }188 }
187 189
M internal/adapters/in/http/admin/handler.go +1 -0
@@ -47,6 +47,7 @@ func toAttachmentResponse(a domain.Attachment) dto.Attachment {
4747 Image: a.Image,
4848 ContainerID: a.ContainerID,
4949 Status: a.Status,
50+ Network: a.Network,
5051 }
5152 }
5253
@@ -47,6 +47,7 @@ func toAttachmentResponse(a domain.Attachment) dto.Attachment {
47 Image: a.Image,47 Image: a.Image,
48 ContainerID: a.ContainerID,48 ContainerID: a.ContainerID,
49 Status: a.Status,49 Status: a.Status,
50+ Network: a.Network,
50 }51 }
51 }52 }
52 53
M internal/domain/container.go +1 -0
@@ -27,6 +27,7 @@ type Attachment struct {
2727 Image string
2828 ContainerID string
2929 Status string
30+ Network string
3031 }
3132
3233 // RouteInfo combines route configuration with runtime state.
@@ -27,6 +27,7 @@ type Attachment struct {
27 Image string27 Image string
28 ContainerID string28 ContainerID string
29 Status string29 Status string
30+ Network string
30 }31 }
31 32
32 // RouteInfo combines route configuration with runtime state.33 // RouteInfo combines route configuration with runtime state.
M internal/usecase/container/routes_test.go +6 -0
@@ -25,6 +25,9 @@ func TestService_ListRoutesWithDetails(t *testing.T) {
2525 }
2626
2727 runtime.EXPECT().GetContainerNetwork(mock.Anything, "container-1").Return("gordon-app-example-com", nil)
28+ // getAllAttachments calls GetContainerNetwork for each attachment
29+ runtime.EXPECT().GetContainerNetwork(mock.Anything, "attach-1").Return("gordon-app-example-com", nil)
30+ runtime.EXPECT().GetContainerNetwork(mock.Anything, "attach-2").Return("gordon-other-example-com", nil)
2831 runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{
2932 {
3033 ID: "attach-1",
@@ -71,6 +74,7 @@ func TestService_ListRoutesWithDetails(t *testing.T) {
7174 assert.Equal(t, "postgres", results[0].Attachments[0].Name)
7275 assert.Equal(t, "postgres:15", results[0].Attachments[0].Image)
7376 assert.Equal(t, "attach-1", results[0].Attachments[0].ContainerID)
77+ assert.Equal(t, "gordon-app-example-com", results[0].Attachments[0].Network)
7478 }
7579 }
7680
@@ -136,6 +140,7 @@ func TestService_ListAttachments(t *testing.T) {
136140 },
137141 },
138142 }, nil)
143+ runtime.EXPECT().GetContainerNetwork(mock.Anything, "attach-1").Return("gordon-app-example-com", nil)
139144
140145 attachments := svc.ListAttachments(ctx, "app.example.com")
141146
@@ -143,6 +148,7 @@ func TestService_ListAttachments(t *testing.T) {
143148 assert.Equal(t, "postgres", attachments[0].Name)
144149 assert.Equal(t, "postgres:15", attachments[0].Image)
145150 assert.Equal(t, "attach-1", attachments[0].ContainerID)
151+ assert.Equal(t, "gordon-app-example-com", attachments[0].Network)
146152 }
147153
148154 func TestService_ListNetworks(t *testing.T) {
@@ -25,6 +25,9 @@ func TestService_ListRoutesWithDetails(t *testing.T) {
25 }25 }
26 26
27 runtime.EXPECT().GetContainerNetwork(mock.Anything, "container-1").Return("gordon-app-example-com", nil)27 runtime.EXPECT().GetContainerNetwork(mock.Anything, "container-1").Return("gordon-app-example-com", nil)
28+ // getAllAttachments calls GetContainerNetwork for each attachment
29+ runtime.EXPECT().GetContainerNetwork(mock.Anything, "attach-1").Return("gordon-app-example-com", nil)
30+ runtime.EXPECT().GetContainerNetwork(mock.Anything, "attach-2").Return("gordon-other-example-com", nil)
28 runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{31 runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{
29 {32 {
30 ID: "attach-1",33 ID: "attach-1",
@@ -71,6 +74,7 @@ func TestService_ListRoutesWithDetails(t *testing.T) {
71 assert.Equal(t, "postgres", results[0].Attachments[0].Name)74 assert.Equal(t, "postgres", results[0].Attachments[0].Name)
72 assert.Equal(t, "postgres:15", results[0].Attachments[0].Image)75 assert.Equal(t, "postgres:15", results[0].Attachments[0].Image)
73 assert.Equal(t, "attach-1", results[0].Attachments[0].ContainerID)76 assert.Equal(t, "attach-1", results[0].Attachments[0].ContainerID)
77+ assert.Equal(t, "gordon-app-example-com", results[0].Attachments[0].Network)
74 }78 }
75 }79 }
76 80
@@ -136,6 +140,7 @@ func TestService_ListAttachments(t *testing.T) {
136 },140 },
137 },141 },
138 }, nil)142 }, nil)
143+ runtime.EXPECT().GetContainerNetwork(mock.Anything, "attach-1").Return("gordon-app-example-com", nil)
139 144
140 attachments := svc.ListAttachments(ctx, "app.example.com")145 attachments := svc.ListAttachments(ctx, "app.example.com")
141 146
@@ -143,6 +148,7 @@ func TestService_ListAttachments(t *testing.T) {
143 assert.Equal(t, "postgres", attachments[0].Name)148 assert.Equal(t, "postgres", attachments[0].Name)
144 assert.Equal(t, "postgres:15", attachments[0].Image)149 assert.Equal(t, "postgres:15", attachments[0].Image)
145 assert.Equal(t, "attach-1", attachments[0].ContainerID)150 assert.Equal(t, "attach-1", attachments[0].ContainerID)
151+ assert.Equal(t, "gordon-app-example-com", attachments[0].Network)
146 }152 }
147 153
148 func TestService_ListNetworks(t *testing.T) {154 func TestService_ListNetworks(t *testing.T) {
M internal/usecase/container/service.go +14 -2
@@ -958,7 +958,7 @@ func (s *Service) getAttachmentsForDomain(ctx context.Context, domainName string
958958 return nil
959959 }
960960
961- return s.filterAttachments(containers, domainName)
961+ return s.filterAttachments(ctx, containers, domainName)
962962 }
963963
964964 // getAllAttachments fetches all containers once and returns attachments grouped by domain.
@@ -987,11 +987,17 @@ func (s *Service) getAllAttachments(ctx context.Context) map[string][]domain.Att
987987 image = labelImage
988988 serviceName = extractServiceName(labelImage)
989989 }
990+ // Get the container's network for debugging/verification
991+ network := ""
992+ if networkName, err := s.runtime.GetContainerNetwork(ctx, container.ID); err == nil {
993+ network = networkName
994+ }
990995 attachment := domain.Attachment{
991996 Name: serviceName,
992997 Image: image,
993998 ContainerID: container.ID,
994999 Status: container.Status,
1000+ Network: network,
9951001 }
9961002 result[ownerDomain] = append(result[ownerDomain], attachment)
9971003 }
@@ -1000,7 +1006,7 @@ func (s *Service) getAllAttachments(ctx context.Context) map[string][]domain.Att
10001006 }
10011007
10021008 // filterAttachments extracts attachments for a specific domain from a container list.
1003-func (s *Service) filterAttachments(containers []*domain.Container, domainName string) []domain.Attachment {
1009+func (s *Service) filterAttachments(ctx context.Context, containers []*domain.Container, domainName string) []domain.Attachment {
10041010 attachments := make([]domain.Attachment, 0)
10051011 for _, container := range containers {
10061012 if container.Labels == nil {
@@ -1018,11 +1024,17 @@ func (s *Service) filterAttachments(containers []*domain.Container, domainName s
10181024 image = labelImage
10191025 serviceName = extractServiceName(labelImage)
10201026 }
1027+ // Get the container's network for debugging/verification
1028+ network := ""
1029+ if networkName, err := s.runtime.GetContainerNetwork(ctx, container.ID); err == nil {
1030+ network = networkName
1031+ }
10211032 attachment := domain.Attachment{
10221033 Name: serviceName,
10231034 Image: image,
10241035 ContainerID: container.ID,
10251036 Status: container.Status,
1037+ Network: network,
10261038 }
10271039 attachments = append(attachments, attachment)
10281040 }
@@ -958,7 +958,7 @@ func (s *Service) getAttachmentsForDomain(ctx context.Context, domainName string
958 return nil958 return nil
959 }959 }
960 960
961- return s.filterAttachments(containers, domainName)961+ return s.filterAttachments(ctx, containers, domainName)
962 }962 }
963 963
964 // getAllAttachments fetches all containers once and returns attachments grouped by domain.964 // getAllAttachments fetches all containers once and returns attachments grouped by domain.
@@ -987,11 +987,17 @@ func (s *Service) getAllAttachments(ctx context.Context) map[string][]domain.Att
987 image = labelImage987 image = labelImage
988 serviceName = extractServiceName(labelImage)988 serviceName = extractServiceName(labelImage)
989 }989 }
990+ // Get the container's network for debugging/verification
991+ network := ""
992+ if networkName, err := s.runtime.GetContainerNetwork(ctx, container.ID); err == nil {
993+ network = networkName
994+ }
990 attachment := domain.Attachment{995 attachment := domain.Attachment{
991 Name: serviceName,996 Name: serviceName,
992 Image: image,997 Image: image,
993 ContainerID: container.ID,998 ContainerID: container.ID,
994 Status: container.Status,999 Status: container.Status,
1000+ Network: network,
995 }1001 }
996 result[ownerDomain] = append(result[ownerDomain], attachment)1002 result[ownerDomain] = append(result[ownerDomain], attachment)
997 }1003 }
@@ -1000,7 +1006,7 @@ func (s *Service) getAllAttachments(ctx context.Context) map[string][]domain.Att
1000 }1006 }
1001 1007
1002 // filterAttachments extracts attachments for a specific domain from a container list.1008 // filterAttachments extracts attachments for a specific domain from a container list.
1003-func (s *Service) filterAttachments(containers []*domain.Container, domainName string) []domain.Attachment {1009+func (s *Service) filterAttachments(ctx context.Context, containers []*domain.Container, domainName string) []domain.Attachment {
1004 attachments := make([]domain.Attachment, 0)1010 attachments := make([]domain.Attachment, 0)
1005 for _, container := range containers {1011 for _, container := range containers {
1006 if container.Labels == nil {1012 if container.Labels == nil {
@@ -1018,11 +1024,17 @@ func (s *Service) filterAttachments(containers []*domain.Container, domainName s
1018 image = labelImage1024 image = labelImage
1019 serviceName = extractServiceName(labelImage)1025 serviceName = extractServiceName(labelImage)
1020 }1026 }
1027+ // Get the container's network for debugging/verification
1028+ network := ""
1029+ if networkName, err := s.runtime.GetContainerNetwork(ctx, container.ID); err == nil {
1030+ network = networkName
1031+ }
1021 attachment := domain.Attachment{1032 attachment := domain.Attachment{
1022 Name: serviceName,1033 Name: serviceName,
1023 Image: image,1034 Image: image,
1024 ContainerID: container.ID,1035 ContainerID: container.ID,
1025 Status: container.Status,1036 Status: container.Status,
1037+ Network: network,
1026 }1038 }
1027 attachments = append(attachments, attachment)1039 attachments = append(attachments, attachment)
1028 }1040 }

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