backup 3.0.19 → 3.0.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +9 -8
  3. data/Gemfile.lock +19 -1
  4. data/Guardfile +13 -9
  5. data/README.md +93 -31
  6. data/backup.gemspec +3 -3
  7. data/bin/backup +6 -283
  8. data/lib/backup.rb +101 -72
  9. data/lib/backup/archive.rb +21 -9
  10. data/lib/backup/binder.rb +22 -0
  11. data/lib/backup/cleaner.rb +36 -0
  12. data/lib/backup/cli/helpers.rb +103 -0
  13. data/lib/backup/cli/utility.rb +308 -0
  14. data/lib/backup/compressor/base.rb +2 -2
  15. data/lib/backup/compressor/pbzip2.rb +76 -0
  16. data/lib/backup/configuration/compressor/pbzip2.rb +28 -0
  17. data/lib/backup/configuration/database/riak.rb +25 -0
  18. data/lib/backup/configuration/encryptor/open_ssl.rb +6 -0
  19. data/lib/backup/configuration/helpers.rb +5 -18
  20. data/lib/backup/configuration/notifier/base.rb +13 -0
  21. data/lib/backup/configuration/notifier/hipchat.rb +41 -0
  22. data/lib/backup/configuration/notifier/mail.rb +38 -0
  23. data/lib/backup/configuration/notifier/prowl.rb +23 -0
  24. data/lib/backup/configuration/storage/cloudfiles.rb +4 -0
  25. data/lib/backup/configuration/storage/dropbox.rb +8 -4
  26. data/lib/backup/database/base.rb +10 -2
  27. data/lib/backup/database/mongodb.rb +16 -19
  28. data/lib/backup/database/mysql.rb +2 -2
  29. data/lib/backup/database/postgresql.rb +2 -2
  30. data/lib/backup/database/redis.rb +15 -7
  31. data/lib/backup/database/riak.rb +45 -0
  32. data/lib/backup/dependency.rb +21 -7
  33. data/lib/backup/encryptor/base.rb +1 -1
  34. data/lib/backup/encryptor/open_ssl.rb +20 -5
  35. data/lib/backup/errors.rb +124 -0
  36. data/lib/backup/finder.rb +11 -3
  37. data/lib/backup/logger.rb +121 -82
  38. data/lib/backup/model.rb +103 -44
  39. data/lib/backup/notifier/base.rb +50 -0
  40. data/lib/backup/notifier/campfire.rb +32 -52
  41. data/lib/backup/notifier/hipchat.rb +99 -0
  42. data/lib/backup/notifier/mail.rb +100 -61
  43. data/lib/backup/notifier/presently.rb +31 -40
  44. data/lib/backup/notifier/prowl.rb +73 -0
  45. data/lib/backup/notifier/twitter.rb +29 -39
  46. data/lib/backup/packager.rb +25 -0
  47. data/lib/backup/splitter.rb +62 -0
  48. data/lib/backup/storage/base.rb +178 -18
  49. data/lib/backup/storage/cloudfiles.rb +34 -28
  50. data/lib/backup/storage/dropbox.rb +64 -67
  51. data/lib/backup/storage/ftp.rb +48 -40
  52. data/lib/backup/storage/local.rb +33 -28
  53. data/lib/backup/storage/ninefold.rb +40 -26
  54. data/lib/backup/storage/object.rb +8 -6
  55. data/lib/backup/storage/rsync.rb +61 -51
  56. data/lib/backup/storage/s3.rb +29 -27
  57. data/lib/backup/storage/scp.rb +56 -36
  58. data/lib/backup/storage/sftp.rb +49 -33
  59. data/lib/backup/syncer/base.rb +1 -1
  60. data/lib/backup/syncer/rsync.rb +1 -1
  61. data/lib/backup/template.rb +46 -0
  62. data/lib/backup/version.rb +1 -1
  63. data/spec/archive_spec.rb +34 -9
  64. data/spec/backup_spec.rb +1 -1
  65. data/spec/cli/helpers_spec.rb +35 -0
  66. data/spec/cli/utility_spec.rb +38 -0
  67. data/spec/compressor/bzip2_spec.rb +1 -1
  68. data/spec/compressor/gzip_spec.rb +1 -1
  69. data/spec/compressor/lzma_spec.rb +1 -1
  70. data/spec/compressor/pbzip2_spec.rb +63 -0
  71. data/spec/configuration/base_spec.rb +1 -1
  72. data/spec/configuration/compressor/bzip2_spec.rb +1 -1
  73. data/spec/configuration/compressor/gzip_spec.rb +1 -1
  74. data/spec/configuration/compressor/lzma_spec.rb +1 -1
  75. data/spec/configuration/database/base_spec.rb +1 -1
  76. data/spec/configuration/database/mongodb_spec.rb +1 -1
  77. data/spec/configuration/database/mysql_spec.rb +1 -1
  78. data/spec/configuration/database/postgresql_spec.rb +1 -1
  79. data/spec/configuration/database/redis_spec.rb +1 -1
  80. data/spec/configuration/database/riak_spec.rb +31 -0
  81. data/spec/configuration/encryptor/gpg_spec.rb +1 -1
  82. data/spec/configuration/encryptor/open_ssl_spec.rb +4 -1
  83. data/spec/configuration/notifier/campfire_spec.rb +1 -1
  84. data/spec/configuration/notifier/hipchat_spec.rb +43 -0
  85. data/spec/configuration/notifier/mail_spec.rb +34 -22
  86. data/spec/configuration/notifier/presently_spec.rb +1 -1
  87. data/spec/configuration/notifier/prowl_spec.rb +28 -0
  88. data/spec/configuration/notifier/twitter_spec.rb +1 -1
  89. data/spec/configuration/storage/cloudfiles_spec.rb +19 -16
  90. data/spec/configuration/storage/dropbox_spec.rb +1 -1
  91. data/spec/configuration/storage/ftp_spec.rb +1 -1
  92. data/spec/configuration/storage/local_spec.rb +1 -1
  93. data/spec/configuration/storage/ninefold_spec.rb +1 -1
  94. data/spec/configuration/storage/rsync_spec.rb +1 -1
  95. data/spec/configuration/storage/s3_spec.rb +1 -1
  96. data/spec/configuration/storage/scp_spec.rb +1 -1
  97. data/spec/configuration/storage/sftp_spec.rb +1 -1
  98. data/spec/configuration/syncer/rsync_spec.rb +1 -1
  99. data/spec/configuration/syncer/s3_spec.rb +1 -1
  100. data/spec/database/base_spec.rb +10 -1
  101. data/spec/database/mongodb_spec.rb +34 -7
  102. data/spec/database/mysql_spec.rb +8 -7
  103. data/spec/database/postgresql_spec.rb +8 -7
  104. data/spec/database/redis_spec.rb +39 -9
  105. data/spec/database/riak_spec.rb +50 -0
  106. data/spec/encryptor/gpg_spec.rb +1 -1
  107. data/spec/encryptor/open_ssl_spec.rb +77 -20
  108. data/spec/errors_spec.rb +306 -0
  109. data/spec/finder_spec.rb +91 -0
  110. data/spec/logger_spec.rb +254 -33
  111. data/spec/model_spec.rb +120 -15
  112. data/spec/notifier/campfire_spec.rb +127 -52
  113. data/spec/notifier/hipchat_spec.rb +193 -0
  114. data/spec/notifier/mail_spec.rb +290 -74
  115. data/spec/notifier/presently_spec.rb +290 -73
  116. data/spec/notifier/prowl_spec.rb +149 -0
  117. data/spec/notifier/twitter_spec.rb +106 -41
  118. data/spec/spec_helper.rb +8 -2
  119. data/spec/splitter_spec.rb +71 -0
  120. data/spec/storage/base_spec.rb +280 -19
  121. data/spec/storage/cloudfiles_spec.rb +38 -22
  122. data/spec/storage/dropbox_spec.rb +17 -13
  123. data/spec/storage/ftp_spec.rb +145 -55
  124. data/spec/storage/local_spec.rb +6 -6
  125. data/spec/storage/ninefold_spec.rb +70 -29
  126. data/spec/storage/object_spec.rb +44 -44
  127. data/spec/storage/rsync_spec.rb +186 -63
  128. data/spec/storage/s3_spec.rb +23 -24
  129. data/spec/storage/scp_spec.rb +116 -41
  130. data/spec/storage/sftp_spec.rb +124 -46
  131. data/spec/syncer/rsync_spec.rb +3 -3
  132. data/spec/syncer/s3_spec.rb +1 -1
  133. data/spec/version_spec.rb +1 -1
  134. data/templates/cli/utility/archive +13 -0
  135. data/{lib/templates → templates/cli/utility}/compressor/bzip2 +1 -1
  136. data/{lib/templates → templates/cli/utility}/compressor/gzip +1 -1
  137. data/{lib/templates → templates/cli/utility}/compressor/lzma +0 -0
  138. data/templates/cli/utility/compressor/pbzip2 +7 -0
  139. data/templates/cli/utility/config +31 -0
  140. data/{lib/templates → templates/cli/utility}/database/mongodb +1 -1
  141. data/{lib/templates → templates/cli/utility}/database/mysql +1 -1
  142. data/{lib/templates → templates/cli/utility}/database/postgresql +1 -1
  143. data/{lib/templates → templates/cli/utility}/database/redis +1 -1
  144. data/templates/cli/utility/database/riak +8 -0
  145. data/{lib/templates → templates/cli/utility}/encryptor/gpg +1 -1
  146. data/templates/cli/utility/encryptor/openssl +9 -0
  147. data/templates/cli/utility/model.erb +23 -0
  148. data/{lib/templates → templates/cli/utility}/notifier/campfire +2 -1
  149. data/templates/cli/utility/notifier/hipchat +15 -0
  150. data/{lib/templates → templates/cli/utility}/notifier/mail +6 -1
  151. data/{lib/templates → templates/cli/utility}/notifier/presently +1 -0
  152. data/templates/cli/utility/notifier/prowl +11 -0
  153. data/{lib/templates → templates/cli/utility}/notifier/twitter +2 -1
  154. data/templates/cli/utility/splitter +7 -0
  155. data/templates/cli/utility/storage/cloudfiles +12 -0
  156. data/{lib/templates → templates/cli/utility}/storage/dropbox +1 -1
  157. data/{lib/templates → templates/cli/utility}/storage/ftp +0 -0
  158. data/templates/cli/utility/storage/local +7 -0
  159. data/{lib/templates → templates/cli/utility}/storage/ninefold +1 -1
  160. data/templates/cli/utility/storage/rsync +11 -0
  161. data/{lib/templates → templates/cli/utility}/storage/s3 +0 -2
  162. data/templates/cli/utility/storage/scp +11 -0
  163. data/templates/cli/utility/storage/sftp +11 -0
  164. data/{lib/templates → templates/cli/utility}/syncer/rsync +1 -1
  165. data/{lib/templates → templates/cli/utility}/syncer/s3 +1 -1
  166. data/templates/general/links +11 -0
  167. data/templates/general/version.erb +2 -0
  168. data/templates/notifier/mail/failure.erb +9 -0
  169. data/templates/notifier/mail/success.erb +7 -0
  170. data/templates/notifier/mail/warning.erb +9 -0
  171. data/templates/storage/dropbox/authorization_url.erb +6 -0
  172. data/templates/storage/dropbox/authorized.erb +4 -0
  173. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  174. metadata +81 -45
  175. data/lib/backup/cli.rb +0 -110
  176. data/lib/backup/exception/command_failed.rb +0 -8
  177. data/lib/backup/exception/command_not_found.rb +0 -8
  178. data/lib/backup/notifier/binder.rb +0 -32
  179. data/lib/backup/notifier/templates/notify_failure.erb +0 -33
  180. data/lib/backup/notifier/templates/notify_success.erb +0 -16
  181. data/lib/templates/archive +0 -7
  182. data/lib/templates/encryptor/openssl +0 -8
  183. data/lib/templates/readme +0 -15
  184. data/lib/templates/storage/cloudfiles +0 -11
  185. data/lib/templates/storage/local +0 -7
  186. data/lib/templates/storage/rsync +0 -11
  187. data/lib/templates/storage/scp +0 -11
  188. data/lib/templates/storage/sftp +0 -11
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Packager
5
+ include Backup::CLI::Helpers
6
+
7
+ ##
8
+ # Holds an instance of the current Backup model
9
+ attr_accessor :model
10
+
11
+ ##
12
+ # Creates a new instance of the Backup::Packager class
13
+ def initialize(model)
14
+ @model = model
15
+ end
16
+
17
+ ##
18
+ # Packages the current state of the backup in to a single archived file.
19
+ def package!
20
+ Logger.message "#{ self.class } started packaging the backup files."
21
+ run("#{ utility(:tar) } -c -f '#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }' -C '#{ Backup::TMP_PATH }' '#{ Backup::TRIGGER }'")
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Splitter
5
+ include Backup::CLI::Helpers
6
+
7
+ ##
8
+ # Separates the end of the file from the chunk extension name
9
+ SUFFIX_SEPARATOR = "-"
10
+
11
+ ##
12
+ # Holds an instance of the current Backup model
13
+ attr_accessor :model
14
+
15
+ ##
16
+ # Instantiates a new instance of Backup::Splitter and takes
17
+ # a Backup model as an argument.
18
+ # Also, (re)set the Backup::Model.chunk_suffixes to an empty array.
19
+ def initialize(model)
20
+ @model = model
21
+ Backup::Model.chunk_suffixes = Array.new
22
+ end
23
+
24
+ ##
25
+ # Splits the file in multiple chunks if necessary, and it's necessary
26
+ # when the requested chunk size is smaller than the actual backup file
27
+ def split!
28
+ return unless model.chunk_size.is_a?(Integer)
29
+
30
+ if File.size(model.file) > bytes_representation_of(model.chunk_size)
31
+ Logger.message "#{ self.class } started splitting the packaged archive in to chunks of #{ model.chunk_size } megabytes."
32
+ run("#{ utility(:split) } -b #{ model.chunk_size }m '#{ model.file }' '#{ model.file + SUFFIX_SEPARATOR }'")
33
+ Backup::Model.chunk_suffixes = chunk_suffixes
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ ##
40
+ # Returns an array of suffixes for each chunk.
41
+ # For example: [aa, ab, ac, ad, ae] - Chunk suffixes are sorted on alphabetical order
42
+ def chunk_suffixes
43
+ chunks.map do |chunk|
44
+ File.extname(chunk).split("-").last
45
+ end.sort
46
+ end
47
+
48
+ ##
49
+ # Returns an array of full paths to the backup chunks.
50
+ # Chunks aresorted on alphabetical order
51
+ def chunks
52
+ Dir["#{model.file}-*"].sort
53
+ end
54
+
55
+ ##
56
+ # Converts the provided megabytes to a bytes representation
57
+ def bytes_representation_of(megabytes)
58
+ megabytes * 1024 * 1024
59
+ end
60
+
61
+ end
62
+ end
@@ -11,9 +11,65 @@ module Backup
11
11
 
12
12
  ##
13
13
  # Sets the limit to how many backups to keep in the remote location.
14
- # If the limit exceeds it will remove the oldest backup to make room for the newest
14
+ # If exceeded, the oldest will be removed to make room for the newest
15
15
  attr_accessor :keep
16
16
 
17
+ # Temporarily holds the configuration block used to instantiate the
18
+ # storage object. Used for updating storage objects loaded from YAML
19
+ # during backup rotation in #cycle!
20
+ attr_accessor :configure_block
21
+
22
+ ##
23
+ # Contains an array of chunk suffixes (if any)
24
+ # If none are set, this will be an empty array, in which case Backup assumes
25
+ # we haven't been splitting the backup in to multiple chunks. The storage object
26
+ # will only attempt to transfer/remove chunks if this array contains chunk suffixes.
27
+ attr_accessor :chunk_suffixes
28
+
29
+ ##
30
+ # Super method for the child classes' perform! method. "super" should
31
+ # always be invoked from the child classes' perform! method to ensure that the
32
+ # @chunk_suffixes array gets set to the storage object, which will be used to transfer all the
33
+ # chunks to the remote location, rather than the single backup file. Also, this will be persisted
34
+ # and loaded back in during the cycling process, so it gets properly deleted from the remote location.
35
+
36
+ ##
37
+ # (Optional)
38
+ # User-defined string used to uniquely identify multiple storages of the same type.
39
+ # This will be appended to the YAML storage file used for cycling backups.
40
+ attr_accessor :storage_id
41
+
42
+ ##
43
+ # Set to Backup::Version.current just before the object is stored
44
+ # in the YAML file for cycling. This way, we know when the object
45
+ # is loaded from the YAML file, which version of Backup stored it.
46
+ attr_reader :version
47
+
48
+ def perform!
49
+ @chunk_suffixes ||= Backup::Model.chunk_suffixes
50
+ end
51
+
52
+ ##
53
+ # Creates a new instance of the storage object
54
+ def initialize(storage_id = nil, &block)
55
+ @configure_block = block
56
+ @storage_id = storage_id
57
+ configure!
58
+ end
59
+
60
+ ##
61
+ # Return the storage name, with optional storage_id
62
+ def storage_name
63
+ self.class.to_s.sub('Backup::', '') +
64
+ (storage_id ? " (#{storage_id})" : '')
65
+ end
66
+
67
+ ##
68
+ # Returns the full filename of the processed backup file
69
+ def filename
70
+ @filename ||= File.basename(Backup::Model.file)
71
+ end
72
+
17
73
  ##
18
74
  # Returns the local path
19
75
  def local_path
@@ -21,44 +77,148 @@ module Backup
21
77
  end
22
78
 
23
79
  ##
24
- # Returns the local archive filename
25
- def local_file
26
- @local_file ||= File.basename(Backup::Model.file)
80
+ # Returns an array of backup chunks
81
+ def chunks
82
+ chunk_suffixes.map do |chunk_suffix|
83
+ "#{ filename }-#{ chunk_suffix }"
84
+ end.sort
85
+ end
86
+
87
+ ##
88
+ # Returns a block with two arguments: "local_file, remote_file"
89
+ # The local_file is the full file name: "2011.08.30.11.00.02.backup.tar.gz.enc"
90
+ # The remote_file is the full file name, minus the timestamp: "backup.tar.gz.enc"
91
+ def files_to_transfer
92
+ if chunks?
93
+ chunks.each do |chunk|
94
+ yield chunk, chunk[20..-1]
95
+ end
96
+ else
97
+ yield filename, filename[20..-1]
98
+ end
27
99
  end
28
100
 
101
+ alias :transferred_files :files_to_transfer
102
+
29
103
  ##
30
- # Returns the name of the file that's stored on the remote location
31
- def remote_file
32
- @remote_file ||= local_file
104
+ # Returns true if we're working with chunks
105
+ # that were splitted by Backup
106
+ def chunks?
107
+ chunk_suffixes.is_a?(Array) and chunk_suffixes.count > 0
33
108
  end
34
109
 
35
110
  ##
36
- # Provider defaults to false and will be overridden when using
37
- # a service-based storage such as Amazon S3, Rackspace Cloud Files or Dropbox
111
+ # Provider defaults to false. Overridden when using a service-based
112
+ # storage such as Amazon S3, Rackspace Cloud Files or Dropbox
38
113
  def provider
39
114
  false
40
115
  end
41
116
 
117
+ private
118
+
119
+ ##
120
+ # Configure the storage object, using optional configuration block
121
+ # Uses #pre_configure to set defaults (if any exist) and then evaluates
122
+ # the optional configuration block which may overwrite these defaults.
123
+ # Then uses #post_configure to adjust the configuration as needed.
124
+ #
125
+ # This method is also used to update storage objects loaded from the
126
+ # YAML data storage file used for backup rotation in #cycle!
127
+ def configure!
128
+ pre_configure
129
+ instance_eval(&@configure_block) if @configure_block
130
+ post_configure
131
+ self
132
+ end
133
+
134
+ ##
135
+ # Set configuration defaults before evaluating configuration block.
136
+ # Each subclass may perform additional actions after calling super()
137
+ def pre_configure
138
+ load_defaults!
139
+ end
140
+
141
+ ##
142
+ # Adjust configuration after evaluating configuration block.
143
+ # Each subclass may perform additional actions after calling super()
144
+ def post_configure
145
+ @time ||= TIME
146
+ end
147
+
148
+ ##
149
+ # Update the configuration with the given +configure_block+
150
+ # This is to update the configuration for this storage object
151
+ # once it's been loaded from the YAML storage file (within #cycle!)
152
+ # so that cycling operations can be performed using the latest
153
+ # configuration from the current backup job.
154
+ def update!(configure_block)
155
+ upgrade_if_needed!
156
+ instance_exec(configure_block) do |block|
157
+ @configure_block = block; configure!
158
+ end
159
+ end
160
+
161
+ ##
162
+ # Upgrades the format of an object loaded from the YAML storage file
163
+ # if it was stored with a previous, incompatible version of Backup,
164
+ # before it is updated using the new configure_block in #update!
165
+ def upgrade_if_needed!
166
+ return if version == Backup::Version.current
167
+ case
168
+ when version.nil? # <= 3.0.19
169
+ @filename = @remote_file
170
+ @chunk_suffixes = []
171
+ clean!
172
+ else; # upgrade not required
173
+ end
174
+ end
175
+
176
+ ##
177
+ # Clear all attributes except those which need to be stored
178
+ # in the YAML storage file for cycling, and version stamp it.
179
+ def clean!
180
+ stored_attrs = [:@filename, :@time, :@chunk_suffixes]
181
+ (instance_variables.map(&:to_sym) - stored_attrs).each do |var|
182
+ remove_instance_variable var
183
+ end
184
+ @version = Backup::Version.current
185
+ end
186
+
42
187
  ##
43
188
  # Checks the persisted storage data by type (S3, CloudFiles, SCP, etc)
44
189
  # to see if the amount of stored backups is greater than the amount of
45
190
  # backups allowed. If this is the case it'll invoke the #remove! method
46
- # on each of the oldest backups that exceed the storage limit (specified by @keep).
47
- # After that it'll re-assign the objects variable with an array of objects that still remain
48
- # after the removal of the older objects and files (that exceeded the @keep range). And finally
49
- # these remaining objects will be converted to YAML format and are written back to the YAML file
191
+ # on each of the oldest backups that exceed the storage limit (specified
192
+ # by @keep). After that it'll re-assign the objects variable with an
193
+ # array of objects that still remain after the removal of the older
194
+ # objects and files (that exceeded the @keep range). And finally these
195
+ # remaining objects will be converted to YAML format and are written back
196
+ # to the YAML file.
197
+ # Each remaining storage object's attributes will be updated using the
198
+ # defaults and configuration block defined for the current backup job
199
+ # in case the storage location is changed or credentials are updated.
50
200
  def cycle!
201
+ return unless keep.to_i > 0
51
202
  type = self.class.name.split("::").last
52
- storage_object = Backup::Storage::Object.new(type)
53
- objects = [self] + storage_object.load
54
- if keep.is_a?(Integer) and keep > 0 and objects.count > keep
203
+ storage_object = Backup::Storage::Object.new(type, storage_id)
204
+ objects = storage_object.load
205
+ objects.each {|object| object.send(:update!, @configure_block) }
206
+ objects.unshift(self)
207
+ if objects.count > keep
55
208
  objects_to_remove = objects[keep..-1]
56
209
  objects_to_remove.each do |object|
57
- Logger.message "#{ self.class } started removing (cycling) \"#{ object.remote_file }\"."
58
- object.send(:remove!)
210
+ Logger.message "#{storage_name} started removing (cycling) " +
211
+ "'#{ object.filename }'."
212
+ begin
213
+ object.send(:remove!)
214
+ rescue => err
215
+ Logger.warn Errors::Storage::CycleError.wrap(err,
216
+ "#{storage_name} failed to remove '#{object.filename}'")
217
+ end
59
218
  end
60
219
  objects = objects - objects_to_remove
61
220
  end
221
+ objects.each {|object| object.send(:clean!) }
62
222
  storage_object.write(objects)
63
223
  end
64
224
 
@@ -13,27 +13,17 @@ module Backup
13
13
  attr_accessor :username, :api_key, :auth_url
14
14
 
15
15
  ##
16
- # Rackspace Cloud Files container name and path
17
- attr_accessor :container, :path
16
+ # Rackspace Service Net (Allows for LAN-based transfers to avoid charges and improve performance)
17
+ attr_accessor :servicenet
18
18
 
19
19
  ##
20
- # Creates a new instance of the Rackspace Cloud Files storage object
21
- # First it sets the defaults (if any exist) and then evaluates
22
- # the configuration block which may overwrite these defaults
23
- def initialize(&block)
24
- load_defaults!
25
-
26
- @path ||= 'backups'
27
-
28
- instance_eval(&block) if block_given?
29
-
30
- @time = TIME
31
- end
20
+ # Rackspace Cloud Files container name and path
21
+ attr_accessor :container, :path
32
22
 
33
23
  ##
34
24
  # This is the remote path to where the backup files will be stored
35
25
  def remote_path
36
- File.join(path, TRIGGER)
26
+ File.join(path, TRIGGER, @time)
37
27
  end
38
28
 
39
29
  ##
@@ -45,12 +35,29 @@ module Backup
45
35
  ##
46
36
  # Performs the backup transfer
47
37
  def perform!
38
+ super
48
39
  transfer!
49
40
  cycle!
50
41
  end
51
42
 
52
43
  private
53
44
 
45
+ ##
46
+ # Set configuration defaults before evaluating configuration block,
47
+ # after setting defaults from Storage::Base
48
+ def pre_configure
49
+ super
50
+ @servicenet ||= false
51
+ @path ||= 'backups'
52
+ end
53
+
54
+ ##
55
+ # Adjust configuration after evaluating configuration block,
56
+ # after adjustments from Storage::Base
57
+ def post_configure
58
+ super
59
+ end
60
+
54
61
  ##
55
62
  # Establishes a connection to Rackspace Cloud Files and returns the Fog object.
56
63
  # Not doing any instance variable caching because this object gets persisted in YAML
@@ -58,37 +65,36 @@ module Backup
58
65
  # gets invoked once per object for a #transfer! and once for a remove! Backups run in the
59
66
  # background anyway so even if it were a bit slower it shouldn't matter.
60
67
  def connection
61
- Fog::Storage.new(
62
- :provider => provider,
63
- :rackspace_username => username,
64
- :rackspace_api_key => api_key,
65
- :rackspace_auth_url => auth_url
68
+ @connection ||= Fog::Storage.new(
69
+ :provider => provider,
70
+ :rackspace_username => username,
71
+ :rackspace_api_key => api_key,
72
+ :rackspace_auth_url => auth_url,
73
+ :rackspace_servicenet => servicenet
66
74
  )
67
75
  end
68
76
 
69
77
  ##
70
78
  # Transfers the archived file to the specified Cloud Files container
71
79
  def transfer!
72
- begin
73
- Logger.message("#{ self.class } started transferring \"#{ remote_file }\".")
80
+ files_to_transfer do |local_file, remote_file|
81
+ Logger.message "#{storage_name} started transferring '#{ local_file }'."
74
82
  connection.put_object(
75
83
  container,
76
84
  File.join(remote_path, remote_file),
77
85
  File.open(File.join(local_path, local_file))
78
86
  )
79
- rescue Excon::Errors::SocketError => e
80
- puts "\nAn error occurred while trying to transfer the backup."
81
- puts "Make sure the container exists and try again.\n\n"
82
- exit
83
87
  end
84
88
  end
85
89
 
86
90
  ##
87
91
  # Removes the transferred archive file from the Cloud Files container
88
92
  def remove!
89
- begin
93
+ transferred_files do |local_file, remote_file|
94
+ Logger.message "#{storage_name} started removing '#{ local_file }'" +
95
+ "from container '#{ container }'"
90
96
  connection.delete_object(container, File.join(remote_path, remote_file))
91
- rescue Excon::Errors::SocketError; end
97
+ end
92
98
  end
93
99
 
94
100
  end
@@ -24,36 +24,38 @@ module Backup
24
24
  # Dropbox connection timeout
25
25
  attr_accessor :timeout
26
26
 
27
- ##
28
- # Creates a new instance of the Dropbox storage object
29
- # First it sets the defaults (if any exist) and then evaluates
30
- # the configuration block which may overwrite these defaults
31
- def initialize(&block)
32
- load_defaults!(:except => ['password', 'email'])
33
-
34
- @path ||= 'backups'
35
-
36
- instance_eval(&block) if block_given?
37
-
38
- @timeout ||= 300
39
- @time = TIME
40
- end
41
-
42
27
  ##
43
28
  # This is the remote path to where the backup files will be stored
44
29
  def remote_path
45
- File.join(path, TRIGGER)
30
+ File.join(path, TRIGGER, @time)
46
31
  end
47
32
 
48
33
  ##
49
34
  # Performs the backup transfer
50
35
  def perform!
36
+ super
51
37
  transfer!
52
38
  cycle!
53
39
  end
54
40
 
55
41
  private
56
42
 
43
+ ##
44
+ # Set configuration defaults before evaluating configuration block,
45
+ # after setting defaults from Storage::Base
46
+ def pre_configure
47
+ super
48
+ @path ||= 'backups'
49
+ end
50
+
51
+ ##
52
+ # Adjust configuration after evaluating configuration block,
53
+ # after adjustments from Storage::Base
54
+ def post_configure
55
+ super
56
+ @timeout ||= 300
57
+ end
58
+
57
59
  ##
58
60
  # The initial connection to Dropbox will provide the user with an authorization url.
59
61
  # The user must open this URL and confirm that the authorization successfully took place.
@@ -62,15 +64,20 @@ module Backup
62
64
  # in Backup::CACHE_PATH. The cached file will be used from that point on to re-establish a connection with
63
65
  # Dropbox at a later time. This allows the user to avoid having to go to a new Dropbox URL to authorize over and over again.
64
66
  def connection
67
+ return @connection if @connection
68
+
65
69
  if cache_exists?
66
70
  begin
67
71
  cached_session = ::Dropbox::Session.deserialize(File.read(cached_file))
68
72
  if cached_session.authorized?
69
73
  Logger.message "Session data loaded from cache!"
70
- return cached_session
74
+ return @connection = cached_session
71
75
  end
72
- rescue ArgumentError => error
73
- Logger.warn "Could not read session data from cache. Cache data might be corrupt."
76
+ rescue ArgumentError => err
77
+ Logger.warn Errors::Storage::Dropbox::ConnectionError.wrap(err, <<-EOS)
78
+ Could not read session data from cache.
79
+ Cache data might be corrupt.
80
+ EOS
74
81
  end
75
82
  end
76
83
 
@@ -81,54 +88,37 @@ module Backup
81
88
  ##
82
89
  # Transfers the archived file to the specified Dropbox folder
83
90
  def transfer!
84
- Logger.message("#{ self.class } started transferring \"#{ remote_file }\".")
85
- connection.upload(File.join(local_path, local_file), remote_path, :timeout => timeout)
91
+ files_to_transfer do |local_file, remote_file|
92
+ Logger.message "#{storage_name} started transferring '#{ local_file }'."
93
+ connection.upload(
94
+ File.join(local_path, local_file),
95
+ remote_path,
96
+ :as => remote_file,
97
+ :timeout => timeout
98
+ )
99
+ end
100
+
101
+ remove_instance_variable(:@connection) if instance_variable_defined?(:@connection)
86
102
  end
87
103
 
88
104
  ##
89
105
  # Removes the transferred archive file from the Dropbox folder
90
106
  def remove!
91
- begin
92
- connection.delete(File.join(remote_path, remote_file))
93
- rescue ::Dropbox::FileNotFoundError
94
- Logger.warn "File \"#{ File.join(remote_path, remote_file) }\" does not exist, skipping removal."
95
- end
96
- end
97
-
98
- ##
99
- # Create a new session, write a serialized version of it to the
100
- # .cache directory, and return the session object
101
- def create_write_and_return_new_session!
102
- session = ::Dropbox::Session.new(api_key, api_secret)
103
- session.mode = :dropbox
104
- Logger.message "Open the following URL in a browser to authorize a session for your Dropbox account:"
105
- Logger.message ""
106
- Logger.message "\s\s#{session.authorize_url}"
107
- Logger.message ""
108
- Logger.message "Once Dropbox says you're authorized, hit enter to proceed."
109
- Timeout::timeout(180) { STDIN.gets }
110
- begin
111
- session.authorize
112
- rescue OAuth::Unauthorized => error
113
- Logger.error "Authorization failed!"
114
- raise error
107
+ messages = []
108
+ transferred_files do |local_file, remote_file|
109
+ messages << "#{storage_name} started removing '#{ local_file }' from Dropbox."
115
110
  end
116
- Logger.message "Authorized!"
117
-
118
- Logger.message "Caching session data to file: #{cached_file}.."
119
- write_cache!(session)
120
- Logger.message "Cache data written! You will no longer need to manually authorize this Dropbox account via an URL on this machine."
121
- Logger.message "Note: If you run Backup with this Dropbox account on other machines, you will need to either authorize them the same way,"
122
- Logger.message "\s\sor simply copy over #{cached_file} to the cache directory"
123
- Logger.message "\s\son your other machines to use this Dropbox account there as well."
111
+ Logger.message messages.join("\n")
124
112
 
125
- session
113
+ connection.delete(remote_path)
114
+ ensure
115
+ remove_instance_variable(:@connection) if instance_variable_defined?(:@connection)
126
116
  end
127
117
 
128
118
  ##
129
119
  # Returns the path to the cached file
130
120
  def cached_file
131
- File.join(Backup::CACHE_PATH, "#{api_key + api_secret}")
121
+ File.join(Backup::CACHE_PATH, api_key + api_secret)
132
122
  end
133
123
 
134
124
  ##
@@ -145,22 +135,29 @@ module Backup
145
135
  end
146
136
  end
147
137
 
148
- public # DEPRECATED METHODS #############################################
138
+ ##
139
+ # Create a new session, write a serialized version of it to the
140
+ # .cache directory, and return the session object
141
+ def create_write_and_return_new_session!
142
+ session = ::Dropbox::Session.new(api_key, api_secret)
143
+ session.mode = :dropbox
149
144
 
150
- def email
151
- Logger.warn "[DEPRECATED] Backup::Storage::Dropbox.email is deprecated and will be removed at some point."
152
- end
145
+ template = Backup::Template.new({:session => session, :cached_file => cached_file})
146
+ template.render("storage/dropbox/authorization_url.erb")
153
147
 
154
- def email=(value)
155
- Logger.warn "[DEPRECATED] Backup::Storage::Dropbox.email= is deprecated and will be removed at some point."
156
- end
148
+ begin
149
+ Timeout::timeout(180) { STDIN.gets }
150
+ session.authorize
151
+ rescue OAuth::Unauthorized => err
152
+ raise Errors::Storage::Dropbox::ConnectionError.
153
+ wrap(err, 'Authorization Failed!')
154
+ end
157
155
 
158
- def password
159
- Logger.warn "[DEPRECATED] Backup::Storage::Dropbox.password is deprecated and will be removed at some point."
160
- end
156
+ template.render("storage/dropbox/authorized.erb")
157
+ write_cache!(session)
158
+ template.render("storage/dropbox/cache_file_written.erb")
161
159
 
162
- def password=(value)
163
- Logger.warn "[DEPRECATED] Backup::Storage::Dropbox.password= is deprecated and will be removed at some point."
160
+ session
164
161
  end
165
162
 
166
163
  end