namxam-backup 2.4.5
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.
- data/CHANGELOG +131 -0
- data/LICENSE +20 -0
- data/README.md +122 -0
- data/bin/backup +108 -0
- data/generators/backup/backup_generator.rb +69 -0
- data/generators/backup/templates/backup.rake +56 -0
- data/generators/backup/templates/backup.rb +253 -0
- data/generators/backup/templates/create_backup_tables.rb +18 -0
- data/generators/backup_update/backup_update_generator.rb +50 -0
- data/generators/backup_update/templates/migrations/update_backup_tables.rb +27 -0
- data/lib/backup.rb +132 -0
- data/lib/backup/adapters/archive.rb +34 -0
- data/lib/backup/adapters/base.rb +167 -0
- data/lib/backup/adapters/custom.rb +41 -0
- data/lib/backup/adapters/mongo_db.rb +139 -0
- data/lib/backup/adapters/mysql.rb +60 -0
- data/lib/backup/adapters/postgresql.rb +56 -0
- data/lib/backup/adapters/sqlite.rb +25 -0
- data/lib/backup/command_helper.rb +14 -0
- data/lib/backup/configuration/adapter.rb +21 -0
- data/lib/backup/configuration/adapter_options.rb +8 -0
- data/lib/backup/configuration/attributes.rb +19 -0
- data/lib/backup/configuration/base.rb +75 -0
- data/lib/backup/configuration/helpers.rb +24 -0
- data/lib/backup/configuration/mail.rb +20 -0
- data/lib/backup/configuration/smtp.rb +8 -0
- data/lib/backup/configuration/storage.rb +8 -0
- data/lib/backup/connection/cloudfiles.rb +75 -0
- data/lib/backup/connection/dropbox.rb +62 -0
- data/lib/backup/connection/s3.rb +88 -0
- data/lib/backup/core_ext/object.rb +5 -0
- data/lib/backup/environment/base.rb +12 -0
- data/lib/backup/environment/rails_configuration.rb +15 -0
- data/lib/backup/environment/unix_configuration.rb +109 -0
- data/lib/backup/mail/base.rb +97 -0
- data/lib/backup/mail/mail.txt +7 -0
- data/lib/backup/record/base.rb +65 -0
- data/lib/backup/record/cloudfiles.rb +28 -0
- data/lib/backup/record/dropbox.rb +27 -0
- data/lib/backup/record/ftp.rb +39 -0
- data/lib/backup/record/local.rb +26 -0
- data/lib/backup/record/s3.rb +25 -0
- data/lib/backup/record/scp.rb +33 -0
- data/lib/backup/record/sftp.rb +38 -0
- data/lib/backup/storage/base.rb +10 -0
- data/lib/backup/storage/cloudfiles.rb +16 -0
- data/lib/backup/storage/dropbox.rb +12 -0
- data/lib/backup/storage/ftp.rb +38 -0
- data/lib/backup/storage/local.rb +22 -0
- data/lib/backup/storage/s3.rb +15 -0
- data/lib/backup/storage/scp.rb +30 -0
- data/lib/backup/storage/sftp.rb +31 -0
- data/lib/backup/version.rb +3 -0
- data/lib/generators/backup/USAGE +10 -0
- data/lib/generators/backup/backup_generator.rb +47 -0
- data/lib/generators/backup/templates/backup.rake +56 -0
- data/lib/generators/backup/templates/backup.rb +236 -0
- data/lib/generators/backup/templates/create_backup_tables.rb +18 -0
- data/setup/backup.rb +255 -0
- data/setup/backup.sqlite3 +0 -0
- metadata +278 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class Archive < Backup::Adapters::Base
|
4
|
+
|
5
|
+
attr_accessor :files, :exclude
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Archives and Compresses all files
|
10
|
+
def perform
|
11
|
+
log system_messages[:archiving]; log system_messages[:compressing]
|
12
|
+
run "tar -czf #{File.join(tmp_path, compressed_file)} #{exclude_files} #{tar_files}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_settings
|
16
|
+
self.files = procedure.get_adapter_configuration.attributes['files']
|
17
|
+
self.exclude = procedure.get_adapter_configuration.attributes['exclude']
|
18
|
+
end
|
19
|
+
|
20
|
+
def performed_file_extension
|
21
|
+
".tar"
|
22
|
+
end
|
23
|
+
|
24
|
+
def tar_files
|
25
|
+
[*files].map{|f| f.gsub(' ', '\ ')}.join(' ')
|
26
|
+
end
|
27
|
+
|
28
|
+
def exclude_files
|
29
|
+
[*exclude].compact.map{|x| "--exclude=#{x}"}.join(' ')
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Backup
|
4
|
+
module Adapters
|
5
|
+
class Base
|
6
|
+
|
7
|
+
include Backup::CommandHelper
|
8
|
+
|
9
|
+
attr_accessor :procedure, :timestamp, :options, :tmp_path, :encrypt_with_password, :encrypt_with_gpg_public_key, :keep_backups, :trigger
|
10
|
+
|
11
|
+
# IMPORTANT
|
12
|
+
# final_file must have the value of the final filename result
|
13
|
+
# so if a file gets compressed, then the file could look like this:
|
14
|
+
# myfile.gz
|
15
|
+
#
|
16
|
+
# and if a file afterwards gets encrypted, the file will look like:
|
17
|
+
# myfile.gz.enc (with a password)
|
18
|
+
# myfile.gz.gpg (with a gpg public key)
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# It is important that, whatever the final filename of the file will be, that :final_file will contain it.
|
22
|
+
attr_accessor :performed_file, :compressed_file, :encrypted_file, :final_file
|
23
|
+
|
24
|
+
# Initializes the Backup Process
|
25
|
+
#
|
26
|
+
# This will first load in any prefixed settings from the Backup::Adapters::Base
|
27
|
+
# Then it will add it's own settings.
|
28
|
+
#
|
29
|
+
# First it will call the 'perform' method. This method is concerned with the backup, and must
|
30
|
+
# be implemented by derived classes!
|
31
|
+
# Then it will optionally encrypt the backed up file
|
32
|
+
# Then it will store it to the specified storage location
|
33
|
+
# Then it will record the data to the database
|
34
|
+
# Once this is all done, all the temporary files will be removed
|
35
|
+
#
|
36
|
+
# Wrapped inside of begin/ensure/end block to ensure the deletion of any files in the tmp directory
|
37
|
+
def initialize(trigger, procedure)
|
38
|
+
self.trigger = trigger
|
39
|
+
self.procedure = procedure
|
40
|
+
self.timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
41
|
+
self.tmp_path = File.join(BACKUP_PATH.gsub(' ', '\ '), 'tmp', 'backup', trigger)
|
42
|
+
self.encrypt_with_password = procedure.attributes['encrypt_with_password']
|
43
|
+
self.encrypt_with_gpg_public_key = procedure.attributes['encrypt_with_gpg_public_key']
|
44
|
+
self.keep_backups = procedure.attributes['keep_backups']
|
45
|
+
|
46
|
+
self.performed_file = "#{timestamp}.#{trigger.gsub(' ', '-')}#{performed_file_extension}"
|
47
|
+
self.compressed_file = "#{performed_file}.gz"
|
48
|
+
self.final_file = compressed_file
|
49
|
+
|
50
|
+
begin
|
51
|
+
create_tmp_folder
|
52
|
+
load_settings # if respond_to?(:load_settings)
|
53
|
+
handle_before_backup
|
54
|
+
perform
|
55
|
+
encrypt
|
56
|
+
store
|
57
|
+
handle_after_backup
|
58
|
+
record
|
59
|
+
notify
|
60
|
+
ensure
|
61
|
+
remove_tmp_files
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates the temporary folder for the specified adapter
|
66
|
+
def create_tmp_folder
|
67
|
+
#need to create with universal privlages as some backup tasks might create this path under sudo
|
68
|
+
run "mkdir -m 0777 -p #{tmp_path.sub(/\/[^\/]+$/, '')}" #this is the parent to the tmp_path
|
69
|
+
run "mkdir -m 0777 -p #{tmp_path}" #the temp path dir
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO make methods in derived classes public? respond_to cannot identify private methods
|
73
|
+
def load_settings
|
74
|
+
end
|
75
|
+
|
76
|
+
def skip_backup(msg)
|
77
|
+
log "Terminating backup early because: #{msg}"
|
78
|
+
exit 1
|
79
|
+
end
|
80
|
+
|
81
|
+
# Removes the files inside the temporary folder
|
82
|
+
def remove_tmp_files
|
83
|
+
run "rm -r #{File.join(tmp_path)}" if File.exists?(tmp_path) #just in case there isn't one because the process was skipped
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_before_backup
|
87
|
+
return unless self.procedure.before_backup_block
|
88
|
+
log system_messages[:before_backup_hook]
|
89
|
+
#run it through this instance so the block is run as a part of this adapter...which means it has access to all sorts of sutff
|
90
|
+
self.instance_eval &self.procedure.before_backup_block
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_after_backup
|
94
|
+
return unless self.procedure.after_backup_block
|
95
|
+
log system_messages[:after_backup_hook]
|
96
|
+
#run it through this instance so the block is run as a part of this adapter...which means it has access to all sorts of sutff
|
97
|
+
self.instance_eval &self.procedure.after_backup_block
|
98
|
+
end
|
99
|
+
|
100
|
+
# Encrypts the archive file
|
101
|
+
def encrypt
|
102
|
+
if encrypt_with_gpg_public_key.is_a?(String) && encrypt_with_password.is_a?(String)
|
103
|
+
puts "both 'encrypt_with_gpg_public_key' and 'encrypt_with_password' are set. Please choose one or the other. Exiting."
|
104
|
+
exit 1
|
105
|
+
end
|
106
|
+
|
107
|
+
if encrypt_with_gpg_public_key.is_a?(String)
|
108
|
+
if `which gpg` == ''
|
109
|
+
puts "Encrypting with a GPG public key requires that gpg be in your public path. gpg was not found. Exiting"
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
log system_messages[:encrypting_w_key]
|
113
|
+
self.encrypted_file = "#{self.final_file}.gpg"
|
114
|
+
|
115
|
+
# tmp_file = Tempfile.new('backup.pub'){ |tmp_file| tmp_file << encrypt_with_gpg_public_key }
|
116
|
+
tmp_file = Tempfile.new('backup.pub')
|
117
|
+
tmp_file << encrypt_with_gpg_public_key
|
118
|
+
tmp_file.close
|
119
|
+
# that will either say the key was added OR that it wasn't needed, but either way we need to parse for the uid
|
120
|
+
# which will be wrapped in '<' and '>' like <someone_famous@me.com>
|
121
|
+
encryptionKeyId = `gpg --import #{tmp_file.path} 2>&1`.match(/<(.+)>/)[1]
|
122
|
+
run "gpg -e --trust-model always -o #{File.join(tmp_path, encrypted_file)} -r '#{encryptionKeyId}' #{File.join(tmp_path, compressed_file)}"
|
123
|
+
elsif encrypt_with_password.is_a?(String)
|
124
|
+
log system_messages[:encrypting_w_pass]
|
125
|
+
self.encrypted_file = "#{self.final_file}.enc"
|
126
|
+
run "openssl enc -des-cbc -in #{File.join(tmp_path, compressed_file)} -out #{File.join(tmp_path, encrypted_file)} -k #{encrypt_with_password}"
|
127
|
+
end
|
128
|
+
self.final_file = encrypted_file if encrypted_file
|
129
|
+
end
|
130
|
+
|
131
|
+
# Initializes the storing process
|
132
|
+
def store
|
133
|
+
procedure.initialize_storage(self)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Records data on every individual file to the database
|
137
|
+
def record
|
138
|
+
record = procedure.initialize_record
|
139
|
+
record.load_adapter(self)
|
140
|
+
record.save
|
141
|
+
end
|
142
|
+
|
143
|
+
# Delivers a notification by email regarding the successfully stored backup
|
144
|
+
def notify
|
145
|
+
if Backup::Mail::Base.setup?
|
146
|
+
Backup::Mail::Base.notify!(self)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def system_messages
|
151
|
+
{ :compressing => "Compressing backup..",
|
152
|
+
:archiving => "Archiving backup..",
|
153
|
+
:encrypting_w_pass => "Encrypting backup with password..",
|
154
|
+
:encrypting_w_key => "Encrypting backup with gpg public key..",
|
155
|
+
:mysqldump => "Creating MySQL dump..",
|
156
|
+
:mongo_dump => "Creating MongoDB dump..",
|
157
|
+
:mongo_copy => "Creating MongoDB disk level copy..",
|
158
|
+
:before_backup_hook => "Running before backup hook..",
|
159
|
+
:after_backup_hook => "Running after backup hook..",
|
160
|
+
:pgdump => "Creating PostgreSQL dump..",
|
161
|
+
:sqlite => "Copying and compressing SQLite database..",
|
162
|
+
:commands => "Executing commands.." }
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class Custom < Backup::Adapters::Base
|
4
|
+
|
5
|
+
attr_accessor :commands
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Execute any given commands, then archive and compress every folder/file
|
10
|
+
def perform
|
11
|
+
execute_commands
|
12
|
+
targz
|
13
|
+
end
|
14
|
+
|
15
|
+
# Executes the commands
|
16
|
+
def execute_commands
|
17
|
+
return unless commands
|
18
|
+
log system_messages[:commands]
|
19
|
+
[*commands].each do |command|
|
20
|
+
run "#{command.gsub(':tmp_path', tmp_path)}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Archives and Compresses
|
25
|
+
def targz
|
26
|
+
log system_messages[:archiving]; log system_messages[:compressing]
|
27
|
+
run "tar -czf #{File.join(tmp_path, compressed_file)} #{File.join(tmp_path, '*')}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def performed_file_extension
|
31
|
+
".tar"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Loads the initial settings
|
35
|
+
def load_settings
|
36
|
+
self.commands = procedure.get_adapter_configuration.attributes['commands']
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class MongoDB < Backup::Adapters::Base
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
attr_accessor :user, :password, :database, :collections, :host, :port, :additional_options, :backup_method
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
BACKUP_METHOD_OPTIONS = [:mongodump, :disk_copy]
|
11
|
+
|
12
|
+
# Dumps and Compresses the Mongodump file
|
13
|
+
def perform
|
14
|
+
tmp_mongo_dir = "mongodump-#{Time.now.strftime("%Y%m%d%H%M%S")}"
|
15
|
+
tmp_dump_dir = File.join(tmp_path, tmp_mongo_dir)
|
16
|
+
|
17
|
+
case self.backup_method.to_sym
|
18
|
+
when :mongodump
|
19
|
+
#this is the default options
|
20
|
+
# PROS:
|
21
|
+
# * non-locking
|
22
|
+
# * much smaller archive sizes
|
23
|
+
# * can specifically target different databases or collections to dump
|
24
|
+
# * de-fragements the datastore
|
25
|
+
# * don't need to run under sudo
|
26
|
+
# * simple logic
|
27
|
+
# CONS:
|
28
|
+
# * a bit longer to restore as you have to do an import
|
29
|
+
# * does not include indexes or other meta data
|
30
|
+
log system_messages[:mongo_dump]
|
31
|
+
exit 1 unless run "#{mongodump} #{mongodump_options} #{collections_to_include} -o #{tmp_dump_dir} #{additional_options} > /dev/null 2>&1"
|
32
|
+
when :disk_copy
|
33
|
+
#this is a bit more complicated AND potentially a lot riskier:
|
34
|
+
# PROS:
|
35
|
+
# * byte level copy, so it includes all the indexes, meta data, etc
|
36
|
+
# * fast recovery; you just copy the files into place and startup mongo
|
37
|
+
# CONS:
|
38
|
+
# * locks the database, so ONLY use against a slave instance
|
39
|
+
# * copies everything; cannot specify a collection or a database
|
40
|
+
# * will probably need to run under sudo as the mongodb db_path file is probably under a different owner.
|
41
|
+
# If you do run under sudo, you will probably need to run rake RAILS_ENV=... if you aren't already
|
42
|
+
# * the logic is a bit brittle...
|
43
|
+
log system_messages[:mongo_copy]
|
44
|
+
|
45
|
+
cmd = "#{mongo} #{mongo_disk_copy_options} --quiet --eval 'printjson(db.isMaster());' admin"
|
46
|
+
output = JSON.parse(run(cmd, :exit_on_failure => true))
|
47
|
+
if output['ismaster']
|
48
|
+
puts "You cannot run in disk_copy mode against a master instance. This mode will lock the database. Please use :mongodump instead."
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
cmd = "#{mongo} #{mongo_disk_copy_options} --quiet --eval 'db.runCommand({fsync : 1, lock : 1}); printjson(db.runCommand({getCmdLineOpts:1}));' admin"
|
54
|
+
output = JSON.parse(run(cmd, :exit_on_failure => true))
|
55
|
+
|
56
|
+
#lets go find the dbpath. it is either going to be in the argv just returned OR we are going to have to parse through the mongo config file
|
57
|
+
cmd = "mongo --quiet --eval 'printjson(db.runCommand({getCmdLineOpts:1}));' admin"
|
58
|
+
output = JSON.parse(run(cmd, :exit_on_failure => true))
|
59
|
+
#see if --dbpath was passed in
|
60
|
+
db_path = output['argv'][output['argv'].index('--dbpath') + 1] if output['argv'].index('--dbpath')
|
61
|
+
#see if --config is passed in, and if so, lets parse it
|
62
|
+
db_path ||= $1 if output['argv'].index('--config') && File.read(output['argv'][output['argv'].index('--config') + 1]) =~ /dbpath\s*=\s*([^\s]*)/
|
63
|
+
db_path ||= "/data/db/" #mongo's default path
|
64
|
+
run "cp -rp #{db_path} #{tmp_dump_dir}"
|
65
|
+
ensure
|
66
|
+
#attempting to unlock
|
67
|
+
cmd = "#{mongo} #{mongo_disk_copy_options} --quiet --eval 'printjson(db.currentOp());' admin"
|
68
|
+
output = JSON.parse(run(cmd, :exit_on_failure => true))
|
69
|
+
(output['fsyncLock'] || 1).to_i.times do
|
70
|
+
run "#{mongo} #{mongo_disk_copy_options} --quiet --eval 'db.$cmd.sys.unlock.findOne();' admin"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else
|
74
|
+
puts "you did not enter a valid backup_method option. Your choices are: #{BACKUP_METHOD_OPTIONS.join(', ')}"
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
log system_messages[:compressing]
|
79
|
+
run "tar -cz -C #{tmp_path} -f #{File.join(tmp_path, compressed_file)} #{tmp_mongo_dir}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def mongodump
|
83
|
+
cmd = run("which mongodump").chomp
|
84
|
+
cmd = 'mongodump' if cmd.empty?
|
85
|
+
cmd
|
86
|
+
end
|
87
|
+
|
88
|
+
def mongo
|
89
|
+
cmd = run("which mongo").chomp
|
90
|
+
cmd = 'mongo' if cmd.empty?
|
91
|
+
cmd
|
92
|
+
end
|
93
|
+
|
94
|
+
def performed_file_extension
|
95
|
+
".tar"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Loads the initial settings
|
99
|
+
def load_settings
|
100
|
+
%w(user password database collections additional_options backup_method).each do |attribute|
|
101
|
+
send(:"#{attribute}=", procedure.get_adapter_configuration.attributes[attribute])
|
102
|
+
end
|
103
|
+
|
104
|
+
%w(host port).each do |attribute|
|
105
|
+
send(:"#{attribute}=", procedure.get_adapter_configuration.get_options.attributes[attribute])
|
106
|
+
end
|
107
|
+
|
108
|
+
self.backup_method ||= :mongodump
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns a list of options in Mongodump syntax
|
112
|
+
def mongodump_options
|
113
|
+
options = String.new
|
114
|
+
options += " --username='#{user}' " unless user.blank?
|
115
|
+
options += " --password='#{password}' " unless password.blank?
|
116
|
+
options += " --host='#{host}' " unless host.blank?
|
117
|
+
options += " --port='#{port}' " unless port.blank?
|
118
|
+
options += " --db='#{database}' " unless database.blank?
|
119
|
+
options
|
120
|
+
end
|
121
|
+
|
122
|
+
def mongo_disk_copy_options
|
123
|
+
options = String.new
|
124
|
+
options += " --username='#{user}' " unless user.blank?
|
125
|
+
options += " --password='#{password}' " unless password.blank?
|
126
|
+
options += " --host='#{host}' " unless host.blank?
|
127
|
+
options += " --port='#{port}' " unless port.blank?
|
128
|
+
options
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns a list of collections to include in Mongodump syntax
|
132
|
+
def collections_to_include
|
133
|
+
return "" unless collections
|
134
|
+
"--collection #{[*collections].join(" ")}"
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class MySQL < Backup::Adapters::Base
|
4
|
+
|
5
|
+
attr_accessor :user, :password, :database, :skip_tables, :host, :port, :socket, :additional_options, :tables
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Dumps and Compresses the MySQL file
|
10
|
+
def perform
|
11
|
+
log system_messages[:mysqldump]; log system_messages[:compressing]
|
12
|
+
run "#{mysqldump} -u #{user} --password='#{password}' #{options} #{additional_options} #{database} #{tables_to_include} #{tables_to_skip} | gzip -f --best > #{File.join(tmp_path, compressed_file)}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def mysqldump
|
16
|
+
# try to determine the full path, and fall back to myqsldump if not found
|
17
|
+
cmd = `which mysqldump`.chomp
|
18
|
+
cmd = 'mysqldump' if cmd.empty?
|
19
|
+
cmd
|
20
|
+
end
|
21
|
+
|
22
|
+
def performed_file_extension
|
23
|
+
".sql"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Loads the initial settings
|
27
|
+
def load_settings
|
28
|
+
%w(user password database tables skip_tables additional_options).each do |attribute|
|
29
|
+
send(:"#{attribute}=", procedure.get_adapter_configuration.attributes[attribute])
|
30
|
+
end
|
31
|
+
|
32
|
+
%w(host port socket).each do |attribute|
|
33
|
+
send(:"#{attribute}=", procedure.get_adapter_configuration.get_options.attributes[attribute])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a list of options in MySQL syntax
|
38
|
+
def options
|
39
|
+
options = String.new
|
40
|
+
options += " --host='#{host}' " unless host.blank?
|
41
|
+
options += " --port='#{port}' " unless port.blank?
|
42
|
+
options += " --socket='#{socket}' " unless socket.blank?
|
43
|
+
options
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a list of tables to skip in MySQL syntax
|
47
|
+
def tables_to_skip
|
48
|
+
return "" unless skip_tables
|
49
|
+
[*skip_tables].map {|table| " --ignore-table='#{database}.#{table}' "}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a list of tables to include in MySQL syntax
|
53
|
+
def tables_to_include
|
54
|
+
return "" unless tables
|
55
|
+
[*tables].join(" ")
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Backup
|
2
|
+
module Adapters
|
3
|
+
class PostgreSQL < Backup::Adapters::Base
|
4
|
+
|
5
|
+
attr_accessor :user, :password, :database, :skip_tables, :host, :port, :socket, :additional_options
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Dumps and Compresses the PostgreSQL file
|
10
|
+
def perform
|
11
|
+
log system_messages[:pgdump]; log system_messages[:compressing]
|
12
|
+
ENV['PGPASSWORD'] = password
|
13
|
+
run "#{pg_dump} -U #{user} #{options} #{additional_options} #{tables_to_skip} #{database} | gzip -f --best > #{File.join(tmp_path, compressed_file)}"
|
14
|
+
ENV['PGPASSWORD'] = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def pg_dump
|
18
|
+
# try to determine the full path, and fall back to pg_dump if not found
|
19
|
+
cmd = `which pg_dump`.chomp
|
20
|
+
cmd = 'pg_dump' if cmd.empty?
|
21
|
+
cmd
|
22
|
+
end
|
23
|
+
|
24
|
+
def performed_file_extension
|
25
|
+
".sql"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Loads the initial settings
|
29
|
+
def load_settings
|
30
|
+
%w(user password database skip_tables additional_options).each do |attribute|
|
31
|
+
send(:"#{attribute}=", procedure.get_adapter_configuration.attributes[attribute])
|
32
|
+
end
|
33
|
+
|
34
|
+
%w(host port socket).each do |attribute|
|
35
|
+
send(:"#{attribute}=", procedure.get_adapter_configuration.get_options.attributes[attribute])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a list of options in PostgreSQL syntax
|
40
|
+
def options
|
41
|
+
options = String.new
|
42
|
+
options += " --port='#{port}' " unless port.blank?
|
43
|
+
options += " --host='#{host}' " unless host.blank?
|
44
|
+
options += " --host='#{socket}' " unless socket.blank? unless options.include?('--host=')
|
45
|
+
options
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a list of tables to skip in PostgreSQL syntax
|
49
|
+
def tables_to_skip
|
50
|
+
return "" unless skip_tables
|
51
|
+
[*skip_tables].map {|table| " -T \"#{table}\" "}
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|