4me-sdk 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/4me-sdk.gemspec +40 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +74 -0
- data/LICENSE +21 -0
- data/README.md +324 -0
- data/lib/sdk4me.rb +37 -0
- data/lib/sdk4me/ca-bundle.crt +3910 -0
- data/lib/sdk4me/client.rb +342 -0
- data/lib/sdk4me/client/attachments.rb +109 -0
- data/lib/sdk4me/client/multipart.rb +75 -0
- data/lib/sdk4me/client/response.rb +119 -0
- data/lib/sdk4me/client/version.rb +5 -0
- metadata +169 -0
@@ -0,0 +1,342 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'date'
|
5
|
+
require 'time'
|
6
|
+
require 'net/https'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'sdk4me'
|
9
|
+
|
10
|
+
require 'sdk4me/client/version'
|
11
|
+
require 'sdk4me/client/response'
|
12
|
+
require 'sdk4me/client/multipart'
|
13
|
+
require 'sdk4me/client/attachments'
|
14
|
+
|
15
|
+
# cherry-pick some core extensions from active support
|
16
|
+
require 'active_support/core_ext/module/aliasing.rb'
|
17
|
+
require 'active_support/core_ext/object/blank'
|
18
|
+
require 'active_support/core_ext/object/try.rb'
|
19
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
20
|
+
|
21
|
+
module Sdk4me
|
22
|
+
class Client
|
23
|
+
MAX_PAGE_SIZE = 100
|
24
|
+
DEFAULT_HEADER = {'Content-Type' => 'application/json'}
|
25
|
+
|
26
|
+
# Create a new 4me SDK Client
|
27
|
+
#
|
28
|
+
# Shared configuration for all 4me SDK Clients:
|
29
|
+
# Sdk4me.configure do |config|
|
30
|
+
# config.api_token = 'd41f5868feb65fc87fa2311a473a8766ea38bc40'
|
31
|
+
# config.account = 'my-sandbox'
|
32
|
+
# ...
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Override configuration per 4me SDK Client:
|
36
|
+
# sdk4me = Sdk4me::Client.new(account: 'trusted-sandbox')
|
37
|
+
#
|
38
|
+
# All options available:
|
39
|
+
# - logger: The Ruby Logger instance, default: Logger.new(STDOUT)
|
40
|
+
# - host: The 4me API host, default: 'https://api.4me.com'
|
41
|
+
# - api_version: The 4me API version, default: 'v1'
|
42
|
+
# - api_token: *required* The 4me API token
|
43
|
+
# - account: Specify a different (trusted) account to work with
|
44
|
+
# @see http://developer.4me.com/v1/#multiple-accounts
|
45
|
+
# - source: The Source used when creating new records
|
46
|
+
# @see http://developer.4me.com/v1/general/source/
|
47
|
+
#
|
48
|
+
# - max_retry_time: maximum nr of seconds to wait for server to respond (default = 5400 = 1.5 hours)
|
49
|
+
# the sleep time between retries starts at 2 seconds and doubles after each retry
|
50
|
+
# retry times: 2, 6, 18, 54, 162, 486, 1458, 4374, 13122, ... seconds
|
51
|
+
# one retry will always be performed unless you set the value to -1
|
52
|
+
# - read_timeout: HTTP GET read timeout in seconds (default = 25)
|
53
|
+
# - block_at_rate_limit: Set to +true+ to block the request until the rate limit is lifted, default: +false+
|
54
|
+
# @see http://developer.4me.com/v1/#rate-limiting
|
55
|
+
#
|
56
|
+
# - proxy_host: Define in case HTTP traffic needs to go through a proxy
|
57
|
+
# - proxy_port: Port of the proxy, defaults to 8080
|
58
|
+
# - proxy_user: Proxy user
|
59
|
+
# - proxy_password: Proxy password
|
60
|
+
def initialize(options = {})
|
61
|
+
@options = Sdk4me.configuration.current.merge(options)
|
62
|
+
[:host, :api_version, :api_token].each do |required_option|
|
63
|
+
raise ::Sdk4me::Exception.new("Missing required configuration option #{required_option}") if option(required_option).blank?
|
64
|
+
end
|
65
|
+
@ssl, @domain, @port = ssl_domain_port_path(option(:host))
|
66
|
+
@ssl_verify_none = options[:ssl_verify_none]
|
67
|
+
@logger = @options[:logger]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Retrieve an option
|
71
|
+
def option(key)
|
72
|
+
@options[key]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Yield all retrieved resources one-by-one for the given (paged) API query.
|
76
|
+
# Raises an ::Sdk4me::Exception with the response retrieved from 4me is invalid
|
77
|
+
# Returns total nr of resources yielded (for logging)
|
78
|
+
def each(path, params = {}, header = {}, &block)
|
79
|
+
# retrieve the resources using the max page size (least nr of API calls)
|
80
|
+
next_path = expand_path(path, {per_page: MAX_PAGE_SIZE, page: 1}.merge(params))
|
81
|
+
size = 0
|
82
|
+
while next_path
|
83
|
+
# retrieve the records (with retry and optionally wait for rate-limit)
|
84
|
+
response = get(next_path, {}, header)
|
85
|
+
# raise exception in case the response is invalid
|
86
|
+
raise ::Sdk4me::Exception.new(response.message) unless response.valid?
|
87
|
+
# yield the resources
|
88
|
+
response.json.each{ |resource| yield resource }
|
89
|
+
size += response.json.size
|
90
|
+
# go to the next page
|
91
|
+
next_path = response.pagination_relative_link(:next)
|
92
|
+
end
|
93
|
+
size
|
94
|
+
end
|
95
|
+
|
96
|
+
# send HTTPS GET request and return instance of Sdk4me::Response
|
97
|
+
def get(path, params = {}, header = {})
|
98
|
+
_send(Net::HTTP::Get.new(expand_path(path, params), expand_header(header)))
|
99
|
+
end
|
100
|
+
|
101
|
+
# send HTTPS DELETE request and return instance of Sdk4me::Response
|
102
|
+
def delete(path, params = {}, header = {})
|
103
|
+
_send(Net::HTTP::Delete.new(expand_path(path, params), expand_header(header)))
|
104
|
+
end
|
105
|
+
|
106
|
+
# send HTTPS PATCH request and return instance of Sdk4me::Response
|
107
|
+
def put(path, data = {}, header = {})
|
108
|
+
_send(json_request(Net::HTTP::Patch, path, data, header))
|
109
|
+
end
|
110
|
+
alias_method :patch, :put
|
111
|
+
|
112
|
+
# send HTTPS POST request and return instance of Sdk4me::Response
|
113
|
+
def post(path, data = {}, header = {})
|
114
|
+
_send(json_request(Net::HTTP::Post, path, data, header))
|
115
|
+
end
|
116
|
+
|
117
|
+
# upload a CSV file to import
|
118
|
+
# @param csv: The CSV File or the location of the CSV file
|
119
|
+
# @param type: The type, e.g. person, organization, people_contact_details
|
120
|
+
# @raise Sdk4me::UploadFailed in case the file could was not accepted by SDK4ME and +block_until_completed+ is +true+
|
121
|
+
# @raise Sdk4me::Exception in case the import progress could not be monitored
|
122
|
+
def import(csv, type, block_until_completed = false)
|
123
|
+
csv = File.open(csv, 'rb') unless csv.respond_to?(:path) && csv.respond_to?(:read)
|
124
|
+
data, headers = Sdk4me::Multipart::Post.prepare_query(type: type, file: csv)
|
125
|
+
request = Net::HTTP::Post.new(expand_path('/import'), expand_header(headers))
|
126
|
+
request.body = data
|
127
|
+
response = _send(request)
|
128
|
+
@logger.info { "Import file '#{csv.path}' successfully uploaded with token '#{response[:token]}'." } if response.valid?
|
129
|
+
|
130
|
+
if block_until_completed
|
131
|
+
raise ::Sdk4me::UploadFailed.new("Failed to queue #{type} import. #{response.message}") unless response.valid?
|
132
|
+
token = response[:token]
|
133
|
+
while true
|
134
|
+
response = get("/import/#{token}")
|
135
|
+
unless response.valid?
|
136
|
+
sleep(5)
|
137
|
+
response = get("/import/#{token}") # single retry to recover from a network error
|
138
|
+
raise ::Sdk4me::Exception.new("Unable to monitor progress for #{type} import. #{response.message}") unless response.valid?
|
139
|
+
end
|
140
|
+
# wait 30 seconds while the response is OK and import is still busy
|
141
|
+
break unless ['queued', 'processing'].include?(response[:state])
|
142
|
+
@logger.debug { "Import of '#{csv.path}' is #{response[:state]}. Checking again in 30 seconds." }
|
143
|
+
sleep(30)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
response
|
148
|
+
end
|
149
|
+
|
150
|
+
# Export CSV files
|
151
|
+
# @param types: The types to export, e.g. person, organization, people_contact_details
|
152
|
+
# @param from: Retrieve all files since a given data and time
|
153
|
+
# @param block_until_completed: Set to true to monitor the export progress
|
154
|
+
# @raise Sdk4me::Exception in case the export progress could not be monitored
|
155
|
+
def export(types, from = nil, block_until_completed = false)
|
156
|
+
data = {type: [types].flatten.join(',')}
|
157
|
+
data[:from] = from unless from.blank?
|
158
|
+
response = post('/export', data)
|
159
|
+
if response.valid?
|
160
|
+
if response.raw.code.to_s == '204'
|
161
|
+
@logger.info { "No changed records for '#{data[:type]}' since #{data[:from]}." }
|
162
|
+
return response
|
163
|
+
end
|
164
|
+
@logger.info { "Export for '#{data[:type]}' successfully queued with token '#{response[:token]}'." }
|
165
|
+
end
|
166
|
+
|
167
|
+
if block_until_completed
|
168
|
+
raise ::Sdk4me::UploadFailed.new("Failed to queue '#{data[:type]}' export. #{response.message}") unless response.valid?
|
169
|
+
token = response[:token]
|
170
|
+
while true
|
171
|
+
response = get("/export/#{token}")
|
172
|
+
unless response.valid?
|
173
|
+
sleep(5)
|
174
|
+
response = get("/export/#{token}") # single retry to recover from a network error
|
175
|
+
raise ::Sdk4me::Exception.new("Unable to monitor progress for '#{data[:type]}' export. #{response.message}") unless response.valid?
|
176
|
+
end
|
177
|
+
# wait 30 seconds while the response is OK and export is still busy
|
178
|
+
break unless ['queued', 'processing'].include?(response[:state])
|
179
|
+
@logger.debug { "Export of '#{data[:type]}' is #{response[:state]}. Checking again in 30 seconds." }
|
180
|
+
sleep(30)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
response
|
185
|
+
end
|
186
|
+
|
187
|
+
def logger
|
188
|
+
@logger
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
# create a request (place data in body if the request becomes too large)
|
194
|
+
def json_request(request_class, path, data = {}, header = {})
|
195
|
+
Sdk4me::Attachments.new(self).upload_attachments!(path, data)
|
196
|
+
request = request_class.new(expand_path(path), expand_header(header))
|
197
|
+
body = {}
|
198
|
+
data.each{ |k,v| body[k.to_s] = typecast(v, false) }
|
199
|
+
request.body = body.to_json
|
200
|
+
request
|
201
|
+
end
|
202
|
+
|
203
|
+
URI_ESCAPE_PATTERN = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
204
|
+
def uri_escape(value)
|
205
|
+
URI.escape(value, URI_ESCAPE_PATTERN).gsub('.', '%2E')
|
206
|
+
end
|
207
|
+
|
208
|
+
# Expand the given header with the default header
|
209
|
+
def expand_header(header = {})
|
210
|
+
header = DEFAULT_HEADER.merge(header)
|
211
|
+
header['X-4me-Account'] = option(:account) if option(:account)
|
212
|
+
header['AUTHORIZATION'] = 'Basic ' + ["#{option(:api_token)}:x"].pack('m*').gsub(/\s/, '')
|
213
|
+
if option(:source)
|
214
|
+
header['X-4me-Source'] = option(:source)
|
215
|
+
header['HTTP_USER_AGENT'] = option(:source)
|
216
|
+
end
|
217
|
+
header
|
218
|
+
end
|
219
|
+
|
220
|
+
# Expand the given path with the parameters
|
221
|
+
# Examples:
|
222
|
+
# person_id: 5
|
223
|
+
# :"updated_at=>" => yesterday
|
224
|
+
# fields: ['id', 'created_at', 'sourceID']
|
225
|
+
def expand_path(path, params = {})
|
226
|
+
path = path.dup
|
227
|
+
path = "/#{path}" unless path =~ /^\// # make sure path starts with /
|
228
|
+
path = "/#{option(:api_version)}#{path}" unless path =~ /^\/v[\d.]+\// # preprend api version
|
229
|
+
params.each do |key, value|
|
230
|
+
path << (path['?'] ? '&' : '?')
|
231
|
+
path << expand_param(key, value)
|
232
|
+
end
|
233
|
+
path
|
234
|
+
end
|
235
|
+
|
236
|
+
# Expand one parameter, e.g. (:"created_at=>", DateTime.now) to "created_at=%3E22011-12-16T12:24:41%2B01:00"
|
237
|
+
def expand_param(key, value)
|
238
|
+
param = uri_escape(key.to_s).gsub('%3D', '=') # handle :"updated_at=>" or :"person_id!=" parameters
|
239
|
+
param << '=' unless key['=']
|
240
|
+
param << typecast(value)
|
241
|
+
param
|
242
|
+
end
|
243
|
+
|
244
|
+
# Parameter value typecasting
|
245
|
+
def typecast(value, escape = true)
|
246
|
+
case value.class.name.to_sym
|
247
|
+
when :NilClass then ''
|
248
|
+
when :String then escape ? uri_escape(value) : value
|
249
|
+
when :TrueClass then 'true'
|
250
|
+
when :FalseClass then 'false'
|
251
|
+
when :DateTime then datetime = value.new_offset(0).iso8601; escape ? uri_escape(datetime) : datetime
|
252
|
+
when :Date then value.strftime("%Y-%m-%d")
|
253
|
+
when :Time then value.strftime("%H:%M")
|
254
|
+
# do not convert arrays in put/post requests as squashing arrays is only used in filtering
|
255
|
+
when :Array then escape ? value.map{ |v| typecast(v, escape) }.join(',') : value
|
256
|
+
# TODO: temporary for special constructions to update contact details, see Request #1444166
|
257
|
+
when :Hash then escape ? value.to_s : value
|
258
|
+
else escape ? value.to_json : value.to_s
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Send a request to 4me and wrap the HTTP Response in an Sdk4me::Response
|
263
|
+
# Guaranteed to return a Response, thought it may be +empty?+
|
264
|
+
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
265
|
+
@logger.debug { "Sending #{request.method} request to #{domain}:#{port}#{request.path}" }
|
266
|
+
_response = begin
|
267
|
+
http_with_proxy = option(:proxy_host).blank? ? Net::HTTP : Net::HTTP::Proxy(option(:proxy_host), option(:proxy_port), option(:proxy_user), option(:proxy_password))
|
268
|
+
http = http_with_proxy.new(domain, port)
|
269
|
+
http.read_timeout = option(:read_timeout)
|
270
|
+
http.use_ssl = ssl
|
271
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @ssl_verify_none
|
272
|
+
http.start{ |_http| _http.request(request) }
|
273
|
+
rescue ::Exception => e
|
274
|
+
Struct.new(:body, :message, :code, :header).new(nil, "No Response from Server - #{e.message} for '#{domain}:#{port}#{request.path}'", 500, {})
|
275
|
+
end
|
276
|
+
response = Sdk4me::Response.new(request, _response)
|
277
|
+
if response.valid?
|
278
|
+
@logger.debug { "Response:\n#{JSON.pretty_generate(response.json)}" }
|
279
|
+
elsif response.raw.body =~ /^\s*<\?xml/i
|
280
|
+
@logger.debug { "XML response:\n#{response.raw.body}" }
|
281
|
+
elsif '303' == response.raw.code.to_s
|
282
|
+
@logger.debug { "Redirect: #{response.raw.header['Location']}" }
|
283
|
+
else
|
284
|
+
@logger.error { "Request failed: #{response.message}" }
|
285
|
+
end
|
286
|
+
response
|
287
|
+
end
|
288
|
+
|
289
|
+
# parse the given URI to [domain, port, ssl, path]
|
290
|
+
def ssl_domain_port_path(uri)
|
291
|
+
uri = URI.parse(uri)
|
292
|
+
ssl = uri.scheme == 'https'
|
293
|
+
[ssl, uri.host, uri.port, uri.path]
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
module SendWithRateLimitBlock
|
299
|
+
# Wraps the _send method with retries when the server does not responsd, see +initialize+ option +:rate_limit_block+
|
300
|
+
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
301
|
+
return super(request, domain, port, ssl) unless option(:block_at_rate_limit)
|
302
|
+
now = Time.now
|
303
|
+
begin
|
304
|
+
_response = super(request, domain, port, ssl)
|
305
|
+
@logger.warn { "Request throttled, trying again in 5 minutes: #{_response.message}" } and sleep(300) if _response.throttled?
|
306
|
+
end while _response.throttled? && (Time.now - now) < 3660 # max 1 hour and 1 minute
|
307
|
+
_response
|
308
|
+
end
|
309
|
+
end
|
310
|
+
Client.send(:prepend, SendWithRateLimitBlock)
|
311
|
+
|
312
|
+
module SendWithRetries
|
313
|
+
# Wraps the _send method with retries when the server does not respond, see +initialize+ option +:retries+
|
314
|
+
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
315
|
+
retries = 0
|
316
|
+
sleep_time = 2
|
317
|
+
total_retry_time = 0
|
318
|
+
begin
|
319
|
+
_response = super(request, domain, port, ssl)
|
320
|
+
@logger.warn { "Request failed, retry ##{retries += 1} in #{sleep_time} seconds: #{_response.message}" } and sleep(sleep_time) if (_response.raw.code.to_s != '204' && _response.empty?) && option(:max_retry_time) > 0
|
321
|
+
total_retry_time += sleep_time
|
322
|
+
sleep_time *= 2
|
323
|
+
end while (_response.raw.code.to_s != '204' && _response.empty?) && total_retry_time < option(:max_retry_time)
|
324
|
+
_response
|
325
|
+
end
|
326
|
+
end
|
327
|
+
Client.send(:prepend, SendWithRetries)
|
328
|
+
end
|
329
|
+
|
330
|
+
# HTTPS with certificate bundle
|
331
|
+
module Net
|
332
|
+
class HTTP
|
333
|
+
alias_method :original_use_ssl=, :use_ssl=
|
334
|
+
|
335
|
+
def use_ssl=(flag)
|
336
|
+
self.ca_file = File.expand_path(Sdk4me.configuration.current[:ca_file], __FILE__) if flag
|
337
|
+
self.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
338
|
+
self.original_use_ssl = flag
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Sdk4me
|
2
|
+
class Attachments
|
3
|
+
|
4
|
+
AWS_PROVIDER = 'aws'
|
5
|
+
FILENAME_TEMPLATE = '${filename}'
|
6
|
+
|
7
|
+
def initialize(client)
|
8
|
+
@client = client
|
9
|
+
end
|
10
|
+
|
11
|
+
# upload the attachments in :attachments to 4me and return the data with the uploaded attachment info
|
12
|
+
def upload_attachments!(path, data)
|
13
|
+
raise_exceptions = !!data.delete(:attachments_exception)
|
14
|
+
attachments = [data.delete(:attachments)].flatten.compact
|
15
|
+
return if attachments.empty?
|
16
|
+
|
17
|
+
# retrieve the upload configuration for this record from 4me
|
18
|
+
storage = @client.get(path =~ /\d+$/ ? path : "#{path}/new", {attachment_upload_token: true}, @client.send(:expand_header))[:storage_upload]
|
19
|
+
report_error("Attachments not allowed for #{path}", raise_exceptions) and return unless storage
|
20
|
+
|
21
|
+
# upload each attachment and store the {key, filesize} has in the note_attachments parameter
|
22
|
+
data[attachments_field(path)] = attachments.map {|attachment| upload_attachment(storage, attachment, raise_exceptions) }.compact.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def attachments_field(path)
|
28
|
+
case path
|
29
|
+
when /cis/, /contracts/, /flsas/, /service_instances/, /slas/
|
30
|
+
:remarks_attachments
|
31
|
+
when /service_offerings/
|
32
|
+
:summary_attachments
|
33
|
+
else
|
34
|
+
:note_attachments
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def report_error(message, raise_exceptions)
|
39
|
+
if raise_exceptions
|
40
|
+
raise Sdk4me::UploadFailed.new(message)
|
41
|
+
else
|
42
|
+
@client.logger.error{ message }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# upload a single attachment and return the data for the note_attachments
|
47
|
+
# returns nil and provides an error in case the attachment upload failed
|
48
|
+
def upload_attachment(storage, attachment, raise_exceptions)
|
49
|
+
begin
|
50
|
+
# attachment is already a file or we need to open the file from disk
|
51
|
+
unless attachment.respond_to?(:path) && attachment.respond_to?(:read)
|
52
|
+
raise "file does not exist: #{attachment}" unless File.exists?(attachment)
|
53
|
+
attachment = File.open(attachment, 'rb')
|
54
|
+
end
|
55
|
+
|
56
|
+
# there are two different upload methods: AWS S3 and 4me local storage
|
57
|
+
key_template = "#{storage[:upload_path]}#{FILENAME_TEMPLATE}"
|
58
|
+
key = key_template.gsub(FILENAME_TEMPLATE, File.basename(attachment.path))
|
59
|
+
upload_method = storage[:provider] == AWS_PROVIDER ? :aws_upload : :upload_to_4me
|
60
|
+
send(upload_method, storage, key_template, key, attachment)
|
61
|
+
|
62
|
+
# return the values for the note_attachments param
|
63
|
+
{key: key, filesize: File.size(attachment.path)}
|
64
|
+
rescue ::Exception => e
|
65
|
+
report_error("Attachment upload failed: #{e.message}", raise_exceptions)
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def aws_upload(aws, key_template, key, attachment)
|
71
|
+
# upload the file to AWS
|
72
|
+
response = send_file(aws[:upload_uri], {
|
73
|
+
:'x-amz-server-side-encryption' => 'AES256',
|
74
|
+
key: key_template,
|
75
|
+
AWSAccessKeyId: aws[:access_key],
|
76
|
+
acl: 'private',
|
77
|
+
signature: aws[:signature],
|
78
|
+
success_action_status: 201,
|
79
|
+
policy: aws[:policy],
|
80
|
+
file: attachment # file must be last
|
81
|
+
})
|
82
|
+
# this is a bit of a hack, but Amazon S3 returns only XML :(
|
83
|
+
xml = response.raw.body || ''
|
84
|
+
error = xml[/<Error>.*<Message>(.*)<\/Message>.*<\/Error>/, 1]
|
85
|
+
raise "AWS upload to #{aws[:upload_uri]} for #{key} failed: #{error}" if error
|
86
|
+
|
87
|
+
# inform 4me of the successful upload
|
88
|
+
response = @client.get(aws[:success_url].split('/').last, {key: key}, @client.send(:expand_header))
|
89
|
+
raise "4me confirmation #{aws[:success_url].split('/').last} for #{key} failed: #{response.message}" unless response.valid?
|
90
|
+
end
|
91
|
+
|
92
|
+
# upload the file directly to 4me
|
93
|
+
def upload_to_4me(storage, key_template, key, attachment)
|
94
|
+
uri = storage[:upload_uri] =~ /\/v1/ ? storage[:upload_uri] : storage[:upload_uri].gsub('/attachments', '/v1/attachments')
|
95
|
+
response = send_file(uri, {file: attachment, key: key_template}, @client.send(:expand_header))
|
96
|
+
raise "4me upload to #{storage[:upload_uri]} for #{key} failed: #{response.message}" unless response.valid?
|
97
|
+
end
|
98
|
+
|
99
|
+
def send_file(uri, params, basic_auth_header = {})
|
100
|
+
params = {:'Content-Type' => MIME::Types.type_for(params[:key])[0] || MIME::Types["application/octet-stream"][0]}.merge(params)
|
101
|
+
data, header = Sdk4me::Multipart::Post.prepare_query(params)
|
102
|
+
ssl, domain, port, path = @client.send(:ssl_domain_port_path, uri)
|
103
|
+
request = Net::HTTP::Post.new(path, basic_auth_header.merge(header))
|
104
|
+
request.body = data
|
105
|
+
@client.send(:_send, request, domain, port, ssl)
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|