aliyun-oss-ruby-sdk 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +95 -0
  3. data/README.md +423 -0
  4. data/examples/aliyun/oss/bucket.rb +144 -0
  5. data/examples/aliyun/oss/callback.rb +61 -0
  6. data/examples/aliyun/oss/object.rb +182 -0
  7. data/examples/aliyun/oss/resumable_download.rb +42 -0
  8. data/examples/aliyun/oss/resumable_upload.rb +49 -0
  9. data/examples/aliyun/oss/streaming.rb +124 -0
  10. data/examples/aliyun/oss/using_sts.rb +48 -0
  11. data/examples/aliyun/sts/assume_role.rb +59 -0
  12. data/lib/aliyun_sdk/common.rb +6 -0
  13. data/lib/aliyun_sdk/common/exception.rb +18 -0
  14. data/lib/aliyun_sdk/common/logging.rb +46 -0
  15. data/lib/aliyun_sdk/common/struct.rb +56 -0
  16. data/lib/aliyun_sdk/oss.rb +16 -0
  17. data/lib/aliyun_sdk/oss/bucket.rb +661 -0
  18. data/lib/aliyun_sdk/oss/client.rb +106 -0
  19. data/lib/aliyun_sdk/oss/config.rb +39 -0
  20. data/lib/aliyun_sdk/oss/download.rb +255 -0
  21. data/lib/aliyun_sdk/oss/exception.rb +108 -0
  22. data/lib/aliyun_sdk/oss/http.rb +338 -0
  23. data/lib/aliyun_sdk/oss/iterator.rb +92 -0
  24. data/lib/aliyun_sdk/oss/multipart.rb +74 -0
  25. data/lib/aliyun_sdk/oss/object.rb +15 -0
  26. data/lib/aliyun_sdk/oss/protocol.rb +1499 -0
  27. data/lib/aliyun_sdk/oss/struct.rb +208 -0
  28. data/lib/aliyun_sdk/oss/upload.rb +238 -0
  29. data/lib/aliyun_sdk/oss/util.rb +89 -0
  30. data/lib/aliyun_sdk/sts.rb +9 -0
  31. data/lib/aliyun_sdk/sts/client.rb +38 -0
  32. data/lib/aliyun_sdk/sts/config.rb +22 -0
  33. data/lib/aliyun_sdk/sts/exception.rb +53 -0
  34. data/lib/aliyun_sdk/sts/protocol.rb +130 -0
  35. data/lib/aliyun_sdk/sts/struct.rb +64 -0
  36. data/lib/aliyun_sdk/sts/util.rb +48 -0
  37. data/lib/aliyun_sdk/version.rb +7 -0
  38. data/spec/aliyun/oss/bucket_spec.rb +597 -0
  39. data/spec/aliyun/oss/client/bucket_spec.rb +554 -0
  40. data/spec/aliyun/oss/client/client_spec.rb +297 -0
  41. data/spec/aliyun/oss/client/resumable_download_spec.rb +220 -0
  42. data/spec/aliyun/oss/client/resumable_upload_spec.rb +413 -0
  43. data/spec/aliyun/oss/http_spec.rb +83 -0
  44. data/spec/aliyun/oss/multipart_spec.rb +686 -0
  45. data/spec/aliyun/oss/object_spec.rb +785 -0
  46. data/spec/aliyun/oss/service_spec.rb +142 -0
  47. data/spec/aliyun/oss/util_spec.rb +50 -0
  48. data/spec/aliyun/sts/client_spec.rb +150 -0
  49. data/spec/aliyun/sts/util_spec.rb +39 -0
  50. data/tests/config.rb +31 -0
  51. data/tests/test_content_encoding.rb +54 -0
  52. data/tests/test_content_type.rb +95 -0
  53. data/tests/test_custom_headers.rb +70 -0
  54. data/tests/test_encoding.rb +77 -0
  55. data/tests/test_large_file.rb +66 -0
  56. data/tests/test_multipart.rb +97 -0
  57. data/tests/test_object_acl.rb +49 -0
  58. data/tests/test_object_key.rb +68 -0
  59. data/tests/test_object_url.rb +69 -0
  60. data/tests/test_resumable.rb +40 -0
  61. metadata +240 -0
@@ -0,0 +1,9 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'common'
4
+ require_relative 'sts/util'
5
+ require_relative 'sts/exception'
6
+ require_relative 'sts/struct'
7
+ require_relative 'sts/config'
8
+ require_relative 'sts/protocol'
9
+ require_relative 'sts/client'
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module AliyunSDK
4
+ module STS
5
+
6
+ # STS服务的客户端,用于向STS申请临时token。
7
+ # @example 创建Client
8
+ # client = Client.new(
9
+ # :access_key_id => 'access_key_id',
10
+ # :access_key_secret => 'access_key_secret')
11
+ # token = client.assume_role('role:arn', 'app')
12
+ #
13
+ # policy = Policy.new
14
+ # policy.allow(['oss:Get*'], ['acs:oss:*:*:my-bucket/*'])
15
+ # token = client.assume_role('role:arn', 'app', policy, 60)
16
+ # puts token.to_s
17
+ class Client
18
+
19
+ def initialize(opts)
20
+ @config = Config.new(opts)
21
+ @protocol = Protocol.new(@config)
22
+ end
23
+
24
+ # Assume a role
25
+ # @param role [String] the role arn
26
+ # @param session [String] the session name
27
+ # @param policy [STS::Policy] the policy
28
+ # @param duration [Fixnum] the duration seconds for the
29
+ # requested token
30
+ # @return [STS::Token] the sts token
31
+ def assume_role(role, session, policy = nil, duration = 3600)
32
+ @protocol.assume_role(role, session, policy, duration)
33
+ end
34
+
35
+ end # Client
36
+
37
+ end # STS
38
+ end # Aliyun
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module AliyunSDK
4
+ module STS
5
+
6
+ # A place to store various configurations: credentials, api
7
+ # timeout, retry mechanism, etc
8
+ class Config < Common::Struct::Base
9
+
10
+ attrs :access_key_id, :access_key_secret, :endpoint
11
+
12
+ def initialize(opts = {})
13
+ super(opts)
14
+
15
+ @access_key_id = @access_key_id.strip if @access_key_id
16
+ @access_key_secret = @access_key_secret.strip if @access_key_secret
17
+ @endpoint = @endpoint.strip if @endpoint
18
+ end
19
+ end # Config
20
+
21
+ end # STS
22
+ end # Aliyun
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'nokogiri'
4
+
5
+ module AliyunSDK
6
+ module STS
7
+
8
+ # ServerError represents exceptions from the STS
9
+ # service. i.e. Client receives a HTTP response whose status is
10
+ # NOT OK. #message provides the error message and #to_s gives
11
+ # detailed information probably including the STS request id.
12
+ class ServerError < Common::Exception
13
+
14
+ attr_reader :http_code, :error_code, :message, :request_id
15
+
16
+ def initialize(response)
17
+ @http_code = response.code
18
+ @attrs = {}
19
+
20
+ doc = Nokogiri::XML(response.body) do |config|
21
+ config.options |= Nokogiri::XML::ParseOptions::NOBLANKS
22
+ end rescue nil
23
+
24
+ if doc and doc.root
25
+ doc.root.children.each do |n|
26
+ @attrs[n.name] = n.text
27
+ end
28
+ end
29
+
30
+ @error_code = @attrs['Code']
31
+ @message = @attrs['Message']
32
+ @request_id = @attrs['RequestId']
33
+ end
34
+
35
+ def message
36
+ msg = @attrs['Message'] || "UnknownError[#{http_code}]."
37
+ "#{msg} RequestId: #{request_id}"
38
+ end
39
+
40
+ def to_s
41
+ @attrs.merge({'HTTPCode' => @http_code}).map do |k, v|
42
+ [k, v].join(": ")
43
+ end.join(", ")
44
+ end
45
+ end # ServerError
46
+
47
+ # ClientError represents client exceptions caused mostly by
48
+ # invalid parameters.
49
+ class ClientError < Common::Exception
50
+ end # ClientError
51
+
52
+ end # STS
53
+ end # Aliyun
@@ -0,0 +1,130 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'rest-client'
4
+ require 'nokogiri'
5
+ require 'time'
6
+
7
+ module AliyunSDK
8
+ module STS
9
+
10
+ # Protocol implements the STS Open API which is low-level. User
11
+ # should refer to {STS::Client} for normal use.
12
+ class Protocol
13
+
14
+ ENDPOINT = 'https://sts.aliyuncs.com'
15
+ FORMAT = 'XML'
16
+ API_VERSION = '2015-04-01'
17
+ SIGNATURE_METHOD = 'HMAC-SHA1'
18
+ SIGNATURE_VERSION = '1.0'
19
+
20
+ include Common::Logging
21
+
22
+ def initialize(config)
23
+ @config = config
24
+ end
25
+
26
+ # Assume a role
27
+ # @param role [String] the role arn
28
+ # @param session [String] the session name
29
+ # @param policy [STS::Policy] the policy
30
+ # @param duration [Fixnum] the duration seconds for the
31
+ # requested token
32
+ # @return [STS::Token] the sts token
33
+ def assume_role(role, session, policy = nil, duration = 3600)
34
+ logger.info("Begin assume role, role: #{role}, session: #{session}, "\
35
+ "policy: #{policy}, duration: #{duration}")
36
+
37
+ params = {
38
+ 'Action' => 'AssumeRole',
39
+ 'RoleArn' => role,
40
+ 'RoleSessionName' => session,
41
+ 'DurationSeconds' => duration.to_s
42
+ }
43
+ params.merge!({'Policy' => policy.serialize}) if policy
44
+
45
+ body = do_request(params)
46
+ doc = parse_xml(body)
47
+
48
+ creds_node = doc.at_css("Credentials")
49
+ creds = {
50
+ session_name: session,
51
+ access_key_id: get_node_text(creds_node, 'AccessKeyId'),
52
+ access_key_secret: get_node_text(creds_node, 'AccessKeySecret'),
53
+ security_token: get_node_text(creds_node, 'SecurityToken'),
54
+ expiration: get_node_text(
55
+ creds_node, 'Expiration') { |x| Time.parse(x) },
56
+ }
57
+
58
+ logger.info("Done assume role, creds: #{creds}")
59
+
60
+ Token.new(creds)
61
+ end
62
+
63
+ private
64
+ # Generate a random signature nonce
65
+ # @return [String] a random string
66
+ def signature_nonce
67
+ (rand * 1_000_000_000).to_s
68
+ end
69
+
70
+ # Do HTTP POST request with specified params
71
+ # @param params [Hash] the parameters to STS
72
+ # @return [String] the response body
73
+ # @raise [ServerError] raise errors if the server responds with errors
74
+ def do_request(params)
75
+ query = params.merge(
76
+ {'Format' => FORMAT,
77
+ 'Version' => API_VERSION,
78
+ 'AccessKeyId' => @config.access_key_id,
79
+ 'SignatureMethod' => SIGNATURE_METHOD,
80
+ 'SignatureVersion' => SIGNATURE_VERSION,
81
+ 'SignatureNonce' => signature_nonce,
82
+ 'Timestamp' => Time.now.utc.iso8601})
83
+
84
+ signature = Util.get_signature('POST', query, @config.access_key_secret)
85
+ query.merge!({'Signature' => signature})
86
+
87
+ r = RestClient::Request.execute(
88
+ :method => 'POST',
89
+ :url => @config.endpoint || ENDPOINT,
90
+ :payload => query
91
+ ) do |response, request, result, &blk|
92
+
93
+ if response.code >= 300
94
+ e = ServerError.new(response)
95
+ logger.error(e.to_s)
96
+ raise e
97
+ else
98
+ response.return!(request, result, &blk)
99
+ end
100
+ end
101
+
102
+ logger.debug("Received HTTP response, code: #{r.code}, headers: "\
103
+ "#{r.headers}, body: #{r.body}")
104
+ r.body
105
+ end
106
+
107
+ # Parse body content to xml document
108
+ # @param content [String] the xml content
109
+ # @return [Nokogiri::XML::Document] the parsed document
110
+ def parse_xml(content)
111
+ doc = Nokogiri::XML(content) do |config|
112
+ config.options |= Nokogiri::XML::ParseOptions::NOBLANKS
113
+ end
114
+
115
+ doc
116
+ end
117
+
118
+ # Get the text of a xml node
119
+ # @param node [Nokogiri::XML::Node] the xml node
120
+ # @param tag [String] the node tag
121
+ # @yield [String] the node text is given to the block
122
+ def get_node_text(node, tag, &block)
123
+ n = node.at_css(tag) if node
124
+ value = n.text if n
125
+ block && value ? yield(value) : value
126
+ end
127
+
128
+ end # Protocol
129
+ end # STS
130
+ end # Aliyun
@@ -0,0 +1,64 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'json'
4
+ require 'cgi'
5
+
6
+ module AliyunSDK
7
+ module STS
8
+
9
+ # STS Policy. Referer to
10
+ # https://help.aliyun.com/document_detail/ram/ram-user-guide/policy_reference/struct_def.html for details.
11
+ class Policy < Common::Struct::Base
12
+ VERSION = '1'
13
+
14
+ attrs :rules
15
+
16
+ # Add an 'Allow' rule
17
+ # @param actions [Array<String>] actions of the rule. e.g.:
18
+ # oss:GetObject, oss:Get*, oss:*
19
+ # @param resources [Array<String>] resources of the rule. e.g.:
20
+ # acs:oss:*:*:my-bucket, acs:oss:*:*:my-bucket/*, acs:oss:*:*:*
21
+ def allow(actions, resources)
22
+ add_rule(true, actions, resources)
23
+ end
24
+
25
+ # Add an 'Deny' rule
26
+ # @param actions [Array<String>] actions of the rule. e.g.:
27
+ # oss:GetObject, oss:Get*, oss:*
28
+ # @param resources [Array<String>] resources of the rule. e.g.:
29
+ # acs:oss:*:*:my-bucket, acs:oss:*:*:my-bucket/*, acs:oss:*:*:*
30
+ def deny(actions, resources)
31
+ add_rule(false, actions, resources)
32
+ end
33
+
34
+ # Serialize to rule to string
35
+ def serialize
36
+ {'Version' => VERSION, 'Statement' => @rules}.to_json
37
+ end
38
+
39
+ private
40
+ def add_rule(allow, actions, resources)
41
+ @rules ||= []
42
+ @rules << {
43
+ 'Effect' => allow ? 'Allow' : 'Deny',
44
+ 'Action' => actions,
45
+ 'Resource' => resources
46
+ }
47
+ end
48
+ end
49
+
50
+ # STS token. User may use the credentials included to access
51
+ # Alicloud resources(OSS, OTS, etc).
52
+ # Attributes:
53
+ # * access_key_id [String] the AccessKeyId
54
+ # * access_key_secret [String] the AccessKeySecret
55
+ # * security_token [String] the SecurityToken
56
+ # * expiration [Time] the time when the token will be expired
57
+ # * session_name [String] the session name for this token
58
+ class Token < Common::Struct::Base
59
+ attrs :access_key_id, :access_key_secret,
60
+ :security_token, :expiration, :session_name
61
+ end
62
+
63
+ end # STS
64
+ end # Aliyun
@@ -0,0 +1,48 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'time'
4
+ require 'cgi'
5
+ require 'base64'
6
+ require 'openssl'
7
+ require 'digest/md5'
8
+
9
+ module AliyunSDK
10
+ module STS
11
+ ##
12
+ # Util functions to help generate formatted Date, signatures,
13
+ # etc.
14
+ #
15
+ module Util
16
+
17
+ class << self
18
+
19
+ include Common::Logging
20
+
21
+ # Calculate request signatures
22
+ def get_signature(verb, params, key)
23
+ logger.debug("Sign, verb: #{verb}, params: #{params}")
24
+
25
+ cano_query = params.sort.map {
26
+ |k, v| [CGI.escape(k), CGI.escape(v)].join('=') }.join('&')
27
+
28
+ string_to_sign =
29
+ verb + '&' + CGI.escape('/') + '&' + CGI.escape(cano_query)
30
+
31
+ logger.debug("String to sign: #{string_to_sign}")
32
+
33
+ Util.sign(key + '&', string_to_sign)
34
+ end
35
+
36
+ # Sign a string using HMAC and BASE64
37
+ # @param [String] key the secret key
38
+ # @param [String] string_to_sign the string to sign
39
+ # @return [String] the signature
40
+ def sign(key, string_to_sign)
41
+ Base64.strict_encode64(
42
+ OpenSSL::HMAC.digest('sha1', key, string_to_sign))
43
+ end
44
+
45
+ end # self
46
+ end # Util
47
+ end # STS
48
+ end # Aliyun
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module AliyunSDK
4
+
5
+ VERSION = "0.4.1"
6
+
7
+ end # Aliyun
@@ -0,0 +1,597 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+ require 'yaml'
5
+ require 'nokogiri'
6
+
7
+ module AliyunSDK
8
+ module OSS
9
+
10
+ describe "Bucket" do
11
+
12
+ before :all do
13
+ @endpoint = 'oss.aliyuncs.com'
14
+ @protocol = Protocol.new(
15
+ Config.new(:endpoint => @endpoint,
16
+ :access_key_id => 'xxx', :access_key_secret => 'yyy'))
17
+ @bucket = 'rubysdk-bucket'
18
+ end
19
+
20
+ def request_path
21
+ @bucket + "." + @endpoint
22
+ end
23
+
24
+ def mock_location(location)
25
+ Nokogiri::XML::Builder.new do |xml|
26
+ xml.CreateBucketConfiguration {
27
+ xml.LocationConstraint location
28
+ }
29
+ end.to_xml
30
+ end
31
+
32
+ def mock_objects(objects, more = {})
33
+ Nokogiri::XML::Builder.new do |xml|
34
+ xml.ListBucketResult {
35
+ {
36
+ :prefix => 'Prefix',
37
+ :delimiter => 'Delimiter',
38
+ :limit => 'MaxKeys',
39
+ :marker => 'Marker',
40
+ :next_marker => 'NextMarker',
41
+ :truncated => 'IsTruncated',
42
+ :encoding => 'EncodingType'
43
+ }.map do |k, v|
44
+ xml.send(v, more[k]) if more[k]
45
+ end
46
+
47
+ objects.each do |o|
48
+ xml.Contents {
49
+ xml.Key o
50
+ xml.LastModified Time.now.to_s
51
+ xml.Type 'Normal'
52
+ xml.Size 1024
53
+ xml.StorageClass 'Standard'
54
+ xml.Etag 'etag'
55
+ xml.Owner {
56
+ xml.ID '10086'
57
+ xml.DisplayName 'CMCC'
58
+ }
59
+ }
60
+ end
61
+
62
+ (more[:common_prefixes] || []).each do |p|
63
+ xml.CommonPrefixes {
64
+ xml.Prefix p
65
+ }
66
+ end
67
+ }
68
+ end.to_xml
69
+ end
70
+
71
+ def mock_acl(acl)
72
+ Nokogiri::XML::Builder.new do |xml|
73
+ xml.AccessControlPolicy {
74
+ xml.Owner {
75
+ xml.ID 'owner_id'
76
+ xml.DisplayName 'owner_name'
77
+ }
78
+
79
+ xml.AccessControlList {
80
+ xml.Grant acl
81
+ }
82
+ }
83
+ end.to_xml
84
+ end
85
+
86
+ def mock_logging(opts)
87
+ Nokogiri::XML::Builder.new do |xml|
88
+ xml.BucketLoggingStatus {
89
+ if opts.enabled?
90
+ xml.LoggingEnabled {
91
+ xml.TargetBucket opts.target_bucket
92
+ xml.TargetPrefix opts.target_prefix
93
+ }
94
+ end
95
+ }
96
+ end.to_xml
97
+ end
98
+
99
+ def mock_website(opts)
100
+ Nokogiri::XML::Builder.new do |xml|
101
+ xml.WebsiteConfiguration {
102
+ xml.IndexDocument {
103
+ xml.Suffix opts.index
104
+ }
105
+ if opts.error
106
+ xml.ErrorDocument {
107
+ xml.Key opts.error
108
+ }
109
+ end
110
+ }
111
+ end.to_xml
112
+ end
113
+
114
+ def mock_referer(opts)
115
+ Nokogiri::XML::Builder.new do |xml|
116
+ xml.RefererConfiguration {
117
+ xml.AllowEmptyReferer opts.allow_empty?
118
+ xml.RefererList {
119
+ opts.whitelist.each do |r|
120
+ xml.Referer r
121
+ end
122
+ }
123
+ }
124
+ end.to_xml
125
+ end
126
+
127
+ def mock_lifecycle(rules)
128
+ Nokogiri::XML::Builder.new do |xml|
129
+ xml.LifecycleConfiguration {
130
+ rules.each do |r|
131
+ xml.Rule {
132
+ xml.ID r.id if r.id
133
+ xml.Status r.enabled? ? 'Enabled' : 'Disabled'
134
+ xml.Prefix r.prefix
135
+ xml.Expiration {
136
+ if r.expiry.is_a?(Date)
137
+ xml.Date Time.utc(r.expiry.year, r.expiry.month, r.expiry.day)
138
+ .iso8601.sub('Z', '.000Z')
139
+ else
140
+ xml.Days r.expiry.to_i
141
+ end
142
+ }
143
+ }
144
+ end
145
+ }
146
+ end.to_xml
147
+ end
148
+
149
+ def mock_cors(rules)
150
+ Nokogiri::XML::Builder.new do |xml|
151
+ xml.CORSConfiguration {
152
+ rules.each do |r|
153
+ xml.CORSRule {
154
+ r.allowed_origins.each do |x|
155
+ xml.AllowedOrigin x
156
+ end
157
+ r.allowed_methods.each do |x|
158
+ xml.AllowedMethod x
159
+ end
160
+ r.allowed_headers.each do |x|
161
+ xml.AllowedHeader x
162
+ end
163
+ r.expose_headers.each do |x|
164
+ xml.ExposeHeader x
165
+ end
166
+ xml.MaxAgeSeconds r.max_age_seconds if r.max_age_seconds
167
+ }
168
+ end
169
+ }
170
+ end.to_xml
171
+ end
172
+
173
+ def mock_error(code, message)
174
+ Nokogiri::XML::Builder.new do |xml|
175
+ xml.Error {
176
+ xml.Code code
177
+ xml.Message message
178
+ xml.RequestId '0000'
179
+ }
180
+ end.to_xml
181
+ end
182
+
183
+ def err(msg, reqid = '0000')
184
+ "#{msg} RequestId: #{reqid}"
185
+ end
186
+
187
+ context "Create bucket" do
188
+
189
+ it "should PUT to create bucket" do
190
+ stub_request(:put, request_path)
191
+
192
+ @protocol.create_bucket(@bucket)
193
+
194
+ expect(WebMock).to have_requested(:put, request_path)
195
+ .with(:body => nil, :query => {})
196
+ end
197
+
198
+ it "should set location when create bucket" do
199
+ location = 'oss-cn-hangzhou'
200
+
201
+ stub_request(:put, request_path).with(:body => mock_location(location))
202
+
203
+ @protocol.create_bucket(@bucket, :location => 'oss-cn-hangzhou')
204
+
205
+ expect(WebMock).to have_requested(:put, request_path)
206
+ .with(:body => mock_location(location), :query => {})
207
+ end
208
+ end # create bucket
209
+
210
+ context "List objects" do
211
+
212
+ it "should list all objects" do
213
+ stub_request(:get, request_path)
214
+
215
+ @protocol.list_objects(@bucket)
216
+
217
+ expect(WebMock).to have_requested(:get, request_path)
218
+ .with(:body => nil, :query => {})
219
+ end
220
+
221
+ it "should parse object response" do
222
+ return_objects = ['hello', 'world', 'foo/bar']
223
+ stub_request(:get, request_path)
224
+ .to_return(:body => mock_objects(return_objects))
225
+
226
+ objects, more = @protocol.list_objects(@bucket)
227
+
228
+ expect(WebMock).to have_requested(:get, request_path)
229
+ .with(:body => nil, :query => {})
230
+
231
+ expect(objects.map {|o| o.key}).to match_array(return_objects)
232
+ expect(more).to be_empty
233
+ end
234
+
235
+ it "should list objects with prefix & delimiter" do
236
+ # Webmock cannot capture the request_path encoded query parameters,
237
+ # so we use 'foo-bar' instead of 'foo/bar' to work around
238
+ # the problem
239
+ opts = {
240
+ :marker => 'foo-bar',
241
+ :prefix => 'foo-',
242
+ :delimiter => '-',
243
+ :limit => 10,
244
+ :encoding => KeyEncoding::URL}
245
+
246
+ query = opts.clone
247
+ query['max-keys'] = query.delete(:limit)
248
+ query['encoding-type'] = query.delete(:encoding)
249
+
250
+ stub_request(:get, request_path).with(:query => query)
251
+
252
+ @protocol.list_objects(@bucket, opts)
253
+
254
+ expect(WebMock).to have_requested(:get, request_path)
255
+ .with(:body => "", :query => query)
256
+ end
257
+
258
+ it "should parse object and common prefixes response" do
259
+ return_objects = ['hello', 'world', 'foo-bar']
260
+ return_more = {
261
+ :marker => 'foo-bar',
262
+ :prefix => 'foo-',
263
+ :delimiter => '-',
264
+ :limit => 10,
265
+ :encoding => KeyEncoding::URL,
266
+ :next_marker => 'foo-xxx',
267
+ :truncated => true,
268
+ :common_prefixes => ['foo/bar/', 'foo/xxx/']
269
+ }
270
+
271
+ opts = {
272
+ :marker => 'foo-bar',
273
+ :prefix => 'foo-',
274
+ :delimiter => '-',
275
+ :limit => 10,
276
+ :encoding => KeyEncoding::URL
277
+ }
278
+
279
+ query = opts.clone
280
+ query['max-keys'] = query.delete(:limit)
281
+ query['encoding-type'] = query.delete(:encoding)
282
+
283
+ stub_request(:get, request_path).with(:query => query).
284
+ to_return(:body => mock_objects(return_objects, return_more))
285
+
286
+ objects, more = @protocol.list_objects(@bucket, opts)
287
+
288
+ expect(WebMock).to have_requested(:get, request_path)
289
+ .with(:body => nil, :query => query)
290
+
291
+ expect(objects.map {|o| o.key}).to match_array(return_objects)
292
+ expect(more).to eq(return_more)
293
+ end
294
+
295
+ it "should decode object key" do
296
+ return_objects = ['中国のruby', 'world', 'foo/bar']
297
+ return_more = {
298
+ :marker => '杭州のruby',
299
+ :prefix => 'foo-',
300
+ :delimiter => '分隔のruby',
301
+ :limit => 10,
302
+ :encoding => KeyEncoding::URL,
303
+ :next_marker => '西湖のruby',
304
+ :truncated => true,
305
+ :common_prefixes => ['玉泉のruby', '苏堤のruby']
306
+ }
307
+
308
+ es_objects = [CGI.escape('中国のruby'), 'world', 'foo/bar']
309
+ es_more = {
310
+ :marker => CGI.escape('杭州のruby'),
311
+ :prefix => 'foo-',
312
+ :delimiter => CGI.escape('分隔のruby'),
313
+ :limit => 10,
314
+ :encoding => KeyEncoding::URL,
315
+ :next_marker => CGI.escape('西湖のruby'),
316
+ :truncated => true,
317
+ :common_prefixes => [CGI.escape('玉泉のruby'), CGI.escape('苏堤のruby')]
318
+ }
319
+
320
+ stub_request(:get, request_path)
321
+ .to_return(:body => mock_objects(es_objects, es_more))
322
+
323
+ objects, more = @protocol.list_objects(@bucket)
324
+
325
+ expect(WebMock).to have_requested(:get, request_path)
326
+ .with(:body => nil, :query => {})
327
+
328
+ expect(objects.map {|o| o.key}).to match_array(return_objects)
329
+ expect(more).to eq(return_more)
330
+ end
331
+ end # list objects
332
+
333
+ context "Delete bucket" do
334
+
335
+ it "should send DELETE reqeust" do
336
+ stub_request(:delete, request_path)
337
+
338
+ @protocol.delete_bucket(@bucket)
339
+
340
+ expect(WebMock).to have_requested(:delete, request_path)
341
+ .with(:body => nil, :query => {})
342
+ end
343
+
344
+ it "should raise Exception on error" do
345
+ code = "NoSuchBucket"
346
+ message = "The bucket to delete does not exist."
347
+
348
+ stub_request(:delete, request_path).to_return(
349
+ :status => 404, :body => mock_error(code, message))
350
+
351
+ expect {
352
+ @protocol.delete_bucket(@bucket)
353
+ }.to raise_error(ServerError, err(message))
354
+ end
355
+ end # delete bucket
356
+
357
+ context "acl, logging, website, referer, lifecycle" do
358
+ it "should update acl" do
359
+ query = {'acl' => ''}
360
+ stub_request(:put, request_path).with(:query => query)
361
+
362
+ @protocol.put_bucket_acl(@bucket, ACL::PUBLIC_READ)
363
+
364
+ expect(WebMock).to have_requested(:put, request_path)
365
+ .with(:query => query, :body => nil)
366
+ end
367
+
368
+ it "should get acl" do
369
+ query = {'acl' => ''}
370
+ return_acl = ACL::PUBLIC_READ
371
+ stub_request(:get, request_path)
372
+ .with(:query => query)
373
+ .to_return(:body => mock_acl(return_acl))
374
+
375
+ acl = @protocol.get_bucket_acl(@bucket)
376
+
377
+ expect(WebMock).to have_requested(:get, request_path)
378
+ .with(:query => query, :body => nil)
379
+ expect(acl).to eq(return_acl)
380
+ end
381
+
382
+ it "should enable logging" do
383
+ query = {'logging' => ''}
384
+ stub_request(:put, request_path).with(:query => query)
385
+
386
+ logging_opts = BucketLogging.new(
387
+ :enable => true,
388
+ :target_bucket => 'target-bucket', :target_prefix => 'foo')
389
+ @protocol.put_bucket_logging(@bucket, logging_opts)
390
+
391
+ expect(WebMock).to have_requested(:put, request_path)
392
+ .with(:query => query, :body => mock_logging(logging_opts))
393
+ end
394
+
395
+ it "should disable logging" do
396
+ query = {'logging' => ''}
397
+ stub_request(:put, request_path).with(:query => query)
398
+
399
+ logging_opts = BucketLogging.new(:enable => false)
400
+ @protocol.put_bucket_logging(@bucket, logging_opts)
401
+
402
+ expect(WebMock).to have_requested(:put, request_path)
403
+ .with(:query => query, :body => mock_logging(logging_opts))
404
+ end
405
+
406
+ it "should get logging" do
407
+ query = {'logging' => ''}
408
+ logging_opts = BucketLogging.new(
409
+ :enable => true,
410
+ :target_bucket => 'target-bucket', :target_prefix => 'foo')
411
+
412
+ stub_request(:get, request_path)
413
+ .with(:query => query)
414
+ .to_return(:body => mock_logging(logging_opts))
415
+
416
+ logging = @protocol.get_bucket_logging(@bucket)
417
+
418
+ expect(WebMock).to have_requested(:get, request_path)
419
+ .with(:query => query, :body => nil)
420
+ expect(logging.to_s).to eq(logging_opts.to_s)
421
+ end
422
+
423
+ it "should delete logging" do
424
+ query = {'logging' => ''}
425
+ stub_request(:delete, request_path).with(:query => query)
426
+
427
+ @protocol.delete_bucket_logging(@bucket)
428
+
429
+ expect(WebMock).to have_requested(:delete, request_path)
430
+ .with(:query => query, :body => nil)
431
+ end
432
+
433
+ it "should update website" do
434
+ query = {'website' => ''}
435
+ stub_request(:put, request_path).with(:query => query)
436
+
437
+ website_opts = BucketWebsite.new(
438
+ :enable => true, :index => 'index.html', :error => 'error.html')
439
+ @protocol.put_bucket_website(@bucket, website_opts)
440
+
441
+ expect(WebMock).to have_requested(:put, request_path)
442
+ .with(:query => query, :body => mock_website(website_opts))
443
+ end
444
+
445
+ it "should get website" do
446
+ query = {'website' => ''}
447
+ website_opts = BucketWebsite.new(
448
+ :enable => true, :index => 'index.html', :error => 'error.html')
449
+
450
+ stub_request(:get, request_path)
451
+ .with(:query => query)
452
+ .to_return(:body => mock_website(website_opts))
453
+
454
+ opts = @protocol.get_bucket_website(@bucket)
455
+
456
+ expect(WebMock).to have_requested(:get, request_path)
457
+ .with(:query => query, :body => nil)
458
+ expect(opts.to_s).to eq(website_opts.to_s)
459
+ end
460
+
461
+ it "should delete website" do
462
+ query = {'website' => ''}
463
+ stub_request(:delete, request_path).with(:query => query)
464
+
465
+ @protocol.delete_bucket_website(@bucket)
466
+
467
+ expect(WebMock).to have_requested(:delete, request_path)
468
+ .with(:query => query, :body => nil)
469
+ end
470
+
471
+ it "should update referer" do
472
+ query = {'referer' => ''}
473
+ stub_request(:put, request_path).with(:query => query)
474
+
475
+ referer_opts = BucketReferer.new(
476
+ :allow_empty => true, :whitelist => ['xxx', 'yyy'])
477
+ @protocol.put_bucket_referer(@bucket, referer_opts)
478
+
479
+ expect(WebMock).to have_requested(:put, request_path)
480
+ .with(:query => query, :body => mock_referer(referer_opts))
481
+ end
482
+
483
+ it "should get referer" do
484
+ query = {'referer' => ''}
485
+ referer_opts = BucketReferer.new(
486
+ :allow_empty => true, :whitelist => ['xxx', 'yyy'])
487
+
488
+ stub_request(:get, request_path)
489
+ .with(:query => query)
490
+ .to_return(:body => mock_referer(referer_opts))
491
+
492
+ opts = @protocol.get_bucket_referer(@bucket)
493
+
494
+ expect(WebMock).to have_requested(:get, request_path)
495
+ .with(:query => query, :body => nil)
496
+ expect(opts.to_s).to eq(referer_opts.to_s)
497
+ end
498
+
499
+ it "should update lifecycle" do
500
+ query = {'lifecycle' => ''}
501
+ stub_request(:put, request_path).with(:query => query)
502
+
503
+ rules = (1..5).map do |i|
504
+ LifeCycleRule.new(
505
+ :id => i, :enable => i % 2 == 0, :prefix => "foo#{i}",
506
+ :expiry => (i % 2 == 1 ? Date.today : 10 + i))
507
+ end
508
+
509
+ @protocol.put_bucket_lifecycle(@bucket, rules)
510
+
511
+ expect(WebMock).to have_requested(:put, request_path)
512
+ .with(:query => query, :body => mock_lifecycle(rules))
513
+ end
514
+
515
+ it "should get lifecycle" do
516
+ query = {'lifecycle' => ''}
517
+ return_rules = (1..5).map do |i|
518
+ LifeCycleRule.new(
519
+ :id => i, :enable => i % 2 == 0, :prefix => "foo#{i}",
520
+ :expiry => (i % 2 == 1 ? Date.today : 10 + i))
521
+ end
522
+
523
+ stub_request(:get, request_path)
524
+ .with(:query => query)
525
+ .to_return(:body => mock_lifecycle(return_rules))
526
+
527
+ rules = @protocol.get_bucket_lifecycle(@bucket)
528
+
529
+ expect(WebMock).to have_requested(:get, request_path)
530
+ .with(:query => query, :body => nil)
531
+ expect(rules.map(&:to_s)).to match_array(return_rules.map(&:to_s))
532
+ end
533
+
534
+ it "should delete lifecycle" do
535
+ query = {'lifecycle' => ''}
536
+ stub_request(:delete, request_path).with(:query => query)
537
+
538
+ @protocol.delete_bucket_lifecycle(@bucket)
539
+
540
+ expect(WebMock).to have_requested(:delete, request_path)
541
+ .with(:query => query, :body => nil)
542
+ end
543
+
544
+ it "should set cors" do
545
+ query = {'cors' => ''}
546
+ stub_request(:put, request_path).with(:query => query)
547
+
548
+ rules = (1..5).map do |i|
549
+ CORSRule.new(
550
+ :allowed_origins => (1..3).map {|x| "origin-#{x}"},
551
+ :allowed_methods => ['PUT', 'GET'],
552
+ :allowed_headers => (1..3).map {|x| "header-#{x}"},
553
+ :expose_headers => (1..3).map {|x| "header-#{x}"})
554
+ end
555
+ @protocol.set_bucket_cors(@bucket, rules)
556
+
557
+ expect(WebMock).to have_requested(:put, request_path)
558
+ .with(:query => query, :body => mock_cors(rules))
559
+ end
560
+
561
+ it "should get cors" do
562
+ query = {'cors' => ''}
563
+ return_rules = (1..5).map do |i|
564
+ CORSRule.new(
565
+ :allowed_origins => (1..3).map {|x| "origin-#{x}"},
566
+ :allowed_methods => ['PUT', 'GET'],
567
+ :allowed_headers => (1..3).map {|x| "header-#{x}"},
568
+ :expose_headers => (1..3).map {|x| "header-#{x}"})
569
+ end
570
+
571
+ stub_request(:get, request_path)
572
+ .with(:query => query)
573
+ .to_return(:body => mock_cors(return_rules))
574
+
575
+ rules = @protocol.get_bucket_cors(@bucket)
576
+
577
+ expect(WebMock).to have_requested(:get, request_path)
578
+ .with(:query => query, :body => nil)
579
+ expect(rules.map(&:to_s)).to match_array(return_rules.map(&:to_s))
580
+ end
581
+
582
+ it "should delete cors" do
583
+ query = {'cors' => ''}
584
+
585
+ stub_request(:delete, request_path).with(:query => query)
586
+
587
+ @protocol.delete_bucket_cors(@bucket)
588
+ expect(WebMock).to have_requested(:delete, request_path)
589
+ .with(:query => query, :body => nil)
590
+ end
591
+
592
+ end # acl, logging, cors, etc
593
+
594
+ end # Bucket
595
+
596
+ end # OSS
597
+ end # Aliyun