require 'ftools'
require 'logger'
require 'optparse'
require 'ostruct'
require 'sysexits'
require 'tempfile'
include SysExits
BEGIN {
unless $log
$log = Logger.new(STDOUT)
$log.level = ($DEBUG ? Logger::DEBUG : Logger::WARN)
$logerr = Logger.new(STDERR)
$logerr.level = ($DEBUG ? Logger::DEBUG : Logger::WARN)
$log.debug("Debug logging enabled by runtime.")
end
}
END {
if $log
$log.close
$log = nil
end
if $logerr
$logerr.close
$logerr = nil
end
}
class BackupTool
PLIST_PARENT = "/etc/server_backup"
LOG_PARENT = "/Library/Logs"
COMMANDS = %w[help version backup browse restore size verify]
DATASETS = %w[configuration data all]
attr_reader :program_name, :command, :options, :settings
protected :program_name, :options, :settings
private :command
def initialize(svc_name = self.class.to_s, ver = "1.0")
$log.debug("BackupTool.initialize(#{svc_name},#{ver})")
@program_name = File.basename($0)
@service_name = svc_name
@version = ver
@banner = "Performs various backup and restore operations for #{@service_name} resources.\n"
@command = nil
@options = nil
@settings = nil
end
def BackupTool.launch(cmd)
method = "BackupTool.#{__method__}"
$log.debug("#{method}(#{cmd}#{block_given? ? ',&block' : ''})")
status = 0
Tempfile.open("backup-") do |tempfile|
$log.debug("#{method}() - running command")
if !system("#{cmd} 1> #{tempfile.path} 2>&1")
status = $?.exitstatus
$logerr.error "#{method}() - failed command: #{cmd}"
$logerr.error "#{method}() - error code: #{status.to_s}"
tempfile.each_line {|line| $logerr.info "#{method}() - stderr+stdout: #{line}" }
elsif block_given?
yield tempfile.read
end
tempfile.unlink
end
return (status == 0)
end
def BackupTool.dictionaryFromServeradmin(svc_name)
method = "BackupTool.#{__method__}"
$log.debug("#{method}(#{svc_name})")
settings = Hash.new
begin
if !self.launch("/usr/sbin/serveradmin settings #{svc_name}") do |output|
output.each_line do |line|
key,val = line.strip.split(' = ')
$log.debug("#{method}() - serveradmin(8) returned \'#{key} = #{val}\'")
if (val != "\"_empty_dictionary\"")
settings[key] = val.delete('"')
end
end
end
then
$log.warn("#{method}() - serveradmin(8) returned an error condition for #{svc_name}.")
end
rescue => exc
$log.error("#{method}() - exception trying to get #{svc_name}: #{exc.to_s.capitalize}")
return nil
end
return (settings.empty? ? nil : settings)
end
def launch(cmd, &block)
self.class.launch(cmd, &block)
end
def settings(svc_name = @service_name)
if @settings.nil?
$log.debug("#{self.class}.#{__method__}() - caching settings for #{svc_name}")
@settings = self.class.dictionaryFromServeradmin(svc_name)
end
return @settings
end
def setting(key, defval = nil)
if @settings.nil?
if (self.settings(key.split(":",2).first) == nil)
return defval
end
end
val = @settings[key]
return (val.nil? ? defval : val)
end
def help_help # :nodoc:
"#{@program_name} -cmd help\n\tShow this message\n"
end
def help_version # :nodoc:
"#{@program_name} -cmd version\n\tPrint the version of this tool\n"
end
def help_backup # :nodoc:
<<"EOS"
#{@program_name} [-debug] -cmd backup -path <destination> -opt {configuration | data | all}
Backup the service files of the type specified by -opt.
<destination>: absolute path to the mounted image where the files are to be backed up
EOS
end
def help_browse # :nodoc:
<<"EOS"
#{@program_name} [-debug] -cmd browse -path <image> -opt {configuration | data | all}
Display the names of the backup snapshots of the type specified by -opt.
<image>: absolute path to backup repository
EOS
end
def help_restore # :nodoc:
<<"EOS"
#{@program_name} [-debug] -cmd restore -path <image> -target <target> -opt {configuration | data | all}
Restore the service files of the type specified by -opt.
<image>: absolute path to backup repository
<target>: absolute path to directory where files are to be restoreed; normally "/"
EOS
end
def help_size # :nodoc:
<<"EOS"
#{@program_name} [-debug] -cmd size -opt {configuration | data | all}
Calculate and display the size, in kb, of the service files of the type specified by -opt
EOS
end
def help_verify # :nodoc:
<<"EOS"
#{@program_name} [-debug] -cmd verify -path <image> -target <target> -opt {configuration | data | all}
Verify the that the specified backup snapshot still matches the service files of the
type specified by -opt.
<image>: absolute path to backup snapshot
<target>: absolute path to directory where files are to be compared; normally "/"
EOS
end
protected :help_help, :help_version, :help_backup, :help_browse
protected :help_restore, :help_size, :help_verify
def help
self.usage(false)
exit EX_OK
end
def version
print("Version #{@version}\n")
exit EX_OK
end
def usage(err = true)
dst = err ? $stderr : $stdout
dst.print(@banner)
self.class::COMMANDS.each do |cmd|
if (self.respond_to?(cmd))
puts("\n")
if (self.respond_to?("help_" + cmd))
print(self.send("help_" + cmd))
else
print("*** Command '#{cmd}' supported but no help available!\n")
end
end
end
end
def parse!(argv)
$log.debug("#{self.class}.#{__method__}('#{argv.join(" ")}'#{block_given? ? ',&block' : ''})")
if (argv.size == 0)
raise OptionParser::InvalidArgument, "No arguments specified."
end
@options = Hash.new
while arg = argv.shift
case arg
when "-cmd"
@command = argv.shift
when "-debug"
$log.level = Logger::DEBUG
$logerr.level = Logger::DEBUG
when "-opt"
@options[:dataset] = argv.shift
when "-path"
@options[:path] = argv.shift
when "-target"
@options[:target] = argv.shift
when "-log"
level = $log.level
log_path = argv.shift
$log = Logger.new(log_path)
$log.level = level
$logerr = Logger.new(log_path)
$logerr.level = level
else
argv.unshift(arg)
if block_given?
unless (argv = yield(argv))
raise OptionParser::InvalidArgument, "Unknown argument."
end
else break
end
end
end
raise OptionParser::InvalidArgument, "No command specified." unless @command
unless (self.class::COMMANDS.include?(@command) && self.respond_to?(@command))
raise OptionParser::InvalidArgument, "Unknown command '#{@command}' specified."
end
return argv
end
def run
raise RuntimeError, "Command line not parsed; call parse!()." unless @command
$log.debug("#{self.class}.#{__method__}() - Performing #{@command}...")
return self.send(@command)
end
end
if __FILE__ == $0
require 'test/unit'
class TestBackupTool < Test::Unit::TestCase
end
end