-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathmsdw
executable file
·211 lines (177 loc) · 6.24 KB
/
msdw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#!/usr/bin/env ruby
############################################################
#
# Title: mysqldump wrapper (msdw)
# Created: Nov 22, 2013
# Author: [email protected]
# Version: 0.3
############################################################
### LIBRARIES ###
require 'optparse'
require 'fileutils'
### CONSTANTS ###
VERS = '0.3'
DAY = `date +\%d`.chomp
MONTH = `date +\%m`.chomp
YEAR = `date +\%Y`.chomp
DATE = "#{YEAR}-#{MONTH}-#{DAY}"
### CLASSES ###
class MySQLDump
attr_reader :database_name, :dump_path, :base_dump_path, :mysqldump_options
def initialize(database_name, dump_path, mysqldump_options, archive)
@database_name = database_name
@dump_path = File.expand_path("#{dump_path}/#{@database_name}")
@dump_folder = File.expand_path("#{@dump_path}/#{DATE}_#{@database_name}")
@mysqldump_options = mysqldump_options
@archive = archive
end
def backup
unless File.exists?("#{@dump_path}/#{DATE}_#{@database_name}.tar.gz")
begin
FileUtils.mkdir_p("#{@dump_folder}", :mode => 0700)
Dir.chdir("#{@dump_path}")
return unless dump
return unless create_md5
return unless compress
puts "Dump of the #{@database_name} database was successful."
rescue Errno::ENOENT => e
message = 'An error occured while trying to dump #{@database_name}:' + "\n\n" + "Error Message: " + e.message + "\n" + "Backtrace: " + e.backtrace.inspect
puts message
return
end
# Check if we need to archive
cleanup if DAY.to_i == 1 && @archive
else
puts "A dump today already exists for #{@database_name}."
end
end
private
def dump
unless system(%Q{mysqldump #{mysqldump_options} #{@database_name} > "#{@dump_folder}/#{@database_name}.sql"})
puts "Could not dump the #{@database_name} database."
`rm -rf "#{@dump_folder}"`
return false
end
true
end
def create_md5
md5 = %x{openssl md5 "#{@dump_folder}/#{@database_name}.sql" | awk '{print $NF}'}
unless $?.exitstatus == 0
puts "Could not create an md5 checksum for #{@database_name}, leaving dump as is."
return false
else
File.open("#{@dump_folder}/checksum.txt", 'w') { |f| f.write "#{@database_name} #{md5}" }
true
end
end
def compress
unless system('bash', '-c', %Q{tar czf #{DATE}_#{@database_name}.tar.gz #{DATE}_#{@database_name} &> /dev/null && rm -rf "#{@dump_folder}"})
puts "Could not compress #{@database_name} dump, leaving dump as is."
return false
end
true
end
def cleanup
files = Dir.glob("*_#{@database_name}.tar.gz").sort
month_count = files.collect { |x| x.split('-')[1] }.uniq.length
if month_count == 2
begin
year, month = last_backup_period
FileUtils.mkdir('monthly_archives', :mode => 0700 ) unless File.directory?('monthly_archives')
last_months_files = Dir.glob("#{@dump_path}/#{year}-#{month}-*_#{@database_name}.tar.gz").sort
unless last_months_files.empty?
FileUtils.mv(last_months_files.pop, 'monthly_archives/')
FileUtils.rm(last_months_files, :force => true)
puts "Archived #{@database_name} successfully."
else
puts "There was nothing to archive for #{@database_name}."
end
rescue Errno::ENOENT => e
message = 'An error occured while trying to archive for #{@database_name}:' + "\n\n" + "Error Message: " + e.message + "\n" + "Backtrace: " + e.backtrace.inspect
puts message
return
end
elsif month_count >= 3
puts "Backups span more than two months for #{@database_name}. Please manually resolve this if you want archiving to work."
return
end
end
def last_backup_period
if MONTH.to_i == 1
return YEAR.to_i - 1, 12
else
return YEAR, sprintf('%02d', MONTH.to_i - 1)
end
end
end
### METHODS ###
def sanity_check(options)
error_messages = []
error_messages << "The dump location needs to be an existing directory - #{options[:dump_path]}" unless File.directory?(options[:dump_path])
check = {}
check['mysqldump'] = system('bash', '-c', 'which mysqldump &> /dev/null')
check['openssl'] = system('bash', '-c', 'which openssl &> /dev/null')
check['tar'] = system('bash', '-c', 'which tar &> /dev/null')
check['rm'] = system('bash', '-c', 'which rm &> /dev/null')
check['date'] = system('bash', '-c', 'which date &> /dev/null')
check.each do |key, value|
error_messages << "This script requires #{key}. Please ensure it's installed and in your PATH." if value == false
end
unless error_messages.empty?
puts error_messages
exit 1
end
end
### ARGUMENT PARSING ###
options = {}
optparse = OptionParser.new do |opts|
opts.banner = "mysqldump wrapper (msdw)\nUsage: #{__FILE__} --databases database1,database2 --dump-path /path [-- mysqldump options]"
opts.separator ""
opts.separator "Specific options:"
opts.on("-d", "--databases database1,database2", Array, "The database(s) to dump. If multiple, they must be separated by commas with no spaces in between.") do |db|
options[:databases] = db
end
opts.on("-p", "--dump-path /path", "Path to store the dump files") do |path|
options[:dump_path] = path
end
options[:archive] = false
opts.on("-a", "--archive", "Purge all but the last dump of the previous month when a new months starts") do |bool|
options[:archive] = bool
end
opts.on_tail( '-h', '--help', 'Display this screen' ) do
puts opts
exit
end
opts.on_tail( '--version', 'Show version' ) do
puts "Version: #{VERS}"
exit
end
end
other_opts = []
begin
optparse.parse!
mandatory = [:databases, :dump_path]
missing = mandatory.select{ |param| options[param].nil? }
unless missing.empty?
puts "Missing options: #{missing.join(', ')}"
puts optparse
exit
end
sanity_check(options)
rescue OptionParser::MissingArgument
puts $!.to_s
puts optparse
exit 1
rescue OptionParser::InvalidOption => e
puts e.to_s
exit 1
end
### MAIN ###
dump_path = File.expand_path("#{options[:dump_path]}")
options[:databases].each do |db|
mysql_db = MySQLDump.new(db, dump_path, ARGV.join(" "), options[:archive])
puts "\n\n=== #{mysql_db.database_name} ==="
mysql_db.backup
end
puts "\n\n"
exit 0