sliday_backup 0.1

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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +29 -0
  3. data/bin/sliday_backup +5 -0
  4. data/lib/sliday_backup.rb +147 -0
  5. data/lib/sliday_backup/archive.rb +170 -0
  6. data/lib/sliday_backup/binder.rb +22 -0
  7. data/lib/sliday_backup/cleaner.rb +116 -0
  8. data/lib/sliday_backup/cli.rb +374 -0
  9. data/lib/sliday_backup/cloud_io/base.rb +41 -0
  10. data/lib/sliday_backup/cloud_io/cloud_files.rb +298 -0
  11. data/lib/sliday_backup/cloud_io/s3.rb +260 -0
  12. data/lib/sliday_backup/compressor/base.rb +35 -0
  13. data/lib/sliday_backup/compressor/bzip2.rb +39 -0
  14. data/lib/sliday_backup/compressor/custom.rb +53 -0
  15. data/lib/sliday_backup/compressor/gzip.rb +74 -0
  16. data/lib/sliday_backup/config.rb +119 -0
  17. data/lib/sliday_backup/config/dsl.rb +103 -0
  18. data/lib/sliday_backup/config/helpers.rb +143 -0
  19. data/lib/sliday_backup/database/base.rb +86 -0
  20. data/lib/sliday_backup/database/mongodb.rb +187 -0
  21. data/lib/sliday_backup/database/mysql.rb +192 -0
  22. data/lib/sliday_backup/database/openldap.rb +95 -0
  23. data/lib/sliday_backup/database/postgresql.rb +133 -0
  24. data/lib/sliday_backup/database/redis.rb +179 -0
  25. data/lib/sliday_backup/database/riak.rb +82 -0
  26. data/lib/sliday_backup/database/sqlite.rb +57 -0
  27. data/lib/sliday_backup/encryptor/base.rb +29 -0
  28. data/lib/sliday_backup/encryptor/gpg.rb +747 -0
  29. data/lib/sliday_backup/encryptor/open_ssl.rb +77 -0
  30. data/lib/sliday_backup/errors.rb +58 -0
  31. data/lib/sliday_backup/logger.rb +199 -0
  32. data/lib/sliday_backup/logger/console.rb +51 -0
  33. data/lib/sliday_backup/logger/fog_adapter.rb +29 -0
  34. data/lib/sliday_backup/logger/logfile.rb +133 -0
  35. data/lib/sliday_backup/logger/syslog.rb +116 -0
  36. data/lib/sliday_backup/model.rb +479 -0
  37. data/lib/sliday_backup/notifier/base.rb +128 -0
  38. data/lib/sliday_backup/notifier/campfire.rb +63 -0
  39. data/lib/sliday_backup/notifier/command.rb +99 -0
  40. data/lib/sliday_backup/notifier/datadog.rb +107 -0
  41. data/lib/sliday_backup/notifier/flowdock.rb +103 -0
  42. data/lib/sliday_backup/notifier/hipchat.rb +112 -0
  43. data/lib/sliday_backup/notifier/http_post.rb +117 -0
  44. data/lib/sliday_backup/notifier/mail.rb +244 -0
  45. data/lib/sliday_backup/notifier/nagios.rb +69 -0
  46. data/lib/sliday_backup/notifier/pagerduty.rb +81 -0
  47. data/lib/sliday_backup/notifier/prowl.rb +68 -0
  48. data/lib/sliday_backup/notifier/pushover.rb +74 -0
  49. data/lib/sliday_backup/notifier/ses.rb +88 -0
  50. data/lib/sliday_backup/notifier/slack.rb +148 -0
  51. data/lib/sliday_backup/notifier/twitter.rb +58 -0
  52. data/lib/sliday_backup/notifier/zabbix.rb +63 -0
  53. data/lib/sliday_backup/package.rb +55 -0
  54. data/lib/sliday_backup/packager.rb +107 -0
  55. data/lib/sliday_backup/pipeline.rb +124 -0
  56. data/lib/sliday_backup/splitter.rb +76 -0
  57. data/lib/sliday_backup/storage/base.rb +69 -0
  58. data/lib/sliday_backup/storage/cloud_files.rb +158 -0
  59. data/lib/sliday_backup/storage/cycler.rb +75 -0
  60. data/lib/sliday_backup/storage/dropbox.rb +212 -0
  61. data/lib/sliday_backup/storage/ftp.rb +112 -0
  62. data/lib/sliday_backup/storage/local.rb +64 -0
  63. data/lib/sliday_backup/storage/qiniu.rb +65 -0
  64. data/lib/sliday_backup/storage/rsync.rb +248 -0
  65. data/lib/sliday_backup/storage/s3.rb +156 -0
  66. data/lib/sliday_backup/storage/scp.rb +67 -0
  67. data/lib/sliday_backup/storage/sftp.rb +82 -0
  68. data/lib/sliday_backup/storage/sliday_storage.rb +79 -0
  69. data/lib/sliday_backup/syncer/base.rb +70 -0
  70. data/lib/sliday_backup/syncer/cloud/base.rb +179 -0
  71. data/lib/sliday_backup/syncer/cloud/cloud_files.rb +83 -0
  72. data/lib/sliday_backup/syncer/cloud/local_file.rb +100 -0
  73. data/lib/sliday_backup/syncer/cloud/s3.rb +110 -0
  74. data/lib/sliday_backup/syncer/rsync/base.rb +54 -0
  75. data/lib/sliday_backup/syncer/rsync/local.rb +31 -0
  76. data/lib/sliday_backup/syncer/rsync/pull.rb +51 -0
  77. data/lib/sliday_backup/syncer/rsync/push.rb +205 -0
  78. data/lib/sliday_backup/template.rb +46 -0
  79. data/lib/sliday_backup/utilities.rb +224 -0
  80. data/lib/sliday_backup/version.rb +5 -0
  81. data/templates/cli/archive +28 -0
  82. data/templates/cli/compressor/bzip2 +4 -0
  83. data/templates/cli/compressor/custom +7 -0
  84. data/templates/cli/compressor/gzip +4 -0
  85. data/templates/cli/config +123 -0
  86. data/templates/cli/databases/mongodb +15 -0
  87. data/templates/cli/databases/mysql +18 -0
  88. data/templates/cli/databases/openldap +24 -0
  89. data/templates/cli/databases/postgresql +16 -0
  90. data/templates/cli/databases/redis +16 -0
  91. data/templates/cli/databases/riak +17 -0
  92. data/templates/cli/databases/sqlite +11 -0
  93. data/templates/cli/encryptor/gpg +27 -0
  94. data/templates/cli/encryptor/openssl +9 -0
  95. data/templates/cli/model +26 -0
  96. data/templates/cli/notifier/zabbix +15 -0
  97. data/templates/cli/notifiers/campfire +12 -0
  98. data/templates/cli/notifiers/command +32 -0
  99. data/templates/cli/notifiers/datadog +57 -0
  100. data/templates/cli/notifiers/flowdock +16 -0
  101. data/templates/cli/notifiers/hipchat +16 -0
  102. data/templates/cli/notifiers/http_post +32 -0
  103. data/templates/cli/notifiers/mail +24 -0
  104. data/templates/cli/notifiers/nagios +13 -0
  105. data/templates/cli/notifiers/pagerduty +12 -0
  106. data/templates/cli/notifiers/prowl +11 -0
  107. data/templates/cli/notifiers/pushover +11 -0
  108. data/templates/cli/notifiers/ses +15 -0
  109. data/templates/cli/notifiers/slack +22 -0
  110. data/templates/cli/notifiers/twitter +13 -0
  111. data/templates/cli/splitter +7 -0
  112. data/templates/cli/storages/cloud_files +11 -0
  113. data/templates/cli/storages/dropbox +20 -0
  114. data/templates/cli/storages/ftp +13 -0
  115. data/templates/cli/storages/local +8 -0
  116. data/templates/cli/storages/qiniu +12 -0
  117. data/templates/cli/storages/rsync +17 -0
  118. data/templates/cli/storages/s3 +16 -0
  119. data/templates/cli/storages/scp +15 -0
  120. data/templates/cli/storages/sftp +15 -0
  121. data/templates/cli/storages/sliday_storage +6 -0
  122. data/templates/cli/syncers/cloud_files +22 -0
  123. data/templates/cli/syncers/rsync_local +20 -0
  124. data/templates/cli/syncers/rsync_pull +28 -0
  125. data/templates/cli/syncers/rsync_push +28 -0
  126. data/templates/cli/syncers/s3 +27 -0
  127. data/templates/general/links +3 -0
  128. data/templates/general/version.erb +2 -0
  129. data/templates/notifier/mail/failure.erb +16 -0
  130. data/templates/notifier/mail/success.erb +16 -0
  131. data/templates/notifier/mail/warning.erb +16 -0
  132. data/templates/storage/dropbox/authorization_url.erb +6 -0
  133. data/templates/storage/dropbox/authorized.erb +4 -0
  134. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  135. metadata +1079 -0
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+
3
+ module SlidayBackup
4
+ module Syncer
5
+ module Cloud
6
+ class Error < SlidayBackup::Error; end
7
+
8
+ class Base < Syncer::Base
9
+ MUTEX = Mutex.new
10
+
11
+ ##
12
+ # Number of threads to use for concurrency.
13
+ #
14
+ # Default: 0 (no concurrency)
15
+ attr_accessor :thread_count
16
+
17
+ ##
18
+ # Number of times to retry failed operations.
19
+ #
20
+ # Default: 10
21
+ attr_accessor :max_retries
22
+
23
+ ##
24
+ # Time in seconds to pause before each retry.
25
+ #
26
+ # Default: 30
27
+ attr_accessor :retry_waitsec
28
+
29
+ def initialize(syncer_id = nil, &block)
30
+ super
31
+ instance_eval(&block) if block_given?
32
+
33
+ @thread_count ||= 0
34
+ @max_retries ||= 10
35
+ @retry_waitsec ||= 30
36
+
37
+ @path ||= 'backups'
38
+ @path = path.sub(/^\//, '')
39
+ end
40
+
41
+ def perform!
42
+ log!(:started)
43
+ @transfer_count = 0
44
+ @unchanged_count = 0
45
+ @skipped_count = 0
46
+ @orphans = thread_count > 0 ? Queue.new : []
47
+
48
+ directories.each {|dir| sync_directory(dir) }
49
+ orphans_result = process_orphans
50
+
51
+ Logger.info "\nSummary:"
52
+ Logger.info "\s\sTransferred Files: #{ @transfer_count }"
53
+ Logger.info "\s\s#{ orphans_result }"
54
+ Logger.info "\s\sUnchanged Files: #{ @unchanged_count }"
55
+ Logger.warn "\s\sSkipped Files: #{ @skipped_count }" if @skipped_count > 0
56
+ log!(:finished)
57
+ end
58
+
59
+ private
60
+
61
+ def sync_directory(dir)
62
+ remote_base = path.empty? ? File.basename(dir) :
63
+ File.join(path, File.basename(dir))
64
+ Logger.info "Gathering remote data for '#{ remote_base }'..."
65
+ remote_files = get_remote_files(remote_base)
66
+
67
+ Logger.info("Gathering local data for '#{ File.expand_path(dir) }'...")
68
+ local_files = LocalFile.find(dir, excludes)
69
+
70
+ relative_paths = (local_files.keys | remote_files.keys).sort
71
+ if relative_paths.empty?
72
+ Logger.info 'No local or remote files found'
73
+ else
74
+ Logger.info 'Syncing...'
75
+ sync_block = Proc.new do |relative_path|
76
+ local_file = local_files[relative_path]
77
+ remote_md5 = remote_files[relative_path]
78
+ remote_path = File.join(remote_base, relative_path)
79
+ sync_file(local_file, remote_path, remote_md5)
80
+ end
81
+
82
+ if thread_count > 0
83
+ sync_in_threads(relative_paths, sync_block)
84
+ else
85
+ relative_paths.each(&sync_block)
86
+ end
87
+ end
88
+ end
89
+
90
+ def sync_in_threads(relative_paths, sync_block)
91
+ queue = Queue.new
92
+ queue << relative_paths.shift until relative_paths.empty?
93
+ num_threads = [thread_count, queue.size].min
94
+ Logger.info "\s\sUsing #{ num_threads } Threads"
95
+ threads = num_threads.times.map do
96
+ Thread.new do
97
+ loop do
98
+ path = queue.shift(true) rescue nil
99
+ path ? sync_block.call(path) : break
100
+ end
101
+ end
102
+ end
103
+
104
+ # abort if any thread raises an exception
105
+ while threads.any?(&:alive?)
106
+ if threads.any? {|thr| thr.status.nil? }
107
+ threads.each(&:kill)
108
+ Thread.pass while threads.any?(&:alive?)
109
+ break
110
+ end
111
+ sleep num_threads * 0.1
112
+ end
113
+ threads.each(&:join)
114
+ end
115
+
116
+ # If an exception is raised in multiple threads, only the exception
117
+ # raised in the first thread that Thread#join is called on will be
118
+ # handled. So all exceptions are logged first with their details,
119
+ # then a generic exception is raised.
120
+ def sync_file(local_file, remote_path, remote_md5)
121
+ if local_file && File.exist?(local_file.path)
122
+ if local_file.md5 == remote_md5
123
+ MUTEX.synchronize { @unchanged_count += 1 }
124
+ else
125
+ Logger.info("\s\s[transferring] '#{ remote_path }'")
126
+ begin
127
+ cloud_io.upload(local_file.path, remote_path)
128
+ MUTEX.synchronize { @transfer_count += 1 }
129
+ rescue CloudIO::FileSizeError => err
130
+ MUTEX.synchronize { @skipped_count += 1 }
131
+ Logger.warn Error.wrap(err, "Skipping '#{ remote_path }'")
132
+ rescue => err
133
+ Logger.error(err)
134
+ raise Error, <<-EOS
135
+ Syncer Failed!
136
+ See the Retry [info] and [error] messages (if any)
137
+ for details on each failed operation.
138
+ EOS
139
+ end
140
+ end
141
+ elsif remote_md5
142
+ @orphans << remote_path
143
+ end
144
+ end
145
+
146
+ def process_orphans
147
+ if @orphans.empty?
148
+ return mirror ? 'Deleted Files: 0' : 'Orphaned Files: 0'
149
+ end
150
+
151
+ if @orphans.is_a?(Queue)
152
+ @orphans = @orphans.size.times.map { @orphans.shift }
153
+ end
154
+
155
+ if mirror
156
+ Logger.info @orphans.map {|path|
157
+ "\s\s[removing] '#{ path }'"
158
+ }.join("\n")
159
+
160
+ begin
161
+ cloud_io.delete(@orphans)
162
+ "Deleted Files: #{ @orphans.count }"
163
+ rescue => err
164
+ Logger.warn Error.wrap(err, 'Delete Operation Failed')
165
+ "Attempted to Delete: #{ @orphans.count } " +
166
+ "(See log messages for actual results)"
167
+ end
168
+ else
169
+ Logger.info @orphans.map {|path|
170
+ "\s\s[orphaned] '#{ path }'"
171
+ }.join("\n")
172
+ "Orphaned Files: #{ @orphans.count }"
173
+ end
174
+ end
175
+
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ require 'sliday_backup/cloud_io/cloud_files'
3
+
4
+ module SlidayBackup
5
+ module Syncer
6
+ module Cloud
7
+ class CloudFiles < Base
8
+ class Error < SlidayBackup::Error; end
9
+
10
+ ##
11
+ # Rackspace CloudFiles Credentials
12
+ attr_accessor :username, :api_key
13
+
14
+ ##
15
+ # Rackspace CloudFiles Container
16
+ attr_accessor :container
17
+
18
+ ##
19
+ # Rackspace AuthURL (optional)
20
+ attr_accessor :auth_url
21
+
22
+ ##
23
+ # Rackspace Region (optional)
24
+ attr_accessor :region
25
+
26
+ ##
27
+ # Rackspace Service Net
28
+ # (LAN-based transfers to avoid charges and improve performance)
29
+ attr_accessor :servicenet
30
+
31
+ ##
32
+ # Additional options to pass along to fog.
33
+ # e.g. Fog::Storage.new({ :provider => 'Rackspace' }.merge(fog_options))
34
+ attr_accessor :fog_options
35
+
36
+ def initialize(syncer_id = nil)
37
+ super
38
+
39
+ @servicenet ||= false
40
+
41
+ check_configuration
42
+ end
43
+
44
+ private
45
+
46
+ def cloud_io
47
+ @cloud_io ||= CloudIO::CloudFiles.new(
48
+ :username => username,
49
+ :api_key => api_key,
50
+ :auth_url => auth_url,
51
+ :region => region,
52
+ :servicenet => servicenet,
53
+ :container => container,
54
+ :max_retries => max_retries,
55
+ :retry_waitsec => retry_waitsec,
56
+ # Syncer can not use SLOs.
57
+ :segments_container => nil,
58
+ :segment_size => 0,
59
+ :fog_options => fog_options
60
+ )
61
+ end
62
+
63
+ def get_remote_files(remote_base)
64
+ hash = {}
65
+ cloud_io.objects(remote_base).each do |object|
66
+ relative_path = object.name.sub(remote_base + '/', '')
67
+ hash[relative_path] = object.hash
68
+ end
69
+ hash
70
+ end
71
+
72
+ def check_configuration
73
+ required = %w{ username api_key container }
74
+ raise Error, <<-EOS if required.map {|name| send(name) }.any?(&:nil?)
75
+ Configuration Error
76
+ #{ required.map {|name| "##{ name }"}.join(', ') } are all required
77
+ EOS
78
+ end
79
+
80
+ end # class Cloudfiles < Base
81
+ end # module Cloud
82
+ end
83
+ end
@@ -0,0 +1,100 @@
1
+ # encoding: utf-8
2
+ require 'digest/md5'
3
+
4
+ module SlidayBackup
5
+ module Syncer
6
+ module Cloud
7
+ class LocalFile
8
+ attr_reader :path
9
+ attr_accessor :md5
10
+
11
+ class << self
12
+
13
+ # Returns a Hash of LocalFile objects for each file within +dir+,
14
+ # except those matching any of the +excludes+.
15
+ # Hash keys are the file's path relative to +dir+.
16
+ def find(dir, excludes = [])
17
+ dir = File.expand_path(dir)
18
+ hash = {}
19
+ find_md5(dir, excludes).each do |file|
20
+ hash[file.path.sub(dir + '/', '')] = file
21
+ end
22
+ hash
23
+ end
24
+
25
+ # Return a new LocalFile object if it's valid.
26
+ # Otherwise, log a warning and return nil.
27
+ def new(*args)
28
+ file = super
29
+ if file.invalid?
30
+ Logger.warn("\s\s[skipping] #{ file.path }\n" +
31
+ "\s\sPath Contains Invalid UTF-8 byte sequences")
32
+ file = nil
33
+ end
34
+ file
35
+ end
36
+
37
+ private
38
+
39
+ # Returns an Array of file paths and their md5 hashes.
40
+ def find_md5(dir, excludes)
41
+ found = []
42
+ (Dir.entries(dir) - %w{. ..}).map {|e| File.join(dir, e) }.each do |path|
43
+ if File.directory?(path)
44
+ unless exclude?(excludes, path)
45
+ found += find_md5(path, excludes)
46
+ end
47
+ elsif File.file?(path)
48
+ if file = new(path)
49
+ unless exclude?(excludes, file.path)
50
+ file.md5 = Digest::MD5.file(file.path).hexdigest
51
+ found << file
52
+ end
53
+ end
54
+ end
55
+ end
56
+ found
57
+ end
58
+
59
+ # Returns true if +path+ matches any of the +excludes+.
60
+ # Note this can not be called if +path+ includes invalid UTF-8.
61
+ def exclude?(excludes, path)
62
+ excludes.any? do |ex|
63
+ if ex.is_a?(String)
64
+ File.fnmatch?(ex, path)
65
+ elsif ex.is_a?(Regexp)
66
+ ex.match(path)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # If +path+ contains invalid UTF-8, it will be sanitized
73
+ # and the LocalFile object will be flagged as invalid.
74
+ # This is done so @file.path may be logged.
75
+ def initialize(path)
76
+ @path = sanitize(path)
77
+ end
78
+
79
+ def invalid?
80
+ !!@invalid
81
+ end
82
+
83
+ private
84
+
85
+ def sanitize(str)
86
+ str.each_char.map do |char|
87
+ begin
88
+ char.unpack('U')
89
+ char
90
+ rescue
91
+ @invalid = true
92
+ "\xEF\xBF\xBD" # => "\uFFFD"
93
+ end
94
+ end.join
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+ require 'sliday_backup/cloud_io/s3'
3
+
4
+ module SlidayBackup
5
+ module Syncer
6
+ module Cloud
7
+ class S3 < Base
8
+ class Error < SlidayBackup::Error; end
9
+
10
+ ##
11
+ # Amazon Simple Storage Service (S3) Credentials
12
+ attr_accessor :access_key_id, :secret_access_key, :use_iam_profile
13
+
14
+ ##
15
+ # Amazon S3 bucket name
16
+ attr_accessor :bucket
17
+
18
+ ##
19
+ # Region of the specified S3 bucket
20
+ attr_accessor :region
21
+
22
+ ##
23
+ # Encryption algorithm to use for Amazon Server-Side Encryption
24
+ #
25
+ # Supported values:
26
+ #
27
+ # - :aes256
28
+ #
29
+ # Default: nil
30
+ attr_accessor :encryption
31
+
32
+ ##
33
+ # Storage class to use for the S3 objects uploaded
34
+ #
35
+ # Supported values:
36
+ #
37
+ # - :standard (default)
38
+ # - :reduced_redundancy
39
+ #
40
+ # Default: :standard
41
+ attr_accessor :storage_class
42
+
43
+ ##
44
+ # Additional options to pass along to fog.
45
+ # e.g. Fog::Storage.new({ :provider => 'AWS' }.merge(fog_options))
46
+ attr_accessor :fog_options
47
+
48
+ def initialize(syncer_id = nil)
49
+ super
50
+
51
+ @storage_class ||= :standard
52
+
53
+ check_configuration
54
+ end
55
+
56
+ private
57
+
58
+ def cloud_io
59
+ @cloud_io ||= CloudIO::S3.new(
60
+ :access_key_id => access_key_id,
61
+ :secret_access_key => secret_access_key,
62
+ :use_iam_profile => use_iam_profile,
63
+ :bucket => bucket,
64
+ :region => region,
65
+ :encryption => encryption,
66
+ :storage_class => storage_class,
67
+ :max_retries => max_retries,
68
+ :retry_waitsec => retry_waitsec,
69
+ # Syncer can not use multipart upload.
70
+ :chunk_size => 0,
71
+ :fog_options => fog_options
72
+ )
73
+ end
74
+
75
+ def get_remote_files(remote_base)
76
+ hash = {}
77
+ cloud_io.objects(remote_base).each do |object|
78
+ relative_path = object.key.sub(remote_base + '/', '')
79
+ hash[relative_path] = object.etag
80
+ end
81
+ hash
82
+ end
83
+
84
+ def check_configuration
85
+ if use_iam_profile
86
+ required = %w{ bucket }
87
+ else
88
+ required = %w{ access_key_id secret_access_key bucket }
89
+ end
90
+ raise Error, <<-EOS if required.map {|name| send(name) }.any?(&:nil?)
91
+ Configuration Error
92
+ #{ required.map {|name| "##{ name }"}.join(', ') } are all required
93
+ EOS
94
+
95
+ raise Error, <<-EOS if encryption && encryption.to_s.upcase != 'AES256'
96
+ Configuration Error
97
+ #encryption must be :aes256 or nil
98
+ EOS
99
+
100
+ classes = ['STANDARD', 'REDUCED_REDUNDANCY']
101
+ raise Error, <<-EOS unless classes.include?(storage_class.to_s.upcase)
102
+ Configuration Error
103
+ #storage_class must be :standard or :reduced_redundancy
104
+ EOS
105
+ end
106
+
107
+ end # Class S3 < Base
108
+ end # module Cloud
109
+ end
110
+ end