detached ed1cab5

Merge pull request #40 from bnema/fix/routes-add-positional-image

cli(routes): allow image as positional argument instead of --image flag
commit
ed1cab56141b460b65e2db48dc40471c2c74b3af
author
bnema · · 2026-01-28 21:07 UTC
committer
GitHub · · 2026-01-28 21:07 UTC
merge of
339400c + af6e90a · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
2 files · +22 -10
M internal/adapters/in/cli/routes.go +21 -9
@@ -250,21 +250,34 @@ func newRoutesAddCmd() *cobra.Command {
250250 var image string
251251
252252 cmd := &cobra.Command{
253- Use: "add <domain>",
253+ Use: "add <domain> <image>",
254254 Short: "Add a new route",
255255 Long: `Add a new route mapping a domain to a container image.
256256
257257 Examples:
258- gordon routes add app.mydomain.com --image myapp:latest
259- gordon --remote https://gordon.mydomain.com routes add api.mydomain.com --image api:v2`,
260- Args: cobra.ExactArgs(1),
258+ gordon routes add app.mydomain.com myapp:latest
259+ gordon --remote https://gordon.mydomain.com routes add api.mydomain.com api:v2`,
260+ Args: cobra.RangeArgs(1, 2),
261261 RunE: func(cmd *cobra.Command, args []string) error {
262262 ctx := context.Background()
263263 routeDomain := args[0]
264264
265- if image == "" {
266- fmt.Println(styles.RenderError("--image flag is required"))
267- return nil
265+ // Resolve image from positional argument or flag
266+ hasPositionalImage := len(args) > 1
267+ hasFlagImage := image != ""
268+
269+ // Check for conflicts: both positional and flag provided
270+ if hasPositionalImage && hasFlagImage {
271+ return fmt.Errorf("error: cannot use both positional image argument and --image flag")
272+ }
273+
274+ // Check that image is provided via either method
275+ if !hasPositionalImage && !hasFlagImage {
276+ return fmt.Errorf("error: image is required (use positional argument or --image flag)")
277+ }
278+
279+ if hasPositionalImage {
280+ image = args[1]
268281 }
269282
270283 route := domain.Route{
@@ -292,8 +305,7 @@ Examples:
292305 },
293306 }
294307
295- cmd.Flags().StringVarP(&image, "image", "i", "", "Container image (required)")
296- _ = cmd.MarkFlagRequired("image")
308+ cmd.Flags().StringVarP(&image, "image", "i", "", "Container image")
297309
298310 return cmd
299311 }
@@ -250,21 +250,34 @@ func newRoutesAddCmd() *cobra.Command {
250 var image string250 var image string
251 251
252 cmd := &cobra.Command{252 cmd := &cobra.Command{
253- Use: "add <domain>",253+ Use: "add <domain> <image>",
254 Short: "Add a new route",254 Short: "Add a new route",
255 Long: `Add a new route mapping a domain to a container image.255 Long: `Add a new route mapping a domain to a container image.
256 256
257 Examples:257 Examples:
258- gordon routes add app.mydomain.com --image myapp:latest258+ gordon routes add app.mydomain.com myapp:latest
259- gordon --remote https://gordon.mydomain.com routes add api.mydomain.com --image api:v2`,259+ gordon --remote https://gordon.mydomain.com routes add api.mydomain.com api:v2`,
260- Args: cobra.ExactArgs(1),260+ Args: cobra.RangeArgs(1, 2),
261 RunE: func(cmd *cobra.Command, args []string) error {261 RunE: func(cmd *cobra.Command, args []string) error {
262 ctx := context.Background()262 ctx := context.Background()
263 routeDomain := args[0]263 routeDomain := args[0]
264 264
265- if image == "" {265+ // Resolve image from positional argument or flag
266- fmt.Println(styles.RenderError("--image flag is required"))266+ hasPositionalImage := len(args) > 1
267- return nil267+ hasFlagImage := image != ""
268+
269+ // Check for conflicts: both positional and flag provided
270+ if hasPositionalImage && hasFlagImage {
271+ return fmt.Errorf("error: cannot use both positional image argument and --image flag")
272+ }
273+
274+ // Check that image is provided via either method
275+ if !hasPositionalImage && !hasFlagImage {
276+ return fmt.Errorf("error: image is required (use positional argument or --image flag)")
277+ }
278+
279+ if hasPositionalImage {
280+ image = args[1]
268 }281 }
269 282
270 route := domain.Route{283 route := domain.Route{
@@ -292,8 +305,7 @@ Examples:
292 },305 },
293 }306 }
294 307
295- cmd.Flags().StringVarP(&image, "image", "i", "", "Container image (required)")308+ cmd.Flags().StringVarP(&image, "image", "i", "", "Container image")
296- _ = cmd.MarkFlagRequired("image")
297 309
298 return cmd310 return cmd
299 }311 }
M internal/adapters/in/http/admin/handler.go +1 -1
@@ -279,7 +279,7 @@ func (h *Handler) handleRoutesPost(w http.ResponseWriter, r *http.Request) {
279279 }
280280
281281 // Validate image exists in registry before adding route
282- if h.registrySvc != nil {
282+ if h.registrySvc != nil && route.Image != "" {
283283 imageName, imageRef := validation.ParseImageReference(route.Image)
284284 if _, err := h.registrySvc.GetManifest(ctx, imageName, imageRef); err != nil {
285285 if errors.Is(err, domain.ErrManifestNotFound) {
@@ -279,7 +279,7 @@ func (h *Handler) handleRoutesPost(w http.ResponseWriter, r *http.Request) {
279 }279 }
280 280
281 // Validate image exists in registry before adding route281 // Validate image exists in registry before adding route
282- if h.registrySvc != nil {282+ if h.registrySvc != nil && route.Image != "" {
283 imageName, imageRef := validation.ParseImageReference(route.Image)283 imageName, imageRef := validation.ParseImageReference(route.Image)
284 if _, err := h.registrySvc.GetManifest(ctx, imageName, imageRef); err != nil {284 if _, err := h.registrySvc.GetManifest(ctx, imageName, imageRef); err != nil {
285 if errors.Is(err, domain.ErrManifestNotFound) {285 if errors.Is(err, domain.ErrManifestNotFound) {

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