s3cp 1.1.29 → 1.1.30
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/lib/s3cp/s3buckets.rb +4 -8
- data/lib/s3cp/s3cat.rb +2 -8
- data/lib/s3cp/s3cp.rb +25 -28
- data/lib/s3cp/s3du.rb +43 -45
- data/lib/s3cp/s3lifecycle.rb +73 -71
- data/lib/s3cp/s3ls.rb +57 -58
- data/lib/s3cp/s3mod.rb +1 -6
- data/lib/s3cp/s3rm.rb +1 -6
- data/lib/s3cp/s3stat.rb +13 -11
- data/lib/s3cp/s3tree.rb +79 -82
- data/lib/s3cp/s3up.rb +2 -8
- data/lib/s3cp/utils.rb +14 -0
- data/lib/s3cp/version.rb +1 -1
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
=== 1.1.30 (2013-05-15)
|
2
|
+
|
3
|
+
* Changed: Improved error handling -- all commands now exit with non-zero exit
|
4
|
+
code on error. [Alexis Midon / alexism@github / Pull request #11]
|
5
|
+
|
6
|
+
* Changed: s3cp properly fails when source key is not specified or does not exist.
|
7
|
+
[Alexis Midon / alexism@github / Pull request #11]
|
8
|
+
|
1
9
|
=== 1.1.29 (2013-05-15)
|
2
10
|
|
3
11
|
* Added: s3buckets command may now be used to delete buckets and enable/suspend
|
data/lib/s3cp/s3buckets.rb
CHANGED
@@ -58,10 +58,11 @@ op = OptionParser.new do |opts|
|
|
58
58
|
end
|
59
59
|
op.parse!(ARGV)
|
60
60
|
|
61
|
-
S3CP.
|
61
|
+
S3CP.standard_exception_handling(options) do
|
62
|
+
|
63
|
+
S3CP.load_config()
|
64
|
+
s3 = S3CP.connect()
|
62
65
|
|
63
|
-
s3 = S3CP.connect()
|
64
|
-
begin
|
65
66
|
if options[:create]
|
66
67
|
name = options[:create]
|
67
68
|
create_options = {}
|
@@ -82,10 +83,5 @@ begin
|
|
82
83
|
puts bucket.name
|
83
84
|
end
|
84
85
|
end
|
85
|
-
rescue => e
|
86
|
-
$stderr.print "s3buckets: [#{e.class}] #{e.message}\n"
|
87
|
-
if options[:debug]
|
88
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
89
|
-
end
|
90
86
|
end
|
91
87
|
|
data/lib/s3cp/s3cat.rb
CHANGED
@@ -73,7 +73,7 @@ if options[:debug]
|
|
73
73
|
puts "Options: \n#{options.inspect}"
|
74
74
|
end
|
75
75
|
|
76
|
-
|
76
|
+
S3CP.standard_exception_handling(options) do
|
77
77
|
@bucket, @prefix = S3CP.bucket_and_key(url)
|
78
78
|
fail "Your URL looks funny, doesn't it?" unless @bucket
|
79
79
|
|
@@ -134,17 +134,11 @@ begin
|
|
134
134
|
else
|
135
135
|
@s3.objects[@prefix].read_as_stream(read_options) do |chunk|
|
136
136
|
begin
|
137
|
-
|
137
|
+
$stdout.print(chunk)
|
138
138
|
rescue Errno::EPIPE
|
139
139
|
break
|
140
140
|
end
|
141
141
|
end
|
142
142
|
end
|
143
|
-
|
144
|
-
rescue => e
|
145
|
-
$stderr.print "s3cat: [#{e.class}] #{e.message}\n"
|
146
|
-
if options[:debug]
|
147
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
148
|
-
end
|
149
143
|
end
|
150
144
|
|
data/lib/s3cp/s3cp.rb
CHANGED
@@ -148,12 +148,12 @@ if ARGV.size < 2
|
|
148
148
|
end
|
149
149
|
|
150
150
|
if options[:include_regex].any? && !options[:recursive]
|
151
|
-
|
151
|
+
$stderr.puts "-i (--include regex) option requires -r (recursive) option."
|
152
152
|
exit(1)
|
153
153
|
end
|
154
154
|
|
155
155
|
if options[:exclude_regex].any? && !options[:recursive]
|
156
|
-
|
156
|
+
$stderr.puts "-x (--exclude regex) option requires -r (recursive) option."
|
157
157
|
exit(1)
|
158
158
|
end
|
159
159
|
|
@@ -161,9 +161,9 @@ destination = ARGV.last
|
|
161
161
|
sources = ARGV[0..-2]
|
162
162
|
|
163
163
|
if options[:debug]
|
164
|
-
|
165
|
-
|
166
|
-
|
164
|
+
$stderr.puts "sources: #{sources.inspect}"
|
165
|
+
$stderr.puts "destination: #{destination}"
|
166
|
+
$stderr.puts "Options: \n#{options.inspect}"
|
167
167
|
end
|
168
168
|
|
169
169
|
class ProxyIO
|
@@ -282,7 +282,7 @@ def local_to_s3(bucket_to, key, file, options = {})
|
|
282
282
|
when :not_found
|
283
283
|
nil
|
284
284
|
when :invalid
|
285
|
-
|
285
|
+
$stderr.puts "Warning: No MD5 checksum available and ETag not suitable due to multi-part upload; file will be force-copied."
|
286
286
|
nil
|
287
287
|
else
|
288
288
|
md5
|
@@ -297,7 +297,7 @@ def local_to_s3(bucket_to, key, file, options = {})
|
|
297
297
|
end
|
298
298
|
if retries > 0
|
299
299
|
delay = options[:retry_delay] * (options[:retry_backoff] ** retries)
|
300
|
-
|
300
|
+
$stderr.puts "Sleeping #{"%0.2f" % delay} seconds. Will retry #{options[:retries] - retries} more time(s)."
|
301
301
|
sleep delay
|
302
302
|
end
|
303
303
|
|
@@ -338,10 +338,10 @@ def local_to_s3(bucket_to, key, file, options = {})
|
|
338
338
|
actual_md5 = s3_checksum(bucket_to, key)
|
339
339
|
if actual_md5.is_a? String
|
340
340
|
if actual_md5 != expected_md5
|
341
|
-
|
341
|
+
$stderr.puts "Warning: invalid MD5 checksum. Expected: #{expected_md5} Actual: #{actual_md5}"
|
342
342
|
end
|
343
343
|
else
|
344
|
-
|
344
|
+
$stderr.puts "Warning: invalid MD5 checksum in metadata: #{actual_md5.inspect}; skipped checksum verification."
|
345
345
|
actual_md5 = nil
|
346
346
|
end
|
347
347
|
end
|
@@ -349,10 +349,10 @@ def local_to_s3(bucket_to, key, file, options = {})
|
|
349
349
|
actual_md5 = "bad"
|
350
350
|
if progress_bar
|
351
351
|
progress_bar.clear
|
352
|
-
|
352
|
+
$stderr.puts "Error copying #{file} to s3://#{bucket_to}/#{key}"
|
353
353
|
end
|
354
354
|
raise e if !options[:checksum] || e.is_a?(AWS::S3::Errors::AccessDenied)
|
355
|
-
|
355
|
+
$stderr.puts e
|
356
356
|
end
|
357
357
|
retries += 1
|
358
358
|
end until options[:checksum] == false || actual_md5.nil? || expected_md5 == actual_md5
|
@@ -364,6 +364,7 @@ end
|
|
364
364
|
|
365
365
|
def s3_to_local(bucket_from, key_from, dest, options = {})
|
366
366
|
log("#{operation(options)} s3://#{bucket_from}/#{key_from} to #{dest}")
|
367
|
+
raise ArgumentError, "source key may not be blank" if key_from.to_s.empty?
|
367
368
|
|
368
369
|
retries = 0
|
369
370
|
begin
|
@@ -374,17 +375,17 @@ def s3_to_local(bucket_from, key_from, dest, options = {})
|
|
374
375
|
if retries > 0
|
375
376
|
delay = options[:retry_delay] * (options[:retry_backoff] ** retries)
|
376
377
|
delay = delay.to_i
|
377
|
-
|
378
|
+
$stderr.puts "Sleeping #{"%0.2f" % delay} seconds. Will retry #{options[:retries] - retries} more time(s)."
|
378
379
|
sleep delay
|
379
380
|
end
|
380
381
|
begin
|
381
382
|
expected_md5 = if options[:checksum] || options[:sync]
|
382
383
|
md5 = s3_checksum(bucket_from, key_from)
|
383
384
|
if options[:sync] && !md5.is_a?(String)
|
384
|
-
|
385
|
+
$stderr.puts "Warning: invalid MD5 checksum in metadata; file will be force-copied."
|
385
386
|
nil
|
386
387
|
elsif !md5.is_a? String
|
387
|
-
|
388
|
+
$stderr.puts "Warning: invalid MD5 checksum in metadata; skipped checksum verification."
|
388
389
|
nil
|
389
390
|
else
|
390
391
|
md5
|
@@ -420,14 +421,15 @@ def s3_to_local(bucket_from, key_from, dest, options = {})
|
|
420
421
|
return
|
421
422
|
end
|
422
423
|
rescue => e
|
424
|
+
raise e if e.is_a?(AWS::S3::Errors::NoSuchKey)
|
423
425
|
raise e unless options[:checksum]
|
424
|
-
|
426
|
+
$stderr.puts e
|
425
427
|
end
|
426
428
|
|
427
429
|
if options[:checksum] && expected_md5 != nil
|
428
430
|
actual_md5 = S3CP.md5(dest)
|
429
431
|
if actual_md5 != expected_md5
|
430
|
-
|
432
|
+
$stderr.puts "Warning: invalid MD5 checksum. Expected: #{expected_md5} Actual: #{actual_md5}"
|
431
433
|
end
|
432
434
|
end
|
433
435
|
|
@@ -487,7 +489,7 @@ def copy(from, to, options)
|
|
487
489
|
if match(key)
|
488
490
|
dest = key_path key_to, relative(key_from, key)
|
489
491
|
if !options[:overwrite] && s3_exist?(bucket_to, dest)
|
490
|
-
|
492
|
+
$stderr.puts "Skipping s3://#{bucket_to}/#{dest} - already exists."
|
491
493
|
else
|
492
494
|
s3_to_s3(bucket_from, key, bucket_to, dest, options)
|
493
495
|
end
|
@@ -496,7 +498,7 @@ def copy(from, to, options)
|
|
496
498
|
else
|
497
499
|
key_to += File.basename(key_from) if key_to[-1..-1] == "/"
|
498
500
|
if !options[:overwrite] && s3_exist?(bucket_to, key_to)
|
499
|
-
|
501
|
+
$stderr.puts "Skipping s3://#{bucket_to}/#{key_to} - already exists."
|
500
502
|
else
|
501
503
|
s3_to_s3(bucket_from, key_from, bucket_to, key_to, options)
|
502
504
|
end
|
@@ -511,7 +513,7 @@ def copy(from, to, options)
|
|
511
513
|
#puts "relative(from, f) #{relative(from, f)}"
|
512
514
|
key = key_path key_to, relative(from, f)
|
513
515
|
if !options[:overwrite] && s3_exist?(bucket_to, key)
|
514
|
-
|
516
|
+
$stderr.puts "Skipping s3://#{bucket_to}/#{key} - already exists."
|
515
517
|
else
|
516
518
|
local_to_s3(bucket_to, key, File.expand_path(f), options)
|
517
519
|
end
|
@@ -520,7 +522,7 @@ def copy(from, to, options)
|
|
520
522
|
else
|
521
523
|
key_to += File.basename(from) if key_to[-1..-1] == "/"
|
522
524
|
if !options[:overwrite] && s3_exist?(bucket_to, key_to)
|
523
|
-
|
525
|
+
$stderr.puts "Skipping s3://#{bucket_to}/#{key_to} - already exists."
|
524
526
|
else
|
525
527
|
local_to_s3(bucket_to, key_to, File.expand_path(from), options)
|
526
528
|
end
|
@@ -539,7 +541,7 @@ def copy(from, to, options)
|
|
539
541
|
FileUtils.mkdir_p dir unless File.exist? dir
|
540
542
|
fail "Destination path is not a directory: #{dir}" unless File.directory?(dir)
|
541
543
|
if !options[:overwrite] && File.exist?(dest)
|
542
|
-
|
544
|
+
$stderr.puts "Skipping #{dest} - already exists."
|
543
545
|
else
|
544
546
|
s3_to_local(bucket_from, key, dest, options)
|
545
547
|
end
|
@@ -549,7 +551,7 @@ def copy(from, to, options)
|
|
549
551
|
dest = File.expand_path(to)
|
550
552
|
dest = File.join(dest, File.basename(key_from)) if File.directory?(dest)
|
551
553
|
if !options[:overwrite] && File.exist?(dest)
|
552
|
-
|
554
|
+
$stderr.puts "Skipping #{dest} - already exists."
|
553
555
|
else
|
554
556
|
s3_to_local(bucket_from, key_from, dest, options)
|
555
557
|
end
|
@@ -568,13 +570,8 @@ def copy(from, to, options)
|
|
568
570
|
end
|
569
571
|
end
|
570
572
|
|
571
|
-
|
573
|
+
S3CP.standard_exception_handling(options) do
|
572
574
|
sources.each do |source|
|
573
575
|
copy(source, destination, options)
|
574
576
|
end
|
575
|
-
rescue => e
|
576
|
-
$stderr.print "s3cp: [#{e.class}] #{e.message}\n"
|
577
|
-
if options[:debug]
|
578
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
579
|
-
end
|
580
577
|
end
|
data/lib/s3cp/s3du.rb
CHANGED
@@ -67,9 +67,6 @@ url = ARGV[0]
|
|
67
67
|
@bucket, @prefix = S3CP.bucket_and_key(url)
|
68
68
|
fail "Your URL looks funny, doesn't it?" unless @bucket
|
69
69
|
|
70
|
-
S3CP.load_config()
|
71
|
-
|
72
|
-
@s3 = S3CP.connect()
|
73
70
|
|
74
71
|
def depth(path)
|
75
72
|
path.count("/")
|
@@ -98,56 +95,57 @@ def print(key, size)
|
|
98
95
|
puts ("%#{7 + @options[:precision]}s " % size) + key
|
99
96
|
end
|
100
97
|
|
101
|
-
|
102
|
-
s3_options = Hash.new
|
103
|
-
s3_options[:bucket_name] = @bucket
|
104
|
-
s3_options[:prefix] = @prefix
|
105
|
-
|
106
|
-
begin
|
107
|
-
response = @s3.client.list_objects(s3_options)
|
108
|
-
response[:contents].each do |object|
|
109
|
-
|
110
|
-
key = object[:key]
|
111
|
-
size = object[:size].to_i
|
98
|
+
S3CP.standard_exception_handling(options) do
|
112
99
|
|
113
|
-
|
114
|
-
|
115
|
-
pos = nth_occurrence(key, "/", actual_depth)
|
116
|
-
(pos != -1) ? key[0..pos-1] : key
|
117
|
-
end
|
100
|
+
S3CP.load_config()
|
101
|
+
@s3 = S3CP.connect()
|
118
102
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
103
|
+
begin
|
104
|
+
s3_options = Hash.new
|
105
|
+
s3_options[:bucket_name] = @bucket
|
106
|
+
s3_options[:prefix] = @prefix
|
107
|
+
|
108
|
+
begin
|
109
|
+
response = @s3.client.list_objects(s3_options)
|
110
|
+
response[:contents].each do |object|
|
111
|
+
|
112
|
+
key = object[:key]
|
113
|
+
size = object[:size].to_i
|
114
|
+
|
115
|
+
if options[:regex].nil? || options[:regex].match(key)
|
116
|
+
current_key = if actual_depth
|
117
|
+
pos = nth_occurrence(key, "/", actual_depth)
|
118
|
+
(pos != -1) ? key[0..pos-1] : key
|
119
|
+
end
|
120
|
+
|
121
|
+
if (last_key && last_key != current_key)
|
122
|
+
print(last_key, subtotal_size)
|
123
|
+
subtotal_size = size
|
124
|
+
else
|
125
|
+
subtotal_size += size
|
126
|
+
end
|
127
|
+
|
128
|
+
last_key = current_key
|
129
|
+
total_size += size
|
124
130
|
end
|
125
|
-
|
126
|
-
last_key = current_key
|
127
|
-
total_size += size
|
128
131
|
end
|
129
|
-
end
|
130
132
|
|
131
|
-
|
133
|
+
break if response[:contents].empty?
|
132
134
|
|
133
|
-
|
134
|
-
|
135
|
+
s3_options.merge!(:marker => response[:contents].last[:key])
|
136
|
+
end while response[:truncated]
|
135
137
|
|
136
|
-
|
137
|
-
|
138
|
-
|
138
|
+
if last_key != nil
|
139
|
+
print(last_key, subtotal_size)
|
140
|
+
end
|
139
141
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
rescue Errno::EPIPE
|
146
|
-
|
147
|
-
rescue => e
|
148
|
-
$stderr.print "s3du: [#{e.class}] #{e.message}\n"
|
149
|
-
if options[:debug]
|
150
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
142
|
+
if options[:depth] > 0
|
143
|
+
print("", total_size)
|
144
|
+
else
|
145
|
+
puts S3CP.format_filesize(total_size, :unit => options[:unit], :precision => options[:precision])
|
146
|
+
end
|
147
|
+
rescue Errno::EPIPE
|
148
|
+
# ignore
|
151
149
|
end
|
152
150
|
end
|
153
151
|
|
data/lib/s3cp/s3lifecycle.rb
CHANGED
@@ -71,9 +71,6 @@ if ARGV.size == 0
|
|
71
71
|
exit
|
72
72
|
end
|
73
73
|
|
74
|
-
S3CP.load_config()
|
75
|
-
@s3 = S3CP.connect()
|
76
|
-
|
77
74
|
def time_or_date_str(msg, t)
|
78
75
|
if t.is_a?(Fixnum) || t.to_s =~ /^\d+$/
|
79
76
|
"%s after %d days" % [msg, t.to_i]
|
@@ -91,83 +88,88 @@ def rule_to_str(r)
|
|
91
88
|
[ r.prefix || "[root]", r.id, r.status, "???"]
|
92
89
|
end
|
93
90
|
end
|
91
|
+
S3CP.standard_exception_handling(options) do
|
92
|
+
|
93
|
+
S3CP.load_config()
|
94
|
+
@s3 = S3CP.connect()
|
95
|
+
|
96
|
+
paths.each do |path|
|
97
|
+
bucket,key = S3CP.bucket_and_key(path)
|
98
|
+
fail "Invalid bucket/key: #{path}" unless key
|
94
99
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
puts "Enabled rule: "
|
124
|
-
puts S3CP.tableify([rule_to_str(r)])
|
125
|
-
success = true
|
100
|
+
case
|
101
|
+
|
102
|
+
when options[:expire]
|
103
|
+
@s3.buckets[bucket].lifecycle_configuration.update do
|
104
|
+
rule_options = {}
|
105
|
+
rule_options[:id] = options[:name] if options[:name]
|
106
|
+
rule_options[:expiration_time] = S3CP.parse_days_or_date(options[:expire])
|
107
|
+
add_rule(key, rule_options)
|
108
|
+
end
|
109
|
+
|
110
|
+
when options[:glacier]
|
111
|
+
@s3.buckets[bucket].lifecycle_configuration.update do
|
112
|
+
rule_options = {}
|
113
|
+
rule_options[:id] = options[:name] if options[:name]
|
114
|
+
rule_options[:glacier_transition_time] = S3CP.parse_days_or_date(options[:glacier])
|
115
|
+
add_rule(key, rule_options)
|
116
|
+
end
|
117
|
+
|
118
|
+
when options[:enable]
|
119
|
+
success = false
|
120
|
+
@s3.buckets[bucket].lifecycle_configuration.update do
|
121
|
+
self.rules.each do |r|
|
122
|
+
if (r.prefix == key) || (r.id == options[:name])
|
123
|
+
r.enable!
|
124
|
+
puts "Enabled rule: "
|
125
|
+
puts S3CP.tableify([rule_to_str(r)])
|
126
|
+
success = true
|
127
|
+
end
|
126
128
|
end
|
127
129
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
130
|
+
fail "Rule or prefix not found" unless success
|
131
|
+
|
132
|
+
when options[:disable]
|
133
|
+
success = false
|
134
|
+
@s3.buckets[bucket].lifecycle_configuration.update do
|
135
|
+
self.rules.each do |r|
|
136
|
+
if (r.prefix == key) || (r.id == options[:name])
|
137
|
+
r.disabled!
|
138
|
+
puts "Disabled rule: "
|
139
|
+
puts S3CP.tableify([rule_to_str(r)])
|
140
|
+
success = true
|
141
|
+
end
|
140
142
|
end
|
141
143
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
144
|
+
fail "Rule or prefix not found" unless success
|
145
|
+
|
146
|
+
when options[:delete]
|
147
|
+
success = false
|
148
|
+
@s3.buckets[bucket].lifecycle_configuration.update do
|
149
|
+
self.rules.each do |r|
|
150
|
+
if (r.prefix == key) || (r.id == options[:name])
|
151
|
+
remove_rule(r)
|
152
|
+
puts "Deleted rule: "
|
153
|
+
puts S3CP.tableify([rule_to_str(r)])
|
154
|
+
success = true
|
155
|
+
end
|
154
156
|
end
|
155
157
|
end
|
156
|
-
|
157
|
-
fail "Rule or prefix not found" unless success
|
158
|
+
fail "Rule or prefix not found" unless success
|
158
159
|
|
159
|
-
else
|
160
|
-
rules = @s3.buckets[bucket].lifecycle_configuration.rules.to_a
|
161
|
-
if rules.empty?
|
162
|
-
puts "#{bucket} - no lifecycle rules"
|
163
160
|
else
|
164
|
-
|
165
|
-
|
166
|
-
puts
|
167
|
-
|
168
|
-
puts rules
|
169
|
-
|
161
|
+
rules = @s3.buckets[bucket].lifecycle_configuration.rules.to_a
|
162
|
+
if rules.empty?
|
163
|
+
puts "#{bucket} - no lifecycle rules"
|
164
|
+
else
|
165
|
+
puts "#{bucket} - lifecycle rules:"
|
166
|
+
begin
|
167
|
+
puts S3CP.tableify(rules.map { |r| rule_to_str(r) })
|
168
|
+
rescue => e
|
169
|
+
puts rules.inspect
|
170
|
+
raise e
|
171
|
+
end
|
170
172
|
end
|
171
|
-
|
173
|
+
end
|
172
174
|
end
|
173
175
|
end
|
data/lib/s3cp/s3ls.rb
CHANGED
@@ -90,75 +90,74 @@ if options[:debug]
|
|
90
90
|
puts "key #{@key}"
|
91
91
|
end
|
92
92
|
|
93
|
-
S3CP.load_config()
|
94
|
-
|
95
|
-
@s3 = S3CP.connect()
|
96
93
|
|
97
94
|
keys = 0
|
98
95
|
rows = 0
|
99
96
|
directories = true
|
100
97
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
98
|
+
S3CP.standard_exception_handling(options) do
|
99
|
+
|
100
|
+
S3CP.load_config()
|
101
|
+
|
102
|
+
@s3 = S3CP.connect()
|
103
|
+
|
104
|
+
begin
|
105
|
+
display = lambda do |entry|
|
106
|
+
# add '---' separator line between directories and files
|
107
|
+
if options[:delimiter] && directories && entry.is_a?(AWS::S3::Tree::LeafNode)
|
108
|
+
directories = false
|
109
|
+
puts "---"
|
110
|
+
end
|
111
|
+
|
112
|
+
key = "s3://#{@bucket}/#{entry.respond_to?(:key) ? entry.key : entry.prefix}"
|
113
|
+
if options[:long_format] && entry.last_modified && entry.content_length
|
114
|
+
size = entry.content_length
|
115
|
+
size = S3CP.format_filesize(size, :unit => options[:unit], :precision => options[:precision])
|
116
|
+
size = ("%#{7 + options[:precision]}s " % size)
|
117
|
+
puts "#{entry.last_modified.strftime(options[:date_format])} #{size} #{key}"
|
118
|
+
else
|
119
|
+
puts key
|
120
|
+
end
|
121
|
+
rows += 1
|
122
|
+
keys += 1
|
123
|
+
response = ''
|
124
|
+
|
125
|
+
if options[:rows_per_page] && (rows % options[:rows_per_page] == 0)
|
126
|
+
begin
|
127
|
+
print "Continue? (Y/n) "
|
128
|
+
response = STDIN.gets.chomp.downcase
|
129
|
+
end until response == 'n' || response == 'y' || response == ''
|
130
|
+
end
|
131
|
+
(response == 'n')
|
107
132
|
end
|
108
133
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
size = ("%#{7 + options[:precision]}s " % size)
|
114
|
-
puts "#{entry.last_modified.strftime(options[:date_format])} #{size} #{key}"
|
134
|
+
if options[:delimiter]
|
135
|
+
@s3.buckets[@bucket].objects.with_prefix(@key).as_tree(:delimier => options[:delimiter], :append => false).children.each do |entry|
|
136
|
+
break if display.call(entry)
|
137
|
+
end
|
115
138
|
else
|
116
|
-
|
117
|
-
end
|
118
|
-
rows += 1
|
119
|
-
keys += 1
|
120
|
-
response = ''
|
139
|
+
Struct.new("S3Entry", :key, :last_modified, :content_length)
|
121
140
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end until response == 'n' || response == 'y' || response == ''
|
127
|
-
end
|
128
|
-
(response == 'n')
|
129
|
-
end
|
141
|
+
s3_options = Hash.new
|
142
|
+
s3_options[:bucket_name] = @bucket
|
143
|
+
s3_options[:prefix] = @key
|
144
|
+
s3_options[:limit] = options[:max_keys] if options[:max_keys]
|
130
145
|
|
131
|
-
|
132
|
-
|
133
|
-
|
146
|
+
stop = false
|
147
|
+
|
148
|
+
begin
|
149
|
+
response = @s3.client.list_objects(s3_options)
|
150
|
+
response[:contents].each do |object|
|
151
|
+
entry = Struct::S3Entry.new(object[:key], object[:last_modified], object[:size].to_i)
|
152
|
+
stop = display.call(entry)
|
153
|
+
break if stop
|
154
|
+
end
|
155
|
+
break if stop || response[:contents].empty?
|
156
|
+
s3_options.merge!(:marker => response[:contents].last[:key])
|
157
|
+
end while response[:truncated]
|
134
158
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
s3_options = Hash.new
|
139
|
-
s3_options[:bucket_name] = @bucket
|
140
|
-
s3_options[:prefix] = @key
|
141
|
-
s3_options[:limit] = options[:max_keys] if options[:max_keys]
|
142
|
-
|
143
|
-
stop = false
|
144
|
-
|
145
|
-
begin
|
146
|
-
response = @s3.client.list_objects(s3_options)
|
147
|
-
response[:contents].each do |object|
|
148
|
-
entry = Struct::S3Entry.new(object[:key], object[:last_modified], object[:size].to_i)
|
149
|
-
stop = display.call(entry)
|
150
|
-
break if stop
|
151
|
-
end
|
152
|
-
break if stop || response[:contents].empty?
|
153
|
-
s3_options.merge!(:marker => response[:contents].last[:key])
|
154
|
-
end while response[:truncated]
|
155
|
-
end
|
156
|
-
rescue Errno::EPIPE
|
157
|
-
# ignore
|
158
|
-
rescue => e
|
159
|
-
$stderr.print "s3ls: [#{e.class}] #{e.message}\n"
|
160
|
-
if options[:debug]
|
161
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
159
|
+
rescue Errno::EPIPE
|
160
|
+
# ignore
|
162
161
|
end
|
163
162
|
end
|
164
163
|
|
data/lib/s3cp/s3mod.rb
CHANGED
@@ -91,7 +91,7 @@ if !options[:acl] && paths.size > 1 && S3CP::LEGAL_MODS.include?(paths.last)
|
|
91
91
|
options[:acl] = S3CP.validate_acl(paths.pop);
|
92
92
|
end
|
93
93
|
|
94
|
-
|
94
|
+
S3CP.standard_exception_handling(options) do
|
95
95
|
S3CP.load_config()
|
96
96
|
@s3 = S3CP.connect()
|
97
97
|
|
@@ -112,9 +112,4 @@ begin
|
|
112
112
|
end
|
113
113
|
}
|
114
114
|
end
|
115
|
-
rescue => e
|
116
|
-
$stderr.print "s3mod: [#{e.class}] #{e.message}\n"
|
117
|
-
if options[:debug]
|
118
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
119
|
-
end
|
120
115
|
end
|
data/lib/s3cp/s3rm.rb
CHANGED
@@ -102,7 +102,7 @@ end
|
|
102
102
|
include_regex = options[:include_regex] ? Regexp.new(options[:include_regex]) : nil
|
103
103
|
exclude_regex = options[:exclude_regex] ? Regexp.new(options[:exclude_regex]) : nil
|
104
104
|
|
105
|
-
|
105
|
+
S3CP.standard_exception_handling(options) do
|
106
106
|
S3CP.load_config()
|
107
107
|
|
108
108
|
@s3 = S3CP.connect()
|
@@ -147,10 +147,5 @@ begin
|
|
147
147
|
raise e unless e.is_a? AWS::S3::Errors::NoSuchKey
|
148
148
|
end
|
149
149
|
end
|
150
|
-
rescue => e
|
151
|
-
$stderr.print "s3rm: [#{e.class}] #{e.message}\n"
|
152
|
-
if options[:debug]
|
153
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
154
|
-
end
|
155
150
|
end
|
156
151
|
|
data/lib/s3cp/s3stat.rb
CHANGED
@@ -45,19 +45,21 @@ permission = ARGV.last
|
|
45
45
|
@bucket, @key = S3CP.bucket_and_key(source)
|
46
46
|
fail "Your URL looks funny, doesn't it?" unless @bucket
|
47
47
|
|
48
|
-
S3CP.
|
48
|
+
S3CP.standard_exception_handling(options) do
|
49
|
+
S3CP.load_config()
|
49
50
|
|
50
|
-
@s3 = S3CP.connect().buckets[@bucket]
|
51
|
+
@s3 = S3CP.connect().buckets[@bucket]
|
51
52
|
|
52
|
-
obj = @s3.objects[@key]
|
53
|
+
obj = @s3.objects[@key]
|
53
54
|
|
54
|
-
metadata = obj.head
|
55
|
-
metadata.to_h.keys.sort { |k1, k2| k1.to_s <=> k2.to_s}.each do |k|
|
56
|
-
|
57
|
-
end
|
55
|
+
metadata = obj.head
|
56
|
+
metadata.to_h.keys.sort { |k1, k2| k1.to_s <=> k2.to_s}.each do |k|
|
57
|
+
puts "#{"%30s" % k} #{metadata[k].is_a?(Hash) ? metadata[k].inspect : metadata[k].to_s}"
|
58
|
+
end
|
58
59
|
|
59
|
-
if @options[:acl]
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
if @options[:acl]
|
61
|
+
puts
|
62
|
+
xml = Nokogiri::XML(obj.acl.to_s)
|
63
|
+
puts xml.to_s
|
64
|
+
end
|
63
65
|
end
|
data/lib/s3cp/s3tree.rb
CHANGED
@@ -68,104 +68,101 @@ url = ARGV[0]
|
|
68
68
|
@bucket, @key = S3CP.bucket_and_key(url)
|
69
69
|
fail "Your URL looks funny, doesn't it?" unless @bucket
|
70
70
|
|
71
|
-
S3CP.load_config()
|
72
|
-
|
73
|
-
@s3 = S3CP.connect().buckets[@bucket]
|
74
71
|
|
75
72
|
keys = 0
|
76
73
|
rows = 0
|
77
74
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
case
|
82
|
-
when str[ch] then str.length-(str.reverse.index(ch)+1)
|
83
|
-
else -1
|
84
|
-
end
|
85
|
-
end
|
75
|
+
S3CP.standard_exception_handling(options) do
|
76
|
+
S3CP.load_config()
|
77
|
+
@s3 = S3CP.connect().buckets[@bucket]
|
86
78
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
rows += 1
|
95
|
-
response = ''
|
96
|
-
if options[:rows_per_page] && (rows % options[:rows_per_page] == 0)
|
97
|
-
begin
|
98
|
-
print "Continue? (Y/n) "
|
99
|
-
response = STDIN.gets.chomp.downcase
|
100
|
-
end until response == 'n' || response == 'y' || response == ''
|
79
|
+
begin
|
80
|
+
# find last index of character `ch` in `str`.
|
81
|
+
last_index_of = lambda do |str, ch|
|
82
|
+
case
|
83
|
+
when str[ch] then str.length-(str.reverse.index(ch)+1)
|
84
|
+
else -1
|
85
|
+
end
|
101
86
|
end
|
102
|
-
(response == 'n')
|
103
|
-
end
|
104
87
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
88
|
+
# displays the next line, returns true if user interrupts or max keys shown
|
89
|
+
display_line = lambda do |line|
|
90
|
+
puts line
|
91
|
+
keys += 1
|
92
|
+
if options[:max_keys] && keys > options[:max_keys]
|
93
|
+
return true
|
94
|
+
end
|
95
|
+
rows += 1
|
96
|
+
response = ''
|
97
|
+
if options[:rows_per_page] && (rows % options[:rows_per_page] == 0)
|
98
|
+
begin
|
99
|
+
print "Continue? (Y/n) "
|
100
|
+
response = STDIN.gets.chomp.downcase
|
101
|
+
end until response == 'n' || response == 'y' || response == ''
|
102
|
+
end
|
103
|
+
(response == 'n')
|
104
|
+
end
|
113
105
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
106
|
+
# returns relative path against @key
|
107
|
+
#
|
108
|
+
# e.g. relative.call("foo/bar") => "bar" (assuming @key = "foo/")
|
109
|
+
#
|
110
|
+
relative = lambda do |key|
|
111
|
+
last_delimiter = last_index_of.call(@key, options[:delimiter])
|
112
|
+
(last_delimiter != -1) ? key[last_delimiter..-1] : key
|
113
|
+
end
|
122
114
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
children.each_with_index do |node, index|
|
132
|
-
node = node
|
133
|
-
if options[:directories_only] && node.leaf?
|
134
|
-
next
|
135
|
-
end
|
115
|
+
# trim up to the last delimiter
|
116
|
+
#
|
117
|
+
# e.g. trim.call("foo/bar") => "foo/"
|
118
|
+
#
|
119
|
+
trim = lambda do |key|
|
120
|
+
last_delimiter = last_index_of.call(key, options[:delimiter])
|
121
|
+
(last_delimiter != -1) ? key[0..last_delimiter] : ""
|
122
|
+
end
|
136
123
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
124
|
+
# recursively display tree elements
|
125
|
+
#
|
126
|
+
# +prefix+: line prefix
|
127
|
+
# +children+: children of the current directory
|
128
|
+
# +depth+: current directory depth
|
129
|
+
display_tree = lambda do |prefix, children, depth|
|
130
|
+
stop = false
|
131
|
+
children = children.to_a # aws-sdk returns a sucky ChildCollection object
|
132
|
+
children.each_with_index do |node, index|
|
133
|
+
node = node
|
134
|
+
if options[:directories_only] && node.leaf?
|
135
|
+
next
|
136
|
+
end
|
137
|
+
|
138
|
+
last = (index == children.size - 1)
|
139
|
+
has_siblings = (children.size > 1)
|
140
|
+
key = node.branch? ? node.prefix : node.key
|
141
|
+
parts = relative.call(key).split(options[:delimiter])
|
142
|
+
postfix = last ? '└── ' : '├── '
|
143
|
+
|
144
|
+
stop = display_line.call(prefix + postfix + parts.last)
|
145
|
+
break if stop
|
142
146
|
|
143
|
-
|
144
|
-
|
147
|
+
if node.branch? && depth < options[:max_depth]
|
148
|
+
new_prefix = prefix + (has_siblings ? "│ " : " ")
|
149
|
+
stop = display_tree.call(new_prefix, node.children, depth + 1)
|
150
|
+
break if stop
|
151
|
+
end
|
145
152
|
|
146
|
-
if node.branch? && depth < options[:max_depth]
|
147
|
-
new_prefix = prefix + (has_siblings ? "│ " : " ")
|
148
|
-
stop = display_tree.call(new_prefix, node.children, depth + 1)
|
149
153
|
break if stop
|
150
154
|
end
|
151
|
-
|
152
|
-
break if stop
|
155
|
+
stop
|
153
156
|
end
|
154
|
-
stop
|
155
|
-
end
|
156
157
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
rescue Errno::EPIPE
|
164
|
-
|
165
|
-
rescue => e
|
166
|
-
$stderr.print "s3tree: [#{e.class}] #{e.message}\n"
|
167
|
-
if options[:debug]
|
168
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
158
|
+
display_line.call("s3://#{@bucket}/#{trim.call(@key)}")
|
159
|
+
|
160
|
+
prefix = ""
|
161
|
+
root = @s3.objects.with_prefix(@key).as_tree( :delimier => options[:delimiter], :append => false)
|
162
|
+
depth = 1
|
163
|
+
display_tree.call(prefix, root.children, depth)
|
164
|
+
rescue Errno::EPIPE
|
165
|
+
# ignore
|
169
166
|
end
|
170
167
|
end
|
171
168
|
|
data/lib/s3cp/s3up.rb
CHANGED
@@ -69,9 +69,8 @@ url = ARGV[0]
|
|
69
69
|
bucket, key = S3CP.bucket_and_key(url)
|
70
70
|
fail "Your URL looks funny, doesn't it?" unless bucket
|
71
71
|
|
72
|
-
|
72
|
+
S3CP.standard_exception_handling(options) do
|
73
73
|
S3CP.load_config()
|
74
|
-
|
75
74
|
@s3 = S3CP.connect()
|
76
75
|
|
77
76
|
# copy all of STDIN to a temp file
|
@@ -93,17 +92,12 @@ begin
|
|
93
92
|
S3CP.set_header_options(s3_options, @headers)
|
94
93
|
s3_options[:acl] = options[:acl]
|
95
94
|
@s3.buckets[bucket].objects[key].write(temp, s3_options)
|
96
|
-
|
95
|
+
$stderr.puts "s3://#{bucket}/#{key} => #{S3CP.format_filesize(temp.size)} "
|
97
96
|
ensure
|
98
97
|
# cleanup
|
99
98
|
temp.close
|
100
99
|
temp.delete
|
101
100
|
end
|
102
|
-
rescue => e
|
103
|
-
$stderr.print "s3up: [#{e.class}] #{e.message}\n"
|
104
|
-
if options[:debug]
|
105
|
-
$stderr.print e.backtrace.join("\n") + "\n"
|
106
|
-
end
|
107
101
|
end
|
108
102
|
|
109
103
|
|
data/lib/s3cp/utils.rb
CHANGED
@@ -250,6 +250,20 @@ module S3CP
|
|
250
250
|
yield bucket.objects[key]
|
251
251
|
end
|
252
252
|
end
|
253
|
+
|
254
|
+
def standard_exception_handling(options)
|
255
|
+
begin
|
256
|
+
yield
|
257
|
+
rescue Exception => ex
|
258
|
+
cmd_name ||= File.basename(caller[1].split(/:/)[0], '.*')
|
259
|
+
$stderr.print "#{cmd_name} [#{ex.class}] #{ex.message}\n"
|
260
|
+
if options[:debug]
|
261
|
+
$stderr.print ex.backtrace.join("\n") + "\n"
|
262
|
+
end
|
263
|
+
exit 1
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
253
267
|
end
|
254
268
|
|
255
269
|
# Monkey-patch S3 object for download streaming
|
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: 47
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 1.1.
|
9
|
+
- 30
|
10
|
+
version: 1.1.30
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex Boisvert
|