backup 3.1.3 → 3.2.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.
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