s3 0.3.2 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,19 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- s3 (0.3.2)
4
+ s3 (0.3.3)
5
5
  proxies
6
- trollop
7
6
 
8
7
  GEM
9
8
  remote: http://rubygems.org/
10
9
  specs:
11
10
  mocha (0.9.8)
12
11
  rake
13
- proxies (0.1.1)
12
+ proxies (0.2.1)
14
13
  rake (0.8.7)
15
14
  test-unit (2.1.1)
16
- trollop (1.16.2)
17
15
 
18
16
  PLATFORMS
19
17
  ruby
@@ -24,4 +22,3 @@ DEPENDENCIES
24
22
  proxies
25
23
  s3!
26
24
  test-unit (>= 2.0)
27
- trollop
data/lib/s3/bucket.rb CHANGED
@@ -85,7 +85,7 @@ module S3
85
85
  # Returns the objects in the bucket and caches the result (see
86
86
  # #reload method).
87
87
  def objects
88
- MethodProxy.new(self, :list_bucket, :extend => ObjectsExtension)
88
+ Proxy.new(lambda { list_bucket }, :owner => self, :extend => ObjectsExtension)
89
89
  end
90
90
 
91
91
  def inspect #:nodoc:
data/lib/s3/service.rb CHANGED
@@ -28,7 +28,7 @@ module S3
28
28
  @use_ssl = options.fetch(:use_ssl, false)
29
29
  @timeout = options.fetch(:timeout, 60)
30
30
  @debug = options.fetch(:debug, false)
31
-
31
+
32
32
  raise ArgumentError, "Missing proxy settings. Must specify at least :host." if options[:proxy] && !options[:proxy][:host]
33
33
  @proxy = options.fetch(:proxy, nil)
34
34
  end
@@ -36,7 +36,7 @@ module S3
36
36
  # Returns all buckets in the service and caches the result (see
37
37
  # +reload+)
38
38
  def buckets
39
- MethodProxy.new(self, :list_all_my_buckets, :extend => BucketsExtension)
39
+ Proxy.new(lambda { list_all_my_buckets}, :owner => self, :extend => BucketsExtension)
40
40
  end
41
41
 
42
42
  # Returns "http://" or "https://", depends on <tt>:use_ssl</tt>
data/lib/s3/signature.rb CHANGED
@@ -23,64 +23,108 @@ module S3
23
23
  # Generated signature string for given hostname and request
24
24
  def self.generate(options)
25
25
  request = options[:request]
26
- host = options[:host]
27
26
  access_key_id = options[:access_key_id]
28
- secret_access_key = options[:secret_access_key]
29
-
30
- http_verb = request.method
31
- content_md5 = request["content-md5"] || ""
32
- content_type = request["content-type"] || ""
33
- date = request["x-amz-date"].nil? ? request["date"] : ""
34
- canonicalized_resource = canonicalized_resource(host, request)
35
- canonicalized_amz_headers = canonicalized_amz_headers(request)
36
27
 
37
- string_to_sign = ""
38
- string_to_sign << http_verb
39
- string_to_sign << "\n"
40
- string_to_sign << content_md5
41
- string_to_sign << "\n"
42
- string_to_sign << content_type
43
- string_to_sign << "\n"
44
- string_to_sign << date
45
- string_to_sign << "\n"
46
- string_to_sign << canonicalized_amz_headers
47
- string_to_sign << canonicalized_resource
28
+ options.merge!(:headers => request,
29
+ :method => request.method,
30
+ :resource => request.path)
48
31
 
49
- digest = OpenSSL::Digest::Digest.new('sha1')
50
- hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
51
- base64 = Base64.encode64(hmac)
52
- signature = base64.chomp
32
+ signature = canonicalized_signature(options)
53
33
 
54
34
  "AWS #{access_key_id}:#{signature}"
55
35
  end
56
36
 
57
- # Generates temporary URL for given resource
37
+ # Generates temporary URL signature for given resource
58
38
  #
59
39
  # ==== Options
60
40
  # * <tt>:bucket</tt> - Bucket in which the resource resides
61
41
  # * <tt>:resource</tt> - Path to the resouce you want to create
62
- # a teporary link to
42
+ # a temporary link to
63
43
  # * <tt>:secret_access_key</tt> - Secret access key
64
- # * <tt>:expires_on</tt> - Unix time stamp of when the resouce
44
+ # * <tt>:expires_at</tt> - Unix time stamp of when the resouce
65
45
  # link will expire
46
+ # * <tt>:method</tt> - HTTP request method you want to use on
47
+ # the resource, defaults to GET
48
+ # * <tt>:headers</tt> - Any additional HTTP headers you intend
49
+ # to use when requesting the resource
66
50
  def self.generate_temporary_url_signature(options)
67
51
  bucket = options[:bucket]
68
52
  resource = options[:resource]
69
53
  secret_access_key = options[:secret_access_key]
70
54
  expires = options[:expires_at]
71
55
 
72
- string_to_sign = "GET\n\n\n#{expires.to_i.to_s}\n/#{bucket}/#{resource}";
56
+ headers = options[:headers] || {}
57
+ headers.merge!('date' => expires.to_i.to_s)
73
58
 
74
- digest = OpenSSL::Digest::Digest.new("sha1")
75
- hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
76
- base64 = Base64.encode64(hmac)
77
- signature = base64.chomp
59
+ options.merge!(:resource => "/#{bucket}/#{resource}",
60
+ :method => options[:method] || :get,
61
+ :headers => headers)
62
+ signature = canonicalized_signature(options)
78
63
 
79
64
  CGI.escape(signature)
80
65
  end
81
66
 
67
+ # Generates temporary URL for given resource
68
+ #
69
+ # ==== Options
70
+ # * <tt>:bucket</tt> - Bucket in which the resource resides
71
+ # * <tt>:resource</tt> - Path to the resouce you want to create
72
+ # a temporary link to
73
+ # * <tt>:access_key</tt> - Access key
74
+ # * <tt>:secret_access_key</tt> - Secret access key
75
+ # * <tt>:expires_at</tt> - Unix time stamp of when the resouce
76
+ # link will expire
77
+ # * <tt>:method</tt> - HTTP request method you want to use on
78
+ # the resource, defaults to GET
79
+ # * <tt>:headers</tt> - Any additional HTTP headers you intend
80
+ # to use when requesting the resource
81
+ def self.generate_temporary_url(options)
82
+ bucket = options[:bucket]
83
+ resource = options[:resource]
84
+ access_key = options[:access_key]
85
+ expires = options[:expires_at].to_i
86
+ signature = generate_temporary_url_signature(options)
87
+
88
+ url = "http://#{S3::HOST}/#{bucket}/#{resource}"
89
+ url << "?AWSAccessKeyId=#{access_key}"
90
+ url << "&Expires=#{expires}"
91
+ url << "&Signature=#{signature}"
92
+ end
93
+
82
94
  private
83
95
 
96
+ def self.canonicalized_signature(options)
97
+ headers = options[:headers] || {}
98
+ host = options[:host] || ""
99
+ resource = options[:resource]
100
+ access_key_id = options[:access_key_id]
101
+ secret_access_key = options[:secret_access_key]
102
+
103
+ http_verb = options[:method].to_s.upcase
104
+ content_md5 = headers["content-md5"] || ""
105
+ content_type = headers["content-type"] || ""
106
+ date = headers["x-amz-date"].nil? ? headers["date"] : ""
107
+ canonicalized_resource = canonicalized_resource(host, resource)
108
+ canonicalized_amz_headers = canonicalized_amz_headers(headers)
109
+
110
+ string_to_sign = ""
111
+ string_to_sign << http_verb
112
+ string_to_sign << "\n"
113
+ string_to_sign << content_md5
114
+ string_to_sign << "\n"
115
+ string_to_sign << content_type
116
+ string_to_sign << "\n"
117
+ string_to_sign << date
118
+ string_to_sign << "\n"
119
+ string_to_sign << canonicalized_amz_headers
120
+ string_to_sign << canonicalized_resource
121
+
122
+ digest = OpenSSL::Digest::Digest.new('sha1')
123
+ hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
124
+ base64 = Base64.encode64(hmac)
125
+ base64.chomp
126
+ end
127
+
84
128
  # Helper method for extracting header fields from Net::HTTPRequest
85
129
  # and preparing them for singing in #generate method
86
130
  #
@@ -157,7 +201,7 @@ module S3
157
201
  #
158
202
  # ==== Returns
159
203
  # String containing extracted canonicalized resource
160
- def self.canonicalized_resource(host, request)
204
+ def self.canonicalized_resource(host, resource)
161
205
  # 1. Start with the empty string ("").
162
206
  string = ""
163
207
 
@@ -172,7 +216,7 @@ module S3
172
216
 
173
217
  # 3. Append the path part of the un-decoded HTTP Request-URI,
174
218
  # up-to but not including the query string.
175
- uri = URI.parse(request.path)
219
+ uri = URI.parse(resource)
176
220
  string << uri.path
177
221
 
178
222
  # 4. If the request addresses a sub-resource, like ?location,
data/lib/s3/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module S3
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.4"
3
3
  end
data/s3.gemspec CHANGED
@@ -9,13 +9,12 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Jakub Kuźma"]
10
10
  s.email = ["qoobaa@gmail.com"]
11
11
  s.homepage = "http://jah.pl/projects/s3.html"
12
- s.summary = "Library for accessing S3 objects and buckets, with command line tool"
12
+ s.summary = "Library for accessing S3 objects and buckets"
13
13
  s.description = "S3 library provides access to Amazon's Simple Storage Service. It supports both: European and US buckets through REST API."
14
14
 
15
15
  s.required_rubygems_version = ">= 1.3.6"
16
16
  s.rubyforge_project = "s3"
17
17
 
18
- s.add_dependency "trollop"
19
18
  s.add_dependency "proxies"
20
19
  s.add_development_dependency "test-unit", ">= 2.0"
21
20
  s.add_development_dependency "mocha"
@@ -140,4 +140,66 @@ class SignatureTest < Test::Unit::TestCase
140
140
  expected = "AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="
141
141
  assert_equal expected, actual
142
142
  end
143
+
144
+ test "temporary signature for object get" do
145
+ actual = S3::Signature.generate_temporary_url_signature(
146
+ :bucket => "johnsmith",
147
+ :resource => "photos/puppy.jpg",
148
+ :secret_access_key => "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
149
+ :expires_at => 1175046589
150
+ )
151
+ expected = "gs6xNznrLJ4Bd%2B1y9pcy2HOSVeg%3D"
152
+ assert_equal expected, actual
153
+ end
154
+
155
+ test "temporary signature for object post" do
156
+ actual = S3::Signature.generate_temporary_url_signature(
157
+ :bucket => "johnsmith",
158
+ :resource => "photos/puppy.jpg",
159
+ :secret_access_key => "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
160
+ :expires_at => 1175046589,
161
+ :method => :post
162
+ )
163
+ expected = "duIzwO2KTEMIlbSYbFFS86Wj0LI%3D"
164
+ assert_equal expected, actual
165
+ end
166
+
167
+ test "temporary signature for object put with headers" do
168
+ actual = S3::Signature.generate_temporary_url_signature(
169
+ :bucket => "johnsmith",
170
+ :resource => "photos/puppy.jpg",
171
+ :secret_access_key => "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
172
+ :expires_at => 1175046589,
173
+ :method => :put,
174
+ :headers => {'x-amz-acl' => 'public-read'}
175
+ )
176
+ expected = "SDMxjIkOKIVR47nWfJ57UNPXxFM%3D"
177
+ assert_equal expected, actual
178
+ end
179
+
180
+ test "temporary signature for object delete" do
181
+ actual = S3::Signature.generate_temporary_url_signature(
182
+ :bucket => "johnsmith",
183
+ :resource => "photos/puppy.jpg",
184
+ :secret_access_key => "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
185
+ :expires_at => 1175046589,
186
+ :method => :delete
187
+ )
188
+ expected = "5Vg7A4HxgS6tVCYzBx%2BkMR8sztY%3D"
189
+ assert_equal expected, actual
190
+ end
191
+
192
+ test "temporary url for object put with headers" do
193
+ actual = S3::Signature.generate_temporary_url(
194
+ :bucket => "johnsmith",
195
+ :resource => "photos/puppy.jpg",
196
+ :access_key => '0PN5J17HBGZHT7JJ3X82',
197
+ :secret_access_key => "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
198
+ :expires_at => 1175046589,
199
+ :method => :put,
200
+ :headers => {'x-amz-acl' => 'public-read'}
201
+ )
202
+ expected = "http://s3.amazonaws.com/johnsmith/photos/puppy.jpg?AWSAccessKeyId=0PN5J17HBGZHT7JJ3X82&Expires=1175046589&Signature=SDMxjIkOKIVR47nWfJ57UNPXxFM%3D"
203
+ assert_equal expected, actual
204
+ end
143
205
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 2
9
- version: 0.3.2
8
+ - 4
9
+ version: 0.3.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - "Jakub Ku\xC5\xBAma"
@@ -14,11 +14,11 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-06 00:00:00 +02:00
17
+ date: 2010-09-25 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: trollop
21
+ name: proxies
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
@@ -30,23 +30,10 @@ dependencies:
30
30
  version: "0"
31
31
  type: :runtime
32
32
  version_requirements: *id001
33
- - !ruby/object:Gem::Dependency
34
- name: proxies
35
- prerelease: false
36
- requirement: &id002 !ruby/object:Gem::Requirement
37
- none: false
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- segments:
42
- - 0
43
- version: "0"
44
- type: :runtime
45
- version_requirements: *id002
46
33
  - !ruby/object:Gem::Dependency
47
34
  name: test-unit
48
35
  prerelease: false
49
- requirement: &id003 !ruby/object:Gem::Requirement
36
+ requirement: &id002 !ruby/object:Gem::Requirement
50
37
  none: false
51
38
  requirements:
52
39
  - - ">="
@@ -56,11 +43,11 @@ dependencies:
56
43
  - 0
57
44
  version: "2.0"
58
45
  type: :development
59
- version_requirements: *id003
46
+ version_requirements: *id002
60
47
  - !ruby/object:Gem::Dependency
61
48
  name: mocha
62
49
  prerelease: false
63
- requirement: &id004 !ruby/object:Gem::Requirement
50
+ requirement: &id003 !ruby/object:Gem::Requirement
64
51
  none: false
65
52
  requirements:
66
53
  - - ">="
@@ -69,11 +56,11 @@ dependencies:
69
56
  - 0
70
57
  version: "0"
71
58
  type: :development
72
- version_requirements: *id004
59
+ version_requirements: *id003
73
60
  - !ruby/object:Gem::Dependency
74
61
  name: bundler
75
62
  prerelease: false
76
- requirement: &id005 !ruby/object:Gem::Requirement
63
+ requirement: &id004 !ruby/object:Gem::Requirement
77
64
  none: false
78
65
  requirements:
79
66
  - - ">="
@@ -84,12 +71,12 @@ dependencies:
84
71
  - 0
85
72
  version: 1.0.0
86
73
  type: :development
87
- version_requirements: *id005
74
+ version_requirements: *id004
88
75
  description: "S3 library provides access to Amazon's Simple Storage Service. It supports both: European and US buckets through REST API."
89
76
  email:
90
77
  - qoobaa@gmail.com
91
- executables:
92
- - s3
78
+ executables: []
79
+
93
80
  extensions: []
94
81
 
95
82
  extra_rdoc_files: []
@@ -101,7 +88,6 @@ files:
101
88
  - LICENSE
102
89
  - README.rdoc
103
90
  - Rakefile
104
- - bin/s3
105
91
  - extra/s3_attachment_fu.rb
106
92
  - extra/s3_paperclip.rb
107
93
  - lib/s3.rb
@@ -155,6 +141,6 @@ rubyforge_project: s3
155
141
  rubygems_version: 1.3.7
156
142
  signing_key:
157
143
  specification_version: 3
158
- summary: Library for accessing S3 objects and buckets, with command line tool
144
+ summary: Library for accessing S3 objects and buckets
159
145
  test_files: []
160
146
 
data/bin/s3 DELETED
@@ -1,187 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
4
-
5
- require "trollop"
6
- require "s3"
7
-
8
- # HELPER METHODS
9
-
10
- include S3
11
-
12
- def list_buckets(service)
13
- service.buckets.each do |bucket|
14
- puts bucket.name
15
- end
16
- end
17
-
18
- def create_bucket(service, name, location)
19
- service.buckets.build(name).save(location)
20
- end
21
-
22
- def destroy_bucket(service, name)
23
- service.buckets.find(name).destroy
24
- end
25
-
26
- def show_bucket(service, name, options = {})
27
- service.buckets.find(name).objects.find_all.each do |object|
28
- puts "#{name}/#{object.key}"
29
- end
30
- end
31
-
32
- def list_objects(service)
33
- service.buckets.each do |bucket|
34
- bucket.objects.each do |object|
35
- puts "#{bucket.name}/#{object.key}"
36
- end
37
- end
38
- end
39
-
40
- def create_object(service, name, file_name, options = {})
41
- bucket_name, object_name = name.split("/", 2)
42
- object = service.buckets.find(bucket_name).objects.build(object_name)
43
- object.content_type = options[:type]
44
- object.content_encoding = options[:encoding]
45
- object.content_disposition = options[:disposition]
46
- object.acl = options[:acl]
47
- object.content = File.new(file_name)
48
- object.save
49
- end
50
-
51
- def destroy_object(service, name)
52
- bucket_name, object_name = name.split("/", 2)
53
- object = service.buckets.find(bucket_name).objects.find(object_name)
54
- object.destroy
55
- end
56
-
57
- def show_object(service, name, file_name = nil)
58
- bucket_name, object_name = name.split("/", 2)
59
- object = service.buckets.find(bucket_name).objects.find_first(object_name)
60
- puts " object: #{object.name}/#{object.key}"
61
- puts " content type: #{object.content_type}"
62
- puts " size: #{object.size}"
63
- puts " etag: #{object.etag}"
64
- puts " last modified: #{object.last_modified}"
65
- if file_name
66
- if file_name == "-"
67
- puts object.content
68
- else
69
- File.open(file_name, "wb") do |file|
70
- file.write(object.content)
71
- end
72
- end
73
- end
74
- end
75
-
76
- # COMMAND LINE PARSER
77
-
78
- ACCESS_KEY_ID = ENV["ACCESS_KEY_ID"]
79
- SECRET_ACCESS_KEY = ENV["SECRET_ACCESS_KEY"]
80
- COMMANDS = %w(bucket object)
81
- BUCKET_SUBCOMMANDS = %w(add remove show)
82
- OBJECT_SUBCOMMANDS = %w(add remove show)
83
-
84
- global_options = Trollop::options do
85
- banner "s3 command line tool"
86
- opt :access_key_id, "Your access key id to AWS", :type => :string, :default => ACCESS_KEY_ID
87
- opt :secret_access_key, "Your secret access key to AWS", :type => :string, :default => SECRET_ACCESS_KEY
88
- opt :debug, "Debug mode", :type => :flag, :default => false
89
- stop_on COMMANDS
90
- end
91
-
92
- Trollop::die "No access key id given" unless global_options[:access_key_id]
93
- Trollop::die "No secret access key given" unless global_options[:secret_access_key]
94
-
95
- service = Service.new(:access_key_id => global_options[:access_key_id],
96
- :secret_access_key => global_options[:secret_access_key],
97
- :debug => global_options[:debug])
98
-
99
- command = ARGV.shift
100
-
101
- begin
102
- case command
103
- when "bucket"
104
- command_options = Trollop::options do
105
- banner "manage buckets"
106
- stop_on BUCKET_SUBCOMMANDS
107
- end
108
- subcommand = ARGV.shift
109
- case subcommand
110
- when "add"
111
- subcommand_options = Trollop::options do
112
- banner "add bucket"
113
- opt :location, "Location of the bucket - EU or US", :default => "US", :type => :string
114
- end
115
- name = ARGV.shift
116
- Trollop::die "Bucket has not been added because of unknown error" unless create_bucket(service, name, subcommand_options[:location])
117
- when "remove"
118
- subcommand_options = Trollop::options do
119
- banner "remove bucket"
120
- end
121
- name = ARGV.shift
122
- Trollop::die "Bucket name must be given" if name.nil? or name.empty?
123
- Trollop::die "Bucket has not been removed because of unknown error" unless destroy_bucket(service, name)
124
- when "show"
125
- subcommand_options = Trollop::options do
126
- banner "show bucket"
127
- opt :prefix, "Limits the response to keys which begin with the indicated prefix", :type => :string
128
- opt :marker, "Indicates where in the bucket to begin listing", :type => :string
129
- opt :max_keys, "The maximum number of keys you'd like to see", :type => :integer
130
- opt :delimiter, "Causes keys that contain the same string between the prefix and the first occurrence of the delimiter to be rolled up into a single result element", :type => :string
131
- end
132
- name = ARGV.shift
133
- Trollop::die "Bucket name must be given" if name.nil? or name.empty?
134
- show_bucket(service, name, subcommand_options)
135
- when nil
136
- list_buckets(service)
137
- else
138
- Trollop::die "Unknown subcommand: #{subcommand.inspect}"
139
- end
140
- when "object"
141
- command_options = Trollop::options do
142
- banner "manage objects"
143
- stop_on OBJECT_SUBCOMMANDS
144
- end
145
- subcommand = ARGV.shift
146
- case subcommand
147
- when "add"
148
- subcommand_options = Trollop::options do
149
- banner "object add s3_object_name local_file_name"
150
- opt :type, "A standard MIME type describing the format of the contents", :default => "binary/octet-stream"
151
- opt :disposition, "Specifies presentational information for the object", :type => :string
152
- opt :encoding, "Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied in order to obtain the media-type referenced by the Content-Type header field", :type => :string
153
- opt :acl, "The canned ACL to apply to the object. Options include private, public-read, public-read-write, and authenticated-read", :type => :string
154
- end
155
- name = ARGV.shift
156
- Trollop::die "No object name given" if name.nil? or name.empty?
157
- file_name = ARGV.shift
158
- Trollop::die "No file name given" if file_name.nil? or file_name.empty?
159
- Trollop::die "Object has not been added because of unknown error" unless create_object(service, name, file_name, subcommand_options)
160
- when "remove"
161
- subcommand_options = Trollop::options do
162
- banner "object remove s3_object_name"
163
- end
164
- name = ARGV.shift
165
- Trollop::die "No object name given" if name.nil? or name.empty?
166
- Trollop::die "Object has not been removed because of unknown error" unless destroy_object(service, name)
167
- when "show"
168
- subcommand_options = Trollop::options do
169
- banner "object show s3_object_name optional_file_name"
170
- end
171
- name = ARGV.shift
172
- Trollop::die "No object name given" if name.nil? or name.empty?
173
- file_name = ARGV.shift
174
- show_object(service, name, file_name)
175
- when nil
176
- list_objects(service)
177
- else
178
- Trollop::die "Unknown subcommand: #{subcommand.inspect}"
179
- end
180
- when nil
181
- Trollop::die "No command given"
182
- else
183
- Trollop::die "Unknown command #{command.inspect}"
184
- end
185
- rescue Error::ResponseError => e
186
- Trollop::die e.message.sub(/\.+\Z/, "")
187
- end