v2.16.0 ebd2ab3

backup.go RawHistoryFolder

1package cli
2
3import (
4	"fmt"
5	"os"
6	"text/tabwriter"
7	"time"
8
9	"github.com/spf13/cobra"
10)
11
12// newBackupCmd creates the backup command group.
13func newBackupCmd() *cobra.Command {
14	cmd := &cobra.Command{
15		Use:     "backups",
16		Aliases: []string{"backup"},
17		Short:   "Manage database backups",
18		Long: `Manage database backups.
19
20Runs locally via in-process services by default, or against a remote Gordon
21instance when --remote targeting is configured.`,
22	}
23
24	cmd.AddCommand(newBackupListCmd())
25	cmd.AddCommand(newBackupRunCmd())
26	cmd.AddCommand(newBackupDetectCmd())
27	cmd.AddCommand(newBackupStatusCmd())
28
29	return cmd
30}
31
32func newBackupListCmd() *cobra.Command {
33	return &cobra.Command{
34		Use:   "list [domain]",
35		Short: "List backups",
36		Args:  cobra.MaximumNArgs(1),
37		RunE: func(cmd *cobra.Command, args []string) error {
38			handle, err := resolveControlPlane(configPath)
39			if err != nil {
40				return err
41			}
42			defer handle.close()
43
44			domainName := ""
45			if len(args) == 1 {
46				domainName = args[0]
47			}
48
49			jobs, err := handle.plane.ListBackups(cmd.Context(), domainName)
50			if err != nil {
51				return fmt.Errorf("failed to list backups: %w", err)
52			}
53
54			if len(jobs) == 0 {
55				fmt.Println("No backups found")
56				return nil
57			}
58			w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
59			if _, err := fmt.Fprintln(w, "DOMAIN\tDB\tSTATUS\tSTARTED_AT\tBACKUP_ID"); err != nil {
60				return err
61			}
62
63			for _, job := range jobs {
64				if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", job.Domain, job.DBName, job.Status, formatBackupTime(job.StartedAt), job.ID); err != nil {
65					return err
66				}
67			}
68			if err := w.Flush(); err != nil {
69				return err
70			}
71
72			return nil
73		},
74	}
75}
76
77func newBackupRunCmd() *cobra.Command {
78	var dbName string
79
80	cmd := &cobra.Command{
81		Use:   "run <domain>",
82		Short: "Run backup now",
83		Args:  cobra.ExactArgs(1),
84		RunE: func(cmd *cobra.Command, args []string) error {
85			handle, err := resolveControlPlane(configPath)
86			if err != nil {
87				return err
88			}
89			defer handle.close()
90
91			result, err := handle.plane.RunBackup(cmd.Context(), args[0], dbName)
92			if err != nil {
93				return fmt.Errorf("failed to run backup: %w", err)
94			}
95
96			if result.Backup == nil {
97				return fmt.Errorf("backup run completed without backup payload")
98			}
99
100			w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
101			if _, err := fmt.Fprintln(w, "DOMAIN\tDB\tSTATUS\tSTARTED_AT\tBACKUP_ID\tSIZE_BYTES"); err != nil {
102				return err
103			}
104			if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%d\n", result.Backup.Domain, result.Backup.DBName, result.Backup.Status, formatBackupTime(result.Backup.StartedAt), result.Backup.ID, result.Backup.SizeBytes); err != nil {
105				return err
106			}
107			if err := w.Flush(); err != nil {
108				return err
109			}
110			return nil
111		},
112	}
113
114	cmd.Flags().StringVar(&dbName, "db", "", "Database attachment name (optional)")
115	return cmd
116}
117
118func newBackupDetectCmd() *cobra.Command {
119	return &cobra.Command{
120		Use:   "detect <domain>",
121		Short: "Detect databases for domain",
122		Args:  cobra.ExactArgs(1),
123		RunE: func(cmd *cobra.Command, args []string) error {
124			handle, err := resolveControlPlane(configPath)
125			if err != nil {
126				return err
127			}
128			defer handle.close()
129
130			dbs, err := handle.plane.DetectDatabases(cmd.Context(), args[0])
131			if err != nil {
132				return fmt.Errorf("failed to detect databases: %w", err)
133			}
134
135			if len(dbs) == 0 {
136				fmt.Println("No supported databases detected")
137				return nil
138			}
139			w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
140			if _, err := fmt.Fprintln(w, "NAME\tTYPE\tHOST\tPORT\tIMAGE"); err != nil {
141				return err
142			}
143
144			for _, db := range dbs {
145				if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\n", db.Name, db.Type, db.Host, db.Port, db.ImageName); err != nil {
146					return err
147				}
148			}
149			if err := w.Flush(); err != nil {
150				return err
151			}
152
153			return nil
154		},
155	}
156}
157
158func newBackupStatusCmd() *cobra.Command {
159	return &cobra.Command{
160		Use:   "status",
161		Short: "Show backup status",
162		RunE: func(cmd *cobra.Command, args []string) error {
163			handle, err := resolveControlPlane(configPath)
164			if err != nil {
165				return err
166			}
167			defer handle.close()
168
169			jobs, err := handle.plane.BackupStatus(cmd.Context())
170			if err != nil {
171				return fmt.Errorf("failed to get backup status: %w", err)
172			}
173
174			if len(jobs) == 0 {
175				fmt.Println("No backup status available")
176				return nil
177			}
178			w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
179			if _, err := fmt.Fprintln(w, "DOMAIN\tDB\tSTATUS\tSTARTED_AT"); err != nil {
180				return err
181			}
182
183			for _, job := range jobs {
184				if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", job.Domain, job.DBName, job.Status, formatBackupTime(job.StartedAt)); err != nil {
185					return err
186				}
187			}
188			if err := w.Flush(); err != nil {
189				return err
190			}
191
192			return nil
193		},
194	}
195}
196
197func formatBackupTime(t *time.Time) string {
198	if t == nil {
199		return ""
200	}
201	return t.UTC().Format("2006-01-02T15:04:05Z")
202}

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