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,58 @@
1
+ # encoding: utf-8
2
+ require 'twitter'
3
+
4
+ module Backup
5
+ module Notifier
6
+ class Twitter < Base
7
+
8
+ ##
9
+ # Twitter consumer key credentials
10
+ attr_accessor :consumer_key, :consumer_secret
11
+
12
+ ##
13
+ # OAuth credentials
14
+ attr_accessor :oauth_token, :oauth_token_secret
15
+
16
+ def initialize(model, &block)
17
+ super
18
+ instance_eval(&block) if block_given?
19
+ end
20
+
21
+ private
22
+
23
+ ##
24
+ # Notify the user of the backup operation results.
25
+ #
26
+ # `status` indicates one of the following:
27
+ #
28
+ # `:success`
29
+ # : The backup completed successfully.
30
+ # : Notification will be sent if `on_success` is `true`.
31
+ #
32
+ # `:warning`
33
+ # : The backup completed successfully, but warnings were logged.
34
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
35
+ #
36
+ # `:failure`
37
+ # : The backup operation failed.
38
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
39
+ #
40
+ def notify!(status)
41
+ send_message(message.call(model, :status => status_data_for(status)))
42
+ end
43
+
44
+ # Twitter::Client will raise an error if unsuccessful.
45
+ def send_message(message)
46
+ client = ::Twitter::REST::Client.new do |config|
47
+ config.consumer_key = @consumer_key
48
+ config.consumer_secret = @consumer_secret
49
+ config.access_token = @oauth_token
50
+ config.access_token_secret = @oauth_token_secret
51
+ end
52
+
53
+ client.update(message)
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Notifier
5
+ class Zabbix < Base
6
+
7
+ attr_accessor :zabbix_host
8
+
9
+ attr_accessor :zabbix_port
10
+
11
+ attr_accessor :service_name
12
+
13
+ attr_accessor :service_host
14
+
15
+ attr_accessor :item_key
16
+
17
+ def initialize(model, &block)
18
+ super
19
+ instance_eval(&block) if block_given?
20
+
21
+ @zabbix_host ||= Config.hostname
22
+ @zabbix_port ||= 10051
23
+ @service_name ||= "Backup #{ model.trigger }"
24
+ @service_host ||= Config.hostname
25
+ @item_key ||= 'backup_status'
26
+ end
27
+
28
+ private
29
+
30
+ ##
31
+ # Notify the user of the backup operation results.
32
+ #
33
+ # `status` indicates one of the following:
34
+ #
35
+ # `:success`
36
+ # : The backup completed successfully.
37
+ # : Notification will be sent if `on_success` is `true`.
38
+ #
39
+ # `:warning`
40
+ # : The backup completed successfully, but warnings were logged.
41
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
42
+ #
43
+ # `:failure`
44
+ # : The backup operation failed.
45
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
46
+ #
47
+ def notify!(status)
48
+ send_message(message.call(model, :status => status_data_for(status)))
49
+ end
50
+
51
+ def send_message(message)
52
+ msg = [service_host, service_name, model.exit_status, message].join("\t")
53
+ cmd = "#{ utility(:zabbix_sender) }" +
54
+ " -z '#{ zabbix_host }'" +
55
+ " -p '#{ zabbix_port }'" +
56
+ " -s #{ service_host }" +
57
+ " -k #{ item_key }" +
58
+ " -o '#{ msg }'"
59
+ run("echo '#{ msg }' | #{ cmd }")
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Package
5
+
6
+ ##
7
+ # The time when the backup initiated (in format: 2011.02.20.03.29.59)
8
+ attr_accessor :time
9
+
10
+ ##
11
+ # The trigger which initiated the backup process
12
+ attr_reader :trigger
13
+
14
+ ##
15
+ # Extension for the final archive file(s)
16
+ attr_accessor :extension
17
+
18
+ ##
19
+ # Set by the Splitter if the final archive was "chunked"
20
+ attr_accessor :chunk_suffixes
21
+
22
+ ##
23
+ # If true, the Cycler will not attempt to remove the package when Cycling.
24
+ attr_accessor :no_cycle
25
+
26
+ ##
27
+ # The version of Backup used to create the package
28
+ attr_reader :version
29
+
30
+ def initialize(model)
31
+ @trigger = model.trigger
32
+ @extension = 'tar'
33
+ @chunk_suffixes = Array.new
34
+ @no_cycle = false
35
+ @version = VERSION
36
+ end
37
+
38
+ def filenames
39
+ if chunk_suffixes.empty?
40
+ [basename]
41
+ else
42
+ chunk_suffixes.map {|suffix| "#{ basename }-#{ suffix }" }
43
+ end
44
+ end
45
+
46
+ def basename
47
+ "#{ trigger }.#{ extension }"
48
+ end
49
+
50
+ def time_as_object
51
+ Time.strptime(time, '%Y.%m.%d.%H.%M.%S')
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,107 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Packager
5
+ class Error < Backup::Error; end
6
+
7
+ class << self
8
+ include Utilities::Helpers
9
+
10
+ ##
11
+ # Build the final package for the backup model.
12
+ def package!(model)
13
+ @package = model.package
14
+ @encryptor = model.encryptor
15
+ @splitter = model.splitter
16
+ @pipeline = Pipeline.new
17
+
18
+ Logger.info "Packaging the backup files..."
19
+ procedure.call
20
+
21
+ if @pipeline.success?
22
+ Logger.info "Packaging Complete!"
23
+ else
24
+ raise Error, "Failed to Create Backup Package\n" +
25
+ @pipeline.error_messages
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ ##
32
+ # Builds a chain of nested Procs which adds each command to a Pipeline
33
+ # needed to package the final command to package the backup.
34
+ # This is done so that the Encryptor and Splitter have the ability
35
+ # to perform actions before and after the final command is executed.
36
+ # No Encryptors currently utilize this, however the Splitter does.
37
+ def procedure
38
+ stack = []
39
+
40
+ ##
41
+ # Initial `tar` command to package the temporary backup folder.
42
+ # The command's output will then be either piped to the Encryptor
43
+ # or the Splitter (if no Encryptor), or through `cat` into the final
44
+ # output file if neither are configured.
45
+ @pipeline.add(
46
+ "#{ utility(:tar) } -cf - " +
47
+ "-C '#{ Config.tmp_path }' '#{ @package.trigger }'",
48
+ tar_success_codes
49
+ )
50
+
51
+ ##
52
+ # If an Encryptor was configured, it will be called first
53
+ # to add the encryption utility command to be piped through,
54
+ # and amend the final package extension.
55
+ # It's output will then be either piped into a Splitter,
56
+ # or through `cat` into the final output file.
57
+ if @encryptor
58
+ stack << lambda do
59
+ @encryptor.encrypt_with do |command, ext|
60
+ @pipeline << command
61
+ @package.extension << ext
62
+ stack.shift.call
63
+ end
64
+ end
65
+ end
66
+
67
+ ##
68
+ # If a Splitter was configured, the `split` utility command will be
69
+ # added to the Pipeline to split the final output into multiple files.
70
+ # Once the Proc executing the Pipeline has completed and returns back
71
+ # to the Splitter, it will check the final output files to determine
72
+ # if the backup was indeed split.
73
+ # If so, it will set the package's chunk_suffixes. If not, it will
74
+ # remove the '-aa' suffix from the only file created by `split`.
75
+ #
76
+ # If no Splitter was configured, the final file output will be
77
+ # piped through `cat` into the final output file.
78
+ if @splitter
79
+ stack << lambda do
80
+ @splitter.split_with do |command|
81
+ @pipeline << command
82
+ stack.shift.call
83
+ end
84
+ end
85
+ else
86
+ stack << lambda do
87
+ outfile = File.join(Config.tmp_path, @package.basename)
88
+ @pipeline << "#{ utility(:cat) } > #{ outfile }"
89
+ stack.shift.call
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Last Proc to be called runs the Pipeline the procedure built.
95
+ # Once complete, the call stack will unwind back through the
96
+ # preceeding Procs in the stack (if any)
97
+ stack << lambda { @pipeline.run }
98
+
99
+ stack.shift
100
+ end
101
+
102
+ def tar_success_codes
103
+ gnu_tar? ? [0, 1] : [0]
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,124 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Pipeline
5
+ class Error < Backup::Error; end
6
+
7
+ include Utilities::Helpers
8
+
9
+ attr_reader :stderr, :errors
10
+
11
+ def initialize
12
+ @commands = []
13
+ @success_codes = []
14
+ @errors = []
15
+ @stderr = ''
16
+ end
17
+
18
+ ##
19
+ # Adds a command to be executed in the pipeline.
20
+ # Each command will be run in the order in which it was added,
21
+ # with it's output being piped to the next command.
22
+ #
23
+ # +success_codes+ must be an Array of Integer exit codes that will
24
+ # be considered successful for the +command+.
25
+ def add(command, success_codes)
26
+ @commands << command
27
+ @success_codes << success_codes
28
+ end
29
+
30
+ ##
31
+ # Commands added using this method will only be considered successful
32
+ # if their exit status is 0.
33
+ #
34
+ # Use #add if successful exit status codes need to be specified.
35
+ def <<(command)
36
+ add(command, [0])
37
+ end
38
+
39
+ ##
40
+ # Runs the command line from `#pipeline` and collects STDOUT/STDERR.
41
+ # STDOUT is then parsed to determine the exit status of each command.
42
+ # For each command with a non-zero exit status, a SystemCallError is
43
+ # created and added to @errors. All STDERR output is set in @stderr.
44
+ #
45
+ # Note that there is no accumulated STDOUT from the commands themselves.
46
+ # Also, the last command should not attempt to write to STDOUT.
47
+ # Any output on STDOUT from the final command will be sent to STDERR.
48
+ # This in itself will not cause #run to fail, but will log warnings
49
+ # when all commands exit with non-zero status.
50
+ #
51
+ # Use `#success?` to determine if all commands in the pipeline succeeded.
52
+ # If `#success?` returns `false`, use `#error_messages` to get an error report.
53
+ def run
54
+ Open4.popen4(pipeline) do |pid, stdin, stdout, stderr|
55
+ pipestatus = stdout.read.gsub("\n", '').split(':').sort
56
+ pipestatus.each do |status|
57
+ index, exitstatus = status.split('|').map(&:to_i)
58
+ unless @success_codes[index].include?(exitstatus)
59
+ command = command_name(@commands[index])
60
+ @errors << SystemCallError.new(
61
+ "'#{ command }' returned exit code: #{ exitstatus }", exitstatus
62
+ )
63
+ end
64
+ end
65
+ @stderr = stderr.read.strip
66
+ end
67
+ Logger.warn(stderr_messages) if success? && stderr_messages
68
+ rescue Exception => err
69
+ raise Error.wrap(err, 'Pipeline failed to execute')
70
+ end
71
+
72
+ def success?
73
+ @errors.empty?
74
+ end
75
+
76
+ ##
77
+ # Returns a multi-line String, reporting all STDERR messages received
78
+ # from the commands in the pipeline (if any), along with the SystemCallError
79
+ # (Errno) message for each command which had a non-zero exit status.
80
+ def error_messages
81
+ @error_messages ||= (stderr_messages || '') +
82
+ "The following system errors were returned:\n" +
83
+ @errors.map {|err| "#{ err.class }: #{ err.message }" }.join("\n")
84
+ end
85
+
86
+ private
87
+
88
+ ##
89
+ # Each command is added as part of the pipeline, grouped with an `echo`
90
+ # command to pass along the command's index in @commands and it's exit status.
91
+ # The command's STDERR is redirected to FD#4, and the `echo` command to
92
+ # report the "index|exit status" is redirected to FD#3.
93
+ # Each command's STDOUT will be connected to the STDIN of the next subshell.
94
+ # The entire pipeline is run within a container group, which redirects
95
+ # FD#3 to STDOUT and FD#4 to STDERR so these can be collected.
96
+ # FD#1 is redirected to STDERR so that any output from the final command
97
+ # on STDOUT will generate warnings, since the final command should not
98
+ # attempt to write to STDOUT, as this would interfere with collecting
99
+ # the exit statuses.
100
+ #
101
+ # There is no guarantee as to the order of this output, which is why the
102
+ # command's index in @commands is passed along with it's exit status.
103
+ # And, if multiple commands output messages on STDERR, those messages
104
+ # may be interleaved. Interleaving of the "index|exit status" outputs
105
+ # should not be an issue, given the small byte size of the data being written.
106
+ def pipeline
107
+ parts = []
108
+ @commands.each_with_index do |command, index|
109
+ parts << %Q[{ #{ command } 2>&4 ; echo "#{ index }|$?:" >&3 ; }]
110
+ end
111
+ %Q[{ #{ parts.join(' | ') } } 3>&1 1>&2 4>&2]
112
+ end
113
+
114
+ def stderr_messages
115
+ @stderr_messages ||= @stderr.empty? ? false : <<-EOS.gsub(/^ +/, ' ')
116
+ Pipeline STDERR Messages:
117
+ (Note: may be interleaved if multiple commands returned error messages)
118
+
119
+ #{ @stderr }
120
+ EOS
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Splitter
5
+ include Utilities::Helpers
6
+
7
+ attr_reader :package, :chunk_size, :suffix_length
8
+
9
+ def initialize(model, chunk_size, suffix_length)
10
+ @package = model.package
11
+ @chunk_size = chunk_size
12
+ @suffix_length = suffix_length
13
+ end
14
+
15
+ ##
16
+ # This is called as part of the procedure used to build the final
17
+ # backup package file(s). It yields it's portion of the command line
18
+ # for this procedure, which will split the data being piped into it
19
+ # into multiple files, based on the @chunk_size, using a suffix length as
20
+ # specified by @suffix_length.
21
+ # Once the packaging procedure is complete, it will return and
22
+ # @package.chunk_suffixes will be set based on the resulting files.
23
+ def split_with
24
+ Logger.info "Splitter configured with a chunk size of #{ chunk_size }MB " +
25
+ "and suffix length of #{ suffix_length }."
26
+ yield split_command
27
+ after_packaging
28
+ end
29
+
30
+ private
31
+
32
+ ##
33
+ # The `split` command reads from $stdin and will store it's output in
34
+ # multiple files, based on @chunk_size and @suffix_length, using the full
35
+ # path to the final @package.basename, plus a '-' separator as the `prefix`.
36
+ def split_command
37
+ "#{ utility(:split) } -a #{ suffix_length } -b #{ chunk_size }m - " +
38
+ "'#{ File.join(Config.tmp_path, package.basename + '-') }'"
39
+ end
40
+
41
+ ##
42
+ # Finds the resulting files from the packaging procedure
43
+ # and stores an Array of suffixes used in @package.chunk_suffixes.
44
+ # If the @chunk_size was never reached and only one file
45
+ # was written, that file will be suffixed with '-aa' (or -a; -aaa; etc
46
+ # depending upon suffix_length). In which case, it will simply
47
+ # remove the suffix from the filename.
48
+ def after_packaging
49
+ suffixes = chunk_suffixes
50
+ first_suffix = 'a' * suffix_length
51
+ if suffixes == [first_suffix]
52
+ FileUtils.mv(
53
+ File.join(Config.tmp_path, "#{ package.basename }-#{ first_suffix }"),
54
+ File.join(Config.tmp_path, package.basename)
55
+ )
56
+ else
57
+ package.chunk_suffixes = suffixes
58
+ end
59
+ end
60
+
61
+ ##
62
+ # Returns an array of suffixes for each chunk, in alphabetical order.
63
+ # For example: [aa, ab, ac, ad, ae] or [aaa, aab, aac aad]
64
+ def chunk_suffixes
65
+ chunks.map {|chunk| File.extname(chunk).split('-').last }.sort
66
+ end
67
+
68
+ ##
69
+ # Returns an array of full paths to the backup chunks.
70
+ # Chunks are sorted in alphabetical order.
71
+ def chunks
72
+ Dir[File.join(Config.tmp_path, package.basename + '-*')].sort
73
+ end
74
+
75
+ end
76
+ end