s3cp 1.1.29 → 1.1.30
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 +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
|