backup 3.1.3 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MDQwZGQzZjE1OWQ2MDAwZjBjMjBkODZiYzRlMTdmZTc0Njc4NzUzOQ==
5
- data.tar.gz: !binary |-
6
- ZDQ1MjVkYjIyNThmZWJkNWFkZjI1MDJiZTAyNTA0MTg1NGZhNzc3Yg==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- NzQwNGViMWM5ZjliYzFhNzVmNGI5NzhhZmFiNDMwYWM5ZTM0NmQ3MmE2NmJm
10
- YTM5MTUwM2QyN2NlMTMxOWM5MWJhMzAwN2M4ZTExYjFkNTdlZjc4MDc2ODVl
11
- NTI1ZGI4OTYzYTVkMTk3ZDFlYjhiNWVkMTZiMzJmZDU5ZTg5M2E=
12
- data.tar.gz: !binary |-
13
- ODUwM2U2MGFkYjQ5MTkwOTQ1Yzg0MWIzNWEwMzdmMDQwYzdhZGNkMmI3NWM5
14
- YzY0MWJiMDA1ZmViZTQxMDVmZTU1ZDAwYmRhZjFhY2ZjZGEzZGFkYWU0ZmQ1
15
- MDEwMWZkOGM3ZGJlY2Q3OWMwOTZhOTNmYzY1NTNjYWViZjU1MTA=
2
+ SHA1:
3
+ metadata.gz: 97b183cb0d0c210153909788775e95a6a686a4cc
4
+ data.tar.gz: 4eb3b105dcdea08f667c8e94625ec9bdf174dc49
5
+ SHA512:
6
+ metadata.gz: d5c1cd997c5e0823022b323b639c7977b7d3f573468c860f0fab496ed619b3b44baae8c56ab8dbe13e9ab0643844dac72be9ac6e79c19ef5859faf2fd05760c5
7
+ data.tar.gz: 07af01b103adf78549d1296145403dded6e8e6d4cea9dbd5b750839ca26f585c32e0c42a0359f46f9ac0bcc57a412e6b6fdb3e8ab5b8b0936f8496109af7ce09
@@ -7,6 +7,7 @@ require 'tempfile'
7
7
  require 'syslog'
8
8
  require 'yaml'
9
9
  require 'etc'
10
+ require 'forwardable'
10
11
 
11
12
  require 'open4'
12
13
  require 'thor'
@@ -3,121 +3,148 @@
3
3
  module Backup
4
4
  class Archive
5
5
  include Backup::Utilities::Helpers
6
+ attr_reader :name, :options
6
7
 
7
8
  ##
8
- # Stores the name of the archive
9
- attr_reader :name
10
-
11
- ##
12
- # Stores an array of different paths/files to store
13
- attr_reader :paths
14
-
15
- ##
16
- # Stores an array of different paths/files to exclude
17
- attr_reader :excludes
18
-
19
- ##
20
- # String of additional arguments for the `tar` command
21
- attr_reader :tar_args
22
-
23
- ##
24
- # Takes the name of the archive and the configuration block
9
+ # Adds a new Archive to a Backup Model.
10
+ #
11
+ # Backup::Model.new(:my_backup, 'My Backup') do
12
+ # archive :my_archive do |archive|
13
+ # archive.add 'path/to/archive'
14
+ # archive.add '/another/path/to/archive'
15
+ # archive.exclude 'path/to/exclude'
16
+ # archive.exclude '/another/path/to/exclude'
17
+ # end
18
+ # end
19
+ #
20
+ # All paths added using `add` or `exclude` will be expanded to their
21
+ # full paths from the root of the filesystem. Files will be added to
22
+ # the tar archive using these full paths, and their leading `/` will
23
+ # be preserved (using tar's `-P` option).
24
+ #
25
+ # /path/to/pwd/path/to/archive/...
26
+ # /another/path/to/archive/...
27
+ #
28
+ # When a `root` path is given, paths to add/exclude are taken as
29
+ # relative to the `root` path, unless given as absolute paths.
30
+ #
31
+ # Backup::Model.new(:my_backup, 'My Backup') do
32
+ # archive :my_archive do |archive|
33
+ # archive.root '~/my_data'
34
+ # archive.add 'path/to/archive'
35
+ # archive.add '/another/path/to/archive'
36
+ # archive.exclude 'path/to/exclude'
37
+ # archive.exclude '/another/path/to/exclude'
38
+ # end
39
+ # end
40
+ #
41
+ # This directs `tar` to change directories to the `root` path to create
42
+ # the archive. Unless paths were given as absolute, the paths within the
43
+ # archive will be relative to the `root` path.
44
+ #
45
+ # path/to/archive/...
46
+ # /another/path/to/archive/...
47
+ #
48
+ # For absolute paths added to this archive, the leading `/` will be
49
+ # preserved. Take note that when archives are extracted, leading `/` are
50
+ # stripped by default, so care must be taken when extracting archives with
51
+ # mixed relative/absolute paths.
25
52
  def initialize(model, name, &block)
26
- @model = model
27
- @name = name.to_s
28
- @paths = Array.new
29
- @excludes = Array.new
30
- @tar_args = ''
31
-
32
- instance_eval(&block) if block_given?
33
- end
34
-
35
- ##
36
- # Adds new paths to the @paths instance variable array
37
- def add(path)
38
- @paths << File.expand_path(path)
39
- end
40
-
41
- ##
42
- # Adds new paths to the @excludes instance variable array
43
- def exclude(path)
44
- @excludes << File.expand_path(path)
53
+ @model = model
54
+ @name = name.to_s
55
+ @options = {
56
+ :root => false,
57
+ :paths => [],
58
+ :excludes => [],
59
+ :tar_options => ''
60
+ }
61
+ DSL.new(@options).instance_eval(&block)
45
62
  end
46
63
 
47
- ##
48
- # Adds the given String of +options+ to the `tar` command.
49
- # e.g. '-h --xattrs'
50
- def tar_options(options)
51
- @tar_args = options
52
- end
53
-
54
- ##
55
- # Archives all the provided paths in to a single .tar file
56
- # and places that .tar file in the folder which later will be packaged
57
- # If the model is configured with a Compressor, the tar command output
58
- # will be piped through the Compressor command and the file extension
59
- # will be adjusted to indicate the type of compression used.
60
64
  def perform!
61
- Logger.info "#{ self.class } has started archiving:\n" +
62
- paths.map {|path| " #{path}" }.join("\n")
65
+ Logger.info "Creating Archive '#{ name }'..."
63
66
 
64
- archive_path = File.join(Config.tmp_path, @model.trigger, 'archives')
65
- FileUtils.mkdir_p(archive_path)
67
+ path = File.join(Config.tmp_path, @model.trigger, 'archives')
68
+ FileUtils.mkdir_p(path)
66
69
 
67
- archive_ext = 'tar'
68
70
  pipeline = Pipeline.new
69
-
70
71
  pipeline.add(
71
- "#{ utility(:tar) } #{ tar_arguments } -cPf - " +
72
+ "#{ utility(:tar) } #{ tar_options } -cPf -#{ tar_root } " +
72
73
  "#{ paths_to_exclude } #{ paths_to_package }",
73
74
  tar_success_codes
74
75
  )
75
76
 
76
- if @model.compressor
77
- @model.compressor.compress_with do |command, ext|
78
- pipeline << command
79
- archive_ext << ext
80
- end
81
- end
77
+ extension = 'tar'
78
+ @model.compressor.compress_with do |command, ext|
79
+ pipeline << command
80
+ extension << ext
81
+ end if @model.compressor
82
82
 
83
83
  pipeline << "#{ utility(:cat) } > " +
84
- "'#{ File.join(archive_path, "#{name}.#{archive_ext}") }'"
84
+ "'#{ File.join(path, "#{ name }.#{ extension }") }'"
85
85
  pipeline.run
86
+
86
87
  if pipeline.success?
87
- Logger.info "#{ self.class } Complete!"
88
+ Logger.info "Archive '#{ name }' Complete!"
88
89
  else
89
90
  raise Errors::Archive::PipelineError,
90
- "Failed to Create Backup Archive\n" +
91
+ "Failed to Create Archive '#{ name }'\n" +
91
92
  pipeline.error_messages
92
93
  end
93
94
  end
94
95
 
95
96
  private
96
97
 
97
- ##
98
- # Returns a "tar-ready" string of all the specified paths combined
98
+ def tar_root
99
+ options[:root] ? " -C '#{ File.expand_path(options[:root]) }'" : ''
100
+ end
101
+
99
102
  def paths_to_package
100
- paths.map {|path| "'#{path}'" }.join(' ')
103
+ options[:paths].map {|path|
104
+ "'#{ prepare_path(path) }'"
105
+ }.join(' ')
101
106
  end
102
107
 
103
- ##
104
- # Returns a "tar-ready" string of all the specified excludes combined
105
108
  def paths_to_exclude
106
- if excludes.any?
107
- excludes.map {|path| "--exclude='#{path}'" }.join(' ')
108
- end
109
+ options[:excludes].map {|path|
110
+ "--exclude='#{ prepare_path(path) }'"
111
+ }.join(' ')
109
112
  end
110
113
 
111
- ##
112
- # Returns arguments for GNU or BSD tar.
113
- def tar_arguments
114
- gnu_tar? ? "--ignore-failed-read #{ tar_args }".strip : tar_args
114
+ def prepare_path(path)
115
+ options[:root] ? path : File.expand_path(path)
116
+ end
117
+
118
+ def tar_options
119
+ args = options[:tar_options]
120
+ gnu_tar? ? "--ignore-failed-read #{ args }".strip : args
115
121
  end
116
122
 
117
- ##
118
- # Returns successful GNU or BSD tar exit codes.
119
123
  def tar_success_codes
120
124
  gnu_tar? ? [0, 1] : [0]
121
125
  end
126
+
127
+ class DSL
128
+ def initialize(options)
129
+ @options = options
130
+ end
131
+
132
+ def root(path)
133
+ @options[:root] = path
134
+ end
135
+
136
+ def add(path)
137
+ @options[:paths] << path
138
+ end
139
+
140
+ def exclude(path)
141
+ @options[:excludes] << path
142
+ end
143
+
144
+ def tar_options(opts)
145
+ @options[:tar_options] = opts
146
+ end
147
+ end
148
+
122
149
  end
123
150
  end
@@ -9,12 +9,27 @@ module Backup
9
9
  ##
10
10
  # [Perform]
11
11
  # Performs the backup process. The only required option is the --trigger [-t].
12
- # If the other options (--config-file, --data-path, --cache-path, --tmp-path) aren't specified
13
- # they will fallback to the (good) defaults
12
+ # If the other options (--config-file, --data-path, --cache-path, --tmp-path)
13
+ # aren't specified they will fallback to the (good) defaults.
14
14
  #
15
15
  # If --root-path is given, it will be used as the base path for our defaults,
16
16
  # as well as the base path for any option specified as a relative path.
17
17
  # Any option given as an absolute path will be used "as-is".
18
+ #
19
+ # If the --check option is given, the config.rb and all model files will be
20
+ # loaded, but no triggers will be run. If the check fails, errors will be
21
+ # reported to the console. If the check passes, a success message will be
22
+ # reported to the console unless --quiet is set. Use --no-quiet to ensure
23
+ # these messages are output. The command will exit with status 0 if
24
+ # successful, or status 1 if there were problems.
25
+ #
26
+ # This command will exit with one of the following status codes:
27
+ #
28
+ # 0: All triggers were successful and no warnings were issued.
29
+ # 1: All triggers were successful, but some had warnings.
30
+ # 2: All triggers were processed, but some failed.
31
+ # 3: A fatal error caused Backup to exit.
32
+ # Some triggers may not have been processed.
18
33
  desc 'perform', "Performs the backup for the specified trigger(s)."
19
34
  long_desc "Performs the backup for the specified trigger(s).\n\n" +
20
35
  "You may perform multiple backups by providing multiple triggers, separated by commas.\n\n" +
@@ -110,13 +125,13 @@ module Backup
110
125
  # Finalize Logger configuration and begin real-time logging.
111
126
  Logger.start!
112
127
 
113
- rescue => err
128
+ rescue Exception => err
114
129
  Logger.error Errors::CLIError.wrap(err)
115
130
  Logger.error 'Configuration Check Failed.' if options[:check]
116
131
  # Logger configuration will be ignored
117
132
  # and messages will be output to the console only.
118
133
  Logger.abort!
119
- exit(1)
134
+ exit(options[:check] ? 1 : 3)
120
135
  end
121
136
 
122
137
  if options[:check]
@@ -128,10 +143,14 @@ module Backup
128
143
  #
129
144
  # Model#perform! handles all exceptions from this point,
130
145
  # as each model may fail and return here to allow others to run.
146
+ warnings = errors = false
131
147
  models.each do |model|
132
148
  model.perform!
149
+ warnings ||= Logger.has_warnings?
150
+ errors ||= Logger.has_errors?
133
151
  Logger.clear!
134
152
  end
153
+ exit(errors ? 2 : 1) if errors || warnings
135
154
  end
136
155
  end
137
156
 
@@ -5,7 +5,7 @@ require 'backup/logger/logfile'
5
5
  require 'backup/logger/syslog'
6
6
 
7
7
  module Backup
8
- module Logger
8
+ class Logger
9
9
 
10
10
  class Config
11
11
  class Logger < Struct.new(:class, :options)
@@ -14,17 +14,22 @@ module Backup
14
14
  end
15
15
  end
16
16
 
17
- DSL = Struct.new(:console, :logfile, :syslog)
17
+ class DSL < Struct.new(:ignores, :console, :logfile, :syslog)
18
+ def ignore_warning(str_or_regexp)
19
+ ignores << str_or_regexp
20
+ end
21
+ end
18
22
 
19
- attr_reader :loggers, :dsl
23
+ attr_reader :ignores, :loggers, :dsl
20
24
 
21
25
  def initialize
26
+ @ignores = []
22
27
  @loggers = [
23
28
  Logger.new(Console, Console::Options.new),
24
29
  Logger.new(Logfile, Logfile::Options.new),
25
30
  Logger.new(Syslog, Syslog::Options.new)
26
31
  ]
27
- @dsl = DSL.new(*@loggers.map(&:options))
32
+ @dsl = DSL.new(ignores, *loggers.map(&:options))
28
33
  end
29
34
  end
30
35
 
@@ -40,13 +45,20 @@ module Backup
40
45
  timestamp = time.strftime("%Y/%m/%d %H:%M:%S")
41
46
  lines.map {|line| "[#{ timestamp }][#{ level }] #{ line }" }
42
47
  end
48
+
49
+ def matches?(ignores)
50
+ text = lines.join("\n")
51
+ ignores.any? {|obj|
52
+ obj.is_a?(Regexp) ? text.match(obj) : text.include?(obj)
53
+ }
54
+ end
43
55
  end
44
56
 
45
57
  class << self
46
- ##
47
- # Returns an Array of Message objects for all logged messages received.
48
- # These are used to attach log files to Mail notifications.
49
- attr_reader :messages
58
+ extend Forwardable
59
+ def_delegators :logger,
60
+ :start!, :abort!, :info, :warn, :error,
61
+ :messages, :has_warnings?, :has_errors?
50
62
 
51
63
  ##
52
64
  # Allows the Logger to be configured.
@@ -69,6 +81,11 @@ module Backup
69
81
  # syslog.info = Syslog::LOG_INFO
70
82
  # syslog.warn = Syslog::LOG_WARNING
71
83
  # syslog.error = Syslog::LOG_ERR
84
+ #
85
+ # # Ignore Warnings:
86
+ # # Converts :warn level messages to level :info
87
+ # ignore_warning 'that contains this string'
88
+ # ignore_warning /that matches this regexp/
72
89
  # end
73
90
  #
74
91
  # See each Logger's Option class for details.
@@ -76,75 +93,102 @@ module Backup
76
93
  # @see Logfile::Options
77
94
  # @see Syslog::Options
78
95
  def configure(&block)
79
- @config.dsl.instance_eval(&block)
96
+ config.dsl.instance_eval(&block)
80
97
  end
81
98
 
82
99
  ##
83
- # Sends a message to the Logger using the specified log level.
84
- # +obj+ may be any Object that responds to #to_s (i.e. an Exception)
85
- [:info, :warn, :error].each do |level|
86
- define_method level, lambda {|obj| log(obj, level) }
100
+ # Called after each backup model/trigger has been performed.
101
+ def clear!
102
+ @logger = nil
103
+ logger.start!
87
104
  end
88
105
 
89
- ##
90
- # Returns true if any +:warn+ level messages have been received.
91
- def has_warnings?
92
- @has_warnings
93
- end
106
+ private
94
107
 
95
- ##
96
- # The Logger is available as soon as Backup is loaded, and stores all
97
- # messages it receives. Since the Logger may be configured via the
98
- # command line and/or the user's +config.rb+, no messages are sent
99
- # until configuration can be completed. (see CLI#perform)
100
- #
101
- # Once configuration is completed, this method is called to activate
102
- # all enabled loggers and send them any messages that have been received
103
- # up to this point. From this point onward, these loggers will be sent
104
- # all messages as soon as they're received.
105
- def start!
106
- @config.loggers.each do |logger|
107
- @loggers << logger.class.new(logger.options) if logger.enabled?
108
- end
109
- @messages.each do |message|
110
- @loggers.each {|logger| logger.log(message) }
111
- end
108
+ def config
109
+ @config ||= Config.new
112
110
  end
113
111
 
114
- ##
115
- # Called after each backup model/trigger has been performed.
116
- def clear!
117
- messages.clear
118
- @has_warnings = false
112
+ def logger
113
+ @logger ||= new(config)
119
114
  end
120
115
 
121
- ##
122
- # If errors are encountered by Backup::CLI while preparing to perform
123
- # the backup jobs, this method is called to dump all messages to the
124
- # console before Backup exits.
125
- def abort!
126
- console = Console.new
127
- console.log(@messages.shift) until @messages.empty?
116
+ def reset!
117
+ @config = @logger = nil
128
118
  end
119
+ end
129
120
 
130
- private
121
+ ##
122
+ # Returns an Array of Message objects for all logged messages received.
123
+ # These are used to attach log files to Mail notifications.
124
+ attr_reader :messages
125
+
126
+ def initialize(config)
127
+ @config = config
128
+ @messages = []
129
+ @loggers = []
130
+ @has_warnings = @has_errors = false
131
+ end
131
132
 
132
- def initialize!
133
- @messages = []
134
- @loggers = []
135
- @config = Config.new
136
- @has_warnings = false
137
- end
133
+ ##
134
+ # Sends a message to the Logger using the specified log level.
135
+ # +obj+ may be any Object that responds to #to_s (i.e. an Exception)
136
+ [:info, :warn, :error].each do |level|
137
+ define_method level, lambda {|obj| log(obj, level) }
138
+ end
139
+
140
+ ##
141
+ # Returns true if any +:warn+ level messages have been received.
142
+ def has_warnings?
143
+ @has_warnings
144
+ end
145
+
146
+ ##
147
+ # Returns true if any +:error+ level messages have been received.
148
+ def has_errors?
149
+ @has_errors
150
+ end
138
151
 
139
- def log(obj, level)
140
- @has_warnings ||= level == :warn
141
- message = Message.new(Time.now, level, obj.to_s.split("\n"))
142
- @messages << message
152
+ ##
153
+ # The Logger is available as soon as Backup is loaded, and stores all
154
+ # messages it receives. Since the Logger may be configured via the
155
+ # command line and/or the user's +config.rb+, no messages are sent
156
+ # until configuration can be completed. (see CLI#perform)
157
+ #
158
+ # Once configuration is completed, this method is called to activate
159
+ # all enabled loggers and send them any messages that have been received
160
+ # up to this point. From this point onward, these loggers will be sent
161
+ # all messages as soon as they're received.
162
+ def start!
163
+ @config.loggers.each do |logger|
164
+ @loggers << logger.class.new(logger.options) if logger.enabled?
165
+ end
166
+ messages.each do |message|
143
167
  @loggers.each {|logger| logger.log(message) }
144
168
  end
169
+ end
145
170
 
171
+ ##
172
+ # If errors are encountered by Backup::CLI while preparing to perform
173
+ # the backup jobs, this method is called to dump all messages to the
174
+ # console before Backup exits.
175
+ def abort!
176
+ console = Console.new
177
+ console.log(messages.shift) until messages.empty?
146
178
  end
147
179
 
148
- initialize!
180
+ private
181
+
182
+ def log(obj, level)
183
+ message = Message.new(Time.now, level, obj.to_s.split("\n"))
184
+
185
+ message.level = :info if message.level == :warn &&
186
+ message.matches?(@config.ignores)
187
+ @has_warnings ||= message.level == :warn
188
+ @has_errors ||= message.level == :error
189
+
190
+ messages << message
191
+ @loggers.each {|logger| logger.log(message) }
192
+ end
149
193
  end
150
194
  end