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: | |||
| 12 | 12 | ||
| 13 | 13 | - `list` - List runtime images and registry tags. | |
| 14 | 14 | - `prune` - Prune dangling runtime images and old registry tags. | |
| 15 | + | - `tags` - List registry tags for a specific repository. | |
| 15 | 16 | ||
| 16 | 17 | > **Note:** Images commands require remote mode (`--remote` + `--token`, or configured remotes). | |
| 17 | 18 | ||
| @@ -81,6 +82,34 @@ Flags: | |||
| 81 | 82 | - `--keep-releases` counts non-`latest` tags, ordered by most recent first. | |
| 82 | 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 | 113 | ## Examples | |
| 85 | 114 | ||
| 86 | 115 | ```bash | |
| @@ -90,6 +119,9 @@ gordon images list | |||
| 90 | 119 | # Prune everything with defaults (dangling + registry, keep latest + 3) | |
| 91 | 120 | gordon images prune | |
| 92 | 121 | ||
| 122 | + | # List tags for a repository | |
| 123 | + | gordon images tags myapp | |
| 124 | + | ||
| 93 | 125 | # Prune dangling runtime images only | |
| 94 | 126 | gordon images prune --dangling | |
| 95 | 127 | ||
| @@ -107,6 +139,7 @@ gordon images prune --no-confirm | |||
| 107 | 139 | ||
| 108 | 140 | - `list` requires `admin:status:read`. | |
| 109 | 141 | - `prune` requires `admin:config:write`. | |
| 142 | + | - `tags` requires `admin:status:read`. | |
| 110 | 143 | ||
| 111 | 144 | ## Related | |
| 112 | 145 | ||
| @@ -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 | ## Examples | 113 | ## Examples | ||
| 85 | 114 | ||||
| 86 | ```bash | 115 | ```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 prune | 120 | gordon images prune | ||
| 92 | 121 | ||||
| 122 | + | # List tags for a repository | |||
| 123 | + | gordon images tags myapp | |||
| 124 | + | ||||
| 93 | # Prune dangling runtime images only | 125 | # Prune dangling runtime images only | ||
| 94 | gordon images prune --dangling | 126 | 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 | ## Related | 144 | ## 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.`, | |||
| 76 | 76 | ||
| 77 | 77 | cmd.AddCommand(newImagesListCmd()) | |
| 78 | 78 | cmd.AddCommand(newImagesPruneCmd()) | |
| 79 | + | cmd.AddCommand(newImagesTagsCmd()) | |
| 79 | 80 | ||
| 80 | 81 | return cmd | |
| 81 | 82 | } | |
| @@ -131,6 +132,72 @@ release tags per repository. Use --dangling or --registry to restrict scope.`, | |||
| 131 | 132 | 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 | 201 | // resolvePruneScopes determines which prune subsystems are active. | |
| 135 | 202 | // No scope flags → both true. Any scope flag present → only selected scopes. | |
| 136 | 203 | 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 cmd | 81 | return cmd | ||
| 81 | } | 82 | } | ||
| @@ -131,6 +132,72 @@ release tags per repository. Use --dangling or --registry to restrict scope.`, | |||||
| 131 | return cmd | 132 | 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) { | ||