backup 3.0.19 → 3.0.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +9 -8
  3. data/Gemfile.lock +19 -1
  4. data/Guardfile +13 -9
  5. data/README.md +93 -31
  6. data/backup.gemspec +3 -3
  7. data/bin/backup +6 -283
  8. data/lib/backup.rb +101 -72
  9. data/lib/backup/archive.rb +21 -9
  10. data/lib/backup/binder.rb +22 -0
  11. data/lib/backup/cleaner.rb +36 -0
  12. data/lib/backup/cli/helpers.rb +103 -0
  13. data/lib/backup/cli/utility.rb +308 -0
  14. data/lib/backup/compressor/base.rb +2 -2
  15. data/lib/backup/compressor/pbzip2.rb +76 -0
  16. data/lib/backup/configuration/compressor/pbzip2.rb +28 -0
  17. data/lib/backup/configuration/database/riak.rb +25 -0
  18. data/lib/backup/configuration/encryptor/open_ssl.rb +6 -0
  19. data/lib/backup/configuration/helpers.rb +5 -18
  20. data/lib/backup/configuration/notifier/base.rb +13 -0
  21. data/lib/backup/configuration/notifier/hipchat.rb +41 -0
  22. data/lib/backup/configuration/notifier/mail.rb +38 -0
  23. data/lib/backup/configuration/notifier/prowl.rb +23 -0
  24. data/lib/backup/configuration/storage/cloudfiles.rb +4 -0
  25. data/lib/backup/configuration/storage/dropbox.rb +8 -4
  26. data/lib/backup/database/base.rb +10 -2
  27. data/lib/backup/database/mongodb.rb +16 -19
  28. data/lib/backup/database/mysql.rb +2 -2
  29. data/lib/backup/database/postgresql.rb +2 -2
  30. data/lib/backup/database/redis.rb +15 -7
  31. data/lib/backup/database/riak.rb +45 -0
  32. data/lib/backup/dependency.rb +21 -7
  33. data/lib/backup/encryptor/base.rb +1 -1
  34. data/lib/backup/encryptor/open_ssl.rb +20 -5
  35. data/lib/backup/errors.rb +124 -0
  36. data/lib/backup/finder.rb +11 -3
  37. data/lib/backup/logger.rb +121 -82
  38. data/lib/backup/model.rb +103 -44
  39. data/lib/backup/notifier/base.rb +50 -0
  40. data/lib/backup/notifier/campfire.rb +32 -52
  41. data/lib/backup/notifier/hipchat.rb +99 -0
  42. data/lib/backup/notifier/mail.rb +100 -61
  43. data/lib/backup/notifier/presently.rb +31 -40
  44. data/lib/backup/notifier/prowl.rb +73 -0
  45. data/lib/backup/notifier/twitter.rb +29 -39
  46. data/lib/backup/packager.rb +25 -0
  47. data/lib/backup/splitter.rb +62 -0
  48. data/lib/backup/storage/base.rb +178 -18
  49. data/lib/backup/storage/cloudfiles.rb +34 -28
  50. data/lib/backup/storage/dropbox.rb +64 -67
  51. data/lib/backup/storage/ftp.rb +48 -40
  52. data/lib/backup/storage/local.rb +33 -28
  53. data/lib/backup/storage/ninefold.rb +40 -26
  54. data/lib/backup/storage/object.rb +8 -6
  55. data/lib/backup/storage/rsync.rb +61 -51
  56. data/lib/backup/storage/s3.rb +29 -27
  57. data/lib/backup/storage/scp.rb +56 -36
  58. data/lib/backup/storage/sftp.rb +49 -33
  59. data/lib/backup/syncer/base.rb +1 -1
  60. data/lib/backup/syncer/rsync.rb +1 -1
  61. data/lib/backup/template.rb +46 -0
  62. data/lib/backup/version.rb +1 -1
  63. data/spec/archive_spec.rb +34 -9
  64. data/spec/backup_spec.rb +1 -1
  65. data/spec/cli/helpers_spec.rb +35 -0
  66. data/spec/cli/utility_spec.rb +38 -0
  67. data/spec/compressor/bzip2_spec.rb +1 -1
  68. data/spec/compressor/gzip_spec.rb +1 -1
  69. data/spec/compressor/lzma_spec.rb +1 -1
  70. data/spec/compressor/pbzip2_spec.rb +63 -0
  71. data/spec/configuration/base_spec.rb +1 -1
  72. data/spec/configuration/compressor/bzip2_spec.rb +1 -1
  73. data/spec/configuration/compressor/gzip_spec.rb +1 -1
  74. data/spec/configuration/compressor/lzma_spec.rb +1 -1
  75. data/spec/configuration/database/base_spec.rb +1 -1
  76. data/spec/configuration/database/mongodb_spec.rb +1 -1
  77. data/spec/configuration/database/mysql_spec.rb +1 -1
  78. data/spec/configuration/database/postgresql_spec.rb +1 -1
  79. data/spec/configuration/database/redis_spec.rb +1 -1
  80. data/spec/configuration/database/riak_spec.rb +31 -0
  81. data/spec/configuration/encryptor/gpg_spec.rb +1 -1
  82. data/spec/configuration/encryptor/open_ssl_spec.rb +4 -1
  83. data/spec/configuration/notifier/campfire_spec.rb +1 -1
  84. data/spec/configuration/notifier/hipchat_spec.rb +43 -0
  85. data/spec/configuration/notifier/mail_spec.rb +34 -22
  86. data/spec/configuration/notifier/presently_spec.rb +1 -1
  87. data/spec/configuration/notifier/prowl_spec.rb +28 -0
  88. data/spec/configuration/notifier/twitter_spec.rb +1 -1
  89. data/spec/configuration/storage/cloudfiles_spec.rb +19 -16
  90. data/spec/configuration/storage/dropbox_spec.rb +1 -1
  91. data/spec/configuration/storage/ftp_spec.rb +1 -1
  92. data/spec/configuration/storage/local_spec.rb +1 -1
  93. data/spec/configuration/storage/ninefold_spec.rb +1 -1
  94. data/spec/configuration/storage/rsync_spec.rb +1 -1
  95. data/spec/configuration/storage/s3_spec.rb +1 -1
  96. data/spec/configuration/storage/scp_spec.rb +1 -1
  97. data/spec/configuration/storage/sftp_spec.rb +1 -1
  98. data/spec/configuration/syncer/rsync_spec.rb +1 -1
  99. data/spec/configuration/syncer/s3_spec.rb +1 -1
  100. data/spec/database/base_spec.rb +10 -1
  101. data/spec/database/mongodb_spec.rb +34 -7
  102. data/spec/database/mysql_spec.rb +8 -7
  103. data/spec/database/postgresql_spec.rb +8 -7
  104. data/spec/database/redis_spec.rb +39 -9
  105. data/spec/database/riak_spec.rb +50 -0
  106. data/spec/encryptor/gpg_spec.rb +1 -1
  107. data/spec/encryptor/open_ssl_spec.rb +77 -20
  108. data/spec/errors_spec.rb +306 -0
  109. data/spec/finder_spec.rb +91 -0
  110. data/spec/logger_spec.rb +254 -33
  111. data/spec/model_spec.rb +120 -15
  112. data/spec/notifier/campfire_spec.rb +127 -52
  113. data/spec/notifier/hipchat_spec.rb +193 -0
  114. data/spec/notifier/mail_spec.rb +290 -74
  115. data/spec/notifier/presently_spec.rb +290 -73
  116. data/spec/notifier/prowl_spec.rb +149 -0
  117. data/spec/notifier/twitter_spec.rb +106 -41
  118. data/spec/spec_helper.rb +8 -2
  119. data/spec/splitter_spec.rb +71 -0
  120. data/spec/storage/base_spec.rb +280 -19
  121. data/spec/storage/cloudfiles_spec.rb +38 -22
  122. data/spec/storage/dropbox_spec.rb +17 -13
  123. data/spec/storage/ftp_spec.rb +145 -55
  124. data/spec/storage/local_spec.rb +6 -6
  125. data/spec/storage/ninefold_spec.rb +70 -29
  126. data/spec/storage/object_spec.rb +44 -44
  127. data/spec/storage/rsync_spec.rb +186 -63
  128. data/spec/storage/s3_spec.rb +23 -24
  129. data/spec/storage/scp_spec.rb +116 -41
  130. data/spec/storage/sftp_spec.rb +124 -46
  131. data/spec/syncer/rsync_spec.rb +3 -3
  132. data/spec/syncer/s3_spec.rb +1 -1
  133. data/spec/version_spec.rb +1 -1
  134. data/templates/cli/utility/archive +13 -0
  135. data/{lib/templates → templates/cli/utility}/compressor/bzip2 +1 -1
  136. data/{lib/templates → templates/cli/utility}/compressor/gzip +1 -1
  137. data/{lib/templates → templates/cli/utility}/compressor/lzma +0 -0
  138. data/templates/cli/utility/compressor/pbzip2 +7 -0
  139. data/templates/cli/utility/config +31 -0
  140. data/{lib/templates → templates/cli/utility}/database/mongodb +1 -1
  141. data/{lib/templates → templates/cli/utility}/database/mysql +1 -1
  142. data/{lib/templates → templates/cli/utility}/database/postgresql +1 -1
  143. data/{lib/templates → templates/cli/utility}/database/redis +1 -1
  144. data/templates/cli/utility/database/riak +8 -0
  145. data/{lib/templates → templates/cli/utility}/encryptor/gpg +1 -1
  146. data/templates/cli/utility/encryptor/openssl +9 -0
  147. data/templates/cli/utility/model.erb +23 -0
  148. data/{lib/templates → templates/cli/utility}/notifier/campfire +2 -1
  149. data/templates/cli/utility/notifier/hipchat +15 -0
  150. data/{lib/templates → templates/cli/utility}/notifier/mail +6 -1
  151. data/{lib/templates → templates/cli/utility}/notifier/presently +1 -0
  152. data/templates/cli/utility/notifier/prowl +11 -0
  153. data/{lib/templates → templates/cli/utility}/notifier/twitter +2 -1
  154. data/templates/cli/utility/splitter +7 -0
  155. data/templates/cli/utility/storage/cloudfiles +12 -0
  156. data/{lib/templates → templates/cli/utility}/storage/dropbox +1 -1
  157. data/{lib/templates → templates/cli/utility}/storage/ftp +0 -0
  158. data/templates/cli/utility/storage/local +7 -0
  159. data/{lib/templates → templates/cli/utility}/storage/ninefold +1 -1
  160. data/templates/cli/utility/storage/rsync +11 -0
  161. data/{lib/templates → templates/cli/utility}/storage/s3 +0 -2
  162. data/templates/cli/utility/storage/scp +11 -0
  163. data/templates/cli/utility/storage/sftp +11 -0
  164. data/{lib/templates → templates/cli/utility}/syncer/rsync +1 -1
  165. data/{lib/templates → templates/cli/utility}/syncer/s3 +1 -1
  166. data/templates/general/links +11 -0
  167. data/templates/general/version.erb +2 -0
  168. data/templates/notifier/mail/failure.erb +9 -0
  169. data/templates/notifier/mail/success.erb +7 -0
  170. data/templates/notifier/mail/warning.erb +9 -0
  171. data/templates/storage/dropbox/authorization_url.erb +6 -0
  172. data/templates/storage/dropbox/authorized.erb +4 -0
  173. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  174. metadata +81 -45
  175. data/lib/backup/cli.rb +0 -110
  176. data/lib/backup/exception/command_failed.rb +0 -8
  177. data/lib/backup/exception/command_not_found.rb +0 -8
  178. data/lib/backup/notifier/binder.rb +0 -32
  179. data/lib/backup/notifier/templates/notify_failure.erb +0 -33
  180. data/lib/backup/notifier/templates/notify_success.erb +0 -16
  181. data/lib/templates/archive +0 -7
  182. data/lib/templates/encryptor/openssl +0 -8
  183. data/lib/templates/readme +0 -15
  184. data/lib/templates/storage/cloudfiles +0 -11
  185. data/lib/templates/storage/local +0 -7
  186. data/lib/templates/storage/rsync +0 -11
  187. data/lib/templates/storage/scp +0 -11
  188. data/lib/templates/storage/sftp +0 -11
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Backup
4
4
  class Archive
5
- include Backup::CLI
5
+ include Backup::CLI::Helpers
6
6
 
7
7
  ##
8
8
  # Stores the name of the archive
@@ -23,10 +23,10 @@ module Backup
23
23
  ##
24
24
  # Takes the name of the archive and the configuration block
25
25
  def initialize(name, &block)
26
- @name = name.to_sym
27
- @paths = Array.new
28
- @excludes = Array.new
29
- @archive_path = File.join(TMP_PATH, TRIGGER, 'archive')
26
+ @name = name.to_sym
27
+ @paths = Array.new
28
+ @excludes = Array.new
29
+ @tar_options = ''
30
30
 
31
31
  instance_eval(&block)
32
32
  end
@@ -34,22 +34,34 @@ module Backup
34
34
  ##
35
35
  # Adds new paths to the @paths instance variable array
36
36
  def add(path)
37
- @paths << path
37
+ @paths << File.expand_path(path)
38
38
  end
39
39
 
40
40
  ##
41
41
  # Adds new paths to the @excludes instance variable array
42
42
  def exclude(path)
43
- @excludes << path
43
+ @excludes << File.expand_path(path)
44
+ end
45
+
46
+ ##
47
+ # Adds the given String of +options+ to the `tar` command.
48
+ # e.g. '-h --xattrs'
49
+ def tar_options(options)
50
+ @tar_options = options
44
51
  end
45
52
 
46
53
  ##
47
54
  # Archives all the provided paths in to a single .tar file
48
55
  # and places that .tar file in the folder which later will be packaged
49
56
  def perform!
57
+ @archive_path = File.join(TMP_PATH, TRIGGER, 'archive')
50
58
  mkdir(archive_path)
51
- Logger.message("#{ self.class } started packaging and archiving #{ paths.map { |path| "\"#{path}\""}.join(", ") }.")
52
- run("#{ utility(:tar) } -c -f '#{ File.join(archive_path, "#{name}.tar") }' #{ paths_to_exclude } #{ paths_to_package } 2> /dev/null")
59
+
60
+ Logger.message "#{ self.class } started packaging and archiving:\n" +
61
+ paths.map {|path| " #{path}" }.join("\n")
62
+
63
+ run("#{ utility(:tar) } #{ @tar_options } -cf '#{ File.join(archive_path, "#{name}.tar") }' " +
64
+ "#{ paths_to_exclude } #{ paths_to_package }", :ignore_exit_codes => [1])
53
65
  end
54
66
 
55
67
  private
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Binder
5
+
6
+ ##
7
+ # Creates a new Backup::Notifier::Binder instance. Loops through the provided
8
+ # Hash to set instance variables
9
+ def initialize(key_and_values)
10
+ key_and_values.each do |key, value|
11
+ instance_variable_set("@#{ key }", value)
12
+ end
13
+ end
14
+
15
+ ##
16
+ # Returns the binding (needs a wrapper method because #binding is a private method)
17
+ def get_binding
18
+ binding
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Cleaner
5
+ include Backup::CLI::Helpers
6
+
7
+ ##
8
+ # Holds an instance of the current Backup model
9
+ attr_accessor :model
10
+
11
+ ##
12
+ # Creates a new instance of Backup::Cleaner
13
+ def initialize(model)
14
+ @model = model
15
+ end
16
+
17
+ ##
18
+ # Runs the cleaner object which removes all the tmp files generated by Backup
19
+ def clean!
20
+ Logger.message "#{self.class} started cleaning up the temporary files."
21
+ run("#{ utility(:rm) } -rf #{ paths.map { |path| "'#{ path }'" }.join(" ") }")
22
+ end
23
+
24
+ private
25
+
26
+ ##
27
+ # Returns an Array of paths to temporary files generated by Backup that need to be removed
28
+ def paths
29
+ Array.new([
30
+ File.join(TMP_PATH, TRIGGER),
31
+ Backup::Model.file,
32
+ Backup::Model.chunk_suffixes.map { |chunk_suffix| "#{Backup::Model.file}-#{chunk_suffix}" }
33
+ ]).flatten
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,103 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module CLI
5
+ module Helpers
6
+
7
+ ##
8
+ # Runs a given command in an isolated (sub) process using POpen4.
9
+ # The STDOUT, STDERR and the returned exit code of the utility will be stored in the process_data Hash.
10
+ #
11
+ # If a command returns an exit code other than 0, an exception will raise and the backup process will abort.
12
+ # Some utilities return exit codes other than 0 which aren't an issue in Backup's context. If this is the case,
13
+ # you can pass in an array of exit codes to ignore (whitelist), for example:
14
+ #
15
+ # run("tar -cf /output.tar /some/folder", :ignore_exit_codes => [1])
16
+ #
17
+ # So if the `tar` utility returns in this case 1, Backup will consider it an acceptable return code.
18
+ #
19
+ # Note: Exit code 0 is always automatically added to the :ignore_exit_codes array, regardless of whether you specify an
20
+ # array to ignore or not.
21
+ def run(command, options = {})
22
+ command.gsub!(/^\s+/, "")
23
+
24
+ process_data = Hash.new
25
+ pid, stdin, stdout, stderr = Open4::popen4(command)
26
+ ignored, process_data[:status] = Process::waitpid2(pid)
27
+ process_data[:stdout] = stdout.read
28
+ process_data[:stderr] = stderr.read
29
+ process_data[:ignore_exit_codes] = ((options[:ignore_exit_codes] || Array.new) << 0).uniq
30
+
31
+ raise_if_command_failed!(command_name(command), process_data)
32
+ process_data[:stdout]
33
+ end
34
+
35
+ ##
36
+ # Wrapper method for FileUtils.mkdir_p to create directories
37
+ # through a ruby method. This helps with test coverage and
38
+ # improves readability
39
+ def mkdir(path)
40
+ FileUtils.mkdir_p(path)
41
+ end
42
+
43
+ ##
44
+ # Wrapper for the FileUtils.rm_rf to remove files and folders
45
+ # through a ruby method. This helps with test coverage and
46
+ # improves readability
47
+ def rm(path)
48
+ FileUtils.rm_rf(path)
49
+ end
50
+
51
+ ##
52
+ # Tries to find the full path of the specified utility. If the full
53
+ # path is found, it'll return that. Otherwise it'll just return the
54
+ # name of the utility. If the 'utility_path' is defined, it'll check
55
+ # to see if it isn't an empty string, and if it isn't, it'll go ahead and
56
+ # always use that path rather than auto-detecting it
57
+ def utility(name)
58
+ if respond_to?(:utility_path)
59
+ if utility_path.is_a?(String) and not utility_path.empty?
60
+ return utility_path
61
+ end
62
+ end
63
+
64
+ if path = %x[which #{name} 2>/dev/null].chomp and not path.empty?
65
+ return path
66
+ end
67
+ name
68
+ end
69
+
70
+ ##
71
+ # Returns the name of the command
72
+ def command_name(command)
73
+ command.slice(0, command.index(/\s/)).split('/')[-1]
74
+ end
75
+
76
+ ##
77
+ # Inspects the exit code returned from the POpen4 child process. If the exit code isn't listed
78
+ # in the process_data[:ignore_exit_codes] array, an exception will be raised, aborting the backup process.
79
+ #
80
+ # Information regarding the error ( EXIT CODE and STDERR ) will be returned to the shell so the user can
81
+ # investigate the issue.
82
+ #
83
+ # raises Backup::Errors::CLI::SystemCallError
84
+ def raise_if_command_failed!(utility, process_data)
85
+ unless process_data[:ignore_exit_codes].include?(process_data[:status].to_i)
86
+
87
+ stderr = process_data[:stderr].empty? ?
88
+ nil : "STDERR:\n#{process_data[:stderr]}\n"
89
+ stdout = process_data[:stdout].empty? ?
90
+ nil : "STDOUT:\n#{process_data[:stdout]}\n"
91
+
92
+ raise Errors::CLI::SystemCallError, <<-EOS
93
+ Failed to run #{utility} on #{RUBY_PLATFORM}
94
+ The following information should help to determine the problem:
95
+ Exit Code: #{process_data[:status]}
96
+ #{stderr}#{stdout}
97
+ EOS
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,308 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # Build the Backup Command Line Interface using Thor
5
+ module Backup
6
+ module CLI
7
+ class Utility < Thor
8
+ include Thor::Actions
9
+
10
+ ##
11
+ # [Perform]
12
+ # Performs the backup process. The only required option is the --trigger [-t].
13
+ # If the other options (--config-file, --data-path, --cache--path, --tmp-path) aren't specified
14
+ # they will fallback to the (good) defaults
15
+ #
16
+ # If --root-path is given, it will be used as the base (Backup::PATH) for our defaults,
17
+ # as well as the base path for any option specified as a relative path.
18
+ # Any option given as an absolute path will be used "as-is".
19
+ method_option :trigger, :type => :string, :required => true, :aliases => ['-t', '--triggers']
20
+ method_option :config_file, :type => :string, :default => '', :aliases => '-c'
21
+ method_option :root_path, :type => :string, :default => '', :aliases => '-r'
22
+ method_option :data_path, :type => :string, :default => '', :aliases => '-d'
23
+ method_option :log_path, :type => :string, :default => '', :aliases => '-l'
24
+ method_option :cache_path, :type => :string, :default => ''
25
+ method_option :tmp_path, :type => :string, :default => ''
26
+ method_option :quiet, :type => :boolean, :default => false, :aliases => '-q'
27
+ desc 'perform', "Performs the backup for the specified trigger.\n" +
28
+ "You may perform multiple backups by providing multiple triggers, separated by commas.\n\n" +
29
+ "Example:\n\s\s$ backup perform --triggers backup1,backup2,backup3,backup4\n\n" +
30
+ "This will invoke 4 backups, and they will run in the order specified (not asynchronous)."
31
+ def perform
32
+ ##
33
+ # Setup required paths based on the given options
34
+ setup_paths(options)
35
+
36
+ ##
37
+ # Silence Backup::Logger from printing to STDOUT, if --quiet was specified
38
+ Logger.send(:const_set, :QUIET, options[:quiet])
39
+
40
+ ##
41
+ # Prepare all trigger names by splitting them by ','
42
+ # and finding trigger names matching wildcard
43
+ triggers = options[:trigger].split(",")
44
+ triggers.map!(&:strip).map!{ |t|
45
+ t.include?(Backup::Finder::WILDCARD) ?
46
+ Backup::Finder.new(t).matching : t
47
+ }.flatten!
48
+
49
+ ##
50
+ # Process each trigger
51
+ triggers.each do |trigger|
52
+
53
+ ##
54
+ # Defines the TRIGGER constant
55
+ Backup.send(:const_set, :TRIGGER, trigger)
56
+
57
+ ##
58
+ # Define the TIME constants
59
+ Backup.send(:const_set, :TIME, Time.now.strftime("%Y.%m.%d.%H.%M.%S"))
60
+
61
+ ##
62
+ # Ensure DATA_PATH and DATA_PATH/TRIGGER are created if they do not yet exist
63
+ FileUtils.mkdir_p(File.join(Backup::DATA_PATH, Backup::TRIGGER))
64
+
65
+ ##
66
+ # Parses the backup configuration file and returns the model instance by trigger
67
+ model = Backup::Finder.new(trigger).find
68
+
69
+ ##
70
+ # Runs the returned model
71
+ Logger.message "Performing backup for #{model.label}!"
72
+ model.perform!
73
+
74
+ ##
75
+ # Removes the TRIGGER constant
76
+ Backup.send(:remove_const, :TRIGGER) if defined? Backup::TRIGGER
77
+
78
+ ##
79
+ # Removes the TIME constant
80
+ Backup.send(:remove_const, :TIME) if defined? Backup::TIME
81
+
82
+ ##
83
+ # Reset the Backup::Model.current to nil for the next potential run
84
+ Backup::Model.current = nil
85
+
86
+ ##
87
+ # Clear the Log Messages for the next potential run
88
+ Logger.clear!
89
+
90
+ ##
91
+ # Reset the Backup::Model.extension to 'tar' so it's at its
92
+ # initial state when the next Backup::Model initializes
93
+ Backup::Model.extension = 'tar'
94
+ end
95
+
96
+ rescue => err
97
+ Logger.error Backup::Errors::CLIError.wrap(err)
98
+ exit(1)
99
+ end
100
+
101
+ ##
102
+ # [Generate:Model]
103
+ # Generates a model configuration file based on the arguments passed in.
104
+ # For example:
105
+ # $ backup generate:model --trigger my_backup --databases='mongodb'
106
+ # will generate a pre-populated model with a base MongoDB setup
107
+ method_option :trigger, :type => :string, :required => true
108
+ method_option :config_path, :type => :string,
109
+ :desc => 'Path to your Backup configuration directory'
110
+ desc 'generate:model', "Generates a Backup model file\n\n" +
111
+ "Note:\n" +
112
+ "\s\s'--config-path' is the path to the directory where 'config.rb' is located.\n" +
113
+ "\s\sThe model file will be created as '<config_path>/models/<trigger>.rb'\n" +
114
+ "\s\sDefault: #{Backup::PATH}\n"
115
+
116
+ # options with their available values
117
+ %w{ databases storages syncers
118
+ encryptors compressors notifiers }.map(&:to_sym).each do |name|
119
+ path = File.join(Backup::TEMPLATE_PATH, 'cli', 'utility', name.to_s[0..-2])
120
+ method_option name, :type => :string, :desc =>
121
+ "(#{Dir[path + '/*'].sort.map {|p| File.basename(p) }.join(', ')})"
122
+ end
123
+
124
+ method_option :archives, :type => :boolean
125
+ method_option :splitter, :type => :boolean, :default => true,
126
+ :desc => "use `--no-splitter` to disable"
127
+
128
+ define_method "generate:model" do
129
+ opts = options.merge(
130
+ :trigger => options[:trigger].gsub(/[\W\s]/, '_'),
131
+ :config_path => options[:config_path] ? File.expand_path(options[:config_path]) : nil
132
+ )
133
+ config_path = opts[:config_path] || Backup::PATH
134
+ models_path = File.join(config_path, "models")
135
+ config = File.join(config_path, "config.rb")
136
+ model = File.join(models_path, "#{opts[:trigger]}.rb")
137
+
138
+ FileUtils.mkdir_p(models_path)
139
+ if overwrite?(model)
140
+ File.open(model, 'w') do |file|
141
+ file.write(Backup::Template.new({:options => opts}).
142
+ result("cli/utility/model.erb"))
143
+ end
144
+ puts "Generated model file in '#{ model }'."
145
+ end
146
+
147
+ if not File.exist?(config)
148
+ File.open(config, "w") do |file|
149
+ file.write(Backup::Template.new.result("cli/utility/config"))
150
+ end
151
+ puts "Generated configuration file in '#{ config }'."
152
+ end
153
+ end
154
+
155
+ ##
156
+ # [Generate:Config]
157
+ # Generates the main configuration file
158
+ desc 'generate:config', 'Generates the main Backup bootstrap/configuration file'
159
+ method_option :path, :type => :string
160
+ define_method 'generate:config' do
161
+ path = options[:path] ? File.expand_path(options[:path]) : nil
162
+ config_path = path || Backup::PATH
163
+ config = File.join(config_path, "config.rb")
164
+
165
+ FileUtils.mkdir_p(config_path)
166
+ if overwrite?(config)
167
+ File.open(config, "w") do |file|
168
+ file.write(Backup::Template.new.result("cli/utility/config"))
169
+ end
170
+ puts "Generated configuration file in '#{ config }'"
171
+ end
172
+ end
173
+
174
+ ##
175
+ # [Decrypt]
176
+ # Shorthand for decrypting encrypted files
177
+ desc 'decrypt', 'Decrypts encrypted files'
178
+ method_option :encryptor, :type => :string, :required => true
179
+ method_option :in, :type => :string, :required => true
180
+ method_option :out, :type => :string, :required => true
181
+ method_option :base64, :type => :boolean, :default => false
182
+ method_option :password_file, :type => :string, :default => ''
183
+ method_option :salt, :type => :boolean, :default => false
184
+ def decrypt
185
+ case options[:encryptor].downcase
186
+ when 'openssl'
187
+ base64 = options[:base64] ? '-base64' : ''
188
+ password = options[:password_file] ? "-pass file:#{options[:password_file]}" : ''
189
+ salt = options[:salt] ? '-salt' : ''
190
+ %x[openssl aes-256-cbc -d #{base64} #{password} #{salt} -in '#{options[:in]}' -out '#{options[:out]}']
191
+ when 'gpg'
192
+ %x[gpg -o '#{options[:out]}' -d '#{options[:in]}']
193
+ else
194
+ puts "Unknown encryptor: #{options[:encryptor]}"
195
+ puts "Use either 'openssl' or 'gpg'"
196
+ end
197
+ end
198
+
199
+ ##
200
+ # [Dependencies]
201
+ # Returns a list of Backup's dependencies
202
+ desc 'dependencies', 'Display the list of dependencies for Backup, or install them through Backup.'
203
+ method_option :install, :type => :string
204
+ method_option :list, :type => :boolean
205
+ def dependencies
206
+ unless options.any?
207
+ puts
208
+ puts "To display a list of available dependencies, run:\n\n"
209
+ puts " backup dependencies --list"
210
+ puts
211
+ puts "To install one of these dependencies (with the correct version), run:\n\n"
212
+ puts " backup dependencies --install <name>"
213
+ exit
214
+ end
215
+
216
+ if options[:list]
217
+ Backup::Dependency.all.each do |name, gemspec|
218
+ puts
219
+ puts name
220
+ puts "--------------------------------------------------"
221
+ puts "version: #{gemspec[:version]}"
222
+ puts "lib required: #{gemspec[:require]}"
223
+ puts "used for: #{gemspec[:for]}"
224
+ end
225
+ end
226
+
227
+ if options[:install]
228
+ puts
229
+ puts "Installing \"#{options[:install]}\" version \"#{Backup::Dependency.all[options[:install]][:version]}\".."
230
+ puts "If this doesn't work, please issue the following command yourself:\n\n"
231
+ puts " gem install #{options[:install]} -v '#{Backup::Dependency.all[options[:install]][:version]}'\n\n"
232
+ puts "Please wait..\n\n"
233
+ puts %x[gem install #{options[:install]} -v '#{Backup::Dependency.all[options[:install]][:version]}']
234
+ end
235
+ end
236
+
237
+ ##
238
+ # [Version]
239
+ # Returns the current version of the Backup gem
240
+ map '-v' => :version
241
+ desc 'version', 'Display installed Backup version'
242
+ def version
243
+ puts "Backup #{Backup::Version.current}"
244
+ end
245
+
246
+ private
247
+
248
+ ##
249
+ # Setup required paths based on the given options
250
+ #
251
+ def setup_paths(options)
252
+ ##
253
+ # Set PATH if --root-path is given and the directory exists
254
+ root_path = false
255
+ root_given = options[:root_path].strip
256
+ if !root_given.empty? && File.directory?(root_given)
257
+ root_path = File.expand_path(root_given)
258
+ Backup.send(:remove_const, :PATH)
259
+ Backup.send(:const_set, :PATH, root_path)
260
+ end
261
+
262
+ ##
263
+ # Update all defaults and given paths to use root_path (if given).
264
+ # Paths given as an absolute path will be used 'as-is'
265
+ { :config_file => 'config.rb',
266
+ :data_path => 'data',
267
+ :log_path => 'log',
268
+ :cache_path => '.cache',
269
+ :tmp_path => '.tmp' }.each do |key, name|
270
+ # strip any trailing '/' in case the user supplied this as part of
271
+ # an absolute path, so we can match it against File.expand_path()
272
+ given = options[key].sub(/\/\s*$/, '').lstrip
273
+ path = false
274
+ if given.empty?
275
+ path = File.join(root_path, name) if root_path
276
+ else
277
+ path = File.expand_path(given)
278
+ unless given == path
279
+ path = File.join(root_path, given) if root_path
280
+ end
281
+ end
282
+
283
+ const = key.to_s.upcase
284
+ if path
285
+ Backup.send(:remove_const, const)
286
+ Backup.send(:const_set, const, path)
287
+ else
288
+ path = Backup.const_get(const)
289
+ end
290
+
291
+ ##
292
+ # Ensure the LOG_PATH, CACHE_PATH and TMP_PATH are created if they do not yet exist
293
+ FileUtils.mkdir_p(path) if [:log_path, :cache_path, :tmp_path].include?(key)
294
+ end
295
+ end
296
+
297
+ ##
298
+ # Helper method for asking the user if he/she wants to overwrite the file
299
+ def overwrite?(path)
300
+ if File.exist?(path)
301
+ return yes? "A file already exists at '#{ path }'. Do you want to overwrite? [y/n]"
302
+ end
303
+ true
304
+ end
305
+
306
+ end
307
+ end
308
+ end