-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
122 lines (106 loc) · 3.2 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package main
import (
"database/sql"
"fmt"
"os"
"github.com/Urethramancer/signor/opt"
_ "github.com/go-sql-driver/mysql"
"github.com/grimdork/sqldump"
_ "github.com/lib/pq"
)
var o struct {
opt.DefaultHelp
Config string `short:"C" long:"config" help:"Configuration file for database and backup options." default:"/etc/dbak/config.json" placeholder:"FILE"`
Path string `short:"p" long:"path" help:"Output directory for dump files." default:"/tmp" placeholder:"PATH"`
Base string `short:"b" long:"base" help:"Base name for dump files." default:"dump" placeholder:"NAME"`
Full bool `short:"F" long:"full" help:"Perform full backup even if no changes."`
Prune int `short:"P" long:"prune" help:"Remove files older than the specified number of days." placeholder:"DAYS"`
MaxRows int64 `short:"m" long:"maxrows" help:"Number of rows to fetch at a time, to avoid memory running out on large databases." placeholder:"N" default:"1000"`
}
func main() {
a := opt.Parse(&o)
if o.Help || o.Path == "" || o.Base == "" || o.Config == "" {
a.Usage()
return
}
cfg, err := loadConfig(o.Config)
fail(err)
t := cfg.Type
if t != "mysql" && t != "postgres" {
t = "mysql"
}
sslmode := "disable"
var conn string
if cfg.Type == "postgres" {
conn = fmt.Sprintf(
"host=%s port=%s dbname=%s user=%s password=%s sslmode=%s",
cfg.Host, cfg.Port, cfg.Name, cfg.Username, cfg.Password, sslmode,
)
} else {
conn = fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s",
cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Name,
)
}
db, err := sql.Open(t, conn)
fail(err)
db.SetMaxIdleConns(100)
db.SetMaxOpenConns(100)
dumper, err := sqldump.NewDumper(db, o.Path, o.Base+"-20060102T150405.sql")
fail(err)
defer dumper.Close()
dumper.SetMaxRows(o.MaxRows)
b, err := NewBucket(cfg.Region, cfg.Bucket)
fail(err)
dates := b.LoadTableDates()
if o.Full || len(cfg.Tables) == 0 {
err = dumper.Dump()
} else {
// In selective table mode we're also looking for the last updated date.
// NOTE: This doesn't work with all database storage types.
list := []string{}
for _, t := range cfg.Tables {
date, err := getLastUpdate(db, cfg.Name, t)
if err != nil {
// No date exists, just include the table.
list = append(list, t)
} else {
d, ok := dates.Dates[t]
if !ok || d == date {
// No previous date, or last modified date is different.
list = append(list, t)
}
}
// Update the map with the latest date found.
dates.Dates[t] = date
}
err = dumper.Dump(list...)
}
fail(err)
err = b.UpdateTableDates(dates)
fail(err)
defer os.Remove(dumper.Path())
pr("Dumped to %s", dumper.Path())
err = b.Upload(dumper.Path())
fail(err)
if o.Prune > 0 {
c, err := b.Prune(o.Base, o.Prune)
fail(err)
pr("Pruned %d files.", c)
}
}
func pr(format string, v ...interface{}) {
fmt.Printf(format+"\n", v...)
}
func fail(err error) {
if err == nil {
return
}
fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
os.Exit(2)
}
func getLastUpdate(db *sql.DB, dbname, table string) (string, error) {
var date string
err := db.QueryRow("SELECT UPDATE_TIME FROM information_schema.tables WHERE TABLE_SCHEMA = '" + dbname + "' AND TABLE_NAME ='" + table + "'").Scan(&date)
return date, err
}