nfm-backup 4.0.1a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.md +24 -0
- data/README.md +20 -0
- data/bin/backup +5 -0
- data/lib/backup.rb +133 -0
- data/lib/backup/archive.rb +170 -0
- data/lib/backup/binder.rb +22 -0
- data/lib/backup/cleaner.rb +116 -0
- data/lib/backup/cli.rb +364 -0
- data/lib/backup/cloud_io/base.rb +41 -0
- data/lib/backup/cloud_io/cloud_files.rb +298 -0
- data/lib/backup/cloud_io/s3.rb +260 -0
- data/lib/backup/compressor/base.rb +35 -0
- data/lib/backup/compressor/bzip2.rb +39 -0
- data/lib/backup/compressor/custom.rb +53 -0
- data/lib/backup/compressor/gzip.rb +74 -0
- data/lib/backup/config.rb +119 -0
- data/lib/backup/config/dsl.rb +102 -0
- data/lib/backup/config/helpers.rb +143 -0
- data/lib/backup/database/base.rb +85 -0
- data/lib/backup/database/mongodb.rb +186 -0
- data/lib/backup/database/mysql.rb +123 -0
- data/lib/backup/database/postgresql.rb +133 -0
- data/lib/backup/database/redis.rb +179 -0
- data/lib/backup/database/riak.rb +82 -0
- data/lib/backup/encryptor/base.rb +29 -0
- data/lib/backup/encryptor/gpg.rb +747 -0
- data/lib/backup/encryptor/open_ssl.rb +72 -0
- data/lib/backup/errors.rb +58 -0
- data/lib/backup/logger.rb +199 -0
- data/lib/backup/logger/console.rb +51 -0
- data/lib/backup/logger/fog_adapter.rb +29 -0
- data/lib/backup/logger/logfile.rb +119 -0
- data/lib/backup/logger/syslog.rb +116 -0
- data/lib/backup/model.rb +454 -0
- data/lib/backup/notifier/base.rb +98 -0
- data/lib/backup/notifier/campfire.rb +69 -0
- data/lib/backup/notifier/hipchat.rb +93 -0
- data/lib/backup/notifier/http_post.rb +122 -0
- data/lib/backup/notifier/mail.rb +238 -0
- data/lib/backup/notifier/nagios.rb +69 -0
- data/lib/backup/notifier/prowl.rb +69 -0
- data/lib/backup/notifier/pushover.rb +80 -0
- data/lib/backup/notifier/slack.rb +149 -0
- data/lib/backup/notifier/twitter.rb +65 -0
- data/lib/backup/package.rb +51 -0
- data/lib/backup/packager.rb +101 -0
- data/lib/backup/pipeline.rb +124 -0
- data/lib/backup/splitter.rb +76 -0
- data/lib/backup/storage/base.rb +57 -0
- data/lib/backup/storage/cloud_files.rb +158 -0
- data/lib/backup/storage/cycler.rb +65 -0
- data/lib/backup/storage/dropbox.rb +236 -0
- data/lib/backup/storage/ftp.rb +98 -0
- data/lib/backup/storage/local.rb +64 -0
- data/lib/backup/storage/ninefold.rb +74 -0
- data/lib/backup/storage/rsync.rb +248 -0
- data/lib/backup/storage/s3.rb +154 -0
- data/lib/backup/storage/scp.rb +67 -0
- data/lib/backup/storage/sftp.rb +82 -0
- data/lib/backup/syncer/base.rb +70 -0
- data/lib/backup/syncer/cloud/base.rb +179 -0
- data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
- data/lib/backup/syncer/cloud/local_file.rb +100 -0
- data/lib/backup/syncer/cloud/s3.rb +110 -0
- data/lib/backup/syncer/rsync/base.rb +48 -0
- data/lib/backup/syncer/rsync/local.rb +31 -0
- data/lib/backup/syncer/rsync/pull.rb +51 -0
- data/lib/backup/syncer/rsync/push.rb +205 -0
- data/lib/backup/template.rb +46 -0
- data/lib/backup/utilities.rb +221 -0
- data/lib/backup/version.rb +5 -0
- data/templates/cli/archive +28 -0
- data/templates/cli/compressor/bzip2 +4 -0
- data/templates/cli/compressor/custom +7 -0
- data/templates/cli/compressor/gzip +4 -0
- data/templates/cli/config +123 -0
- data/templates/cli/databases/mongodb +15 -0
- data/templates/cli/databases/mysql +18 -0
- data/templates/cli/databases/postgresql +16 -0
- data/templates/cli/databases/redis +16 -0
- data/templates/cli/databases/riak +17 -0
- data/templates/cli/encryptor/gpg +27 -0
- data/templates/cli/encryptor/openssl +9 -0
- data/templates/cli/model +26 -0
- data/templates/cli/notifiers/campfire +12 -0
- data/templates/cli/notifiers/hipchat +15 -0
- data/templates/cli/notifiers/http_post +32 -0
- data/templates/cli/notifiers/mail +21 -0
- data/templates/cli/notifiers/nagios +13 -0
- data/templates/cli/notifiers/prowl +11 -0
- data/templates/cli/notifiers/pushover +11 -0
- data/templates/cli/notifiers/twitter +13 -0
- data/templates/cli/splitter +7 -0
- data/templates/cli/storages/cloud_files +11 -0
- data/templates/cli/storages/dropbox +19 -0
- data/templates/cli/storages/ftp +12 -0
- data/templates/cli/storages/local +7 -0
- data/templates/cli/storages/ninefold +9 -0
- data/templates/cli/storages/rsync +17 -0
- data/templates/cli/storages/s3 +14 -0
- data/templates/cli/storages/scp +14 -0
- data/templates/cli/storages/sftp +14 -0
- data/templates/cli/syncers/cloud_files +22 -0
- data/templates/cli/syncers/rsync_local +20 -0
- data/templates/cli/syncers/rsync_pull +28 -0
- data/templates/cli/syncers/rsync_push +28 -0
- data/templates/cli/syncers/s3 +27 -0
- data/templates/general/links +3 -0
- data/templates/general/version.erb +2 -0
- data/templates/notifier/mail/failure.erb +16 -0
- data/templates/notifier/mail/success.erb +16 -0
- data/templates/notifier/mail/warning.erb +16 -0
- data/templates/storage/dropbox/authorization_url.erb +6 -0
- data/templates/storage/dropbox/authorized.erb +4 -0
- data/templates/storage/dropbox/cache_file_written.erb +10 -0
- metadata +688 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Database
|
|
5
|
+
class MongoDB < Base
|
|
6
|
+
class Error < Backup::Error; end
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Name of the database that needs to get dumped
|
|
10
|
+
attr_accessor :name
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Credentials for the specified database
|
|
14
|
+
attr_accessor :username, :password
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Connectivity options
|
|
18
|
+
attr_accessor :host, :port
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# IPv6 support (disabled by default)
|
|
22
|
+
attr_accessor :ipv6
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# Collections to dump, collections that aren't specified won't get dumped
|
|
26
|
+
attr_accessor :only_collections
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Additional "mongodump" options
|
|
30
|
+
attr_accessor :additional_options
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Forces mongod to flush all pending write operations to the disk and
|
|
34
|
+
# locks the entire mongod instance to prevent additional writes until the
|
|
35
|
+
# dump is complete.
|
|
36
|
+
#
|
|
37
|
+
# Note that if Profiling is enabled, this will disable it and will not
|
|
38
|
+
# re-enable it after the dump is complete.
|
|
39
|
+
attr_accessor :lock
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Creates a dump of the database that includes an oplog, to create a
|
|
43
|
+
# point-in-time snapshot of the state of a mongod instance.
|
|
44
|
+
#
|
|
45
|
+
# If this option is used, you would not use the `lock` option.
|
|
46
|
+
#
|
|
47
|
+
# This will only work against nodes that maintain a oplog.
|
|
48
|
+
# This includes all members of a replica set, as well as master nodes in
|
|
49
|
+
# master/slave replication deployments.
|
|
50
|
+
attr_accessor :oplog
|
|
51
|
+
|
|
52
|
+
def initialize(model, database_id = nil, &block)
|
|
53
|
+
super
|
|
54
|
+
instance_eval(&block) if block_given?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def perform!
|
|
58
|
+
super
|
|
59
|
+
|
|
60
|
+
lock_database if @lock
|
|
61
|
+
dump!
|
|
62
|
+
package!
|
|
63
|
+
|
|
64
|
+
ensure
|
|
65
|
+
unlock_database if @lock
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Performs all required mongodump commands, dumping the output files
|
|
72
|
+
# into the +dump_packaging_path+ directory for packaging.
|
|
73
|
+
def dump!
|
|
74
|
+
FileUtils.mkdir_p dump_packaging_path
|
|
75
|
+
|
|
76
|
+
collections = Array(only_collections)
|
|
77
|
+
if collections.empty?
|
|
78
|
+
run(mongodump)
|
|
79
|
+
else
|
|
80
|
+
collections.each do |collection|
|
|
81
|
+
run("#{ mongodump } --collection='#{ collection }'")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Creates a tar archive of the +dump_packaging_path+ directory
|
|
88
|
+
# and stores it in the +dump_path+ using +dump_filename+.
|
|
89
|
+
#
|
|
90
|
+
# <trigger>/databases/MongoDB[-<database_id>].tar[.gz]
|
|
91
|
+
#
|
|
92
|
+
# If successful, +dump_packaging_path+ is removed.
|
|
93
|
+
def package!
|
|
94
|
+
pipeline = Pipeline.new
|
|
95
|
+
dump_ext = 'tar'
|
|
96
|
+
|
|
97
|
+
pipeline << "#{ utility(:tar) } -cf - " +
|
|
98
|
+
"-C '#{ dump_path }' '#{ dump_filename }'"
|
|
99
|
+
|
|
100
|
+
model.compressor.compress_with do |command, ext|
|
|
101
|
+
pipeline << command
|
|
102
|
+
dump_ext << ext
|
|
103
|
+
end if model.compressor
|
|
104
|
+
|
|
105
|
+
pipeline << "#{ utility(:cat) } > " +
|
|
106
|
+
"'#{ File.join(dump_path, dump_filename) }.#{ dump_ext }'"
|
|
107
|
+
|
|
108
|
+
pipeline.run
|
|
109
|
+
if pipeline.success?
|
|
110
|
+
FileUtils.rm_rf dump_packaging_path
|
|
111
|
+
log!(:finished)
|
|
112
|
+
else
|
|
113
|
+
raise Error, "Dump Failed!\n" + pipeline.error_messages
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def dump_packaging_path
|
|
118
|
+
File.join(dump_path, dump_filename)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def mongodump
|
|
122
|
+
"#{ utility(:mongodump) } #{ name_option } #{ credential_options } " +
|
|
123
|
+
"#{ connectivity_options } #{ ipv6_option } #{ oplog_option } " +
|
|
124
|
+
"#{ user_options } --out='#{ dump_packaging_path }'"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def name_option
|
|
128
|
+
"--db='#{ name }'" if name
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def credential_options
|
|
132
|
+
opts = []
|
|
133
|
+
opts << "--username='#{ username }'" if username
|
|
134
|
+
opts << "--password='#{ password }'" if password
|
|
135
|
+
opts.join(' ')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def connectivity_options
|
|
139
|
+
opts = []
|
|
140
|
+
opts << "--host='#{ host }'" if host
|
|
141
|
+
opts << "--port='#{ port }'" if port
|
|
142
|
+
opts.join(' ')
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def ipv6_option
|
|
146
|
+
'--ipv6' if ipv6
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def oplog_option
|
|
150
|
+
'--oplog' if oplog
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def user_options
|
|
154
|
+
Array(additional_options).join(' ')
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def lock_database
|
|
158
|
+
lock_command = <<-EOS.gsub(/^ +/, '')
|
|
159
|
+
echo 'use admin
|
|
160
|
+
db.setProfilingLevel(0)
|
|
161
|
+
db.fsyncLock()' | #{ mongo_shell }
|
|
162
|
+
EOS
|
|
163
|
+
|
|
164
|
+
run(lock_command)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def unlock_database
|
|
168
|
+
unlock_command = <<-EOS.gsub(/^ +/, '')
|
|
169
|
+
echo 'use admin
|
|
170
|
+
db.fsyncUnlock()' | #{ mongo_shell }
|
|
171
|
+
EOS
|
|
172
|
+
|
|
173
|
+
run(unlock_command)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def mongo_shell
|
|
177
|
+
cmd = "#{ utility(:mongo) } #{ connectivity_options }".rstrip
|
|
178
|
+
cmd << " #{ credential_options }".rstrip
|
|
179
|
+
cmd << " #{ ipv6_option }".rstrip
|
|
180
|
+
cmd << " '#{ name }'" if name
|
|
181
|
+
cmd
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Database
|
|
5
|
+
class MySQL < Base
|
|
6
|
+
class Error < Backup::Error; end
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Name of the database that needs to get dumped
|
|
10
|
+
# To dump all databases, set this to `:all` or leave blank.
|
|
11
|
+
attr_accessor :name
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Credentials for the specified database
|
|
15
|
+
attr_accessor :username, :password
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Connectivity options
|
|
19
|
+
attr_accessor :host, :port, :socket
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Tables to skip while dumping the database
|
|
23
|
+
#
|
|
24
|
+
# If `name` is set to :all (or not specified), these must include
|
|
25
|
+
# a database name. e.g. 'name.table'.
|
|
26
|
+
# If `name` is given, these may simply be table names.
|
|
27
|
+
attr_accessor :skip_tables
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Tables to dump. This in only valid if `name` is specified.
|
|
31
|
+
# If none are given, the entire database will be dumped.
|
|
32
|
+
attr_accessor :only_tables
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Additional "mysqldump" options
|
|
36
|
+
attr_accessor :additional_options
|
|
37
|
+
|
|
38
|
+
def initialize(model, database_id = nil, &block)
|
|
39
|
+
super
|
|
40
|
+
instance_eval(&block) if block_given?
|
|
41
|
+
|
|
42
|
+
@name ||= :all
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Performs the mysqldump command and outputs the dump file
|
|
47
|
+
# in the +dump_path+ using +dump_filename+.
|
|
48
|
+
#
|
|
49
|
+
# <trigger>/databases/MySQL[-<database_id>].sql[.gz]
|
|
50
|
+
def perform!
|
|
51
|
+
super
|
|
52
|
+
|
|
53
|
+
pipeline = Pipeline.new
|
|
54
|
+
dump_ext = 'sql'
|
|
55
|
+
|
|
56
|
+
pipeline << mysqldump
|
|
57
|
+
|
|
58
|
+
model.compressor.compress_with do |command, ext|
|
|
59
|
+
pipeline << command
|
|
60
|
+
dump_ext << ext
|
|
61
|
+
end if model.compressor
|
|
62
|
+
|
|
63
|
+
pipeline << "#{ utility(:cat) } > " +
|
|
64
|
+
"'#{ File.join(dump_path, dump_filename) }.#{ dump_ext }'"
|
|
65
|
+
|
|
66
|
+
pipeline.run
|
|
67
|
+
if pipeline.success?
|
|
68
|
+
log!(:finished)
|
|
69
|
+
else
|
|
70
|
+
raise Error, "Dump Failed!\n" + pipeline.error_messages
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def mysqldump
|
|
77
|
+
"#{ utility(:mysqldump) } #{ credential_options } " +
|
|
78
|
+
"#{ connectivity_options } #{ user_options } #{ name_option } " +
|
|
79
|
+
"#{ tables_to_dump } #{ tables_to_skip }"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def credential_options
|
|
83
|
+
opts = []
|
|
84
|
+
opts << "--user='#{ username }'" if username
|
|
85
|
+
opts << "--password='#{ password }'" if password
|
|
86
|
+
opts.join(' ')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def connectivity_options
|
|
90
|
+
return "--socket='#{ socket }'" if socket
|
|
91
|
+
|
|
92
|
+
opts = []
|
|
93
|
+
opts << "--host='#{ host }'" if host
|
|
94
|
+
opts << "--port='#{ port }'" if port
|
|
95
|
+
opts.join(' ')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def user_options
|
|
99
|
+
Array(additional_options).join(' ')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def name_option
|
|
103
|
+
dump_all? ? '--all-databases' : name
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def tables_to_dump
|
|
107
|
+
Array(only_tables).join(' ') unless dump_all?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def tables_to_skip
|
|
111
|
+
Array(skip_tables).map do |table|
|
|
112
|
+
table = (dump_all? || table['.']) ? table : "#{ name }.#{ table }"
|
|
113
|
+
"--ignore-table='#{ table }'"
|
|
114
|
+
end.join(' ')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def dump_all?
|
|
118
|
+
name == :all
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Database
|
|
5
|
+
class PostgreSQL < Base
|
|
6
|
+
class Error < Backup::Error; end
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Name of the database that needs to get dumped.
|
|
10
|
+
# To dump all databases, set this to `:all` or leave blank.
|
|
11
|
+
# +username+ must be a PostgreSQL superuser to run `pg_dumpall`.
|
|
12
|
+
attr_accessor :name
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Credentials for the specified database
|
|
16
|
+
attr_accessor :username, :password
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# If set the pg_dump(all) command is executed as the given user
|
|
20
|
+
attr_accessor :sudo_user
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Connectivity options
|
|
24
|
+
attr_accessor :host, :port, :socket
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Tables to skip while dumping the database.
|
|
28
|
+
# If `name` is set to :all (or not specified), these are ignored.
|
|
29
|
+
attr_accessor :skip_tables
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Tables to dump. This in only valid if `name` is specified.
|
|
33
|
+
# If none are given, the entire database will be dumped.
|
|
34
|
+
attr_accessor :only_tables
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Additional "pg_dump" or "pg_dumpall" options
|
|
38
|
+
attr_accessor :additional_options
|
|
39
|
+
|
|
40
|
+
def initialize(model, database_id = nil, &block)
|
|
41
|
+
super
|
|
42
|
+
instance_eval(&block) if block_given?
|
|
43
|
+
|
|
44
|
+
@name ||= :all
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Performs the mysqldump command and outputs the dump file
|
|
49
|
+
# in the +dump_path+ using +dump_filename+.
|
|
50
|
+
#
|
|
51
|
+
# <trigger>/databases/PostgreSQL[-<database_id>].sql[.gz]
|
|
52
|
+
def perform!
|
|
53
|
+
super
|
|
54
|
+
|
|
55
|
+
pipeline = Pipeline.new
|
|
56
|
+
dump_ext = 'sql'
|
|
57
|
+
|
|
58
|
+
pipeline << (dump_all? ? pgdumpall : pgdump)
|
|
59
|
+
|
|
60
|
+
model.compressor.compress_with do |command, ext|
|
|
61
|
+
pipeline << command
|
|
62
|
+
dump_ext << ext
|
|
63
|
+
end if model.compressor
|
|
64
|
+
|
|
65
|
+
pipeline << "#{ utility(:cat) } > " +
|
|
66
|
+
"'#{ File.join(dump_path, dump_filename) }.#{ dump_ext }'"
|
|
67
|
+
|
|
68
|
+
pipeline.run
|
|
69
|
+
if pipeline.success?
|
|
70
|
+
log!(:finished)
|
|
71
|
+
else
|
|
72
|
+
raise Error, "Dump Failed!\n" + pipeline.error_messages
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def pgdump
|
|
77
|
+
"#{ password_option }" +
|
|
78
|
+
"#{ sudo_option }" +
|
|
79
|
+
"#{ utility(:pg_dump) } #{ username_option } #{ connectivity_options } " +
|
|
80
|
+
"#{ user_options } #{ tables_to_dump } #{ tables_to_skip } #{ name }"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def pgdumpall
|
|
84
|
+
"#{ password_option }" +
|
|
85
|
+
"#{ sudo_option }" +
|
|
86
|
+
"#{ utility(:pg_dumpall) } #{ username_option } " +
|
|
87
|
+
"#{ connectivity_options } #{ user_options }"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def password_option
|
|
91
|
+
"PGPASSWORD='#{ password }' " if password
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def sudo_option
|
|
95
|
+
"#{ utility(:sudo) } -n -u #{ sudo_user } " if sudo_user
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def username_option
|
|
99
|
+
"--username='#{ username }'" if username
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def connectivity_options
|
|
103
|
+
return "--host='#{ socket }'" if socket
|
|
104
|
+
|
|
105
|
+
opts = []
|
|
106
|
+
opts << "--host='#{ host }'" if host
|
|
107
|
+
opts << "--port='#{ port }'" if port
|
|
108
|
+
opts.join(' ')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def user_options
|
|
112
|
+
Array(additional_options).join(' ')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def tables_to_dump
|
|
116
|
+
Array(only_tables).map do |table|
|
|
117
|
+
"--table='#{ table }'"
|
|
118
|
+
end.join(' ')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def tables_to_skip
|
|
122
|
+
Array(skip_tables).map do |table|
|
|
123
|
+
"--exclude-table='#{ table }'"
|
|
124
|
+
end.join(' ')
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def dump_all?
|
|
128
|
+
name == :all
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Database
|
|
5
|
+
class Redis < Base
|
|
6
|
+
class Error < Backup::Error; end
|
|
7
|
+
|
|
8
|
+
MODES = [:copy, :sync]
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# Mode of operation.
|
|
12
|
+
#
|
|
13
|
+
# [:copy]
|
|
14
|
+
# Copies the redis dump file specified by {#rdb_path}.
|
|
15
|
+
# This data will be current as of the last RDB Snapshot
|
|
16
|
+
# performed by the server (per your redis.conf settings).
|
|
17
|
+
# You may set {#invoke_save} to +true+ to have Backup issue
|
|
18
|
+
# a +SAVE+ command to update the dump file with the current
|
|
19
|
+
# data before performing the copy.
|
|
20
|
+
#
|
|
21
|
+
# [:sync]
|
|
22
|
+
# Performs a dump of your redis data using +redis-cli --rdb -+.
|
|
23
|
+
# Redis implements this internally using a +SYNC+ command.
|
|
24
|
+
# The operation is analogous to requesting a +BGSAVE+, then having the
|
|
25
|
+
# dump returned. This mode is capable of dumping data from a local or
|
|
26
|
+
# remote server. Requires Redis v2.6 or better.
|
|
27
|
+
#
|
|
28
|
+
# Defaults to +:copy+.
|
|
29
|
+
attr_accessor :mode
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Full path to the redis dump file.
|
|
33
|
+
#
|
|
34
|
+
# Required when {#mode} is +:copy+.
|
|
35
|
+
attr_accessor :rdb_path
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Perform a +SAVE+ command using the +redis-cli+ utility
|
|
39
|
+
# before copying the dump file specified by {#rdb_path}.
|
|
40
|
+
#
|
|
41
|
+
# Only valid when {#mode} is +:copy+.
|
|
42
|
+
attr_accessor :invoke_save
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Connectivity options for the +redis-cli+ utility.
|
|
46
|
+
attr_accessor :host, :port, :socket
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Password for the +redis-cli+ utility.
|
|
50
|
+
attr_accessor :password
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Additional options for the +redis-cli+ utility.
|
|
54
|
+
attr_accessor :additional_options
|
|
55
|
+
|
|
56
|
+
def initialize(model, database_id = nil, &block)
|
|
57
|
+
super
|
|
58
|
+
instance_eval(&block) if block_given?
|
|
59
|
+
|
|
60
|
+
@mode ||= :copy
|
|
61
|
+
|
|
62
|
+
unless MODES.include?(mode)
|
|
63
|
+
raise Error, "'#{ mode }' is not a valid mode"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if mode == :copy && rdb_path.nil?
|
|
67
|
+
raise Error, '`rdb_path` must be set when `mode` is :copy'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Performs the dump based on {#mode} and stores the Redis dump file
|
|
73
|
+
# to the +dump_path+ using the +dump_filename+.
|
|
74
|
+
#
|
|
75
|
+
# <trigger>/databases/Redis[-<database_id>].rdb[.gz]
|
|
76
|
+
def perform!
|
|
77
|
+
super
|
|
78
|
+
|
|
79
|
+
case mode
|
|
80
|
+
when :sync
|
|
81
|
+
# messages output by `redis-cli --rdb` on $stderr
|
|
82
|
+
Logger.configure do
|
|
83
|
+
ignore_warning(/Transfer finished with success/)
|
|
84
|
+
ignore_warning(/SYNC sent to master/)
|
|
85
|
+
end
|
|
86
|
+
sync!
|
|
87
|
+
when :copy
|
|
88
|
+
save! if invoke_save
|
|
89
|
+
copy!
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
log!(:finished)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def sync!
|
|
98
|
+
pipeline = Pipeline.new
|
|
99
|
+
dump_ext = 'rdb'
|
|
100
|
+
|
|
101
|
+
pipeline << "#{ redis_cli_cmd } --rdb -"
|
|
102
|
+
|
|
103
|
+
model.compressor.compress_with do |command, ext|
|
|
104
|
+
pipeline << command
|
|
105
|
+
dump_ext << ext
|
|
106
|
+
end if model.compressor
|
|
107
|
+
|
|
108
|
+
pipeline << "#{ utility(:cat) } > " +
|
|
109
|
+
"'#{ File.join(dump_path, dump_filename) }.#{ dump_ext }'"
|
|
110
|
+
|
|
111
|
+
pipeline.run
|
|
112
|
+
|
|
113
|
+
unless pipeline.success?
|
|
114
|
+
raise Error, "Dump Failed!\n" + pipeline.error_messages
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def save!
|
|
119
|
+
resp = run("#{ redis_cli_cmd } SAVE")
|
|
120
|
+
unless resp =~ /OK$/
|
|
121
|
+
raise Error, <<-EOS
|
|
122
|
+
Failed to invoke the `SAVE` command
|
|
123
|
+
Response was: #{ resp }
|
|
124
|
+
EOS
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
rescue Error
|
|
128
|
+
if resp =~ /save already in progress/
|
|
129
|
+
unless (attempts ||= '0').next! == '5'
|
|
130
|
+
sleep 5
|
|
131
|
+
retry
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
raise
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def copy!
|
|
138
|
+
unless File.exist?(rdb_path)
|
|
139
|
+
raise Error, <<-EOS
|
|
140
|
+
Redis database dump not found
|
|
141
|
+
`rdb_path` was '#{ rdb_path }'
|
|
142
|
+
EOS
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
dst_path = File.join(dump_path, dump_filename + '.rdb')
|
|
146
|
+
if model.compressor
|
|
147
|
+
model.compressor.compress_with do |command, ext|
|
|
148
|
+
run("#{ command } -c '#{ rdb_path }' > '#{ dst_path + ext }'")
|
|
149
|
+
end
|
|
150
|
+
else
|
|
151
|
+
FileUtils.cp(rdb_path, dst_path)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def redis_cli_cmd
|
|
156
|
+
"#{ utility('redis-cli') } #{ password_option } " +
|
|
157
|
+
"#{ connectivity_options } #{ user_options }"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def password_option
|
|
161
|
+
"-a '#{ password }'" if password
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def connectivity_options
|
|
165
|
+
return "-s '#{ socket }'" if socket
|
|
166
|
+
|
|
167
|
+
opts = []
|
|
168
|
+
opts << "-h '#{ host }'" if host
|
|
169
|
+
opts << "-p '#{ port }'" if port
|
|
170
|
+
opts.join(' ')
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def user_options
|
|
174
|
+
Array(additional_options).join(' ')
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|