v2.31.1 f37fe67

volumes.go RawHistoryFolder

1package cli
2
3import (
4	"context"
5	"fmt"
6	"io"
7
8	"github.com/spf13/cobra"
9
10	"github.com/bnema/gordon/internal/adapters/dto"
11	"github.com/bnema/gordon/internal/adapters/in/cli/ui/components"
12	"github.com/bnema/gordon/pkg/bytesize"
13)
14
15type volumesClient interface {
16	ListVolumes(ctx context.Context) ([]dto.Volume, error)
17	PruneVolumes(ctx context.Context, req dto.VolumePruneRequest) (*dto.VolumePruneResponse, error)
18}
19
20type volumesPruneOptions struct {
21	DryRun    bool
22	NoConfirm bool
23	Json      bool
24}
25
26func newVolumesCmd() *cobra.Command {
27	cmd := &cobra.Command{
28		Use:     "volumes",
29		Short:   "Manage volumes",
30		GroupID: groupManage,
31	}
32	cmd.AddCommand(newVolumesListCmd())
33	cmd.AddCommand(newVolumesPruneCmd())
34	return cmd
35}
36
37func newVolumesListCmd() *cobra.Command {
38	var jsonOut bool
39	cmd := &cobra.Command{
40		Use:   "list",
41		Short: "List all volumes",
42		RunE: func(cmd *cobra.Command, _ []string) error {
43			handle, err := resolveControlPlane(configPath)
44			if err != nil {
45				return err
46			}
47			defer handle.close()
48			return runVolumesList(cmd.Context(), handle.plane, cmd.OutOrStdout(), jsonOut)
49		},
50	}
51	cmd.Flags().BoolVar(&jsonOut, "json", false, "output as JSON")
52	return cmd
53}
54
55func newVolumesPruneCmd() *cobra.Command {
56	var opts volumesPruneOptions
57	cmd := &cobra.Command{
58		Use:   "prune",
59		Short: "Remove orphaned volumes",
60		RunE: func(cmd *cobra.Command, _ []string) error {
61			handle, err := resolveControlPlane(configPath)
62			if err != nil {
63				return err
64			}
65			defer handle.close()
66			return runVolumesPrune(cmd.Context(), handle.plane, opts, cmd.OutOrStdout())
67		},
68	}
69	cmd.Flags().BoolVar(&opts.DryRun, "dry-run", false, "show what would be removed")
70	cmd.Flags().BoolVar(&opts.NoConfirm, "no-confirm", false, "skip confirmation prompt")
71	cmd.Flags().BoolVar(&opts.Json, "json", false, "output as JSON")
72	return cmd
73}
74
75func runVolumesList(ctx context.Context, client volumesClient, out io.Writer, jsonOut bool) error {
76	volumes, err := client.ListVolumes(ctx)
77	if err != nil {
78		return err
79	}
80
81	if jsonOut {
82		return writeJSON(out, volumes)
83	}
84
85	if len(volumes) == 0 {
86		return cliWriteLine(out, "No volumes found.")
87	}
88
89	if err := cliWriteLine(out, cliRenderTitle("Volumes")); err != nil {
90		return err
91	}
92
93	for _, v := range volumes {
94		status := "orphaned"
95		if v.InUse {
96			status = "in-use"
97		}
98		containers := "-"
99		if len(v.Containers) > 0 {
100			containers = fmt.Sprintf("%v", v.Containers)
101		}
102		if err := cliWritef(out, "  %-30s %-10s %-10s %s\n", v.Name, status, bytesize.Format(v.Size), containers); err != nil {
103			return err
104		}
105	}
106
107	var inUse, orphaned int
108	for _, v := range volumes {
109		if v.InUse {
110			inUse++
111		} else {
112			orphaned++
113		}
114	}
115	return cliWritef(out, "\n%d volumes (%d in-use, %d orphaned)\n", len(volumes), inUse, orphaned)
116}
117
118func renderPrunePreview(out io.Writer, resp *dto.VolumePruneResponse) error {
119	if err := cliWriteLine(out, cliRenderTitle("Volumes to remove")); err != nil {
120		return err
121	}
122	for _, v := range resp.Volumes {
123		if err := cliWritef(out, "  %s (%s)\n", v.Name, bytesize.Format(v.Size)); err != nil {
124			return err
125		}
126	}
127	return cliWritef(out, "\n%d volumes, %s total\n", resp.VolumesRemoved, bytesize.Format(resp.SpaceReclaimed))
128}
129
130func runVolumesPrune(ctx context.Context, client volumesClient, opts volumesPruneOptions, out io.Writer) error {
131	preview, err := client.PruneVolumes(ctx, dto.VolumePruneRequest{DryRun: true})
132	if err != nil {
133		return err
134	}
135
136	if preview.VolumesRemoved == 0 {
137		if opts.Json {
138			return writeJSON(out, preview)
139		}
140		return cliWriteLine(out, "No orphaned volumes to remove.")
141	}
142
143	if opts.DryRun {
144		if opts.Json {
145			return writeJSON(out, preview)
146		}
147		return renderPrunePreview(out, preview)
148	}
149
150	if !opts.Json {
151		if err := renderPrunePreview(out, preview); err != nil {
152			return err
153		}
154	}
155
156	if !opts.NoConfirm && !opts.Json {
157		confirmed, err := components.RunConfirm("Remove these volumes?")
158		if err != nil {
159			return err
160		}
161		if !confirmed {
162			return cliWriteLine(out, "Cancelled.")
163		}
164	}
165
166	result, err := client.PruneVolumes(ctx, dto.VolumePruneRequest{DryRun: false})
167	if err != nil {
168		return err
169	}
170
171	if opts.Json {
172		return writeJSON(out, result)
173	}
174
175	return cliWriteLine(out, cliRenderSuccess(fmt.Sprintf("Removed %d volumes, reclaimed %s", result.VolumesRemoved, bytesize.Format(result.SpaceReclaimed))))
176}

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