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.
- checksums.yaml +7 -0
- data/README.md +29 -0
- data/bin/sliday_backup +5 -0
- data/lib/sliday_backup.rb +147 -0
- data/lib/sliday_backup/archive.rb +170 -0
- data/lib/sliday_backup/binder.rb +22 -0
- data/lib/sliday_backup/cleaner.rb +116 -0
- data/lib/sliday_backup/cli.rb +374 -0
- data/lib/sliday_backup/cloud_io/base.rb +41 -0
- data/lib/sliday_backup/cloud_io/cloud_files.rb +298 -0
- data/lib/sliday_backup/cloud_io/s3.rb +260 -0
- data/lib/sliday_backup/compressor/base.rb +35 -0
- data/lib/sliday_backup/compressor/bzip2.rb +39 -0
- data/lib/sliday_backup/compressor/custom.rb +53 -0
- data/lib/sliday_backup/compressor/gzip.rb +74 -0
- data/lib/sliday_backup/config.rb +119 -0
- data/lib/sliday_backup/config/dsl.rb +103 -0
- data/lib/sliday_backup/config/helpers.rb +143 -0
- data/lib/sliday_backup/database/base.rb +86 -0
- data/lib/sliday_backup/database/mongodb.rb +187 -0
- data/lib/sliday_backup/database/mysql.rb +192 -0
- data/lib/sliday_backup/database/openldap.rb +95 -0
- data/lib/sliday_backup/database/postgresql.rb +133 -0
- data/lib/sliday_backup/database/redis.rb +179 -0
- data/lib/sliday_backup/database/riak.rb +82 -0
- data/lib/sliday_backup/database/sqlite.rb +57 -0
- data/lib/sliday_backup/encryptor/base.rb +29 -0
- data/lib/sliday_backup/encryptor/gpg.rb +747 -0
- data/lib/sliday_backup/encryptor/open_ssl.rb +77 -0
- data/lib/sliday_backup/errors.rb +58 -0
- data/lib/sliday_backup/logger.rb +199 -0
- data/lib/sliday_backup/logger/console.rb +51 -0
- data/lib/sliday_backup/logger/fog_adapter.rb +29 -0
- data/lib/sliday_backup/logger/logfile.rb +133 -0
- data/lib/sliday_backup/logger/syslog.rb +116 -0
- data/lib/sliday_backup/model.rb +479 -0
- data/lib/sliday_backup/notifier/base.rb +128 -0
- data/lib/sliday_backup/notifier/campfire.rb +63 -0
- data/lib/sliday_backup/notifier/command.rb +99 -0
- data/lib/sliday_backup/notifier/datadog.rb +107 -0
- data/lib/sliday_backup/notifier/flowdock.rb +103 -0
- data/lib/sliday_backup/notifier/hipchat.rb +112 -0
- data/lib/sliday_backup/notifier/http_post.rb +117 -0
- data/lib/sliday_backup/notifier/mail.rb +244 -0
- data/lib/sliday_backup/notifier/nagios.rb +69 -0
- data/lib/sliday_backup/notifier/pagerduty.rb +81 -0
- data/lib/sliday_backup/notifier/prowl.rb +68 -0
- data/lib/sliday_backup/notifier/pushover.rb +74 -0
- data/lib/sliday_backup/notifier/ses.rb +88 -0
- data/lib/sliday_backup/notifier/slack.rb +148 -0
- data/lib/sliday_backup/notifier/twitter.rb +58 -0
- data/lib/sliday_backup/notifier/zabbix.rb +63 -0
- data/lib/sliday_backup/package.rb +55 -0
- data/lib/sliday_backup/packager.rb +107 -0
- data/lib/sliday_backup/pipeline.rb +124 -0
- data/lib/sliday_backup/splitter.rb +76 -0
- data/lib/sliday_backup/storage/base.rb +69 -0
- data/lib/sliday_backup/storage/cloud_files.rb +158 -0
- data/lib/sliday_backup/storage/cycler.rb +75 -0
- data/lib/sliday_backup/storage/dropbox.rb +212 -0
- data/lib/sliday_backup/storage/ftp.rb +112 -0
- data/lib/sliday_backup/storage/local.rb +64 -0
- data/lib/sliday_backup/storage/qiniu.rb +65 -0
- data/lib/sliday_backup/storage/rsync.rb +248 -0
- data/lib/sliday_backup/storage/s3.rb +156 -0
- data/lib/sliday_backup/storage/scp.rb +67 -0
- data/lib/sliday_backup/storage/sftp.rb +82 -0
- data/lib/sliday_backup/storage/sliday_storage.rb +79 -0
- data/lib/sliday_backup/syncer/base.rb +70 -0
- data/lib/sliday_backup/syncer/cloud/base.rb +179 -0
- data/lib/sliday_backup/syncer/cloud/cloud_files.rb +83 -0
- data/lib/sliday_backup/syncer/cloud/local_file.rb +100 -0
- data/lib/sliday_backup/syncer/cloud/s3.rb +110 -0
- data/lib/sliday_backup/syncer/rsync/base.rb +54 -0
- data/lib/sliday_backup/syncer/rsync/local.rb +31 -0
- data/lib/sliday_backup/syncer/rsync/pull.rb +51 -0
- data/lib/sliday_backup/syncer/rsync/push.rb +205 -0
- data/lib/sliday_backup/template.rb +46 -0
- data/lib/sliday_backup/utilities.rb +224 -0
- data/lib/sliday_backup/version.rb +5 -0
- data/templates/cli/archive +28 -0
- data/templates/cli/compressor/bzip2 +4 -0
- data/templates/cli/compressor/custom +7 -0
- data/templates/cli/compressor/gzip +4 -0
- data/templates/cli/config +123 -0
- data/templates/cli/databases/mongodb +15 -0
- data/templates/cli/databases/mysql +18 -0
- data/templates/cli/databases/openldap +24 -0
- data/templates/cli/databases/postgresql +16 -0
- data/templates/cli/databases/redis +16 -0
- data/templates/cli/databases/riak +17 -0
- data/templates/cli/databases/sqlite +11 -0
- data/templates/cli/encryptor/gpg +27 -0
- data/templates/cli/encryptor/openssl +9 -0
- data/templates/cli/model +26 -0
- data/templates/cli/notifier/zabbix +15 -0
- data/templates/cli/notifiers/campfire +12 -0
- data/templates/cli/notifiers/command +32 -0
- data/templates/cli/notifiers/datadog +57 -0
- data/templates/cli/notifiers/flowdock +16 -0
- data/templates/cli/notifiers/hipchat +16 -0
- data/templates/cli/notifiers/http_post +32 -0
- data/templates/cli/notifiers/mail +24 -0
- data/templates/cli/notifiers/nagios +13 -0
- data/templates/cli/notifiers/pagerduty +12 -0
- data/templates/cli/notifiers/prowl +11 -0
- data/templates/cli/notifiers/pushover +11 -0
- data/templates/cli/notifiers/ses +15 -0
- data/templates/cli/notifiers/slack +22 -0
- data/templates/cli/notifiers/twitter +13 -0
- data/templates/cli/splitter +7 -0
- data/templates/cli/storages/cloud_files +11 -0
- data/templates/cli/storages/dropbox +20 -0
- data/templates/cli/storages/ftp +13 -0
- data/templates/cli/storages/local +8 -0
- data/templates/cli/storages/qiniu +12 -0
- data/templates/cli/storages/rsync +17 -0
- data/templates/cli/storages/s3 +16 -0
- data/templates/cli/storages/scp +15 -0
- data/templates/cli/storages/sftp +15 -0
- data/templates/cli/storages/sliday_storage +6 -0
- data/templates/cli/syncers/cloud_files +22 -0
- data/templates/cli/syncers/rsync_local +20 -0
- data/templates/cli/syncers/rsync_pull +28 -0
- data/templates/cli/syncers/rsync_push +28 -0
- data/templates/cli/syncers/s3 +27 -0
- data/templates/general/links +3 -0
- data/templates/general/version.erb +2 -0
- data/templates/notifier/mail/failure.erb +16 -0
- data/templates/notifier/mail/success.erb +16 -0
- data/templates/notifier/mail/warning.erb +16 -0
- data/templates/storage/dropbox/authorization_url.erb +6 -0
- data/templates/storage/dropbox/authorized.erb +4 -0
- data/templates/storage/dropbox/cache_file_written.erb +10 -0
- 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
|