detached 85688ed

feat: add gordon networks list command

commit
85688ed88feb6fa0695c68f7d428afafb1625611
author
brice · · 2026-03-14 03:23 UTC
parent
d6ce1c7
signature
unsigned
4 files · +159 -0
M docs/cli/index.md +1 -0
@@ -37,6 +37,7 @@ Management commands run locally through in-process services by default. Add `--r
3737 | `gordon backups` | Manage database backups | [backup](./backup.md) |
3838 | `gordon images` | List and prune images | [images](./images.md) |
3939 | `gordon config show` | Show server configuration | [config](./config.md) |
40+| `gordon networks list` | List Gordon-managed Docker networks | [networks](./networks.md) |
4041
4142 ## Client Commands
4243
@@ -37,6 +37,7 @@ Management commands run locally through in-process services by default. Add `--r
37 | `gordon backups` | Manage database backups | [backup](./backup.md) |37 | `gordon backups` | Manage database backups | [backup](./backup.md) |
38 | `gordon images` | List and prune images | [images](./images.md) |38 | `gordon images` | List and prune images | [images](./images.md) |
39 | `gordon config show` | Show server configuration | [config](./config.md) |39 | `gordon config show` | Show server configuration | [config](./config.md) |
40+| `gordon networks list` | List Gordon-managed Docker networks | [networks](./networks.md) |
40 41
41 ## Client Commands42 ## Client Commands
42 43
A docs/cli/networks.md +47 -0
@@ -0,0 +1,47 @@
1+# Networks Commands
2+
3+Inspect Gordon-managed Docker networks.
4+
5+Remote targeting uses client config or an active remote by default.
6+Use `--remote` and `--token` to override. See [CLI Overview](./index.md).
7+
8+## gordon networks
9+
10+### Subcommands
11+
12+| Subcommand | Description |
13+|------------|-------------|
14+| `list` | List Gordon-managed Docker networks |
15+
16+---
17+
18+## gordon networks list
19+
20+Display Docker networks managed by Gordon, including which containers
21+are connected to each network.
22+
23+```bash
24+gordon networks list
25+gordon networks list --json
26+gordon networks list --remote https://gordon.mydomain.com --token $TOKEN
27+```
28+
29+### Flags
30+
31+| Flag | Description |
32+|------|-------------|
33+| `--json` | Output as JSON |
34+
35+### JSON Output
36+
37+```json
38+[
39+ {
40+ "ID": "abc123...",
41+ "Name": "gordon_myapp",
42+ "Driver": "bridge",
43+ "Containers": ["container1", "container2"],
44+ "Labels": {"gordon.managed": "true"}
45+ }
46+]
47+```
@@ -0,0 +1,47 @@
1+# Networks Commands
2+
3+Inspect Gordon-managed Docker networks.
4+
5+Remote targeting uses client config or an active remote by default.
6+Use `--remote` and `--token` to override. See [CLI Overview](./index.md).
7+
8+## gordon networks
9+
10+### Subcommands
11+
12+| Subcommand | Description |
13+|------------|-------------|
14+| `list` | List Gordon-managed Docker networks |
15+
16+---
17+
18+## gordon networks list
19+
20+Display Docker networks managed by Gordon, including which containers
21+are connected to each network.
22+
23+```bash
24+gordon networks list
25+gordon networks list --json
26+gordon networks list --remote https://gordon.mydomain.com --token $TOKEN
27+```
28+
29+### Flags
30+
31+| Flag | Description |
32+|------|-------------|
33+| `--json` | Output as JSON |
34+
35+### JSON Output
36+
37+```json
38+[
39+ {
40+ "ID": "abc123...",
41+ "Name": "gordon_myapp",
42+ "Driver": "bridge",
43+ "Containers": ["container1", "container2"],
44+ "Labels": {"gordon.managed": "true"}
45+ }
46+]
47+```
A internal/adapters/in/cli/networks.go +107 -0
@@ -0,0 +1,107 @@
1+package cli
2+
3+import (
4+ "context"
5+ "fmt"
6+ "io"
7+ "strings"
8+
9+ "github.com/bnema/gordon/internal/adapters/in/cli/ui/components"
10+ "github.com/bnema/gordon/internal/domain"
11+ "github.com/spf13/cobra"
12+)
13+
14+func newNetworksCmd() *cobra.Command {
15+ cmd := &cobra.Command{
16+ Use: "networks",
17+ Short: "Inspect Gordon-managed networks",
18+ }
19+
20+ cmd.AddCommand(newNetworksListCmd())
21+
22+ return cmd
23+}
24+
25+func newNetworksListCmd() *cobra.Command {
26+ var jsonOut bool
27+
28+ cmd := &cobra.Command{
29+ Use: "list",
30+ Short: "List Gordon-managed Docker networks",
31+ Long: `Display Docker networks managed by Gordon, including which containers
32+are connected to each network.
33+
34+Examples:
35+ gordon networks list
36+ gordon networks list --json
37+ gordon networks list --remote https://gordon.mydomain.com --token $TOKEN`,
38+ RunE: func(cmd *cobra.Command, _ []string) error {
39+ ctx := context.Background()
40+ handle, err := resolveControlPlane(configPath)
41+ if err != nil {
42+ return err
43+ }
44+ defer handle.close()
45+ return runNetworksList(ctx, handle.plane, cmd.OutOrStdout(), jsonOut)
46+ },
47+ }
48+
49+ cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON")
50+
51+ return cmd
52+}
53+
54+func runNetworksList(ctx context.Context, cp ControlPlane, out io.Writer, jsonOut bool) error {
55+ networks, err := cp.ListNetworks(ctx)
56+ if err != nil {
57+ return fmt.Errorf("failed to list networks: %w", err)
58+ }
59+
60+ if jsonOut {
61+ return writeJSON(out, networks)
62+ }
63+
64+ if len(networks) == 0 {
65+ return cliWriteLine(out, cliRenderMuted("No Gordon-managed networks found"))
66+ }
67+
68+ if err := cliWriteLine(out, cliRenderTitle("Networks")); err != nil {
69+ return err
70+ }
71+
72+ return renderNetworksTable(out, networks)
73+}
74+
75+func renderNetworksTable(out io.Writer, networks []*domain.NetworkInfo) error {
76+ rows := make([][]string, 0, len(networks))
77+ for _, net := range networks {
78+ if net == nil {
79+ continue
80+ }
81+ containers := strings.Join(net.Containers, ", ")
82+ if containers == "" {
83+ containers = "-"
84+ }
85+ if len(containers) > 40 {
86+ containers = containers[:37] + "..."
87+ }
88+ rows = append(rows, []string{
89+ net.Name,
90+ net.Driver,
91+ fmt.Sprintf("%d", len(net.Containers)),
92+ containers,
93+ })
94+ }
95+
96+ table := components.NewTable(
97+ components.WithColumns([]components.TableColumn{
98+ {Title: "Name", Width: 25},
99+ {Title: "Driver", Width: 10},
100+ {Title: "Containers", Width: 12},
101+ {Title: "Connected", Width: 40},
102+ }),
103+ components.WithRows(rows),
104+ )
105+
106+ return cliWriteLine(out, table.View())
107+}
@@ -0,0 +1,107 @@
1+package cli
2+
3+import (
4+ "context"
5+ "fmt"
6+ "io"
7+ "strings"
8+
9+ "github.com/bnema/gordon/internal/adapters/in/cli/ui/components"
10+ "github.com/bnema/gordon/internal/domain"
11+ "github.com/spf13/cobra"
12+)
13+
14+func newNetworksCmd() *cobra.Command {
15+ cmd := &cobra.Command{
16+ Use: "networks",
17+ Short: "Inspect Gordon-managed networks",
18+ }
19+
20+ cmd.AddCommand(newNetworksListCmd())
21+
22+ return cmd
23+}
24+
25+func newNetworksListCmd() *cobra.Command {
26+ var jsonOut bool
27+
28+ cmd := &cobra.Command{
29+ Use: "list",
30+ Short: "List Gordon-managed Docker networks",
31+ Long: `Display Docker networks managed by Gordon, including which containers
32+are connected to each network.
33+
34+Examples:
35+ gordon networks list
36+ gordon networks list --json
37+ gordon networks list --remote https://gordon.mydomain.com --token $TOKEN`,
38+ RunE: func(cmd *cobra.Command, _ []string) error {
39+ ctx := context.Background()
40+ handle, err := resolveControlPlane(configPath)
41+ if err != nil {
42+ return err
43+ }
44+ defer handle.close()
45+ return runNetworksList(ctx, handle.plane, cmd.OutOrStdout(), jsonOut)
46+ },
47+ }
48+
49+ cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON")
50+
51+ return cmd
52+}
53+
54+func runNetworksList(ctx context.Context, cp ControlPlane, out io.Writer, jsonOut bool) error {
55+ networks, err := cp.ListNetworks(ctx)
56+ if err != nil {
57+ return fmt.Errorf("failed to list networks: %w", err)
58+ }
59+
60+ if jsonOut {
61+ return writeJSON(out, networks)
62+ }
63+
64+ if len(networks) == 0 {
65+ return cliWriteLine(out, cliRenderMuted("No Gordon-managed networks found"))
66+ }
67+
68+ if err := cliWriteLine(out, cliRenderTitle("Networks")); err != nil {
69+ return err
70+ }
71+
72+ return renderNetworksTable(out, networks)
73+}
74+
75+func renderNetworksTable(out io.Writer, networks []*domain.NetworkInfo) error {
76+ rows := make([][]string, 0, len(networks))
77+ for _, net := range networks {
78+ if net == nil {
79+ continue
80+ }
81+ containers := strings.Join(net.Containers, ", ")
82+ if containers == "" {
83+ containers = "-"
84+ }
85+ if len(containers) > 40 {
86+ containers = containers[:37] + "..."
87+ }
88+ rows = append(rows, []string{
89+ net.Name,
90+ net.Driver,
91+ fmt.Sprintf("%d", len(net.Containers)),
92+ containers,
93+ })
94+ }
95+
96+ table := components.NewTable(
97+ components.WithColumns([]components.TableColumn{
98+ {Title: "Name", Width: 25},
99+ {Title: "Driver", Width: 10},
100+ {Title: "Containers", Width: 12},
101+ {Title: "Connected", Width: 40},
102+ }),
103+ components.WithRows(rows),
104+ )
105+
106+ return cliWriteLine(out, table.View())
107+}
M internal/adapters/in/cli/root.go +4 -0
@@ -133,6 +133,10 @@ Commands are organized by where they run:
133133 configCmd.GroupID = groupManage
134134 rootCmd.AddCommand(configCmd)
135135
136+ networksCmd := newNetworksCmd()
137+ networksCmd.GroupID = groupManage
138+ rootCmd.AddCommand(networksCmd)
139+
136140 // Client-only commands (no server needed)
137141 remotesCmd := newRemotesCmd()
138142 remotesCmd.GroupID = groupClient
@@ -133,6 +133,10 @@ Commands are organized by where they run:
133 configCmd.GroupID = groupManage133 configCmd.GroupID = groupManage
134 rootCmd.AddCommand(configCmd)134 rootCmd.AddCommand(configCmd)
135 135
136+ networksCmd := newNetworksCmd()
137+ networksCmd.GroupID = groupManage
138+ rootCmd.AddCommand(networksCmd)
139+
136 // Client-only commands (no server needed)140 // Client-only commands (no server needed)
137 remotesCmd := newRemotesCmd()141 remotesCmd := newRemotesCmd()
138 remotesCmd.GroupID = groupClient142 remotesCmd.GroupID = groupClient

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