backup 3.0.27 → 3.1.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 (166) hide show
  1. data/LICENSE.md +1 -1
  2. data/README.md +139 -386
  3. data/bin/backup +1 -7
  4. data/lib/backup.rb +3 -9
  5. data/lib/backup/archive.rb +26 -20
  6. data/lib/backup/cleaner.rb +2 -2
  7. data/lib/backup/cli.rb +366 -0
  8. data/lib/backup/compressor/base.rb +2 -2
  9. data/lib/backup/compressor/gzip.rb +35 -1
  10. data/lib/backup/config.rb +1 -2
  11. data/lib/backup/database/base.rb +2 -2
  12. data/lib/backup/database/mongodb.rb +3 -3
  13. data/lib/backup/database/mysql.rb +3 -2
  14. data/lib/backup/database/postgresql.rb +3 -2
  15. data/lib/backup/database/riak.rb +18 -5
  16. data/lib/backup/dependency.rb +144 -93
  17. data/lib/backup/encryptor/base.rb +2 -2
  18. data/lib/backup/logger.rb +108 -110
  19. data/lib/backup/logger/console.rb +51 -0
  20. data/lib/backup/logger/logfile.rb +113 -0
  21. data/lib/backup/logger/syslog.rb +116 -0
  22. data/lib/backup/model.rb +67 -65
  23. data/lib/backup/notifier/base.rb +1 -1
  24. data/lib/backup/notifier/hipchat.rb +1 -1
  25. data/lib/backup/notifier/mail.rb +1 -1
  26. data/lib/backup/notifier/pushover.rb +6 -3
  27. data/lib/backup/packager.rb +4 -4
  28. data/lib/backup/pipeline.rb +17 -3
  29. data/lib/backup/splitter.rb +2 -2
  30. data/lib/backup/storage/base.rb +2 -2
  31. data/lib/backup/storage/cloudfiles.rb +2 -2
  32. data/lib/backup/storage/dropbox.rb +4 -4
  33. data/lib/backup/storage/ftp.rb +2 -2
  34. data/lib/backup/storage/local.rb +2 -2
  35. data/lib/backup/storage/ninefold.rb +2 -2
  36. data/lib/backup/storage/rsync.rb +3 -3
  37. data/lib/backup/storage/s3.rb +2 -2
  38. data/lib/backup/storage/scp.rb +2 -6
  39. data/lib/backup/storage/sftp.rb +2 -5
  40. data/lib/backup/syncer/base.rb +1 -1
  41. data/lib/backup/syncer/cloud/base.rb +15 -8
  42. data/lib/backup/syncer/rsync/local.rb +1 -1
  43. data/lib/backup/syncer/rsync/pull.rb +1 -1
  44. data/lib/backup/syncer/rsync/push.rb +1 -1
  45. data/lib/backup/utilities.rb +211 -0
  46. data/lib/backup/version.rb +1 -1
  47. data/templates/cli/{utility/archive → archive} +4 -8
  48. data/templates/cli/{utility/compressor → compressor}/bzip2 +0 -0
  49. data/templates/cli/{utility/compressor → compressor}/custom +0 -0
  50. data/templates/cli/{utility/compressor → compressor}/gzip +0 -0
  51. data/templates/cli/{utility/compressor → compressor}/lzma +0 -0
  52. data/templates/cli/{utility/compressor → compressor}/pbzip2 +0 -0
  53. data/templates/cli/config +68 -0
  54. data/templates/cli/{utility/database → database}/mongodb +1 -1
  55. data/templates/cli/{utility/database → database}/mysql +1 -1
  56. data/templates/cli/{utility/database → database}/postgresql +1 -1
  57. data/templates/cli/{utility/database → database}/redis +0 -0
  58. data/templates/cli/database/riak +20 -0
  59. data/templates/cli/{utility/encryptor → encryptor}/gpg +0 -0
  60. data/templates/cli/{utility/encryptor → encryptor}/openssl +0 -0
  61. data/templates/cli/{utility/model.erb → model.erb} +4 -4
  62. data/templates/cli/{utility/notifier → notifier}/campfire +0 -0
  63. data/templates/cli/{utility/notifier → notifier}/hipchat +0 -0
  64. data/templates/cli/{utility/notifier → notifier}/mail +0 -0
  65. data/templates/cli/{utility/notifier → notifier}/prowl +0 -0
  66. data/templates/cli/{utility/notifier → notifier}/pushover +0 -0
  67. data/templates/cli/{utility/notifier → notifier}/twitter +0 -0
  68. data/templates/cli/{utility/splitter → splitter} +0 -0
  69. data/templates/cli/{utility/storage → storage}/cloud_files +0 -0
  70. data/templates/cli/{utility/storage → storage}/dropbox +0 -0
  71. data/templates/cli/{utility/storage → storage}/ftp +0 -0
  72. data/templates/cli/{utility/storage → storage}/local +0 -0
  73. data/templates/cli/{utility/storage → storage}/ninefold +0 -0
  74. data/templates/cli/{utility/storage → storage}/rsync +0 -0
  75. data/templates/cli/{utility/storage → storage}/s3 +0 -0
  76. data/templates/cli/{utility/storage → storage}/scp +0 -0
  77. data/templates/cli/{utility/storage → storage}/sftp +0 -0
  78. data/templates/cli/{utility/syncer → syncer}/cloud_files +0 -0
  79. data/templates/cli/{utility/syncer → syncer}/rsync_local +0 -0
  80. data/templates/cli/{utility/syncer → syncer}/rsync_pull +0 -0
  81. data/templates/cli/{utility/syncer → syncer}/rsync_push +0 -0
  82. data/templates/cli/{utility/syncer → syncer}/s3 +0 -0
  83. metadata +55 -131
  84. data/.gitignore +0 -8
  85. data/.travis.yml +0 -10
  86. data/Gemfile +0 -28
  87. data/Guardfile +0 -23
  88. data/backup.gemspec +0 -32
  89. data/lib/backup/cli/helpers.rb +0 -93
  90. data/lib/backup/cli/utility.rb +0 -255
  91. data/spec-live/.gitignore +0 -6
  92. data/spec-live/README +0 -7
  93. data/spec-live/backups/config.rb +0 -83
  94. data/spec-live/backups/config.yml.template +0 -46
  95. data/spec-live/backups/models.rb +0 -184
  96. data/spec-live/compressor/custom_spec.rb +0 -30
  97. data/spec-live/compressor/gzip_spec.rb +0 -30
  98. data/spec-live/encryptor/gpg_keys.rb +0 -239
  99. data/spec-live/encryptor/gpg_spec.rb +0 -287
  100. data/spec-live/notifier/mail_spec.rb +0 -121
  101. data/spec-live/spec_helper.rb +0 -151
  102. data/spec-live/storage/dropbox_spec.rb +0 -151
  103. data/spec-live/storage/local_spec.rb +0 -83
  104. data/spec-live/storage/scp_spec.rb +0 -193
  105. data/spec-live/syncer/cloud/s3_spec.rb +0 -124
  106. data/spec/archive_spec.rb +0 -335
  107. data/spec/cleaner_spec.rb +0 -312
  108. data/spec/cli/helpers_spec.rb +0 -301
  109. data/spec/cli/utility_spec.rb +0 -411
  110. data/spec/compressor/base_spec.rb +0 -52
  111. data/spec/compressor/bzip2_spec.rb +0 -217
  112. data/spec/compressor/custom_spec.rb +0 -106
  113. data/spec/compressor/gzip_spec.rb +0 -217
  114. data/spec/compressor/lzma_spec.rb +0 -123
  115. data/spec/compressor/pbzip2_spec.rb +0 -165
  116. data/spec/config_spec.rb +0 -321
  117. data/spec/configuration/helpers_spec.rb +0 -247
  118. data/spec/configuration/store_spec.rb +0 -39
  119. data/spec/configuration_spec.rb +0 -62
  120. data/spec/database/base_spec.rb +0 -63
  121. data/spec/database/mongodb_spec.rb +0 -510
  122. data/spec/database/mysql_spec.rb +0 -411
  123. data/spec/database/postgresql_spec.rb +0 -353
  124. data/spec/database/redis_spec.rb +0 -334
  125. data/spec/database/riak_spec.rb +0 -176
  126. data/spec/dependency_spec.rb +0 -51
  127. data/spec/encryptor/base_spec.rb +0 -40
  128. data/spec/encryptor/gpg_spec.rb +0 -909
  129. data/spec/encryptor/open_ssl_spec.rb +0 -148
  130. data/spec/errors_spec.rb +0 -306
  131. data/spec/logger_spec.rb +0 -367
  132. data/spec/model_spec.rb +0 -666
  133. data/spec/notifier/base_spec.rb +0 -104
  134. data/spec/notifier/campfire_spec.rb +0 -217
  135. data/spec/notifier/hipchat_spec.rb +0 -211
  136. data/spec/notifier/mail_spec.rb +0 -316
  137. data/spec/notifier/prowl_spec.rb +0 -138
  138. data/spec/notifier/pushover_spec.rb +0 -123
  139. data/spec/notifier/twitter_spec.rb +0 -153
  140. data/spec/package_spec.rb +0 -61
  141. data/spec/packager_spec.rb +0 -213
  142. data/spec/pipeline_spec.rb +0 -259
  143. data/spec/spec_helper.rb +0 -60
  144. data/spec/splitter_spec.rb +0 -120
  145. data/spec/storage/base_spec.rb +0 -166
  146. data/spec/storage/cloudfiles_spec.rb +0 -254
  147. data/spec/storage/cycler_spec.rb +0 -247
  148. data/spec/storage/dropbox_spec.rb +0 -480
  149. data/spec/storage/ftp_spec.rb +0 -271
  150. data/spec/storage/local_spec.rb +0 -259
  151. data/spec/storage/ninefold_spec.rb +0 -343
  152. data/spec/storage/rsync_spec.rb +0 -362
  153. data/spec/storage/s3_spec.rb +0 -245
  154. data/spec/storage/scp_spec.rb +0 -233
  155. data/spec/storage/sftp_spec.rb +0 -244
  156. data/spec/syncer/base_spec.rb +0 -109
  157. data/spec/syncer/cloud/base_spec.rb +0 -515
  158. data/spec/syncer/cloud/cloud_files_spec.rb +0 -181
  159. data/spec/syncer/cloud/s3_spec.rb +0 -174
  160. data/spec/syncer/rsync/base_spec.rb +0 -98
  161. data/spec/syncer/rsync/local_spec.rb +0 -149
  162. data/spec/syncer/rsync/pull_spec.rb +0 -98
  163. data/spec/syncer/rsync/push_spec.rb +0 -333
  164. data/spec/version_spec.rb +0 -21
  165. data/templates/cli/utility/config +0 -32
  166. data/templates/cli/utility/database/riak +0 -11
@@ -3,7 +3,7 @@
3
3
  module Backup
4
4
  module Compressor
5
5
  class Base
6
- include Backup::CLI::Helpers
6
+ include Backup::Utilities::Helpers
7
7
  include Backup::Configuration::Helpers
8
8
 
9
9
  ##
@@ -25,7 +25,7 @@ module Backup
25
25
  # Logs a message to the console and log file to inform
26
26
  # the client that Backup is using the compressor
27
27
  def log!
28
- Logger.message "Using #{ compressor_name } for compression.\n" +
28
+ Logger.info "Using #{ compressor_name } for compression.\n" +
29
29
  " Command: '#{ @cmd }'\n" +
30
30
  " Ext: '#{ @ext }'"
31
31
  end
@@ -3,6 +3,7 @@
3
3
  module Backup
4
4
  module Compressor
5
5
  class Gzip < Base
6
+ extend Utilities::Helpers
6
7
 
7
8
  ##
8
9
  # Specify the level of compression to use.
@@ -26,12 +27,35 @@ module Backup
26
27
  klass.level = 9 if val
27
28
  }
28
29
 
30
+ ##
31
+ # Use the `--rsyncable` option with `gzip`.
32
+ #
33
+ # This option directs `gzip` to compress data using an algorithm that
34
+ # allows `rsync` to efficiently detect changes. This is especially useful
35
+ # when used to compress `Archive` or `Database` backups that will be
36
+ # stored using Backup's `RSync` Storage option.
37
+ #
38
+ # The `--rsyncable` option is only available on patched versions of `gzip`.
39
+ # While most distributions apply this patch, this option may not be
40
+ # available on your system. If it's not available, Backup will log a
41
+ # warning and continue to use the compressor without this option.
42
+ attr_accessor :rsyncable
43
+
44
+ ##
45
+ # Determine if +--rsyncable+ is supported and cache the result.
46
+ def self.has_rsyncable?
47
+ return @has_rsyncable unless @has_rsyncable.nil?
48
+ cmd = "#{ utility(:gzip) } --rsyncable --version >/dev/null 2>&1; echo $?"
49
+ @has_rsyncable = %x[#{ cmd }].chomp == '0'
50
+ end
51
+
29
52
  ##
30
53
  # Creates a new instance of Backup::Compressor::Gzip
31
54
  def initialize(&block)
32
55
  load_defaults!
33
56
 
34
57
  @level ||= false
58
+ @rsyncable ||= false
35
59
 
36
60
  instance_eval(&block) if block_given?
37
61
 
@@ -42,7 +66,17 @@ module Backup
42
66
  private
43
67
 
44
68
  def options
45
- " -#{ @level }" if @level
69
+ opts = ''
70
+ opts << " -#{ @level }" if @level
71
+ if self.class.has_rsyncable?
72
+ opts << ' --rsyncable'
73
+ else
74
+ Logger.warn Errors::Compressor::Gzip::RsyncableError.new(<<-EOS)
75
+ 'rsyncable' option ignored.
76
+ Your system's 'gzip' does not support the `--rsyncable` option.
77
+ EOS
78
+ end if @rsyncable
79
+ opts
46
80
  end
47
81
 
48
82
  end
data/lib/backup/config.rb CHANGED
@@ -5,14 +5,13 @@ module Backup
5
5
  DEFAULTS = {
6
6
  :config_file => 'config.rb',
7
7
  :data_path => 'data',
8
- :log_path => 'log',
9
8
  :cache_path => '.cache',
10
9
  :tmp_path => '.tmp'
11
10
  }
12
11
 
13
12
  class << self
14
13
  attr_reader :user, :root_path, :config_file,
15
- :data_path, :log_path, :cache_path, :tmp_path
14
+ :data_path, :cache_path, :tmp_path
16
15
 
17
16
  ##
18
17
  # Setup required paths based on the given options
@@ -3,7 +3,7 @@
3
3
  module Backup
4
4
  module Database
5
5
  class Base
6
- include Backup::CLI::Helpers
6
+ include Backup::Utilities::Helpers
7
7
  include Backup::Configuration::Helpers
8
8
 
9
9
  ##
@@ -46,7 +46,7 @@ module Backup
46
46
  # Logs a message to the console and log file to inform
47
47
  # the client that Backup is dumping the database
48
48
  def log!
49
- Logger.message "#{ database_name } started dumping and archiving '#{ name }'."
49
+ Logger.info "#{ database_name } started dumping and archiving '#{ name }'."
50
50
  end
51
51
  end
52
52
  end
@@ -121,7 +121,7 @@ module Backup
121
121
  timestamp = Time.now.to_i.to_s[-5, 5]
122
122
  outfile = @dump_path + '-' + timestamp + '.tar'
123
123
 
124
- Logger.message(
124
+ Logger.info(
125
125
  "#{ database_name } started compressing and packaging:\n" +
126
126
  " '#{ @dump_path }'"
127
127
  )
@@ -131,11 +131,11 @@ module Backup
131
131
  pipeline << command
132
132
  outfile << ext
133
133
  end
134
- pipeline << "cat > #{ outfile }"
135
134
 
135
+ pipeline << "#{ utility(:cat) } > #{ outfile }"
136
136
  pipeline.run
137
137
  if pipeline.success?
138
- Logger.message(
138
+ Logger.info(
139
139
  "#{ database_name } completed compressing and packaging:\n" +
140
140
  " '#{ outfile }'"
141
141
  )
@@ -68,11 +68,12 @@ module Backup
68
68
  dump_ext << ext
69
69
  end
70
70
  end
71
- pipeline << "cat > '#{ File.join(@dump_path, dump_filename) }.#{ dump_ext }'"
72
71
 
72
+ pipeline << "#{ utility(:cat) } > " +
73
+ "'#{ File.join(@dump_path, dump_filename) }.#{ dump_ext }'"
73
74
  pipeline.run
74
75
  if pipeline.success?
75
- Logger.message "#{ database_name } Complete!"
76
+ Logger.info "#{ database_name } Complete!"
76
77
  else
77
78
  raise Errors::Database::PipelineError,
78
79
  "#{ database_name } Dump Failed!\n" +
@@ -68,11 +68,12 @@ module Backup
68
68
  dump_ext << ext
69
69
  end
70
70
  end
71
- pipeline << "cat > '#{ File.join(@dump_path, name) }.#{ dump_ext }'"
72
71
 
72
+ pipeline << "#{ utility(:cat) } > " +
73
+ "'#{ File.join(@dump_path, name) }.#{ dump_ext }'"
73
74
  pipeline.run
74
75
  if pipeline.success?
75
- Logger.message "#{ database_name } Complete!"
76
+ Logger.info "#{ database_name } Complete!"
76
77
  else
77
78
  raise Errors::Database::PipelineError,
78
79
  "#{ database_name } Dump Failed!\n" +
@@ -20,6 +20,14 @@ module Backup
20
20
  # Path to riak-admin utility (optional)
21
21
  attr_accessor :riak_admin_utility
22
22
 
23
+ ##
24
+ # Username for the riak instance (optional)
25
+ attr_accessor :user
26
+
27
+ ##
28
+ # Group for the riak instance (optional)
29
+ attr_accessor :group
30
+
23
31
  attr_deprecate :utility_path, :version => '3.0.21',
24
32
  :message => 'Use Riak#riak_admin_utility instead.',
25
33
  :action => lambda {|klass, val| klass.riak_admin_utility = val }
@@ -32,22 +40,27 @@ module Backup
32
40
  instance_eval(&block) if block_given?
33
41
 
34
42
  @riak_admin_utility ||= utility('riak-admin')
43
+ @user ||= 'riak'
44
+ @group ||= 'riak'
35
45
  end
36
46
 
37
47
  ##
38
- # Performs the riak-admin command and outputs the
39
- # data to the specified path based on the 'trigger'
48
+ # Performs the `riak-admin` command which creates a single dump file in
49
+ # @dump_path based on the `name` and `node`.
50
+ #
51
+ # `riak-admin` will append the `node` to the filename.
52
+ # i.e. <tmp_path>/<trigger>/databases/Riak/<name>-<node>
40
53
  def perform!
41
54
  super
42
- # have to make riak the owner since the riak-admin tool runs
43
- # as the riak user in a default setup.
44
- FileUtils.chown_R('riak', 'riak', @dump_path)
55
+ # ensure riak-admin user has permissions to write backup file
56
+ FileUtils.chown_R(@user, @group, @dump_path)
45
57
 
46
58
  backup_file = File.join(@dump_path, name)
47
59
  run("#{ riakadmin } #{ backup_file } node")
48
60
 
49
61
  if @model.compressor
50
62
  @model.compressor.compress_with do |command, ext|
63
+ backup_file << "-#{ node }"
51
64
  run("#{ command } -c #{ backup_file } > #{ backup_file + ext }")
52
65
  FileUtils.rm_f(backup_file)
53
66
  end
@@ -1,108 +1,159 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Backup
4
-
5
4
  ##
6
5
  # A little self-contained gem manager for Backup.
7
- # Rather than specifying hard dependencies in the gemspec, forcing users
8
- # to install gems they do not want/need, Backup will notify them when a gem
9
- # has not been installed, or when the gem's version is incorrect, and provide the
10
- # command to install the gem. These dependencies are dynamically loaded in the Gemfile
6
+ # Rather than specifying hard dependencies in the gemspec, forcing users to
7
+ # install gems they do not want/need, Backup will notify them when a gem has
8
+ # not been installed, or version is incorrect, and provide the command to
9
+ # install the gem. These dependencies are dynamically loaded in the Gemfile.
11
10
  class Dependency
11
+ DEPENDENCIES = {
12
+ 'fog' => {
13
+ :require => 'fog',
14
+ :version => '~> 1.4',
15
+ :for => 'Amazon S3, Rackspace Cloud Files (S3, CloudFiles Storages)',
16
+ :dependencies => ['net-ssh', 'net-scp']
17
+ },
18
+
19
+ 'dropbox-sdk' => {
20
+ :require => 'dropbox_sdk',
21
+ :version => '~> 1.5.0',
22
+ :for => 'Dropbox Web Service (Dropbox Storage)'
23
+ },
24
+
25
+ 'net-sftp' => {
26
+ :require => 'net/sftp',
27
+ :version => ['>= 2.0.0', '<= 2.0.5'],
28
+ :for => 'SFTP Protocol (SFTP Storage)',
29
+ :dependencies => 'net-ssh'
30
+ },
31
+
32
+ 'net-scp' => {
33
+ :require => 'net/scp',
34
+ :version => ['>= 1.0.0', '<= 1.0.4'],
35
+ :for => 'SCP Protocol (SCP Storage)',
36
+ :dependencies => 'net-ssh'
37
+ },
38
+
39
+ 'net-ssh' => {
40
+ :require => 'net/ssh',
41
+ :version => ['>= 2.3.0', '<= 2.5.2'],
42
+ :for => 'SSH Protocol (SSH Storage)'
43
+ },
44
+
45
+ 'mail' => {
46
+ :require => 'mail',
47
+ :version => '~> 2.5.0',
48
+ :for => 'Sending Emails (Mail Notifier)'
49
+ },
50
+
51
+ 'twitter' => {
52
+ :require => 'twitter',
53
+ :version => '~> 4.0',
54
+ :for => 'Sending Twitter Updates (Twitter Notifier)'
55
+ },
12
56
 
13
- ##
14
- # Returns a hash of dependencies that Backup requires
15
- # in order to run every available feature
16
- def self.all
17
- {
18
- 'fog' => {
19
- :require => 'fog',
20
- :version => '~> 1.4.0',
21
- :for => 'Amazon S3, Rackspace Cloud Files (S3, CloudFiles Storages)'
22
- },
23
-
24
- 'dropbox-sdk' => {
25
- :require => 'dropbox_sdk',
26
- :version => '~> 1.2.0',
27
- :for => 'Dropbox Web Service (Dropbox Storage)'
28
- },
29
-
30
- 'net-sftp' => {
31
- :require => 'net/sftp',
32
- :version => '~> 2.0.5',
33
- :for => 'SFTP Protocol (SFTP Storage)'
34
- },
35
-
36
- 'net-scp' => {
37
- :require => 'net/scp',
38
- :version => '~> 1.0.4',
39
- :for => 'SCP Protocol (SCP Storage)'
40
- },
41
-
42
- 'net-ssh' => {
43
- :require => 'net/ssh',
44
- :version => '~> 2.3.0',
45
- :for => 'SSH Protocol (SSH Storage)'
46
- },
47
-
48
- 'mail' => {
49
- :require => 'mail',
50
- :version => '~> 2.4.0',
51
- :for => 'Sending Emails (Mail Notifier)'
52
- },
53
-
54
- 'twitter' => {
55
- :require => 'twitter',
56
- :version => '>= 1.7.1',
57
- :for => 'Sending Twitter Updates (Twitter Notifier)'
58
- },
59
-
60
- 'httparty' => {
61
- :require => 'httparty',
62
- :version => '~> 0.8.1',
63
- :for => 'Sending Http Updates'
64
- },
65
-
66
- 'prowler' => {
67
- :require => 'prowler',
68
- :version => '>= 1.3.1',
69
- :for => 'Sending iOS push notifications (Prowl Notifier)'
70
- },
71
-
72
- 'hipchat' => {
73
- :require => 'hipchat',
74
- :version => '~> 0.4.1',
75
- :for => 'Sending notifications to Hipchat'
76
- },
77
-
78
- 'parallel' => {
79
- :require => 'parallel',
80
- :version => '~> 0.5.12',
81
- :for => 'Adding concurrency to Cloud-based syncers.'
82
- }
57
+ 'httparty' => {
58
+ :require => 'httparty',
59
+ :version => '~> 0.10.2',
60
+ :for => 'Sending Http Updates (Campfire Notifier)'
61
+ },
62
+
63
+ 'prowler' => {
64
+ :require => 'prowler',
65
+ :version => '~> 1.3.1',
66
+ :for => 'Sending iOS push notifications (Prowl Notifier)'
67
+ },
68
+
69
+ 'hipchat' => {
70
+ :require => 'hipchat',
71
+ :version => '~> 0.7.0',
72
+ :for => 'Sending notifications to Hipchat'
73
+ },
74
+
75
+ 'parallel' => {
76
+ :require => 'parallel',
77
+ :version => '~> 0.6.0',
78
+ :for => 'Adding concurrency to Cloud-based syncers.'
83
79
  }
80
+ }
81
+
82
+ class << self
83
+ def all
84
+ @all ||= []
85
+ end
86
+
87
+ def find(name)
88
+ all.select {|dep| dep.name == name }.first
89
+ end
90
+
91
+ def load(name)
92
+ find(name).load!
93
+ end
94
+ end
95
+
96
+ attr_reader :name, :require_as, :used_for, :requirements
97
+
98
+ def initialize(name, options = {})
99
+ @name = name
100
+ @require_as = options[:require]
101
+ @requirements = Array(options[:version])
102
+ @dependencies = Array(options[:dependencies])
103
+ @used_for = options[:for]
104
+ end
105
+
106
+ # dependencies should be defined in the order
107
+ # they should be installed or loaded.
108
+ def dependencies
109
+ @dependencies.map {|name| self.class.find(name) }
110
+ end
111
+
112
+ def load!
113
+ dependencies.each(&:load!)
114
+
115
+ gem(name, *requirements)
116
+ require require_as
117
+ rescue LoadError
118
+ raise Errors::Dependency::LoadError, <<-EOS
119
+ Dependency Missing
120
+ Gem Name: #{ name }
121
+ Used for: #{ used_for }
122
+
123
+ To install the gem, issue the following command:
124
+ > backup dependencies --install #{ name }
125
+ Please try again after installing the missing dependency.
126
+ EOS
127
+ end
128
+
129
+ def installed?
130
+ Gem::Specification.find_by_name(name, *requirements)
131
+ true
132
+ rescue LoadError
133
+ false
84
134
  end
85
135
 
86
- ##
87
- # Attempts to load the specified gem (by name and version).
88
- # If the gem with the correct version cannot be found, it'll display a message
89
- # to the user with instructions on how to install the required gem
90
- def self.load(name)
91
- begin
92
- gem(name, all[name][:version])
93
- require(all[name][:require])
94
- rescue LoadError
95
- Logger.error Errors::Dependency::LoadError.new(<<-EOS)
96
- Dependency missing
97
- Dependency required for:
98
- #{all[name][:for]}
99
- To install the gem, issue the following command:
100
- > gem install #{name} -v '#{all[name][:version]}'
101
- Please try again after installing the missing dependency.
102
- EOS
103
- exit 1
136
+ # If multiple version requirements are defined, this tries to find the
137
+ # version that matches them all. Otherwise, it will fallback to installing
138
+ # based on the last requirement. This should only be called from the CLI
139
+ # for `backup dependencies --install <name>`.
140
+ def install!
141
+ version = nil
142
+ if requirements.count > 1
143
+ begin
144
+ require 'rubygems/dependency_installer'
145
+ inst = Gem::DependencyInstaller.new
146
+ spec, _ = inst.find_spec_by_name_and_version(name, *requirements).first
147
+ version = spec.version
148
+ rescue
149
+ end
104
150
  end
151
+ version ||= requirements.last
152
+ command = "gem install --no-ri --no-rdoc #{ name } -v '#{ version }'"
153
+ puts "\nLaunching `#{ command }`"
154
+ exec command
105
155
  end
106
156
 
157
+ DEPENDENCIES.each {|name, options| all << new(name, options) }
107
158
  end
108
159
  end