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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +24 -0
  3. data/README.md +20 -0
  4. data/bin/backup +5 -0
  5. data/lib/backup.rb +133 -0
  6. data/lib/backup/archive.rb +170 -0
  7. data/lib/backup/binder.rb +22 -0
  8. data/lib/backup/cleaner.rb +116 -0
  9. data/lib/backup/cli.rb +364 -0
  10. data/lib/backup/cloud_io/base.rb +41 -0
  11. data/lib/backup/cloud_io/cloud_files.rb +298 -0
  12. data/lib/backup/cloud_io/s3.rb +260 -0
  13. data/lib/backup/compressor/base.rb +35 -0
  14. data/lib/backup/compressor/bzip2.rb +39 -0
  15. data/lib/backup/compressor/custom.rb +53 -0
  16. data/lib/backup/compressor/gzip.rb +74 -0
  17. data/lib/backup/config.rb +119 -0
  18. data/lib/backup/config/dsl.rb +102 -0
  19. data/lib/backup/config/helpers.rb +143 -0
  20. data/lib/backup/database/base.rb +85 -0
  21. data/lib/backup/database/mongodb.rb +186 -0
  22. data/lib/backup/database/mysql.rb +123 -0
  23. data/lib/backup/database/postgresql.rb +133 -0
  24. data/lib/backup/database/redis.rb +179 -0
  25. data/lib/backup/database/riak.rb +82 -0
  26. data/lib/backup/encryptor/base.rb +29 -0
  27. data/lib/backup/encryptor/gpg.rb +747 -0
  28. data/lib/backup/encryptor/open_ssl.rb +72 -0
  29. data/lib/backup/errors.rb +58 -0
  30. data/lib/backup/logger.rb +199 -0
  31. data/lib/backup/logger/console.rb +51 -0
  32. data/lib/backup/logger/fog_adapter.rb +29 -0
  33. data/lib/backup/logger/logfile.rb +119 -0
  34. data/lib/backup/logger/syslog.rb +116 -0
  35. data/lib/backup/model.rb +454 -0
  36. data/lib/backup/notifier/base.rb +98 -0
  37. data/lib/backup/notifier/campfire.rb +69 -0
  38. data/lib/backup/notifier/hipchat.rb +93 -0
  39. data/lib/backup/notifier/http_post.rb +122 -0
  40. data/lib/backup/notifier/mail.rb +238 -0
  41. data/lib/backup/notifier/nagios.rb +69 -0
  42. data/lib/backup/notifier/prowl.rb +69 -0
  43. data/lib/backup/notifier/pushover.rb +80 -0
  44. data/lib/backup/notifier/slack.rb +149 -0
  45. data/lib/backup/notifier/twitter.rb +65 -0
  46. data/lib/backup/package.rb +51 -0
  47. data/lib/backup/packager.rb +101 -0
  48. data/lib/backup/pipeline.rb +124 -0
  49. data/lib/backup/splitter.rb +76 -0
  50. data/lib/backup/storage/base.rb +57 -0
  51. data/lib/backup/storage/cloud_files.rb +158 -0
  52. data/lib/backup/storage/cycler.rb +65 -0
  53. data/lib/backup/storage/dropbox.rb +236 -0
  54. data/lib/backup/storage/ftp.rb +98 -0
  55. data/lib/backup/storage/local.rb +64 -0
  56. data/lib/backup/storage/ninefold.rb +74 -0
  57. data/lib/backup/storage/rsync.rb +248 -0
  58. data/lib/backup/storage/s3.rb +154 -0
  59. data/lib/backup/storage/scp.rb +67 -0
  60. data/lib/backup/storage/sftp.rb +82 -0
  61. data/lib/backup/syncer/base.rb +70 -0
  62. data/lib/backup/syncer/cloud/base.rb +179 -0
  63. data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
  64. data/lib/backup/syncer/cloud/local_file.rb +100 -0
  65. data/lib/backup/syncer/cloud/s3.rb +110 -0
  66. data/lib/backup/syncer/rsync/base.rb +48 -0
  67. data/lib/backup/syncer/rsync/local.rb +31 -0
  68. data/lib/backup/syncer/rsync/pull.rb +51 -0
  69. data/lib/backup/syncer/rsync/push.rb +205 -0
  70. data/lib/backup/template.rb +46 -0
  71. data/lib/backup/utilities.rb +221 -0
  72. data/lib/backup/version.rb +5 -0
  73. data/templates/cli/archive +28 -0
  74. data/templates/cli/compressor/bzip2 +4 -0
  75. data/templates/cli/compressor/custom +7 -0
  76. data/templates/cli/compressor/gzip +4 -0
  77. data/templates/cli/config +123 -0
  78. data/templates/cli/databases/mongodb +15 -0
  79. data/templates/cli/databases/mysql +18 -0
  80. data/templates/cli/databases/postgresql +16 -0
  81. data/templates/cli/databases/redis +16 -0
  82. data/templates/cli/databases/riak +17 -0
  83. data/templates/cli/encryptor/gpg +27 -0
  84. data/templates/cli/encryptor/openssl +9 -0
  85. data/templates/cli/model +26 -0
  86. data/templates/cli/notifiers/campfire +12 -0
  87. data/templates/cli/notifiers/hipchat +15 -0
  88. data/templates/cli/notifiers/http_post +32 -0
  89. data/templates/cli/notifiers/mail +21 -0
  90. data/templates/cli/notifiers/nagios +13 -0
  91. data/templates/cli/notifiers/prowl +11 -0
  92. data/templates/cli/notifiers/pushover +11 -0
  93. data/templates/cli/notifiers/twitter +13 -0
  94. data/templates/cli/splitter +7 -0
  95. data/templates/cli/storages/cloud_files +11 -0
  96. data/templates/cli/storages/dropbox +19 -0
  97. data/templates/cli/storages/ftp +12 -0
  98. data/templates/cli/storages/local +7 -0
  99. data/templates/cli/storages/ninefold +9 -0
  100. data/templates/cli/storages/rsync +17 -0
  101. data/templates/cli/storages/s3 +14 -0
  102. data/templates/cli/storages/scp +14 -0
  103. data/templates/cli/storages/sftp +14 -0
  104. data/templates/cli/syncers/cloud_files +22 -0
  105. data/templates/cli/syncers/rsync_local +20 -0
  106. data/templates/cli/syncers/rsync_pull +28 -0
  107. data/templates/cli/syncers/rsync_push +28 -0
  108. data/templates/cli/syncers/s3 +27 -0
  109. data/templates/general/links +3 -0
  110. data/templates/general/version.erb +2 -0
  111. data/templates/notifier/mail/failure.erb +16 -0
  112. data/templates/notifier/mail/success.erb +16 -0
  113. data/templates/notifier/mail/warning.erb +16 -0
  114. data/templates/storage/dropbox/authorization_url.erb +6 -0
  115. data/templates/storage/dropbox/authorized.erb +4 -0
  116. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  117. 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