backedup 5.0.0.beta.3

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 (144) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +33 -0
  4. data/bin/backedup +5 -0
  5. data/bin/docker_test +24 -0
  6. data/lib/backup/archive.rb +169 -0
  7. data/lib/backup/binder.rb +18 -0
  8. data/lib/backup/cleaner.rb +112 -0
  9. data/lib/backup/cli.rb +370 -0
  10. data/lib/backup/cloud_io/base.rb +38 -0
  11. data/lib/backup/cloud_io/cloud_files.rb +296 -0
  12. data/lib/backup/cloud_io/gcs.rb +121 -0
  13. data/lib/backup/cloud_io/s3.rb +253 -0
  14. data/lib/backup/cloud_io/swift.rb +96 -0
  15. data/lib/backup/compressor/base.rb +32 -0
  16. data/lib/backup/compressor/bzip2.rb +35 -0
  17. data/lib/backup/compressor/custom.rb +49 -0
  18. data/lib/backup/compressor/gzip.rb +73 -0
  19. data/lib/backup/compressor/pbzip2.rb +45 -0
  20. data/lib/backup/config/dsl.rb +102 -0
  21. data/lib/backup/config/helpers.rb +137 -0
  22. data/lib/backup/config.rb +118 -0
  23. data/lib/backup/database/base.rb +86 -0
  24. data/lib/backup/database/mongodb.rb +186 -0
  25. data/lib/backup/database/mysql.rb +191 -0
  26. data/lib/backup/database/openldap.rb +93 -0
  27. data/lib/backup/database/postgresql.rb +164 -0
  28. data/lib/backup/database/redis.rb +176 -0
  29. data/lib/backup/database/riak.rb +79 -0
  30. data/lib/backup/database/sqlite.rb +55 -0
  31. data/lib/backup/encryptor/base.rb +27 -0
  32. data/lib/backup/encryptor/gpg.rb +737 -0
  33. data/lib/backup/encryptor/open_ssl.rb +74 -0
  34. data/lib/backup/errors.rb +53 -0
  35. data/lib/backup/logger/console.rb +48 -0
  36. data/lib/backup/logger/fog_adapter.rb +25 -0
  37. data/lib/backup/logger/logfile.rb +131 -0
  38. data/lib/backup/logger/syslog.rb +114 -0
  39. data/lib/backup/logger.rb +197 -0
  40. data/lib/backup/model.rb +472 -0
  41. data/lib/backup/notifier/base.rb +126 -0
  42. data/lib/backup/notifier/campfire.rb +61 -0
  43. data/lib/backup/notifier/command.rb +99 -0
  44. data/lib/backup/notifier/datadog.rb +104 -0
  45. data/lib/backup/notifier/flowdock.rb +99 -0
  46. data/lib/backup/notifier/hipchat.rb +116 -0
  47. data/lib/backup/notifier/http_post.rb +114 -0
  48. data/lib/backup/notifier/mail.rb +232 -0
  49. data/lib/backup/notifier/nagios.rb +65 -0
  50. data/lib/backup/notifier/pagerduty.rb +79 -0
  51. data/lib/backup/notifier/prowl.rb +68 -0
  52. data/lib/backup/notifier/pushover.rb +71 -0
  53. data/lib/backup/notifier/ses.rb +123 -0
  54. data/lib/backup/notifier/slack.rb +147 -0
  55. data/lib/backup/notifier/twitter.rb +55 -0
  56. data/lib/backup/notifier/zabbix.rb +60 -0
  57. data/lib/backup/package.rb +51 -0
  58. data/lib/backup/packager.rb +106 -0
  59. data/lib/backup/pipeline.rb +120 -0
  60. data/lib/backup/splitter.rb +73 -0
  61. data/lib/backup/storage/base.rb +66 -0
  62. data/lib/backup/storage/cloud_files.rb +156 -0
  63. data/lib/backup/storage/cycler.rb +70 -0
  64. data/lib/backup/storage/dropbox.rb +206 -0
  65. data/lib/backup/storage/ftp.rb +116 -0
  66. data/lib/backup/storage/gcs.rb +93 -0
  67. data/lib/backup/storage/local.rb +61 -0
  68. data/lib/backup/storage/qiniu.rb +65 -0
  69. data/lib/backup/storage/rsync.rb +246 -0
  70. data/lib/backup/storage/s3.rb +155 -0
  71. data/lib/backup/storage/scp.rb +65 -0
  72. data/lib/backup/storage/sftp.rb +80 -0
  73. data/lib/backup/storage/swift.rb +124 -0
  74. data/lib/backup/storage/webdav.rb +102 -0
  75. data/lib/backup/syncer/base.rb +67 -0
  76. data/lib/backup/syncer/cloud/base.rb +176 -0
  77. data/lib/backup/syncer/cloud/cloud_files.rb +81 -0
  78. data/lib/backup/syncer/cloud/local_file.rb +97 -0
  79. data/lib/backup/syncer/cloud/s3.rb +109 -0
  80. data/lib/backup/syncer/rsync/base.rb +50 -0
  81. data/lib/backup/syncer/rsync/local.rb +27 -0
  82. data/lib/backup/syncer/rsync/pull.rb +47 -0
  83. data/lib/backup/syncer/rsync/push.rb +201 -0
  84. data/lib/backup/template.rb +41 -0
  85. data/lib/backup/utilities.rb +234 -0
  86. data/lib/backup/version.rb +3 -0
  87. data/lib/backup.rb +145 -0
  88. data/templates/cli/archive +28 -0
  89. data/templates/cli/compressor/bzip2 +4 -0
  90. data/templates/cli/compressor/custom +7 -0
  91. data/templates/cli/compressor/gzip +4 -0
  92. data/templates/cli/config +123 -0
  93. data/templates/cli/databases/mongodb +15 -0
  94. data/templates/cli/databases/mysql +18 -0
  95. data/templates/cli/databases/openldap +24 -0
  96. data/templates/cli/databases/postgresql +16 -0
  97. data/templates/cli/databases/redis +16 -0
  98. data/templates/cli/databases/riak +17 -0
  99. data/templates/cli/databases/sqlite +11 -0
  100. data/templates/cli/encryptor/gpg +27 -0
  101. data/templates/cli/encryptor/openssl +9 -0
  102. data/templates/cli/model +26 -0
  103. data/templates/cli/notifier/zabbix +15 -0
  104. data/templates/cli/notifiers/campfire +12 -0
  105. data/templates/cli/notifiers/command +32 -0
  106. data/templates/cli/notifiers/datadog +57 -0
  107. data/templates/cli/notifiers/flowdock +16 -0
  108. data/templates/cli/notifiers/hipchat +16 -0
  109. data/templates/cli/notifiers/http_post +32 -0
  110. data/templates/cli/notifiers/mail +24 -0
  111. data/templates/cli/notifiers/nagios +13 -0
  112. data/templates/cli/notifiers/pagerduty +12 -0
  113. data/templates/cli/notifiers/prowl +11 -0
  114. data/templates/cli/notifiers/pushover +11 -0
  115. data/templates/cli/notifiers/ses +15 -0
  116. data/templates/cli/notifiers/slack +22 -0
  117. data/templates/cli/notifiers/twitter +13 -0
  118. data/templates/cli/splitter +7 -0
  119. data/templates/cli/storages/cloud_files +11 -0
  120. data/templates/cli/storages/dropbox +20 -0
  121. data/templates/cli/storages/ftp +13 -0
  122. data/templates/cli/storages/gcs +8 -0
  123. data/templates/cli/storages/local +8 -0
  124. data/templates/cli/storages/qiniu +12 -0
  125. data/templates/cli/storages/rsync +17 -0
  126. data/templates/cli/storages/s3 +16 -0
  127. data/templates/cli/storages/scp +15 -0
  128. data/templates/cli/storages/sftp +15 -0
  129. data/templates/cli/storages/swift +19 -0
  130. data/templates/cli/storages/webdav +13 -0
  131. data/templates/cli/syncers/cloud_files +22 -0
  132. data/templates/cli/syncers/rsync_local +20 -0
  133. data/templates/cli/syncers/rsync_pull +28 -0
  134. data/templates/cli/syncers/rsync_push +28 -0
  135. data/templates/cli/syncers/s3 +27 -0
  136. data/templates/general/links +3 -0
  137. data/templates/general/version.erb +2 -0
  138. data/templates/notifier/mail/failure.erb +16 -0
  139. data/templates/notifier/mail/success.erb +16 -0
  140. data/templates/notifier/mail/warning.erb +16 -0
  141. data/templates/storage/dropbox/authorization_url.erb +6 -0
  142. data/templates/storage/dropbox/authorized.erb +4 -0
  143. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  144. metadata +1255 -0
@@ -0,0 +1,191 @@
1
+ module Backup
2
+ module Database
3
+ class MySQL < Base
4
+ class Error < Backup::Error; end
5
+
6
+ ##
7
+ # Name of the database that needs to get dumped
8
+ # To dump all databases, set this to `:all` or leave blank.
9
+ attr_accessor :name
10
+
11
+ ##
12
+ # Credentials for the specified database
13
+ attr_accessor :username, :password
14
+
15
+ ##
16
+ # Connectivity options
17
+ attr_accessor :host, :port, :socket
18
+
19
+ ##
20
+ # Tables to skip while dumping the database
21
+ #
22
+ # If `name` is set to :all (or not specified), these must include
23
+ # a database name. e.g. 'name.table'.
24
+ # If `name` is given, these may simply be table names.
25
+ attr_accessor :skip_tables
26
+
27
+ ##
28
+ # Tables to dump. This in only valid if `name` is specified.
29
+ # If none are given, the entire database will be dumped.
30
+ attr_accessor :only_tables
31
+
32
+ ##
33
+ # Additional "mysqldump" or "innobackupex (backup creation)" options
34
+ attr_accessor :additional_options
35
+
36
+ ##
37
+ # Additional innobackupex log preparation phase ("apply-logs") options
38
+ attr_accessor :prepare_options
39
+
40
+ ##
41
+ # Default is :mysqldump (which is built in MySQL and generates
42
+ # a textual SQL file), but can be changed to :innobackupex, which
43
+ # has more feasible restore times for large databases.
44
+ # See: http://www.percona.com/doc/percona-xtrabackup/
45
+ attr_accessor :backup_engine
46
+
47
+ ##
48
+ # If true (which is the default behaviour), the backup will be prepared
49
+ # after it has been successfuly created. This option is only valid if
50
+ # :backup_engine is set to :innobackupex.
51
+ attr_accessor :prepare_backup
52
+
53
+ ##
54
+ # If set the backup engine command block is executed as the given user
55
+ attr_accessor :sudo_user
56
+
57
+ ##
58
+ # If set, do not suppress innobackupdb output (useful for debugging)
59
+ attr_accessor :verbose
60
+
61
+ def initialize(model, database_id = nil, &block)
62
+ super
63
+ instance_eval(&block) if block_given?
64
+
65
+ @name ||= :all
66
+ @backup_engine ||= :mysqldump
67
+ @prepare_backup = true if @prepare_backup.nil?
68
+ end
69
+
70
+ ##
71
+ # Performs the mysqldump or innobackupex command and outputs
72
+ # the dump file in the +dump_path+ using +dump_filename+.
73
+ #
74
+ # <trigger>/databases/MySQL[-<database_id>].[sql|tar][.gz]
75
+ def perform!
76
+ super
77
+
78
+ pipeline = Pipeline.new
79
+ dump_ext = sql_backup? ? "sql" : "tar"
80
+
81
+ pipeline << sudo_option(sql_backup? ? mysqldump : innobackupex)
82
+
83
+ if model.compressor
84
+ model.compressor.compress_with do |command, ext|
85
+ pipeline << command
86
+ dump_ext << ext
87
+ end
88
+ end
89
+
90
+ pipeline << "#{utility(:cat)} > " \
91
+ "'#{File.join(dump_path, dump_filename)}.#{dump_ext}'"
92
+
93
+ pipeline.run
94
+ if pipeline.success?
95
+ log!(:finished)
96
+ else
97
+ raise Error, "Dump Failed!\n#{pipeline.error_messages}"
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def mysqldump
104
+ "#{utility(:mysqldump)} #{user_options} #{credential_options} " \
105
+ "#{connectivity_options} #{name_option} " \
106
+ "#{tables_to_dump} #{tables_to_skip}"
107
+ end
108
+
109
+ def credential_options
110
+ opts = []
111
+ opts << "--user=#{Shellwords.escape(username)}" if username
112
+ opts << "--password=#{Shellwords.escape(password)}" if password
113
+ opts.join(" ")
114
+ end
115
+
116
+ def connectivity_options
117
+ return "--socket='#{socket}'" if socket
118
+
119
+ opts = []
120
+ opts << "--host='#{host}'" if host
121
+ opts << "--port='#{port}'" if port
122
+ opts.join(" ")
123
+ end
124
+
125
+ def user_options
126
+ Array(additional_options).join(" ")
127
+ end
128
+
129
+ def user_prepare_options
130
+ Array(prepare_options).join(" ")
131
+ end
132
+
133
+ def name_option
134
+ dump_all? ? "--all-databases" : name
135
+ end
136
+
137
+ def tables_to_dump
138
+ Array(only_tables).join(" ") unless dump_all?
139
+ end
140
+
141
+ def tables_to_skip
142
+ Array(skip_tables).map do |table|
143
+ table = dump_all? || table["."] ? table : "#{name}.#{table}"
144
+ "--ignore-table='#{table}'"
145
+ end.join(" ")
146
+ end
147
+
148
+ def dump_all?
149
+ name == :all
150
+ end
151
+
152
+ def sql_backup?
153
+ backup_engine.to_sym == :mysqldump
154
+ end
155
+
156
+ def innobackupex
157
+ # Creation phase
158
+ "#{utility(:innobackupex)} #{credential_options} " \
159
+ "#{connectivity_options} #{user_options} " \
160
+ "--no-timestamp #{temp_dir} #{quiet_option} && " +
161
+ innobackupex_prepare +
162
+ # Move files to tar-ed stream on stdout
163
+ "#{utility(:tar)} --remove-files -cf - " \
164
+ "-C #{File.dirname(temp_dir)} #{File.basename(temp_dir)}"
165
+ end
166
+
167
+ def innobackupex_prepare
168
+ return "" unless @prepare_backup
169
+ # Log applying phase (prepare for restore)
170
+ "#{utility(:innobackupex)} --apply-log #{temp_dir} " \
171
+ "#{user_prepare_options} #{quiet_option} && "
172
+ end
173
+
174
+ def sudo_option(command_block)
175
+ return command_block unless sudo_user
176
+
177
+ "sudo -s -u #{sudo_user} -- <<END_OF_SUDO\n" \
178
+ "#{command_block}\n" \
179
+ "END_OF_SUDO\n"
180
+ end
181
+
182
+ def quiet_option
183
+ verbose ? "" : " 2> /dev/null "
184
+ end
185
+
186
+ def temp_dir
187
+ File.join(dump_path, "#{dump_filename}.bkpdir")
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,93 @@
1
+ module Backup
2
+ module Database
3
+ class OpenLDAP < Base
4
+ class Error < Backup::Error; end
5
+
6
+ ##
7
+ # Name of the ldap backup
8
+ attr_accessor :name
9
+
10
+ ##
11
+ # run slapcat under sudo if needed
12
+ # make sure to set SUID on a file, to let you run the file with permissions of file owner
13
+ # eg. sudo chmod u+s /usr/sbin/slapcat
14
+ attr_accessor :use_sudo
15
+
16
+ ##
17
+ # Stores the location of the slapd.conf or slapcat confdir
18
+ attr_accessor :slapcat_conf
19
+
20
+ ##
21
+ # Additional slapcat options
22
+ attr_accessor :slapcat_args
23
+
24
+ ##
25
+ # Path to slapcat utility (optional)
26
+ attr_accessor :slapcat_utility
27
+
28
+ ##
29
+ # Takes the name of the archive and the configuration block
30
+ def initialize(model, database_id = nil, &block)
31
+ super
32
+ instance_eval(&block) if block_given?
33
+
34
+ @name ||= "ldap_backup"
35
+ @use_sudo ||= false
36
+ @slapcat_args ||= []
37
+ @slapcat_utility ||= utility(:slapcat)
38
+ @slapcat_conf ||= "/etc/ldap/slapd.d"
39
+ end
40
+
41
+ ##
42
+ # Performs the slapcat command and outputs the
43
+ # data to the specified path based on the 'trigger'
44
+ def perform!
45
+ super
46
+
47
+ pipeline = Pipeline.new
48
+ dump_ext = "ldif"
49
+
50
+ pipeline << slapcat
51
+ if @model.compressor
52
+ @model.compressor.compress_with do |command, ext|
53
+ pipeline << command
54
+ dump_ext << ext
55
+ end
56
+ end
57
+
58
+ pipeline << "#{utility(:cat)} > " \
59
+ "'#{File.join(dump_path, dump_filename)}.#{dump_ext}'"
60
+
61
+ pipeline.run
62
+ if pipeline.success?
63
+ log!(:finished)
64
+ else
65
+ raise Error, "Dump Failed!\n" + pipeline.error_messages
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ ##
72
+ # Builds the full slapcat string based on all attributes
73
+ def slapcat
74
+ command = "#{slapcat_utility} #{slapcat_conf_option} #{slapcat_conf} #{user_options}"
75
+ command.prepend("sudo ") if use_sudo
76
+ command
77
+ end
78
+
79
+ ##
80
+ # Uses different slapcat switch depending on confdir or conffile set
81
+ def slapcat_conf_option
82
+ @slapcat_conf.include?(".d") ? "-F" : "-f"
83
+ end
84
+
85
+ ##
86
+ # Builds a compatible string for the additional options
87
+ # specified by the user
88
+ def user_options
89
+ slapcat_args.join(" ")
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,164 @@
1
+ module Backup
2
+ module Database
3
+ class PostgreSQL < Base
4
+ class Error < Backup::Error; end
5
+
6
+ ##
7
+ # Name of the database that needs to get dumped.
8
+ # To dump all databases, set this to `:all` or leave blank.
9
+ # +username+ must be a PostgreSQL superuser to run `pg_dumpall`.
10
+ attr_accessor :name
11
+
12
+ ##
13
+ # Credentials for the specified database
14
+ attr_accessor :username, :password
15
+
16
+ ##
17
+ # If set the pg_dump(all) command is executed as the given user
18
+ attr_accessor :sudo_user
19
+
20
+ ##
21
+ # Connectivity options
22
+ attr_accessor :host, :port, :socket
23
+
24
+ ##
25
+ # URL includes credentials and connectivity options
26
+ attr_accessor :url
27
+
28
+ ##
29
+ # Tables to skip while dumping the database.
30
+ # If `name` is set to :all (or not specified), these are ignored.
31
+ attr_accessor :skip_tables
32
+
33
+ ##
34
+ # Tables to dump. This in only valid if `name` is specified.
35
+ # If none are given, the entire database will be dumped.
36
+ attr_accessor :only_tables
37
+
38
+ ##
39
+ # Additional "pg_dump" or "pg_dumpall" options
40
+ attr_accessor :additional_options
41
+
42
+ def check_options
43
+ return unless url
44
+ ignored_options = {
45
+ username: username,
46
+ password: password,
47
+ host: host,
48
+ port: port,
49
+ socket: socket,
50
+ name: name
51
+ }.reject { |_, value| value.nil? }
52
+ return if ignored_options.empty?
53
+
54
+ Logger.warn "PostgreSQL: the options " \
55
+ "#{ignored_options.keys.join(", ")} are set, but will be " \
56
+ "ignored because the `:url` option is set."
57
+ end
58
+
59
+ def initialize(model, database_id = nil, &block)
60
+ super
61
+ instance_eval(&block) if block_given?
62
+
63
+ @name ||= :all
64
+
65
+ # Warn user if url option is mixed with the others
66
+ check_options
67
+ end
68
+
69
+ ##
70
+ # Performs the pgdump command and outputs the dump file
71
+ # in the +dump_path+ using +dump_filename+.
72
+ #
73
+ # <trigger>/databases/PostgreSQL[-<database_id>].sql[.gz]
74
+ def perform!
75
+ super
76
+
77
+ pipeline = Pipeline.new
78
+ dump_ext = "sql"
79
+
80
+ pipeline << (dump_all? ? pgdumpall : pgdump)
81
+
82
+ if model.compressor
83
+ model.compressor.compress_with do |command, ext|
84
+ pipeline << command
85
+ dump_ext << ext
86
+ end
87
+ end
88
+
89
+ pipeline << "#{utility(:cat)} > " \
90
+ "'#{File.join(dump_path, dump_filename)}.#{dump_ext}'"
91
+
92
+ pipeline.run
93
+ if pipeline.success?
94
+ log!(:finished)
95
+ else
96
+ raise Error, "Dump Failed!\n" + pipeline.error_messages
97
+ end
98
+ end
99
+
100
+ def pgdump
101
+ password_option.to_s +
102
+ sudo_option.to_s +
103
+ "#{utility(:pg_dump)} #{pgdump_arguments}"
104
+ end
105
+
106
+ def pgdump_arguments
107
+ if url.nil?
108
+ "#{username_option} #{connectivity_options} #{user_options} #{tables_to_dump} " \
109
+ "#{tables_to_skip} #{name}"
110
+ else
111
+ "#{user_options} #{tables_to_dump} #{tables_to_skip} #{url}"
112
+ end
113
+ end
114
+
115
+ def pgdumpall
116
+ password_option.to_s +
117
+ sudo_option.to_s +
118
+ "#{utility(:pg_dumpall)} #{username_option} " \
119
+ "#{connectivity_options} #{user_options}"
120
+ end
121
+
122
+ def password_option
123
+ "PGPASSWORD=#{Shellwords.escape(password)} " if password
124
+ end
125
+
126
+ def sudo_option
127
+ "#{utility(:sudo)} -n -H -u #{sudo_user} " if sudo_user
128
+ end
129
+
130
+ def username_option
131
+ "--username=#{Shellwords.escape(username)}" if username
132
+ end
133
+
134
+ def connectivity_options
135
+ return "--host='#{socket}'" if socket
136
+
137
+ opts = []
138
+ opts << "--host='#{host}'" if host
139
+ opts << "--port='#{port}'" if port
140
+ opts.join(" ")
141
+ end
142
+
143
+ def user_options
144
+ Array(additional_options).join(" ")
145
+ end
146
+
147
+ def tables_to_dump
148
+ Array(only_tables).map do |table|
149
+ "--table='#{table}'"
150
+ end.join(" ")
151
+ end
152
+
153
+ def tables_to_skip
154
+ Array(skip_tables).map do |table|
155
+ "--exclude-table='#{table}'"
156
+ end.join(" ")
157
+ end
158
+
159
+ def dump_all?
160
+ name == :all
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,176 @@
1
+ module Backup
2
+ module Database
3
+ class Redis < Base
4
+ class Error < Backup::Error; end
5
+
6
+ MODES = [:copy, :sync]
7
+
8
+ ##
9
+ # Mode of operation.
10
+ #
11
+ # [:copy]
12
+ # Copies the redis dump file specified by {#rdb_path}.
13
+ # This data will be current as of the last RDB Snapshot
14
+ # performed by the server (per your redis.conf settings).
15
+ # You may set {#invoke_save} to +true+ to have Backup issue
16
+ # a +SAVE+ command to update the dump file with the current
17
+ # data before performing the copy.
18
+ #
19
+ # [:sync]
20
+ # Performs a dump of your redis data using +redis-cli --rdb -+.
21
+ # Redis implements this internally using a +SYNC+ command.
22
+ # The operation is analogous to requesting a +BGSAVE+, then having the
23
+ # dump returned. This mode is capable of dumping data from a local or
24
+ # remote server. Requires Redis v2.6 or better.
25
+ #
26
+ # Defaults to +:copy+.
27
+ attr_accessor :mode
28
+
29
+ ##
30
+ # Full path to the redis dump file.
31
+ #
32
+ # Required when {#mode} is +:copy+.
33
+ attr_accessor :rdb_path
34
+
35
+ ##
36
+ # Perform a +SAVE+ command using the +redis-cli+ utility
37
+ # before copying the dump file specified by {#rdb_path}.
38
+ #
39
+ # Only valid when {#mode} is +:copy+.
40
+ attr_accessor :invoke_save
41
+
42
+ ##
43
+ # Connectivity options for the +redis-cli+ utility.
44
+ attr_accessor :host, :port, :socket
45
+
46
+ ##
47
+ # Password for the +redis-cli+ utility.
48
+ attr_accessor :password
49
+
50
+ ##
51
+ # Additional options for the +redis-cli+ utility.
52
+ attr_accessor :additional_options
53
+
54
+ def initialize(model, database_id = nil, &block)
55
+ super
56
+ instance_eval(&block) if block_given?
57
+
58
+ @mode ||= :copy
59
+
60
+ raise Error, "'#{mode}' is not a valid mode" unless MODES.include?(mode)
61
+
62
+ if mode == :copy && rdb_path.nil?
63
+ raise Error, "`rdb_path` must be set when `mode` is :copy"
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Performs the dump based on {#mode} and stores the Redis dump file
69
+ # to the +dump_path+ using the +dump_filename+.
70
+ #
71
+ # <trigger>/databases/Redis[-<database_id>].rdb[.gz]
72
+ def perform!
73
+ super
74
+
75
+ case mode
76
+ when :sync
77
+ # messages output by `redis-cli --rdb` on $stderr
78
+ Logger.configure do
79
+ ignore_warning(/Transfer finished with success/)
80
+ ignore_warning(/SYNC sent to master/)
81
+ end
82
+ sync!
83
+ when :copy
84
+ save! if invoke_save
85
+ copy!
86
+ end
87
+
88
+ log!(:finished)
89
+ end
90
+
91
+ private
92
+
93
+ def sync!
94
+ pipeline = Pipeline.new
95
+ dump_ext = "rdb"
96
+
97
+ pipeline << "#{redis_cli_cmd} --rdb -"
98
+
99
+ if model.compressor
100
+ model.compressor.compress_with do |command, ext|
101
+ pipeline << command
102
+ dump_ext << ext
103
+ end
104
+ end
105
+
106
+ pipeline << "#{utility(:cat)} > " \
107
+ "'#{File.join(dump_path, dump_filename)}.#{dump_ext}'"
108
+
109
+ pipeline.run
110
+
111
+ unless pipeline.success?
112
+ raise Error, "Dump Failed!\n" + pipeline.error_messages
113
+ end
114
+ end
115
+
116
+ def save!
117
+ resp = run("#{redis_cli_cmd} SAVE")
118
+ unless resp =~ /OK$/
119
+ raise Error, <<-EOS
120
+ Failed to invoke the `SAVE` command
121
+ Response was: #{resp}
122
+ EOS
123
+ end
124
+ rescue Error
125
+ if resp =~ /save already in progress/
126
+ unless (attempts ||= "0").next! == "5"
127
+ sleep 5
128
+ retry
129
+ end
130
+ end
131
+ raise
132
+ end
133
+
134
+ def copy!
135
+ unless File.exist?(rdb_path)
136
+ raise Error, <<-EOS
137
+ Redis database dump not found
138
+ `rdb_path` was '#{rdb_path}'
139
+ EOS
140
+ end
141
+
142
+ dst_path = File.join(dump_path, dump_filename + ".rdb")
143
+ if model.compressor
144
+ model.compressor.compress_with do |command, ext|
145
+ run("#{command} -c '#{rdb_path}' > '#{dst_path + ext}'")
146
+ end
147
+ else
148
+ FileUtils.cp(rdb_path, dst_path)
149
+ end
150
+ end
151
+
152
+ def redis_cli_cmd
153
+ "#{utility("redis-cli")} #{password_option} " \
154
+ "#{connectivity_options} #{user_options}"
155
+ end
156
+
157
+ def password_option
158
+ return unless password
159
+ "-a '#{password}'"
160
+ end
161
+
162
+ def connectivity_options
163
+ return "-s '#{socket}'" if socket
164
+
165
+ opts = []
166
+ opts << "-h '#{host}'" if host
167
+ opts << "-p '#{port}'" if port
168
+ opts.join(" ")
169
+ end
170
+
171
+ def user_options
172
+ Array(additional_options).join(" ")
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,79 @@
1
+ module Backup
2
+ module Database
3
+ class Riak < Base
4
+ ##
5
+ # Node is the node from which to perform the backup.
6
+ # Default: riak@127.0.0.1
7
+ attr_accessor :node
8
+
9
+ ##
10
+ # Cookie is the Erlang cookie/shared secret used to connect to the node.
11
+ # Default: riak
12
+ attr_accessor :cookie
13
+
14
+ ##
15
+ # Username for the riak instance
16
+ # Default: riak
17
+ attr_accessor :user
18
+
19
+ def initialize(model, database_id = nil, &block)
20
+ super
21
+ instance_eval(&block) if block_given?
22
+
23
+ @node ||= "riak@127.0.0.1"
24
+ @cookie ||= "riak"
25
+ @user ||= "riak"
26
+ end
27
+
28
+ ##
29
+ # Performs the dump using `riak-admin backup`.
30
+ #
31
+ # This will be stored in the final backup package as
32
+ # <trigger>/databases/<dump_filename>-<node>[.gz]
33
+ def perform!
34
+ super
35
+
36
+ dump_file = File.join(dump_path, dump_filename)
37
+ with_riak_owned_dump_path do
38
+ run("#{riakadmin} backup #{node} #{cookie} '#{dump_file}' node")
39
+ end
40
+
41
+ if model.compressor
42
+ model.compressor.compress_with do |command, ext|
43
+ dump_file << "-#{node}" # `riak-admin` appends `node` to the filename.
44
+ run("#{command} -c '#{dump_file}' > '#{dump_file + ext}'")
45
+ FileUtils.rm_f(dump_file)
46
+ end
47
+ end
48
+
49
+ log!(:finished)
50
+ end
51
+
52
+ private
53
+
54
+ ##
55
+ # The `riak-admin backup` command is run as the riak +user+,
56
+ # so +user+ must have write priviledges to the +dump_path+.
57
+ #
58
+ # Note that the riak +user+ must also have access to +dump_path+.
59
+ # This means Backup's +tmp_path+ can not be under the home directory of
60
+ # the user running Backup, since the absence of the execute bit on their
61
+ # home directory would deny +user+ access.
62
+ def with_riak_owned_dump_path
63
+ run "#{utility(:sudo)} -n #{utility(:chown)} #{user} '#{dump_path}'"
64
+ yield
65
+ ensure
66
+ # reclaim ownership
67
+ run "#{utility(:sudo)} -n #{utility(:chown)} -R " \
68
+ "#{Config.user} '#{dump_path}'"
69
+ end
70
+
71
+ ##
72
+ # `riak-admin` must be run as the riak +user+.
73
+ # It will do this itself, but without `-n` and emits a message on STDERR.
74
+ def riakadmin
75
+ "#{utility(:sudo)} -n -u #{user} #{utility("riak-admin")}"
76
+ end
77
+ end
78
+ end
79
+ end