Skip to content

Instantly share code, notes, and snippets.

@tobstarr
Last active August 29, 2015 13:57
Show Gist options
  • Save tobstarr/9505300 to your computer and use it in GitHub Desktop.
Save tobstarr/9505300 to your computer and use it in GitHub Desktop.
bin_name = $(shell basename $$(pwd))
linux_bin_path = $(GOPATH)/bin/linux_amd64/$(bin_name)
default:
go get .
release:
rm -f $(linux_bin_path)
GOOS=linux GOARCH=amd64 go get .
cp $(linux_bin_path) $(bin_name).linux.amd64
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"time"
)
var (
logger = log.New(os.Stderr, "[pg_basedump] ", 0)
tmpPort = "54321"
)
func main() {
user := flag.String("U", "postgres", "postgres user to use for base backup (needs replication privilege)")
host := flag.String("H", "", "postgres master to create base backup from")
database := flag.String("d", "", "database to dump. Default: all")
flag.Parse()
action := &PgBaseDump{Host: *host, User: *user, Database: *database}
if e := action.Execute(); e != nil {
logger.Fatal(e)
}
}
type PgBaseDump struct {
Host string
User string
Database string
dataDir string
}
func (a *PgBaseDump) Execute() error {
e := a.setDataDir()
if e != nil {
return e
}
defer os.RemoveAll(a.dataDir)
e = a.baseBackup()
if e != nil {
return e
}
c, e := a.startTmpPostgres()
if e != nil {
return e
}
defer c.Process.Kill()
e = a.waitForTmpPostgres()
if e != nil {
return e
}
return a.dump()
}
func (a *PgBaseDump) setDataDir() (e error) {
a.dataDir, e = ioutil.TempDir(os.TempDir(), "pg_basedump")
logger.Printf("using data dir %s", a.dataDir)
return e
}
func (a *PgBaseDump) baseBackup() error {
baseBackupArguments := []string{"-Pvx", "-U", a.User, "-D", a.dataDir}
if a.Host != "" {
baseBackupArguments = append(baseBackupArguments, "-h", a.User)
}
c := exec.Command("pg_basebackup", baseBackupArguments...)
c.Stdout = os.Stderr
c.Stderr = os.Stderr
return c.Run()
}
func (a *PgBaseDump) startTmpPostgres() (*exec.Cmd, error) {
logger.Printf("staring tmp postgres")
c := exec.Command("postgres", "-D", a.dataDir, "-p", tmpPort)
c.Stdout = os.Stderr
c.Stderr = os.Stderr
e := c.Start()
if e != nil {
return c, e
}
return c, nil
}
func (a *PgBaseDump) waitForTmpPostgres() error {
defer benchmark("waiting for tmp postgres")()
started := time.Now()
timeout := 60 * time.Second
for {
_, e := exec.Command("psql", "-p", tmpPort, "-c", "SELECT 1").CombinedOutput()
if e != nil {
time.Sleep(10 * time.Millisecond)
if time.Since(started) > timeout {
return fmt.Errorf("timeout connecting to postgres")
}
continue
}
logger.Print("able to connect")
return nil
}
}
func (a *PgBaseDump) dump() error {
defer benchmark("sql dump")()
logger.Printf("started sql dump")
dumpCommand := a.dumpCommand()
dumpCommand.Stdout = os.Stdout
dumpCommand.Stderr = os.Stderr
return dumpCommand.Run()
}
func (a *PgBaseDump) dumpCommand() *exec.Cmd {
if a.Database != "" {
return exec.Command("pg_dump", "-p", tmpPort, a.Database)
}
return exec.Command("pg_dumpall", "-p", tmpPort)
}
func benchmark(message string) func() {
logger.Printf("STARTED %s", message)
started := time.Now()
return func() {
logger.Printf("FINISHED %s in %.1fs", message, time.Since(started).Seconds())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment