-
Notifications
You must be signed in to change notification settings - Fork 77
Start supporting barman #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
60ac465
Start supporting barman
lubien 51d390f
Remove deprecated fns
lubien 250a911
Delete unused
lubien debbc9e
Fix home
lubien b6cdc1c
Fix pgpass issues
lubien 5f247fe
Try trigger switch-wal and cron on startup
lubien 594dd4a
Make ssh just work with barman
lubien 2331a59
Use base image instead of barman
lubien 3e9cb28
Add health checks to barman
lubien 7c95cc7
Missing var oopsie
lubien 07d3a15
Oopsie command
lubien 9501aea
We need to start admin to get checks
lubien cb799a3
barman repmgr settings
lubien b507732
Ignore repmgr for now
lubien 67e5614
Pg should also be able to root ssh when needed
lubien 6f77df6
No early return
lubien 803e573
Consistency
lubien 33bbff5
Comment
lubien 21c32be
Mininmal pg_hba needed
lubien dfad0bd
Lint
lubien 03ee142
Fix defer
lubien 441c2ae
No capital error
lubien 7f883f2
Ignore ctx
lubien 8ffdf26
Simplify settings
lubien 1245efa
Simplify role check
lubien 6e95709
Add some tests
lubien ae3b856
Fix second .pgpass
lubien ae2bd87
Timescaledb too
lubien File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| package flybarman | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
| "os/exec" | ||
|
|
||
| "github.com/fly-apps/postgres-flex/internal/flypg" | ||
| "github.com/fly-apps/postgres-flex/internal/flypg/admin" | ||
| ) | ||
|
|
||
| var dataDir = "/data" | ||
| var barmanConfigFile = dataDir + "/barman.conf" | ||
| var barmanCronFile = dataDir + "/barman.cron" | ||
| var globalBarmanConfigFile = "/etc/barman.conf" | ||
|
|
||
| type Node struct { | ||
| AppName string | ||
| PrivateIP string | ||
| PrimaryRegion string | ||
| DataDir string | ||
| Port int | ||
|
|
||
| BarmanHome string | ||
| LogFile string | ||
| PasswordConfigPath string | ||
|
|
||
| SUCredentials admin.Credential | ||
| OperatorCredentials admin.Credential | ||
| ReplCredentials admin.Credential | ||
| } | ||
|
|
||
| func NewNode() (*Node, error) { | ||
| node := &Node{ | ||
| AppName: "local", | ||
| BarmanHome: dataDir + "/barman.d", | ||
davissp14 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| LogFile: dataDir + "/barman.log", | ||
| PasswordConfigPath: "/root/.pgpass", | ||
| } | ||
|
|
||
| if appName := os.Getenv("FLY_APP_NAME"); appName != "" { | ||
| node.AppName = appName | ||
| } | ||
|
|
||
| // Internal user | ||
| node.SUCredentials = admin.Credential{ | ||
| Username: "flypgadmin", | ||
| Password: os.Getenv("SU_PASSWORD"), | ||
| } | ||
|
|
||
| // Postgres user | ||
| node.OperatorCredentials = admin.Credential{ | ||
| Username: "postgres", | ||
| Password: os.Getenv("OPERATOR_PASSWORD"), | ||
| } | ||
|
|
||
| // Repmgr user | ||
| node.ReplCredentials = admin.Credential{ | ||
| Username: "repmgr", | ||
| Password: os.Getenv("REPL_PASSWORD"), | ||
| } | ||
|
|
||
| return node, nil | ||
| } | ||
|
|
||
| func (n *Node) Init(ctx context.Context) error { | ||
| err := flypg.WriteSSHKey() | ||
| if err != nil { | ||
| return fmt.Errorf("failed write ssh keys: %s", err) | ||
| } | ||
|
|
||
| if _, err := os.Stat(barmanConfigFile); os.IsNotExist(err) { | ||
| barmanConfigFileContent := fmt.Sprintf(`[barman] | ||
| barman_user = root | ||
| barman_home = /data/barman.d | ||
| log_level = info | ||
| log_file = /data/barman.log | ||
| [pg] | ||
| description = "Fly.io Postgres Cluster" | ||
| conninfo = host=%s.internal user=repmgr dbname=postgres | ||
| streaming_conninfo = host=%s.internal user=repmgr dbname=postgres | ||
| backup_method = postgres | ||
| streaming_archiver = on | ||
| slot_name = barman | ||
| create_slot = auto | ||
| retention_policy_mode = auto | ||
lubien marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| retention_policy = RECOVERY WINDOW OF 7 days | ||
| wal_retention_policy = main | ||
| `, n.AppName, n.AppName) | ||
|
|
||
| if err := os.WriteFile(barmanConfigFile, []byte(barmanConfigFileContent), 0644); err != nil { | ||
| return fmt.Errorf("failed write %s: %s", barmanConfigFile, err) | ||
| } | ||
|
|
||
| log.Println(barmanConfigFile + " created successfully.") | ||
| } | ||
|
|
||
| if err := deleteGlobalBarmanFile(); err != nil { | ||
| return fmt.Errorf("failed delete /etc/barman.conf: %s", err) | ||
| } | ||
|
|
||
| if err := os.Symlink(barmanConfigFile, globalBarmanConfigFile); err != nil { | ||
| return fmt.Errorf("failed symlink %s to %s: %s", barmanConfigFile, globalBarmanConfigFile, err) | ||
| } | ||
|
|
||
| log.Println("Symbolic link to barman config created successfully.") | ||
|
|
||
| if err := os.MkdirAll(n.BarmanHome, os.ModePerm); err != nil { | ||
| return fmt.Errorf("failed to mkdir %s: %s", n.BarmanHome, err) | ||
| } | ||
|
|
||
| log.Println("Barman home directory successfully.") | ||
|
|
||
| passStr := fmt.Sprintf("*:*:*:%s:%s", n.ReplCredentials.Username, n.ReplCredentials.Password) | ||
| if err := os.WriteFile(n.PasswordConfigPath, []byte(passStr), 0700); err != nil { | ||
| return fmt.Errorf("failed to write file %s: %s", n.PasswordConfigPath, err) | ||
| } | ||
| // We need this in case the user ssh to the vm as root | ||
| if err := os.WriteFile("/.pgpass", []byte(passStr), 0700); err != nil { | ||
| return fmt.Errorf("failed to write file %s: %s", n.PasswordConfigPath, err) | ||
| } | ||
|
|
||
| if _, err := os.Stat(barmanCronFile); os.IsNotExist(err) { | ||
| barmanCronFileContent := `* * * * * /usr/bin/barman cron | ||
| ` | ||
| if err := os.WriteFile(barmanCronFile, []byte(barmanCronFileContent), 0644); err != nil { | ||
| return fmt.Errorf("failed write %s: %s", barmanCronFile, err) | ||
| } | ||
|
|
||
| log.Println(barmanCronFile + " created successfully.") | ||
| } | ||
|
|
||
| if _, err := os.Stat(n.LogFile); os.IsNotExist(err) { | ||
| file, err := os.Create(n.LogFile) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to touch %s: %s", n.LogFile, err) | ||
| } | ||
| defer func() { _ = file.Close() }() | ||
|
|
||
| log.Println(n.LogFile + " created successfully.") | ||
| } | ||
|
|
||
| crontabCommand := exec.Command("/usr/bin/crontab", barmanCronFile) | ||
| if _, err := crontabCommand.Output(); err != nil { | ||
| return fmt.Errorf("failed set crontab: %s", err) | ||
| } | ||
|
|
||
| log.Println("Crontab updated") | ||
|
|
||
| serviceCmd := exec.Command("/usr/sbin/service", "--version") | ||
| if err := serviceCmd.Run(); err != nil { | ||
| log.Println("service command not found, skipping initializing cron service") | ||
| } else { | ||
| serviceCronStartCommand := exec.Command("service", "cron", "start") | ||
| if _, err := serviceCronStartCommand.Output(); err != nil { | ||
| return fmt.Errorf("failed starting cron service: %s", err) | ||
| } | ||
| log.Println("Started cron service") | ||
| } | ||
|
|
||
| switchWalCommand := exec.Command("barman", "switch-wal", "--archive", "--force", "pg") | ||
| if _, err := switchWalCommand.Output(); err != nil { | ||
| log.Println(fmt.Errorf("failed switching WAL: %s", err)) | ||
| log.Println("try running `barman switch-wal --archive --force pg` or wait for the next WAL") | ||
| } else { | ||
| log.Println("successfully switched WAL files to start barman") | ||
| } | ||
|
|
||
| cronCommand := exec.Command("barman", "cron") | ||
| if _, err := cronCommand.Output(); err != nil { | ||
| log.Println(fmt.Errorf("failed running barman cron: %s", err)) | ||
| log.Println("try running `cronCommand` or wait for the next run") | ||
| } else { | ||
| log.Println("successfully ran `barman cron`") | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func deleteGlobalBarmanFile() error { | ||
| if _, err := os.Stat(globalBarmanConfigFile); os.IsNotExist(err) { | ||
| return nil | ||
| } | ||
|
|
||
| if err := os.Remove(globalBarmanConfigFile); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| log.Println(globalBarmanConfigFile + " deleted successfully") | ||
| return nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package flycheck | ||
|
|
||
| import ( | ||
| "errors" | ||
| "os/exec" | ||
| "regexp" | ||
| "strings" | ||
|
|
||
| "github.com/superfly/fly-checks/check" | ||
| ) | ||
|
|
||
| func CheckBarmanConnection(checks *check.CheckSuite) *check.CheckSuite { | ||
| cmd := exec.Command("barman", "check", "pg") | ||
|
|
||
| output, err := cmd.CombinedOutput() | ||
| if err != nil { | ||
| checks.AddCheck("connection", func() (string, error) { | ||
| msg := "failed running `barman check pg`" | ||
| return "", errors.New(msg) | ||
| }) | ||
|
|
||
| return checks | ||
| } | ||
|
|
||
| // Each line besides the first represents a check and will include FAILED or OK | ||
| // We just separate those lines and create a health check entry of our own | ||
| // so it's uniform how we handle it | ||
| lines := strings.Split(string(output), "\n") | ||
|
|
||
davissp14 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for _, line := range lines { | ||
| pattern := `\s*(.*?):(.*)$` | ||
| regex := regexp.MustCompile(pattern) | ||
| matches := regex.FindStringSubmatch(line) | ||
|
|
||
| if len(matches) == 3 { | ||
| left := matches[1] | ||
| right := strings.Trim(matches[2], "") | ||
|
|
||
| if right == "" { | ||
| continue | ||
| } | ||
|
|
||
| checks.AddCheck(left, func() (string, error) { | ||
| if strings.Contains(right, "FAILED") { | ||
| return "", errors.New(right) | ||
| } | ||
|
|
||
| return right, nil | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return checks | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just wrap this: