v2.14.0 5e11a00

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

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