detached b1fd1ce

refactor(cli): rename rollback command to pin

commit
b1fd1ced929b67b039def2f484faf91cf0b2f498
author
brice · · 2026-04-28 02:52 UTC
parent
062dcae
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +28 -28
R internal/adapters/in/cli/rollback.go → internal/adapters/in/cli/pin.go +25 -25 renamed, 76% similar
@@ -16,40 +16,40 @@ import (
1616 "github.com/bnema/gordon/internal/domain"
1717 )
1818
19-func newRollbackCmd() *cobra.Command {
19+func newPinCmd() *cobra.Command {
2020 var targetTag string
2121
2222 cmd := &cobra.Command{
23- Use: "rollback <domain>",
24- Short: "Roll back to a previous image version",
23+ Use: "pin <domain>",
24+ Short: "Pin a route to a specific image tag",
2525 Long: `Lists available image tags for a domain and deploys the selected version.
2626 Tags are read from the Gordon registry.
2727
2828 Examples:
29- gordon rollback myapp.example.com --remote ...
30- gordon rollback myapp.example.com --tag v1.0.0 --remote ...
31- gordon rollback list myapp.example.com --remote ...`,
29+ gordon pin myapp.example.com --remote ...
30+ gordon pin myapp.example.com --tag v1.0.0 --remote ...
31+ gordon pin list myapp.example.com --remote ...`,
3232 Args: cobra.ExactArgs(1),
3333 RunE: func(cmd *cobra.Command, args []string) error {
34- return runRollback(cmd.Context(), args[0], targetTag)
34+ return runPin(cmd.Context(), args[0], targetTag)
3535 },
3636 }
3737
3838 cmd.Flags().StringVar(&targetTag, "tag", "", "Target tag (skips interactive selection)")
39- cmd.AddCommand(newRollbackListCmd())
39+ cmd.AddCommand(newPinListCmd())
4040
4141 return cmd
4242 }
4343
44-func newRollbackListCmd() *cobra.Command {
44+func newPinListCmd() *cobra.Command {
4545 var jsonOut bool
4646
4747 cmd := &cobra.Command{
4848 Use: "list <domain>",
49- Short: "List available rollback tags",
49+ Short: "List available tags",
5050 Args: cobra.ExactArgs(1),
5151 RunE: func(cmd *cobra.Command, args []string) error {
52- return runRollbackList(cmd.Context(), cmd.OutOrStdout(), args[0], jsonOut)
52+ return runPinList(cmd.Context(), cmd.OutOrStdout(), args[0], jsonOut)
5353 },
5454 }
5555
@@ -58,14 +58,14 @@ func newRollbackListCmd() *cobra.Command {
5858 return cmd
5959 }
6060
61-func runRollback(ctx context.Context, rollbackDomain, targetTag string) error {
61+func runPin(ctx context.Context, pinDomain, targetTag string) error {
6262 handle, err := resolveControlPlane(configPath)
6363 if err != nil {
6464 return err
6565 }
6666 defer handle.close()
6767
68- route, err := handle.plane.GetRoute(ctx, rollbackDomain)
68+ route, err := handle.plane.GetRoute(ctx, pinDomain)
6969 if err != nil {
7070 return fmt.Errorf("failed to get route: %w", err)
7171 }
@@ -80,7 +80,7 @@ func runRollback(ctx context.Context, rollbackDomain, targetTag string) error {
8080 return err
8181 }
8282
83- selectedTag, err := selectTag(targetTag, tags, currentTag, rollbackDomain)
83+ selectedTag, err := selectTag(targetTag, tags, currentTag, pinDomain)
8484 if err != nil || selectedTag == "" {
8585 return err
8686 }
@@ -90,17 +90,17 @@ func runRollback(ctx context.Context, rollbackDomain, targetTag string) error {
9090 return nil
9191 }
9292
93- return deploySelectedTag(ctx, handle.plane, route, rollbackDomain, imageName, selectedTag)
93+ return deploySelectedTag(ctx, handle.plane, route, pinDomain, imageName, selectedTag)
9494 }
9595
96-func runRollbackList(ctx context.Context, w io.Writer, rollbackDomain string, jsonOut bool) error {
96+func runPinList(ctx context.Context, w io.Writer, pinDomain string, jsonOut bool) error {
9797 handle, err := resolveControlPlane(configPath)
9898 if err != nil {
9999 return err
100100 }
101101 defer handle.close()
102102
103- route, err := handle.plane.GetRoute(ctx, rollbackDomain)
103+ route, err := handle.plane.GetRoute(ctx, pinDomain)
104104 if err != nil {
105105 return fmt.Errorf("failed to get route: %w", err)
106106 }
@@ -115,19 +115,19 @@ func runRollbackList(ctx context.Context, w io.Writer, rollbackDomain string, js
115115 return err
116116 }
117117
118- return printRollbackTags(w, rollbackDomain, currentTag, tags, jsonOut)
118+ return printPinTags(w, pinDomain, currentTag, tags, jsonOut)
119119 }
120120
121-func printRollbackTags(w io.Writer, rollbackDomain, currentTag string, tags []string, jsonOut bool) error {
121+func printPinTags(w io.Writer, pinDomain, currentTag string, tags []string, jsonOut bool) error {
122122 if jsonOut {
123123 return writeJSON(w, map[string]any{
124- "domain": rollbackDomain,
124+ "domain": pinDomain,
125125 "current_tag": currentTag,
126126 "tags": tags,
127127 })
128128 }
129129
130- if _, err := fmt.Fprintf(w, "Available tags for %s:\n", styles.Theme.Bold.Render(rollbackDomain)); err != nil {
130+ if _, err := fmt.Fprintf(w, "Available tags for %s:\n", styles.Theme.Bold.Render(pinDomain)); err != nil {
131131 return err
132132 }
133133 for _, tag := range tags {
@@ -223,18 +223,18 @@ func validateTagExists(targetTag string, tags []string) bool {
223223 return false
224224 }
225225
226-func deploySelectedTag(ctx context.Context, cp ControlPlane, route *domain.Route, rollbackDomain, imageName, selectedTag string) error {
226+func deploySelectedTag(ctx context.Context, cp ControlPlane, route *domain.Route, pinDomain, imageName, selectedTag string) error {
227227 registry, _, _ := parseImageRef(route.Image)
228228 oldImage := route.Image
229229 route.Image = fmt.Sprintf("%s/%s:%s", registry, imageName, selectedTag)
230230
231- fmt.Printf("Rolling back to %s...\n", styles.Theme.Bold.Render(selectedTag))
231+ fmt.Printf("Pinning to %s...\n", styles.Theme.Bold.Render(selectedTag))
232232
233233 if err := cp.UpdateRoute(ctx, *route); err != nil {
234234 return fmt.Errorf("failed to update route: %w", err)
235235 }
236236
237- result, err := cp.Deploy(ctx, rollbackDomain)
237+ result, err := cp.Deploy(ctx, pinDomain)
238238 if err != nil {
239239 // Attempt to revert route to previous image
240240 route.Image = oldImage
@@ -245,6 +245,6 @@ func deploySelectedTag(ctx context.Context, cp ControlPlane, route *domain.Route
245245 return fmt.Errorf("failed to deploy (route reverted): %w", err)
246246 }
247247 containerID := shortContainerID(result.ContainerID)
248- fmt.Println(styles.RenderSuccess(fmt.Sprintf("Rolled back %s to %s (container: %s)", rollbackDomain, selectedTag, containerID)))
248+ fmt.Println(styles.RenderSuccess(fmt.Sprintf("Pinned %s to %s (container: %s)", pinDomain, selectedTag, containerID)))
249249 return nil
250250 }
@@ -16,40 +16,40 @@ import (
16 "github.com/bnema/gordon/internal/domain"16 "github.com/bnema/gordon/internal/domain"
17 )17 )
18 18
19-func newRollbackCmd() *cobra.Command {19+func newPinCmd() *cobra.Command {
20 var targetTag string20 var targetTag string
21 21
22 cmd := &cobra.Command{22 cmd := &cobra.Command{
23- Use: "rollback <domain>",23+ Use: "pin <domain>",
24- Short: "Roll back to a previous image version",24+ Short: "Pin a route to a specific image tag",
25 Long: `Lists available image tags for a domain and deploys the selected version.25 Long: `Lists available image tags for a domain and deploys the selected version.
26 Tags are read from the Gordon registry.26 Tags are read from the Gordon registry.
27 27
28 Examples:28 Examples:
29- gordon rollback myapp.example.com --remote ...29+ gordon pin myapp.example.com --remote ...
30- gordon rollback myapp.example.com --tag v1.0.0 --remote ...30+ gordon pin myapp.example.com --tag v1.0.0 --remote ...
31- gordon rollback list myapp.example.com --remote ...`,31+ gordon pin list myapp.example.com --remote ...`,
32 Args: cobra.ExactArgs(1),32 Args: cobra.ExactArgs(1),
33 RunE: func(cmd *cobra.Command, args []string) error {33 RunE: func(cmd *cobra.Command, args []string) error {
34- return runRollback(cmd.Context(), args[0], targetTag)34+ return runPin(cmd.Context(), args[0], targetTag)
35 },35 },
36 }36 }
37 37
38 cmd.Flags().StringVar(&targetTag, "tag", "", "Target tag (skips interactive selection)")38 cmd.Flags().StringVar(&targetTag, "tag", "", "Target tag (skips interactive selection)")
39- cmd.AddCommand(newRollbackListCmd())39+ cmd.AddCommand(newPinListCmd())
40 40
41 return cmd41 return cmd
42 }42 }
43 43
44-func newRollbackListCmd() *cobra.Command {44+func newPinListCmd() *cobra.Command {
45 var jsonOut bool45 var jsonOut bool
46 46
47 cmd := &cobra.Command{47 cmd := &cobra.Command{
48 Use: "list <domain>",48 Use: "list <domain>",
49- Short: "List available rollback tags",49+ Short: "List available tags",
50 Args: cobra.ExactArgs(1),50 Args: cobra.ExactArgs(1),
51 RunE: func(cmd *cobra.Command, args []string) error {51 RunE: func(cmd *cobra.Command, args []string) error {
52- return runRollbackList(cmd.Context(), cmd.OutOrStdout(), args[0], jsonOut)52+ return runPinList(cmd.Context(), cmd.OutOrStdout(), args[0], jsonOut)
53 },53 },
54 }54 }
55 55
@@ -58,14 +58,14 @@ func newRollbackListCmd() *cobra.Command {
58 return cmd58 return cmd
59 }59 }
60 60
61-func runRollback(ctx context.Context, rollbackDomain, targetTag string) error {61+func runPin(ctx context.Context, pinDomain, targetTag string) error {
62 handle, err := resolveControlPlane(configPath)62 handle, err := resolveControlPlane(configPath)
63 if err != nil {63 if err != nil {
64 return err64 return err
65 }65 }
66 defer handle.close()66 defer handle.close()
67 67
68- route, err := handle.plane.GetRoute(ctx, rollbackDomain)68+ route, err := handle.plane.GetRoute(ctx, pinDomain)
69 if err != nil {69 if err != nil {
70 return fmt.Errorf("failed to get route: %w", err)70 return fmt.Errorf("failed to get route: %w", err)
71 }71 }
@@ -80,7 +80,7 @@ func runRollback(ctx context.Context, rollbackDomain, targetTag string) error {
80 return err80 return err
81 }81 }
82 82
83- selectedTag, err := selectTag(targetTag, tags, currentTag, rollbackDomain)83+ selectedTag, err := selectTag(targetTag, tags, currentTag, pinDomain)
84 if err != nil || selectedTag == "" {84 if err != nil || selectedTag == "" {
85 return err85 return err
86 }86 }
@@ -90,17 +90,17 @@ func runRollback(ctx context.Context, rollbackDomain, targetTag string) error {
90 return nil90 return nil
91 }91 }
92 92
93- return deploySelectedTag(ctx, handle.plane, route, rollbackDomain, imageName, selectedTag)93+ return deploySelectedTag(ctx, handle.plane, route, pinDomain, imageName, selectedTag)
94 }94 }
95 95
96-func runRollbackList(ctx context.Context, w io.Writer, rollbackDomain string, jsonOut bool) error {96+func runPinList(ctx context.Context, w io.Writer, pinDomain string, jsonOut bool) error {
97 handle, err := resolveControlPlane(configPath)97 handle, err := resolveControlPlane(configPath)
98 if err != nil {98 if err != nil {
99 return err99 return err
100 }100 }
101 defer handle.close()101 defer handle.close()
102 102
103- route, err := handle.plane.GetRoute(ctx, rollbackDomain)103+ route, err := handle.plane.GetRoute(ctx, pinDomain)
104 if err != nil {104 if err != nil {
105 return fmt.Errorf("failed to get route: %w", err)105 return fmt.Errorf("failed to get route: %w", err)
106 }106 }
@@ -115,19 +115,19 @@ func runRollbackList(ctx context.Context, w io.Writer, rollbackDomain string, js
115 return err115 return err
116 }116 }
117 117
118- return printRollbackTags(w, rollbackDomain, currentTag, tags, jsonOut)118+ return printPinTags(w, pinDomain, currentTag, tags, jsonOut)
119 }119 }
120 120
121-func printRollbackTags(w io.Writer, rollbackDomain, currentTag string, tags []string, jsonOut bool) error {121+func printPinTags(w io.Writer, pinDomain, currentTag string, tags []string, jsonOut bool) error {
122 if jsonOut {122 if jsonOut {
123 return writeJSON(w, map[string]any{123 return writeJSON(w, map[string]any{
124- "domain": rollbackDomain,124+ "domain": pinDomain,
125 "current_tag": currentTag,125 "current_tag": currentTag,
126 "tags": tags,126 "tags": tags,
127 })127 })
128 }128 }
129 129
130- if _, err := fmt.Fprintf(w, "Available tags for %s:\n", styles.Theme.Bold.Render(rollbackDomain)); err != nil {130+ if _, err := fmt.Fprintf(w, "Available tags for %s:\n", styles.Theme.Bold.Render(pinDomain)); err != nil {
131 return err131 return err
132 }132 }
133 for _, tag := range tags {133 for _, tag := range tags {
@@ -223,18 +223,18 @@ func validateTagExists(targetTag string, tags []string) bool {
223 return false223 return false
224 }224 }
225 225
226-func deploySelectedTag(ctx context.Context, cp ControlPlane, route *domain.Route, rollbackDomain, imageName, selectedTag string) error {226+func deploySelectedTag(ctx context.Context, cp ControlPlane, route *domain.Route, pinDomain, imageName, selectedTag string) error {
227 registry, _, _ := parseImageRef(route.Image)227 registry, _, _ := parseImageRef(route.Image)
228 oldImage := route.Image228 oldImage := route.Image
229 route.Image = fmt.Sprintf("%s/%s:%s", registry, imageName, selectedTag)229 route.Image = fmt.Sprintf("%s/%s:%s", registry, imageName, selectedTag)
230 230
231- fmt.Printf("Rolling back to %s...\n", styles.Theme.Bold.Render(selectedTag))231+ fmt.Printf("Pinning to %s...\n", styles.Theme.Bold.Render(selectedTag))
232 232
233 if err := cp.UpdateRoute(ctx, *route); err != nil {233 if err := cp.UpdateRoute(ctx, *route); err != nil {
234 return fmt.Errorf("failed to update route: %w", err)234 return fmt.Errorf("failed to update route: %w", err)
235 }235 }
236 236
237- result, err := cp.Deploy(ctx, rollbackDomain)237+ result, err := cp.Deploy(ctx, pinDomain)
238 if err != nil {238 if err != nil {
239 // Attempt to revert route to previous image239 // Attempt to revert route to previous image
240 route.Image = oldImage240 route.Image = oldImage
@@ -245,6 +245,6 @@ func deploySelectedTag(ctx context.Context, cp ControlPlane, route *domain.Route
245 return fmt.Errorf("failed to deploy (route reverted): %w", err)245 return fmt.Errorf("failed to deploy (route reverted): %w", err)
246 }246 }
247 containerID := shortContainerID(result.ContainerID)247 containerID := shortContainerID(result.ContainerID)
248- fmt.Println(styles.RenderSuccess(fmt.Sprintf("Rolled back %s to %s (container: %s)", rollbackDomain, selectedTag, containerID)))248+ fmt.Println(styles.RenderSuccess(fmt.Sprintf("Pinned %s to %s (container: %s)", pinDomain, selectedTag, containerID)))
249 return nil249 return nil
250 }250 }
M internal/adapters/in/cli/root.go +3 -3
@@ -101,9 +101,9 @@ Commands are organized by where they run:
101101 pushCmd.GroupID = groupManage
102102 rootCmd.AddCommand(pushCmd)
103103
104- rollbackCmd := newRollbackCmd()
105- rollbackCmd.GroupID = groupManage
106- rootCmd.AddCommand(rollbackCmd)
104+ pinCmd := newPinCmd()
105+ pinCmd.GroupID = groupManage
106+ rootCmd.AddCommand(pinCmd)
107107
108108 reloadCmd := newReloadCmd()
109109 reloadCmd.GroupID = groupManage
@@ -101,9 +101,9 @@ Commands are organized by where they run:
101 pushCmd.GroupID = groupManage101 pushCmd.GroupID = groupManage
102 rootCmd.AddCommand(pushCmd)102 rootCmd.AddCommand(pushCmd)
103 103
104- rollbackCmd := newRollbackCmd()104+ pinCmd := newPinCmd()
105- rollbackCmd.GroupID = groupManage105+ pinCmd.GroupID = groupManage
106- rootCmd.AddCommand(rollbackCmd)106+ rootCmd.AddCommand(pinCmd)
107 107
108 reloadCmd := newReloadCmd()108 reloadCmd := newReloadCmd()
109 reloadCmd.GroupID = groupManage109 reloadCmd.GroupID = groupManage

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