detached 573718b

feat: add gordon images tags command

commit
573718b461649d91cddbd4293968065ffe82f7d9
author
brice · · 2026-03-14 03:24 UTC
parent
85688ed
signature
unsigned
2 files · +100 -0
M docs/cli/images.md +33 -0
@@ -12,6 +12,7 @@ Subcommands:
1212
1313 - `list` - List runtime images and registry tags.
1414 - `prune` - Prune dangling runtime images and old registry tags.
15+- `tags` - List registry tags for a specific repository.
1516
1617 > **Note:** Images commands require remote mode (`--remote` + `--token`, or configured remotes).
1718
@@ -81,6 +82,34 @@ Flags:
8182 - `--keep-releases` counts non-`latest` tags, ordered by most recent first.
8283 - `--keep-releases=0` with registry scope enabled still runs registry cleanup but keeps no non-`latest` tags.
8384
85+## gordon images tags
86+
87+```bash
88+gordon images tags <repository>
89+gordon images tags <repository> --json
90+```
91+
92+Lists all tags in the Gordon registry for the specified repository.
93+
94+Flags:
95+
96+| Flag | Description |
97+|------|-------------|
98+| `--json` | Output repository and tags as JSON |
99+
100+### JSON Output
101+
102+```bash
103+gordon images tags myapp --json
104+```
105+
106+```json
107+{
108+ "repository": "myapp",
109+ "tags": ["latest", "v1.2.0", "v1.1.0"]
110+}
111+```
112+
84113 ## Examples
85114
86115 ```bash
@@ -90,6 +119,9 @@ gordon images list
90119 # Prune everything with defaults (dangling + registry, keep latest + 3)
91120 gordon images prune
92121
122+# List tags for a repository
123+gordon images tags myapp
124+
93125 # Prune dangling runtime images only
94126 gordon images prune --dangling
95127
@@ -107,6 +139,7 @@ gordon images prune --no-confirm
107139
108140 - `list` requires `admin:status:read`.
109141 - `prune` requires `admin:config:write`.
142+- `tags` requires `admin:status:read`.
110143
111144 ## Related
112145
@@ -12,6 +12,7 @@ Subcommands:
12 12
13 - `list` - List runtime images and registry tags.13 - `list` - List runtime images and registry tags.
14 - `prune` - Prune dangling runtime images and old registry tags.14 - `prune` - Prune dangling runtime images and old registry tags.
15+- `tags` - List registry tags for a specific repository.
15 16
16 > **Note:** Images commands require remote mode (`--remote` + `--token`, or configured remotes).17 > **Note:** Images commands require remote mode (`--remote` + `--token`, or configured remotes).
17 18
@@ -81,6 +82,34 @@ Flags:
81 - `--keep-releases` counts non-`latest` tags, ordered by most recent first.82 - `--keep-releases` counts non-`latest` tags, ordered by most recent first.
82 - `--keep-releases=0` with registry scope enabled still runs registry cleanup but keeps no non-`latest` tags.83 - `--keep-releases=0` with registry scope enabled still runs registry cleanup but keeps no non-`latest` tags.
83 84
85+## gordon images tags
86+
87+```bash
88+gordon images tags <repository>
89+gordon images tags <repository> --json
90+```
91+
92+Lists all tags in the Gordon registry for the specified repository.
93+
94+Flags:
95+
96+| Flag | Description |
97+|------|-------------|
98+| `--json` | Output repository and tags as JSON |
99+
100+### JSON Output
101+
102+```bash
103+gordon images tags myapp --json
104+```
105+
106+```json
107+{
108+ "repository": "myapp",
109+ "tags": ["latest", "v1.2.0", "v1.1.0"]
110+}
111+```
112+
84 ## Examples113 ## Examples
85 114
86 ```bash115 ```bash
@@ -90,6 +119,9 @@ gordon images list
90 # Prune everything with defaults (dangling + registry, keep latest + 3)119 # Prune everything with defaults (dangling + registry, keep latest + 3)
91 gordon images prune120 gordon images prune
92 121
122+# List tags for a repository
123+gordon images tags myapp
124+
93 # Prune dangling runtime images only125 # Prune dangling runtime images only
94 gordon images prune --dangling126 gordon images prune --dangling
95 127
@@ -107,6 +139,7 @@ gordon images prune --no-confirm
107 139
108 - `list` requires `admin:status:read`.140 - `list` requires `admin:status:read`.
109 - `prune` requires `admin:config:write`.141 - `prune` requires `admin:config:write`.
142+- `tags` requires `admin:status:read`.
110 143
111 ## Related144 ## Related
112 145
M internal/adapters/in/cli/images.go +67 -0
@@ -76,6 +76,7 @@ These commands currently require remote mode with a configured target.`,
7676
7777 cmd.AddCommand(newImagesListCmd())
7878 cmd.AddCommand(newImagesPruneCmd())
79+ cmd.AddCommand(newImagesTagsCmd())
7980
8081 return cmd
8182 }
@@ -131,6 +132,72 @@ release tags per repository. Use --dangling or --registry to restrict scope.`,
131132 return cmd
132133 }
133134
135+func newImagesTagsCmd() *cobra.Command {
136+ var jsonOut bool
137+
138+ cmd := &cobra.Command{
139+ Use: "tags <repository>",
140+ Short: "List registry tags for a repository",
141+ Long: `Display all tags in the Gordon registry for a given repository.
142+
143+Examples:
144+ gordon images tags myapp
145+ gordon images tags myapp --json
146+ gordon images tags myapp --remote https://gordon.mydomain.com --token $TOKEN`,
147+ Args: cobra.ExactArgs(1),
148+ RunE: func(cmd *cobra.Command, args []string) error {
149+ ctx := context.Background()
150+ handle, err := resolveControlPlane(configPath)
151+ if err != nil {
152+ return err
153+ }
154+ defer handle.close()
155+ return runImagesTags(ctx, handle.plane, cmd.OutOrStdout(), args[0], jsonOut)
156+ },
157+ }
158+
159+ cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON")
160+
161+ return cmd
162+}
163+
164+func runImagesTags(ctx context.Context, cp ControlPlane, out io.Writer, repository string, jsonOut bool) error {
165+ tags, err := cp.ListTags(ctx, repository)
166+ if err != nil {
167+ return fmt.Errorf("failed to list tags: %w", err)
168+ }
169+
170+ if jsonOut {
171+ payload := map[string]any{
172+ "repository": repository,
173+ "tags": tags,
174+ }
175+ return writeJSON(out, payload)
176+ }
177+
178+ if len(tags) == 0 {
179+ return cliWriteLine(out, cliRenderMuted(fmt.Sprintf("No tags found for %s", repository)))
180+ }
181+
182+ if err := cliWriteLine(out, cliRenderTitle(fmt.Sprintf("Tags for %s", repository))); err != nil {
183+ return err
184+ }
185+ if err := cliWriteLine(out, ""); err != nil {
186+ return err
187+ }
188+
189+ for _, tag := range tags {
190+ if err := cliWriteLine(out, cliRenderListItem(tag)); err != nil {
191+ return err
192+ }
193+ }
194+
195+ if err := cliWriteLine(out, ""); err != nil {
196+ return err
197+ }
198+ return cliWritef(out, "Total: %d tags\n", len(tags))
199+}
200+
134201 // resolvePruneScopes determines which prune subsystems are active.
135202 // No scope flags → both true. Any scope flag present → only selected scopes.
136203 func resolvePruneScopes(opts imagesPruneOptions) (pruneDangling, pruneRegistry bool) {
@@ -76,6 +76,7 @@ These commands currently require remote mode with a configured target.`,
76 76
77 cmd.AddCommand(newImagesListCmd())77 cmd.AddCommand(newImagesListCmd())
78 cmd.AddCommand(newImagesPruneCmd())78 cmd.AddCommand(newImagesPruneCmd())
79+ cmd.AddCommand(newImagesTagsCmd())
79 80
80 return cmd81 return cmd
81 }82 }
@@ -131,6 +132,72 @@ release tags per repository. Use --dangling or --registry to restrict scope.`,
131 return cmd132 return cmd
132 }133 }
133 134
135+func newImagesTagsCmd() *cobra.Command {
136+ var jsonOut bool
137+
138+ cmd := &cobra.Command{
139+ Use: "tags <repository>",
140+ Short: "List registry tags for a repository",
141+ Long: `Display all tags in the Gordon registry for a given repository.
142+
143+Examples:
144+ gordon images tags myapp
145+ gordon images tags myapp --json
146+ gordon images tags myapp --remote https://gordon.mydomain.com --token $TOKEN`,
147+ Args: cobra.ExactArgs(1),
148+ RunE: func(cmd *cobra.Command, args []string) error {
149+ ctx := context.Background()
150+ handle, err := resolveControlPlane(configPath)
151+ if err != nil {
152+ return err
153+ }
154+ defer handle.close()
155+ return runImagesTags(ctx, handle.plane, cmd.OutOrStdout(), args[0], jsonOut)
156+ },
157+ }
158+
159+ cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON")
160+
161+ return cmd
162+}
163+
164+func runImagesTags(ctx context.Context, cp ControlPlane, out io.Writer, repository string, jsonOut bool) error {
165+ tags, err := cp.ListTags(ctx, repository)
166+ if err != nil {
167+ return fmt.Errorf("failed to list tags: %w", err)
168+ }
169+
170+ if jsonOut {
171+ payload := map[string]any{
172+ "repository": repository,
173+ "tags": tags,
174+ }
175+ return writeJSON(out, payload)
176+ }
177+
178+ if len(tags) == 0 {
179+ return cliWriteLine(out, cliRenderMuted(fmt.Sprintf("No tags found for %s", repository)))
180+ }
181+
182+ if err := cliWriteLine(out, cliRenderTitle(fmt.Sprintf("Tags for %s", repository))); err != nil {
183+ return err
184+ }
185+ if err := cliWriteLine(out, ""); err != nil {
186+ return err
187+ }
188+
189+ for _, tag := range tags {
190+ if err := cliWriteLine(out, cliRenderListItem(tag)); err != nil {
191+ return err
192+ }
193+ }
194+
195+ if err := cliWriteLine(out, ""); err != nil {
196+ return err
197+ }
198+ return cliWritef(out, "Total: %d tags\n", len(tags))
199+}
200+
134 // resolvePruneScopes determines which prune subsystems are active.201 // resolvePruneScopes determines which prune subsystems are active.
135 // No scope flags → both true. Any scope flag present → only selected scopes.202 // No scope flags → both true. Any scope flag present → only selected scopes.
136 func resolvePruneScopes(opts imagesPruneOptions) (pruneDangling, pruneRegistry bool) {203 func resolvePruneScopes(opts imagesPruneOptions) (pruneDangling, pruneRegistry bool) {

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