yore 0.0.2 → 0.0.3

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/History.txt CHANGED
@@ -2,3 +2,15 @@
2
2
 
3
3
  * 1 major enhancement:
4
4
  * Initial release
5
+
6
+ == 0.0.2 2009-2-04
7
+
8
+ * now using popen4
9
+ * raises exceptions when commandline tools fail
10
+ * now using shoulda for tests
11
+
12
+ == 0.0.3 2009-02-20
13
+
14
+ * much better logging, reporting and console output
15
+
16
+
data/Manifest.txt CHANGED
@@ -3,8 +3,11 @@ History.txt
3
3
  lib/ihl_ruby/enum.rb
4
4
  lib/ihl_ruby/extend_base_classes.rb
5
5
  lib/ihl_ruby/misc_utils.rb
6
+ lib/ihl_ruby/config.rb
7
+ lib/ihl_ruby/logging.rb
6
8
  lib/ihl_ruby/string_utils.rb
7
9
  lib/ihl_ruby/xml_utils.rb
10
+ lib/ihl_ruby/shell_extras.rb
8
11
  lib/yore.orig.rb
9
12
  lib/yore/yore_core.rb
10
13
  Manifest.txt
data/bin/yore CHANGED
@@ -24,9 +24,10 @@ def command(aParser,aController,aAction,aShortDescription=nil,aOptionParser=nil,
24
24
  c.options = aOptionParser if aOptionParser
25
25
  c.set_execution_block do |args|
26
26
  job = args.first
27
+ aController.logger.info "Job file: #{File.expand_path(job)}"
27
28
  xmlRoot = XmlUtils.get_file_root(job)
28
- aController.configure(xmlRoot,CMD_OPTIONS,{:basepath => File.dirname(File.expand_path(job))})
29
- aController.send(aAction,args)
29
+ aController.configure(xmlRoot,CMD_OPTIONS,{:basepath => File.dirname(File.expand_path(job))})
30
+ aController.do_action(aAction,args)
30
31
  end
31
32
  aParser.add_command(c)
32
33
  end
@@ -63,3 +64,6 @@ command(cmd,yore,:db_dump,"Dump database by name in job\n")
63
64
 
64
65
  cmd.parse
65
66
 
67
+ yore.logger.info "\nComplete.\n"
68
+ yore.report
69
+
@@ -0,0 +1,107 @@
1
+ class ConfigClass < Hash
2
+
3
+ attr_reader :default_values
4
+
5
+ def initialize(aDefaultValues,aNewValues=nil,&aBlock)
6
+ self.merge!(@default_values = aDefaultValues.clone)
7
+ if aNewValues
8
+ block_given? ? read(aNewValues,&aBlock) : read(aNewValues)
9
+ end
10
+ end
11
+
12
+ # aBlock allows values to be filtered based on key,default and new values
13
+ def read(aSource,&aBlock)
14
+ default_values.each do |k,v|
15
+ done = false
16
+ if block_given? && ((newv = yield(k,v,aSource && aSource[k])) != nil)
17
+ self[k] = newv
18
+ done = true
19
+ end
20
+ copy_item(aSource,k) if !done && aSource && aSource[k]
21
+ end
22
+ self
23
+ end
24
+
25
+ # reset values back to defaults
26
+ def reset
27
+ self.clear
28
+ self.merge!(default_values)
29
+ end
30
+
31
+ def set_int(aKey,aValue)
32
+ case aValue
33
+ when String then self[aKey] = aValue.to_integer(self[aKey]);
34
+ when Fixnum then self[aKey] = aValue;
35
+ when Float then self[aKey] = aValue.to_i;
36
+ end
37
+ end
38
+
39
+ def set_float(aKey,aValue)
40
+ case aValue
41
+ when String then self[aKey] = aValue.to_float(self[aKey]);
42
+ when Fixnum then self[aKey] = aValue.to_f;
43
+ when Float then self[aKey] = aValue;
44
+ end
45
+ end
46
+
47
+ def set_boolean(aKey,aValue)
48
+ case aValue
49
+ when TrueClass,FalseClass then self[aKey] = aValue;
50
+ when String then self[aKey] = (['1','yes','y','true','on'].include?(aValue.downcase))
51
+ else
52
+ set_boolean(aKey,aValue.to_s)
53
+ end
54
+ end
55
+
56
+ def set_symbol(aKey,aValue)
57
+ case aValue
58
+ when String then self[aKey] = (aValue.to_sym rescue nil);
59
+ when Symbol then self[aKey] = aValue;
60
+ end
61
+ end
62
+
63
+ def copy_item(aHash,aKey)
64
+ case default_values[aKey]
65
+ when NilClass then ;
66
+ when String then self[aKey] = aHash[aKey].to_s unless aHash[aKey].nil?
67
+ when Float then set_float(aKey,aHash[aKey]);
68
+ when Fixnum then set_int(aKey,aHash[aKey]);
69
+ when TrueClass, FalseClass then set_boolean(aKey,aHash[aKey]);
70
+ when Symbol then self[aKey] = (aHash[aKey].to_sym rescue nil)
71
+ else
72
+ raise Error.new('unsupported type')
73
+ end
74
+ end
75
+
76
+ def copy_strings(aHash,*aKeys)
77
+ aKeys.each do |k|
78
+ self[k] = aHash[k].to_s unless aHash[k].nil?
79
+ end
80
+ end
81
+
82
+ def copy_ints(*aDb)
83
+ aHash = aDb.shift
84
+ aKeys = aDb
85
+ aKeys.each do |k|
86
+ set_int(k,aHash[k])
87
+ end
88
+ end
89
+
90
+ def copy_floats(aHash,*aKeys)
91
+ aKeys.each do |k|
92
+ set_float(k,aHash[k])
93
+ end
94
+ end
95
+
96
+ def copy_booleans(aHash,*aKeys)
97
+ aKeys.each do |k|
98
+ set_boolean(k,aHash[k])
99
+ end
100
+ end
101
+
102
+ def to_hash
103
+ {}.merge(self)
104
+ end
105
+
106
+ end
107
+
@@ -0,0 +1,159 @@
1
+ require 'logger'
2
+ require 'ihl_ruby/misc_utils'
3
+
4
+ class Logger
5
+ attr_reader :logdev
6
+ end
7
+
8
+ module LogUtils
9
+
10
+ # eg.
11
+ # {
12
+ # 'destination' => 'STDERR|STDOUT|FILE',
13
+ # 'filename' => '/path/to/file.ext',
14
+ # 'level' => 'DEBUG|INFO|...',
15
+ #
16
+ # 'age' = 'daily|weekly|monthly',
17
+ # OR
18
+ # 'max_files' => 3,
19
+ # 'max_bytes' => 1024000
20
+ # }
21
+ def self.create_logger_from_config(aConfigHash)
22
+ if not aConfigHash
23
+ result = Logger.new(STDERR)
24
+ result.level = Logger::INFO
25
+ return result
26
+ end
27
+
28
+ result = nil
29
+ case aConfigHash['destination']
30
+ when 'STDERR' then
31
+ result = Logger.new(STDERR)
32
+ when 'STDOUT' then
33
+ result = Logger.new(STDOUT)
34
+ when 'FILE' then
35
+ result = aConfigHash['age'] ?
36
+ Logger.new(aConfigHash['filename'],aConfigHash['age']) :
37
+ Logger.new(
38
+ aConfigHash['filename'],
39
+ (aConfigHash['max_files'] || 3).to_i,
40
+ (aConfigHash['max_bytes'] || 1024000).to_i
41
+ )
42
+ else
43
+ result = Logger.new(STDERR)
44
+ end
45
+ puts valstr = "Logger::#{(aConfigHash['level'] || 'INFO').upcase}"
46
+ result.level = eval(valstr)
47
+ return result
48
+ end
49
+
50
+ # use this to trunc a log file to 0 bytes
51
+ def self.trunc(aFilename)
52
+ f = File.open(aFilename, "w")
53
+ f.close
54
+ end
55
+
56
+ class ReportFormatter < Logger::Formatter
57
+ def call(severity, time, progname, msg)
58
+ "|%s %1s %s\n" % [(time.strftime('%H%M%S.')<<"%03d" % (time.usec/1000)),severity[0..0],msg2str(msg)]
59
+ end
60
+ end
61
+
62
+ class Reporter < Logger
63
+ def initialize(logdev)
64
+ super(logdev)
65
+ end
66
+
67
+ end
68
+
69
+ def self.create_reporter(aFilename=nil)
70
+ aFilename ||= MiscUtils::temp_file()
71
+ result = Logger.new(aFilename)
72
+ result.formatter = ReportFormatter.new
73
+ result
74
+ end
75
+ end
76
+
77
+
78
+
79
+ class MultiLogger < Logger
80
+
81
+ attr_reader :loggers
82
+
83
+ def initialize(aLoggers)
84
+ @loggers = aLoggers.is_a?(Array) ? aLoggers : [aLoggers]
85
+ end
86
+
87
+ def add(severity, message = nil, progname = nil, &block)
88
+ return true if !@loggers
89
+ severity ||= UNKNOWN
90
+ @loggers.each do |lr|
91
+ block_given? ? lr.add(severity,message,progname,&block) : lr.add(severity,message,progname)
92
+ end
93
+ true
94
+ end
95
+ alias log add
96
+
97
+ def <<(msg)
98
+ @loggers.each do |lr|
99
+ lr << msg
100
+ end
101
+ end
102
+
103
+ def close
104
+ @loggers.each do |lr|
105
+ lr.close
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ #DEBUG D
112
+ #INFO
113
+ #WARN ?
114
+ #ERROR !
115
+ #FATAL F
116
+ #UNKNOWN U
117
+
118
+ # Logger that mostly works like a STDOUT logger, except that warnings and above get sent to STDERR instead
119
+ class ConsoleLogger < Logger
120
+
121
+ class ReportFormatter < Logger::Formatter
122
+ def call(severity, time, progname, msg)
123
+ msg2str(msg)+"\n"
124
+ end
125
+ end
126
+
127
+ def initialize(aErrLevel = Severity::WARN)
128
+ super(STDOUT)
129
+ self.formatter = ReportFormatter.new
130
+ self.level = Severity::INFO
131
+ self << "\n"
132
+ @err_logger = Logger.new(STDERR)
133
+ @err_level = aErrLevel
134
+ @err_logger.formatter = ReportFormatter.new
135
+ end
136
+
137
+ alias_method :orig_add, :add
138
+ def add(severity, message = nil, progname = nil, &block)
139
+ if severity >= @err_level
140
+ block_given? ? @err_logger.add(severity,message,progname,&block) : @err_logger.add(severity,message,progname)
141
+ else
142
+ block_given? ? orig_add(severity,message,progname,&block) : orig_add(severity,message,progname)
143
+ end
144
+ end
145
+ alias log add
146
+
147
+ #
148
+ # Close the logging device.
149
+ #
150
+ def close
151
+ begin
152
+ @logdev.close if @logdev
153
+ ensure
154
+ @err_logger.close
155
+ end
156
+ end
157
+
158
+ end
159
+
@@ -2,6 +2,8 @@ require 'tmpdir'
2
2
  require 'logger'
3
3
  require 'pathname'
4
4
 
5
+ require 'ihl_ruby/logging'
6
+
5
7
  module MiscUtils
6
8
 
7
9
  def self.logger
@@ -346,81 +348,6 @@ END_OF_MESSAGE
346
348
 
347
349
  end
348
350
 
349
- class Logger
350
- attr_reader :logdev
351
- end
352
-
353
- module LogUtils
354
-
355
- # eg.
356
- # {
357
- # 'destination' => 'STDERR|STDOUT|FILE',
358
- # 'filename' => '/path/to/file.ext',
359
- # 'level' => 'DEBUG|INFO|...',
360
- #
361
- # 'age' = 'daily|weekly|monthly',
362
- # OR
363
- # 'max_files' => 3,
364
- # 'max_bytes' => 1024000
365
- # }
366
- def self.create_logger_from_config(aConfigHash)
367
- if not aConfigHash
368
- result = Logger.new(STDERR)
369
- result.level = Logger::DEBUG
370
- return result
371
- end
372
-
373
- result = nil
374
- case aConfigHash['destination']
375
- when 'STDERR' then
376
- result = Logger.new(STDERR)
377
- when 'STDOUT' then
378
- result = Logger.new(STDOUT)
379
- when 'FILE' then
380
- result = aConfigHash['age'] ?
381
- Logger.new(aConfigHash['filename'],aConfigHash['age']) :
382
- Logger.new(
383
- aConfigHash['filename'],
384
- (aConfigHash['max_files'] || 3).to_i,
385
- (aConfigHash['max_bytes'] || 1024000).to_i
386
- )
387
- else
388
- result = Logger.new(STDERR)
389
- end
390
- puts valstr = "Logger::#{(aConfigHash['level'] || 'INFO').upcase}"
391
- result.level = eval(valstr)
392
- return result
393
- end
394
-
395
- # use this to trunc a log file to 0 bytes
396
- def self.trunc(aFilename)
397
- f = File.open(aFilename, "w")
398
- f.close
399
- end
400
-
401
- class ReportFormatter < Logger::Formatter
402
- def call(severity, time, progname, msg)
403
- "|%s %1s %s\n" % [(time.strftime('%H%M%S.')<<"%03d" % (time.usec/1000)),severity[0..0],msg2str(msg)]
404
- end
405
- end
406
-
407
- class Reporter < Logger
408
- def initialize(logdev)
409
- super(logdev)
410
- end
411
-
412
- end
413
-
414
- def self.create_reporter(aFilename=nil)
415
- aFilename ||= MiscUtils::temp_file()
416
- result = Logger.new(aFilename)
417
- result.formatter = ReportFormatter.new
418
- result
419
- end
420
- end
421
-
422
-
423
-
424
351
  # include this at the top of a class to protect it from baddies.
425
352
  # eg.
426
353
  # + nearly all ancestor public_instance_methods will be hidden
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ gem 'RequirePaths'; require 'require_paths'
3
+ require_paths '.','..'
4
+
5
+ gem 'Platform'; require 'platform'
6
+ gem 'shairontoledo-popen4'; require 'popen4'
7
+
8
+ module POpen4
9
+
10
+ class ExecuteError < StandardError
11
+
12
+ attr_reader :result #,:stderr,:stdout,:exitcode,:pid
13
+
14
+ def initialize(aArg)
15
+ if aArg.is_a? Hash
16
+ msg = ([aArg[:stderr],aArg[:stdout],"Error #{aArg[:exitcode].to_s}"].find {|i| i && !i.empty?})
17
+ super(msg)
18
+ @result = aArg
19
+ else
20
+ super(aArg)
21
+ end
22
+ end
23
+
24
+ def inspect
25
+ "#{self.class.to_s}: #{@result.inspect}"
26
+ end
27
+
28
+ end
29
+
30
+ def self.pump_thread(aIn,aOut)
31
+ Thread.new do
32
+ loop { aOut.puts aIn.gets }
33
+ end
34
+ end
35
+
36
+ # Usage :
37
+ # result = POpen4::shell('somebinary') do |r| # block gives opportunity to adjust result, and avoid exception raised from non-zero exit codes
38
+ # if r[:exitcode]==254 # eg. say this binary returns 254 to mean something special but not an error
39
+ # r[:stdout] = 'some correct output'
40
+ # r[:stderr] = ''
41
+ # r[:exitcode] = 0
42
+ # end
43
+ # end
44
+ #
45
+ # OR
46
+ #
47
+ # result = POpen4::shell('somebinary');
48
+ # puts result[:stdout]
49
+ #
50
+ # Giving aStdOut,aStdErr causes the command output to be connected to the given stream, and that stream to not be given in the result hash
51
+ def self.shell(aCommand,aWorkingDir=nil,aTimeout=nil,aStdOut=nil,aStdErr=nil)
52
+ raise ExecuteError.new('aWorkingDir doesnt exist') unless !aWorkingDir || File.exists?(aWorkingDir)
53
+ orig_wd = Dir.getwd
54
+ result = {:command => aCommand, :dir => (aWorkingDir || orig_wd)}
55
+ status = nil
56
+ begin
57
+ Dir.chdir(aWorkingDir) if aWorkingDir
58
+ Timeout.timeout(aTimeout,ExecuteError) do # nil aTimeout will not time out
59
+ status = POpen4::popen4(aCommand) do |stdout, stderr, stdin, pid|
60
+ thrOut = aStdOut ? Thread.new { aStdOut.puts stdout.read } : nil
61
+ thrErr = aStdErr ? Thread.new { aStdErr.puts stderr.read } : nil
62
+ thrOut.join if thrOut
63
+ thrErr.join if thrErr
64
+
65
+ result[:stdout] = stdout.read unless aStdOut
66
+ result[:stderr] = stderr.read unless aStdErr
67
+ result[:pid] = pid
68
+ end
69
+ end
70
+ ensure
71
+ Dir.chdir(orig_wd)
72
+ end
73
+ result[:exitcode] = (status && status.exitstatus) || 1
74
+ yield(result) if block_given?
75
+ raise ExecuteError.new(result) if result[:exitcode] != 0
76
+ return result
77
+ end
78
+
79
+ def self.shell_out(aCommand,aWorkingDir=nil,aTimeout=nil,&block)
80
+ block_given? ? POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR,&block) : POpen4::shell(aCommand,aWorkingDir,aTimeout,STDOUT,STDERR)
81
+ end
82
+
83
+ end
84
+
data/lib/yore.orig.rb CHANGED
@@ -2,5 +2,5 @@ $:.unshift(File.dirname(__FILE__)) unless
2
2
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
3
 
4
4
  module Yore
5
- VERSION = '0.0.2'
5
+ VERSION = '0.0.3'
6
6
  end
@@ -2,78 +2,21 @@ require 'rubygems'
2
2
  gem 'RequirePaths'; require 'require_paths'
3
3
  require_paths '.','..'
4
4
 
5
- gem 'Platform'; require 'platform'
6
- gem 'shairontoledo-popen4'; require 'popen4'
7
5
 
8
6
  require 'fileutils'
9
7
  require 'net/smtp'
10
8
 
11
9
  require 'ihl_ruby/misc_utils'
10
+ require 'ihl_ruby/logging'
12
11
  require 'ihl_ruby/string_utils'
13
12
  require 'ihl_ruby/xml_utils'
14
13
  require 'ihl_ruby/extend_base_classes'
14
+ require 'ihl_ruby/shell_extras'
15
+ require 'ihl_ruby/config'
15
16
 
16
17
  THIS_FILE = __FILE__
17
18
  THIS_DIR = File.dirname(THIS_FILE)
18
19
 
19
- module POpen4
20
-
21
- class ExecuteError < StandardError
22
-
23
- attr_reader :result #,:stderr,:stdout,:exitcode,:pid
24
-
25
- def initialize(aArg)
26
- if aArg.is_a? Hash
27
- super(aArg[:stdout] || '')
28
- @result = aArg
29
- # @stderr = aArg[:stderr]
30
- # @stdout = aArg[:stdout]
31
- # @exitcode = aArg[:exitcode]
32
- # @pid = aArg[:pid]
33
- else
34
- super(aArg)
35
- end
36
- end
37
-
38
- end
39
-
40
- # Usage :
41
- # result = POpen4::shell('somebinary') do |r| # block gives opportunity to adjust result, and avoid exception raised from non-zero exit codes
42
- # if r[:exitcode]==254 # eg. say this binary returns 254 to mean something special but not an error
43
- # r[:stdout] = 'some correct output'
44
- # r[:stderr] = ''
45
- # r[:exitcode] = 0
46
- # end
47
- # end
48
- #
49
- # OR
50
- #
51
- # result = POpen4::shell('somebinary');
52
- # puts result[:stdout]
53
- def self.shell(aCommand,aWorkingDir=nil,aTimeout=nil)
54
- raise ExecuteError.new('aWorkingDir doesnt exist') unless !aWorkingDir || File.exists?(aWorkingDir)
55
- orig_wd = Dir.getwd
56
- result = {:command => aCommand, :dir => (aWorkingDir || orig_wd)}
57
- status = nil
58
- begin
59
- Dir.chdir(aWorkingDir) if aWorkingDir
60
- Timeout.timeout(aTimeout,ExecuteError) do # nil aTimeout will not time out
61
- status = POpen4::popen4(aCommand) do |stdout, stderr, stdin, pid|
62
- result[:stdout] = stdout.read
63
- result[:stderr] = stderr.read
64
- result[:pid] = pid
65
- end
66
- end
67
- ensure
68
- Dir.chdir(orig_wd)
69
- end
70
- result[:exitcode] = (status && status.exitstatus) || 1
71
- yield(result) if block_given?
72
- raise ExecuteError.new(result) if result[:exitcode] != 0
73
- return result
74
- end
75
-
76
- end
77
20
 
78
21
 
79
22
  module YoreCore
@@ -137,88 +80,6 @@ module YoreCore
137
80
  end
138
81
 
139
82
 
140
- class ConfigClass < Hash
141
-
142
- def initialize(aDefaultHash=nil)
143
- self.merge!(aDefaultHash) if aDefaultHash
144
- end
145
-
146
- def set_int(aKey,aValue)
147
- case aValue
148
- when String then self[aKey] = aValue.to_integer(self[aKey]);
149
- when Fixnum then self[aKey] = aValue;
150
- when Float then self[aKey] = aValue.to_i;
151
- end
152
- end
153
-
154
- def set_float(aKey,aValue)
155
- case aValue
156
- when String then self[aKey] = aValue.to_float(self[aKey]);
157
- when Fixnum then self[aKey] = aValue.to_f;
158
- when Float then self[aKey] = aValue;
159
- end
160
- end
161
-
162
- def set_boolean(aKey,aValue)
163
- case aValue
164
- when TrueClass,FalseClass then self[aKey] = aValue;
165
- when String then self[aKey] = (['1','yes','y','true','on'].include?(aValue.downcase))
166
- else
167
- default set_boolean(aKey,aValue.to_s)
168
- end
169
- end
170
-
171
- def set_symbol(aKey,aValue)
172
- case aValue
173
- when String then self[aKey] = (aValue.to_sym rescue nil);
174
- when Symbol then self[aKey] = aValue;
175
- end
176
- end
177
-
178
- def copy_item(aHash,aKey)
179
- case self[aKey]
180
- when NilClass then ;
181
- when String then self[aKey] = aHash[aKey].to_s unless aHash[aKey].nil?
182
- when Float then set_float(aKey,aHash[aKey]);
183
- when Fixnum then set_int(aKey,aHash[aKey]);
184
- when TrueClass, FalseClass then set_boolean(aKey,aHash[aKey]);
185
- when Symbol then self[aKey] = (aHash[aKey].to_sym rescue nil)
186
- else
187
- raise Error.new('unsupported type')
188
- end
189
- end
190
-
191
- def copy_strings(aHash,*aKeys)
192
- aKeys.each do |k|
193
- self[k] = aHash[k].to_s unless aHash[k].nil?
194
- end
195
- end
196
-
197
- def copy_ints(*aDb)
198
- aHash = aDb.shift
199
- aKeys = aDb
200
- aKeys.each do |k|
201
- set_int(k,aHash[k])
202
- end
203
- end
204
-
205
- def copy_floats(aHash,*aKeys)
206
- aKeys.each do |k|
207
- set_float(k,aHash[k])
208
- end
209
- end
210
-
211
- def copy_booleans(aHash,*aKeys)
212
- aKeys.each do |k|
213
- set_boolean(k,aHash[k])
214
- end
215
- end
216
-
217
- def to_hash
218
- {}.merge(self)
219
- end
220
-
221
- end
222
83
 
223
84
  class Yore
224
85
 
@@ -230,7 +91,7 @@ module YoreCore
230
91
  :crypto_key => "07692FC8656F04AE5518B80D38681E038A3C12050DF6CC97CEEC33D800D5E2FE", # apparently a string of up to 64 random hex digits
231
92
  :first_hour => 4,
232
93
  :prefix => 'backup',
233
- :log_level => 'DEBUG',
94
+ :log_level => 'INFO',
234
95
  :bucket => '',
235
96
  :email_report => false,
236
97
  :mail_host => '',
@@ -254,17 +115,26 @@ module YoreCore
254
115
 
255
116
  def initialize(aConfig=nil)
256
117
  DEFAULT_CONFIG[:email_report] = false # fixes some bug where this was nil
257
- configure(aConfig || DEFAULT_CONFIG)
258
- end
118
+ @config = ConfigClass.new(DEFAULT_CONFIG,aConfig)
259
119
 
260
- def self.clean_config(aConfig)
261
- result = ConfigClass.new(DEFAULT_CONFIG)
262
- DEFAULT_CONFIG.each do |k,v|
263
- result.copy_item(aConfig,k) if aConfig[k]
264
- end
265
- #result.copy_ints(aConfig,:keep_daily,:keep_weekly,:keep_monthly)
266
- #result.copy_strings(aConfig,:crypto_iv,:crypto_key,:prefix,:bucket,:log_level,:basepath)
267
- result.to_hash
120
+ cons = ConsoleLogger.new()
121
+ cons.level = Logger::Severity.const_get(config[:log_level]) rescue Logger::Severity::INFO
122
+
123
+ report_file = MiscUtils::temp_file
124
+ @reporter = Logger.new(report_file)
125
+ @reporter.formatter = ConsoleLogger::ReportFormatter.new
126
+ @reporter.level = cons.level
127
+
128
+ @logger = MultiLogger.new([cons,@reporter])
129
+ #require 'ruby-debug'; debugger
130
+ @logger.info "Yore file and database backup tool for Amazon S3 "
131
+ @logger.info "(c) 2009 Buzzware Solutions (www.buzzware.com.au)"
132
+ @logger.info "-------------------------------------------------"
133
+ @logger.info ""
134
+
135
+ @logger.info "report file: #{report_file}"
136
+
137
+ configure(@config)
268
138
  end
269
139
 
270
140
  # read the config however its given and return a hash with values in their correct type, and either valid or nil
@@ -272,45 +142,47 @@ module YoreCore
272
142
  def configure(aConfig,aCmdOptions = nil,aOptions = nil)
273
143
  config_h = nil
274
144
  case aConfig
275
- when Hash then config_h = aConfig
145
+ when Hash,::ConfigClass then config_h = aConfig
276
146
  when REXML::Element then config_h = XmlUtils.read_simple_items(aConfig,'/Yore/SimpleItems')
277
147
  else
278
- raise Error.new('unsupported type')
148
+ raise StandardError.new('unsupported type')
279
149
  end
280
150
  config_i = {}
281
151
  config_h.each{|n,v| config_i[n.to_sym] = v} if config_h
282
152
  aCmdOptions.each{|k,v| config_i[k.to_sym] = v} if aCmdOptions
283
153
  config_i.merge!(aOptions) if aOptions
284
- @config = Yore.clean_config(config_i)
154
+ config.read(config_i)
155
+
285
156
  @keepers = Array.new
286
157
  @keepers << KeepDaily.new(config[:keep_daily])
287
158
  @keepers << KeepWeekly.new(config[:keep_weekly])
288
159
  @keepers << KeepMonthly.new(config[:keep_monthly])
289
-
290
- @log_file = MiscUtils::temp_file()
291
-
292
- @logger = LogUtils::create_logger_from_config({
293
- 'destination' => 'STDOUT',
294
- 'level' => config[:log_level]
295
- # 'destination' => 'FILE',
296
- # 'filename' => @log_file,
297
- # 'level' => config[:log_level]
298
- })
299
- report_file = MiscUtils::temp_file
300
- logger.info "report file: #{report_file}"
301
- @reporter = LogUtils::create_reporter(report_file)
160
+
302
161
  @basepath = config_h[:basepath]
303
- #sources = aConfig
304
- #
305
- #@filelist =
306
162
  end
163
+
164
+ def do_action(aAction,aArgs)
165
+ logger.info "Executing command: #{aAction} ...\n"
166
+ begin
167
+ send(aAction,aArgs)
168
+ rescue Exception => e
169
+ logger.warn "#{e.class.to_s}: during #{aAction}(#{aArgs.inspect}): #{e.message}"
170
+ end
171
+ end
307
172
 
308
173
  def shell(aCommandline,&aBlock)
309
- reporter.debug "To shell: " + aCommandline
174
+ #require 'ruby-debug'; debugger
175
+ logger.debug "To shell: " + aCommandline
310
176
  result = block_given? ? POpen4::shell(aCommandline,nil,nil,&aBlock) : POpen4::shell(aCommandline)
311
- reporter.debug "From shell: '#{result[:stdout]}'"
177
+ logger.debug "From shell: '#{result.inspect}'"
312
178
  return result[:stdout]
313
179
  end
180
+
181
+ def s3shell(aCommandline)
182
+ shell(aCommandline) do |r|
183
+ r[:exitcode] = 1 if r[:stderr].length > 0
184
+ end
185
+ end
314
186
 
315
187
  def get_log
316
188
  logger.close
@@ -318,7 +190,7 @@ module YoreCore
318
190
  end
319
191
 
320
192
  def get_report
321
- MiscUtils::string_from_file(reporter.logdev.filename)
193
+ MiscUtils::string_from_file(@reporter.logdev.filename)
322
194
  end
323
195
 
324
196
  def self.filemap_from_filelist(aFiles)
@@ -334,11 +206,21 @@ module YoreCore
334
206
 
335
207
  end
336
208
 
209
+ #def self.nice_format(aNumber)
210
+ # if aNumber >= 100
211
+ # sprintf('%.0f', aNumber)
212
+ # else
213
+ # sprintf('%.3f', aNumber).sub(/\.0{1,3}$/, '')
214
+ # end
215
+ #end
216
+
337
217
  # 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.)
338
218
  # tar : http://my.safaribooksonline.com/0596102461/I_0596102461_CHP_3_SECT_9#snippet
339
219
 
340
220
  # get files from wherever they are into a single file
341
221
  def collect(aSourceFiles,aDestFile,aParentDir=nil)
222
+ logger.info "Collecting files ..."
223
+ logger.info aSourceFiles.join("\n")
342
224
  filelist = filemap = nil
343
225
  if aSourceFiles.is_a?(Hash)
344
226
  filelist = aSourceFiles.keys
@@ -354,12 +236,17 @@ module YoreCore
354
236
  listfile
355
237
  )
356
238
  tarfile = MiscUtils.file_change_ext(aDestFile, 'tar')
239
+
357
240
  shell("tar cv --directory=#{aParentDir} --file=#{tarfile} --files-from=#{listfile}")
358
241
  shell("tar --append --directory=#{aParentDir} --file=#{tarfile} .contents")
242
+ logger.info "Compressing ..."
243
+ tarfile_size = File.size(tarfile)
359
244
  shell("bzip2 #{tarfile}; mv #{tarfile}.bz2 #{aDestFile}")
245
+ logger.info "Compressed #{'%.1f' % (tarfile_size*1.0/2**10)} KB to #{'%.1f' % (File.size(aDestFile)*1.0/2**10)} KB"
360
246
  end
361
247
 
362
248
  def pack(aFileIn,aFileOut)
249
+ logger.info "Encrypting ..."
363
250
  shell "openssl enc -aes-256-cbc -K #{config[:crypto_key]} -iv #{config[:crypto_iv]} -in #{aFileIn} -out #{aFileOut}"
364
251
  end
365
252
 
@@ -369,18 +256,20 @@ module YoreCore
369
256
 
370
257
  def ensure_bucket(aBucket=nil)
371
258
  aBucket ||= config[:bucket]
372
- shell "s3cmd createbucket #{aBucket}"
259
+ logger.info "Ensuring S3 bucket #{aBucket} exists ..."
260
+ s3shell "s3cmd createbucket #{aBucket}"
373
261
  end
374
262
 
375
263
  # uploads the given file to the current bucket as its basename
376
264
  def upload(aFile)
377
265
  ensure_bucket()
378
- shell "s3cmd put #{config[:bucket]}:#{File.basename(aFile)} #{aFile}"
266
+ logger.info "Uploading #{File.basename(aFile)} to S3 bucket #{config[:bucket]} ..."
267
+ s3shell "s3cmd put #{config[:bucket]}:#{File.basename(aFile)} #{aFile}"
379
268
  end
380
269
 
381
270
  # downloads the given file from the current bucket as its basename
382
271
  def download(aFile)
383
- shell "s3cmd get #{config[:bucket]}:#{File.basename(aFile)} #{aFile}"
272
+ s3shell "s3cmd get #{config[:bucket]}:#{File.basename(aFile)} #{aFile}"
384
273
  end
385
274
 
386
275
  # calculate the date (with no time component) based on :day_begins_hour and the local time
@@ -407,7 +296,6 @@ module YoreCore
407
296
  #end
408
297
 
409
298
  def backup_process(aSourceFiles,aTimeNow=Time.now,aTempDir=nil)
410
- #require 'ruby-debug'; debugger
411
299
  aTempDir ||= MiscUtils.make_temp_dir('yore_')
412
300
  temp_file = File.expand_path('backup.tar',aTempDir)
413
301
  collect(aSourceFiles,temp_file)
@@ -418,6 +306,7 @@ module YoreCore
418
306
 
419
307
  # aDb : Hash containing :db_host,db_user,db_password,db_name,
420
308
  def db_to_file(aDb,aFile)
309
+ logger.info "Dumping database #{aDb[:db_name]} ..."
421
310
  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}"
422
311
  end
423
312
 
@@ -449,6 +338,7 @@ module YoreCore
449
338
  def report
450
339
  return unless config[:email_report]
451
340
  msg = get_report()
341
+ logger.info "Sending report via email to #{config[:mail_to]} ..."
452
342
  MiscUtils::send_email(
453
343
  :host => config[:mail_host],
454
344
  :port => config[:mail_port],
@@ -476,7 +366,6 @@ module YoreCore
476
366
  end
477
367
 
478
368
  def backup(aJobFiles)
479
- #require 'ruby-debug'; debugger
480
369
  return unless job = aJobFiles.is_a?(Array) ? aJobFiles.first : aJobFiles # just use first job
481
370
 
482
371
  xmlRoot = XmlUtils.get_file_root(job)
@@ -501,16 +390,16 @@ module YoreCore
501
390
  args = Yore::database_from_xml(xmlDb)
502
391
  file = args.delete(:file)
503
392
  unless args[:db_host] && args[:db_user] && args[:db_password] && args[:db_name] && file
504
- raise Error.new("Invalid or missing parameter")
393
+ raise StandardError.new("Invalid or missing parameter")
505
394
  end
506
395
  db_to_file(args,file)
507
- filelist += file
396
+ filelist << file
508
397
  sourceFound = true
509
398
  end
510
399
  end
511
400
  end
512
401
 
513
- raise Error.new("Backup source found but file list empty") if sourceFound && filelist.empty?
402
+ raise StandardError.new("Backup source found but file list empty") if sourceFound && filelist.empty?
514
403
 
515
404
  filelist.uniq!
516
405
  filelist.sort!
@@ -520,7 +409,6 @@ module YoreCore
520
409
 
521
410
  backup_process(filelist,time,tempdir)
522
411
  #clean
523
- report
524
412
  end
525
413
 
526
414
  def test_email(*aDb)
@@ -553,11 +441,11 @@ module YoreCore
553
441
  else
554
442
  xmlDb = XmlUtils::single_node(xmlRoot,"/Yore/Sources/Source[@Type='MySql']/Database")
555
443
  end
556
- raise Error.new("No database") unless xmlDb
444
+ raise StandardError.new("No database") unless xmlDb
557
445
  args = Yore.database_from_xml(xmlDb)
558
446
  file = args.delete(:file)
559
447
  unless args[:db_host] && args[:db_user] && args[:db_password] && args[:db_name] && file
560
- raise Error.new("Invalid or missing parameter")
448
+ raise StandardError.new("Invalid or missing parameter")
561
449
  end
562
450
  db_to_file(args,file)
563
451
  end
data/test/test_job_a.xml CHANGED
@@ -8,7 +8,7 @@
8
8
  <Item Name="crypto_key">07692FC8656F04AE5518B80D38681E038A3C12050DF6CC97CEEC33D800D5E2FE</Item>
9
9
  <Item Name="first_hour">4</Item>
10
10
  <Item Name="prefix">backup</Item>
11
- <Item Name="log_level">DEBUG</Item>
11
+ <Item Name="log_level">INFO</Item>
12
12
  </SimpleItems>
13
13
  <Sources>
14
14
  <Source>
data/test/test_job_b.xml CHANGED
@@ -9,7 +9,7 @@
9
9
  <Item Name="bucket">yoretest</Item>
10
10
  <Item Name="first_hour">4</Item>
11
11
  <Item Name="prefix">yoretest</Item>
12
- <Item Name="log_level">DEBUG</Item>
12
+ <Item Name="log_level">INFO</Item>
13
13
  <Item Name="email_report">false</Item>
14
14
 
15
15
  <Item Name="mail_host">mail.authsmtp.com</Item>
data/test/yore_test.rb CHANGED
@@ -7,6 +7,7 @@ gem 'Shoulda'; require 'shoulda'
7
7
 
8
8
 
9
9
  require 'ihl_ruby/misc_utils'
10
+ require 'ihl_ruby/shell_extras'
10
11
 
11
12
  require 'fileutils'
12
13
 
@@ -126,7 +127,7 @@ job_template = <<EOF
126
127
  <Item Name="crypto_iv">3A63775C1E3F291B0925578165EB917E</Item>
127
128
  <Item Name="crypto_key">07692FC8656F04AE5518B80D38681E038A3C12050DF6CC97CEEC33D800D5E2FE</Item>
128
129
  <Item Name="prefix">backup</Item>
129
- <Item Name="log_level">DEBUG</Item>
130
+ <Item Name="log_level">INFO</Item>
130
131
  <Item Name="bucket">${BUCKET}</Item>
131
132
  </SimpleItems>
132
133
  <Sources>
@@ -186,6 +187,14 @@ EOF
186
187
  aController = YoreCore::Yore.new # main program object
187
188
  job = File.expand_path('../../test/test_job_b.xml',THIS_DIR)
188
189
  xmlRoot = XmlUtils.get_file_root(job)
190
+ srcdir = '/tmp/yoretest'
191
+ FileUtils.rm_rf srcdir+'/*'
192
+ FileUtils.mkdir_p srcdir
193
+ ['a','a/1','b','c'].each {|p| FileUtils.mkdir_p(File.expand_path(p,srcdir))}
194
+ %w(a/blah.txt a/1/blahblah.txt b/apples.txt c/carrots.txt).each do |f|
195
+ MiscUtils::make_temp_file(f,srcdir)
196
+ end
197
+
189
198
  aController.configure(xmlRoot,nil,{:basepath => File.dirname(File.expand_path(job))})
190
199
  aController.backup(job)
191
200
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gary McGhee
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-04 00:00:00 +09:00
12
+ date: 2009-02-20 00:00:00 +09:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -90,8 +90,11 @@ files:
90
90
  - lib/ihl_ruby/enum.rb
91
91
  - lib/ihl_ruby/extend_base_classes.rb
92
92
  - lib/ihl_ruby/misc_utils.rb
93
+ - lib/ihl_ruby/config.rb
94
+ - lib/ihl_ruby/logging.rb
93
95
  - lib/ihl_ruby/string_utils.rb
94
96
  - lib/ihl_ruby/xml_utils.rb
97
+ - lib/ihl_ruby/shell_extras.rb
95
98
  - lib/yore.orig.rb
96
99
  - lib/yore/yore_core.rb
97
100
  - Manifest.txt