s3cp 0.2.3 → 0.2.4
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.
- data/History.txt +28 -1
- data/lib/s3cp/s3cp.rb +4 -1
- data/lib/s3cp/version.rb +1 -1
- metadata +4 -5
- data/lib/s3cp/#Untitled-7# +0 -288
data/History.txt
CHANGED
@@ -1,4 +1,31 @@
|
|
1
|
-
=== 0.2.
|
1
|
+
=== 0.2.5 / (Pending)
|
2
|
+
|
3
|
+
=== 0.2.4 / 2012-02-27
|
4
|
+
|
5
|
+
* Fixed: Handling of relative paths in s3cp.
|
6
|
+
|
7
|
+
% s3cp -r bucket:path/to/files /local/path
|
8
|
+
|
9
|
+
will now produce local files:
|
10
|
+
|
11
|
+
/local/path/files/A
|
12
|
+
/local/path/files/B
|
13
|
+
/local/path/files/C
|
14
|
+
/local/path/files2/D
|
15
|
+
/local/path/files2/E
|
16
|
+
...
|
17
|
+
|
18
|
+
whereas,
|
19
|
+
|
20
|
+
% s3cp -r bucket:path/to/files/ /local/path
|
21
|
+
|
22
|
+
|
23
|
+
/local/path/A
|
24
|
+
/local/path/B
|
25
|
+
/local/path/C
|
26
|
+
...
|
27
|
+
|
28
|
+
(note the trailing slash on "bucket:path/to/files")
|
2
29
|
|
3
30
|
=== 0.2.3 / (2012-02-24)
|
4
31
|
|
data/lib/s3cp/s3cp.rb
CHANGED
@@ -198,8 +198,11 @@ def no_slash(path)
|
|
198
198
|
path.match(/^\//) ? path[1..-1] : path
|
199
199
|
end
|
200
200
|
|
201
|
+
# relative("path/to/", "path/to/file") => "file"
|
202
|
+
# relative("path/to", "path/to/file") => "to/file"
|
201
203
|
def relative(base, path)
|
202
|
-
|
204
|
+
dir = base.rindex("/") ? base[0..base.rindex("/")] : ""
|
205
|
+
no_slash(path[dir.length..-1])
|
203
206
|
end
|
204
207
|
|
205
208
|
def log(msg)
|
data/lib/s3cp/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: s3cp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 4
|
10
|
+
version: 0.2.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex Boisvert
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-02-
|
18
|
+
date: 2012-02-28 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
prerelease: false
|
@@ -154,7 +154,6 @@ files:
|
|
154
154
|
- lib/s3cp/utils.rb
|
155
155
|
- lib/s3cp/s3mod.rb
|
156
156
|
- lib/s3cp/s3cat.rb
|
157
|
-
- lib/s3cp/#Untitled-7#
|
158
157
|
- lib/s3cp/completion.rb
|
159
158
|
- History.txt
|
160
159
|
- README.md
|
data/lib/s3cp/#Untitled-7#
DELETED
@@ -1,288 +0,0 @@
|
|
1
|
-
diff --git a/History.txt b/History.txt
|
2
|
-
index 8de2725..37eaec8 100644
|
3
|
-
--- a/History.txt
|
4
|
-
+++ b/History.txt
|
5
|
-
@@ -1,5 +1,9 @@
|
6
|
-
=== 0.2.3 / (Pending)
|
7
|
-
|
8
|
-
+* Added: --include and --exclude REGEX support for s3cp
|
9
|
-
+
|
10
|
-
+* Added: --sync mode for s3cp
|
11
|
-
+
|
12
|
-
=== 0.2.2 / (2012-02-23)
|
13
|
-
|
14
|
-
* Added: Progress bars during upload/download if $stdout.isatty
|
15
|
-
diff --git a/lib/s3cp/s3cp.rb b/lib/s3cp/s3cp.rb
|
16
|
-
index 0fabcdd..a1423f5 100644
|
17
|
-
--- a/lib/s3cp/s3cp.rb
|
18
|
-
+++ b/lib/s3cp/s3cp.rb
|
19
|
-
@@ -35,6 +35,9 @@ options[:overwrite] = ENV["S3CP_RETRIES"] ? (ENV["S3CP_OVERWRITE"] =~ /y|y
|
20
|
-
options[:checksum] = ENV["S3CP_CHECKSUM"] ? (ENV["S3CP_CHECKSUM"] =~ /y|yes|true|1|^\s*$/i ? true : false) : true
|
21
|
-
options[:retries] = ENV["S3CP_RETRIES"] ? ENV["S3CP_RETRIES"].to_i : 5
|
22
|
-
options[:retry_delay] = ENV["S3CP_RETRY_DELAY"] ? ENV["S3CP_RETRY_DELAY"].to_i : 1
|
23
|
-
+options[:include_regex] = []
|
24
|
-
+options[:exclude_regex] = []
|
25
|
-
+options[:sync] = false
|
26
|
-
|
27
|
-
op = OptionParser.new do |opts|
|
28
|
-
opts.banner = <<-BANNER
|
29
|
-
@@ -67,6 +70,10 @@ op = OptionParser.new do |opts|
|
30
|
-
options[:overwrite] = false
|
31
|
-
end
|
32
|
-
|
33
|
-
+ opts.on("--sync", "Sync mode: use checksum to determine if files need copying.") do
|
34
|
-
+ options[:sync] = true
|
35
|
-
+ end
|
36
|
-
+
|
37
|
-
opts.on("--max-attempts N", "Number of attempts to upload/download until checksum matches (default #{options[:retries]})") do |attempts|
|
38
|
-
options[:retries] = attempts.to_i
|
39
|
-
end
|
40
|
-
@@ -90,6 +97,14 @@ op = OptionParser.new do |opts|
|
41
|
-
opts.separator " AMZ headers: \'x-amz-acl: public-read\'"
|
42
|
-
opts.separator ""
|
43
|
-
|
44
|
-
+ opts.on("-i REGEX", "--include REGEX", "Copy only files matching the following regular expression.") do |regex|
|
45
|
-
+ options[:include_regex] << regex
|
46
|
-
+ end
|
47
|
-
+
|
48
|
-
+ opts.on("-x REGEX", "--exclude REGEX", "Do not copy files matching provided regular expression.") do |regex|
|
49
|
-
+ options[:exclude_regex] << regex
|
50
|
-
+ end
|
51
|
-
+
|
52
|
-
opts.on("--verbose", "Verbose mode") do
|
53
|
-
options[:verbose] = true
|
54
|
-
end
|
55
|
-
@@ -110,6 +125,16 @@ if ARGV.size < 2
|
56
|
-
exit
|
57
|
-
end
|
58
|
-
|
59
|
-
+if options[:include_regex].any? && !options[:recursive]
|
60
|
-
+ puts "-i (--include regex) option requires -r (recursive) option."
|
61
|
-
+ exit(1)
|
62
|
-
+end
|
63
|
-
+
|
64
|
-
+if options[:exclude_regex].any? && !options[:recursive]
|
65
|
-
+ puts "-x (--exclude regex) option requires -r (recursive) option."
|
66
|
-
+ exit(1)
|
67
|
-
+end
|
68
|
-
+
|
69
|
-
destination = ARGV.last
|
70
|
-
sources = ARGV[0..-2]
|
71
|
-
|
72
|
-
@@ -144,6 +169,16 @@ end
|
73
|
-
@bucket = $1
|
74
|
-
@prefix = $2
|
75
|
-
|
76
|
-
+@includes = options[:include_regex].map { |s| Regexp.new(s) }
|
77
|
-
+@excludes = options[:exclude_regex].map { |s| Regexp.new(s) }
|
78
|
-
+
|
79
|
-
+def match(path)
|
80
|
-
+ matching = true
|
81
|
-
+ return false if @includes.any? && !@includes.any? { |regex| regex.match(path) }
|
82
|
-
+ return false if @excludes.any? && @excludes.any? { |regex| regex.match(path) }
|
83
|
-
+ true
|
84
|
-
+end
|
85
|
-
+
|
86
|
-
@s3 = S3CP.connect()
|
87
|
-
|
88
|
-
def direction(from, to)
|
89
|
-
@@ -217,68 +252,90 @@ end
|
90
|
-
|
91
|
-
def local_to_s3(bucket_to, key, file, options = {})
|
92
|
-
log(with_headers("Copy #{file} to s3://#{bucket_to}/#{key}"))
|
93
|
-
- if options[:checksum]
|
94
|
-
- expected_md5 = md5(file)
|
95
|
-
+
|
96
|
-
+ expected_md5 = if options[:checksum] || options[:sync]
|
97
|
-
+ md5(file)
|
98
|
-
end
|
99
|
-
- retries = 0
|
100
|
-
- begin
|
101
|
-
- if retries == options[:retries]
|
102
|
-
- fail "Unable to upload to s3://#{bucket_to}/#{key} after #{retries} attempts."
|
103
|
-
- end
|
104
|
-
- if retries > 0
|
105
|
-
- STDERR.puts "Warning: failed checksum for s3://#{bucket_to}/#{bucket_to}. Retrying #{options[:retries] - retries} more time(s)."
|
106
|
-
- sleep options[:retry_delay]
|
107
|
-
+
|
108
|
-
+ actual_md5 = if options[:sync]
|
109
|
-
+ md5 = s3_checksum(bucket_to, key)
|
110
|
-
+ case md5
|
111
|
-
+ when :not_found
|
112
|
-
+ nil
|
113
|
-
+ when :invalid
|
114
|
-
+ STDERR.puts "Warning: invalid MD5 checksum in metadata; file will be force-copied."
|
115
|
-
+ nil
|
116
|
-
+ else
|
117
|
-
+ md5
|
118
|
-
end
|
119
|
-
+ end
|
120
|
-
|
121
|
-
- f = File.open(file)
|
122
|
-
+ if actual_md5.nil? || (options[:sync] && expected_md5 != actual_md5)
|
123
|
-
+ retries = 0
|
124
|
-
begin
|
125
|
-
- if $stdout.isatty
|
126
|
-
- f = Proxy.new(f)
|
127
|
-
- progress_bar = ProgressBar.new(File.basename(file), File.size(file)).tap do |p|
|
128
|
-
- p.file_transfer_mode
|
129
|
-
- end
|
130
|
-
- class << f
|
131
|
-
- attr_accessor :progress_bar
|
132
|
-
- def read(length, buffer=nil)
|
133
|
-
- begin
|
134
|
-
- result = @target.read(length, buffer)
|
135
|
-
- @progress_bar.inc result.length if result
|
136
|
-
- result
|
137
|
-
- rescue => e
|
138
|
-
- puts e
|
139
|
-
- raise e
|
140
|
-
+ if retries == options[:retries]
|
141
|
-
+ fail "Unable to upload to s3://#{bucket_to}/#{key} after #{retries} attempts."
|
142
|
-
+ end
|
143
|
-
+ if retries > 0
|
144
|
-
+ STDERR.puts "Warning: failed checksum for s3://#{bucket_to}/#{bucket_to}. Retrying #{options[:retries] - retries} more time(s)."
|
145
|
-
+ sleep options[:retry_delay]
|
146
|
-
+ end
|
147
|
-
+
|
148
|
-
+ f = File.open(file)
|
149
|
-
+ begin
|
150
|
-
+ if $stdout.isatty
|
151
|
-
+ f = Proxy.new(f)
|
152
|
-
+ progress_bar = ProgressBar.new(File.basename(file), File.size(file)).tap do |p|
|
153
|
-
+ p.file_transfer_mode
|
154
|
-
+ end
|
155
|
-
+ class << f
|
156
|
-
+ attr_accessor :progress_bar
|
157
|
-
+ def read(length, buffer=nil)
|
158
|
-
+ begin
|
159
|
-
+ result = @target.read(length, buffer)
|
160
|
-
+ @progress_bar.inc result.length if result
|
161
|
-
+ result
|
162
|
-
+ rescue => e
|
163
|
-
+ puts e
|
164
|
-
+ raise e
|
165
|
-
+ end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
+ f.progress_bar = progress_bar
|
169
|
-
+ else
|
170
|
-
+ progress_bar = nil
|
171
|
-
end
|
172
|
-
- f.progress_bar = progress_bar
|
173
|
-
- else
|
174
|
-
- progress_bar = nil
|
175
|
-
- end
|
176
|
-
|
177
|
-
- meta = @s3.interface.put(bucket_to, key, f, @headers)
|
178
|
-
- progress_bar.finish if progress_bar
|
179
|
-
+ meta = @s3.interface.put(bucket_to, key, f, @headers)
|
180
|
-
+ progress_bar.finish if progress_bar
|
181
|
-
|
182
|
-
- if options[:checksum]
|
183
|
-
- metadata = @s3.interface.head(bucket_to, key)
|
184
|
-
- actual_md5 = metadata["etag"] or fail "Unable to get etag/md5 for #{bucket_to}:#{key}"
|
185
|
-
- actual_md5 = actual_md5.sub(/^"/, "").sub(/"$/, "") # strip beginning and trailing quotes
|
186
|
-
- if actual_md5 =~ /-/
|
187
|
-
- STDERR.puts "Warning: invalid MD5 checksum in metadata; skipped checksum verification."
|
188
|
-
- actual_md5 = nil
|
189
|
-
+ if options[:checksum]
|
190
|
-
+ actual_md5 = s3_checksum(bucket_to, key)
|
191
|
-
+ unless actual_md5.is_a? String
|
192
|
-
+ STDERR.puts "Warning: invalid MD5 checksum in metadata; skipped checksum verification."
|
193
|
-
+ actual_md5 = nil
|
194
|
-
+ end
|
195
|
-
end
|
196
|
-
+ rescue => e
|
197
|
-
+ raise e unless options[:checksum]
|
198
|
-
+ STDERR.puts e
|
199
|
-
+ ensure
|
200
|
-
+ f.close()
|
201
|
-
end
|
202
|
-
- rescue => e
|
203
|
-
- raise e unless options[:checksum]
|
204
|
-
- STDERR.puts e
|
205
|
-
- ensure
|
206
|
-
- f.close()
|
207
|
-
- end
|
208
|
-
- retries += 1
|
209
|
-
+ retries += 1
|
210
|
-
end until options[:checksum] == false || actual_md5.nil? || expected_md5 == actual_md5
|
211
|
-
+ else
|
212
|
-
+ log "Already synchronized."
|
213
|
-
+ end
|
214
|
-
end
|
215
|
-
|
216
|
-
def s3_to_local(bucket_from, key_from, dest, options = {})
|
217
|
-
log("Copy s3://#{bucket_from}/#{key_from} to #{dest}")
|
218
|
-
+
|
219
|
-
+ actual_md5 = if options[:sync] && File.exist?(dest)
|
220
|
-
+ md5(dest)
|
221
|
-
+ end
|
222
|
-
+
|
223
|
-
retries = 0
|
224
|
-
begin
|
225
|
-
if retries == options[:retries]
|
226
|
-
@@ -292,34 +349,38 @@ def s3_to_local(bucket_from, key_from, dest, options = {})
|
227
|
-
|
228
|
-
f = File.new(dest, "wb")
|
229
|
-
begin
|
230
|
-
- size = nil
|
231
|
-
- if options[:checksum]
|
232
|
-
- metadata = @s3.interface.head(bucket_from, key_from)
|
233
|
-
- expected_md5 = metadata["etag"] or fail "Unable to get etag/md5 for #{bucket_from}:#{key_from}"
|
234
|
-
- expected_md5 = expected_md5.sub(/^"/, "").sub(/"$/, "") # strip beginning and trailing quotes
|
235
|
-
- if expected_md5 =~ /-/
|
236
|
-
+ expected_md5 = if options[:checksum] || options[:sync]
|
237
|
-
+ md5 = s3_checksum(bucket_from, key_from)
|
238
|
-
+ if options[:sync] && !md5.is_a?(String)
|
239
|
-
+ STDERR.puts "Warning: invalid MD5 checksum in metadata; file will be force-copied."
|
240
|
-
+ nil
|
241
|
-
+ elsif !md5.is_a? String
|
242
|
-
STDERR.puts "Warning: invalid MD5 checksum in metadata; skipped checksum verification."
|
243
|
-
- expected_md5 = nil
|
244
|
-
+ nil
|
245
|
-
+ else
|
246
|
-
+ md5
|
247
|
-
end
|
248
|
-
- size = metadata["content-length"].to_i
|
249
|
-
- elsif $stdout.isatty
|
250
|
-
- metadata = @s3.interface.head(bucket_from, key_from)
|
251
|
-
- size = metadata["content-length"].to_i
|
252
|
-
end
|
253
|
-
- begin
|
254
|
-
- progress_bar = if size
|
255
|
-
- ProgressBar.new(File.basename(key_from), size).tap do |p|
|
256
|
-
- p.file_transfer_mode
|
257
|
-
+
|
258
|
-
+ if (expected_md5 == nil) || (options[:sync] && expected_md5 != actual_md5)
|
259
|
-
+ begin
|
260
|
-
+ progress_bar = if $stdout.isatty
|
261
|
-
+ size = s3_size(bucket_from, key_from)
|
262
|
-
+ ProgressBar.new(File.basename(key_from), size).tap do |p|
|
263
|
-
+ p.file_transfer_mode
|
264
|
-
+ end
|
265
|
-
end
|
266
|
-
+ @s3.interface.get(bucket_from, key_from) do |chunk|
|
267
|
-
+ f.write(chunk)
|
268
|
-
+ progress_bar.inc chunk.size if progress_bar
|
269
|
-
+ end
|
270
|
-
+ progress_bar.finish
|
271
|
-
+ rescue => e
|
272
|
-
+ progress_bar.halt if progress_bar
|
273
|
-
+ raise e
|
274
|
-
end
|
275
|
-
- @s3.interface.get(bucket_from, key_from) do |chunk|
|
276
|
-
- f.write(chunk)
|
277
|
-
- progress_bar.inc chunk.size if progress_bar
|
278
|
-
- end
|
279
|
-
- progress_bar.finish
|
280
|
-
- rescue => e
|
281
|
-
- progress_bar.halt if progress_bar
|
282
|
-
- raise e
|
283
|
-
+ else
|
284
|
-
+ log("Already synchronized")
|
285
|
-
end
|
286
|
-
rescue => e
|
287
|
-
raise e unless options[:checksum]
|
288
|
-
|