Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 173 additions & 59 deletions internal/command/postgres/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import (
"github.com/superfly/flyctl/internal/flyutil"
)

const (
backupVersion = "0.0.53"
backupConfigVersion = "0.0.54"
)

func newBackup() *cobra.Command {
const (
short = "Backup commands"
Expand All @@ -27,7 +32,8 @@ func newBackup() *cobra.Command {
cmd := command.New("backup", short, long, nil)
cmd.Aliases = []string{"backups"}

cmd.AddCommand(newBackupCreate(), newBackupEnable(), newBackupList(), newBackupRestore())
cmd.AddCommand(newBackupConfig(), newBackupCreate(), newBackupEnable(), newBackupList(), newBackupRestore())

return cmd
}

Expand Down Expand Up @@ -106,7 +112,7 @@ func runBackupRestore(ctx context.Context) error {
}

// Ensure the the app has the required flex version.
if err := hasRequiredVersion(appName, machines); err != nil {
if err := hasRequiredVersionOnMachines(appName, machines, "", backupVersion, ""); err != nil {
return err
}

Expand Down Expand Up @@ -204,7 +210,6 @@ func newBackupCreate() *cobra.Command {
func runBackupCreate(ctx context.Context) error {
var (
appName = appconfig.NameFromContext(ctx)
io = iostreams.FromContext(ctx)
)

enabled, err := isBackupEnabled(ctx, appName)
Expand Down Expand Up @@ -232,15 +237,10 @@ func runBackupCreate(ctx context.Context) error {
return fmt.Errorf("No active machines")
}

if err := hasRequiredVersion(appName, machines); err != nil {
if err := hasRequiredVersionOnMachines(appName, machines, "", backupVersion, ""); err != nil {
return err
}

// Ensure the backup is issued against the primary.
leader, err := pickLeader(ctx, machines)
if err != nil {
return err
}
cmd := "flexctl backup create"

if flag.GetBool(ctx, "immediate-checkpoint") {
Expand All @@ -252,27 +252,7 @@ func runBackupCreate(ctx context.Context) error {
cmd += " -n " + name
}

in := &fly.MachineExecRequest{
Cmd: cmd,
}

out, err := flapsClient.Exec(ctx, leader.ID, in)
if err != nil {
return err
}

if out.ExitCode != 0 {
fmt.Fprintf(io.Out, "Exit code: %d\n", out.ExitCode)
}

if out.StdOut != "" {
fmt.Fprint(io.Out, out.StdOut)
}
if out.StdErr != "" {
fmt.Fprint(io.ErrOut, out.StdErr)
}

return nil
return ExecOnLeader(ctx, flapsClient, cmd)
}

func newBackupEnable() *cobra.Command {
Expand Down Expand Up @@ -336,7 +316,7 @@ func runBackupEnable(ctx context.Context) error {
return err
}

if err := hasRequiredVersion(appName, machines); err != nil {
if err := hasRequiredVersionOnMachines(appName, machines, "", backupVersion, ""); err != nil {
return err
}

Expand Down Expand Up @@ -392,7 +372,6 @@ func newBackupList() *cobra.Command {
func runBackupList(ctx context.Context) error {
var (
appName = appconfig.NameFromContext(ctx)
io = iostreams.FromContext(ctx)
)

enabled, err := isBackupEnabled(ctx, appName)
Expand Down Expand Up @@ -420,34 +399,13 @@ func runBackupList(ctx context.Context) error {
return fmt.Errorf("No active machines")
}

err = hasRequiredVersion(appName, machines)
if err != nil {
if err = hasRequiredVersionOnMachines(appName, machines, "", backupVersion, ""); err != nil {
return err
}

machine := machines[0]

in := &fly.MachineExecRequest{
Cmd: "flexctl backup list",
}

out, err := flapsClient.Exec(ctx, machine.ID, in)
if err != nil {
return err
}

if out.ExitCode != 0 {
fmt.Fprintf(io.Out, "Exit code: %d\n", out.ExitCode)
}

if out.StdOut != "" {
fmt.Fprint(io.Out, out.StdOut)
}
if out.StdErr != "" {
fmt.Fprint(io.ErrOut, out.StdErr)
}

return nil
return ExecOnMachine(ctx, flapsClient, machine.ID, "flexctl backup list")
}

func resolveRestoreTarget(ctx context.Context) string {
Expand All @@ -468,10 +426,6 @@ func resolveRestoreTarget(ctx context.Context) string {
return target
}

func hasRequiredVersion(appName string, machines []*fly.Machine) error {
return hasRequiredVersionOnMachines(appName, machines, "", "0.0.53", "")
}

func isBackupEnabled(ctx context.Context, appName string) (bool, error) {
var (
client = flyutil.ClientFromContext(ctx)
Expand All @@ -490,3 +444,163 @@ func isBackupEnabled(ctx context.Context, appName string) (bool, error) {

return false, nil
}

func newBackupConfig() *cobra.Command {
const (
short = "Manage backup configuration"
long = short + "\n"
)

cmd := command.New("config", short, long, nil)

cmd.AddCommand(newBackupConfigShow(), newBackupConfigUpdate())

return cmd
}

func newBackupConfigShow() *cobra.Command {
const (
short = "Show backup configuration"
long = short + "\n"
)

cmd := command.New("show", short, long, runBackupConfigShow,
command.RequireSession,
command.RequireAppName,
)

flag.Add(cmd, flag.App(), flag.AppConfig())

return cmd
}

func newBackupConfigUpdate() *cobra.Command {
const (
short = "Update backup configuration"
long = short + "\n"

usage = "update"
)

cmd := command.New(usage, short, long, runBackupConfigUpdate,
command.RequireSession,
command.RequireAppName,
)

flag.Add(
cmd,
flag.App(),
flag.AppConfig(),
flag.String{
Name: "archive-timeout",
Description: "Archive timeout",
},
flag.String{
Name: "recovery-window",
Description: "Recovery window",
},
flag.String{
Name: "full-backup-frequency",
Description: "Full backup frequency",
},
flag.String{
Name: "minimum-redundancy",
Description: "Minimum redundancy",
},
)

return cmd
}

func runBackupConfigShow(ctx context.Context) error {
var (
appName = appconfig.NameFromContext(ctx)
)

enabled, err := isBackupEnabled(ctx, appName)
if err != nil {
return err
}

if !enabled {
return fmt.Errorf("backups are not enabled. Run `fly pg backup enable -a %s` to enable them", appName)
}

flapsClient, err := flapsutil.NewClientWithOptions(ctx, flaps.NewClientOpts{
AppName: appName,
})
if err != nil {
return fmt.Errorf("failed to initialize flaps client: %w", err)
}

machines, err := flapsClient.ListActive(ctx)
if err != nil {
return fmt.Errorf("list of machines could not be retrieved: %w", err)
}

if len(machines) == 0 {
return fmt.Errorf("No active machines")
}

// Ensure the the app has the required flex version.
if err := hasRequiredVersionOnMachines(appName, machines, "", backupConfigVersion, ""); err != nil {
return err
}

return ExecOnLeader(ctx, flapsClient, "flexctl backup config show")
}

func runBackupConfigUpdate(ctx context.Context) error {
var (
appName = appconfig.NameFromContext(ctx)
)

flapsClient, err := flapsutil.NewClientWithOptions(ctx, flaps.NewClientOpts{
AppName: appName,
})
if err != nil {
return fmt.Errorf("failed to initialize flaps client: %w", err)
}

machines, err := flapsClient.ListActive(ctx)
if err != nil {
return fmt.Errorf("list of machines could not be retrieved: %w", err)
}

if len(machines) == 0 {
return fmt.Errorf("No active machines")
}

// Ensure the the app has the required flex version.
if err := hasRequiredVersionOnMachines(appName, machines, "", backupConfigVersion, ""); err != nil {
return err
}

command := "flexctl backup config update"
enabled, err := isBackupEnabled(ctx, appName)
if err != nil {
return err
}

if !enabled {
return fmt.Errorf("backups are not enabled. Run `fly pg backup enable -a %s` to enable them", appName)
}

if flag.GetString(ctx, "archive-timeout") != "" {
command += " --archive-timeout " + flag.GetString(ctx, "archive-timeout")
}

if flag.GetString(ctx, "recovery-window") != "" {
command += " --recovery-window " + flag.GetString(ctx, "recovery-window")
}

if flag.GetString(ctx, "full-backup-frequency") != "" {
command += " --full-backup-frequency " + flag.GetString(ctx, "full-backup-frequency")
}

if flag.GetString(ctx, "minimum-redundancy") != "" {
command += " --minimum-redundancy " + flag.GetString(ctx, "minimum-redundancy")
}

return ExecOnLeader(ctx, flapsClient, command)
}
43 changes: 43 additions & 0 deletions internal/command/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"github.com/hashicorp/go-version"
"github.com/spf13/cobra"
fly "github.com/superfly/fly-go"
"github.com/superfly/fly-go/flaps"
"github.com/superfly/flyctl/flypg"
"github.com/superfly/flyctl/internal/command"
mach "github.com/superfly/flyctl/internal/machine"
"github.com/superfly/flyctl/iostreams"
)

func New() *cobra.Command {
Expand Down Expand Up @@ -191,3 +193,44 @@ func UnregisterMember(ctx context.Context, app *fly.AppCompact, machine *fly.Mac

return nil
}

// Runs a command on the specified machine ID in the named app.
func ExecOnMachine(ctx context.Context, client *flaps.Client, machineId, command string) error {
var (
io = iostreams.FromContext(ctx)
)

in := &fly.MachineExecRequest{
Cmd: command,
}

out, err := client.Exec(ctx, machineId, in)
if err != nil {
return err
}

if out.StdOut != "" {
fmt.Fprint(io.Out, out.StdOut)
}

if out.StdErr != "" {
fmt.Fprint(io.ErrOut, out.StdErr)
}

return nil
}

// Runs a command on the leader of the named cluster.
func ExecOnLeader(ctx context.Context, client *flaps.Client, command string) error {
machines, err := client.ListActive(ctx)
if err != nil {
return err
}

leader, err := pickLeader(ctx, machines)
if err != nil {
return err
}

return ExecOnMachine(ctx, client, leader.ID, command)
}