backup-agoddard 3.0.27

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of backup-agoddard might be problematic. Click here for more details.

Files changed (190) hide show
  1. data/.gitignore +8 -0
  2. data/.travis.yml +10 -0
  3. data/Gemfile +28 -0
  4. data/Guardfile +23 -0
  5. data/LICENSE.md +24 -0
  6. data/README.md +478 -0
  7. data/backup.gemspec +32 -0
  8. data/bin/backup +11 -0
  9. data/lib/backup.rb +133 -0
  10. data/lib/backup/archive.rb +117 -0
  11. data/lib/backup/binder.rb +22 -0
  12. data/lib/backup/cleaner.rb +121 -0
  13. data/lib/backup/cli/helpers.rb +93 -0
  14. data/lib/backup/cli/utility.rb +255 -0
  15. data/lib/backup/compressor/base.rb +35 -0
  16. data/lib/backup/compressor/bzip2.rb +50 -0
  17. data/lib/backup/compressor/custom.rb +53 -0
  18. data/lib/backup/compressor/gzip.rb +50 -0
  19. data/lib/backup/compressor/lzma.rb +52 -0
  20. data/lib/backup/compressor/pbzip2.rb +59 -0
  21. data/lib/backup/config.rb +174 -0
  22. data/lib/backup/configuration.rb +33 -0
  23. data/lib/backup/configuration/helpers.rb +130 -0
  24. data/lib/backup/configuration/store.rb +24 -0
  25. data/lib/backup/database/base.rb +53 -0
  26. data/lib/backup/database/mongodb.rb +230 -0
  27. data/lib/backup/database/mysql.rb +160 -0
  28. data/lib/backup/database/postgresql.rb +144 -0
  29. data/lib/backup/database/redis.rb +136 -0
  30. data/lib/backup/database/riak.rb +67 -0
  31. data/lib/backup/dependency.rb +108 -0
  32. data/lib/backup/encryptor/base.rb +29 -0
  33. data/lib/backup/encryptor/gpg.rb +760 -0
  34. data/lib/backup/encryptor/open_ssl.rb +72 -0
  35. data/lib/backup/errors.rb +124 -0
  36. data/lib/backup/hooks.rb +68 -0
  37. data/lib/backup/logger.rb +152 -0
  38. data/lib/backup/model.rb +409 -0
  39. data/lib/backup/notifier/base.rb +81 -0
  40. data/lib/backup/notifier/campfire.rb +155 -0
  41. data/lib/backup/notifier/hipchat.rb +93 -0
  42. data/lib/backup/notifier/mail.rb +206 -0
  43. data/lib/backup/notifier/prowl.rb +65 -0
  44. data/lib/backup/notifier/pushover.rb +88 -0
  45. data/lib/backup/notifier/twitter.rb +70 -0
  46. data/lib/backup/package.rb +47 -0
  47. data/lib/backup/packager.rb +100 -0
  48. data/lib/backup/pipeline.rb +110 -0
  49. data/lib/backup/splitter.rb +75 -0
  50. data/lib/backup/storage/base.rb +99 -0
  51. data/lib/backup/storage/cloudfiles.rb +87 -0
  52. data/lib/backup/storage/cycler.rb +117 -0
  53. data/lib/backup/storage/dropbox.rb +178 -0
  54. data/lib/backup/storage/ftp.rb +119 -0
  55. data/lib/backup/storage/local.rb +82 -0
  56. data/lib/backup/storage/ninefold.rb +116 -0
  57. data/lib/backup/storage/rsync.rb +149 -0
  58. data/lib/backup/storage/s3.rb +94 -0
  59. data/lib/backup/storage/scp.rb +99 -0
  60. data/lib/backup/storage/sftp.rb +108 -0
  61. data/lib/backup/syncer/base.rb +46 -0
  62. data/lib/backup/syncer/cloud/base.rb +247 -0
  63. data/lib/backup/syncer/cloud/cloud_files.rb +78 -0
  64. data/lib/backup/syncer/cloud/s3.rb +68 -0
  65. data/lib/backup/syncer/rsync/base.rb +49 -0
  66. data/lib/backup/syncer/rsync/local.rb +55 -0
  67. data/lib/backup/syncer/rsync/pull.rb +36 -0
  68. data/lib/backup/syncer/rsync/push.rb +116 -0
  69. data/lib/backup/template.rb +46 -0
  70. data/lib/backup/version.rb +43 -0
  71. data/spec-live/.gitignore +6 -0
  72. data/spec-live/README +7 -0
  73. data/spec-live/backups/config.rb +83 -0
  74. data/spec-live/backups/config.yml.template +46 -0
  75. data/spec-live/backups/models.rb +184 -0
  76. data/spec-live/compressor/custom_spec.rb +30 -0
  77. data/spec-live/compressor/gzip_spec.rb +30 -0
  78. data/spec-live/encryptor/gpg_keys.rb +239 -0
  79. data/spec-live/encryptor/gpg_spec.rb +287 -0
  80. data/spec-live/notifier/mail_spec.rb +121 -0
  81. data/spec-live/spec_helper.rb +151 -0
  82. data/spec-live/storage/dropbox_spec.rb +151 -0
  83. data/spec-live/storage/local_spec.rb +83 -0
  84. data/spec-live/storage/scp_spec.rb +193 -0
  85. data/spec-live/syncer/cloud/s3_spec.rb +124 -0
  86. data/spec/archive_spec.rb +335 -0
  87. data/spec/cleaner_spec.rb +312 -0
  88. data/spec/cli/helpers_spec.rb +301 -0
  89. data/spec/cli/utility_spec.rb +411 -0
  90. data/spec/compressor/base_spec.rb +52 -0
  91. data/spec/compressor/bzip2_spec.rb +217 -0
  92. data/spec/compressor/custom_spec.rb +106 -0
  93. data/spec/compressor/gzip_spec.rb +217 -0
  94. data/spec/compressor/lzma_spec.rb +123 -0
  95. data/spec/compressor/pbzip2_spec.rb +165 -0
  96. data/spec/config_spec.rb +321 -0
  97. data/spec/configuration/helpers_spec.rb +247 -0
  98. data/spec/configuration/store_spec.rb +39 -0
  99. data/spec/configuration_spec.rb +62 -0
  100. data/spec/database/base_spec.rb +63 -0
  101. data/spec/database/mongodb_spec.rb +510 -0
  102. data/spec/database/mysql_spec.rb +411 -0
  103. data/spec/database/postgresql_spec.rb +353 -0
  104. data/spec/database/redis_spec.rb +334 -0
  105. data/spec/database/riak_spec.rb +176 -0
  106. data/spec/dependency_spec.rb +51 -0
  107. data/spec/encryptor/base_spec.rb +40 -0
  108. data/spec/encryptor/gpg_spec.rb +909 -0
  109. data/spec/encryptor/open_ssl_spec.rb +148 -0
  110. data/spec/errors_spec.rb +306 -0
  111. data/spec/hooks_spec.rb +35 -0
  112. data/spec/logger_spec.rb +367 -0
  113. data/spec/model_spec.rb +694 -0
  114. data/spec/notifier/base_spec.rb +104 -0
  115. data/spec/notifier/campfire_spec.rb +217 -0
  116. data/spec/notifier/hipchat_spec.rb +211 -0
  117. data/spec/notifier/mail_spec.rb +316 -0
  118. data/spec/notifier/prowl_spec.rb +138 -0
  119. data/spec/notifier/pushover_spec.rb +123 -0
  120. data/spec/notifier/twitter_spec.rb +153 -0
  121. data/spec/package_spec.rb +61 -0
  122. data/spec/packager_spec.rb +213 -0
  123. data/spec/pipeline_spec.rb +259 -0
  124. data/spec/spec_helper.rb +60 -0
  125. data/spec/splitter_spec.rb +120 -0
  126. data/spec/storage/base_spec.rb +166 -0
  127. data/spec/storage/cloudfiles_spec.rb +254 -0
  128. data/spec/storage/cycler_spec.rb +247 -0
  129. data/spec/storage/dropbox_spec.rb +480 -0
  130. data/spec/storage/ftp_spec.rb +271 -0
  131. data/spec/storage/local_spec.rb +259 -0
  132. data/spec/storage/ninefold_spec.rb +343 -0
  133. data/spec/storage/rsync_spec.rb +362 -0
  134. data/spec/storage/s3_spec.rb +245 -0
  135. data/spec/storage/scp_spec.rb +233 -0
  136. data/spec/storage/sftp_spec.rb +244 -0
  137. data/spec/syncer/base_spec.rb +109 -0
  138. data/spec/syncer/cloud/base_spec.rb +515 -0
  139. data/spec/syncer/cloud/cloud_files_spec.rb +181 -0
  140. data/spec/syncer/cloud/s3_spec.rb +174 -0
  141. data/spec/syncer/rsync/base_spec.rb +98 -0
  142. data/spec/syncer/rsync/local_spec.rb +149 -0
  143. data/spec/syncer/rsync/pull_spec.rb +98 -0
  144. data/spec/syncer/rsync/push_spec.rb +333 -0
  145. data/spec/version_spec.rb +21 -0
  146. data/templates/cli/utility/archive +25 -0
  147. data/templates/cli/utility/compressor/bzip2 +4 -0
  148. data/templates/cli/utility/compressor/custom +11 -0
  149. data/templates/cli/utility/compressor/gzip +4 -0
  150. data/templates/cli/utility/compressor/lzma +10 -0
  151. data/templates/cli/utility/compressor/pbzip2 +10 -0
  152. data/templates/cli/utility/config +32 -0
  153. data/templates/cli/utility/database/mongodb +18 -0
  154. data/templates/cli/utility/database/mysql +21 -0
  155. data/templates/cli/utility/database/postgresql +17 -0
  156. data/templates/cli/utility/database/redis +16 -0
  157. data/templates/cli/utility/database/riak +11 -0
  158. data/templates/cli/utility/encryptor/gpg +27 -0
  159. data/templates/cli/utility/encryptor/openssl +9 -0
  160. data/templates/cli/utility/model.erb +23 -0
  161. data/templates/cli/utility/notifier/campfire +12 -0
  162. data/templates/cli/utility/notifier/hipchat +15 -0
  163. data/templates/cli/utility/notifier/mail +22 -0
  164. data/templates/cli/utility/notifier/prowl +11 -0
  165. data/templates/cli/utility/notifier/pushover +11 -0
  166. data/templates/cli/utility/notifier/twitter +13 -0
  167. data/templates/cli/utility/splitter +7 -0
  168. data/templates/cli/utility/storage/cloud_files +22 -0
  169. data/templates/cli/utility/storage/dropbox +20 -0
  170. data/templates/cli/utility/storage/ftp +12 -0
  171. data/templates/cli/utility/storage/local +7 -0
  172. data/templates/cli/utility/storage/ninefold +9 -0
  173. data/templates/cli/utility/storage/rsync +11 -0
  174. data/templates/cli/utility/storage/s3 +19 -0
  175. data/templates/cli/utility/storage/scp +11 -0
  176. data/templates/cli/utility/storage/sftp +11 -0
  177. data/templates/cli/utility/syncer/cloud_files +46 -0
  178. data/templates/cli/utility/syncer/rsync_local +12 -0
  179. data/templates/cli/utility/syncer/rsync_pull +17 -0
  180. data/templates/cli/utility/syncer/rsync_push +17 -0
  181. data/templates/cli/utility/syncer/s3 +43 -0
  182. data/templates/general/links +11 -0
  183. data/templates/general/version.erb +2 -0
  184. data/templates/notifier/mail/failure.erb +9 -0
  185. data/templates/notifier/mail/success.erb +7 -0
  186. data/templates/notifier/mail/warning.erb +9 -0
  187. data/templates/storage/dropbox/authorization_url.erb +6 -0
  188. data/templates/storage/dropbox/authorized.erb +4 -0
  189. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  190. metadata +277 -0
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Encryptor
5
+ class OpenSSL < Base
6
+
7
+ ##
8
+ # The password that'll be used to encrypt the backup. This
9
+ # password will be required to decrypt the backup later on.
10
+ attr_accessor :password
11
+
12
+ ##
13
+ # The password file to use to encrypt the backup.
14
+ attr_accessor :password_file
15
+
16
+ ##
17
+ # Determines whether the 'base64' should be used or not
18
+ attr_accessor :base64
19
+
20
+ ##
21
+ # Determines whether the 'salt' flag should be used
22
+ attr_accessor :salt
23
+
24
+ ##
25
+ # Creates a new instance of Backup::Encryptor::OpenSSL and
26
+ # sets the password attribute to what was provided
27
+ def initialize(&block)
28
+ super
29
+
30
+ @base64 ||= false
31
+ @salt ||= true
32
+ @password_file ||= nil
33
+
34
+ instance_eval(&block) if block_given?
35
+ end
36
+
37
+ ##
38
+ # This is called as part of the procedure run by the Packager.
39
+ # It sets up the needed options to pass to the openssl command,
40
+ # then yields the command to use as part of the packaging procedure.
41
+ # Once the packaging procedure is complete, it will return
42
+ # so that any clean-up may be performed after the yield.
43
+ def encrypt_with
44
+ log!
45
+ yield "#{ utility(:openssl) } #{ options }", '.enc'
46
+ end
47
+
48
+ private
49
+
50
+ ##
51
+ # Uses the 256bit AES encryption cipher, which is what the
52
+ # US Government uses to encrypt information at the "Top Secret" level.
53
+ #
54
+ # The -base64 option will make the encrypted output base64 encoded,
55
+ # this makes the encrypted file readable using text editors
56
+ #
57
+ # The -salt option adds strength to the encryption
58
+ #
59
+ # Always sets a password option, if even no password is given,
60
+ # but will prefer the password_file option if both are given.
61
+ def options
62
+ opts = ['aes-256-cbc']
63
+ opts << '-base64' if @base64
64
+ opts << '-salt' if @salt
65
+ opts << ( @password_file.to_s.empty? ?
66
+ "-k '#{@password}'" : "-pass file:#{@password_file}" )
67
+ opts.join(' ')
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,124 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ ##
5
+ # - automatically defines module namespaces referenced under Backup::Errors
6
+ # - any constant name referenced that ends with 'Error' will be created
7
+ # as a subclass of Backup::Errors::Error
8
+ # e.g.
9
+ # err = Backup::Errors::Foo::Bar::FooError.new('error message')
10
+ # err.message => "Foo::Bar::FooError: error message"
11
+ #
12
+ module ErrorsHelper
13
+ def const_missing(const)
14
+ if const.to_s.end_with?('Error')
15
+ module_eval("class #{const} < Backup::Errors::Error; end")
16
+ else
17
+ module_eval("module #{const}; extend Backup::ErrorsHelper; end")
18
+ end
19
+ const_get(const)
20
+ end
21
+ end
22
+
23
+ ##
24
+ # provides cascading errors with formatted messages
25
+ # see the specs for details
26
+ #
27
+ # e.g.
28
+ # module Backup
29
+ # begin
30
+ # begin
31
+ # begin
32
+ # raise Errors::ZoneAError, 'an error occurred in Zone A'
33
+ # rescue => err
34
+ # raise Errors::ZoneBError.wrap(err, <<-EOS)
35
+ # an error occurred in Zone B
36
+ #
37
+ # the following error should give a reason
38
+ # EOS
39
+ # end
40
+ # rescue => err
41
+ # raise Errors::ZoneCError.wrap(err)
42
+ # end
43
+ # rescue => err
44
+ # puts Errors::ZoneDError.wrap(err, 'an error occurred in Zone D')
45
+ # end
46
+ # end
47
+ #
48
+ # Outputs:
49
+ # ZoneDError: an error occurred in Zone D
50
+ # Reason: ZoneCError
51
+ # ZoneBError: an error occurred in Zone B
52
+ #
53
+ # the following error should give a reason
54
+ # Reason: ZoneAError
55
+ # an error occurred in Zone A
56
+ #
57
+ module Errors
58
+ extend ErrorsHelper
59
+
60
+ class Error < StandardError
61
+
62
+ def self.wrap(orig_err, msg = nil)
63
+ new(msg, orig_err)
64
+ end
65
+
66
+ def initialize(msg = nil, orig_err = nil)
67
+ super(msg)
68
+ set_backtrace(orig_err.backtrace) if @orig_err = orig_err
69
+ end
70
+
71
+ def to_s
72
+ return @to_s if @to_s
73
+ orig_to_s = super()
74
+
75
+ if orig_to_s == self.class.to_s
76
+ msg = orig_err_msg ?
77
+ "#{orig_err_class}: #{orig_err_msg}" : orig_err_class
78
+ else
79
+ msg = format_msg(orig_to_s)
80
+ msg << "\n Reason: #{orig_err_class}" + (orig_err_msg ?
81
+ "\n #{orig_err_msg}" : ' (no message given)') if @orig_err
82
+ end
83
+
84
+ @to_s = msg ? msg_prefix + msg : class_name
85
+ end
86
+
87
+ private
88
+
89
+ def msg_prefix
90
+ @msg_prefix ||= class_name + ': '
91
+ end
92
+
93
+ def orig_msg
94
+ @orig_msg ||= to_s.sub(msg_prefix, '')
95
+ end
96
+
97
+ def class_name
98
+ @class_name ||= self.class.to_s.sub('Backup::Errors::', '')
99
+ end
100
+
101
+ def orig_err_class
102
+ return unless @orig_err
103
+
104
+ @orig_err_class ||= @orig_err.is_a?(Errors::Error) ?
105
+ @orig_err.send(:class_name) : @orig_err.class.to_s
106
+ end
107
+
108
+ def orig_err_msg
109
+ return unless @orig_err
110
+ return @orig_err_msg unless @orig_err_msg.nil?
111
+
112
+ msg = @orig_err.is_a?(Errors::Error) ?
113
+ @orig_err.send(:orig_msg) : @orig_err.to_s
114
+ @orig_err_msg = (msg == orig_err_class) ?
115
+ false : format_msg(msg)
116
+ end
117
+
118
+ def format_msg(msg)
119
+ msg.gsub(/^ */, ' ').strip
120
+ end
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,68 @@
1
+
2
+ # encoding: utf-8
3
+
4
+ module Backup
5
+ class Hooks
6
+ include Backup::CLI::Helpers
7
+
8
+ ##
9
+ # Stores a block to be run before the backup
10
+ attr_accessor :before_proc
11
+
12
+ ##
13
+ # Stores a block to be run before the backup
14
+ attr_accessor :after_proc
15
+
16
+ ##
17
+ # The model
18
+ attr_reader :model
19
+
20
+ def initialize(model, &block)
21
+ @model = model
22
+ @before_proc = Proc.new { } # noop
23
+ @after_proc = Proc.new { } # noop
24
+ instance_eval(&block) if block_given?
25
+ end
26
+
27
+ def before(&code)
28
+ @before_proc = code
29
+ end
30
+
31
+ def after(&code)
32
+ @after_proc = code
33
+ end
34
+
35
+ def before!
36
+ begin
37
+ Logger.message "Performing Before Hook"
38
+ @before_proc.call(model)
39
+ Logger.message "Before Hook Completed Successfully"
40
+ rescue => err
41
+ raise Errors::Hooks::BeforeHookError.wrap(
42
+ err, "Before Hook Failed!"
43
+ )
44
+ end
45
+ end
46
+
47
+ def after!
48
+ begin
49
+ Logger.message "Performing After Hook"
50
+ @after_proc.call(model)
51
+ Logger.message "After Hook Completed Successfully"
52
+ rescue => err
53
+ raise Errors::Hooks::AfterHookError.wrap(
54
+ err, "After Hook Failed!"
55
+ )
56
+ end
57
+ end
58
+
59
+ def perform!(hook)
60
+ case hook
61
+ when :before
62
+ before!
63
+ when :after
64
+ after!
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,152 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Logger
5
+ class << self
6
+
7
+ attr_accessor :quiet
8
+
9
+ ##
10
+ # Outputs a messages to the console and writes it to the backup.log
11
+ def message(string)
12
+ to_console loggify(string, :message, :green)
13
+ to_file loggify(string, :message)
14
+ end
15
+
16
+ ##
17
+ # Outputs an error to the console and writes it to the backup.log
18
+ # Called when an Exception has caused the backup process to abort.
19
+ def error(string)
20
+ to_console loggify(string, :error, :red), true
21
+ to_file loggify(string, :error)
22
+ end
23
+
24
+ ##
25
+ # Outputs a notice to the console and writes it to the backup.log
26
+ # Sets #has_warnings? true so :on_warning notifications will be sent
27
+ def warn(string)
28
+ @has_warnings = true
29
+ to_console loggify(string, :warning, :yellow), true
30
+ to_file loggify(string, :warning)
31
+ end
32
+
33
+ # Outputs the data as if it were a regular 'puts' command,
34
+ # but also logs it to the backup.log
35
+ def normal(string)
36
+ to_console loggify(string)
37
+ to_file loggify(string)
38
+ end
39
+
40
+ ##
41
+ # Silently logs data to the log file
42
+ def silent(string)
43
+ to_file loggify(string, :silent)
44
+ end
45
+
46
+ ##
47
+ # Returns an Array of all messages written to the log file for this session
48
+ def messages
49
+ @messages ||= []
50
+ end
51
+
52
+ ##
53
+ # Returns true if any warnings have been issued
54
+ def has_warnings?
55
+ @has_warnings ||= false
56
+ end
57
+
58
+ def clear!
59
+ messages.clear
60
+ @has_warnings = false
61
+ end
62
+
63
+ def truncate!(max_bytes = 500_000)
64
+ log_file = File.join(Config.log_path, 'backup.log')
65
+ return unless File.exist?(log_file)
66
+
67
+ if File.stat(log_file).size > max_bytes
68
+ FileUtils.mv(log_file, log_file + '~')
69
+ File.open(log_file + '~', 'r') do |io_in|
70
+ File.open(log_file, 'w') do |io_out|
71
+ io_in.seek(-max_bytes, IO::SEEK_END) && io_in.gets
72
+ while line = io_in.gets
73
+ io_out.puts line
74
+ end
75
+ end
76
+ end
77
+ FileUtils.rm_f(log_file + '~')
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ ##
84
+ # Returns the time in [YYYY/MM/DD HH:MM:SS] format
85
+ def time
86
+ Time.now.strftime("%Y/%m/%d %H:%M:%S")
87
+ end
88
+
89
+ ##
90
+ # Receives a String, or an Object that responds to #to_s (e.g. an
91
+ # Exception), from one of the messaging methods and converts it into an
92
+ # Array of Strings, split on newline separators. Each line is then
93
+ # formatted into a log format based on the given options, and the Array
94
+ # returned to be passed to to_console() and/or to_file().
95
+ def loggify(string, type = false, color = false)
96
+ lines = string.to_s.split("\n")
97
+ if type
98
+ type = send(color, type) if color
99
+ time_now = time
100
+ lines.map {|line| "[#{time_now}][#{type}] #{line}" }
101
+ else
102
+ lines
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Receives an Array of Strings to be written to the console.
108
+ def to_console(lines, stderr = false)
109
+ return if quiet
110
+ lines.each {|line| stderr ? Kernel.warn(line) : puts(line) }
111
+ end
112
+
113
+ ##
114
+ # Receives an Array of Strings to be written to the log file.
115
+ def to_file(lines)
116
+ File.open(File.join(Config.log_path, 'backup.log'), 'a') do |file|
117
+ lines.each {|line| file.puts line }
118
+ end
119
+ messages.push(*lines)
120
+ end
121
+
122
+ ##
123
+ # Invokes the #colorize method with the provided string
124
+ # and the color code "32" (for green)
125
+ def green(string)
126
+ colorize(string, 32)
127
+ end
128
+
129
+ ##
130
+ # Invokes the #colorize method with the provided string
131
+ # and the color code "33" (for yellow)
132
+ def yellow(string)
133
+ colorize(string, 33)
134
+ end
135
+
136
+ ##
137
+ # Invokes the #colorize method the with provided string
138
+ # and the color code "31" (for red)
139
+ def red(string)
140
+ colorize(string, 31)
141
+ end
142
+
143
+ ##
144
+ # Wraps the provided string in colorizing tags to provide
145
+ # easier to view output to the client
146
+ def colorize(string, code)
147
+ "\e[#{code}m#{string}\e[0m"
148
+ end
149
+
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,409 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Model
5
+ include Backup::CLI::Helpers
6
+
7
+ class << self
8
+ ##
9
+ # The Backup::Model.all class method keeps track of all the models
10
+ # that have been instantiated. It returns the @all class variable,
11
+ # which contains an array of all the models
12
+ def all
13
+ @all ||= []
14
+ end
15
+
16
+ ##
17
+ # Return the first model matching +trigger+.
18
+ # Raises Errors::MissingTriggerError if no matches are found.
19
+ def find(trigger)
20
+ trigger = trigger.to_s
21
+ all.each do |model|
22
+ return model if model.trigger == trigger
23
+ end
24
+ raise Errors::Model::MissingTriggerError,
25
+ "Could not find trigger '#{trigger}'."
26
+ end
27
+
28
+ ##
29
+ # Find and return an Array of all models matching +trigger+
30
+ # Used to match triggers using a wildcard (*)
31
+ def find_matching(trigger)
32
+ regex = /^#{ trigger.to_s.gsub('*', '(.*)') }$/
33
+ all.select {|model| regex =~ model.trigger }
34
+ end
35
+ end
36
+
37
+ ##
38
+ # The trigger (stored as a String) is used as an identifier
39
+ # for initializing the backup process
40
+ attr_reader :trigger
41
+
42
+ ##
43
+ # The label (stored as a String) is used for a more friendly user output
44
+ attr_reader :label
45
+
46
+ ##
47
+ # The databases attribute holds an array of database objects
48
+ attr_reader :databases
49
+
50
+ ##
51
+ # The archives attr_accessor holds an array of archive objects
52
+ attr_reader :archives
53
+
54
+ ##
55
+ # The notifiers attr_accessor holds an array of notifier objects
56
+ attr_reader :notifiers
57
+
58
+ ##
59
+ # The storages attribute holds an array of storage objects
60
+ attr_reader :storages
61
+
62
+ ##
63
+ # The syncers attribute holds an array of syncer objects
64
+ attr_reader :syncers
65
+
66
+ ##
67
+ # Holds the configured Compressor
68
+ attr_reader :compressor
69
+
70
+ ##
71
+ # Holds the configured Encryptor
72
+ attr_reader :encryptor
73
+
74
+ ##
75
+ # Holds the configured Splitter
76
+ attr_reader :splitter
77
+
78
+ ##
79
+ # The final backup Package this model will create.
80
+ attr_reader :package
81
+
82
+ ##
83
+ # The time when the backup initiated (in format: 2011.02.20.03.29.59)
84
+ attr_reader :time
85
+
86
+ ##
87
+ # Hooks which can be run before or after the backup process
88
+ attr_reader :hooks
89
+
90
+ ##
91
+ # Takes a trigger, label and the configuration block.
92
+ # After the instance has evaluated the configuration block
93
+ # to configure the model, it will be appended to Model.all
94
+ def initialize(trigger, label, &block)
95
+ @trigger = trigger.to_s
96
+ @label = label.to_s
97
+
98
+ # default noop hooks
99
+ @hooks = Hooks.new(self)
100
+
101
+ procedure_instance_variables.each do |variable|
102
+ instance_variable_set(variable, Array.new)
103
+ end
104
+
105
+ instance_eval(&block) if block_given?
106
+ Model.all << self
107
+ end
108
+
109
+ ##
110
+ # Adds an archive to the array of archives
111
+ # to store during the backup process
112
+ def archive(name, &block)
113
+ @archives << Archive.new(self, name, &block)
114
+ end
115
+
116
+ ##
117
+ # Adds a database to the array of databases
118
+ # to dump during the backup process
119
+ def database(name, &block)
120
+ @databases << get_class_from_scope(Database, name).new(self, &block)
121
+ end
122
+
123
+ ##
124
+ # Adds a storage method to the array of storage
125
+ # methods to use during the backup process
126
+ def store_with(name, storage_id = nil, &block)
127
+ @storages << get_class_from_scope(Storage, name).new(self, storage_id, &block)
128
+ end
129
+
130
+ ##
131
+ # Adds a syncer method to the array of syncer
132
+ # methods to use during the backup process
133
+ def sync_with(name, &block)
134
+ ##
135
+ # Warn user of DSL changes
136
+ case name.to_s
137
+ when 'Backup::Config::RSync'
138
+ Logger.warn Errors::ConfigError.new(<<-EOS)
139
+ Configuration Update Needed for Syncer::RSync
140
+ The RSync Syncer has been split into three separate modules:
141
+ RSync::Local, RSync::Push and RSync::Pull
142
+ Please update your configuration.
143
+ i.e. 'sync_with RSync' is now 'sync_with RSync::Push'
144
+ EOS
145
+ name = 'RSync::Push'
146
+ when /(Backup::Config::S3|Backup::Config::CloudFiles)/
147
+ syncer = $1.split('::')[2]
148
+ Logger.warn Errors::ConfigError.new(<<-EOS)
149
+ Configuration Update Needed for '#{ syncer }' Syncer.
150
+ This Syncer is now referenced as Cloud::#{ syncer }
151
+ i.e. 'sync_with #{ syncer }' is now 'sync_with Cloud::#{ syncer }'
152
+ EOS
153
+ name = "Cloud::#{ syncer }"
154
+ end
155
+ @syncers << get_class_from_scope(Syncer, name).new(&block)
156
+ end
157
+
158
+ ##
159
+ # Adds a notifier to the array of notifiers
160
+ # to use during the backup process
161
+ def notify_by(name, &block)
162
+ @notifiers << get_class_from_scope(Notifier, name).new(self, &block)
163
+ end
164
+
165
+ ##
166
+ # Adds an encryptor to use during the backup process
167
+ def encrypt_with(name, &block)
168
+ @encryptor = get_class_from_scope(Encryptor, name).new(&block)
169
+ end
170
+
171
+ ##
172
+ # Adds a compressor to use during the backup process
173
+ def compress_with(name, &block)
174
+ @compressor = get_class_from_scope(Compressor, name).new(&block)
175
+ end
176
+
177
+ ##
178
+ # Run a block of ruby code before the backup process
179
+ def before(&block)
180
+ @hooks.before &block
181
+ end
182
+
183
+ ##
184
+ # Run a block of ruby code after the backup process
185
+ def after(&block)
186
+ @hooks.after &block
187
+ end
188
+
189
+ ##
190
+ # Adds a method that allows the user to configure this backup model
191
+ # to use a Splitter, with the given +chunk_size+
192
+ # The +chunk_size+ (in megabytes) will later determine
193
+ # in how many chunks the backup needs to be split into
194
+ def split_into_chunks_of(chunk_size)
195
+ if chunk_size.is_a?(Integer)
196
+ @splitter = Splitter.new(self, chunk_size)
197
+ else
198
+ raise Errors::Model::ConfigurationError, <<-EOS
199
+ Invalid Chunk Size for Splitter
200
+ Argument to #split_into_chunks_of() must be an Integer
201
+ EOS
202
+ end
203
+ end
204
+
205
+ ##
206
+ # Ensure DATA_PATH and DATA_PATH/TRIGGER are created
207
+ # if they do not yet exist
208
+ #
209
+ # Clean any temporary files and/or package files left over
210
+ # from the last time this model/trigger was performed.
211
+ # Logs warnings if files exist and are cleaned.
212
+ def prepare!
213
+ FileUtils.mkdir_p(File.join(Config.data_path, trigger))
214
+ Cleaner.prepare(self)
215
+ end
216
+
217
+ ##
218
+ # Performs the backup process
219
+ ##
220
+ # [Databases]
221
+ # Runs all (if any) database objects to dump the databases
222
+ ##
223
+ # [Archives]
224
+ # Runs all (if any) archive objects to package all their
225
+ # paths in to a single tar file and places it in the backup folder
226
+ ##
227
+ # [Packaging]
228
+ # After all the database dumps and archives are placed inside
229
+ # the folder, it'll make a single .tar package (archive) out of it
230
+ ##
231
+ # [Encryption]
232
+ # Optionally encrypts the packaged file with the configured encryptor
233
+ ##
234
+ # [Compression]
235
+ # Optionally compresses the each Archive and Database dump with the configured compressor
236
+ ##
237
+ # [Splitting]
238
+ # Optionally splits the backup file in to multiple smaller chunks before transferring them
239
+ ##
240
+ # [Storages]
241
+ # Runs all (if any) storage objects to store the backups to remote locations
242
+ # and (if configured) it'll cycle the files on the remote location to limit the
243
+ # amount of backups stored on each individual location
244
+ ##
245
+ # [Syncers]
246
+ # Runs all (if any) sync objects to store the backups to remote locations.
247
+ # A Syncer does not go through the process of packaging, compressing, encrypting backups.
248
+ # A Syncer directly transfers data from the filesystem to the remote location
249
+ ##
250
+ # [Notifiers]
251
+ # Runs all (if any) notifier objects when a backup proces finished with or without
252
+ # any errors.
253
+ ##
254
+ # [Cleaning]
255
+ # Once the final Packaging is complete, the temporary folder used will be removed.
256
+ # Then, once all Storages have run, the final packaged files will be removed.
257
+ # If any errors occur during the backup process, all temporary files will be left in place.
258
+ # If the error occurs before Packaging, then the temporary folder (tmp_path/trigger)
259
+ # will remain and may contain all or some of the configured Archives and/or Database dumps.
260
+ # If the error occurs after Packaging, but before the Storages complete, then the final
261
+ # packaged files (located in the root of tmp_path) will remain.
262
+ # *** Important *** If an error occurs and any of the above mentioned temporary files remain,
263
+ # those files *** will be removed *** before the next scheduled backup for the same trigger.
264
+ #
265
+ def perform!
266
+ @started_at = Time.now
267
+ @time = @started_at.strftime("%Y.%m.%d.%H.%M.%S")
268
+ log!(:started)
269
+
270
+ @hooks.perform!(:before)
271
+
272
+ if databases.any? or archives.any?
273
+ procedures.each do |procedure|
274
+ (procedure.call; next) if procedure.is_a?(Proc)
275
+ procedure.each(&:perform!)
276
+ end
277
+ end
278
+
279
+ syncers.each(&:perform!)
280
+ notifiers.each(&:perform!)
281
+
282
+ @hooks.perform!(:after)
283
+
284
+ log!(:finished)
285
+
286
+ rescue Exception => err
287
+ fatal = !err.is_a?(StandardError)
288
+
289
+ err = Errors::ModelError.wrap(err, <<-EOS)
290
+ Backup for #{label} (#{trigger}) Failed!
291
+ An Error occured which has caused this Backup to abort before completion.
292
+ EOS
293
+ Logger.error err
294
+ Logger.error "\nBacktrace:\n\s\s" + err.backtrace.join("\n\s\s") + "\n\n"
295
+
296
+ Cleaner.warnings(self)
297
+
298
+ if fatal
299
+ Logger.error Errors::ModelError.new(<<-EOS)
300
+ This Error was Fatal and Backup will now exit.
301
+ If you have other Backup jobs (triggers) configured to run,
302
+ they will not be processed.
303
+ EOS
304
+ else
305
+ Logger.message Errors::ModelError.new(<<-EOS)
306
+ If you have other Backup jobs (triggers) configured to run,
307
+ Backup will now attempt to continue...
308
+ EOS
309
+ end
310
+
311
+ notifiers.each do |n|
312
+ begin
313
+ n.perform!(true)
314
+ rescue Exception; end
315
+ end
316
+
317
+ exit(1) if fatal
318
+ end
319
+
320
+ private
321
+
322
+ ##
323
+ # After all the databases and archives have been dumped and sorted,
324
+ # these files will be bundled in to a .tar archive (uncompressed),
325
+ # which may be optionally Encrypted and/or Split into multiple "chunks".
326
+ # All information about this final archive is stored in the @package.
327
+ # Once complete, the temporary folder used during packaging is removed.
328
+ def package!
329
+ @package = Package.new(self)
330
+ Packager.package!(self)
331
+ Cleaner.remove_packaging(self)
332
+ end
333
+
334
+ ##
335
+ # Removes the final package file(s) once all configured Storages have run.
336
+ def clean!
337
+ Cleaner.remove_package(@package)
338
+ end
339
+
340
+ ##
341
+ # Returns an array of procedures
342
+ def procedures
343
+ [databases, archives, lambda { package! }, storages, lambda { clean! }]
344
+ end
345
+
346
+ ##
347
+ # Returns an Array of the names (String) of the procedure instance variables
348
+ def procedure_instance_variables
349
+ [:@databases, :@archives, :@storages, :@notifiers, :@syncers]
350
+ end
351
+
352
+ ##
353
+ # Returns the class/model specified by +name+ inside of +scope+.
354
+ # +scope+ should be a Class/Module.
355
+ # +name+ may be Class/Module or String representation
356
+ # of any namespace which exists under +scope+.
357
+ #
358
+ # The 'Backup::Config::' namespace is stripped from +name+,
359
+ # since this is the namespace where we define module namespaces
360
+ # for use with Model's DSL methods.
361
+ #
362
+ # Examples:
363
+ # get_class_from_scope(Backup::Database, 'MySQL')
364
+ # returns the class Backup::Database::MySQL
365
+ #
366
+ # get_class_from_scope(Backup::Syncer, Backup::Config::RSync::Local)
367
+ # returns the class Backup::Syncer::RSync::Local
368
+ #
369
+ def get_class_from_scope(scope, name)
370
+ klass = scope
371
+ name = name.to_s.sub(/^Backup::Config::/, '')
372
+ name.split('::').each do |chunk|
373
+ klass = klass.const_get(chunk)
374
+ end
375
+ klass
376
+ end
377
+
378
+ ##
379
+ # Logs messages when the backup starts and finishes
380
+ def log!(action)
381
+ case action
382
+ when :started
383
+ Logger.message "Performing Backup for '#{label} (#{trigger})'!\n" +
384
+ "[ backup #{ Version.current } : #{ RUBY_DESCRIPTION } ]"
385
+
386
+ when :finished
387
+ msg = "Backup for '#{ label } (#{ trigger })' " +
388
+ "Completed %s in #{ elapsed_time }"
389
+ if Logger.has_warnings?
390
+ Logger.warn msg % 'Successfully (with Warnings)'
391
+ else
392
+ Logger.message msg % 'Successfully'
393
+ end
394
+ end
395
+ end
396
+
397
+ ##
398
+ # Returns a string representing the elapsed time since the backup started.
399
+ def elapsed_time
400
+ duration = Time.now.to_i - @started_at.to_i
401
+ hours = duration / 3600
402
+ remainder = duration - (hours * 3600)
403
+ minutes = remainder / 60
404
+ seconds = remainder - (minutes * 60)
405
+ '%02d:%02d:%02d' % [hours, minutes, seconds]
406
+ end
407
+
408
+ end
409
+ end