cm-backup 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -0
  3. data/bin/backup +5 -0
  4. data/lib/backup.rb +144 -0
  5. data/lib/backup/archive.rb +170 -0
  6. data/lib/backup/binder.rb +22 -0
  7. data/lib/backup/cleaner.rb +116 -0
  8. data/lib/backup/cli.rb +374 -0
  9. data/lib/backup/cloud_io/base.rb +41 -0
  10. data/lib/backup/cloud_io/cloud_files.rb +298 -0
  11. data/lib/backup/cloud_io/s3.rb +260 -0
  12. data/lib/backup/compressor/base.rb +35 -0
  13. data/lib/backup/compressor/bzip2.rb +39 -0
  14. data/lib/backup/compressor/custom.rb +53 -0
  15. data/lib/backup/compressor/gzip.rb +74 -0
  16. data/lib/backup/config.rb +119 -0
  17. data/lib/backup/config/dsl.rb +103 -0
  18. data/lib/backup/config/helpers.rb +143 -0
  19. data/lib/backup/database/base.rb +85 -0
  20. data/lib/backup/database/mongodb.rb +187 -0
  21. data/lib/backup/database/mysql.rb +192 -0
  22. data/lib/backup/database/openldap.rb +95 -0
  23. data/lib/backup/database/postgresql.rb +133 -0
  24. data/lib/backup/database/redis.rb +179 -0
  25. data/lib/backup/database/riak.rb +82 -0
  26. data/lib/backup/database/sqlite.rb +57 -0
  27. data/lib/backup/encryptor/base.rb +29 -0
  28. data/lib/backup/encryptor/gpg.rb +747 -0
  29. data/lib/backup/encryptor/open_ssl.rb +77 -0
  30. data/lib/backup/errors.rb +58 -0
  31. data/lib/backup/logger.rb +199 -0
  32. data/lib/backup/logger/console.rb +51 -0
  33. data/lib/backup/logger/fog_adapter.rb +29 -0
  34. data/lib/backup/logger/logfile.rb +133 -0
  35. data/lib/backup/logger/syslog.rb +116 -0
  36. data/lib/backup/model.rb +479 -0
  37. data/lib/backup/notifier/base.rb +128 -0
  38. data/lib/backup/notifier/campfire.rb +63 -0
  39. data/lib/backup/notifier/command.rb +102 -0
  40. data/lib/backup/notifier/datadog.rb +107 -0
  41. data/lib/backup/notifier/flowdock.rb +103 -0
  42. data/lib/backup/notifier/hipchat.rb +118 -0
  43. data/lib/backup/notifier/http_post.rb +117 -0
  44. data/lib/backup/notifier/mail.rb +249 -0
  45. data/lib/backup/notifier/nagios.rb +69 -0
  46. data/lib/backup/notifier/pagerduty.rb +81 -0
  47. data/lib/backup/notifier/prowl.rb +68 -0
  48. data/lib/backup/notifier/pushover.rb +74 -0
  49. data/lib/backup/notifier/ses.rb +105 -0
  50. data/lib/backup/notifier/slack.rb +148 -0
  51. data/lib/backup/notifier/twitter.rb +58 -0
  52. data/lib/backup/notifier/zabbix.rb +63 -0
  53. data/lib/backup/package.rb +55 -0
  54. data/lib/backup/packager.rb +107 -0
  55. data/lib/backup/pipeline.rb +124 -0
  56. data/lib/backup/splitter.rb +76 -0
  57. data/lib/backup/storage/base.rb +69 -0
  58. data/lib/backup/storage/cloud_files.rb +158 -0
  59. data/lib/backup/storage/cycler.rb +75 -0
  60. data/lib/backup/storage/dropbox.rb +212 -0
  61. data/lib/backup/storage/ftp.rb +112 -0
  62. data/lib/backup/storage/local.rb +64 -0
  63. data/lib/backup/storage/qiniu.rb +65 -0
  64. data/lib/backup/storage/rsync.rb +248 -0
  65. data/lib/backup/storage/s3.rb +156 -0
  66. data/lib/backup/storage/scp.rb +67 -0
  67. data/lib/backup/storage/sftp.rb +82 -0
  68. data/lib/backup/syncer/base.rb +70 -0
  69. data/lib/backup/syncer/cloud/base.rb +179 -0
  70. data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
  71. data/lib/backup/syncer/cloud/local_file.rb +100 -0
  72. data/lib/backup/syncer/cloud/s3.rb +110 -0
  73. data/lib/backup/syncer/rsync/base.rb +54 -0
  74. data/lib/backup/syncer/rsync/local.rb +31 -0
  75. data/lib/backup/syncer/rsync/pull.rb +51 -0
  76. data/lib/backup/syncer/rsync/push.rb +205 -0
  77. data/lib/backup/template.rb +46 -0
  78. data/lib/backup/utilities.rb +224 -0
  79. data/lib/backup/version.rb +5 -0
  80. data/templates/cli/archive +28 -0
  81. data/templates/cli/compressor/bzip2 +4 -0
  82. data/templates/cli/compressor/custom +7 -0
  83. data/templates/cli/compressor/gzip +4 -0
  84. data/templates/cli/config +123 -0
  85. data/templates/cli/databases/mongodb +15 -0
  86. data/templates/cli/databases/mysql +18 -0
  87. data/templates/cli/databases/openldap +24 -0
  88. data/templates/cli/databases/postgresql +16 -0
  89. data/templates/cli/databases/redis +16 -0
  90. data/templates/cli/databases/riak +17 -0
  91. data/templates/cli/databases/sqlite +11 -0
  92. data/templates/cli/encryptor/gpg +27 -0
  93. data/templates/cli/encryptor/openssl +9 -0
  94. data/templates/cli/model +26 -0
  95. data/templates/cli/notifier/zabbix +15 -0
  96. data/templates/cli/notifiers/campfire +12 -0
  97. data/templates/cli/notifiers/command +32 -0
  98. data/templates/cli/notifiers/datadog +57 -0
  99. data/templates/cli/notifiers/flowdock +16 -0
  100. data/templates/cli/notifiers/hipchat +16 -0
  101. data/templates/cli/notifiers/http_post +32 -0
  102. data/templates/cli/notifiers/mail +24 -0
  103. data/templates/cli/notifiers/nagios +13 -0
  104. data/templates/cli/notifiers/pagerduty +12 -0
  105. data/templates/cli/notifiers/prowl +11 -0
  106. data/templates/cli/notifiers/pushover +11 -0
  107. data/templates/cli/notifiers/ses +15 -0
  108. data/templates/cli/notifiers/slack +22 -0
  109. data/templates/cli/notifiers/twitter +13 -0
  110. data/templates/cli/splitter +7 -0
  111. data/templates/cli/storages/cloud_files +11 -0
  112. data/templates/cli/storages/dropbox +20 -0
  113. data/templates/cli/storages/ftp +13 -0
  114. data/templates/cli/storages/local +8 -0
  115. data/templates/cli/storages/qiniu +12 -0
  116. data/templates/cli/storages/rsync +17 -0
  117. data/templates/cli/storages/s3 +16 -0
  118. data/templates/cli/storages/scp +15 -0
  119. data/templates/cli/storages/sftp +15 -0
  120. data/templates/cli/syncers/cloud_files +22 -0
  121. data/templates/cli/syncers/rsync_local +20 -0
  122. data/templates/cli/syncers/rsync_pull +28 -0
  123. data/templates/cli/syncers/rsync_push +28 -0
  124. data/templates/cli/syncers/s3 +27 -0
  125. data/templates/general/links +3 -0
  126. data/templates/general/version.erb +2 -0
  127. data/templates/notifier/mail/failure.erb +16 -0
  128. data/templates/notifier/mail/success.erb +16 -0
  129. data/templates/notifier/mail/warning.erb +16 -0
  130. data/templates/storage/dropbox/authorization_url.erb +6 -0
  131. data/templates/storage/dropbox/authorized.erb +4 -0
  132. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  133. metadata +1077 -0
@@ -0,0 +1,119 @@
1
+ # encoding: utf-8
2
+ require 'backup/config/dsl'
3
+ require 'backup/config/helpers'
4
+
5
+ module Backup
6
+ module Config
7
+ class Error < Backup::Error; end
8
+
9
+ DEFAULTS = {
10
+ :config_file => 'config.rb',
11
+ :data_path => '.data',
12
+ :tmp_path => '.tmp'
13
+ }
14
+
15
+ class << self
16
+ include Utilities::Helpers
17
+
18
+ attr_reader :user, :root_path, :config_file, :data_path, :tmp_path
19
+
20
+ # Loads the user's +config.rb+ and all model files.
21
+ def load(options = {})
22
+ update(options) # from the command line
23
+
24
+ unless File.exist?(config_file)
25
+ raise Error, "Could not find configuration file: '#{config_file}'."
26
+ end
27
+
28
+ config = File.read(config_file)
29
+ version = Backup::VERSION.split('.').first
30
+ unless config =~ /^# Backup v#{ version }\.x Configuration$/
31
+ raise Error, <<-EOS
32
+ Invalid Configuration File
33
+ The configuration file at '#{ config_file }'
34
+ does not appear to be a Backup v#{ version }.x configuration file.
35
+ If you have upgraded to v#{ version }.x from a previous version,
36
+ you need to upgrade your configuration file.
37
+ Please see the instructions for upgrading in the Backup documentation.
38
+ EOS
39
+ end
40
+
41
+ dsl = DSL.new
42
+ dsl.instance_eval(config, config_file)
43
+
44
+ update(dsl._config_options) # from config.rb
45
+ update(options) # command line takes precedence
46
+
47
+ Dir[File.join(File.dirname(config_file), 'models', '*.rb')].each do |model|
48
+ dsl.instance_eval(File.read(model), model)
49
+ end
50
+ end
51
+
52
+ def hostname
53
+ @hostname ||= run(utility(:hostname))
54
+ end
55
+
56
+ private
57
+
58
+ # If :root_path is set in the options, all paths will be updated.
59
+ # Otherwise, only the paths given will be updated.
60
+ def update(options = {})
61
+ root_path = options[:root_path].to_s.strip
62
+ new_root = root_path.empty? ? false : set_root_path(root_path)
63
+
64
+ DEFAULTS.each do |name, ending|
65
+ set_path_variable(name, options[name], ending, new_root)
66
+ end
67
+ end
68
+
69
+ # Sets the @root_path to the given +path+ and returns it.
70
+ # Raises an error if the given +path+ does not exist.
71
+ def set_root_path(path)
72
+ # allows #reset! to set the default @root_path,
73
+ # then use #update to set all other paths,
74
+ # without requiring that @root_path exist.
75
+ return @root_path if path == @root_path
76
+
77
+ path = File.expand_path(path)
78
+ unless File.directory?(path)
79
+ raise Error, <<-EOS
80
+ Root Path Not Found
81
+ When specifying a --root-path, the path must exist.
82
+ Path was: #{ path }
83
+ EOS
84
+ end
85
+ @root_path = path
86
+ end
87
+
88
+ def set_path_variable(name, path, ending, root_path)
89
+ # strip any trailing '/' in case the user supplied this as part of
90
+ # an absolute path, so we can match it against File.expand_path()
91
+ path = path.to_s.sub(/\/\s*$/, '').lstrip
92
+ new_path = false
93
+ # If no path is given, the variable will not be set/updated
94
+ # unless a root_path was given. In which case the value will
95
+ # be updated with our default ending.
96
+ if path.empty?
97
+ new_path = File.join(root_path, ending) if root_path
98
+ else
99
+ # When a path is given, the variable will be set/updated.
100
+ # If the path is relative, it will be joined with root_path (if given),
101
+ # or expanded relative to PWD.
102
+ new_path = File.expand_path(path)
103
+ unless path == new_path
104
+ new_path = File.join(root_path, path) if root_path
105
+ end
106
+ end
107
+ instance_variable_set(:"@#{name}", new_path) if new_path
108
+ end
109
+
110
+ def reset!
111
+ @user = ENV['USER'] || Etc.getpwuid.name
112
+ @root_path = File.join(File.expand_path(ENV['HOME'] || ''), 'Backup')
113
+ update(:root_path => @root_path)
114
+ end
115
+ end
116
+
117
+ reset! # set defaults on load
118
+ end
119
+ end
@@ -0,0 +1,103 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Config
5
+ # Context for loading user config.rb and model files.
6
+ class DSL
7
+ class Error < Backup::Error; end
8
+ Model = Backup::Model
9
+
10
+ class << self
11
+ private
12
+
13
+ # List the available database, storage, syncer, compressor, encryptor
14
+ # and notifier constants. These are used to define constant names within
15
+ # Backup::Config::DSL so that users may use a constant instead of a string.
16
+ # Nested namespaces are represented using Hashs. Deep nesting supported.
17
+ #
18
+ # Example, instead of:
19
+ # database "MySQL" do |mysql|
20
+ # sync_with "RSync::Local" do |rsync|
21
+ #
22
+ # You can do:
23
+ # database MySQL do |mysql|
24
+ # sync_with RSync::Local do |rsync|
25
+ #
26
+ def add_dsl_constants
27
+ create_modules(
28
+ DSL,
29
+ [ # Databases
30
+ ['MySQL', 'PostgreSQL', 'MongoDB', 'Redis', 'Riak', 'OpenLDAP', 'SQLite'],
31
+ # Storages
32
+ ['S3', 'CloudFiles', 'Ninefold', 'Dropbox', 'FTP',
33
+ 'SFTP', 'SCP', 'RSync', 'Local', 'Qiniu'],
34
+ # Compressors
35
+ ['Gzip', 'Bzip2', 'Custom', 'Pbzip2', 'Lzma'],
36
+ # Encryptors
37
+ ['OpenSSL', 'GPG'],
38
+ # Syncers
39
+ [
40
+ { 'Cloud' => ['CloudFiles', 'S3'] },
41
+ { 'RSync' => ['Push', 'Pull', 'Local'] }
42
+ ],
43
+ # Notifiers
44
+ ['Mail', 'Twitter', 'Campfire', 'Prowl',
45
+ 'Hipchat', 'PagerDuty', 'Pushover', 'HttpPost', 'Nagios',
46
+ 'Slack', 'FlowDock', 'Zabbix', 'Ses', 'DataDog', 'Command']
47
+ ]
48
+ )
49
+ end
50
+
51
+ def create_modules(scope, names)
52
+ names.flatten.each do |name|
53
+ if name.is_a?(Hash)
54
+ name.each do |key, val|
55
+ create_modules(get_or_create_empty_module(scope, key), [val])
56
+ end
57
+ else
58
+ get_or_create_empty_module(scope, name)
59
+ end
60
+ end
61
+ end
62
+
63
+ def get_or_create_empty_module(scope, const)
64
+ if scope.const_defined?(const)
65
+ scope.const_get(const)
66
+ else
67
+ scope.const_set(const, Module.new)
68
+ end
69
+ end
70
+ end
71
+
72
+ add_dsl_constants # add constants on load
73
+
74
+ attr_reader :_config_options
75
+
76
+ def initialize
77
+ @_config_options = {}
78
+ end
79
+
80
+ # Allow users to set command line path options in config.rb
81
+ [:root_path, :data_path, :tmp_path].each do |name|
82
+ define_method name, lambda {|path| _config_options[name] = path }
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
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,143 @@
1
+ # encoding: utf-8
2
+ require 'ostruct'
3
+
4
+ module Backup
5
+ module Config
6
+ module Helpers
7
+
8
+ def self.included(klass)
9
+ klass.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def defaults
15
+ @defaults ||= Config::Defaults.new
16
+
17
+ if block_given?
18
+ yield @defaults
19
+ else
20
+ @defaults
21
+ end
22
+ end
23
+
24
+ # Used only within the specs
25
+ def clear_defaults!
26
+ defaults.reset!
27
+ end
28
+
29
+ def deprecations
30
+ @deprecations ||= {}
31
+ end
32
+
33
+ def log_deprecation_warning(name, deprecation)
34
+ msg = "#{ self }##{ name } has been deprecated as of " +
35
+ "backup v.#{ deprecation[:version] }"
36
+ msg << "\n#{ deprecation[:message] }" if deprecation[:message]
37
+ Logger.warn Config::Error.new(<<-EOS)
38
+ [DEPRECATION WARNING]
39
+ #{ msg }
40
+ EOS
41
+ end
42
+
43
+ protected
44
+
45
+ ##
46
+ # Method to deprecate an attribute.
47
+ #
48
+ # :version
49
+ # Must be set to the backup version which will first
50
+ # introduce the deprecation.
51
+ #
52
+ # :action
53
+ # If set, this Proc will be called with a reference to the
54
+ # class instance and the value set on the deprecated accessor.
55
+ # e.g. deprecation[:action].call(klass, value)
56
+ # This should perform whatever action is neccessary, such as
57
+ # transferring the value to a new accessor.
58
+ #
59
+ # :message
60
+ # If set, this will be appended to #log_deprecation_warning
61
+ #
62
+ # Note that this replaces the `attr_accessor` method, or other
63
+ # method previously used to set the accessor being deprecated.
64
+ # #method_missing will handle any calls to `name=`.
65
+ #
66
+ def attr_deprecate(name, args = {})
67
+ deprecations[name] = {
68
+ :version => nil,
69
+ :message => nil,
70
+ :action => nil
71
+ }.merge(args)
72
+ end
73
+
74
+ end # ClassMethods
75
+
76
+ private
77
+
78
+ ##
79
+ # Sets any pre-configured default values.
80
+ # If a default value was set for an invalid accessor,
81
+ # this will raise a NameError.
82
+ def load_defaults!
83
+ self.class.defaults._attributes.each do |name|
84
+ val = self.class.defaults.send(name)
85
+ val = val.dup rescue val
86
+ send(:"#{ name }=", val)
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Check missing methods for deprecated attribute accessors.
92
+ #
93
+ # If a value is set on an accessor that has been deprecated
94
+ # using #attr_deprecate, a warning will be issued and any
95
+ # :action (Proc) specified will be called with a reference to
96
+ # the class instance and the value set on the deprecated accessor.
97
+ # See #attr_deprecate and #log_deprecation_warning
98
+ #
99
+ # Note that OpenStruct (used for setting defaults) does not allow
100
+ # multiple arguments when assigning values for members.
101
+ # So, we won't allow it here either, even though an attr_accessor
102
+ # will accept and convert them into an Array. Therefore, setting
103
+ # an option value using multiple values, whether as a default or
104
+ # directly on the class' accessor, should not be supported.
105
+ # i.e. if an option will accept being set as an Array, then it
106
+ # should be explicitly set as such. e.g. option = [val1, val2]
107
+ #
108
+ def method_missing(name, *args)
109
+ deprecation = nil
110
+ if method = name.to_s.chomp!('=')
111
+ if (len = args.count) != 1
112
+ raise ArgumentError,
113
+ "wrong number of arguments (#{ len } for 1)", caller(1)
114
+ end
115
+ deprecation = self.class.deprecations[method.to_sym]
116
+ end
117
+
118
+ if deprecation
119
+ self.class.log_deprecation_warning(method, deprecation)
120
+ deprecation[:action].call(self, args[0]) if deprecation[:action]
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ end # Helpers
127
+
128
+ # Store for pre-configured defaults.
129
+ class Defaults < OpenStruct
130
+ # Returns an Array of all attribute method names
131
+ # that default values were set for.
132
+ def _attributes
133
+ @table.keys
134
+ end
135
+
136
+ # Used only within the specs
137
+ def reset!
138
+ @table.clear
139
+ end
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Database
5
+ class Error < Backup::Error; end
6
+
7
+ class Base
8
+ include Utilities::Helpers
9
+ include Config::Helpers
10
+
11
+ attr_reader :model, :database_id, :dump_path
12
+
13
+ ##
14
+ # If given, +database_id+ will be appended to the #dump_filename.
15
+ # This is required if multiple Databases of the same class are added to
16
+ # the model.
17
+ def initialize(model, database_id = nil)
18
+ @model = model
19
+ @database_id = database_id.to_s.gsub(/\W/, '_') if database_id
20
+ @dump_path = File.join(Config.tmp_path, model.trigger, 'databases')
21
+ load_defaults!
22
+ end
23
+
24
+ def perform!
25
+ log!(:started)
26
+ prepare!
27
+ end
28
+
29
+ private
30
+
31
+ def prepare!
32
+ FileUtils.mkdir_p(dump_path)
33
+ end
34
+
35
+ ##
36
+ # Sets the base filename for the final dump file to be saved in +dump_path+,
37
+ # based on the class name. e.g. databases/MySQL.sql
38
+ #
39
+ # +database_id+ will be appended if it is defined.
40
+ # e.g. databases/MySQL-database_id.sql
41
+ #
42
+ # If multiple Databases of the same class are defined and no +database_id+
43
+ # is defined, the user will be warned and one will be auto-generated.
44
+ #
45
+ # Model#initialize calls this method *after* all defined databases have
46
+ # been initialized so `backup check` can report these warnings.
47
+ def dump_filename
48
+ @dump_filename ||= begin
49
+ unless database_id
50
+ if model.databases.select {|d| d.class == self.class }.count > 1
51
+ sleep 1; @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 = case action
78
+ when :started then 'Started...'
79
+ when :finished then 'Finished!'
80
+ end
81
+ Logger.info "#{ database_name } #{ msg }"
82
+ end
83
+ end
84
+ end
85
+ end