backedup 5.0.0.beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +33 -0
  4. data/bin/backedup +5 -0
  5. data/bin/docker_test +24 -0
  6. data/lib/backup/archive.rb +169 -0
  7. data/lib/backup/binder.rb +18 -0
  8. data/lib/backup/cleaner.rb +112 -0
  9. data/lib/backup/cli.rb +370 -0
  10. data/lib/backup/cloud_io/base.rb +38 -0
  11. data/lib/backup/cloud_io/cloud_files.rb +296 -0
  12. data/lib/backup/cloud_io/gcs.rb +121 -0
  13. data/lib/backup/cloud_io/s3.rb +253 -0
  14. data/lib/backup/cloud_io/swift.rb +96 -0
  15. data/lib/backup/compressor/base.rb +32 -0
  16. data/lib/backup/compressor/bzip2.rb +35 -0
  17. data/lib/backup/compressor/custom.rb +49 -0
  18. data/lib/backup/compressor/gzip.rb +73 -0
  19. data/lib/backup/compressor/pbzip2.rb +45 -0
  20. data/lib/backup/config/dsl.rb +102 -0
  21. data/lib/backup/config/helpers.rb +137 -0
  22. data/lib/backup/config.rb +118 -0
  23. data/lib/backup/database/base.rb +86 -0
  24. data/lib/backup/database/mongodb.rb +186 -0
  25. data/lib/backup/database/mysql.rb +191 -0
  26. data/lib/backup/database/openldap.rb +93 -0
  27. data/lib/backup/database/postgresql.rb +164 -0
  28. data/lib/backup/database/redis.rb +176 -0
  29. data/lib/backup/database/riak.rb +79 -0
  30. data/lib/backup/database/sqlite.rb +55 -0
  31. data/lib/backup/encryptor/base.rb +27 -0
  32. data/lib/backup/encryptor/gpg.rb +737 -0
  33. data/lib/backup/encryptor/open_ssl.rb +74 -0
  34. data/lib/backup/errors.rb +53 -0
  35. data/lib/backup/logger/console.rb +48 -0
  36. data/lib/backup/logger/fog_adapter.rb +25 -0
  37. data/lib/backup/logger/logfile.rb +131 -0
  38. data/lib/backup/logger/syslog.rb +114 -0
  39. data/lib/backup/logger.rb +197 -0
  40. data/lib/backup/model.rb +472 -0
  41. data/lib/backup/notifier/base.rb +126 -0
  42. data/lib/backup/notifier/campfire.rb +61 -0
  43. data/lib/backup/notifier/command.rb +99 -0
  44. data/lib/backup/notifier/datadog.rb +104 -0
  45. data/lib/backup/notifier/flowdock.rb +99 -0
  46. data/lib/backup/notifier/hipchat.rb +116 -0
  47. data/lib/backup/notifier/http_post.rb +114 -0
  48. data/lib/backup/notifier/mail.rb +232 -0
  49. data/lib/backup/notifier/nagios.rb +65 -0
  50. data/lib/backup/notifier/pagerduty.rb +79 -0
  51. data/lib/backup/notifier/prowl.rb +68 -0
  52. data/lib/backup/notifier/pushover.rb +71 -0
  53. data/lib/backup/notifier/ses.rb +123 -0
  54. data/lib/backup/notifier/slack.rb +147 -0
  55. data/lib/backup/notifier/twitter.rb +55 -0
  56. data/lib/backup/notifier/zabbix.rb +60 -0
  57. data/lib/backup/package.rb +51 -0
  58. data/lib/backup/packager.rb +106 -0
  59. data/lib/backup/pipeline.rb +120 -0
  60. data/lib/backup/splitter.rb +73 -0
  61. data/lib/backup/storage/base.rb +66 -0
  62. data/lib/backup/storage/cloud_files.rb +156 -0
  63. data/lib/backup/storage/cycler.rb +70 -0
  64. data/lib/backup/storage/dropbox.rb +206 -0
  65. data/lib/backup/storage/ftp.rb +116 -0
  66. data/lib/backup/storage/gcs.rb +93 -0
  67. data/lib/backup/storage/local.rb +61 -0
  68. data/lib/backup/storage/qiniu.rb +65 -0
  69. data/lib/backup/storage/rsync.rb +246 -0
  70. data/lib/backup/storage/s3.rb +155 -0
  71. data/lib/backup/storage/scp.rb +65 -0
  72. data/lib/backup/storage/sftp.rb +80 -0
  73. data/lib/backup/storage/swift.rb +124 -0
  74. data/lib/backup/storage/webdav.rb +102 -0
  75. data/lib/backup/syncer/base.rb +67 -0
  76. data/lib/backup/syncer/cloud/base.rb +176 -0
  77. data/lib/backup/syncer/cloud/cloud_files.rb +81 -0
  78. data/lib/backup/syncer/cloud/local_file.rb +97 -0
  79. data/lib/backup/syncer/cloud/s3.rb +109 -0
  80. data/lib/backup/syncer/rsync/base.rb +50 -0
  81. data/lib/backup/syncer/rsync/local.rb +27 -0
  82. data/lib/backup/syncer/rsync/pull.rb +47 -0
  83. data/lib/backup/syncer/rsync/push.rb +201 -0
  84. data/lib/backup/template.rb +41 -0
  85. data/lib/backup/utilities.rb +234 -0
  86. data/lib/backup/version.rb +3 -0
  87. data/lib/backup.rb +145 -0
  88. data/templates/cli/archive +28 -0
  89. data/templates/cli/compressor/bzip2 +4 -0
  90. data/templates/cli/compressor/custom +7 -0
  91. data/templates/cli/compressor/gzip +4 -0
  92. data/templates/cli/config +123 -0
  93. data/templates/cli/databases/mongodb +15 -0
  94. data/templates/cli/databases/mysql +18 -0
  95. data/templates/cli/databases/openldap +24 -0
  96. data/templates/cli/databases/postgresql +16 -0
  97. data/templates/cli/databases/redis +16 -0
  98. data/templates/cli/databases/riak +17 -0
  99. data/templates/cli/databases/sqlite +11 -0
  100. data/templates/cli/encryptor/gpg +27 -0
  101. data/templates/cli/encryptor/openssl +9 -0
  102. data/templates/cli/model +26 -0
  103. data/templates/cli/notifier/zabbix +15 -0
  104. data/templates/cli/notifiers/campfire +12 -0
  105. data/templates/cli/notifiers/command +32 -0
  106. data/templates/cli/notifiers/datadog +57 -0
  107. data/templates/cli/notifiers/flowdock +16 -0
  108. data/templates/cli/notifiers/hipchat +16 -0
  109. data/templates/cli/notifiers/http_post +32 -0
  110. data/templates/cli/notifiers/mail +24 -0
  111. data/templates/cli/notifiers/nagios +13 -0
  112. data/templates/cli/notifiers/pagerduty +12 -0
  113. data/templates/cli/notifiers/prowl +11 -0
  114. data/templates/cli/notifiers/pushover +11 -0
  115. data/templates/cli/notifiers/ses +15 -0
  116. data/templates/cli/notifiers/slack +22 -0
  117. data/templates/cli/notifiers/twitter +13 -0
  118. data/templates/cli/splitter +7 -0
  119. data/templates/cli/storages/cloud_files +11 -0
  120. data/templates/cli/storages/dropbox +20 -0
  121. data/templates/cli/storages/ftp +13 -0
  122. data/templates/cli/storages/gcs +8 -0
  123. data/templates/cli/storages/local +8 -0
  124. data/templates/cli/storages/qiniu +12 -0
  125. data/templates/cli/storages/rsync +17 -0
  126. data/templates/cli/storages/s3 +16 -0
  127. data/templates/cli/storages/scp +15 -0
  128. data/templates/cli/storages/sftp +15 -0
  129. data/templates/cli/storages/swift +19 -0
  130. data/templates/cli/storages/webdav +13 -0
  131. data/templates/cli/syncers/cloud_files +22 -0
  132. data/templates/cli/syncers/rsync_local +20 -0
  133. data/templates/cli/syncers/rsync_pull +28 -0
  134. data/templates/cli/syncers/rsync_push +28 -0
  135. data/templates/cli/syncers/s3 +27 -0
  136. data/templates/general/links +3 -0
  137. data/templates/general/version.erb +2 -0
  138. data/templates/notifier/mail/failure.erb +16 -0
  139. data/templates/notifier/mail/success.erb +16 -0
  140. data/templates/notifier/mail/warning.erb +16 -0
  141. data/templates/storage/dropbox/authorization_url.erb +6 -0
  142. data/templates/storage/dropbox/authorized.erb +4 -0
  143. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  144. metadata +1255 -0
@@ -0,0 +1,102 @@
1
+ module Backup
2
+ module Config
3
+ # Context for loading user config.rb and model files.
4
+ class DSL
5
+ class Error < Backup::Error; end
6
+ Model = Backup::Model
7
+
8
+ class << self
9
+ private
10
+
11
+ # List the available database, storage, syncer, compressor, encryptor
12
+ # and notifier constants. These are used to define constant names within
13
+ # Backup::Config::DSL so that users may use a constant instead of a string.
14
+ # Nested namespaces are represented using Hashs. Deep nesting supported.
15
+ #
16
+ # Example, instead of:
17
+ # database "MySQL" do |mysql|
18
+ # sync_with "RSync::Local" do |rsync|
19
+ #
20
+ # You can do:
21
+ # database MySQL do |mysql|
22
+ # sync_with RSync::Local do |rsync|
23
+ #
24
+ def add_dsl_constants
25
+ create_modules(
26
+ DSL,
27
+ [ # Databases
28
+ ["MySQL", "PostgreSQL", "MongoDB", "Redis", "Riak", "OpenLDAP", "SQLite"],
29
+ # Storages
30
+ ["S3", "Swift", "GCS", "CloudFiles", "Ninefold", "Dropbox", "FTP",
31
+ "SFTP", "SCP", "RSync", "Local", "Qiniu", "Webdav"],
32
+ # Compressors
33
+ ["Gzip", "Bzip2", "PBzip2", "Custom"],
34
+ # Encryptors
35
+ ["OpenSSL", "GPG"],
36
+ # Syncers
37
+ [
38
+ { "Cloud" => ["CloudFiles", "S3"] },
39
+ { "RSync" => ["Push", "Pull", "Local"] }
40
+ ],
41
+ # Notifiers
42
+ ["Mail", "Twitter", "Campfire", "Prowl",
43
+ "Hipchat", "PagerDuty", "Pushover", "HttpPost", "Nagios",
44
+ "Slack", "FlowDock", "Zabbix", "Ses", "DataDog", "Command"]
45
+ ]
46
+ )
47
+ end
48
+
49
+ def create_modules(scope, names)
50
+ names.flatten.each do |name|
51
+ if name.is_a?(Hash)
52
+ name.each do |key, val|
53
+ create_modules(get_or_create_empty_module(scope, key), [val])
54
+ end
55
+ else
56
+ get_or_create_empty_module(scope, name)
57
+ end
58
+ end
59
+ end
60
+
61
+ def get_or_create_empty_module(scope, const)
62
+ if scope.const_defined?(const)
63
+ scope.const_get(const)
64
+ else
65
+ scope.const_set(const, Module.new)
66
+ end
67
+ end
68
+ end
69
+
70
+ add_dsl_constants # add constants on load
71
+
72
+ attr_reader :_config_options
73
+
74
+ def initialize
75
+ @_config_options = {}
76
+ end
77
+
78
+ # Allow users to set command line path options in config.rb
79
+ [:root_path, :data_path, :tmp_path].each do |name|
80
+ define_method name do |path|
81
+ _config_options[name] = path
82
+ end
83
+ end
84
+
85
+ # Allows users to create preconfigured models.
86
+ def preconfigure(name, &block)
87
+ unless name.is_a?(String) && name =~ /^[A-Z]/
88
+ raise Error, "Preconfigured model names must be given as a string " \
89
+ "and start with a capital letter."
90
+ end
91
+
92
+ if DSL.const_defined?(name)
93
+ raise Error, "'#{name}' is already in use " \
94
+ "and can not be used for a preconfigured model."
95
+ end
96
+
97
+ DSL.const_set(name, Class.new(Model))
98
+ DSL.const_get(name).preconfigure(&block)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,137 @@
1
+ require "ostruct"
2
+
3
+ module Backup
4
+ module Config
5
+ module Helpers
6
+ def self.included(klass)
7
+ klass.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def defaults
12
+ @defaults ||= Config::Defaults.new
13
+
14
+ if block_given?
15
+ yield @defaults
16
+ else
17
+ @defaults
18
+ end
19
+ end
20
+
21
+ # Used only within the specs
22
+ def clear_defaults!
23
+ defaults.reset!
24
+ end
25
+
26
+ def deprecations
27
+ @deprecations ||= {}
28
+ end
29
+
30
+ def log_deprecation_warning(name, deprecation)
31
+ msg = "#{self}##{name} has been deprecated as of " \
32
+ "backup v.#{deprecation[:version]}"
33
+ msg << "\n#{deprecation[:message]}" if deprecation[:message]
34
+ Logger.warn Config::Error.new(<<-EOS)
35
+ [DEPRECATION WARNING]
36
+ #{msg}
37
+ EOS
38
+ end
39
+
40
+ protected
41
+
42
+ ##
43
+ # Method to deprecate an attribute.
44
+ #
45
+ # :version
46
+ # Must be set to the backup version which will first
47
+ # introduce the deprecation.
48
+ #
49
+ # :action
50
+ # If set, this Proc will be called with a reference to the
51
+ # class instance and the value set on the deprecated accessor.
52
+ # e.g. deprecation[:action].call(klass, value)
53
+ # This should perform whatever action is neccessary, such as
54
+ # transferring the value to a new accessor.
55
+ #
56
+ # :message
57
+ # If set, this will be appended to #log_deprecation_warning
58
+ #
59
+ # Note that this replaces the `attr_accessor` method, or other
60
+ # method previously used to set the accessor being deprecated.
61
+ # #method_missing will handle any calls to `name=`.
62
+ #
63
+ def attr_deprecate(name, args = {})
64
+ deprecations[name] = {
65
+ version: nil,
66
+ message: nil,
67
+ action: nil
68
+ }.merge(args)
69
+ end
70
+ end # ClassMethods
71
+
72
+ private
73
+
74
+ ##
75
+ # Sets any pre-configured default values.
76
+ # If a default value was set for an invalid accessor,
77
+ # this will raise a NameError.
78
+ def load_defaults!
79
+ self.class.defaults._attributes.each do |name|
80
+ val = self.class.defaults.send(name)
81
+ val = val.dup rescue val
82
+ send(:"#{ name }=", val)
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Check missing methods for deprecated attribute accessors.
88
+ #
89
+ # If a value is set on an accessor that has been deprecated
90
+ # using #attr_deprecate, a warning will be issued and any
91
+ # :action (Proc) specified will be called with a reference to
92
+ # the class instance and the value set on the deprecated accessor.
93
+ # See #attr_deprecate and #log_deprecation_warning
94
+ #
95
+ # Note that OpenStruct (used for setting defaults) does not allow
96
+ # multiple arguments when assigning values for members.
97
+ # So, we won't allow it here either, even though an attr_accessor
98
+ # will accept and convert them into an Array. Therefore, setting
99
+ # an option value using multiple values, whether as a default or
100
+ # directly on the class' accessor, should not be supported.
101
+ # i.e. if an option will accept being set as an Array, then it
102
+ # should be explicitly set as such. e.g. option = [val1, val2]
103
+ #
104
+ def method_missing(name, *args)
105
+ deprecation = nil
106
+ if method = name.to_s.chomp!("=")
107
+ if (len = args.count) != 1
108
+ raise ArgumentError,
109
+ "wrong number of arguments (#{len} for 1)", caller(1)
110
+ end
111
+ deprecation = self.class.deprecations[method.to_sym]
112
+ end
113
+
114
+ if deprecation
115
+ self.class.log_deprecation_warning(method, deprecation)
116
+ deprecation[:action].call(self, args[0]) if deprecation[:action]
117
+ else
118
+ super
119
+ end
120
+ end
121
+ end # Helpers
122
+
123
+ # Store for pre-configured defaults.
124
+ class Defaults < OpenStruct
125
+ # Returns an Array of all attribute method names
126
+ # that default values were set for.
127
+ def _attributes
128
+ @table.keys
129
+ end
130
+
131
+ # Used only within the specs
132
+ def reset!
133
+ @table.clear
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,118 @@
1
+ require "backup/config/dsl"
2
+ require "backup/config/helpers"
3
+
4
+ module Backup
5
+ module Config
6
+ class Error < Backup::Error; end
7
+
8
+ DEFAULTS = {
9
+ config_file: "config.rb",
10
+ data_path: ".data",
11
+ tmp_path: ".tmp"
12
+ }
13
+
14
+ class << self
15
+ include Utilities::Helpers
16
+
17
+ attr_reader :user, :root_path, :config_file, :data_path, :tmp_path
18
+
19
+ # Loads the user's +config.rb+ and all model files.
20
+ def load(options = {})
21
+ update(options) # from the command line
22
+
23
+ unless File.exist?(config_file)
24
+ raise Error, "Could not find configuration file: '#{config_file}'."
25
+ end
26
+
27
+ config = File.read(config_file)
28
+ version = Backup::VERSION.split(".").first
29
+ unless config =~ /^# Backup v#{ version }\.x Configuration$/
30
+ raise Error, <<-EOS
31
+ Invalid Configuration File
32
+ The configuration file at '#{config_file}'
33
+ does not appear to be a Backup v#{version}.x configuration file.
34
+ If you have upgraded to v#{version}.x from a previous version,
35
+ you need to upgrade your configuration file.
36
+ Please see the instructions for upgrading in the Backup documentation.
37
+ EOS
38
+ end
39
+
40
+ dsl = DSL.new
41
+ dsl.instance_eval(config, config_file)
42
+
43
+ update(dsl._config_options) # from config.rb
44
+ update(options) # command line takes precedence
45
+
46
+ Dir[File.join(File.dirname(config_file), "models", "*.rb")].each do |model|
47
+ dsl.instance_eval(File.read(model), model)
48
+ end
49
+ end
50
+
51
+ def hostname
52
+ @hostname ||= run(utility(:hostname))
53
+ end
54
+
55
+ private
56
+
57
+ # If :root_path is set in the options, all paths will be updated.
58
+ # Otherwise, only the paths given will be updated.
59
+ def update(options = {})
60
+ root_path = options[:root_path].to_s.strip
61
+ new_root = root_path.empty? ? false : set_root_path(root_path)
62
+
63
+ DEFAULTS.each do |name, ending|
64
+ set_path_variable(name, options[name], ending, new_root)
65
+ end
66
+ end
67
+
68
+ # Sets the @root_path to the given +path+ and returns it.
69
+ # Raises an error if the given +path+ does not exist.
70
+ def set_root_path(path)
71
+ # allows #reset! to set the default @root_path,
72
+ # then use #update to set all other paths,
73
+ # without requiring that @root_path exist.
74
+ return @root_path if path == @root_path
75
+
76
+ path = File.expand_path(path)
77
+ unless File.directory?(path)
78
+ raise Error, <<-EOS
79
+ Root Path Not Found
80
+ When specifying a --root-path, the path must exist.
81
+ Path was: #{path}
82
+ EOS
83
+ end
84
+ @root_path = path
85
+ end
86
+
87
+ def set_path_variable(name, path, ending, root_path)
88
+ # strip any trailing '/' in case the user supplied this as part of
89
+ # an absolute path, so we can match it against File.expand_path()
90
+ path = path.to_s.sub(/\/\s*$/, "").lstrip
91
+ new_path = false
92
+ # If no path is given, the variable will not be set/updated
93
+ # unless a root_path was given. In which case the value will
94
+ # be updated with our default ending.
95
+ if path.empty?
96
+ new_path = File.join(root_path, ending) if root_path
97
+ else
98
+ # When a path is given, the variable will be set/updated.
99
+ # If the path is relative, it will be joined with root_path (if given),
100
+ # or expanded relative to PWD.
101
+ new_path = File.expand_path(path)
102
+ unless path == new_path
103
+ new_path = File.join(root_path, path) if root_path
104
+ end
105
+ end
106
+ instance_variable_set(:"@#{name}", new_path) if new_path
107
+ end
108
+
109
+ def reset!
110
+ @user = ENV["USER"] || Etc.getpwuid.name
111
+ @root_path = File.join(File.expand_path(ENV["HOME"] || ""), "Backup")
112
+ update(root_path: @root_path)
113
+ end
114
+ end
115
+
116
+ reset! # set defaults on load
117
+ end
118
+ end
@@ -0,0 +1,86 @@
1
+ module Backup
2
+ module Database
3
+ class Error < Backup::Error; end
4
+
5
+ class Base
6
+ include Utilities::Helpers
7
+ include Config::Helpers
8
+
9
+ attr_reader :model, :database_id, :dump_path
10
+
11
+ ##
12
+ # If given, +database_id+ will be appended to the #dump_filename.
13
+ # This is required if multiple Databases of the same class are added to
14
+ # the model.
15
+ def initialize(model, database_id = nil)
16
+ @model = model
17
+ @database_id = database_id.to_s.gsub(/\W/, "_") if database_id
18
+ @dump_path = File.join(Config.tmp_path, model.trigger, "databases")
19
+ load_defaults!
20
+ end
21
+
22
+ def perform!
23
+ log!(:started)
24
+ prepare!
25
+ end
26
+
27
+ private
28
+
29
+ def prepare!
30
+ FileUtils.mkdir_p(dump_path)
31
+ end
32
+
33
+ ##
34
+ # Sets the base filename for the final dump file to be saved in +dump_path+,
35
+ # based on the class name. e.g. databases/MySQL.sql
36
+ #
37
+ # +database_id+ will be appended if it is defined.
38
+ # e.g. databases/MySQL-database_id.sql
39
+ #
40
+ # If multiple Databases of the same class are defined and no +database_id+
41
+ # is defined, the user will be warned and one will be auto-generated.
42
+ #
43
+ # Model#initialize calls this method *after* all defined databases have
44
+ # been initialized so `backup check` can report these warnings.
45
+ def dump_filename
46
+ @dump_filename ||=
47
+ begin
48
+ unless database_id
49
+ if model.databases.select { |d| d.class == self.class }.count > 1
50
+ sleep 1
51
+ @database_id = Time.now.to_i.to_s[-5, 5]
52
+ Logger.warn Error.new(<<-EOS)
53
+ Database Identifier Missing
54
+ When multiple Databases are configured in a single Backup Model
55
+ that have the same class (MySQL, PostgreSQL, etc.), the optional
56
+ +database_id+ must be specified to uniquely identify each instance.
57
+ e.g. database MySQL, :database_id do |db|
58
+ This will result in an output file in your final backup package like:
59
+ databases/MySQL-database_id.sql
60
+
61
+ Backup has auto-generated an identifier (#{database_id}) for this
62
+ database dump and will now continue.
63
+ EOS
64
+ end
65
+ end
66
+
67
+ self.class.name.split("::").last + (database_id ? "-#{database_id}" : "")
68
+ end
69
+ end
70
+
71
+ def database_name
72
+ @database_name ||= self.class.to_s.sub("Backup::", "") +
73
+ (database_id ? " (#{database_id})" : "")
74
+ end
75
+
76
+ def log!(action)
77
+ msg =
78
+ case action
79
+ when :started then "Started..."
80
+ when :finished then "Finished!"
81
+ end
82
+ Logger.info "#{database_name} #{msg}"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,186 @@
1
+ module Backup
2
+ module Database
3
+ class MongoDB < Base
4
+ class Error < Backup::Error; end
5
+
6
+ ##
7
+ # Name of the database that needs to get dumped
8
+ attr_accessor :name
9
+
10
+ ##
11
+ # Credentials for the specified database
12
+ attr_accessor :username, :password, :authdb
13
+
14
+ ##
15
+ # Connectivity options
16
+ attr_accessor :host, :port
17
+
18
+ ##
19
+ # IPv6 support (disabled by default)
20
+ attr_accessor :ipv6
21
+
22
+ ##
23
+ # Collections to dump, collections that aren't specified won't get dumped
24
+ attr_accessor :only_collections
25
+
26
+ ##
27
+ # Additional "mongodump" options
28
+ attr_accessor :additional_options
29
+
30
+ ##
31
+ # Forces mongod to flush all pending write operations to the disk and
32
+ # locks the entire mongod instance to prevent additional writes until the
33
+ # dump is complete.
34
+ #
35
+ # Note that if Profiling is enabled, this will disable it and will not
36
+ # re-enable it after the dump is complete.
37
+ attr_accessor :lock
38
+
39
+ ##
40
+ # Creates a dump of the database that includes an oplog, to create a
41
+ # point-in-time snapshot of the state of a mongod instance.
42
+ #
43
+ # If this option is used, you would not use the `lock` option.
44
+ #
45
+ # This will only work against nodes that maintain a oplog.
46
+ # This includes all members of a replica set, as well as master nodes in
47
+ # master/slave replication deployments.
48
+ attr_accessor :oplog
49
+
50
+ def initialize(model, database_id = nil, &block)
51
+ super
52
+ instance_eval(&block) if block_given?
53
+ end
54
+
55
+ def perform!
56
+ super
57
+
58
+ lock_database if @lock
59
+ dump!
60
+ package!
61
+ ensure
62
+ unlock_database if @lock
63
+ end
64
+
65
+ private
66
+
67
+ ##
68
+ # Performs all required mongodump commands, dumping the output files
69
+ # into the +dump_packaging_path+ directory for packaging.
70
+ def dump!
71
+ FileUtils.mkdir_p dump_packaging_path
72
+
73
+ collections = Array(only_collections)
74
+ if collections.empty?
75
+ run(mongodump)
76
+ else
77
+ collections.each do |collection|
78
+ run("#{mongodump} --collection='#{collection}'")
79
+ end
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Creates a tar archive of the +dump_packaging_path+ directory
85
+ # and stores it in the +dump_path+ using +dump_filename+.
86
+ #
87
+ # <trigger>/databases/MongoDB[-<database_id>].tar[.gz]
88
+ #
89
+ # If successful, +dump_packaging_path+ is removed.
90
+ def package!
91
+ pipeline = Pipeline.new
92
+ dump_ext = "tar"
93
+
94
+ pipeline << "#{utility(:tar)} -cf - " \
95
+ "-C '#{dump_path}' '#{dump_filename}'"
96
+
97
+ if model.compressor
98
+ model.compressor.compress_with do |command, ext|
99
+ pipeline << command
100
+ dump_ext << ext
101
+ end
102
+ end
103
+
104
+ pipeline << "#{utility(:cat)} > " \
105
+ "'#{File.join(dump_path, dump_filename)}.#{dump_ext}'"
106
+
107
+ pipeline.run
108
+ if pipeline.success?
109
+ FileUtils.rm_rf dump_packaging_path
110
+ log!(:finished)
111
+ else
112
+ raise Error, "Dump Failed!\n#{pipeline.error_messages}"
113
+ end
114
+ end
115
+
116
+ def dump_packaging_path
117
+ File.join(dump_path, dump_filename)
118
+ end
119
+
120
+ def mongodump
121
+ "#{utility(:mongodump)} #{name_option} #{credential_options} " \
122
+ "#{connectivity_options} #{ipv6_option} #{oplog_option} " \
123
+ "#{user_options} --out='#{dump_packaging_path}'"
124
+ end
125
+
126
+ def name_option
127
+ return unless name
128
+ "--db='#{name}'"
129
+ end
130
+
131
+ def credential_options
132
+ opts = []
133
+ opts << "--username='#{username}'" if username
134
+ opts << "--password='#{password}'" if password
135
+ opts << "--authenticationDatabase='#{authdb}'" if authdb
136
+ opts.join(" ")
137
+ end
138
+
139
+ def connectivity_options
140
+ opts = []
141
+ opts << "--host='#{host}'" if host
142
+ opts << "--port='#{port}'" if port
143
+ opts.join(" ")
144
+ end
145
+
146
+ def ipv6_option
147
+ "--ipv6" if ipv6
148
+ end
149
+
150
+ def oplog_option
151
+ "--oplog" if oplog
152
+ end
153
+
154
+ def user_options
155
+ Array(additional_options).join(" ")
156
+ end
157
+
158
+ def lock_database
159
+ lock_command = <<-EOS.gsub(/^ +/, "")
160
+ echo 'use admin
161
+ db.setProfilingLevel(0)
162
+ db.fsyncLock()' | #{mongo_shell}
163
+ EOS
164
+
165
+ run(lock_command)
166
+ end
167
+
168
+ def unlock_database
169
+ unlock_command = <<-EOS.gsub(/^ +/, "")
170
+ echo 'use admin
171
+ db.fsyncUnlock()' | #{mongo_shell}
172
+ EOS
173
+
174
+ run(unlock_command)
175
+ end
176
+
177
+ def mongo_shell
178
+ cmd = "#{utility(:mongo)} #{connectivity_options}".rstrip
179
+ cmd << " #{credential_options}".rstrip
180
+ cmd << " #{ipv6_option}".rstrip
181
+ cmd << " '#{name}'" if name
182
+ cmd
183
+ end
184
+ end
185
+ end
186
+ end