aliyunsdkcore 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e2aa2eda4b1227ed6df56755568a56581b32687d09ffafd7a5d409ae97693f9c
4
+ data.tar.gz: ac4f65d9277dbd14c0aa8cce81031b7a5235b12263d0c29bf39e381ab76d3a36
5
+ SHA512:
6
+ metadata.gz: a0a9cc6e88a2aad8345da1e3091d319339b9c82f1d3f933bb66a42e0567260d79c2dd54ab908637b097131e3dfe03252c1b163b7398eaef90b12e86e4e05ed20
7
+ data.tar.gz: 715239a7c720126240e85aa19cba49a5ccd7d78dbc0c6e4048e1f7b079ae2f0b8c259a92b59204b167c4fb94176973558c996946fbad9c456bf974a34cf754f1
data/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # Alibaba Cloud Ruby Software Development Kit(Working in progress)
2
+
3
+ [![Build Status](https://travis-ci.org/aliyun/openapi-core-ruby-sdk.svg?branch=master)](https://travis-ci.org/aliyun/openapi-core-ruby-sdk)
4
+ [![codecov](https://codecov.io/gh/aliyun/openapi-core-ruby-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/aliyun/openapi-core-ruby-sdk)
5
+
6
+ The Alibaba Cloud Ruby Software Development Kit (SDK) allows you to access Alibaba Cloud services such as Elastic Compute Service (ECS), Server Load Balancer (SLB), and CloudMonitor. You can access Alibaba Cloud services without the need to handle API related tasks, such as signing and constructing your requests.
7
+
8
+ This document introduces how to install and use Alibaba Cloud Ruby SDK.
9
+
10
+ If you have any problem while using Ruby SDK, please submit an issue.
data/lib/roa_client.rb ADDED
@@ -0,0 +1,150 @@
1
+ require 'faraday'
2
+ require 'securerandom'
3
+ require 'active_support/all'
4
+
5
+ class ROAClient
6
+
7
+ attr_accessor :endpoint, :api_version, :access_key_id, :access_key_secret, :security_token, :hostname, :opts
8
+
9
+ def initialize(config)
10
+
11
+ validate config
12
+
13
+ self.endpoint = config[:endpoint]
14
+ self.api_version = config[:api_version]
15
+ self.access_key_id = config[:access_key_id]
16
+ self.access_key_secret = config[:access_key_secret]
17
+ self.security_token = config[:security_token]
18
+
19
+ end
20
+
21
+ def request(method:, uri:, params: {}, body: {}, headers: {}, options: {})
22
+
23
+ mix_headers = default_headers.merge(headers)
24
+
25
+ response = connection.send(method.downcase) do |request|
26
+ request.url uri, params
27
+ if body.present?
28
+ request_body = body.to_json
29
+ request.body = request_body
30
+ mix_headers['content-md5'] = Digest::MD5.base64digest request_body
31
+ mix_headers['content-length'] = request_body.length.to_s
32
+ end
33
+ string_to_sign = string_to_sign(method, uri, mix_headers, params)
34
+ mix_headers.merge!(authorization: authorization(string_to_sign))
35
+ mix_headers.each { |key, value| request.headers[key] = value }
36
+ end
37
+
38
+ return response if options.has_key? :raw_body
39
+
40
+ response_content_type = response.headers['Content-Type'] || ''
41
+ if response_content_type.start_with?('application/json')
42
+ if response.status >= 400
43
+ result = JSON.parse(response.body)
44
+ raise StandardError, "code: #{response.status}, #{result['Message']} requestid: #{result['RequestId']}"
45
+ end
46
+ end
47
+
48
+ if response_content_type.start_with?('text/xml')
49
+ result = Hash.from_xml(response.body)
50
+ raise ACSError, result['Error'] if result['Error']
51
+ end
52
+
53
+ response
54
+ end
55
+
56
+ def connection(adapter = Faraday.default_adapter)
57
+ Faraday.new(:url => self.endpoint) { |faraday| faraday.adapter adapter }
58
+ end
59
+
60
+ def get(uri: '', headers: {}, params: {}, options: {})
61
+ request(method: :get, uri: uri, params: params, body: {}, headers: headers, options: options)
62
+ end
63
+
64
+ def post(uri: '', headers: {}, params: {}, body: {}, options: {})
65
+ request(method: :get, uri: uri, params: params, body: body, headers: headers, options: options)
66
+ end
67
+
68
+ def put(uri: '', headers: {}, params: {}, body: {}, options: {})
69
+ request(method: :get, uri: uri, params: params, body: body, headers: headers, options: options)
70
+ end
71
+
72
+ def delete(uri: '', headers: {}, params: {}, options: {})
73
+ request(method: :get, uri: uri, params: params, body: {}, headers: headers, options: options)
74
+ end
75
+
76
+ def default_headers
77
+ default_headers = {
78
+ 'accept': 'application/json',
79
+ 'date': Time.now.httpdate,
80
+ 'host': URI(self.endpoint).host,
81
+ 'x-acs-signature-nonce': SecureRandom.hex(16),
82
+ 'x-acs-signature-method': 'HMAC-SHA1',
83
+ 'x-acs-signature-version': '1.0',
84
+ 'x-acs-version': self.api_version,
85
+ 'x-sdk-client': "RUBY(#{RUBY_VERSION})" # FIXME: 如何获取Gem的名称和版本号
86
+ }
87
+ if self.security_token
88
+ default_headers.merge!('x-acs-accesskey-id': self.access_key_id, 'x-acs-security-token': self.security_token)
89
+ end
90
+ default_headers
91
+ end
92
+
93
+ private
94
+
95
+ def string_to_sign(method, uri, headers, query = {})
96
+ headers.stringify_keys!
97
+ header_string = [
98
+ method,
99
+ headers['accept'],
100
+ headers['content-md5'] || '',
101
+ headers['content-type'] || '',
102
+ headers['date'],
103
+ ].join("\n")
104
+ "#{header_string}\n#{canonicalized_headers(headers)}#{canonicalized_resource(uri, query)}"
105
+ end
106
+
107
+ def canonicalized_headers(headers)
108
+ headers.keys.select { |key| key.to_s.start_with? 'x-acs-' }
109
+ .sort.map { |key| "#{key}:#{headers[key].strip}\n" }.join
110
+ end
111
+
112
+ def canonicalized_resource(uri, query_hash = {})
113
+ query_string = query_hash.map { |key, value| "#{key}=#{value}" }.join('&')
114
+ query_string.empty? ? uri : "#{uri}?#{query_string}"
115
+ end
116
+
117
+ def authorization(string_to_sign)
118
+ "acs #{self.access_key_id}:#{signature(string_to_sign)}"
119
+ end
120
+
121
+ def signature(string_to_sign)
122
+ Base64.encode64(OpenSSL::HMAC.digest('sha1', self.access_key_secret, string_to_sign)).strip
123
+ end
124
+
125
+ def validate(config)
126
+ raise ArgumentError, 'must pass "config"' unless config
127
+ raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
128
+ unless config[:endpoint].start_with?('http://') || config[:endpoint].start_with?('https://')
129
+ raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
130
+ end
131
+ raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
132
+ raise ArgumentError, 'must pass "config[:access_key_id]"' unless config[:access_key_id]
133
+ raise ArgumentError, 'must pass "config[:access_key_secret]"' unless config[:access_key_secret]
134
+ end
135
+
136
+ class ACSError < StandardError
137
+
138
+ attr_accessor :code
139
+
140
+ def initialize(error)
141
+ self.code = error['Code']
142
+ message = error['Message']
143
+ host_id = error['HostId']
144
+ request_id = error['RequestId']
145
+ super("#{message} host_id: #{host_id}, request_id: #{request_id}")
146
+ end
147
+
148
+ end
149
+
150
+ end
data/lib/rpc_client.rb ADDED
@@ -0,0 +1,126 @@
1
+ require 'set'
2
+ require 'faraday'
3
+ require 'active_support/all'
4
+
5
+ class RPCClient
6
+
7
+ attr_accessor :endpoint, :api_version, :access_key_id, :access_key_secret, :security_token, :codes, :opts, :verbose
8
+
9
+ def initialize(config, verbose = false)
10
+
11
+ validate config
12
+
13
+ self.endpoint = config[:endpoint]
14
+ self.api_version = config[:api_version]
15
+ self.access_key_id = config[:access_key_id]
16
+ self.access_key_secret = config[:access_key_secret]
17
+ self.security_token = config[:security_token]
18
+ self.opts = config[:opts] || {}
19
+ self.verbose = verbose.instance_of?(TrueClass) && verbose
20
+ self.codes = Set.new [200, '200', 'OK', 'Success']
21
+ self.codes.merge config[:codes] if config[:codes]
22
+ end
23
+
24
+ def request(action:, params: {}, opts: {})
25
+ opts = self.opts.merge(opts)
26
+ action = action.upcase_first if opts[:format_action]
27
+ params = format_params(params) unless opts[:format_params]
28
+ defaults = default_params
29
+ params = { Action: action }.merge(defaults).merge(params)
30
+ method = (opts[:method] || 'GET').upcase
31
+ normalized = normalize(params)
32
+ canonicalized = canonicalize(normalized)
33
+ string_to_sign = "#{method}&#{encode('/')}&#{encode(canonicalized)}"
34
+ key = self.access_key_secret + '&'
35
+ signature = Base64.encode64(OpenSSL::HMAC.digest('sha1', key, string_to_sign)).strip
36
+ normalized.push(['Signature', encode(signature)])
37
+
38
+ uri = opts[:method] == 'POST' ? '/' : "/?#{canonicalize(normalized)}"
39
+ response = connection.send(method.downcase, uri) do |request|
40
+ if opts[:method] == 'POST'
41
+ request.headers['Content-Type'] = 'application/x-www-form-urlencoded'
42
+ request.body = canonicalize(normalized)
43
+ end
44
+ end
45
+ response_body = JSON.parse(response.body)
46
+ if response_body['Code'] && !self.codes.include?(response_body['Code'])
47
+ raise StandardError, "#{response_body['Message']}, URL: #{uri}"
48
+ end
49
+ response
50
+ end
51
+
52
+ private
53
+
54
+ def connection(adapter = Faraday.default_adapter)
55
+ Faraday.new(:url => self.endpoint) { |faraday| faraday.adapter adapter }
56
+ end
57
+
58
+ def default_params
59
+ default_params = {
60
+ :Format => 'JSON',
61
+ :SignatureMethod => 'HMAC-SHA1',
62
+ :SignatureNonce => SecureRandom.hex(16),
63
+ :SignatureVersion => '1.0',
64
+ :Timestamp => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
65
+ :AccessKeyId => self.access_key_id,
66
+ :Version => self.api_version,
67
+ }
68
+ default_params.merge!(SecurityToken: self.security_token) if self.security_token
69
+ default_params
70
+ end
71
+
72
+ def encode(string)
73
+ CGI.escape string
74
+ end
75
+
76
+ def format_params(param_hash)
77
+ param_hash.keys.each { |key| param_hash[key.to_s.upcase_first.to_sym] = param_hash.delete key }
78
+ param_hash
79
+ end
80
+
81
+ def replace_repeat_list(target, key, repeat)
82
+ repeat.each_with_index do |item, index|
83
+ if item && item.instance_of?(Hash)
84
+ item.each_key { |k| target["#{key}.#{index.next}.#{k}"] = item[k] }
85
+ else
86
+ target["#{key}.#{index.next}"] = item
87
+ end
88
+ end
89
+ target
90
+ end
91
+
92
+ def flat_params(params)
93
+ target = {}
94
+ params.each do |key, value|
95
+ if value.instance_of?(Array)
96
+ replace_repeat_list(target, key, value)
97
+ else
98
+ target[key] = value
99
+ end
100
+ end
101
+ target
102
+ end
103
+
104
+ def normalize(params)
105
+ flat_params(params)
106
+ .stringify_keys
107
+ .sort
108
+ .to_h
109
+ .map { |key, value| [encode(key), encode(value)] }
110
+ end
111
+
112
+ def canonicalize(normalized)
113
+ normalized.map { |element| "#{element.first}=#{element.last}" }.join('&')
114
+ end
115
+
116
+ def validate(config)
117
+ raise ArgumentError, 'must pass "config"' unless config
118
+ raise ArgumentError, 'must pass "config[:endpoint]"' unless config[:endpoint]
119
+ unless config[:endpoint].start_with?('http://') || config[:endpoint].start_with?('https://')
120
+ raise ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.'
121
+ end
122
+ raise ArgumentError, 'must pass "config[:api_version]"' unless config[:api_version]
123
+ raise ArgumentError, 'must pass "config[:access_key_id]"' unless config[:access_key_id]
124
+ raise ArgumentError, 'must pass "config[:access_key_secret]"' unless config[:access_key_secret]
125
+ end
126
+ end
@@ -0,0 +1,34 @@
1
+ require 'rspec'
2
+ require 'roa_client'
3
+ require 'webmock/rspec'
4
+
5
+ describe 'roa request' do
6
+
7
+ WebMock.disable_net_connect!(allow: %r{ros.aliyuncs.com/regions})
8
+
9
+ let(:roa_client) do
10
+ ROAClient.new(
11
+ endpoint: 'http://ros.aliyuncs.com',
12
+ api_version: '2015-09-01',
13
+ access_key_id: ENV['ACCESS_KEY_ID'],
14
+ access_key_secret: ENV['ACCESS_KEY_SECRET'],
15
+ )
16
+ end
17
+
18
+ it 'request' do
19
+ response = roa_client.request(method: 'GET', uri: '/regions', options: { timeout: 15000 })
20
+ expect(JSON.parse(response.body).keys.include?('Regions')).to be true
21
+ end
22
+
23
+ it 'get should ok' do
24
+ response = roa_client.request(method: 'GET', uri: '/regions', options: { timeout: 10000 })
25
+ expect(JSON.parse(response.body).keys.include?('Regions')).to be true
26
+ end
27
+
28
+ it 'get raw body should ok' do
29
+ options = { raw_body: true, timeout: 10000 }
30
+ response = roa_client.request(method: 'GET', uri: '/regions', options: options)
31
+ expect(response.body.instance_of? String).to be true
32
+ end
33
+
34
+ end
@@ -0,0 +1,285 @@
1
+ require 'rspec'
2
+ require 'roa_client'
3
+ require 'webmock/rspec'
4
+
5
+ describe 'roa core' do
6
+
7
+ it 'should pass into "config"' do
8
+ expect {
9
+ ROAClient.new(nil)
10
+ }.to raise_error(ArgumentError, 'must pass "config"')
11
+ end
12
+
13
+ it 'should pass into "config[:endpoint]"' do
14
+ expect {
15
+ ROAClient.new({})
16
+ }.to raise_error(ArgumentError, 'must pass "config[:endpoint]"')
17
+ end
18
+
19
+ it 'should pass into valid "config[:endpoint]"' do
20
+ expect {
21
+ ROAClient.new(endpoint: 'ecs.aliyuncs.com/')
22
+ }.to raise_error(ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.')
23
+ end
24
+
25
+ it 'should pass into "config[:api_version]"' do
26
+ expect {
27
+ ROAClient.new(endpoint: 'http://ecs.aliyuncs.com/')
28
+ }.to raise_error(ArgumentError, 'must pass "config[:api_version]"')
29
+ end
30
+
31
+ it 'should pass into "config[:access_key_id]"' do
32
+ expect {
33
+ ROAClient.new(endpoint: 'http://ecs.aliyuncs.com/', api_version: '1.0')
34
+ }.to raise_error(ArgumentError, 'must pass "config[:access_key_id]"')
35
+ end
36
+
37
+ it 'should pass into "config[:access_key_secret]"' do
38
+ expect {
39
+ ROAClient.new(endpoint: 'http://ecs.aliyuncs.com/', api_version: '1.0', access_key_id: 'access_key_id')
40
+ }.to raise_error(ArgumentError, 'must pass "config[:access_key_secret]"')
41
+ end
42
+
43
+ it 'should ok with http protocol' do
44
+ roa_client = ROAClient.new(
45
+ endpoint: 'http://ecs.aliyuncs.com/',
46
+ api_version: '1.0',
47
+ access_key_id: 'access_key_id',
48
+ access_key_secret: 'access_key_secret'
49
+ )
50
+ expect(roa_client.endpoint).to eq('http://ecs.aliyuncs.com/')
51
+ end
52
+
53
+ it 'should ok with https protocol' do
54
+ roa_client = ROAClient.new(
55
+ endpoint: 'https://ecs.aliyuncs.com/',
56
+ api_version: '1.0',
57
+ access_key_id: 'access_key_id',
58
+ access_key_secret: 'access_key_secret'
59
+ )
60
+ expect(roa_client.endpoint).to eq('https://ecs.aliyuncs.com/')
61
+ end
62
+
63
+ let(:roa_client) do
64
+ ROAClient.new(
65
+ endpoint: 'https://ecs.aliyuncs.com/',
66
+ api_version: '1.0',
67
+ access_key_id: 'access_key_id',
68
+ access_key_secret: 'access_key_secret'
69
+ )
70
+ end
71
+
72
+ let(:default_header_keys) do
73
+ %w(accept date host
74
+ x-acs-signature-nonce
75
+ x-acs-signature-method
76
+ x-acs-signature-version
77
+ x-acs-version
78
+ x-sdk-client).map(&:to_sym)
79
+ end
80
+
81
+ it 'default headers should ok' do
82
+ expect(roa_client.default_headers.keys).to match_array(default_header_keys)
83
+ expect(roa_client.default_headers[:accept]).to eq('application/json')
84
+ expect(roa_client.default_headers[:date]).to match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/)
85
+ expect(roa_client.default_headers[:host]).to eq('ecs.aliyuncs.com')
86
+ end
87
+
88
+ it 'default_headers should ok with security_token' do
89
+
90
+ roa_client = ROAClient.new(
91
+ endpoint: 'https://ecs.aliyuncs.com/',
92
+ api_version: '1.0',
93
+ access_key_id: 'access_key_id',
94
+ access_key_secret: 'access_key_secret',
95
+ security_token: 'security_token'
96
+ )
97
+
98
+ default_header_keys = %w(
99
+ accept
100
+ date
101
+ host
102
+ x-acs-signature-nonce
103
+ x-acs-signature-method
104
+ x-acs-signature-version
105
+ x-acs-version
106
+ x-sdk-client
107
+ x-acs-accesskey-id
108
+ x-acs-security-token
109
+ ).map(&:to_sym)
110
+
111
+ expect(roa_client.default_headers[:accept]).to eq('application/json')
112
+ expect(roa_client.default_headers.keys).to match_array(default_header_keys)
113
+ expect(roa_client.default_headers[:date]).to match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/)
114
+ expect(roa_client.default_headers[:accept]).to eq('application/json')
115
+ end
116
+
117
+ it 'request with raw body should ok' do
118
+ stub_request(:get, "https://ecs.aliyuncs.com/").to_return(body: 'raw body')
119
+ response = roa_client.request(method: 'GET', uri: '/', options: { raw_body: true })
120
+ expect(response.body).to eq('raw body')
121
+ end
122
+
123
+ it 'get request with raw body should ok' do
124
+ stub_request(:get, "https://ecs.aliyuncs.com/").to_return(body: 'raw body')
125
+ response = roa_client.get(uri: '/', options: { raw_body: true })
126
+ expect(response.body).to eq('raw body')
127
+ end
128
+
129
+ describe 'request with json response should ok' do
130
+ it 'json response should ok' do
131
+ stub_request(:get, "https://ecs.aliyuncs.com/")
132
+ .to_return(status: 200, headers: { 'Content-Type': 'application/json' }, body: { ok: true }.to_json)
133
+ response = roa_client.get(uri: '/')
134
+ expect(response.body).to eq({ ok: true }.to_json)
135
+ end
136
+ end
137
+
138
+ describe 'request(204) with json response should ok' do
139
+ it 'json response should ok' do
140
+ stub_request(:get, "https://ecs.aliyuncs.com/")
141
+ .to_return(status: 204, headers: { 'Content-Type': 'application/json' }, body: '')
142
+ response = roa_client.get(uri: '/')
143
+ expect(response.body).to eq('')
144
+ end
145
+ end
146
+
147
+ describe 'request(400) with json response should ok' do
148
+ let(:status) { 400 }
149
+ let(:headers) { { 'Content-Type': 'application/json' } }
150
+ let(:body) { { 'Message': 'error message', 'RequestId': 'requestid', 'Code': 'errorcode' }.to_json }
151
+ it 'json response should ok' do
152
+ stub_request(:get, "https://ecs.aliyuncs.com/")
153
+ .to_return(status: status, headers: headers, body: body)
154
+ expect {
155
+ roa_client.get(uri: '/')
156
+ }.to raise_error(StandardError, 'code: 400, error message requestid: requestid')
157
+ end
158
+ end
159
+
160
+ describe 'request with xml response should ok' do
161
+ let(:status) { 200 }
162
+ let(:headers) { { 'Content-Type': 'text/xml' } }
163
+ let(:body) do
164
+ <<-XML
165
+ <note>
166
+ <to>George</to>
167
+ <from>John</from>
168
+ <heading>Reminder</heading>
169
+ <body>Don't forget the meeting!</body>
170
+ </note>
171
+ XML
172
+ end
173
+ it 'xml response should ok' do
174
+ stub_request(:get, "https://ecs.aliyuncs.com/")
175
+ .to_return(status: status, headers: headers, body: body)
176
+ response = roa_client.get(uri: '/')
177
+ expect(response.body).to eq(body)
178
+ end
179
+ end
180
+
181
+ describe 'request(400) with xml response should ok' do
182
+ let(:status) { 400 }
183
+ let(:headers) { { 'Content-Type': 'text/xml' } }
184
+ let(:body) do
185
+ <<-XML
186
+ <Error>
187
+ <Message>error message</Message>
188
+ <RequestId>requestid</RequestId>
189
+ <HostId>hostid</HostId>
190
+ <Code>errorcode</Code>
191
+ </Error>
192
+ XML
193
+ end
194
+ it 'xml response should ok' do
195
+ stub_request(:get, "https://ecs.aliyuncs.com/")
196
+ .to_return(status: status, headers: headers, body: body)
197
+ expect {
198
+ roa_client.get(uri: '/')
199
+ }.to raise_error(ROAClient::ACSError, 'error message host_id: hostid, request_id: requestid')
200
+ end
201
+ end
202
+
203
+ describe 'request(200) with plain response should ok' do
204
+ it 'plain response should ok' do
205
+ stub_request(:get, "https://ecs.aliyuncs.com/").to_return(status: 200, body: 'plain text')
206
+ response = roa_client.get(uri: '/')
207
+ expect(response.body).to eq('plain text')
208
+ end
209
+ end
210
+
211
+ describe 'post should ok' do
212
+ it 'should ok' do
213
+ stub_request(:get, "https://ecs.aliyuncs.com/")
214
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
215
+ response = roa_client.post(uri: '/', body: 'text')
216
+ expect(response.body).to eq('{"ok":true}')
217
+ end
218
+ it 'should ok with query' do
219
+ stub_request(:get, "https://ecs.aliyuncs.com/?k=v")
220
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
221
+ response = roa_client.post(uri: '/', params: { k: 'v' }, body: 'text')
222
+ expect(response.body).to eq('{"ok":true}')
223
+ end
224
+ end
225
+
226
+ describe 'put should ok' do
227
+ it 'should ok' do
228
+ stub_request(:get, "https://ecs.aliyuncs.com/")
229
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
230
+ response = roa_client.put(uri: '/', body: 'text')
231
+ expect(response.body).to eq('{"ok":true}')
232
+ end
233
+ end
234
+
235
+ describe 'delete should ok' do
236
+ it 'should ok' do
237
+ stub_request(:get, "https://ecs.aliyuncs.com/")
238
+ .to_return(status: 200, headers: { 'content-type': 'application/json' }, body: { ok: true }.to_json)
239
+ response = roa_client.delete(uri: '/')
240
+ expect(response.body).to eq('{"ok":true}')
241
+ end
242
+ end
243
+
244
+ it 'signature should ok' do
245
+ expect(roa_client.send(:signature, '123456')).to eq('BeAYlq/e5iWAoTNmzf8jbcBxdq0=')
246
+ end
247
+
248
+ it 'authorization should ok' do
249
+ expect(roa_client.send(:authorization, '123456')).to eq('acs access_key_id:BeAYlq/e5iWAoTNmzf8jbcBxdq0=')
250
+ end
251
+
252
+ it 'canonicalized_headers should ok' do
253
+ expect(roa_client.send(:canonicalized_headers, {})).to be_empty
254
+ expect(roa_client.send(:canonicalized_headers, { key: 'value' })).to be_empty
255
+ expect(roa_client.send(:canonicalized_headers, { 'x-acs-key': 'value' })).to eq("x-acs-key:value\n")
256
+ end
257
+
258
+ it 'canonicalized_resource should ok' do
259
+ expect(roa_client.send(:canonicalized_resource, '/')).to eq('/')
260
+ expect(roa_client.send(:canonicalized_resource, '/', { key: 'value' })).to eq('/?key=value')
261
+ expect(roa_client.send(:canonicalized_resource, '/', { key: 'value', 'key1': 'value2' })).to eq('/?key=value&key1=value2')
262
+ end
263
+
264
+ it 'string_to_sign should ok ' do
265
+ expect(
266
+ roa_client.send(:string_to_sign, 'GET', '/', { accept: 'application/json' })
267
+ ).to eq("GET\napplication/json\n\n\n\n/")
268
+ end
269
+
270
+ describe 'ROAClient::ACSError class' do
271
+ it 'ACSError should ok' do
272
+ expect {
273
+ error_info = {
274
+ Message: 'error message',
275
+ Code: 'errorcode',
276
+ HostId: 'hostid',
277
+ RequestId: 'requestid',
278
+ }
279
+ raise ROAClient::ACSError, error_info.stringify_keys!
280
+ }.to raise_error(ROAClient::ACSError, 'error message host_id: hostid, request_id: requestid')
281
+ end
282
+ end
283
+
284
+
285
+ end
@@ -0,0 +1,35 @@
1
+ require 'rspec'
2
+ require 'rpc_client'
3
+ require 'webmock/rspec'
4
+
5
+ describe 'rpc request' do
6
+
7
+ WebMock.disable_net_connect!(allow: %r{ecs.aliyuncs.com})
8
+
9
+ let(:rpc_client) do
10
+ RPCClient.new(
11
+ endpoint: 'https://ecs.aliyuncs.com',
12
+ api_version: '2014-05-26',
13
+ access_key_id: ENV['ACCESS_KEY_ID'],
14
+ access_key_secret: ENV['ACCESS_KEY_SECRET'],
15
+ )
16
+ end
17
+
18
+ it 'should ok' do
19
+ params = { key: (1..11).to_a.map(&:to_s) }
20
+ request_option = { method: 'POST', timeout: 15000 }
21
+ response = rpc_client.request(action: 'DescribeRegions', params: params, opts: request_option)
22
+ response_body = JSON.parse(response.body)
23
+ expect(response_body.keys.include?('Regions')).to be true
24
+ expect(response_body.keys.include?('RequestId')).to be true
25
+ end
26
+
27
+ it 'should ok with repeat list less 10 item' do
28
+ params = { key: (1..9).to_a.map(&:to_s) }
29
+ request_option = { method: 'POST', timeout: 15000 }
30
+ response = rpc_client.request(action: 'DescribeRegions', params: params, opts: request_option)
31
+ response_body = JSON.parse(response.body)
32
+ expect(response_body.keys.include?('Regions')).to be true
33
+ expect(response_body.keys.include?('RequestId')).to be true
34
+ end
35
+ end
@@ -0,0 +1,234 @@
1
+ require 'rspec'
2
+ require 'rpc_client'
3
+ require 'webmock/rspec'
4
+
5
+ describe 'rpc core' do
6
+
7
+ describe 'RPCClient' do
8
+ it 'should pass into "config"' do
9
+ expect {
10
+ RPCClient.new(nil)
11
+ }.to raise_error(ArgumentError, 'must pass "config"')
12
+ end
13
+
14
+ it 'should pass into "config[:endpoint]"' do
15
+ expect {
16
+ RPCClient.new({})
17
+ }.to raise_error(ArgumentError, 'must pass "config[:endpoint]"')
18
+ end
19
+
20
+ it 'should pass into valid "config[:endpoint]"' do
21
+ expect {
22
+ RPCClient.new(endpoint: 'ecs.aliyuncs.com/')
23
+ }.to raise_error(ArgumentError, '"config.endpoint" must starts with \'https://\' or \'http://\'.')
24
+ end
25
+
26
+ it 'should pass into "config[:api_version]"' do
27
+ expect {
28
+ RPCClient.new(endpoint: 'http://ecs.aliyuncs.com/')
29
+ }.to raise_error(ArgumentError, 'must pass "config[:api_version]"')
30
+ end
31
+
32
+ it 'should pass into "config[:access_key_id]"' do
33
+ expect {
34
+ RPCClient.new(endpoint: 'http://ecs.aliyuncs.com/', api_version: '1.0')
35
+ }.to raise_error(ArgumentError, 'must pass "config[:access_key_id]"')
36
+ end
37
+
38
+ it 'should pass into "config[:access_key_secret]"' do
39
+ expect {
40
+ RPCClient.new(endpoint: 'http://ecs.aliyuncs.com/', api_version: '1.0', access_key_id: 'access_key_id')
41
+ }.to raise_error(ArgumentError, 'must pass "config[:access_key_secret]"')
42
+ end
43
+
44
+ it 'should ok with http protocol' do
45
+ rpc_client = RPCClient.new(
46
+ endpoint: 'http://ecs.aliyuncs.com/',
47
+ api_version: '1.0',
48
+ access_key_id: 'access_key_id',
49
+ access_key_secret: 'access_key_secret'
50
+ )
51
+ expect(rpc_client.endpoint).to eq('http://ecs.aliyuncs.com/')
52
+ end
53
+
54
+ it 'should ok with https protocol' do
55
+ rpc_client = RPCClient.new(
56
+ endpoint: 'https://ecs.aliyuncs.com/',
57
+ api_version: '1.0',
58
+ access_key_id: 'access_key_id',
59
+ access_key_secret: 'access_key_secret'
60
+ )
61
+ expect(rpc_client.endpoint).to eq('https://ecs.aliyuncs.com/')
62
+ end
63
+
64
+ it 'should ok with codes' do
65
+ rpc_client = RPCClient.new(
66
+ endpoint: 'https://ecs.aliyuncs.com/',
67
+ api_version: '1.0',
68
+ access_key_id: 'access_key_id',
69
+ access_key_secret: 'access_key_secret',
70
+ codes: ['True']
71
+ )
72
+ expect(rpc_client.codes.include? 'True').to be true
73
+ end
74
+ end
75
+
76
+ describe 'default params' do
77
+
78
+ it 'should ok' do
79
+ rpc_client = RPCClient.new(
80
+ endpoint: 'https://ecs.aliyuncs.com/',
81
+ api_version: '1.0',
82
+ access_key_id: 'access_key_id',
83
+ access_key_secret: 'access_key_secret',
84
+ )
85
+ default_params_keys = %w(Format SignatureMethod SignatureNonce SignatureVersion Timestamp AccessKeyId Version)
86
+ expect(rpc_client.send(:default_params).stringify_keys.keys).to match_array default_params_keys
87
+ end
88
+ end
89
+
90
+ it 'should ok with securityToken' do
91
+ rpc_client = RPCClient.new(
92
+ endpoint: 'https://ecs.aliyuncs.com/',
93
+ api_version: '1.0',
94
+ access_key_id: 'access_key_id',
95
+ access_key_secret: 'access_key_secret',
96
+ security_token: 'security_token'
97
+ )
98
+ default_params_keys = %w(Format SignatureMethod SignatureNonce SignatureVersion Timestamp AccessKeyId Version SecurityToken)
99
+ expect(rpc_client.send(:default_params).stringify_keys.keys).to match_array default_params_keys
100
+ end
101
+
102
+ describe 'request' do
103
+ it 'get with raw body should ok' do
104
+ rpc_client = RPCClient.new(
105
+ endpoint: 'https://ecs.aliyuncs.com/',
106
+ api_version: '1.0',
107
+ access_key_id: 'access_key_id',
108
+ access_key_secret: 'access_key_secret',
109
+ )
110
+ stub_request(:get, /https:\/\/ecs.aliyuncs.com/).to_return(status: 200, body: {}.to_json)
111
+ expect(rpc_client.request(action: 'action').body).to eq({}.to_json)
112
+ end
113
+ end
114
+
115
+ describe 'request with post' do
116
+
117
+ let(:rpc_client) do
118
+ RPCClient.new(
119
+ endpoint: 'https://ecs.aliyuncs.com/',
120
+ api_version: '1.0',
121
+ access_key_id: 'access_key_id',
122
+ access_key_secret: 'access_key_secret',
123
+ security_token: 'security_token'
124
+ )
125
+ end
126
+
127
+ it 'should ok' do
128
+ stub_request(:get, /https:\/\/ecs.aliyuncs.com/).to_return(status: 200, body: {}.to_json)
129
+ expect(rpc_client.request(action: 'action').body).to eq({}.to_json)
130
+ end
131
+
132
+ it 'should ok with format_action' do
133
+ stub_request(:get, /https:\/\/ecs.aliyuncs.com/).to_return(status: 200, body: {}.to_json)
134
+ response = rpc_client.request(action: 'action', opts: { format_action: false })
135
+ expect(response.body).to eq({}.to_json)
136
+ end
137
+
138
+ it 'should ok with format_params' do
139
+ stub_request(:get, /https:\/\/ecs.aliyuncs.com/).to_return(status: 200, body: {}.to_json)
140
+ response = rpc_client.request(action: 'action', opts: { format_params: false })
141
+ expect(response.body).to eq({}.to_json)
142
+ end
143
+
144
+ it 'get with raw body should ok' do
145
+ stub_request(:post, "https://ecs.aliyuncs.com").to_return(status: 200, body: {}.to_json)
146
+ response = rpc_client.request(action: 'action', opts: { method: 'POST' })
147
+ expect(response.body).to eq({}.to_json)
148
+ end
149
+
150
+ it 'get with verbose should ok' do
151
+ stub_request(:get, /https:\/\/ecs.aliyuncs.com/).to_return(status: 200, body: {}.to_json)
152
+ response = rpc_client.request(action: 'action')
153
+ expect(response.status).to eq 200
154
+ expect(response.success?).to be true
155
+ expect(response.body).to eq({}.to_json)
156
+ end
157
+ end
158
+
159
+ describe 'request with error' do
160
+
161
+ let(:rpc_client) do
162
+ RPCClient.new(
163
+ endpoint: 'https://ecs.aliyuncs.com/',
164
+ api_version: '1.0',
165
+ access_key_id: 'access_key_id',
166
+ access_key_secret: 'access_key_secret',
167
+ )
168
+ end
169
+
170
+ it 'request with 400 should ok' do
171
+ mock_response = { Code: 400, Message: 'error message' }.to_json
172
+ stub_request(:get, /https:\/\/ecs.aliyuncs.com/).to_return(status: 400, body: mock_response)
173
+ expect {
174
+ rpc_client.request(action: 'action')
175
+ }.to raise_error(StandardError, /error message, URL:/)
176
+ end
177
+ end
178
+
179
+ describe 'RPC private methods' do
180
+
181
+ let(:rpc_client) do
182
+ RPCClient.new(
183
+ endpoint: 'https://ecs.aliyuncs.com/',
184
+ api_version: '1.0',
185
+ access_key_id: 'access_key_id',
186
+ access_key_secret: 'access_key_secret',
187
+ security_token: 'security_token'
188
+ )
189
+ end
190
+
191
+ it 'formatParams should ok' do
192
+ expect(rpc_client.send(:format_params, { foo: 1, bar: 2 })).to eq(Foo: 1, Bar: 2)
193
+ end
194
+
195
+ it 'encode should ok' do
196
+ expect(rpc_client.send(:encode, 'str')).to eq 'str'
197
+ expect(rpc_client.send(:encode, 'str\'str')).to eq 'str%27str'
198
+ expect(rpc_client.send(:encode, 'str(str')).to eq 'str%28str'
199
+ expect(rpc_client.send(:encode, 'str)str')).to eq 'str%29str'
200
+ expect(rpc_client.send(:encode, 'str*str')).to eq 'str%2Astr'
201
+ end
202
+
203
+ it 'replace_repeat_list should ok' do
204
+ expect(rpc_client.send(:replace_repeat_list, {}, 'key', [])).to eq({})
205
+ expect(rpc_client.send(:replace_repeat_list, {}, 'key', ['value'])).to eq({ 'key.1' => 'value' })
206
+ expect(rpc_client.send(:replace_repeat_list, {}, 'key', [{ :Domain => '1.com' }])).to eq({ 'key.1.Domain' => '1.com' })
207
+ end
208
+
209
+ it 'flat_params should ok' do
210
+ expect(rpc_client.send(:flat_params, {})).to eq({})
211
+ expect(rpc_client.send(:flat_params, { key: ['value'] })).to eq({ 'key.1' => 'value' })
212
+ expect(rpc_client.send(:flat_params, { key: 'value' })).to eq({ key: 'value' })
213
+ expect(rpc_client.send(:flat_params, { key: [{ Domain: '1.com' }] })).to eq({ 'key.1.Domain' => '1.com' })
214
+ end
215
+
216
+ it 'normalize should ok' do
217
+ expect(rpc_client.send(:normalize, {})).to be_empty
218
+ expect(rpc_client.send(:normalize, { key: ['value'] })).to match_array [%w(key.1 value)]
219
+ expect(rpc_client.send(:normalize, { key: 'value' })).to match_array [%w(key value)]
220
+ expect(rpc_client.send(:normalize, { key: [{ Domain: '1.com' }] })).to match_array [%w(key.1.Domain 1.com)]
221
+ expect(rpc_client.send(:normalize, { a: 'value', c: 'value', b: 'value' }))
222
+ .to match_array [%w(a value), %w(b value), %w(c value)]
223
+ end
224
+
225
+ it 'canonicalize should ok' do
226
+ expect(rpc_client.send(:canonicalize, [])).to be_empty
227
+ expect(rpc_client.send(:canonicalize, { foo: 1 })).to eq 'foo=1'
228
+ expect(rpc_client.send(:canonicalize, [['foo', 1]])).to eq 'foo=1'
229
+ expect(rpc_client.send(:canonicalize, { foo: 1, bar: 2 })).to eq 'foo=1&bar=2'
230
+ expect(rpc_client.send(:canonicalize, [['foo', 1], ['bar', 2]])).to eq 'foo=1&bar=2'
231
+ end
232
+ end
233
+
234
+ end
@@ -0,0 +1,110 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter "/spec/"
4
+ end
5
+
6
+ if ENV['CI'] == 'true'
7
+ require 'codecov'
8
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
9
+ end
10
+
11
+ # This file was generated by the `rspec --init` command. Conventionally, all
12
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
13
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
14
+ # this file to always be loaded, without a need to explicitly require it in any
15
+ # files.
16
+ #
17
+ # Given that it is always loaded, you are encouraged to keep this file as
18
+ # light-weight as possible. Requiring heavyweight dependencies from this file
19
+ # will add to the boot time of your test suite on EVERY test run, even for an
20
+ # individual file that may not need all of that loaded. Instead, consider making
21
+ # a separate helper file that requires the additional dependencies and performs
22
+ # the additional setup, and require it from the spec files that actually need
23
+ # it.
24
+ #
25
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
26
+ RSpec.configure do |config|
27
+ # rspec-expectations config goes here. You can use an alternate
28
+ # assertion/expectation library such as wrong or the stdlib/minitest
29
+ # assertions if you prefer.
30
+ config.expect_with :rspec do |expectations|
31
+ # This option will default to `true` in RSpec 4. It makes the `description`
32
+ # and `failure_message` of custom matchers include text for helper methods
33
+ # defined using `chain`, e.g.:
34
+ # be_bigger_than(2).and_smaller_than(4).description
35
+ # # => "be bigger than 2 and smaller than 4"
36
+ # ...rather than:
37
+ # # => "be bigger than 2"
38
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
39
+ end
40
+
41
+ # rspec-mocks config goes here. You can use an alternate test double
42
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
43
+ config.mock_with :rspec do |mocks|
44
+ # Prevents you from mocking or stubbing a method that does not exist on
45
+ # a real object. This is generally recommended, and will default to
46
+ # `true` in RSpec 4.
47
+ mocks.verify_partial_doubles = true
48
+ end
49
+
50
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
51
+ # have no way to turn it off -- the option exists only for backwards
52
+ # compatibility in RSpec 3). It causes shared context metadata to be
53
+ # inherited by the metadata hash of host groups and examples, rather than
54
+ # triggering implicit auto-inclusion in groups with matching metadata.
55
+ config.shared_context_metadata_behavior = :apply_to_host_groups
56
+
57
+ # The settings below are suggested to provide a good initial experience
58
+ # with RSpec, but feel free to customize to your heart's content.
59
+ =begin
60
+ # This allows you to limit a spec run to individual examples or groups
61
+ # you care about by tagging them with `:focus` metadata. When nothing
62
+ # is tagged with `:focus`, all examples get run. RSpec also provides
63
+ # aliases for `it`, `describe`, and `context` that include `:focus`
64
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
65
+ config.filter_run_when_matching :focus
66
+
67
+ # Allows RSpec to persist some state between runs in order to support
68
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
69
+ # you configure your source control system to ignore this file.
70
+ config.example_status_persistence_file_path = "spec/examples.txt"
71
+
72
+ # Limits the available syntax to the non-monkey patched syntax that is
73
+ # recommended. For more details, see:
74
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
75
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
76
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
77
+ config.disable_monkey_patching!
78
+
79
+ # This setting enables warnings. It's recommended, but in some cases may
80
+ # be too noisy due to issues in dependencies.
81
+ config.warnings = true
82
+
83
+ # Many RSpec users commonly either run the entire suite or an individual
84
+ # file, and it's useful to allow more verbose output when running an
85
+ # individual spec file.
86
+ if config.files_to_run.one?
87
+ # Use the documentation formatter for detailed output,
88
+ # unless a formatter has already been configured
89
+ # (e.g. via a command-line flag).
90
+ config.default_formatter = "doc"
91
+ end
92
+
93
+ # Print the 10 slowest examples and example groups at the
94
+ # end of the spec run, to help surface which specs are running
95
+ # particularly slow.
96
+ config.profile_examples = 10
97
+
98
+ # Run specs in random order to surface order dependencies. If you find an
99
+ # order dependency and want to debug it, you can fix the order by providing
100
+ # the seed, which is printed after each run.
101
+ # --seed 1234
102
+ config.order = :random
103
+
104
+ # Seed global randomization in this process using the `--seed` CLI option.
105
+ # Setting this allows you to use `--seed` to deterministically reproduce
106
+ # test failures related to randomization by passing the same `--seed` value
107
+ # as the one that triggered the failure.
108
+ Kernel.srand config.seed
109
+ =end
110
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aliyunsdkcore
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alibaba Cloud SDK
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: simplecov
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.10
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.10
55
+ description: Alibaba Cloud Ruby Core SDK
56
+ email:
57
+ - sdk-team@alibabacloud.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - README.md
63
+ - lib/roa_client.rb
64
+ - lib/rpc_client.rb
65
+ - spec/roa_cilent_integration_spec.rb
66
+ - spec/roa_client_spec.rb
67
+ - spec/rpc_cilent_integration_spec.rb
68
+ - spec/rpc_client_spec.rb
69
+ - spec/spec_helper.rb
70
+ homepage: http://www.alibabacloud.com/
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.0.2
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Alibaba Cloud Ruby Core SDK
93
+ test_files:
94
+ - spec/roa_cilent_integration_spec.rb
95
+ - spec/roa_client_spec.rb
96
+ - spec/rpc_cilent_integration_spec.rb
97
+ - spec/rpc_client_spec.rb
98
+ - spec/spec_helper.rb