Skip to content

Commit 130f29f

Browse files
authored
flexctl (#239)
* Adding flexctl * Cleanup * Backup name is now listed correctly * cleanup * whoops
1 parent f756231 commit 130f29f

File tree

11 files changed

+336
-21
lines changed

11 files changed

+336
-21
lines changed

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/failover_validation ./cmd/f
1212
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/pg_unregister ./cmd/pg_unregister
1313
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/start_monitor ./cmd/monitor
1414
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/start_admin_server ./cmd/admin_server
15+
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/flexctl ./cmd/flexctl
16+
1517
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/start ./cmd/start
1618

1719
COPY ./bin/* /fly/bin/
@@ -20,6 +22,8 @@ FROM wrouesnel/postgres_exporter:latest AS postgres_exporter
2022
FROM postgres:${PG_VERSION}
2123
ENV PGDATA=/data/postgresql
2224
ENV PGPASSFILE=/data/.pgpass
25+
ENV AWS_SHARED_CREDENTIALS_FILE=/data/.aws/credentials
26+
2327
ARG VERSION
2428
ARG PG_MAJOR_VERSION
2529
ARG POSTGIS_MAJOR=3

Dockerfile-timescaledb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/failover_validation ./cmd/f
1212
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/pg_unregister ./cmd/pg_unregister
1313
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/start_monitor ./cmd/monitor
1414
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/start_admin_server ./cmd/admin_server
15+
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/flexctl ./cmd/flexctl
16+
1517
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /fly/bin/start ./cmd/start
1618

1719
COPY ./bin/* /fly/bin/
@@ -21,6 +23,8 @@ FROM wrouesnel/postgres_exporter:latest AS postgres_exporter
2123
FROM postgres:${PG_VERSION}
2224
ENV PGDATA=/data/postgresql
2325
ENV PGPASSFILE=/data/.pgpass
26+
ENV AWS_SHARED_CREDENTIALS_FILE=/data/.aws/credentials
27+
2428
ARG VERSION
2529
ARG PG_MAJOR_VERSION
2630
ARG POSTGIS_MAJOR=3

cmd/flexctl/backups.go

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"time"
8+
9+
"github.com/fly-apps/postgres-flex/internal/flypg"
10+
"github.com/fly-apps/postgres-flex/internal/flypg/state"
11+
"github.com/olekukonko/tablewriter"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
var backupListCmd = &cobra.Command{
16+
Use: "list",
17+
Short: "Lists all backups",
18+
Long: `Lists all available backups created.`,
19+
RunE: func(cmd *cobra.Command, args []string) error {
20+
if !backupsEnabled() {
21+
return fmt.Errorf("backups are not enabled")
22+
}
23+
24+
return listBackups(cmd)
25+
},
26+
Args: cobra.NoArgs,
27+
}
28+
29+
var backupCreateCmd = &cobra.Command{
30+
Use: "create",
31+
Short: "Creates a new backup",
32+
Long: `Creates a new backup.`,
33+
RunE: func(cmd *cobra.Command, args []string) error {
34+
if !backupsEnabled() {
35+
return fmt.Errorf("backups are not enabled")
36+
}
37+
38+
if err := createBackup(cmd); err != nil {
39+
return err
40+
}
41+
42+
fmt.Println("Backup completed successfully!")
43+
44+
return nil
45+
},
46+
Args: cobra.NoArgs,
47+
}
48+
49+
var backupShowCmd = &cobra.Command{
50+
Use: "show",
51+
Short: "Shows details about a specific backup",
52+
Long: `Shows details about a specific backup.`,
53+
RunE: func(cmd *cobra.Command, args []string) error {
54+
if !backupsEnabled() {
55+
return fmt.Errorf("backups are not enabled")
56+
}
57+
return showBackup(cmd, args)
58+
},
59+
Args: cobra.ExactArgs(1),
60+
}
61+
62+
func showBackup(cmd *cobra.Command, args []string) error {
63+
id := args[0]
64+
65+
ctx, cancel := context.WithTimeout(cmd.Context(), 10*time.Second)
66+
defer cancel()
67+
68+
store, err := state.NewStore()
69+
if err != nil {
70+
return fmt.Errorf("failed to initialize store: %v", err)
71+
}
72+
73+
barman, err := flypg.NewBarman(store, os.Getenv("S3_ARCHIVE_CONFIG"), flypg.DefaultAuthProfile)
74+
if err != nil {
75+
return fmt.Errorf("failed to initialize barman: %v", err)
76+
}
77+
78+
backupDetails, err := barman.ShowBackup(ctx, id)
79+
if err != nil {
80+
return fmt.Errorf("failed to get backup details: %v", err)
81+
}
82+
83+
fmt.Println(string(backupDetails))
84+
85+
return nil
86+
}
87+
88+
func createBackup(cmd *cobra.Command) error {
89+
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Minute)
90+
defer cancel()
91+
92+
n, err := flypg.NewNode()
93+
if err != nil {
94+
return fmt.Errorf("failed to initialize node: %v", err)
95+
}
96+
97+
conn, err := n.RepMgr.NewLocalConnection(ctx)
98+
if err != nil {
99+
return fmt.Errorf("failed to connect to local db: %v", err)
100+
}
101+
defer func() { _ = conn.Close(ctx) }()
102+
103+
isPrimary, err := n.RepMgr.IsPrimary(ctx, conn)
104+
if err != nil {
105+
return fmt.Errorf("failed to determine if node is primary: %v", err)
106+
}
107+
108+
if !isPrimary {
109+
return fmt.Errorf("backups can only be performed against the primary node")
110+
}
111+
112+
store, err := state.NewStore()
113+
if err != nil {
114+
return fmt.Errorf("failed to initialize store: %v", err)
115+
}
116+
117+
barman, err := flypg.NewBarman(store, os.Getenv("S3_ARCHIVE_CONFIG"), flypg.DefaultAuthProfile)
118+
if err != nil {
119+
return fmt.Errorf("failed to initialize barman: %v", err)
120+
}
121+
122+
name, err := cmd.Flags().GetString("name")
123+
if err != nil {
124+
return fmt.Errorf("failed to get name flag: %v", err)
125+
}
126+
127+
immediateCheckpoint, err := cmd.Flags().GetBool("immediate-checkpoint")
128+
if err != nil {
129+
return fmt.Errorf("failed to get immediate-checkpoint flag: %v", err)
130+
}
131+
132+
cfg := flypg.BackupConfig{
133+
ImmediateCheckpoint: immediateCheckpoint,
134+
Name: name,
135+
}
136+
137+
fmt.Println("Performing backup...")
138+
139+
if _, err := barman.Backup(ctx, cfg); err != nil {
140+
return fmt.Errorf("failed to create backup: %v", err)
141+
}
142+
143+
return nil
144+
}
145+
146+
func listBackups(cmd *cobra.Command) error {
147+
ctx, cancel := context.WithTimeout(cmd.Context(), 10*time.Second)
148+
defer cancel()
149+
150+
store, err := state.NewStore()
151+
if err != nil {
152+
return fmt.Errorf("failed to initialize store: %v", err)
153+
}
154+
155+
barman, err := flypg.NewBarman(store, os.Getenv("S3_ARCHIVE_CONFIG"), flypg.DefaultAuthProfile)
156+
if err != nil {
157+
return fmt.Errorf("failed to initialize barman: %v", err)
158+
}
159+
160+
isJSON, err := cmd.Flags().GetBool("json")
161+
if err != nil {
162+
return fmt.Errorf("failed to get json flag: %v", err)
163+
}
164+
165+
if isJSON {
166+
jsonBytes, err := barman.ListRawBackups(cmd.Context())
167+
if err != nil {
168+
return fmt.Errorf("failed to list backups: %v", err)
169+
}
170+
171+
fmt.Println(string(jsonBytes))
172+
return nil
173+
}
174+
175+
backupList, err := barman.ListBackups(ctx)
176+
if err != nil {
177+
return fmt.Errorf("failed to list backups: %v", err)
178+
}
179+
180+
if len(backupList.Backups) == 0 {
181+
fmt.Println("No backups found")
182+
return nil
183+
}
184+
185+
var filterStatus string
186+
187+
filterStatus, err = cmd.Flags().GetString("status")
188+
if err != nil {
189+
return fmt.Errorf("failed to get status flag: %v", err)
190+
}
191+
192+
table := tablewriter.NewWriter(os.Stdout)
193+
table.SetHeader([]string{"ID", "Name", "Status", "End time", "Begin WAL"})
194+
195+
// Set table alignment, borders, padding, etc. as needed
196+
table.SetAlignment(tablewriter.ALIGN_LEFT)
197+
table.SetBorder(true) // Set to false to hide borders
198+
table.SetCenterSeparator("|")
199+
table.SetColumnSeparator("|")
200+
table.SetRowSeparator("-")
201+
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
202+
table.SetHeaderLine(true) // Enable header line
203+
table.SetAutoWrapText(false)
204+
205+
for _, b := range backupList.Backups {
206+
if filterStatus != "" && b.Status != filterStatus {
207+
continue
208+
}
209+
210+
table.Append([]string{
211+
b.ID,
212+
b.Name,
213+
b.Status,
214+
b.EndTime,
215+
b.BeginWal,
216+
})
217+
}
218+
219+
table.Render()
220+
221+
return nil
222+
}
223+
224+
func backupsEnabled() bool {
225+
return os.Getenv("S3_ARCHIVE_CONFIG") != ""
226+
}

cmd/flexctl/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
func main() {
11+
var rootCmd = &cobra.Command{Use: "flexctl"}
12+
13+
// Backup commands
14+
var backupCmd = &cobra.Command{Use: "backup"}
15+
16+
rootCmd.AddCommand(backupCmd)
17+
backupCmd.AddCommand(backupListCmd)
18+
backupCmd.AddCommand(backupCreateCmd)
19+
backupCmd.AddCommand(backupShowCmd)
20+
21+
if err := rootCmd.Execute(); err != nil {
22+
fmt.Println(err)
23+
os.Exit(1)
24+
}
25+
}
26+
27+
func init() {
28+
// Backup commands
29+
backupListCmd.Flags().StringP("status", "s", "", "Filter backups by status (Not applicable for JSON output)")
30+
backupListCmd.Flags().BoolP("json", "", false, "Output in JSON format")
31+
32+
backupShowCmd.Flags().BoolP("json", "", false, "Output in JSON format")
33+
34+
backupCreateCmd.Flags().StringP("name", "n", "", "Name of the backup")
35+
backupCreateCmd.Flags().BoolP("immediate-checkpoint", "", false, "Forces Postgres to perform an immediate checkpoint")
36+
}

cmd/monitor/monitor_backup_schedule.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ func performBaseBackup(ctx context.Context, barman *flypg.Barman, immediateCheck
149149
case <-ctx.Done():
150150
return ctx.Err()
151151
default:
152-
if _, err := barman.Backup(ctx, immediateCheckpoint); err != nil {
152+
cfg := flypg.BackupConfig{ImmediateCheckpoint: immediateCheckpoint}
153+
if _, err := barman.Backup(ctx, cfg); err != nil {
153154
log.Printf("[WARN] Failed to perform full backup: %s. Retrying in 30 seconds.", err)
154155

155156
// If we've exceeded the maximum number of retries, we should return an error.

cmd/start/main.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,6 @@ func main() {
5555
return
5656
}
5757

58-
// TODO - Find a better way to handle this
59-
if os.Getenv("S3_ARCHIVE_CONFIG") != "" || os.Getenv("S3_ARCHIVE_REMOTE_RESTORE_CONFIG") != "" {
60-
if err := os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "/data/.aws/credentials"); err != nil {
61-
panicHandler(err)
62-
return
63-
}
64-
}
65-
6658
node, err := flypg.NewNode()
6759
if err != nil {
6860
panicHandler(err)

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,20 @@ require (
2525
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
2626
github.com/hashicorp/golang-lru v0.5.4 // indirect
2727
github.com/hashicorp/serf v0.10.1 // indirect
28+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2829
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
2930
github.com/jackc/pgio v1.0.0 // indirect
3031
github.com/jackc/pgpassfile v1.0.0 // indirect
3132
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
3233
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
3334
github.com/mattn/go-colorable v0.1.6 // indirect
3435
github.com/mattn/go-isatty v0.0.12 // indirect
36+
github.com/mattn/go-runewidth v0.0.9 // indirect
3537
github.com/mitchellh/go-homedir v1.1.0 // indirect
3638
github.com/mitchellh/mapstructure v1.4.1 // indirect
39+
github.com/olekukonko/tablewriter v0.0.5 // indirect
40+
github.com/spf13/cobra v1.8.1 // indirect
41+
github.com/spf13/pflag v1.0.5 // indirect
3742
github.com/stretchr/objx v0.5.0 // indirect
3843
golang.org/x/crypto v0.20.0 // indirect
3944
golang.org/x/sys v0.17.0 // indirect

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
44
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
55
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
66
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
7+
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
78
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
89
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
910
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -58,6 +59,8 @@ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR
5859
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
5960
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
6061
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
62+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
63+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6164
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
6265
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
6366
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
@@ -90,6 +93,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
9093
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
9194
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
9295
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
96+
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
97+
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
9398
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
9499
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
95100
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -101,6 +106,8 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb
101106
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
102107
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
103108
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
109+
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
110+
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
104111
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
105112
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
106113
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -112,10 +119,15 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
112119
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
113120
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
114121
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
122+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
115123
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
116124
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
117125
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
118126
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
127+
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
128+
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
129+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
130+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
119131
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
120132
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
121133
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=

0 commit comments

Comments
 (0)