detached 3eb7cb6

cli: apply follow-up review fixes

commit
3eb7cb6f3b8fce2a342ca9bc0d7b4ae2679b2d79
author
brice · · 2026-05-15 09:40 UTC
parent
1e6e9b3
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +107 -61
M internal/adapters/in/cli/attachments.go +10 -10
@@ -52,7 +52,7 @@ Examples:
5252 gordon attachments list backend # List attachments for network group`,
5353 Args: cobra.MaximumNArgs(1),
5454 RunE: func(cmd *cobra.Command, args []string) error {
55- ctx := context.Background()
55+ ctx := cmd.Context()
5656 target := ""
5757 if len(args) > 0 {
5858 target = args[0]
@@ -110,14 +110,14 @@ func runAttachmentsList(ctx context.Context, cp ControlPlane, target string, jso
110110 }
111111
112112 func renderAttachmentTargetList(out io.Writer, title string, images []string) error {
113- if _, err := fmt.Fprintln(out, styles.Theme.Title.Render(title)); err != nil {
113+ if err := cliWriteLine(out, cliRenderTitle(title)); err != nil {
114114 return err
115115 }
116- if _, err := fmt.Fprintln(out); err != nil {
116+ if err := cliWriteLine(out, ""); err != nil {
117117 return err
118118 }
119119 for _, img := range images {
120- if _, err := fmt.Fprintf(out, " %s\n", img); err != nil {
120+ if err := cliWritef(out, " %s\n", img); err != nil {
121121 return err
122122 }
123123 }
@@ -131,19 +131,19 @@ func renderAllAttachmentsList(out io.Writer, title string, attachments map[strin
131131 }
132132 sort.Strings(targets)
133133
134- if _, err := fmt.Fprintln(out, styles.Theme.Title.Render(title)); err != nil {
134+ if err := cliWriteLine(out, cliRenderTitle(title)); err != nil {
135135 return err
136136 }
137- if _, err := fmt.Fprintln(out); err != nil {
137+ if err := cliWriteLine(out, ""); err != nil {
138138 return err
139139 }
140140 for _, t := range targets {
141141 images := attachments[t]
142- if _, err := fmt.Fprintf(out, "%s\n", styles.Theme.Bold.Render(t)); err != nil {
142+ if err := cliWritef(out, "%s\n", styles.Theme.Bold.Render(t)); err != nil {
143143 return err
144144 }
145145 for _, img := range images {
146- if _, err := fmt.Fprintf(out, " %s\n", img); err != nil {
146+ if err := cliWritef(out, " %s\n", img); err != nil {
147147 return err
148148 }
149149 }
@@ -295,7 +295,7 @@ Examples:
295295 gordon --remote https://gordon.mydomain.com attachments add api.mydomain.com memcached:latest`,
296296 Args: cobra.ExactArgs(2),
297297 RunE: func(cmd *cobra.Command, args []string) error {
298- ctx := context.Background()
298+ ctx := cmd.Context()
299299 target := args[0]
300300 image := args[1]
301301
@@ -347,7 +347,7 @@ Examples:
347347 gordon attachments remove backend redis:7-alpine --force`,
348348 Args: cobra.ExactArgs(2),
349349 RunE: func(cmd *cobra.Command, args []string) error {
350- ctx := context.Background()
350+ ctx := cmd.Context()
351351 target := args[0]
352352 image := args[1]
353353
@@ -52,7 +52,7 @@ Examples:
52 gordon attachments list backend # List attachments for network group`,52 gordon attachments list backend # List attachments for network group`,
53 Args: cobra.MaximumNArgs(1),53 Args: cobra.MaximumNArgs(1),
54 RunE: func(cmd *cobra.Command, args []string) error {54 RunE: func(cmd *cobra.Command, args []string) error {
55- ctx := context.Background()55+ ctx := cmd.Context()
56 target := ""56 target := ""
57 if len(args) > 0 {57 if len(args) > 0 {
58 target = args[0]58 target = args[0]
@@ -110,14 +110,14 @@ func runAttachmentsList(ctx context.Context, cp ControlPlane, target string, jso
110 }110 }
111 111
112 func renderAttachmentTargetList(out io.Writer, title string, images []string) error {112 func renderAttachmentTargetList(out io.Writer, title string, images []string) error {
113- if _, err := fmt.Fprintln(out, styles.Theme.Title.Render(title)); err != nil {113+ if err := cliWriteLine(out, cliRenderTitle(title)); err != nil {
114 return err114 return err
115 }115 }
116- if _, err := fmt.Fprintln(out); err != nil {116+ if err := cliWriteLine(out, ""); err != nil {
117 return err117 return err
118 }118 }
119 for _, img := range images {119 for _, img := range images {
120- if _, err := fmt.Fprintf(out, " %s\n", img); err != nil {120+ if err := cliWritef(out, " %s\n", img); err != nil {
121 return err121 return err
122 }122 }
123 }123 }
@@ -131,19 +131,19 @@ func renderAllAttachmentsList(out io.Writer, title string, attachments map[strin
131 }131 }
132 sort.Strings(targets)132 sort.Strings(targets)
133 133
134- if _, err := fmt.Fprintln(out, styles.Theme.Title.Render(title)); err != nil {134+ if err := cliWriteLine(out, cliRenderTitle(title)); err != nil {
135 return err135 return err
136 }136 }
137- if _, err := fmt.Fprintln(out); err != nil {137+ if err := cliWriteLine(out, ""); err != nil {
138 return err138 return err
139 }139 }
140 for _, t := range targets {140 for _, t := range targets {
141 images := attachments[t]141 images := attachments[t]
142- if _, err := fmt.Fprintf(out, "%s\n", styles.Theme.Bold.Render(t)); err != nil {142+ if err := cliWritef(out, "%s\n", styles.Theme.Bold.Render(t)); err != nil {
143 return err143 return err
144 }144 }
145 for _, img := range images {145 for _, img := range images {
146- if _, err := fmt.Fprintf(out, " %s\n", img); err != nil {146+ if err := cliWritef(out, " %s\n", img); err != nil {
147 return err147 return err
148 }148 }
149 }149 }
@@ -295,7 +295,7 @@ Examples:
295 gordon --remote https://gordon.mydomain.com attachments add api.mydomain.com memcached:latest`,295 gordon --remote https://gordon.mydomain.com attachments add api.mydomain.com memcached:latest`,
296 Args: cobra.ExactArgs(2),296 Args: cobra.ExactArgs(2),
297 RunE: func(cmd *cobra.Command, args []string) error {297 RunE: func(cmd *cobra.Command, args []string) error {
298- ctx := context.Background()298+ ctx := cmd.Context()
299 target := args[0]299 target := args[0]
300 image := args[1]300 image := args[1]
301 301
@@ -347,7 +347,7 @@ Examples:
347 gordon attachments remove backend redis:7-alpine --force`,347 gordon attachments remove backend redis:7-alpine --force`,
348 Args: cobra.ExactArgs(2),348 Args: cobra.ExactArgs(2),
349 RunE: func(cmd *cobra.Command, args []string) error {349 RunE: func(cmd *cobra.Command, args []string) error {
350- ctx := context.Background()350+ ctx := cmd.Context()
351 target := args[0]351 target := args[0]
352 image := args[1]352 image := args[1]
353 353
M internal/adapters/in/cli/push.go +14 -6
@@ -227,7 +227,7 @@ func resolveVersion(ctx context.Context, tag string) (string, error) {
227227 }
228228
229229 func runPush(ctx context.Context, out io.Writer, req pushRequest) error {
230- dockerfile, inferredRemote, handle, err := resolvePushTarget(ctx, req)
230+ dockerfile, inferredRemote, handle, err := resolvePushTarget(ctx, out, req)
231231 if err != nil {
232232 return err
233233 }
@@ -254,11 +254,17 @@ func runPush(ctx context.Context, out io.Writer, req pushRequest) error {
254254 return err
255255 }
256256
257- fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(img.VersionRef))
257+ if err := cliWritef(out, "Image: %s\n", styles.Theme.Bold.Render(img.VersionRef)); err != nil {
258+ return err
259+ }
258260 if version != "latest" {
259- fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(img.LatestRef))
261+ if err := cliWritef(out, "Also: %s\n", styles.Theme.Bold.Render(img.LatestRef)); err != nil {
262+ return err
263+ }
264+ }
265+ if err := cliWritef(out, "Domain: %s\n", styles.Theme.Bold.Render(pushDomain)); err != nil {
266+ return err
260267 }
261- fmt.Printf("Domain: %s\n", styles.Theme.Bold.Render(pushDomain))
262268
263269 skipExplicitDeploy := shouldSkipDeploy(ctx, handle.plane, imageName, req.NoDeploy)
264270 build := buildConfig{Enabled: req.Build.Enabled, Platform: req.Build.Platform, Dockerfile: dockerfile, BuildArgs: req.Build.BuildArgs}
@@ -275,7 +281,7 @@ func runPush(ctx context.Context, out io.Writer, req pushRequest) error {
275281 return nil
276282 }
277283
278-func resolvePushTarget(ctx context.Context, req pushRequest) (dockerfile string, inferredRemote *remote.ResolvedRemote, handle *controlPlaneHandle, err error) {
284+func resolvePushTarget(ctx context.Context, out io.Writer, req pushRequest) (dockerfile string, inferredRemote *remote.ResolvedRemote, handle *controlPlaneHandle, err error) {
279285 dockerfile, err = resolveDockerfile(req.Build.Dockerfile, req.Build.Enabled)
280286 if err != nil {
281287 return "", nil, nil, err
@@ -286,7 +292,9 @@ func resolvePushTarget(ctx context.Context, req pushRequest) (dockerfile string,
286292 }
287293 if inferredRemote != nil {
288294 handle = newRemoteControlPlaneHandle(inferredRemote)
289- fmt.Printf("Remote: %s %s\n", styles.Theme.Bold.Render(inferredRemote.DisplayName()), styles.Theme.Muted.Render("(auto-detected)"))
295+ if err := cliWritef(out, "Remote: %s %s\n", styles.Theme.Bold.Render(inferredRemote.DisplayName()), styles.Theme.Muted.Render("(auto-detected)")); err != nil {
296+ return "", nil, nil, err
297+ }
290298 return dockerfile, inferredRemote, handle, nil
291299 }
292300 handle, err = resolveControlPlane(configPath)
@@ -227,7 +227,7 @@ func resolveVersion(ctx context.Context, tag string) (string, error) {
227 }227 }
228 228
229 func runPush(ctx context.Context, out io.Writer, req pushRequest) error {229 func runPush(ctx context.Context, out io.Writer, req pushRequest) error {
230- dockerfile, inferredRemote, handle, err := resolvePushTarget(ctx, req)230+ dockerfile, inferredRemote, handle, err := resolvePushTarget(ctx, out, req)
231 if err != nil {231 if err != nil {
232 return err232 return err
233 }233 }
@@ -254,11 +254,17 @@ func runPush(ctx context.Context, out io.Writer, req pushRequest) error {
254 return err254 return err
255 }255 }
256 256
257- fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(img.VersionRef))257+ if err := cliWritef(out, "Image: %s\n", styles.Theme.Bold.Render(img.VersionRef)); err != nil {
258+ return err
259+ }
258 if version != "latest" {260 if version != "latest" {
259- fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(img.LatestRef))261+ if err := cliWritef(out, "Also: %s\n", styles.Theme.Bold.Render(img.LatestRef)); err != nil {
262+ return err
263+ }
264+ }
265+ if err := cliWritef(out, "Domain: %s\n", styles.Theme.Bold.Render(pushDomain)); err != nil {
266+ return err
260 }267 }
261- fmt.Printf("Domain: %s\n", styles.Theme.Bold.Render(pushDomain))
262 268
263 skipExplicitDeploy := shouldSkipDeploy(ctx, handle.plane, imageName, req.NoDeploy)269 skipExplicitDeploy := shouldSkipDeploy(ctx, handle.plane, imageName, req.NoDeploy)
264 build := buildConfig{Enabled: req.Build.Enabled, Platform: req.Build.Platform, Dockerfile: dockerfile, BuildArgs: req.Build.BuildArgs}270 build := buildConfig{Enabled: req.Build.Enabled, Platform: req.Build.Platform, Dockerfile: dockerfile, BuildArgs: req.Build.BuildArgs}
@@ -275,7 +281,7 @@ func runPush(ctx context.Context, out io.Writer, req pushRequest) error {
275 return nil281 return nil
276 }282 }
277 283
278-func resolvePushTarget(ctx context.Context, req pushRequest) (dockerfile string, inferredRemote *remote.ResolvedRemote, handle *controlPlaneHandle, err error) {284+func resolvePushTarget(ctx context.Context, out io.Writer, req pushRequest) (dockerfile string, inferredRemote *remote.ResolvedRemote, handle *controlPlaneHandle, err error) {
279 dockerfile, err = resolveDockerfile(req.Build.Dockerfile, req.Build.Enabled)285 dockerfile, err = resolveDockerfile(req.Build.Dockerfile, req.Build.Enabled)
280 if err != nil {286 if err != nil {
281 return "", nil, nil, err287 return "", nil, nil, err
@@ -286,7 +292,9 @@ func resolvePushTarget(ctx context.Context, req pushRequest) (dockerfile string,
286 }292 }
287 if inferredRemote != nil {293 if inferredRemote != nil {
288 handle = newRemoteControlPlaneHandle(inferredRemote)294 handle = newRemoteControlPlaneHandle(inferredRemote)
289- fmt.Printf("Remote: %s %s\n", styles.Theme.Bold.Render(inferredRemote.DisplayName()), styles.Theme.Muted.Render("(auto-detected)"))295+ if err := cliWritef(out, "Remote: %s %s\n", styles.Theme.Bold.Render(inferredRemote.DisplayName()), styles.Theme.Muted.Render("(auto-detected)")); err != nil {
296+ return "", nil, nil, err
297+ }
290 return dockerfile, inferredRemote, handle, nil298 return dockerfile, inferredRemote, handle, nil
291 }299 }
292 handle, err = resolveControlPlane(configPath)300 handle, err = resolveControlPlane(configPath)
M internal/adapters/in/cli/push_remote_inference_test.go +57 -20
@@ -11,9 +11,10 @@ import (
1111 "strings"
1212 "testing"
1313
14- "github.com/bnema/gordon/internal/domain"
1514 "github.com/stretchr/testify/assert"
1615 "github.com/stretchr/testify/require"
16+
17+ "github.com/bnema/gordon/internal/domain"
1718 )
1819
1920 func TestInferPushRemote_UsesUniqueMatchingSavedRemote(t *testing.T) {
@@ -305,21 +306,27 @@ func newFindRoutesByImageTestServer(t *testing.T, handler func(image string) ([]
305306 }
306307
307308 image, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/routes/by-image/"))
308- require.NoError(t, err)
309+ if !assert.NoError(t, err) {
310+ return
311+ }
309312
310313 routes, status := handler(image)
311314 if status >= http.StatusBadRequest {
312315 w.Header().Set("Content-Type", "application/json")
313316 w.WriteHeader(status)
314- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
317+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
318+ return
319+ }
315320 return
316321 }
317322
318323 w.Header().Set("Content-Type", "application/json")
319- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{
324+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{
320325 "image": image,
321326 "routes": routes,
322- }))
327+ })) {
328+ return
329+ }
323330 }))
324331 t.Cleanup(server.Close)
325332 return server
@@ -375,20 +382,28 @@ func handleGetRouteProbe(t *testing.T, w http.ResponseWriter, r *http.Request, h
375382 return
376383 }
377384 domainName, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/routes/"))
378- require.NoError(t, err)
385+ if !assert.NoError(t, err) {
386+ return
387+ }
379388 route, status := handler(domainName)
380389 if status >= http.StatusBadRequest {
381390 w.Header().Set("Content-Type", "application/json")
382391 w.WriteHeader(status)
383392 if status == http.StatusNotFound {
384- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": domain.ErrRouteNotFound.Error()}))
393+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": domain.ErrRouteNotFound.Error()})) {
394+ return
395+ }
396+ return
397+ }
398+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
385399 return
386400 }
387- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
388401 return
389402 }
390403 w.Header().Set("Content-Type", "application/json")
391- require.NoError(t, json.NewEncoder(w).Encode(route))
404+ if !assert.NoError(t, json.NewEncoder(w).Encode(route)) {
405+ return
406+ }
392407 }
393408
394409 func handleAttachmentTargetsByImageProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(image string) ([]string, int)) {
@@ -398,16 +413,22 @@ func handleAttachmentTargetsByImageProbe(t *testing.T, w http.ResponseWriter, r
398413 return
399414 }
400415 image, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/attachments/by-image/"))
401- require.NoError(t, err)
416+ if !assert.NoError(t, err) {
417+ return
418+ }
402419 targets, status := handler(image)
403420 if status >= http.StatusBadRequest {
404421 w.Header().Set("Content-Type", "application/json")
405422 w.WriteHeader(status)
406- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
423+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
424+ return
425+ }
407426 return
408427 }
409428 w.Header().Set("Content-Type", "application/json")
410- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"image": image, "targets": targets}))
429+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"image": image, "targets": targets})) {
430+ return
431+ }
411432 }
412433
413434 func handleGetAttachmentsConfigProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(target string) ([]string, int)) {
@@ -417,20 +438,28 @@ func handleGetAttachmentsConfigProbe(t *testing.T, w http.ResponseWriter, r *htt
417438 return
418439 }
419440 target, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/attachments/"))
420- require.NoError(t, err)
441+ if !assert.NoError(t, err) {
442+ return
443+ }
421444 images, status := handler(target)
422445 if status >= http.StatusBadRequest {
423446 w.Header().Set("Content-Type", "application/json")
424447 w.WriteHeader(status)
425448 if status == http.StatusNotFound {
426- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "no attachments found for target"}))
449+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "no attachments found for target"})) {
450+ return
451+ }
452+ return
453+ }
454+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
427455 return
428456 }
429- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
430457 return
431458 }
432459 w.Header().Set("Content-Type", "application/json")
433- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"target": target, "images": images}))
460+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"target": target, "images": images})) {
461+ return
462+ }
434463 }
435464
436465 func handleListTagsProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(repository string) ([]string, int)) {
@@ -440,18 +469,26 @@ func handleListTagsProbe(t *testing.T, w http.ResponseWriter, r *http.Request, h
440469 return
441470 }
442471 repository, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/tags/"))
443- require.NoError(t, err)
472+ if !assert.NoError(t, err) {
473+ return
474+ }
444475 tags, status := handler(repository)
445476 if status >= http.StatusBadRequest {
446477 w.Header().Set("Content-Type", "application/json")
447478 w.WriteHeader(status)
448479 if status == http.StatusNotFound {
449- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "repository not found"}))
480+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "repository not found"})) {
481+ return
482+ }
483+ return
484+ }
485+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
450486 return
451487 }
452- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
453488 return
454489 }
455490 w.Header().Set("Content-Type", "application/json")
456- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"repository": repository, "tags": tags}))
491+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"repository": repository, "tags": tags})) {
492+ return
493+ }
457494 }
@@ -11,9 +11,10 @@ import (
11 "strings"11 "strings"
12 "testing"12 "testing"
13 13
14- "github.com/bnema/gordon/internal/domain"
15 "github.com/stretchr/testify/assert"14 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"15 "github.com/stretchr/testify/require"
16+
17+ "github.com/bnema/gordon/internal/domain"
17 )18 )
18 19
19 func TestInferPushRemote_UsesUniqueMatchingSavedRemote(t *testing.T) {20 func TestInferPushRemote_UsesUniqueMatchingSavedRemote(t *testing.T) {
@@ -305,21 +306,27 @@ func newFindRoutesByImageTestServer(t *testing.T, handler func(image string) ([]
305 }306 }
306 307
307 image, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/routes/by-image/"))308 image, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/routes/by-image/"))
308- require.NoError(t, err)309+ if !assert.NoError(t, err) {
310+ return
311+ }
309 312
310 routes, status := handler(image)313 routes, status := handler(image)
311 if status >= http.StatusBadRequest {314 if status >= http.StatusBadRequest {
312 w.Header().Set("Content-Type", "application/json")315 w.Header().Set("Content-Type", "application/json")
313 w.WriteHeader(status)316 w.WriteHeader(status)
314- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))317+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
318+ return
319+ }
315 return320 return
316 }321 }
317 322
318 w.Header().Set("Content-Type", "application/json")323 w.Header().Set("Content-Type", "application/json")
319- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{324+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{
320 "image": image,325 "image": image,
321 "routes": routes,326 "routes": routes,
322- }))327+ })) {
328+ return
329+ }
323 }))330 }))
324 t.Cleanup(server.Close)331 t.Cleanup(server.Close)
325 return server332 return server
@@ -375,20 +382,28 @@ func handleGetRouteProbe(t *testing.T, w http.ResponseWriter, r *http.Request, h
375 return382 return
376 }383 }
377 domainName, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/routes/"))384 domainName, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/routes/"))
378- require.NoError(t, err)385+ if !assert.NoError(t, err) {
386+ return
387+ }
379 route, status := handler(domainName)388 route, status := handler(domainName)
380 if status >= http.StatusBadRequest {389 if status >= http.StatusBadRequest {
381 w.Header().Set("Content-Type", "application/json")390 w.Header().Set("Content-Type", "application/json")
382 w.WriteHeader(status)391 w.WriteHeader(status)
383 if status == http.StatusNotFound {392 if status == http.StatusNotFound {
384- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": domain.ErrRouteNotFound.Error()}))393+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": domain.ErrRouteNotFound.Error()})) {
394+ return
395+ }
396+ return
397+ }
398+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
385 return399 return
386 }400 }
387- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
388 return401 return
389 }402 }
390 w.Header().Set("Content-Type", "application/json")403 w.Header().Set("Content-Type", "application/json")
391- require.NoError(t, json.NewEncoder(w).Encode(route))404+ if !assert.NoError(t, json.NewEncoder(w).Encode(route)) {
405+ return
406+ }
392 }407 }
393 408
394 func handleAttachmentTargetsByImageProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(image string) ([]string, int)) {409 func handleAttachmentTargetsByImageProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(image string) ([]string, int)) {
@@ -398,16 +413,22 @@ func handleAttachmentTargetsByImageProbe(t *testing.T, w http.ResponseWriter, r
398 return413 return
399 }414 }
400 image, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/attachments/by-image/"))415 image, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/attachments/by-image/"))
401- require.NoError(t, err)416+ if !assert.NoError(t, err) {
417+ return
418+ }
402 targets, status := handler(image)419 targets, status := handler(image)
403 if status >= http.StatusBadRequest {420 if status >= http.StatusBadRequest {
404 w.Header().Set("Content-Type", "application/json")421 w.Header().Set("Content-Type", "application/json")
405 w.WriteHeader(status)422 w.WriteHeader(status)
406- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))423+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
424+ return
425+ }
407 return426 return
408 }427 }
409 w.Header().Set("Content-Type", "application/json")428 w.Header().Set("Content-Type", "application/json")
410- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"image": image, "targets": targets}))429+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"image": image, "targets": targets})) {
430+ return
431+ }
411 }432 }
412 433
413 func handleGetAttachmentsConfigProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(target string) ([]string, int)) {434 func handleGetAttachmentsConfigProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(target string) ([]string, int)) {
@@ -417,20 +438,28 @@ func handleGetAttachmentsConfigProbe(t *testing.T, w http.ResponseWriter, r *htt
417 return438 return
418 }439 }
419 target, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/attachments/"))440 target, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/attachments/"))
420- require.NoError(t, err)441+ if !assert.NoError(t, err) {
442+ return
443+ }
421 images, status := handler(target)444 images, status := handler(target)
422 if status >= http.StatusBadRequest {445 if status >= http.StatusBadRequest {
423 w.Header().Set("Content-Type", "application/json")446 w.Header().Set("Content-Type", "application/json")
424 w.WriteHeader(status)447 w.WriteHeader(status)
425 if status == http.StatusNotFound {448 if status == http.StatusNotFound {
426- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "no attachments found for target"}))449+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "no attachments found for target"})) {
450+ return
451+ }
452+ return
453+ }
454+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
427 return455 return
428 }456 }
429- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
430 return457 return
431 }458 }
432 w.Header().Set("Content-Type", "application/json")459 w.Header().Set("Content-Type", "application/json")
433- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"target": target, "images": images}))460+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"target": target, "images": images})) {
461+ return
462+ }
434 }463 }
435 464
436 func handleListTagsProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(repository string) ([]string, int)) {465 func handleListTagsProbe(t *testing.T, w http.ResponseWriter, r *http.Request, handler func(repository string) ([]string, int)) {
@@ -440,18 +469,26 @@ func handleListTagsProbe(t *testing.T, w http.ResponseWriter, r *http.Request, h
440 return469 return
441 }470 }
442 repository, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/tags/"))471 repository, err := url.PathUnescape(strings.TrimPrefix(r.URL.Path, "/admin/tags/"))
443- require.NoError(t, err)472+ if !assert.NoError(t, err) {
473+ return
474+ }
444 tags, status := handler(repository)475 tags, status := handler(repository)
445 if status >= http.StatusBadRequest {476 if status >= http.StatusBadRequest {
446 w.Header().Set("Content-Type", "application/json")477 w.Header().Set("Content-Type", "application/json")
447 w.WriteHeader(status)478 w.WriteHeader(status)
448 if status == http.StatusNotFound {479 if status == http.StatusNotFound {
449- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "repository not found"}))480+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "repository not found"})) {
481+ return
482+ }
483+ return
484+ }
485+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"})) {
450 return486 return
451 }487 }
452- require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"error": "boom"}))
453 return488 return
454 }489 }
455 w.Header().Set("Content-Type", "application/json")490 w.Header().Set("Content-Type", "application/json")
456- require.NoError(t, json.NewEncoder(w).Encode(map[string]any{"repository": repository, "tags": tags}))491+ if !assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"repository": repository, "tags": tags})) {
492+ return
493+ }
457 }494 }
M internal/adapters/in/cli/root.go +25 -24
@@ -5,6 +5,7 @@ package cli
55 import (
66 "context"
77 "fmt"
8+ "io"
89 "os"
910 "os/signal"
1011 "syscall"
@@ -298,7 +299,7 @@ Remote mode:
298299 if len(args) > 0 {
299300 logDomain = args[0]
300301 }
301- return runLogs(cmd.Context(), logsConfigPath, logDomain, follow, lines)
302+ return runLogs(cmd.Context(), logsConfigPath, logDomain, follow, lines, cmd.OutOrStdout())
302303 },
303304 }
304305
@@ -310,30 +311,30 @@ Remote mode:
310311 }
311312
312313 // runLogs handles the logs command logic.
313-func runLogs(ctx context.Context, logsConfigPath, logDomain string, follow bool, lines int) error {
314+func runLogs(ctx context.Context, logsConfigPath, logDomain string, follow bool, lines int, out io.Writer) error {
314315 if logDomain != "" {
315316 handle, err := resolveControlPlaneForRouteDomain(ctx, logDomain)
316317 if err != nil {
317318 return err
318319 }
319320 defer handle.close()
320- return runContainerLogs(ctx, handle.plane, logDomain, follow, lines)
321+ return runContainerLogs(ctx, handle.plane, logDomain, follow, lines, out)
321322 }
322323
323324 client, isRemote := GetRemoteClient()
324325 if isRemote {
325- return runLogsRemote(client, logDomain, follow, lines)
326+ return runLogsRemote(ctx, client, logDomain, follow, lines, out)
326327 }
327- return runLogsLocal(logsConfigPath, logDomain, follow, lines)
328+ return runLogsLocal(logsConfigPath, logDomain, follow, lines, out)
328329 }
329330
330331 // runLogsRemote fetches logs from a remote Gordon instance.
331-func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines int) error {
332- ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
332+func runLogsRemote(ctx context.Context, client *remote.Client, logDomain string, follow bool, lines int, out io.Writer) error {
333+ ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
333334 defer cancel()
334335
335336 if follow {
336- return streamLogsRemote(ctx, client, logDomain, lines)
337+ return streamLogsRemote(ctx, client, logDomain, lines, out)
337338 }
338339
339340 if logDomain == "" {
@@ -343,7 +344,7 @@ func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines i
343344 return fmt.Errorf("failed to get process logs: %w", err)
344345 }
345346 for _, line := range logLines {
346- if err := cliWriteLine(os.Stdout, line); err != nil {
347+ if err := cliWriteLine(out, line); err != nil {
347348 return err
348349 }
349350 }
@@ -354,7 +355,7 @@ func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines i
354355 return fmt.Errorf("failed to get container logs: %w", err)
355356 }
356357 for _, line := range logLines {
357- if err := cliWriteLine(os.Stdout, line); err != nil {
358+ if err := cliWriteLine(out, line); err != nil {
358359 return err
359360 }
360361 }
@@ -363,7 +364,7 @@ func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines i
363364 }
364365
365366 // streamLogsRemote streams logs from a remote Gordon instance.
366-func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain string, lines int) error {
367+func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain string, lines int, out io.Writer) error {
367368 var ch <-chan string
368369 var err error
369370
@@ -377,7 +378,7 @@ func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain stri
377378 }
378379
379380 for line := range ch {
380- if err := cliWriteLine(os.Stdout, line); err != nil {
381+ if err := cliWriteLine(out, line); err != nil {
381382 return err
382383 }
383384 }
@@ -385,16 +386,16 @@ func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain stri
385386 }
386387
387388 // runLogsLocal shows logs from a local Gordon instance.
388-func runLogsLocal(logsConfigPath, logDomain string, follow bool, lines int) error {
389+func runLogsLocal(logsConfigPath, logDomain string, follow bool, lines int, out io.Writer) error {
389390 if logDomain == "" {
390391 // Process logs - use existing app.ShowLogs
391392 return app.ShowLogs(logsConfigPath, follow, lines)
392393 }
393394
394- return showContainerLogsLocal(logsConfigPath, logDomain, follow, lines)
395+ return showContainerLogsLocal(out, logsConfigPath, logDomain, follow, lines)
395396 }
396397
397-func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, follow bool, lines int) error {
398+func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, follow bool, lines int, out io.Writer) error {
398399 ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
399400 defer cancel()
400401
@@ -404,7 +405,7 @@ func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, fo
404405 return fmt.Errorf("failed to stream container logs: %w", err)
405406 }
406407 for line := range ch {
407- if err := cliWriteLine(os.Stdout, line); err != nil {
408+ if err := cliWriteLine(out, line); err != nil {
408409 return err
409410 }
410411 }
@@ -416,7 +417,7 @@ func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, fo
416417 return fmt.Errorf("failed to get container logs: %w", err)
417418 }
418419 for _, line := range logLines {
419- if err := cliWriteLine(os.Stdout, line); err != nil {
420+ if err := cliWriteLine(out, line); err != nil {
420421 return err
421422 }
422423 }
@@ -424,25 +425,25 @@ func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, fo
424425 }
425426
426427 // showContainerLogsLocal is retained for UI adoption coverage and fallback messaging.
427-func showContainerLogsLocal(_ string, logDomain string, follow bool, lines int) error {
428- if err := cliWriteLine(os.Stdout, cliRenderTitle(fmt.Sprintf("Container logs for %s", logDomain))); err != nil {
428+func showContainerLogsLocal(out io.Writer, _ string, logDomain string, follow bool, lines int) error {
429+ if err := cliWriteLine(out, cliRenderTitle(fmt.Sprintf("Container logs for %s", logDomain))); err != nil {
429430 return err
430431 }
431- if err := cliWriteLine(os.Stdout, cliRenderInfo("To view container logs locally, use:")); err != nil {
432+ if err := cliWriteLine(out, cliRenderInfo("To view container logs locally, use:")); err != nil {
432433 return err
433434 }
434- if err := cliWritef(os.Stdout, " docker logs --tail %d %s\n", lines, logDomain); err != nil {
435+ if err := cliWritef(out, " docker logs --tail %d %s\n", lines, logDomain); err != nil {
435436 return err
436437 }
437438 if follow {
438- if err := cliWritef(os.Stdout, " docker logs -f --tail %d %s\n", lines, logDomain); err != nil {
439+ if err := cliWritef(out, " docker logs -f --tail %d %s\n", lines, logDomain); err != nil {
439440 return err
440441 }
441442 }
442- if err := cliWriteLine(os.Stdout, ""); err != nil {
443+ if err := cliWriteLine(out, ""); err != nil {
443444 return err
444445 }
445- if err := cliWriteLine(os.Stdout, cliRenderMuted("Or use remote mode to access logs via the admin API.")); err != nil {
446+ if err := cliWriteLine(out, cliRenderMuted("Or use remote mode to access logs via the admin API.")); err != nil {
446447 return err
447448 }
448449 return nil
@@ -5,6 +5,7 @@ package cli
5 import (5 import (
6 "context"6 "context"
7 "fmt"7 "fmt"
8+ "io"
8 "os"9 "os"
9 "os/signal"10 "os/signal"
10 "syscall"11 "syscall"
@@ -298,7 +299,7 @@ Remote mode:
298 if len(args) > 0 {299 if len(args) > 0 {
299 logDomain = args[0]300 logDomain = args[0]
300 }301 }
301- return runLogs(cmd.Context(), logsConfigPath, logDomain, follow, lines)302+ return runLogs(cmd.Context(), logsConfigPath, logDomain, follow, lines, cmd.OutOrStdout())
302 },303 },
303 }304 }
304 305
@@ -310,30 +311,30 @@ Remote mode:
310 }311 }
311 312
312 // runLogs handles the logs command logic.313 // runLogs handles the logs command logic.
313-func runLogs(ctx context.Context, logsConfigPath, logDomain string, follow bool, lines int) error {314+func runLogs(ctx context.Context, logsConfigPath, logDomain string, follow bool, lines int, out io.Writer) error {
314 if logDomain != "" {315 if logDomain != "" {
315 handle, err := resolveControlPlaneForRouteDomain(ctx, logDomain)316 handle, err := resolveControlPlaneForRouteDomain(ctx, logDomain)
316 if err != nil {317 if err != nil {
317 return err318 return err
318 }319 }
319 defer handle.close()320 defer handle.close()
320- return runContainerLogs(ctx, handle.plane, logDomain, follow, lines)321+ return runContainerLogs(ctx, handle.plane, logDomain, follow, lines, out)
321 }322 }
322 323
323 client, isRemote := GetRemoteClient()324 client, isRemote := GetRemoteClient()
324 if isRemote {325 if isRemote {
325- return runLogsRemote(client, logDomain, follow, lines)326+ return runLogsRemote(ctx, client, logDomain, follow, lines, out)
326 }327 }
327- return runLogsLocal(logsConfigPath, logDomain, follow, lines)328+ return runLogsLocal(logsConfigPath, logDomain, follow, lines, out)
328 }329 }
329 330
330 // runLogsRemote fetches logs from a remote Gordon instance.331 // runLogsRemote fetches logs from a remote Gordon instance.
331-func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines int) error {332+func runLogsRemote(ctx context.Context, client *remote.Client, logDomain string, follow bool, lines int, out io.Writer) error {
332- ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)333+ ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
333 defer cancel()334 defer cancel()
334 335
335 if follow {336 if follow {
336- return streamLogsRemote(ctx, client, logDomain, lines)337+ return streamLogsRemote(ctx, client, logDomain, lines, out)
337 }338 }
338 339
339 if logDomain == "" {340 if logDomain == "" {
@@ -343,7 +344,7 @@ func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines i
343 return fmt.Errorf("failed to get process logs: %w", err)344 return fmt.Errorf("failed to get process logs: %w", err)
344 }345 }
345 for _, line := range logLines {346 for _, line := range logLines {
346- if err := cliWriteLine(os.Stdout, line); err != nil {347+ if err := cliWriteLine(out, line); err != nil {
347 return err348 return err
348 }349 }
349 }350 }
@@ -354,7 +355,7 @@ func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines i
354 return fmt.Errorf("failed to get container logs: %w", err)355 return fmt.Errorf("failed to get container logs: %w", err)
355 }356 }
356 for _, line := range logLines {357 for _, line := range logLines {
357- if err := cliWriteLine(os.Stdout, line); err != nil {358+ if err := cliWriteLine(out, line); err != nil {
358 return err359 return err
359 }360 }
360 }361 }
@@ -363,7 +364,7 @@ func runLogsRemote(client *remote.Client, logDomain string, follow bool, lines i
363 }364 }
364 365
365 // streamLogsRemote streams logs from a remote Gordon instance.366 // streamLogsRemote streams logs from a remote Gordon instance.
366-func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain string, lines int) error {367+func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain string, lines int, out io.Writer) error {
367 var ch <-chan string368 var ch <-chan string
368 var err error369 var err error
369 370
@@ -377,7 +378,7 @@ func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain stri
377 }378 }
378 379
379 for line := range ch {380 for line := range ch {
380- if err := cliWriteLine(os.Stdout, line); err != nil {381+ if err := cliWriteLine(out, line); err != nil {
381 return err382 return err
382 }383 }
383 }384 }
@@ -385,16 +386,16 @@ func streamLogsRemote(ctx context.Context, client *remote.Client, logDomain stri
385 }386 }
386 387
387 // runLogsLocal shows logs from a local Gordon instance.388 // runLogsLocal shows logs from a local Gordon instance.
388-func runLogsLocal(logsConfigPath, logDomain string, follow bool, lines int) error {389+func runLogsLocal(logsConfigPath, logDomain string, follow bool, lines int, out io.Writer) error {
389 if logDomain == "" {390 if logDomain == "" {
390 // Process logs - use existing app.ShowLogs391 // Process logs - use existing app.ShowLogs
391 return app.ShowLogs(logsConfigPath, follow, lines)392 return app.ShowLogs(logsConfigPath, follow, lines)
392 }393 }
393 394
394- return showContainerLogsLocal(logsConfigPath, logDomain, follow, lines)395+ return showContainerLogsLocal(out, logsConfigPath, logDomain, follow, lines)
395 }396 }
396 397
397-func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, follow bool, lines int) error {398+func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, follow bool, lines int, out io.Writer) error {
398 ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)399 ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
399 defer cancel()400 defer cancel()
400 401
@@ -404,7 +405,7 @@ func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, fo
404 return fmt.Errorf("failed to stream container logs: %w", err)405 return fmt.Errorf("failed to stream container logs: %w", err)
405 }406 }
406 for line := range ch {407 for line := range ch {
407- if err := cliWriteLine(os.Stdout, line); err != nil {408+ if err := cliWriteLine(out, line); err != nil {
408 return err409 return err
409 }410 }
410 }411 }
@@ -416,7 +417,7 @@ func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, fo
416 return fmt.Errorf("failed to get container logs: %w", err)417 return fmt.Errorf("failed to get container logs: %w", err)
417 }418 }
418 for _, line := range logLines {419 for _, line := range logLines {
419- if err := cliWriteLine(os.Stdout, line); err != nil {420+ if err := cliWriteLine(out, line); err != nil {
420 return err421 return err
421 }422 }
422 }423 }
@@ -424,25 +425,25 @@ func runContainerLogs(ctx context.Context, cp ControlPlane, logDomain string, fo
424 }425 }
425 426
426 // showContainerLogsLocal is retained for UI adoption coverage and fallback messaging.427 // showContainerLogsLocal is retained for UI adoption coverage and fallback messaging.
427-func showContainerLogsLocal(_ string, logDomain string, follow bool, lines int) error {428+func showContainerLogsLocal(out io.Writer, _ string, logDomain string, follow bool, lines int) error {
428- if err := cliWriteLine(os.Stdout, cliRenderTitle(fmt.Sprintf("Container logs for %s", logDomain))); err != nil {429+ if err := cliWriteLine(out, cliRenderTitle(fmt.Sprintf("Container logs for %s", logDomain))); err != nil {
429 return err430 return err
430 }431 }
431- if err := cliWriteLine(os.Stdout, cliRenderInfo("To view container logs locally, use:")); err != nil {432+ if err := cliWriteLine(out, cliRenderInfo("To view container logs locally, use:")); err != nil {
432 return err433 return err
433 }434 }
434- if err := cliWritef(os.Stdout, " docker logs --tail %d %s\n", lines, logDomain); err != nil {435+ if err := cliWritef(out, " docker logs --tail %d %s\n", lines, logDomain); err != nil {
435 return err436 return err
436 }437 }
437 if follow {438 if follow {
438- if err := cliWritef(os.Stdout, " docker logs -f --tail %d %s\n", lines, logDomain); err != nil {439+ if err := cliWritef(out, " docker logs -f --tail %d %s\n", lines, logDomain); err != nil {
439 return err440 return err
440 }441 }
441 }442 }
442- if err := cliWriteLine(os.Stdout, ""); err != nil {443+ if err := cliWriteLine(out, ""); err != nil {
443 return err444 return err
444 }445 }
445- if err := cliWriteLine(os.Stdout, cliRenderMuted("Or use remote mode to access logs via the admin API.")); err != nil {446+ if err := cliWriteLine(out, cliRenderMuted("Or use remote mode to access logs via the admin API.")); err != nil {
446 return err447 return err
447 }448 }
448 return nil449 return nil
M internal/adapters/in/cli/routes.go +1 -1
@@ -1162,7 +1162,7 @@ Examples:
11621162 gordon routes remove app.mydomain.com --force`,
11631163 Args: cobra.ExactArgs(1),
11641164 RunE: func(cmd *cobra.Command, args []string) error {
1165- ctx := context.Background()
1165+ ctx := cmd.Context()
11661166 routeDomain := args[0]
11671167
11681168 // Confirm unless --force
@@ -1162,7 +1162,7 @@ Examples:
1162 gordon routes remove app.mydomain.com --force`,1162 gordon routes remove app.mydomain.com --force`,
1163 Args: cobra.ExactArgs(1),1163 Args: cobra.ExactArgs(1),
1164 RunE: func(cmd *cobra.Command, args []string) error {1164 RunE: func(cmd *cobra.Command, args []string) error {
1165- ctx := context.Background()1165+ ctx := cmd.Context()
1166 routeDomain := args[0]1166 routeDomain := args[0]
1167 1167
1168 // Confirm unless --force1168 // Confirm unless --force

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