yore 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,491 @@
1
+ require 'rubygems'
2
+ gem 'RequirePaths'; require 'require_paths'
3
+ require_paths '.','..'
4
+
5
+ require 'fileutils'
6
+ require 'net/smtp'
7
+
8
+ require 'ihl_ruby/misc_utils'
9
+ require 'ihl_ruby/string_utils'
10
+ require 'ihl_ruby/xml_utils'
11
+ require 'ihl_ruby/extend_base_classes'
12
+
13
+ THIS_FILE = __FILE__
14
+ THIS_DIR = File.dirname(THIS_FILE)
15
+
16
+ module YoreCore
17
+
18
+ class KeepDaily
19
+
20
+ attr_reader :keep_age
21
+
22
+ def initialize(aKeepAge=14)
23
+ @keep_age = aKeepAge
24
+ end
25
+
26
+ def is?
27
+ true
28
+ end
29
+
30
+ def age(aDate)
31
+
32
+ end
33
+ def keep?(aDate)
34
+
35
+ end
36
+ end
37
+
38
+ class KeepWeekly
39
+
40
+ attr_reader :keep_age
41
+
42
+ def initialize(aKeepAge=14)
43
+ @keep_age = aKeepAge
44
+ end
45
+
46
+ def is?
47
+
48
+ end
49
+ def age(aDate)
50
+
51
+ end
52
+ def keep?(aDate)
53
+
54
+ end
55
+ end
56
+
57
+ class KeepMonthly
58
+
59
+ attr_reader :keep_age
60
+
61
+ def initialize(aKeepAge=14)
62
+ @keep_age = aKeepAge
63
+ end
64
+
65
+ def is?
66
+
67
+ end
68
+ def age(aDate)
69
+
70
+ end
71
+ def keep?(aDate)
72
+
73
+ end
74
+ end
75
+
76
+
77
+ class ConfigClass < Hash
78
+
79
+ def initialize(aDefaultHash=nil)
80
+ self.merge!(aDefaultHash) if aDefaultHash
81
+ end
82
+
83
+ def set_int(aKey,aValue)
84
+ case aValue
85
+ when String then self[aKey] = aValue.to_integer(self[aKey]);
86
+ when Fixnum then self[aKey] = aValue;
87
+ when Float then self[aKey] = aValue.to_i;
88
+ end
89
+ end
90
+
91
+ def set_float(aKey,aValue)
92
+ case aValue
93
+ when String then self[aKey] = aValue.to_float(self[aKey]);
94
+ when Fixnum then self[aKey] = aValue.to_f;
95
+ when Float then self[aKey] = aValue;
96
+ end
97
+ end
98
+
99
+ def set_boolean(aKey,aValue)
100
+ case aValue
101
+ when TrueClass,FalseClass then self[aKey] = aValue;
102
+ when String then self[aKey] = (['1','yes','y','true','on'].include?(aValue.downcase))
103
+ else
104
+ default set_boolean(aKey,aValue.to_s)
105
+ end
106
+ end
107
+
108
+ def set_symbol(aKey,aValue)
109
+ case aValue
110
+ when String then self[aKey] = (aValue.to_sym rescue nil);
111
+ when Symbol then self[aKey] = aValue;
112
+ end
113
+ end
114
+
115
+ def copy_item(aHash,aKey)
116
+ case self[aKey]
117
+ when NilClass then ;
118
+ when String then self[aKey] = aHash[aKey].to_s unless aHash[aKey].nil?
119
+ when Float then set_float(aKey,aHash[aKey]);
120
+ when Fixnum then set_int(aKey,aHash[aKey]);
121
+ when TrueClass, FalseClass then set_boolean(aKey,aHash[aKey]);
122
+ when Symbol then self[aKey] = (aHash[aKey].to_sym rescue nil)
123
+ else
124
+ raise Error.new('unsupported type')
125
+ end
126
+ end
127
+
128
+ def copy_strings(aHash,*aKeys)
129
+ aKeys.each do |k|
130
+ self[k] = aHash[k].to_s unless aHash[k].nil?
131
+ end
132
+ end
133
+
134
+ def copy_ints(*aDb)
135
+ aHash = aDb.shift
136
+ aKeys = aDb
137
+ aKeys.each do |k|
138
+ set_int(k,aHash[k])
139
+ end
140
+ end
141
+
142
+ def copy_floats(aHash,*aKeys)
143
+ aKeys.each do |k|
144
+ set_float(k,aHash[k])
145
+ end
146
+ end
147
+
148
+ def copy_booleans(aHash,*aKeys)
149
+ aKeys.each do |k|
150
+ set_boolean(k,aHash[k])
151
+ end
152
+ end
153
+
154
+ def to_hash
155
+ {}.merge(self)
156
+ end
157
+
158
+ end
159
+
160
+ class Yore
161
+
162
+ DEFAULT_CONFIG = {
163
+ :keep_daily => 14,
164
+ :keep_weekly => 12,
165
+ :keep_monthly => 12,
166
+ :crypto_iv => "3A63775C1E3F291B0925578165EB917E",
167
+ :crypto_key => "07692FC8656F04AE5518B80D38681E038A3C12050DF6CC97CEEC33D800D5E2FE",
168
+ :first_hour => 4,
169
+ :prefix => 'backup',
170
+ :log_level => 'DEBUG',
171
+ :bucket => '',
172
+ :email_report => false,
173
+ :mail_host => '',
174
+ :mail_port => 25,
175
+ :mail_helodomain => '',
176
+ :mail_user => '',
177
+ :mail_password => '',
178
+ :mail_from => '',
179
+ :mail_from_alias => '',
180
+ :mail_to => '',
181
+ :mail_to_alias => '',
182
+ :mail_auth => :plain,
183
+ :mysqldump => 'mysqldump'
184
+ }
185
+
186
+ attr_reader :config
187
+ attr_reader :logger
188
+ attr_reader :reporter
189
+ attr_reader :keepers
190
+ attr_reader :basepath
191
+
192
+ def initialize(aConfig=nil)
193
+ DEFAULT_CONFIG[:email_report] = true # fixes some bug where this was nil
194
+ configure(aConfig || DEFAULT_CONFIG)
195
+ end
196
+
197
+ def self.clean_config(aConfig)
198
+ result = ConfigClass.new(DEFAULT_CONFIG)
199
+ DEFAULT_CONFIG.each do |k,v|
200
+ result.copy_item(aConfig,k) if aConfig[k]
201
+ end
202
+ #result.copy_ints(aConfig,:keep_daily,:keep_weekly,:keep_monthly)
203
+ #result.copy_strings(aConfig,:crypto_iv,:crypto_key,:prefix,:bucket,:log_level,:basepath)
204
+ result.to_hash
205
+ end
206
+
207
+ # read the config however its given and return a hash with values in their correct type, and either valid or nil
208
+ # keys must be :symbols for aOptions. aConfig and aCmdOptions can be strings
209
+ def configure(aConfig,aCmdOptions = nil,aOptions = nil)
210
+ config_h = nil
211
+ case aConfig
212
+ when Hash then config_h = aConfig
213
+ when REXML::Element then config_h = XmlUtils.read_simple_items(aConfig,'/Yore/SimpleItems')
214
+ else
215
+ raise Error.new('unsupported type')
216
+ end
217
+ config_i = {}
218
+ config_h.each{|n,v| config_i[n.to_sym] = v} if config_h
219
+ aCmdOptions.each{|k,v| config_i[k.to_sym] = v} if aCmdOptions
220
+ config_i.merge!(aOptions) if aOptions
221
+ @config = Yore.clean_config(config_i)
222
+ @keepers = Array.new
223
+ @keepers << KeepDaily.new(config[:keep_daily])
224
+ @keepers << KeepWeekly.new(config[:keep_weekly])
225
+ @keepers << KeepMonthly.new(config[:keep_monthly])
226
+
227
+ @log_file = MiscUtils::temp_file()
228
+
229
+ @logger = LogUtils::create_logger_from_config({
230
+ 'destination' => 'STDOUT',
231
+ 'level' => config[:log_level]
232
+ # 'destination' => 'FILE',
233
+ # 'filename' => @log_file,
234
+ # 'level' => config[:log_level]
235
+ })
236
+ report_file = MiscUtils::temp_file
237
+ logger.info "report file: #{report_file}"
238
+ @reporter = LogUtils::create_reporter(report_file)
239
+ @basepath = config_h[:basepath]
240
+ #sources = aConfig
241
+ #
242
+ #@filelist =
243
+ end
244
+
245
+ def shell(aCommandline)
246
+ reporter.debug aCommandline
247
+ reporter.debug response = `#{aCommandline}`
248
+ response
249
+ end
250
+
251
+ def get_log
252
+ logger.close
253
+ # read in log and return
254
+ end
255
+
256
+ def get_report
257
+ MiscUtils::string_from_file(reporter.logdev.filename)
258
+ end
259
+
260
+ def self.filemap_from_filelist(aFiles)
261
+ ancestor_path = MiscUtils.file_list_ancestor(aFiles)
262
+ filemap = {}
263
+ aFiles.each do |fp|
264
+ filemap[fp] = MiscUtils.path_debase(fp,ancestor_path)
265
+ end
266
+ filemap
267
+ end
268
+
269
+ def keep_file?(aFile)
270
+
271
+ end
272
+
273
+ # By default, GNU tar suppresses a leading slash on absolute pathnames while creating or reading a tar archive. (You can suppress this with the -p option.)
274
+ # tar : http://my.safaribooksonline.com/0596102461/I_0596102461_CHP_3_SECT_9#snippet
275
+
276
+ # get files from wherever they are into a single file
277
+ def collect(aSourceFiles,aDestFile,aParentDir=nil)
278
+ filelist = filemap = nil
279
+ if aSourceFiles.is_a?(Hash)
280
+ filelist = aSourceFiles.keys
281
+ filemap = aSourceFiles
282
+ else # assume array
283
+ filelist = aSourceFiles
284
+ filemap = Yore.filemap_from_filelist(aSourceFiles)
285
+ end
286
+ aParentDir ||= MiscUtils.file_list_ancestor(filelist)
287
+ listfile = File.join(aParentDir,'.contents')
288
+ MiscUtils.string_to_file(
289
+ filelist.sort.map{|p| MiscUtils.path_debase(p, aParentDir)}.join("\n"),
290
+ listfile
291
+ )
292
+ tarfile = MiscUtils.file_change_ext(aDestFile, 'tar')
293
+ shell("tar cv --directory=#{aParentDir} --file=#{tarfile} --files-from=#{listfile}")
294
+ shell("tar --append --directory=#{aParentDir} --file=#{tarfile} .contents")
295
+ shell("bzip2 #{tarfile}; mv #{tarfile}.bz2 #{aDestFile}")
296
+ end
297
+
298
+ def pack(aFileIn,aFileOut)
299
+ shell "openssl enc -aes-256-cbc -K #{config[:crypto_key]} -iv #{config[:crypto_iv]} -in #{aFileIn} -out #{aFileOut}"
300
+ end
301
+
302
+ def unpack(aFileIn,aFileOut)
303
+ shell "openssl enc -d -aes-256-cbc -K #{config[:crypto_key]} -iv #{config[:crypto_iv]} -in #{aFileIn} -out #{aFileOut}"
304
+ end
305
+
306
+ # uploads the given file to the current bucket as its basename
307
+ def upload(aFile)
308
+ shell "s3cmd put #{config[:bucket]}:#{File.basename(aFile)} #{aFile}"
309
+ end
310
+
311
+ # downloads the given file from the current bucket as its basename
312
+ def download(aFile)
313
+ shell "s3cmd get #{config[:bucket]}:#{File.basename(aFile)} #{aFile}"
314
+ end
315
+
316
+ # calculate the date (with no time component) based on :day_begins_hour and the local time
317
+ def backup_date(aTime)
318
+ (aTime.localtime - (config[:first_hour]*3600)).date
319
+ end
320
+
321
+ # generates filename based on date and config
322
+ # config :
323
+ # :first_hour
324
+ # :
325
+ def encode_file_name(aTimeNow=Time.now)
326
+ "#{config[:prefix]}-#{backup_date(aTimeNow).date_numeric}.rew"
327
+ end
328
+
329
+ # return date based on filename
330
+ def decode_file_name(aFilename)
331
+ prefix,date,ext = aFilename.scan(/(.*?)\-(.*?)\.(.*)/).flatten
332
+ return Time.from_date_numeric(date)
333
+ end
334
+
335
+ #def set_file_name(aFile,aNewName)#
336
+ #
337
+ #end
338
+
339
+ def backup_process(aSourceFiles,aTimeNow=Time.now,aTempDir=nil)
340
+ aTempDir ||= MiscUtils.make_temp_dir('yore_')
341
+ temp_file = File.expand_path('backup.tar',aTempDir)
342
+ collect(aSourceFiles,temp_file)
343
+ backup_file = File.expand_path(encode_file_name(aTimeNow),aTempDir)
344
+ pack(temp_file,backup_file)
345
+ upload(backup_file)
346
+ end
347
+
348
+ # aDb : Hash containing :db_host,db_user,db_password,db_name,
349
+ def db_to_file(aDb,aFile)
350
+ shell "#{config[:mysqldump]} --host=#{aDb[:db_host]} --user=#{aDb[:db_user]} --password=#{aDb[:db_password]} --databases --skip-extended-insert --add-drop-database #{aDb[:db_name]} > #{aFile}"
351
+ end
352
+
353
+ def file_to_db(aFile,aDatabase)
354
+ #run "mysql --user=root --password=prot123ection </root/joomla_db_snapshot/joomla_db.sql"
355
+ end
356
+
357
+ #
358
+ #
359
+ # COMMANDLINE COMMANDS
360
+ #
361
+ #
362
+
363
+
364
+
365
+ def clean
366
+
367
+ end
368
+
369
+ # "/usr/bin/env" sets normal vars
370
+ # eg. 30 14 * * * /usr/bin/env ruby /Users/kip/svn/thewall/script/runner /Users/kip/svn/thewall/app/delete_old_posts.rb
371
+ # http://www.ameravant.com/posts/recurring-tasks-in-ruby-on-rails-using-runner-and-cron-jobs
372
+
373
+ # install gems
374
+ # make folder with correct folder structure
375
+ # copy in files
376
+ # add to crontab, with just email sending, then call backup
377
+
378
+ def report
379
+ return unless config[:email_report]
380
+ msg = get_report()
381
+ MiscUtils::send_email(
382
+ :host => config[:mail_host],
383
+ :port => config[:mail_port],
384
+ :helodomain => config[:mail_helodomain],
385
+ :user => config[:mail_user],
386
+ :password => config[:mail_password],
387
+ :from => config[:mail_from],
388
+ :from_alias => config[:mail_from_alias],
389
+ :to => config[:mail_to],
390
+ :to_alias => config[:mail_to_alias],
391
+ :auth => config[:mail_auth],
392
+ :subject => 'backup report',
393
+ :message => msg
394
+ )
395
+ end
396
+
397
+ def self.database_from_xml(aDatabaseNode)
398
+ return {
399
+ :db_host => aDatabaseNode.attributes['Host'],
400
+ :db_user => aDatabaseNode.attributes['User'],
401
+ :db_password => aDatabaseNode.attributes['Password'],
402
+ :db_name => aDatabaseNode.attributes['Name'],
403
+ :file => XmlUtils::peek_node_value(aDatabaseNode, "ToFile")
404
+ }
405
+ end
406
+
407
+ def backup(aJobFiles)
408
+ #require 'ruby-debug'; debugger
409
+ return unless job = aJobFiles.is_a?(Array) ? aJobFiles.first : aJobFiles # just use first job
410
+
411
+ xmlRoot = XmlUtils.get_file_root(job)
412
+
413
+ filelist = []
414
+
415
+ REXML::XPath.each(xmlRoot, '/Yore/Sources/Source') do |xmlSource|
416
+ case xmlSource['Type']
417
+ when 'File' then
418
+ REXML::XPath.each(xmlSource, 'IncludePath') do |xmlPath|
419
+ filelist += MiscUtils::recursive_file_list(File.expand_path(xmlPath.text,config[:basepath]))
420
+ end
421
+ when 'MySql' then
422
+ #<Source Type="MySql" >
423
+ # <Database Host="" Name="" User="" Password="">
424
+ # <ToFile>~/dbdump.sql</ToFile>
425
+ # </Database>
426
+ #</Source>
427
+ REXML::XPath.each(xmlSource, 'Database') do |xmlDb|
428
+ args = Yore::database_from_xml(xmlDb)
429
+ file = args.delete(:file)
430
+ unless args[:db_host] && args[:db_user] && args[:db_password] && args[:db_name] && file
431
+ raise Error.new("Invalid or missing parameter")
432
+ end
433
+ db_to_file(args,file)
434
+ filelist += file
435
+ end
436
+ end
437
+ end
438
+
439
+ filelist.uniq!
440
+ filelist.sort!
441
+
442
+ tempdir = MiscUtils.make_temp_dir('yore')
443
+ time = Time.now
444
+
445
+ backup_process(filelist,time,tempdir)
446
+ #clean
447
+ report
448
+ end
449
+
450
+ def test_email(*aDb)
451
+ args = {
452
+ :host => config[:mail_host],
453
+ :port => config[:mail_port],
454
+ :helodomain => config[:mail_helodomain],
455
+ :user => config[:mail_user],
456
+ :password => config[:mail_password],
457
+ :from => config[:mail_from],
458
+ :from_alias => config[:mail_from_alias],
459
+ :to => config[:mail_to],
460
+ :to_alias => config[:mail_to_alias],
461
+ :auth => config[:mail_auth],
462
+ :subject => 'email test',
463
+ :message => 'Just testing email sending'
464
+ }
465
+ logger.debug args.inspect
466
+ MiscUtils::send_email(args)
467
+ end
468
+
469
+ def db_dump(aArgs)
470
+ return nil unless aArgs
471
+ return nil unless job = aArgs[0]
472
+
473
+ xmlRoot = XmlUtils.get_file_root(job)
474
+ xmlDb = nil
475
+ if db_name = aArgs[1]
476
+ xmlDb = XmlUtils::single_node(xmlRoot,"/Yore/Sources/Source[@Type='MySql']/Database[@Name='#{db_name}']")
477
+ else
478
+ xmlDb = XmlUtils::single_node(xmlRoot,"/Yore/Sources/Source[@Type='MySql']/Database")
479
+ end
480
+ raise Error.new("No database") unless xmlDb
481
+ args = Yore.database_from_xml(xmlDb)
482
+ file = args.delete(:file)
483
+ unless args[:db_host] && args[:db_user] && args[:db_password] && args[:db_name] && file
484
+ raise Error.new("Invalid or missing parameter")
485
+ end
486
+ db_to_file(args,file)
487
+ end
488
+
489
+ end
490
+
491
+ end
data/lib/yore.orig.rb ADDED
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module Yore
5
+ VERSION = '0.0.1'
6
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/yore.rb'}"
9
+ puts "Loading yore gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)