backup 3.0.27 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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