s3sync 0.3.4 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,98 +0,0 @@
1
- # s3sync - Tool belt for managing your S3 buckets
2
- #
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2013 Lincoln de Sousa <lincoln@clarete.li>
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- # Part of this software was inspired by the original s3sync, so here's their
26
- # copyright notice:
27
-
28
- # This software code is made available "AS IS" without warranties of any
29
- # kind. You may copy, display, modify and redistribute the software
30
- # code either by itself or as incorporated into your code; provided that
31
- # you do not remove any proprietary notices. Your use of this software
32
- # code is at your own risk and you waive any claim against the author
33
- # with respect to your use of this software code.
34
- # (c) 2007 alastair brunton
35
- #
36
- # modified to search out the yaml in several places, thanks wkharold.
37
-
38
- require 'yaml'
39
- require 's3sync/exceptions'
40
-
41
-
42
- module S3Sync
43
-
44
- class Config < Hash
45
-
46
- REQUIRED_VARS = [:AWS_ACCESS_KEY_ID, :AWS_SECRET_ACCESS_KEY]
47
-
48
- CONFIG_PATHS = ["#{ENV['S3SYNC_PATH']}", "#{ENV['HOME']}/.s3sync.yml", "/etc/s3sync.yml"]
49
-
50
- def read_from_file
51
- paths_checked = []
52
-
53
- CONFIG_PATHS.each do |path|
54
-
55
- # Filtering some garbage
56
- next if path.nil? or path.strip.empty?
57
-
58
- # Feeding the user feedback in case of failure
59
- paths_checked << path
60
-
61
- # Time for the dirty work, let's parse the config file and feed our
62
- # internal hash
63
- if File.exists? path
64
- config = YAML.load_file path
65
- config.each_pair do |key, value|
66
- self[key.upcase.to_sym] = value
67
- end
68
- return
69
- end
70
- end
71
-
72
- return paths_checked
73
- end
74
-
75
- def read_from_env
76
- REQUIRED_VARS.each do |v|
77
- self[v] = ENV[v.to_s] unless ENV[v.to_s].nil?
78
- end
79
- end
80
-
81
- def read
82
- # Reading from file and then trying from env
83
- paths_checked = read_from_file
84
- read_from_env
85
-
86
- # Checking which variables we have
87
- not_found = []
88
-
89
- REQUIRED_VARS.each {|v|
90
- not_found << v if self[v].nil?
91
- }
92
-
93
- # Cleaning possibly empty env var from CONFIG_PATH
94
- paths = (paths_checked || CONFIG_PATHS).select {|e| !e.empty?}
95
- raise NoConfigFound.new(not_found, paths) if not_found.count > 0
96
- end
97
- end
98
- end
@@ -1,55 +0,0 @@
1
- # s3sync - Tool belt for managing your S3 buckets
2
- #
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2013 Lincoln de Sousa <lincoln@clarete.li>
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- module S3Sync
26
-
27
- class SyncException < StandardError
28
- end
29
-
30
- class NoConfigFound < SyncException
31
-
32
- attr_accessor :missing_vars
33
- attr_accessor :paths_checked
34
-
35
- def initialize missing_vars, paths_checked
36
- @missing_vars = missing_vars
37
- @paths_checked = paths_checked
38
- end
39
- end
40
-
41
- class WrongUsage < SyncException
42
-
43
- attr_accessor :error_code
44
- attr_accessor :msg
45
-
46
- def initialize(error_code, msg)
47
- @error_code = error_code || 1
48
- @msg = msg
49
- end
50
- end
51
-
52
- class FailureFeedback < SyncException
53
- end
54
-
55
- end
@@ -1,371 +0,0 @@
1
- # s3sync - Tool belt for managing your S3 buckets
2
- #
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2013 Lincoln de Sousa <lincoln@clarete.li>
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- # Part of this software was inspired by the original s3sync, so here's their
26
- # copyright notice:
27
-
28
- # (c) 2007 s3sync.net
29
- #
30
- # This software code is made available "AS IS" without warranties of any
31
- # kind. You may copy, display, modify and redistribute the software
32
- # code either by itself or as incorporated into your code; provided that
33
- # you do not remove any proprietary notices. Your use of this software
34
- # code is at your own risk and you waive any claim against the author
35
- # with respect to your use of this software code.
36
-
37
- require 'find'
38
- require 'fileutils'
39
- require 's3sync/util'
40
-
41
- module S3Sync
42
-
43
- class Location
44
- attr_accessor :path
45
- attr_accessor :bucket
46
-
47
- def initialize path, bucket=nil
48
- raise RuntimeError if path.nil?
49
- @path = path
50
- @bucket = bucket || nil
51
- end
52
-
53
- def to_s
54
- out = []
55
- out << @bucket unless @bucket.nil?
56
- out << @path
57
- out.join ':'
58
- end
59
-
60
- def local?
61
- @bucket.nil?
62
- end
63
-
64
- def == other
65
- @path == other.path and @bucket == other.bucket
66
- end
67
-
68
- alias eql? ==
69
- end
70
-
71
- class Node
72
- include Comparable
73
-
74
- attr_accessor :base
75
- attr_accessor :path
76
- attr_accessor :size
77
-
78
- def initialize base, path, size
79
- @base = base
80
- @path = path
81
- @size = size
82
- end
83
-
84
- def full
85
- S3Sync.safe_join [@base, @path]
86
- end
87
-
88
- def == other
89
- full == other.full and @size == other.size
90
- end
91
-
92
- def <=> other
93
- if self.size < other.size
94
- -1
95
- elsif self.size > other.size
96
- 1
97
- else
98
- 0
99
- end
100
- end
101
-
102
- alias eql? ==
103
- end
104
-
105
- class LocalDirectory
106
- attr_accessor :source
107
-
108
- def initialize source
109
- @source = source
110
- end
111
-
112
- def list_files
113
- nodes = {}
114
- Find.find(@source) do |file|
115
- begin
116
- st = File.stat file # Might fail
117
- raise if not st.readable? # We're not interested in things we can't read
118
- rescue
119
- $stderr.puts "WARNING: Skipping unreadable file #{file}"
120
- Find.prune
121
- end
122
-
123
- # We don't support following symlinks for now, we don't need to follow
124
- # folders and I don't think we care about any other thing, right?
125
- next unless st.file?
126
-
127
- # We only need the relative path here
128
- file_name = file.gsub(/^#{@source}\/?/, '').squeeze('/')
129
- node = Node.new(@source.squeeze('/'), file_name, st.size)
130
- nodes[node.path] = node
131
- end
132
-
133
- return nodes
134
- end
135
- end
136
-
137
- class SyncCommand
138
-
139
- def self.cmp hash1, hash2
140
- same, to_add_to_2 = [], []
141
-
142
- hash1.each do |key, value|
143
- value2 = hash2.delete key
144
- if value2.nil?
145
- to_add_to_2 << value
146
- elsif value2.size == value.size
147
- same << value
148
- else
149
- to_add_to_2 << value
150
- end
151
- end
152
-
153
- to_remove_from_2 = hash2.values
154
-
155
- [same, to_add_to_2, to_remove_from_2]
156
- end
157
-
158
- def initialize args, source, destination
159
- @args = args
160
- @source = source
161
- @destination = destination
162
- end
163
-
164
- def run
165
- # Reading the source and destination using our helper method
166
- if (source, destination, bucket = self.class.parse_params [@source, @destination]).nil?
167
- raise WrongUsage.new(nil, 'Need a source and a destination')
168
- end
169
-
170
- # Getting the trees
171
- source_tree, destination_tree = read_trees source, destination
172
-
173
- # Getting the list of resources to be exchanged between the two peers
174
- _, to_add, to_remove = self.class.cmp source_tree, destination_tree
175
-
176
- # Removing the items matching the exclude pattern if requested
177
- to_add.select! { |e|
178
- begin
179
- (e.path =~ /#{@args.exclude}/).nil?
180
- rescue RegexpError => exc
181
- raise WrongUsage.new nil, exc.message
182
- end
183
- } if @args.exclude
184
-
185
- # Calling the methods that perform the actual IO
186
- if source.local?
187
- upload_files destination, to_add
188
- remove_files destination, to_remove unless @args.keep
189
- else
190
- download_files destination, source, to_add
191
- remove_local_files destination, source, to_remove unless @args.keep
192
- end
193
- end
194
-
195
- def self.parse_params args
196
- # Reading the arbitrary parameters from the command line and getting
197
- # modifiable copies to parse
198
- source, destination = args; return nil if source.nil? or destination.nil?
199
-
200
- # Sync from one s3 to another is currently not supported
201
- if remote_prefix? source and remote_prefix? destination
202
- raise WrongUsage.new(nil, 'Both arguments can\'t be on S3')
203
- end
204
-
205
- # C'mon, there's rsync out there
206
- if !remote_prefix? source and !remote_prefix? destination
207
- raise WrongUsage.new(nil, 'One argument must be on S3')
208
- end
209
-
210
- source, destination = process_destination source, destination
211
- return [Location.new(*source), Location.new(*destination)]
212
- end
213
-
214
- def self.remote_prefix?(prefix)
215
- # allow for dos-like things e.g. C:\ to be treated as local even with
216
- # colon.
217
- prefix.include? ':' and not prefix.match '^[A-Za-z]:[\\\\/]'
218
- end
219
-
220
- def self.process_file_destination source, destination, file=""
221
- if not file.empty?
222
- sub = (remote_prefix? source) ? source.split(":")[1] : source
223
- file = file.gsub(/^#{sub}/, '')
224
- end
225
-
226
- # no slash on end of source means we need to append the last src dir to
227
- # dst prefix testing for empty isn't good enough here.. needs to be
228
- # "empty apart from potentially having 'bucket:'"
229
- if source =~ %r{/$}
230
- if remote_prefix? destination and destination.end_with? ':'
231
- S3Sync.safe_join [destination, file]
232
- else
233
- File.join [destination, file]
234
- end
235
- else
236
- if remote_prefix? source
237
- _, name = source.split ":"
238
- File.join [destination, File.basename(name || ""), file]
239
- else
240
- source = /^\/?(.*)/.match(source)[1]
241
-
242
- # Corner case: the root of the remote path is empty, we don't want to
243
- # add an unnecessary slash here.
244
- if destination.end_with? ':'
245
- File.join [destination + source, file]
246
- else
247
- File.join [destination, source, file]
248
- end
249
- end
250
- end
251
- end
252
-
253
- def self.process_destination source, destination
254
- source, destination = source.dup, destination.dup
255
-
256
- # don't repeat slashes
257
- source.squeeze! '/'
258
- destination.squeeze! '/'
259
-
260
- # Making sure that local paths won't break our stuff later
261
- source.gsub!(/^\.\//, '')
262
- destination.gsub!(/^\.\//, '')
263
-
264
- # Parsing the final destination
265
- destination = process_file_destination source, destination, ""
266
-
267
- # here's where we find out what direction we're going
268
- source_is_s3 = remote_prefix? source
269
-
270
- # canonicalize the S3 stuff
271
- remote_prefix = source_is_s3 ? source : destination
272
- bucket, remote_prefix = remote_prefix.split ":"
273
-
274
- remote_prefix ||= ""
275
-
276
- # Just making sure we preserve the direction
277
- if source_is_s3
278
- [[remote_prefix, bucket], destination]
279
- else
280
- [source, [remote_prefix, bucket]]
281
- end
282
- end
283
-
284
- def read_tree_remote location
285
- dir = location.path
286
- dir += '/' if not dir.empty? and not dir.end_with?('/')
287
-
288
- nodes = {}
289
- @args.s3.buckets[location.bucket].objects.with_prefix(dir || "").to_a.collect do |obj|
290
- node = Node.new(location.path, obj.key, obj.content_length)
291
- nodes[node.path] = node
292
- end
293
- return nodes
294
- end
295
-
296
- def read_trees source, destination
297
- if source.local?
298
- source_tree = LocalDirectory.new(source.path).list_files
299
- destination_tree = read_tree_remote destination
300
- else
301
- source_tree = read_tree_remote source
302
- destination_tree = LocalDirectory.new(destination.path).list_files
303
- end
304
-
305
- [source_tree, destination_tree]
306
- end
307
-
308
- def upload_files remote, list
309
- list.each do |e|
310
- if @args.verbose
311
- puts " + #{e.full} => #{remote}#{e.path}"
312
- end
313
-
314
- unless @args.dry_run
315
- remote_path = "#{remote.path}#{e.path}"
316
- @args.s3.buckets[remote.bucket].objects[remote_path].write Pathname.new(e.full), :acl => @args.acl
317
- end
318
- end
319
- end
320
-
321
- def remove_files remote, list
322
- if @args.verbose
323
- list.each {|e|
324
- puts " - #{remote}#{e.path}"
325
- }
326
- end
327
-
328
- unless @args.dry_run
329
- @args.s3.buckets[remote.bucket].objects.delete_if { |obj| list.map(&:path).include? obj.key }
330
- end
331
- end
332
-
333
- def download_files destination, source, list
334
- list.each {|e|
335
- path = File.join destination.path, e.path
336
-
337
- if @args.verbose
338
- puts " + #{source}#{e.path} => #{path}"
339
- end
340
-
341
- unless @args.dry_run
342
- obj = @args.s3.buckets[source.bucket].objects[e.path]
343
-
344
- # Making sure this new file will have a safe shelter
345
- FileUtils.mkdir_p File.dirname(path)
346
-
347
- # Downloading and saving the files
348
- File.open(path, 'wb') do |file|
349
- obj.read do |chunk|
350
- file.write chunk
351
- end
352
- end
353
- end
354
- }
355
- end
356
-
357
- def remove_local_files destination, source, list
358
- list.each {|e|
359
- path = File.join destination.path, e.path
360
-
361
- if @args.verbose
362
- puts " * #{e.path} => #{path}"
363
- end
364
-
365
- unless @args.dry_run
366
- FileUtils.rm_rf path
367
- end
368
- }
369
- end
370
- end
371
- end