backup-gundua 2.3.1.1

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 (52) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/CHANGELOG +77 -0
  4. data/LICENSE +9 -0
  5. data/README.textile +175 -0
  6. data/Rakefile +69 -0
  7. data/VERSION +1 -0
  8. data/backup.gemspec +123 -0
  9. data/bin/backup +124 -0
  10. data/generators/backup/backup_generator.rb +72 -0
  11. data/generators/backup/templates/config/backup.rb +202 -0
  12. data/generators/backup/templates/migrations/create_backup_tables.rb +18 -0
  13. data/generators/backup/templates/tasks/backup.rake +71 -0
  14. data/generators/backup_update/backup_update_generator.rb +50 -0
  15. data/generators/backup_update/templates/migrations/update_backup_tables.rb +27 -0
  16. data/lib/backup/adapters/archive.rb +34 -0
  17. data/lib/backup/adapters/base.rb +113 -0
  18. data/lib/backup/adapters/custom.rb +41 -0
  19. data/lib/backup/adapters/mysql.rb +54 -0
  20. data/lib/backup/adapters/postgresql.rb +56 -0
  21. data/lib/backup/adapters/sqlite.rb +25 -0
  22. data/lib/backup/command_helper.rb +11 -0
  23. data/lib/backup/configuration/adapter.rb +21 -0
  24. data/lib/backup/configuration/adapter_options.rb +8 -0
  25. data/lib/backup/configuration/attributes.rb +19 -0
  26. data/lib/backup/configuration/base.rb +55 -0
  27. data/lib/backup/configuration/helpers.rb +24 -0
  28. data/lib/backup/configuration/mail.rb +20 -0
  29. data/lib/backup/configuration/smtp.rb +8 -0
  30. data/lib/backup/configuration/storage.rb +8 -0
  31. data/lib/backup/connection/s3.rb +85 -0
  32. data/lib/backup/environment/base.rb +12 -0
  33. data/lib/backup/environment/rails.rb +17 -0
  34. data/lib/backup/environment/unix.rb +94 -0
  35. data/lib/backup/mail/base.rb +93 -0
  36. data/lib/backup/mail/mail.txt +7 -0
  37. data/lib/backup/record/base.rb +65 -0
  38. data/lib/backup/record/ftp.rb +37 -0
  39. data/lib/backup/record/local.rb +26 -0
  40. data/lib/backup/record/s3.rb +24 -0
  41. data/lib/backup/record/scp.rb +31 -0
  42. data/lib/backup/record/sftp.rb +36 -0
  43. data/lib/backup/storage/ftp.rb +36 -0
  44. data/lib/backup/storage/local.rb +24 -0
  45. data/lib/backup/storage/s3.rb +14 -0
  46. data/lib/backup/storage/scp.rb +28 -0
  47. data/lib/backup/storage/sftp.rb +29 -0
  48. data/lib/backup.rb +118 -0
  49. data/setup/backup.rb +202 -0
  50. data/setup/backup.sqlite3 +0 -0
  51. data/spec/configuration/attributes_spec.rb +35 -0
  52. metadata +185 -0
@@ -0,0 +1,25 @@
1
+ module Backup
2
+ module Adapters
3
+ class SQLite < Base
4
+
5
+ attr_accessor :database
6
+
7
+ private
8
+
9
+ # Compress the sqlite file
10
+ def perform
11
+ log system_messages[:sqlite]
12
+ run "gzip -c --best #{database} > #{File.join(tmp_path, compressed_file)}"
13
+ end
14
+
15
+ def load_settings
16
+ self.database = procedure.get_adapter_configuration.attributes['database']
17
+ end
18
+
19
+ def performed_file_extension
20
+ ""
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Backup
2
+ module CommandHelper
3
+ def run(command)
4
+ Kernel.system command
5
+ end
6
+
7
+ def log(command)
8
+ puts "Backup => #{command}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module Backup
2
+ module Configuration
3
+ class Adapter
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(files exclude user password database skip_tables commands additional_options)
6
+
7
+ def initialize
8
+ @options = Backup::Configuration::AdapterOptions.new
9
+ end
10
+
11
+ def options(&block)
12
+ @options.instance_eval &block
13
+ end
14
+
15
+ def get_options
16
+ @options
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ module Backup
2
+ module Configuration
3
+ class AdapterOptions
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(host port socket)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module Backup
2
+ module Configuration
3
+ module Attributes
4
+
5
+ def generate_attributes(*attrs)
6
+ define_method :attributes do
7
+ @attributes ||= {}
8
+ end
9
+
10
+ attrs.flatten.each do |att|
11
+ define_method att do |value|
12
+ self.attributes[att.to_s] = value
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,55 @@
1
+ module Backup
2
+ module Configuration
3
+ class Base
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(encrypt_with_password keep_backups notify)
6
+
7
+ attr_accessor :trigger, :storage_name, :adapter_name
8
+
9
+ def initialize(trigger)
10
+ @trigger = trigger
11
+ @adapter_configuration = Backup::Configuration::Adapter.new
12
+ @storage_configuration = Backup::Configuration::Storage.new
13
+ end
14
+
15
+ def adapter(adapter, &block)
16
+ @adapter_name = adapter
17
+ @adapter_configuration.instance_eval &block
18
+ end
19
+
20
+ def storage(storage, &block)
21
+ @storage_name = storage
22
+ @storage_configuration.instance_eval &block
23
+ end
24
+
25
+ # Initializes the storing process depending on the store settings
26
+ def initialize_storage(adapter)
27
+ case @storage_name.to_sym
28
+ when :s3 then Backup::Storage::S3.new(adapter)
29
+ when :scp then Backup::Storage::SCP.new(adapter)
30
+ when :ftp then Backup::Storage::FTP.new(adapter)
31
+ when :sftp then Backup::Storage::SFTP.new(adapter)
32
+ when :local then Backup::Storage::Local.new(adapter)
33
+ end
34
+ end
35
+
36
+ def initialize_record
37
+ case @storage_name.to_sym
38
+ when :s3 then Backup::Record::S3.new
39
+ when :scp then Backup::Record::SCP.new
40
+ when :ftp then Backup::Record::FTP.new
41
+ when :sftp then Backup::Record::SFTP.new
42
+ when :local then Backup::Record::Local.new
43
+ end
44
+ end
45
+
46
+ def get_adapter_configuration
47
+ @adapter_configuration
48
+ end
49
+
50
+ def get_storage_configuration
51
+ @storage_configuration
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,24 @@
1
+ module Backup
2
+ module Configuration
3
+ module Helpers
4
+
5
+ # A helper method for the config/backup.rb configuration file
6
+ # Expects a trigger in argument one (STRING)
7
+ # Expects a block of settings
8
+ def backup(trigger, &block)
9
+ backup = Backup::Configuration::Base.new(trigger)
10
+ backup.instance_eval &block
11
+ @backup_procedures ||= Array.new
12
+ @backup_procedures << backup
13
+ end
14
+
15
+ # A helper method for the config/mail.rb configuration file
16
+ # Takes a block containing the mail options
17
+ def notifier_settings(&block)
18
+ @mail_configuration = Backup::Configuration::Mail.new
19
+ @mail_configuration.instance_eval &block
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Backup
2
+ module Configuration
3
+ class Mail
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(from to smtp)
6
+
7
+ def initialize
8
+ @smtp_configuration = Backup::Configuration::SMTP.new
9
+ end
10
+
11
+ def smtp(&block)
12
+ @smtp_configuration.instance_eval &block
13
+ end
14
+
15
+ def get_smtp_configuration
16
+ @smtp_configuration
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ module Backup
2
+ module Configuration
3
+ class SMTP
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(host port username password authentication domain tls)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Backup
2
+ module Configuration
3
+ class Storage
4
+ extend Backup::Configuration::Attributes
5
+ generate_attributes %w(ip user password path access_key_id secret_access_key use_ssl bucket)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,85 @@
1
+ module Backup
2
+ module Connection
3
+ class S3
4
+
5
+ attr_accessor :adapter, :procedure, :access_key_id, :secret_access_key, :s3_bucket, :use_ssl, :final_file, :tmp_path
6
+
7
+ # Initializes the S3 connection, setting the values using the S3 adapter
8
+ def initialize(adapter = false)
9
+ if adapter
10
+ self.adapter = adapter
11
+ self.procedure = adapter.procedure
12
+ self.final_file = adapter.final_file
13
+ self.tmp_path = adapter.tmp_path.gsub('\ ', ' ')
14
+ load_storage_configuration_attributes
15
+ end
16
+ end
17
+
18
+ # Sets values from a procedure, rather than from the adapter object
19
+ def static_initialize(procedure)
20
+ self.procedure = procedure
21
+ load_storage_configuration_attributes(true)
22
+ end
23
+
24
+ # Establishes a connection with Amazon S3 using the credentials provided by the user
25
+ def connect
26
+ AWS::S3::Base.establish_connection!(
27
+ :access_key_id => access_key_id,
28
+ :secret_access_key => secret_access_key,
29
+ :use_ssl => use_ssl
30
+ )
31
+ end
32
+
33
+ # Wrapper for the Service object
34
+ def service
35
+ AWS::S3::Service
36
+ end
37
+
38
+ # Wrapper for the Bucket object
39
+ def bucket
40
+ AWS::S3::Bucket
41
+ end
42
+
43
+ # Wrapper for the Object object
44
+ def object
45
+ AWS::S3::S3Object
46
+ end
47
+
48
+ # Initializes the file transfer to Amazon S3
49
+ # This can only run after a connection has been made using the #connect method
50
+ def store
51
+ puts "Storing \"#{final_file}\" to bucket \"#{s3_bucket}\" on Amazon S3."
52
+ object.store(
53
+ final_file,
54
+ open(File.join(tmp_path, final_file)),
55
+ s3_bucket )
56
+ end
57
+
58
+ # Destroys file from a bucket on Amazon S3
59
+ def destroy(file, bucket)
60
+ object.delete(
61
+ file,
62
+ bucket )
63
+ end
64
+
65
+ private
66
+
67
+ def load_storage_configuration_attributes(static = false)
68
+ %w(access_key_id secret_access_key use_ssl).each do |attribute|
69
+ if static
70
+ send("#{attribute}=", procedure.get_storage_configuration.attributes[attribute])
71
+ else
72
+ send("#{attribute}=", adapter.procedure.get_storage_configuration.attributes[attribute])
73
+ end
74
+ end
75
+
76
+ if static
77
+ self.s3_bucket = procedure.get_storage_configuration.attributes['bucket']
78
+ else
79
+ self.s3_bucket = adapter.procedure.get_storage_configuration.attributes['bucket']
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,12 @@
1
+ module Backup
2
+ module Environment
3
+ module Base
4
+
5
+ def current_environment
6
+ return :rails if defined?(RAILS_ENV)
7
+ return :unix
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module Backup
2
+ module Environment
3
+ module Rails
4
+
5
+ if defined? RAILS_ENV
6
+
7
+ # Sets BACKUP_PATH equal to RAILS_ROOT
8
+ BACKUP_PATH = RAILS_ROOT
9
+
10
+ # Sets DB_CONNECTION_SETTINGS to false
11
+ DB_CONNECTION_SETTINGS = false
12
+
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,94 @@
1
+ module Backup
2
+ module Environment
3
+ module Unix
4
+
5
+ require 'active_record'
6
+ require 'optparse'
7
+
8
+ # Sets BACKUP_PATH equal to /opt/backup
9
+ BACKUP_PATH = "/opt/backup"
10
+
11
+ # Sets DB_CONNECTION_SETTINGS
12
+ DB_CONNECTION_SETTINGS = {
13
+ :adapter => "sqlite3",
14
+ :database => "/opt/backup/backup.sqlite3",
15
+ :pool => 5,
16
+ :timeout => 5000
17
+ }
18
+
19
+ module Commands
20
+
21
+ def setup
22
+ unless File.directory?(BACKUP_PATH)
23
+ puts "Installing Backup in #{BACKUP_PATH}.."
24
+ %x{ sudo mkdir -p #{File.join(BACKUP_PATH, 'config')} }
25
+ %x{ sudo cp #{File.join(File.dirname(__FILE__), '..', '..', '..', 'setup', 'backup.sqlite3')} #{BACKUP_PATH} }
26
+ %x{ sudo cp #{File.join(File.dirname(__FILE__), '..', '..', '..', 'setup', 'backup.rb')} #{File.join(BACKUP_PATH, 'config')} }
27
+ puts <<-MESSAGE
28
+
29
+ ==============================================================
30
+ Backup has been set up!
31
+ ==============================================================
32
+
33
+ 1: Set up some "Backup Settings" inside the configuration file!
34
+
35
+ /opt/backup/config/backup.rb
36
+
37
+
38
+ 2: Run the backups!
39
+
40
+ sudo backup --run [trigger]
41
+
42
+
43
+ For a list of Backup commands:
44
+
45
+ sudo backup --help
46
+
47
+
48
+ For More Information:
49
+
50
+ http://github.com/meskyanichi/backup
51
+
52
+ ==============================================================
53
+
54
+ MESSAGE
55
+ else
56
+ puts "\nBackup is already installed in #{BACKUP_PATH}..\n"
57
+ puts "If you want to reset it, run:\n\nbackup --reset\n\n"
58
+ puts "This will reinstall it."
59
+ puts "Warning: All configuration will be lost!\n\n"
60
+ end
61
+ end
62
+
63
+ def reset
64
+ if File.directory?(BACKUP_PATH)
65
+ remove
66
+ setup
67
+ else
68
+ puts "Backup is not installed.\n"
69
+ puts "Run the following command to install it:\n\nbackup --setup"
70
+ end
71
+ end
72
+
73
+ def remove
74
+ puts "Removing Backup..\n"
75
+ %x{ sudo rm -rf #{BACKUP_PATH} }
76
+ end
77
+ end
78
+
79
+ module Helpers
80
+
81
+ def confirm_configuration_file_existence
82
+ unless File.exist?(File.join(BACKUP_PATH, 'config', 'backup.rb'))
83
+ puts "\nBackup could not find the Backup Configuration File."
84
+ puts "Did you set up Backup? Do so if you haven't yet:"
85
+ puts "\nbackup --setup\n "
86
+ exit
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,93 @@
1
+ module Backup
2
+ module Mail
3
+ class Base
4
+
5
+ # Sets up the Mail Configuration for the Backup::Mail::Base class.
6
+ # This must be set in order to send emails
7
+ # It will dynamically add class methods (configuration) for each email that will be sent
8
+ def self.setup(config)
9
+ if config
10
+ (class << self; self; end).instance_eval do
11
+ config.attributes.each do |method, value|
12
+ define_method method do
13
+ value
14
+ end
15
+ end
16
+ config.get_smtp_configuration.attributes.each do |method, value|
17
+ define_method method do
18
+ value
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # Returns true if the "to" and "from" attributes are set
26
+ def self.setup?
27
+ return true if defined?(from) and defined?(to)
28
+ false
29
+ end
30
+
31
+ # Delivers the backup details by email to the recipient
32
+ # Requires the Backup Object
33
+ def self.notify!(backup)
34
+ if self.setup? and backup.procedure.attributes['notify'].eql?(true)
35
+ @backup = backup
36
+ self.parse_body
37
+ Pony.mail({
38
+ :subject => "Backup for \"#{@backup.trigger}\" was successfully created!",
39
+ :body => @content
40
+ }.merge(self.smtp_configuration))
41
+ puts "Sending notification to #{self.to}."
42
+ end
43
+ end
44
+
45
+ # Retrieves SMTP configuration
46
+ def self.smtp_configuration
47
+ options = {
48
+ :to => self.to,
49
+ :from => self.from,
50
+ :via => :smtp,
51
+ :smtp => {
52
+ :host => self.host,
53
+ :port => self.port,
54
+ :domain => self.domain,
55
+ :tls => self.tls
56
+ }}
57
+ options[:smtp].merge(:auth => self.authentication, :user => self.username, :password => self.password) if self.responds_to? :authentication
58
+
59
+ end
60
+
61
+ def self.parse_body
62
+ File.open(File.join(File.dirname(__FILE__), 'mail.txt'), 'r') do |file|
63
+ self.gsub_content(file.readlines)
64
+ end
65
+ end
66
+
67
+ def self.gsub_content(lines)
68
+ bucket = @backup.procedure.get_storage_configuration.attributes['bucket']
69
+ path = @backup.procedure.get_storage_configuration.attributes['path']
70
+ ip = @backup.procedure.get_storage_configuration.attributes['ip']
71
+
72
+ lines.each do |line|
73
+ line.gsub!(':trigger', @backup.trigger)
74
+ line.gsub!(':day', Time.now.strftime("%A (%d)"))
75
+ line.gsub!(':month', Time.now.strftime("%B"))
76
+ line.gsub!(':year', Time.now.strftime("%Y"))
77
+ line.gsub!(':time', Time.now.strftime("%r"))
78
+ line.gsub!(':adapter', @backup.procedure.adapter_name.to_s)
79
+ line.gsub!(':location', bucket || path)
80
+ line.gsub!(':backup', @backup.final_file)
81
+ case @backup.procedure.storage_name.to_sym
82
+ when :s3 then line.gsub!(':remote', "on Amazon S3")
83
+ when :local then line.gsub!(':remote', "on the local server")
84
+ when :scp, :sftp, :ftp then line.gsub!(':remote', "on the remote server (#{ip})")
85
+ end
86
+ @content ||= String.new
87
+ @content << line
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,7 @@
1
+ A backup was successfully created for the trigger: ":trigger"!
2
+
3
+ The backup was created on ":day of :month :year at :time", using the ":adapter" adapter.
4
+
5
+ The Backup was stored in ":location" :remote.
6
+
7
+ The following backup was stored: ":backup"
@@ -0,0 +1,65 @@
1
+ module Backup
2
+ module Record
3
+ class Base < ActiveRecord::Base
4
+
5
+ if DB_CONNECTION_SETTINGS
6
+ establish_connection(DB_CONNECTION_SETTINGS)
7
+ end
8
+
9
+ set_table_name 'backup'
10
+
11
+ default_scope :order => 'created_at desc'
12
+
13
+ # Callbacks
14
+ after_save :clean_backups
15
+
16
+ # Attributes
17
+ attr_accessor :adapter_config, :keep_backups
18
+
19
+ # Receives the options hash and stores it
20
+ def load_adapter(adapter)
21
+ self.adapter_config = adapter
22
+ self.trigger = adapter.procedure.trigger
23
+ self.adapter = adapter.procedure.adapter_name.to_s
24
+ self.filename = adapter.final_file
25
+ self.keep_backups = adapter.procedure.attributes['keep_backups']
26
+
27
+ # TODO calculate md5sum of file
28
+ load_specific_settings(adapter) if respond_to?(:load_specific_settings)
29
+ end
30
+
31
+ # Destroys all backups for the specified trigger from Remote Server (FTP)
32
+ def self.destroy_all_backups(procedure, trigger)
33
+ backups = self.all(:conditions => {:trigger => trigger})
34
+ unless backups.empty?
35
+ # Derived classes must implement this method!
36
+ self.destroy_backups(procedure, backups)
37
+
38
+ puts "\nAll \"#{procedure.trigger}\" backups destroyed.\n\n"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Maintains the backup file amount on the remote server
45
+ # This is invoked after a successful record save
46
+ # This deletes the oldest files when the backup limit has been exceeded
47
+ def clean_backups
48
+ if keep_backups.is_a?(Integer)
49
+ backups = self.class.all(:conditions => {:trigger => trigger})
50
+ backups_to_destroy = backups[keep_backups, backups.size] || []
51
+
52
+ unless backups_to_destroy.empty?
53
+ # Derived classes must implement this method!
54
+ self.class.destroy_backups(adapter_config.procedure, backups_to_destroy)
55
+
56
+ puts "\nBackup storage for \"#{trigger}\" is limited to #{keep_backups} backups."
57
+ puts "\nThe #{keep_backups} most recent backups are now stored on the remote server.\n\n"
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,37 @@
1
+ module Backup
2
+ module Record
3
+ class FTP < Backup::Record::Base
4
+
5
+ attr_accessor :ip, :user, :password
6
+
7
+ def load_specific_settings(adapter)
8
+ %w(ip user password path).each do |method|
9
+ send(:"#{method}=", adapter.procedure.get_storage_configuration.attributes[method])
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def self.destroy_backups(procedure, backups)
16
+ ip = procedure.get_storage_configuration.attributes['ip']
17
+ user = procedure.get_storage_configuration.attributes['user']
18
+ password = procedure.get_storage_configuration.attributes['password']
19
+
20
+ Net::FTP.open(ip, user, password) do |ftp|
21
+ backups.each do |backup|
22
+ puts "\nDestroying backup \"#{backup.filename}\" from path \"#{backup.path}\"."
23
+ begin
24
+ ftp.chdir(backup.path)
25
+ ftp.delete(backup.filename)
26
+ backup.destroy
27
+ rescue
28
+ puts "Could not find backup #{backup.path}/#{backup.filename}."
29
+ backup.destroy
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ module Backup
2
+ module Record
3
+ class Local < Backup::Record::Base
4
+
5
+ def load_specific_settings(adapter)
6
+ self.path = adapter.procedure.get_storage_configuration.attributes['path']
7
+ end
8
+
9
+ private
10
+
11
+ class << self
12
+ include Backup::CommandHelper
13
+
14
+ def destroy_backups(procedure, backups)
15
+ backups.each do |backup|
16
+ puts "\nDestroying backup \"#{backup.filename}\" from path \"#{backup.path}\"."
17
+ run "rm #{File.join(backup.path, backup.filename)}"
18
+ backup.destroy
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,24 @@
1
+ module Backup
2
+ module Record
3
+ class S3 < Backup::Record::Base
4
+
5
+ def load_specific_settings(adapter)
6
+ self.bucket = adapter.procedure.get_storage_configuration.attributes['bucket']
7
+ end
8
+
9
+ private
10
+
11
+ def self.destroy_backups(procedure, backups)
12
+ s3 = Backup::Connection::S3.new
13
+ s3.static_initialize(procedure)
14
+ s3.connect
15
+ backups.each do |backup|
16
+ puts "\nDestroying backup \"#{backup.filename}\" from bucket \"#{backup.bucket}\"."
17
+ s3.destroy(backup.filename, backup.bucket)
18
+ backup.destroy
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end