yore 0.0.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.
@@ -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)