Skip to content

Commit a0e95ba

Browse files
authored
Add backup config management to flexctl (#244)
* Stub out commands for config management. * Get `flexctl backup config show` working. * Get `flexctl backup config update` working. * gofmt * Appease our linter overlords. * Probably not necessary to close these as they're short-lived. * Retrieve flag values separately. * Style fixes. * Construct URL from app environment. * Cosmetic changes. * Print restart instructions if required. * Fix restart command. * Switch from `fly app restart` to `fly pg restart`. * Cosmetic updates. * Check that backups are enabled before updating backup configs. * Add missing newline.
1 parent a453ca4 commit a0e95ba

File tree

4 files changed

+191
-4
lines changed

4 files changed

+191
-4
lines changed

cmd/flexctl/backups.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package main
22

33
import (
4+
"bytes"
45
"context"
6+
"encoding/json"
57
"fmt"
8+
"net/http"
69
"os"
710
"time"
811

@@ -224,3 +227,185 @@ func listBackups(cmd *cobra.Command) error {
224227
func backupsEnabled() bool {
225228
return os.Getenv("S3_ARCHIVE_CONFIG") != ""
226229
}
230+
231+
func newBackupConfig() *cobra.Command {
232+
var cmd = &cobra.Command{
233+
Use: "config",
234+
Short: "Manage backup configuration",
235+
}
236+
237+
cmd.AddCommand(newConfigShow(), newConfigUpdate())
238+
239+
return cmd
240+
}
241+
242+
type configShowResult struct {
243+
Result flypg.BarmanSettings `json:"result"`
244+
}
245+
246+
func getAppName() (string, error) {
247+
name := os.Getenv("FLY_APP_NAME")
248+
if name == "" {
249+
return "", fmt.Errorf("FLY_APP_NAME is not set")
250+
}
251+
return name, nil
252+
}
253+
254+
func getApiUrl() (string, error) {
255+
hostname, err := getAppName()
256+
if err != nil {
257+
return "", err
258+
}
259+
url := fmt.Sprintf("http://%s.internal:5500", hostname)
260+
return url, nil
261+
}
262+
263+
func newConfigShow() *cobra.Command {
264+
var cmd = &cobra.Command{
265+
Use: "show",
266+
Short: "Show current configuration",
267+
RunE: func(cmd *cobra.Command, args []string) error {
268+
if !backupsEnabled() {
269+
return fmt.Errorf("backups are not enabled")
270+
}
271+
272+
url, err := getApiUrl()
273+
if err != nil {
274+
return err
275+
}
276+
277+
url = fmt.Sprintf("%s/commands/admin/settings/view/barman", url)
278+
resp, err := http.Get(url)
279+
if err != nil {
280+
return err
281+
}
282+
283+
var rv configShowResult
284+
if err := json.NewDecoder(resp.Body).Decode(&rv); err != nil {
285+
return err
286+
}
287+
288+
fmt.Printf(" ArchiveTimeout = %s\n", rv.Result.ArchiveTimeout)
289+
fmt.Printf(" RecoveryWindow = %s\n", rv.Result.RecoveryWindow)
290+
fmt.Printf(" FullBackupFrequency = %s\n", rv.Result.FullBackupFrequency)
291+
fmt.Printf(" MinimumRedundancy = %s\n", rv.Result.MinimumRedundancy)
292+
293+
return nil
294+
},
295+
}
296+
297+
return cmd
298+
}
299+
300+
type successfulUpdateResult struct {
301+
Message string `json:"message,omitempty"`
302+
RestartRequired bool `json:"restart_required"`
303+
}
304+
305+
type configUpdateResult struct {
306+
Result successfulUpdateResult `json:"result,omitempty"`
307+
Error string `json:"error,omitempty"`
308+
}
309+
310+
func newConfigUpdate() *cobra.Command {
311+
var cmd = &cobra.Command{
312+
Use: "update",
313+
Short: "Update configuration",
314+
}
315+
316+
cmd.RunE = func(cmd *cobra.Command, args []string) error {
317+
if !backupsEnabled() {
318+
return fmt.Errorf("backups are not enabled")
319+
}
320+
321+
archiveTimeout, err := cmd.Flags().GetString("archive-timeout")
322+
if err != nil {
323+
return err
324+
}
325+
326+
recoveryWindow, err := cmd.Flags().GetString("recovery-window")
327+
if err != nil {
328+
return err
329+
}
330+
331+
fullBackupFrequency, err := cmd.Flags().GetString("full-backup-frequency")
332+
if err != nil {
333+
return err
334+
}
335+
336+
minimumRedundancy, err := cmd.Flags().GetString("minimum-redundancy")
337+
if err != nil {
338+
return err
339+
}
340+
341+
update := flypg.BarmanSettings{
342+
ArchiveTimeout: archiveTimeout,
343+
RecoveryWindow: recoveryWindow,
344+
FullBackupFrequency: fullBackupFrequency,
345+
MinimumRedundancy: minimumRedundancy,
346+
}
347+
348+
jsonBody, err := json.Marshal(update)
349+
if err != nil {
350+
return err
351+
}
352+
353+
url, err := getApiUrl()
354+
if err != nil {
355+
return err
356+
}
357+
358+
url = fmt.Sprintf("%s/commands/admin/settings/update/barman", url)
359+
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody))
360+
if err != nil {
361+
return err
362+
}
363+
364+
var rv configUpdateResult
365+
if err := json.NewDecoder(resp.Body).Decode(&rv); err != nil {
366+
return err
367+
}
368+
369+
if rv.Error != "" {
370+
return fmt.Errorf("error updating configuration: %s", rv.Error)
371+
}
372+
373+
if rv.Result.Message != "" {
374+
fmt.Println(rv.Result.Message)
375+
}
376+
377+
if rv.Result.RestartRequired {
378+
appName, err := getAppName()
379+
if err != nil {
380+
return err
381+
}
382+
fmt.Printf("A restart is required for these changes to take effect. Run `fly pg restart -a %s` to restart.)\n", appName)
383+
}
384+
385+
return nil
386+
}
387+
388+
cmd.Flags().StringP("archive-timeout", "", "", "Archive timeout")
389+
cmd.Flags().StringP("recovery-window", "", "", "Recovery window")
390+
cmd.Flags().StringP("full-backup-frequency", "", "", "Full backup frequency")
391+
cmd.Flags().StringP("minimum-redundancy", "", "", "Minimum redundancy")
392+
393+
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
394+
requiredFlags := []string{"archive-timeout", "recovery-window", "full-backup-frequency", "minimum-redundancy"}
395+
providedFlags := 0
396+
397+
for _, flag := range requiredFlags {
398+
if cmd.Flag(flag).Changed {
399+
providedFlags++
400+
}
401+
}
402+
403+
if providedFlags < 1 {
404+
return fmt.Errorf("at least one flag must be specified")
405+
}
406+
407+
return nil
408+
}
409+
410+
return cmd
411+
}

cmd/flexctl/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func main() {
2121
backupCmd.AddCommand(backupListCmd)
2222
backupCmd.AddCommand(backupShowCmd)
2323
backupCmd.AddCommand(backupCreateCmd)
24+
backupCmd.AddCommand(newBackupConfig())
2425

2526
if err := rootCmd.Execute(); err != nil {
2627
fmt.Println(err)

internal/flypg/barman_config.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import (
1313
)
1414

1515
type BarmanSettings struct {
16-
ArchiveTimeout string
17-
RecoveryWindow string
18-
FullBackupFrequency string
19-
MinimumRedundancy string
16+
ArchiveTimeout string `json:"archive_timeout,omitempty"`
17+
RecoveryWindow string `json:"recovery_window,omitempty"`
18+
FullBackupFrequency string `json:"full_backup_frequency,omitempty"`
19+
MinimumRedundancy string `json:"minimum_redundancy,omitempty"`
2020
}
2121

2222
type BarmanConfig struct {

internal/supervisor/ensure_kill_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build linux
12
// +build linux
23

34
package supervisor

0 commit comments

Comments
 (0)