backup 2.4.5.1 → 3.0.0.build.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. data/.gitignore +2 -0
  2. data/.infinity_test +7 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +88 -0
  6. data/LICENSE.md +24 -0
  7. data/README.md +189 -75
  8. data/backup.gemspec +41 -0
  9. data/bin/backup +161 -90
  10. data/lib/backup.rb +133 -117
  11. data/lib/backup/archive.rb +54 -0
  12. data/lib/backup/cli.rb +50 -0
  13. data/lib/backup/compressor/base.rb +17 -0
  14. data/lib/backup/compressor/gzip.rb +61 -0
  15. data/lib/backup/configuration/base.rb +7 -67
  16. data/lib/backup/configuration/compressor/base.rb +10 -0
  17. data/lib/backup/configuration/compressor/gzip.rb +23 -0
  18. data/lib/backup/configuration/database/base.rb +18 -0
  19. data/lib/backup/configuration/database/mongodb.rb +37 -0
  20. data/lib/backup/configuration/database/mysql.rb +37 -0
  21. data/lib/backup/configuration/database/postgresql.rb +37 -0
  22. data/lib/backup/configuration/database/redis.rb +35 -0
  23. data/lib/backup/configuration/encryptor/base.rb +10 -0
  24. data/lib/backup/configuration/encryptor/gpg.rb +17 -0
  25. data/lib/backup/configuration/encryptor/open_ssl.rb +26 -0
  26. data/lib/backup/configuration/helpers.rb +47 -17
  27. data/lib/backup/configuration/notifier/base.rb +39 -0
  28. data/lib/backup/configuration/notifier/mail.rb +52 -0
  29. data/lib/backup/configuration/storage/base.rb +18 -0
  30. data/lib/backup/configuration/storage/cloudfiles.rb +21 -0
  31. data/lib/backup/configuration/storage/dropbox.rb +25 -0
  32. data/lib/backup/configuration/storage/ftp.rb +25 -0
  33. data/lib/backup/configuration/storage/rsync.rb +25 -0
  34. data/lib/backup/configuration/storage/s3.rb +25 -0
  35. data/lib/backup/configuration/storage/scp.rb +25 -0
  36. data/lib/backup/configuration/storage/sftp.rb +25 -0
  37. data/lib/backup/database/base.rb +33 -0
  38. data/lib/backup/database/mongodb.rb +137 -0
  39. data/lib/backup/database/mysql.rb +104 -0
  40. data/lib/backup/database/postgresql.rb +111 -0
  41. data/lib/backup/database/redis.rb +105 -0
  42. data/lib/backup/encryptor/base.rb +17 -0
  43. data/lib/backup/encryptor/gpg.rb +78 -0
  44. data/lib/backup/encryptor/open_ssl.rb +67 -0
  45. data/lib/backup/finder.rb +39 -0
  46. data/lib/backup/logger.rb +80 -0
  47. data/lib/backup/model.rb +249 -0
  48. data/lib/backup/notifier/base.rb +29 -0
  49. data/lib/backup/notifier/binder.rb +32 -0
  50. data/lib/backup/notifier/mail.rb +141 -0
  51. data/lib/backup/notifier/templates/notify_failure.erb +31 -0
  52. data/lib/backup/notifier/templates/notify_success.erb +16 -0
  53. data/lib/backup/storage/base.rb +60 -3
  54. data/lib/backup/storage/cloudfiles.rb +85 -6
  55. data/lib/backup/storage/dropbox.rb +74 -4
  56. data/lib/backup/storage/ftp.rb +103 -27
  57. data/lib/backup/storage/object.rb +45 -0
  58. data/lib/backup/storage/rsync.rb +100 -0
  59. data/lib/backup/storage/s3.rb +100 -7
  60. data/lib/backup/storage/scp.rb +94 -19
  61. data/lib/backup/storage/sftp.rb +94 -19
  62. data/lib/backup/version.rb +70 -1
  63. data/lib/templates/archive +4 -0
  64. data/lib/templates/compressor/gzip +4 -0
  65. data/lib/templates/database/mongodb +10 -0
  66. data/lib/templates/database/mysql +11 -0
  67. data/lib/templates/database/postgresql +11 -0
  68. data/lib/templates/database/redis +10 -0
  69. data/lib/templates/encryptor/gpg +9 -0
  70. data/lib/templates/encryptor/openssl +5 -0
  71. data/lib/templates/notifier/mail +14 -0
  72. data/lib/templates/readme +15 -0
  73. data/lib/templates/storage/cloudfiles +7 -0
  74. data/lib/templates/storage/dropbox +8 -0
  75. data/lib/templates/storage/ftp +8 -0
  76. data/lib/templates/storage/rsync +7 -0
  77. data/lib/templates/storage/s3 +8 -0
  78. data/lib/templates/storage/scp +8 -0
  79. data/lib/templates/storage/sftp +8 -0
  80. data/spec/archive_spec.rb +53 -0
  81. data/spec/backup_spec.rb +11 -0
  82. data/spec/compressor/gzip_spec.rb +59 -0
  83. data/spec/configuration/base_spec.rb +35 -0
  84. data/spec/configuration/compressor/gzip_spec.rb +28 -0
  85. data/spec/configuration/database/base_spec.rb +16 -0
  86. data/spec/configuration/database/mongodb_spec.rb +30 -0
  87. data/spec/configuration/database/mysql_spec.rb +32 -0
  88. data/spec/configuration/database/postgresql_spec.rb +32 -0
  89. data/spec/configuration/database/redis_spec.rb +30 -0
  90. data/spec/configuration/encryptor/gpg_spec.rb +25 -0
  91. data/spec/configuration/encryptor/open_ssl_spec.rb +31 -0
  92. data/spec/configuration/notifier/mail_spec.rb +32 -0
  93. data/spec/configuration/storage/cloudfiles_spec.rb +34 -0
  94. data/spec/configuration/storage/dropbox_spec.rb +40 -0
  95. data/spec/configuration/storage/ftp_spec.rb +40 -0
  96. data/spec/configuration/storage/rsync_spec.rb +37 -0
  97. data/spec/configuration/storage/s3_spec.rb +37 -0
  98. data/spec/configuration/storage/scp_spec.rb +40 -0
  99. data/spec/configuration/storage/sftp_spec.rb +40 -0
  100. data/spec/database/base_spec.rb +30 -0
  101. data/spec/database/mongodb_spec.rb +144 -0
  102. data/spec/database/mysql_spec.rb +150 -0
  103. data/spec/database/postgresql_spec.rb +164 -0
  104. data/spec/database/redis_spec.rb +122 -0
  105. data/spec/encryptor/gpg_spec.rb +57 -0
  106. data/spec/encryptor/open_ssl_spec.rb +102 -0
  107. data/spec/logger_spec.rb +37 -0
  108. data/spec/model_spec.rb +236 -0
  109. data/spec/notifier/mail_spec.rb +97 -0
  110. data/spec/spec_helper.rb +21 -0
  111. data/spec/storage/base_spec.rb +33 -0
  112. data/spec/storage/cloudfiles_spec.rb +102 -0
  113. data/spec/storage/dropbox_spec.rb +89 -0
  114. data/spec/storage/ftp_spec.rb +133 -0
  115. data/spec/storage/object_spec.rb +74 -0
  116. data/spec/storage/rsync_spec.rb +115 -0
  117. data/spec/storage/s3_spec.rb +110 -0
  118. data/spec/storage/scp_spec.rb +129 -0
  119. data/spec/storage/sftp_spec.rb +125 -0
  120. data/spec/version_spec.rb +32 -0
  121. metadata +139 -123
  122. data/CHANGELOG +0 -131
  123. data/LICENSE +0 -20
  124. data/generators/backup/backup_generator.rb +0 -69
  125. data/generators/backup/templates/backup.rake +0 -56
  126. data/generators/backup/templates/backup.rb +0 -253
  127. data/generators/backup/templates/create_backup_tables.rb +0 -18
  128. data/generators/backup_update/backup_update_generator.rb +0 -50
  129. data/generators/backup_update/templates/migrations/update_backup_tables.rb +0 -27
  130. data/lib/backup/adapters/archive.rb +0 -34
  131. data/lib/backup/adapters/base.rb +0 -167
  132. data/lib/backup/adapters/custom.rb +0 -41
  133. data/lib/backup/adapters/mongo_db.rb +0 -139
  134. data/lib/backup/adapters/mysql.rb +0 -60
  135. data/lib/backup/adapters/postgresql.rb +0 -60
  136. data/lib/backup/adapters/sqlite.rb +0 -25
  137. data/lib/backup/command_helper.rb +0 -14
  138. data/lib/backup/configuration/adapter.rb +0 -21
  139. data/lib/backup/configuration/adapter_options.rb +0 -8
  140. data/lib/backup/configuration/attributes.rb +0 -19
  141. data/lib/backup/configuration/mail.rb +0 -20
  142. data/lib/backup/configuration/smtp.rb +0 -8
  143. data/lib/backup/configuration/storage.rb +0 -8
  144. data/lib/backup/connection/cloudfiles.rb +0 -75
  145. data/lib/backup/connection/dropbox.rb +0 -63
  146. data/lib/backup/connection/s3.rb +0 -88
  147. data/lib/backup/core_ext/object.rb +0 -5
  148. data/lib/backup/environment/base.rb +0 -12
  149. data/lib/backup/environment/rails_configuration.rb +0 -15
  150. data/lib/backup/environment/unix_configuration.rb +0 -109
  151. data/lib/backup/mail/base.rb +0 -97
  152. data/lib/backup/mail/mail.txt +0 -7
  153. data/lib/backup/record/base.rb +0 -65
  154. data/lib/backup/record/cloudfiles.rb +0 -28
  155. data/lib/backup/record/dropbox.rb +0 -27
  156. data/lib/backup/record/ftp.rb +0 -39
  157. data/lib/backup/record/local.rb +0 -26
  158. data/lib/backup/record/s3.rb +0 -25
  159. data/lib/backup/record/scp.rb +0 -33
  160. data/lib/backup/record/sftp.rb +0 -38
  161. data/lib/backup/storage/local.rb +0 -22
  162. data/lib/generators/backup/USAGE +0 -10
  163. data/lib/generators/backup/backup_generator.rb +0 -47
  164. data/lib/generators/backup/templates/backup.rake +0 -56
  165. data/lib/generators/backup/templates/backup.rb +0 -236
  166. data/lib/generators/backup/templates/create_backup_tables.rb +0 -18
  167. data/setup/backup.rb +0 -257
  168. data/setup/backup.sqlite3 +0 -0
@@ -1,15 +0,0 @@
1
- module Backup
2
- module Environment
3
- module RailsConfiguration
4
-
5
- if defined?(Rails.root)
6
- # Sets BACKUP_PATH equal to Rails.root
7
- BACKUP_PATH = Rails.root.to_s
8
-
9
- # Sets DB_CONNECTION_SETTINGS to false
10
- DB_CONNECTION_SETTINGS = false
11
- end
12
-
13
- end
14
- end
15
- end
@@ -1,109 +0,0 @@
1
- module Backup
2
- module Environment
3
- module UnixConfiguration
4
-
5
- require 'active_record'
6
-
7
- # Sets BACKUP_PATH
8
- BACKUP_PATH = ENV['BACKUP_PATH'] || "/opt/backup"
9
-
10
- # Sets DB_CONNECTION_SETTINGS
11
- DB_CONNECTION_SETTINGS = {
12
- :adapter => "sqlite3",
13
- :database => "#{BACKUP_PATH}/backup.sqlite3",
14
- :pool => 5,
15
- :timeout => 5000
16
- }
17
-
18
- module Commands
19
-
20
- def setup
21
- unless File.directory?(BACKUP_PATH)
22
- puts "Installing Backup in #{BACKUP_PATH}.."
23
- %x{ #{sudo} mkdir -p #{File.join(BACKUP_PATH, 'config')} }
24
- %x{ #{sudo} cp #{File.join(File.dirname(__FILE__), '..', '..', '..', 'setup', 'backup.sqlite3')} #{BACKUP_PATH} }
25
- %x{ #{sudo} cp #{File.join(File.dirname(__FILE__), '..', '..', '..', 'setup', 'backup.rb')} #{File.join(BACKUP_PATH, 'config')} }
26
- puts <<-MESSAGE
27
-
28
- ==============================================================
29
- Backup has been set up!
30
- ==============================================================
31
-
32
- 1: Set up some "Backup Settings" inside the configuration file!
33
-
34
- #{BACKUP_PATH}/config/backup.rb
35
-
36
-
37
- 2: Run the backups!
38
-
39
- sudo backup --run [trigger]
40
-
41
-
42
- For a list of Backup commands:
43
-
44
- sudo backup --help
45
-
46
-
47
- For More Information:
48
-
49
- http://github.com/meskyanichi/backup
50
-
51
- ==============================================================
52
-
53
- MESSAGE
54
- else
55
- puts "\nBackup is already installed in #{BACKUP_PATH}..\n"
56
- puts "If you want to reset it, run:\n\nbackup --reset\n\n"
57
- puts "This will reinstall it."
58
- puts "Warning: All configuration will be lost!\n\n"
59
- end
60
- end
61
-
62
- def reset
63
- if File.directory?(BACKUP_PATH)
64
- remove
65
- setup
66
- else
67
- puts "Backup is not installed.\n"
68
- puts "Run the following command to install it:\n\nbackup --setup"
69
- end
70
- end
71
-
72
- def remove
73
- puts "Removing Backup..\n"
74
- %x{ #{sudo} rm -rf #{BACKUP_PATH} }
75
- end
76
- end
77
-
78
- module Helpers
79
-
80
- def confirm_configuration_file_existence
81
- unless File.exist?(File.join(BACKUP_PATH, 'config', 'backup.rb'))
82
- puts "\nBackup could not find the Backup Configuration File."
83
- puts "Did you set up Backup? Do so if you haven't yet:"
84
- puts "\nbackup --setup\n "
85
- exit
86
- end
87
- end
88
-
89
- def sudo
90
- if writable?(BACKUP_PATH)
91
- ""
92
- else
93
- "sudo"
94
- end
95
- end
96
-
97
- private
98
- def writable?(f)
99
- unless File.exists?(f)
100
- writable?(File.dirname(f))
101
- else
102
- File.writable?(f)
103
- end
104
- end
105
- end
106
-
107
- end
108
- end
109
- end
@@ -1,97 +0,0 @@
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
- require 'pony'
36
-
37
- @backup = backup
38
- self.parse_body
39
- Pony.mail({
40
- :subject => "Backup for \"#{@backup.trigger}\" was successfully created!",
41
- :body => @content
42
- }.merge(self.smtp_configuration))
43
- puts "Sending notification to #{self.to}."
44
- end
45
- end
46
-
47
- # Retrieves SMTP configuration
48
- def self.smtp_configuration
49
- { :to => self.to,
50
- :from => self.from,
51
- :via => :smtp,
52
- :smtp => {
53
- :host => self.host,
54
- :port => self.port,
55
- :user => self.username,
56
- :password => self.password,
57
- :auth => self.authentication,
58
- :domain => self.domain,
59
- :tls => self.tls
60
- }}
61
- end
62
-
63
- def self.parse_body
64
- File.open(File.join(File.dirname(__FILE__), 'mail.txt'), 'r') do |file|
65
- self.gsub_content(file.readlines)
66
- end
67
- end
68
-
69
- def self.gsub_content(lines)
70
- container = @backup.procedure.get_storage_configuration.attributes['container']
71
- bucket = @backup.procedure.get_storage_configuration.attributes['bucket']
72
- path = @backup.procedure.get_storage_configuration.attributes['path']
73
- ip = @backup.procedure.get_storage_configuration.attributes['ip']
74
-
75
- lines.each do |line|
76
- line.gsub!(':trigger', @backup.trigger)
77
- line.gsub!(':day', Time.now.strftime("%A (%d)"))
78
- line.gsub!(':month', Time.now.strftime("%B"))
79
- line.gsub!(':year', Time.now.strftime("%Y"))
80
- line.gsub!(':time', Time.now.strftime("%r"))
81
- line.gsub!(':adapter', @backup.procedure.adapter_name.to_s)
82
- line.gsub!(':location', container || bucket || path)
83
- line.gsub!(':backup', @backup.final_file)
84
- case @backup.procedure.storage_name.to_sym
85
- when :cloudfiles then line.gsub!(':remote', "on Rackspace Cloudfiles")
86
- when :s3 then line.gsub!(':remote', "on Amazon S3")
87
- when :local then line.gsub!(':remote', "on the local server")
88
- when :scp, :sftp, :ftp then line.gsub!(':remote', "on the remote server (#{ip})")
89
- end
90
- @content ||= String.new
91
- @content << line
92
- end
93
- end
94
-
95
- end
96
- end
97
- end
@@ -1,7 +0,0 @@
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"
@@ -1,65 +0,0 @@
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
-
@@ -1,28 +0,0 @@
1
- require 'backup/connection/cloudfiles'
2
-
3
- module Backup
4
- module Record
5
- class CloudFiles < Backup::Record::Base
6
-
7
- alias_attribute :container, :bucket
8
-
9
- def load_specific_settings(adapter)
10
- self.container = adapter.procedure.get_storage_configuration.attributes['container']
11
- end
12
-
13
- private
14
-
15
- def self.destroy_backups(procedure, backups)
16
- cf = Backup::Connection::CloudFiles.new
17
- cf.static_initialize(procedure)
18
- cf.connect
19
- backups.each do |backup|
20
- puts "\nDestroying backup \"#{backup.filename}\" from container \"#{backup.container}\"."
21
- cf.destroy(backup.filename, backup.container)
22
- backup.destroy
23
- end
24
- end
25
-
26
- end
27
- end
28
- end
@@ -1,27 +0,0 @@
1
- require 'backup/connection/dropbox'
2
-
3
- module Backup
4
- module Record
5
- class Dropbox < Backup::Record::Base
6
- def load_specific_settings(adapter)
7
- end
8
-
9
- private
10
-
11
- def self.destroy_backups(procedure, backups)
12
- dropbox = Backup::Connection::Dropbox.new
13
- dropbox.static_initialize(procedure)
14
- session = dropbox.session
15
- backups.each do |backup|
16
- puts "\nDestroying backup \"#{backup.filename}\"."
17
- path_to_file = File.join(dropbox.path, backup.filename)
18
- begin
19
- session.delete(path_to_file, :mode => :dropbox)
20
- rescue ::Dropbox::FileNotFoundError => e
21
- puts "\n Backup with name '#{backup.filename}' was not found in '#{dropbox.path}'"
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,39 +0,0 @@
1
- require 'net/ftp'
2
-
3
- module Backup
4
- module Record
5
- class FTP < Backup::Record::Base
6
-
7
- attr_accessor :ip, :user, :password
8
-
9
- def load_specific_settings(adapter)
10
- %w(ip user password path).each do |method|
11
- send(:"#{method}=", adapter.procedure.get_storage_configuration.attributes[method])
12
- end
13
- end
14
-
15
- private
16
-
17
- def self.destroy_backups(procedure, backups)
18
- ip = procedure.get_storage_configuration.attributes['ip']
19
- user = procedure.get_storage_configuration.attributes['user']
20
- password = procedure.get_storage_configuration.attributes['password']
21
-
22
- Net::FTP.open(ip, user, password) do |ftp|
23
- backups.each do |backup|
24
- puts "\nDestroying backup \"#{backup.filename}\" from path \"#{backup.path}\"."
25
- begin
26
- ftp.chdir(backup.path)
27
- ftp.delete(backup.filename)
28
- backup.destroy
29
- rescue
30
- puts "Could not find backup #{backup.path}/#{backup.filename}."
31
- backup.destroy
32
- end
33
- end
34
- end
35
- end
36
-
37
- end
38
- end
39
- end
@@ -1,26 +0,0 @@
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
-
@@ -1,25 +0,0 @@
1
- require 'backup/connection/s3'
2
-
3
- module Backup
4
- module Record
5
- class S3 < Backup::Record::Base
6
-
7
- def load_specific_settings(adapter)
8
- self.bucket = adapter.procedure.get_storage_configuration.attributes['bucket']
9
- end
10
-
11
- private
12
-
13
- def self.destroy_backups(procedure, backups)
14
- s3 = Backup::Connection::S3.new
15
- s3.static_initialize(procedure)
16
- backups.each do |backup|
17
- puts "\nDestroying backup \"#{backup.filename}\" from bucket \"#{backup.bucket}\"."
18
- s3.destroy(backup.filename, backup.bucket)
19
- backup.destroy
20
- end
21
- end
22
-
23
- end
24
- end
25
- end