cm-backup 1.0.0

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.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -0
  3. data/bin/backup +5 -0
  4. data/lib/backup.rb +144 -0
  5. data/lib/backup/archive.rb +170 -0
  6. data/lib/backup/binder.rb +22 -0
  7. data/lib/backup/cleaner.rb +116 -0
  8. data/lib/backup/cli.rb +374 -0
  9. data/lib/backup/cloud_io/base.rb +41 -0
  10. data/lib/backup/cloud_io/cloud_files.rb +298 -0
  11. data/lib/backup/cloud_io/s3.rb +260 -0
  12. data/lib/backup/compressor/base.rb +35 -0
  13. data/lib/backup/compressor/bzip2.rb +39 -0
  14. data/lib/backup/compressor/custom.rb +53 -0
  15. data/lib/backup/compressor/gzip.rb +74 -0
  16. data/lib/backup/config.rb +119 -0
  17. data/lib/backup/config/dsl.rb +103 -0
  18. data/lib/backup/config/helpers.rb +143 -0
  19. data/lib/backup/database/base.rb +85 -0
  20. data/lib/backup/database/mongodb.rb +187 -0
  21. data/lib/backup/database/mysql.rb +192 -0
  22. data/lib/backup/database/openldap.rb +95 -0
  23. data/lib/backup/database/postgresql.rb +133 -0
  24. data/lib/backup/database/redis.rb +179 -0
  25. data/lib/backup/database/riak.rb +82 -0
  26. data/lib/backup/database/sqlite.rb +57 -0
  27. data/lib/backup/encryptor/base.rb +29 -0
  28. data/lib/backup/encryptor/gpg.rb +747 -0
  29. data/lib/backup/encryptor/open_ssl.rb +77 -0
  30. data/lib/backup/errors.rb +58 -0
  31. data/lib/backup/logger.rb +199 -0
  32. data/lib/backup/logger/console.rb +51 -0
  33. data/lib/backup/logger/fog_adapter.rb +29 -0
  34. data/lib/backup/logger/logfile.rb +133 -0
  35. data/lib/backup/logger/syslog.rb +116 -0
  36. data/lib/backup/model.rb +479 -0
  37. data/lib/backup/notifier/base.rb +128 -0
  38. data/lib/backup/notifier/campfire.rb +63 -0
  39. data/lib/backup/notifier/command.rb +102 -0
  40. data/lib/backup/notifier/datadog.rb +107 -0
  41. data/lib/backup/notifier/flowdock.rb +103 -0
  42. data/lib/backup/notifier/hipchat.rb +118 -0
  43. data/lib/backup/notifier/http_post.rb +117 -0
  44. data/lib/backup/notifier/mail.rb +249 -0
  45. data/lib/backup/notifier/nagios.rb +69 -0
  46. data/lib/backup/notifier/pagerduty.rb +81 -0
  47. data/lib/backup/notifier/prowl.rb +68 -0
  48. data/lib/backup/notifier/pushover.rb +74 -0
  49. data/lib/backup/notifier/ses.rb +105 -0
  50. data/lib/backup/notifier/slack.rb +148 -0
  51. data/lib/backup/notifier/twitter.rb +58 -0
  52. data/lib/backup/notifier/zabbix.rb +63 -0
  53. data/lib/backup/package.rb +55 -0
  54. data/lib/backup/packager.rb +107 -0
  55. data/lib/backup/pipeline.rb +124 -0
  56. data/lib/backup/splitter.rb +76 -0
  57. data/lib/backup/storage/base.rb +69 -0
  58. data/lib/backup/storage/cloud_files.rb +158 -0
  59. data/lib/backup/storage/cycler.rb +75 -0
  60. data/lib/backup/storage/dropbox.rb +212 -0
  61. data/lib/backup/storage/ftp.rb +112 -0
  62. data/lib/backup/storage/local.rb +64 -0
  63. data/lib/backup/storage/qiniu.rb +65 -0
  64. data/lib/backup/storage/rsync.rb +248 -0
  65. data/lib/backup/storage/s3.rb +156 -0
  66. data/lib/backup/storage/scp.rb +67 -0
  67. data/lib/backup/storage/sftp.rb +82 -0
  68. data/lib/backup/syncer/base.rb +70 -0
  69. data/lib/backup/syncer/cloud/base.rb +179 -0
  70. data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
  71. data/lib/backup/syncer/cloud/local_file.rb +100 -0
  72. data/lib/backup/syncer/cloud/s3.rb +110 -0
  73. data/lib/backup/syncer/rsync/base.rb +54 -0
  74. data/lib/backup/syncer/rsync/local.rb +31 -0
  75. data/lib/backup/syncer/rsync/pull.rb +51 -0
  76. data/lib/backup/syncer/rsync/push.rb +205 -0
  77. data/lib/backup/template.rb +46 -0
  78. data/lib/backup/utilities.rb +224 -0
  79. data/lib/backup/version.rb +5 -0
  80. data/templates/cli/archive +28 -0
  81. data/templates/cli/compressor/bzip2 +4 -0
  82. data/templates/cli/compressor/custom +7 -0
  83. data/templates/cli/compressor/gzip +4 -0
  84. data/templates/cli/config +123 -0
  85. data/templates/cli/databases/mongodb +15 -0
  86. data/templates/cli/databases/mysql +18 -0
  87. data/templates/cli/databases/openldap +24 -0
  88. data/templates/cli/databases/postgresql +16 -0
  89. data/templates/cli/databases/redis +16 -0
  90. data/templates/cli/databases/riak +17 -0
  91. data/templates/cli/databases/sqlite +11 -0
  92. data/templates/cli/encryptor/gpg +27 -0
  93. data/templates/cli/encryptor/openssl +9 -0
  94. data/templates/cli/model +26 -0
  95. data/templates/cli/notifier/zabbix +15 -0
  96. data/templates/cli/notifiers/campfire +12 -0
  97. data/templates/cli/notifiers/command +32 -0
  98. data/templates/cli/notifiers/datadog +57 -0
  99. data/templates/cli/notifiers/flowdock +16 -0
  100. data/templates/cli/notifiers/hipchat +16 -0
  101. data/templates/cli/notifiers/http_post +32 -0
  102. data/templates/cli/notifiers/mail +24 -0
  103. data/templates/cli/notifiers/nagios +13 -0
  104. data/templates/cli/notifiers/pagerduty +12 -0
  105. data/templates/cli/notifiers/prowl +11 -0
  106. data/templates/cli/notifiers/pushover +11 -0
  107. data/templates/cli/notifiers/ses +15 -0
  108. data/templates/cli/notifiers/slack +22 -0
  109. data/templates/cli/notifiers/twitter +13 -0
  110. data/templates/cli/splitter +7 -0
  111. data/templates/cli/storages/cloud_files +11 -0
  112. data/templates/cli/storages/dropbox +20 -0
  113. data/templates/cli/storages/ftp +13 -0
  114. data/templates/cli/storages/local +8 -0
  115. data/templates/cli/storages/qiniu +12 -0
  116. data/templates/cli/storages/rsync +17 -0
  117. data/templates/cli/storages/s3 +16 -0
  118. data/templates/cli/storages/scp +15 -0
  119. data/templates/cli/storages/sftp +15 -0
  120. data/templates/cli/syncers/cloud_files +22 -0
  121. data/templates/cli/syncers/rsync_local +20 -0
  122. data/templates/cli/syncers/rsync_pull +28 -0
  123. data/templates/cli/syncers/rsync_push +28 -0
  124. data/templates/cli/syncers/s3 +27 -0
  125. data/templates/general/links +3 -0
  126. data/templates/general/version.erb +2 -0
  127. data/templates/notifier/mail/failure.erb +16 -0
  128. data/templates/notifier/mail/success.erb +16 -0
  129. data/templates/notifier/mail/warning.erb +16 -0
  130. data/templates/storage/dropbox/authorization_url.erb +6 -0
  131. data/templates/storage/dropbox/authorized.erb +4 -0
  132. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  133. metadata +1077 -0
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Encryptor
5
+ class OpenSSL < Base
6
+
7
+ ##
8
+ # The password that'll be used to encrypt the backup. This
9
+ # password will be required to decrypt the backup later on.
10
+ attr_accessor :password
11
+
12
+ ##
13
+ # The password file to use to encrypt the backup.
14
+ attr_accessor :password_file
15
+
16
+ ##
17
+ # Determines whether the 'base64' should be used or not
18
+ attr_accessor :base64
19
+
20
+ ##
21
+ # Determines whether the 'salt' flag should be used
22
+ attr_accessor :salt
23
+
24
+ ##
25
+ # Creates a new instance of Backup::Encryptor::OpenSSL and
26
+ # sets the password attribute to what was provided
27
+ def initialize(&block)
28
+ super
29
+
30
+ @base64 ||= false
31
+ @salt ||= true
32
+ @password_file ||= nil
33
+
34
+ instance_eval(&block) if block_given?
35
+ end
36
+
37
+ ##
38
+ # This is called as part of the procedure run by the Packager.
39
+ # It sets up the needed options to pass to the openssl command,
40
+ # then yields the command to use as part of the packaging procedure.
41
+ # Once the packaging procedure is complete, it will return
42
+ # so that any clean-up may be performed after the yield.
43
+ def encrypt_with
44
+ log!
45
+ yield "#{ utility(:openssl) } #{ options }", '.enc'
46
+ end
47
+
48
+ private
49
+
50
+ ##
51
+ # Uses the 256bit AES encryption cipher, which is what the
52
+ # US Government uses to encrypt information at the "Top Secret" level.
53
+ #
54
+ # The -base64 option will make the encrypted output base64 encoded,
55
+ # this makes the encrypted file readable using text editors
56
+ #
57
+ # The -salt option adds strength to the encryption
58
+ #
59
+ # Always sets a password option, if even no password is given,
60
+ # but will prefer the password_file option if both are given.
61
+ def options
62
+ opts = ['aes-256-cbc']
63
+ opts << '-base64' if @base64
64
+ opts << '-salt' if @salt
65
+
66
+ if @password_file.to_s.empty?
67
+ opts << "-k #{Shellwords.escape(@password)}"
68
+ else
69
+ opts << "-pass file:#{@password_file}"
70
+ end
71
+
72
+ opts.join(' ')
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+
5
+ # Provides cascading errors with formatted messages.
6
+ # See the specs for details.
7
+ module NestedExceptions
8
+
9
+ def self.included(klass)
10
+ klass.extend Module.new {
11
+ def wrap(wrapped_exception, msg = nil)
12
+ new(msg, wrapped_exception)
13
+ end
14
+ }
15
+ end
16
+
17
+ def initialize(obj = nil, wrapped_exception = nil)
18
+ @wrapped_exception = wrapped_exception
19
+ msg = (obj.respond_to?(:to_str) ? obj.to_str : obj.to_s).
20
+ gsub(/^ */, ' ').strip
21
+ msg = clean_name(self.class.name) + (msg.empty? ? '' : ": #{ msg }")
22
+
23
+ if wrapped_exception
24
+ msg << "\n--- Wrapped Exception ---\n"
25
+ class_name = clean_name(wrapped_exception.class.name)
26
+ msg << class_name + ': ' unless
27
+ wrapped_exception.message.start_with? class_name
28
+ msg << wrapped_exception.message
29
+ end
30
+
31
+ super(msg)
32
+ set_backtrace(wrapped_exception.backtrace) if wrapped_exception
33
+ end
34
+
35
+ def exception(obj = nil)
36
+ return self if obj.nil? || equal?(obj)
37
+
38
+ ex = self.class.new(obj, @wrapped_exception)
39
+ ex.set_backtrace(backtrace) unless ex.backtrace
40
+ ex
41
+ end
42
+
43
+ private
44
+
45
+ def clean_name(name)
46
+ name.sub(/^Backup::/, '')
47
+ end
48
+
49
+ end
50
+
51
+ class Error < StandardError
52
+ include NestedExceptions
53
+ end
54
+
55
+ class FatalError < Exception
56
+ include NestedExceptions
57
+ end
58
+ end
@@ -0,0 +1,199 @@
1
+ # encoding: utf-8
2
+
3
+ require 'backup/logger/console'
4
+ require 'backup/logger/logfile'
5
+ require 'backup/logger/syslog'
6
+ require 'backup/logger/fog_adapter'
7
+
8
+ module Backup
9
+ class Logger
10
+
11
+ class Config
12
+ class Logger < Struct.new(:class, :options)
13
+ def enabled?
14
+ options.enabled?
15
+ end
16
+ end
17
+
18
+ class DSL < Struct.new(:ignores, :console, :logfile, :syslog)
19
+ def ignore_warning(str_or_regexp)
20
+ ignores << str_or_regexp
21
+ end
22
+ end
23
+
24
+ attr_reader :ignores, :loggers, :dsl
25
+
26
+ def initialize
27
+ @ignores = []
28
+ @loggers = [
29
+ Logger.new(Console, Console::Options.new),
30
+ Logger.new(Logfile, Logfile::Options.new),
31
+ Logger.new(Syslog, Syslog::Options.new)
32
+ ]
33
+ @dsl = DSL.new(ignores, *loggers.map(&:options))
34
+ end
35
+ end
36
+
37
+ ##
38
+ # All messages sent to the Logger are stored in Logger.messages
39
+ # and sent to all enabled logger's #log method as Message objects.
40
+ class Message < Struct.new(:time, :level, :lines)
41
+ ##
42
+ # Returns an Array of the message lines in the following format:
43
+ #
44
+ # [YYYY/MM/DD HH:MM:SS][level] message line text
45
+ def formatted_lines
46
+ timestamp = time.strftime("%Y/%m/%d %H:%M:%S")
47
+ lines.map {|line| "[#{ timestamp }][#{ level }] #{ line }" }
48
+ end
49
+
50
+ def matches?(ignores)
51
+ text = lines.join("\n")
52
+ ignores.any? {|obj|
53
+ obj.is_a?(Regexp) ? text.match(obj) : text.include?(obj)
54
+ }
55
+ end
56
+ end
57
+
58
+ class << self
59
+ extend Forwardable
60
+ def_delegators :logger,
61
+ :start!, :abort!, :info, :warn, :error,
62
+ :messages, :has_warnings?, :has_errors?
63
+
64
+ ##
65
+ # Allows the Logger to be configured.
66
+ #
67
+ # # shown with their default values
68
+ # Backup::Logger.configure do
69
+ # # Console options:
70
+ # console.quiet = false
71
+ #
72
+ # # Logfile options:
73
+ # logfile.enabled = true
74
+ # logfile.log_path = 'log'
75
+ # logfile.max_bytes = 500_000
76
+ #
77
+ # # Syslog options:
78
+ # syslog.enabled = false
79
+ # syslog.ident = 'backup'
80
+ # syslog.options = Syslog::LOG_PID
81
+ # syslog.facility = Syslog::LOG_LOCAL0
82
+ # syslog.info = Syslog::LOG_INFO
83
+ # syslog.warn = Syslog::LOG_WARNING
84
+ # syslog.error = Syslog::LOG_ERR
85
+ #
86
+ # # Ignore Warnings:
87
+ # # Converts :warn level messages to level :info
88
+ # ignore_warning 'that contains this string'
89
+ # ignore_warning /that matches this regexp/
90
+ # end
91
+ #
92
+ # See each Logger's Option class for details.
93
+ # @see Console::Options
94
+ # @see Logfile::Options
95
+ # @see Syslog::Options
96
+ def configure(&block)
97
+ config.dsl.instance_eval(&block)
98
+ end
99
+
100
+ ##
101
+ # Called after each backup model/trigger has been performed.
102
+ def clear!
103
+ @logger = nil
104
+ logger.start!
105
+ end
106
+
107
+ private
108
+
109
+ def config
110
+ @config ||= Config.new
111
+ end
112
+
113
+ def logger
114
+ @logger ||= new(config)
115
+ end
116
+
117
+ def reset!
118
+ @config = @logger = nil
119
+ end
120
+ end
121
+
122
+ MUTEX = Mutex.new
123
+
124
+ ##
125
+ # Returns an Array of Message objects for all logged messages received.
126
+ # These are used to attach log files to Mail notifications.
127
+ attr_reader :messages
128
+
129
+ def initialize(config)
130
+ @config = config
131
+ @messages = []
132
+ @loggers = []
133
+ @has_warnings = @has_errors = false
134
+ end
135
+
136
+ ##
137
+ # Sends a message to the Logger using the specified log level.
138
+ # +obj+ may be any Object that responds to #to_s (i.e. an Exception)
139
+ [:info, :warn, :error].each do |level|
140
+ define_method level, lambda {|obj|
141
+ MUTEX.synchronize { log(obj, level) }
142
+ }
143
+ end
144
+
145
+ ##
146
+ # Returns true if any +:warn+ level messages have been received.
147
+ def has_warnings?
148
+ @has_warnings
149
+ end
150
+
151
+ ##
152
+ # Returns true if any +:error+ level messages have been received.
153
+ def has_errors?
154
+ @has_errors
155
+ end
156
+
157
+ ##
158
+ # The Logger is available as soon as Backup is loaded, and stores all
159
+ # messages it receives. Since the Logger may be configured via the
160
+ # command line and/or the user's +config.rb+, no messages are sent
161
+ # until configuration can be completed. (see CLI#perform)
162
+ #
163
+ # Once configuration is completed, this method is called to activate
164
+ # all enabled loggers and send them any messages that have been received
165
+ # up to this point. From this point onward, these loggers will be sent
166
+ # all messages as soon as they're received.
167
+ def start!
168
+ @config.loggers.each do |logger|
169
+ @loggers << logger.class.new(logger.options) if logger.enabled?
170
+ end
171
+ messages.each do |message|
172
+ @loggers.each {|logger| logger.log(message) }
173
+ end
174
+ end
175
+
176
+ ##
177
+ # If errors are encountered by Backup::CLI while preparing to perform
178
+ # the backup jobs, this method is called to dump all messages to the
179
+ # console before Backup exits.
180
+ def abort!
181
+ console = Console.new
182
+ console.log(messages.shift) until messages.empty?
183
+ end
184
+
185
+ private
186
+
187
+ def log(obj, level)
188
+ message = Message.new(Time.now.utc, level, obj.to_s.split("\n"))
189
+
190
+ message.level = :info if message.level == :warn &&
191
+ message.matches?(@config.ignores)
192
+ @has_warnings ||= message.level == :warn
193
+ @has_errors ||= message.level == :error
194
+
195
+ messages << message
196
+ @loggers.each {|logger| logger.log(message) }
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Logger
5
+ class Console
6
+ class Options
7
+ ##
8
+ # Disables all console output.
9
+ #
10
+ # This may also be set on the command line using +--quiet+.
11
+ #
12
+ # If +--no-quiet+ is used on the command line, console output
13
+ # will be enabled and any setting here will be ignored.
14
+ #
15
+ # @param [Boolean, nil]
16
+ # @return [Boolean, nil] Default: +false+
17
+ attr_reader :quiet
18
+
19
+ def initialize
20
+ @quiet = false
21
+ end
22
+
23
+ def enabled?
24
+ !quiet
25
+ end
26
+
27
+ def quiet=(val)
28
+ @quiet = val unless quiet.nil?
29
+ end
30
+ end
31
+
32
+ COLORS = {
33
+ :info => "\e[32m%s\e[0m", # green
34
+ :warn => "\e[33m%s\e[0m", # yellow
35
+ :error => "\e[31m%s\e[0m" # red
36
+ }
37
+
38
+ def initialize(options = nil)
39
+ $stdout.sync = $stderr.sync = true
40
+ end
41
+
42
+ def log(message)
43
+ io = message.level == :info ? $stdout : $stderr
44
+ lines = message.formatted_lines
45
+ lines.map! {|line| COLORS[message.level] % line } if io.tty?
46
+ io.puts lines
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ # require only the logger
4
+ require 'formatador'
5
+ require 'fog/core/logger'
6
+
7
+ module Backup
8
+ class Logger
9
+ module FogAdapter
10
+ class << self
11
+
12
+ # Logged as :info so these won't generate warnings.
13
+ # This is mostly to keep STDOUT clean and to provide
14
+ # supplemental messages for our own warnings.
15
+ # These will generally occur during retry attempts.
16
+ def write(message)
17
+ Logger.info message.chomp
18
+ end
19
+
20
+ def tty?
21
+ false
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ Fog::Logger[:warning] = Backup::Logger::FogAdapter
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Logger
5
+ class Logfile
6
+ class Error < Backup::Error; end
7
+
8
+ class Options
9
+ ##
10
+ # Enable the use of Backup's log file.
11
+ #
12
+ # While not necessary, as this is +true+ by default,
13
+ # this may also be set on the command line using +--logfile+.
14
+ #
15
+ # The use of Backup's log file may be disabled using the
16
+ # command line option +--no-logfile+.
17
+ #
18
+ # If +--no--logfile+ is used on the command line, then the
19
+ # log file will be disabled and any setting here will be ignored.
20
+ #
21
+ # @param [Boolean, nil]
22
+ # @return [Boolean, nil] Default: +true+
23
+ attr_reader :enabled
24
+
25
+ ##
26
+ # Path to directory where Backup's logfile will be written.
27
+ #
28
+ # This may be given as an absolute path, or a path relative
29
+ # to Backup's +--root-path+ (which defaults to +~/Backup+).
30
+ #
31
+ # This may also be set on the command line using +--log-path+.
32
+ # If set on the command line, any setting here will be ignored.
33
+ #
34
+ # @param [String]
35
+ # @return [String] Default: 'log'
36
+ attr_reader :log_path
37
+
38
+ ##
39
+ # Backup's logfile in which backup logs can be written
40
+ #
41
+ # As there is already a log_path, this can simply be just a file name
42
+ # that will be created (If not exists) on log_path directory
43
+ #
44
+ # This may also be set on the command line using +--log-file+.
45
+ # If set on the command line, any setting here will be ignored.
46
+ #
47
+ # @param [String]
48
+ # @return [String] Default: 'backup.log'
49
+ attr_reader :log_file
50
+
51
+ ##
52
+ # Size in bytes to truncate logfile to before backup jobs are run.
53
+ #
54
+ # This is done once before all +triggers+, so the maximum logfile size
55
+ # would be this value plus whatever the jobs produce.
56
+ #
57
+ # @param [Integer]
58
+ # @return [Integer] Default: +500_000+
59
+ attr_accessor :max_bytes
60
+
61
+ def initialize
62
+ @enabled = true
63
+ @log_path = ''
64
+ @max_bytes = 500_000
65
+ end
66
+
67
+ def enabled?
68
+ !!enabled
69
+ end
70
+
71
+ def enabled=(val)
72
+ @enabled = val unless enabled.nil?
73
+ end
74
+
75
+ def log_path=(val)
76
+ @log_path = val.to_s.strip if log_path.empty?
77
+ end
78
+ end
79
+
80
+ def initialize(options)
81
+ @options = options
82
+ @logfile = setup_logfile
83
+ truncate!
84
+ end
85
+
86
+ def log(message)
87
+ File.open(@logfile, 'a') {|f| f.puts message.formatted_lines }
88
+ end
89
+
90
+ private
91
+
92
+ ##
93
+ # Returns the full path to the log file, based on the configured
94
+ # @options.log_path, and ensures the path to the log file exists.
95
+ def setup_logfile
96
+ # strip any trailing '/' in case the user supplied this as part of
97
+ # an absolute path, so we can match it against File.expand_path()
98
+ path = @options.log_path.chomp('/')
99
+ if path.empty?
100
+ path = File.join(Backup::Config.root_path, 'log')
101
+ elsif path != File.expand_path(path)
102
+ path = File.join(Backup::Config.root_path, path)
103
+ end
104
+ FileUtils.mkdir_p(path)
105
+ log_file = @options.log_file || 'backup.log'
106
+ path = File.join(path, log_file)
107
+ if File.exist?(path) && !File.writable?(path)
108
+ raise Error, "Log File at '#{ path }' is not writable"
109
+ end
110
+ path
111
+ end
112
+
113
+ ##
114
+ # Truncates the logfile to @options.max_bytes
115
+ def truncate!
116
+ return unless File.exist?(@logfile)
117
+
118
+ if File.stat(@logfile).size > @options.max_bytes
119
+ FileUtils.cp(@logfile, @logfile + '~')
120
+ File.open(@logfile + '~', 'r') do |io_in|
121
+ File.open(@logfile, 'w') do |io_out|
122
+ io_in.seek(-@options.max_bytes, IO::SEEK_END) && io_in.gets
123
+ while line = io_in.gets
124
+ io_out.puts line
125
+ end
126
+ end
127
+ end
128
+ FileUtils.rm_f(@logfile + '~')
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end