spectre-core 1.8.4 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/spectre/curl.rb CHANGED
@@ -1,368 +1,397 @@
1
- require 'open3'
2
- require 'ostruct'
3
-
4
-
5
- module Spectre::Curl
6
- class SpectreHttpRequest < Spectre::DslClass
7
- def initialize request
8
- @__req = request
9
- end
10
-
11
- def method method_name
12
- @__req['method'] = method_name.upcase
13
- end
14
-
15
- def url base_url
16
- @__req['base_url'] = base_url
17
- end
18
-
19
- def path url_path
20
- @__req['path'] = url_path
21
- end
22
-
23
- def header name, value
24
- @__req['headers'] = [] if not @__req['headers']
25
- @__req['headers'].append [name, value.to_s.strip]
26
- end
27
-
28
- def param name, value
29
- @__req['query'] = [] if not @__req['query']
30
- @__req['query'].append [name, value.to_s.strip]
31
- end
32
-
33
- def content_type media_type
34
- @__req['headers'] = [] if not @__req['headers']
35
- @__req['headers'].append ['Content-Type', media_type]
36
- end
37
-
38
- def json data
39
- body JSON.pretty_generate(data)
40
- content_type 'application/json'
41
- end
42
-
43
- def body body_content
44
- @__req['body'] = body_content
45
- end
46
-
47
- def ensure_success!
48
- @__req['ensure_success'] = true
49
- end
50
-
51
- def ensure_success?
52
- @__req['ensure_success']
53
- end
54
-
55
- def authenticate method
56
- @__req['auth'] = method
57
- end
58
-
59
- def certificate path
60
- @__req['cert'] = path
61
- use_ssl!
62
- end
63
-
64
- def use_ssl!
65
- @__req['use_ssl'] = true
66
- end
67
-
68
- alias_method :auth, :authenticate
69
- alias_method :cert, :certificate
70
- alias_method :media_type, :content_type
71
- end
72
-
73
- class SpectreHttpHeader
74
- def initialize headers
75
- @headers = headers || {}
76
- end
77
-
78
- def [] key
79
- @headers[key.downcase]
80
- end
81
- end
82
-
83
- class SpectreHttpResponse
84
- def initialize res
85
- @res = res
86
- @data = nil
87
- end
88
-
89
- def code
90
- @res[:code]
91
- end
92
-
93
- def message
94
- @res[:message]
95
- end
96
-
97
- def protocol
98
- @res[:protocol]
99
- end
100
-
101
- def version
102
- @res[:version]
103
- end
104
-
105
- def headers
106
- SpectreHttpHeader.new @res[:headers]
107
- end
108
-
109
- def body
110
- @res[:body]
111
- end
112
-
113
- def json
114
- return nil if not @res[:body]
115
-
116
- if @data == nil
117
- begin
118
- @data = JSON.parse(@res[:body], object_class: OpenStruct)
119
- rescue
120
- raise 'invalid json'
121
- end
122
- end
123
-
124
- @data
125
- end
126
-
127
- def success?
128
- @res[:code] < 400
129
- end
130
-
131
- def pretty
132
- @res.pretty
133
- end
134
- end
135
-
136
- # DEFAULT_HTTP_REQUEST = {
137
- # 'method' => 'GET', # -X, --request <cmd>
138
- # 'base_url' => nil,
139
- # 'path' => nil,
140
- # 'headers' => nil, # -H, --header <header/@file>
141
- # 'query' => nil,
142
- # 'body' => nil, # -d, --data <data>
143
- # 'cert' => nil, # --cacert
144
- # 'follow' => false, # -L, --location
145
- # 'username' => nil, # -u, --user <user:password>
146
- # 'password' => nil,
147
- # 'use_ssl' => false, # -k
148
- # }
149
-
150
-
151
- class << self
152
- @@http_cfg = {}
153
- @@response = nil
154
- @@request = nil
155
- @@modules = []
156
-
157
- def curl name, secure: false, &block
158
- req = {
159
- 'use_ssl' => secure,
160
- }
161
-
162
- if @@http_cfg.key? name
163
- req.merge! @@http_cfg[name]
164
- raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." if !req['base_url']
165
- else
166
- req['base_url'] = name
167
- end
168
-
169
- SpectreHttpRequest.new(req).instance_eval(&block) if block_given?
170
-
171
- invoke(req)
172
- end
173
-
174
- def curl_request
175
- raise 'No request has been invoked yet' unless @@request
176
- @@request
177
- end
178
-
179
- def curl_response
180
- raise 'There is no response. No request has been invoked yet.' unless @@response
181
- @@response
182
- end
183
-
184
- def register mod
185
- raise 'Module must not be nil' unless mod
186
- @@modules << mod
187
- end
188
-
189
- private
190
-
191
- def try_format_json str, pretty: false
192
- return str unless str or str.empty?
193
-
194
- begin
195
- json = JSON.parse str
196
-
197
- if pretty
198
- str = JSON.pretty_generate(json)
199
- else
200
- str = JSON.dump(json)
201
- end
202
- rescue
203
- # do nothing
204
- end
205
-
206
- str
207
- end
208
-
209
- def invoke req
210
- cmd = [@@curl_path]
211
-
212
- if req['cert'] or req['use_ssl']
213
- scheme = 'https'
214
- else
215
- scheme = 'http'
216
- end
217
-
218
- uri = req['base_url']
219
-
220
- if not uri.match /http(?:s)?:\/\//
221
- uri = scheme + '://' + uri
222
- end
223
-
224
- if req['path']
225
- uri += '/' if !uri.end_with? '/'
226
- uri += req['path']
227
- end
228
-
229
- if req['query']
230
- uri += '?'
231
- uri += req['query']
232
- .map { |x| x.join '='}
233
- .join '&'
234
- end
235
-
236
- cmd.append '"' + uri + '"'
237
- cmd.append '-X', req['method'] unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
238
-
239
- # Call all registered modules
240
- @@modules.each do |mod|
241
- mod.on_req(req, cmd) if mod.respond_to? :on_req
242
- end
243
-
244
- # Add headers to curl command
245
- req['headers'].each do |header|
246
- cmd.append '-H', '"' + header.join(':') + '"'
247
- end if req['headers']
248
-
249
- # Add request body
250
- if req['body'] != nil and not req['body'].empty?
251
- req_body = try_format_json(req['body']).gsub(/"/, '\\"')
252
- cmd.append '-d', '"' + req_body + '"'
253
- elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
254
- cmd.append '-d', '"\n"'
255
- end
256
-
257
- # Add certificate path if one if given
258
- if req['cert']
259
- raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
260
- cmd.append '--cacert', req['cert']
261
- elsif req['use_ssl'] or uri.start_with? 'https'
262
- cmd.append '-k'
263
- end
264
-
265
- cmd.append '-i'
266
- cmd.append '-v'
267
-
268
- @@request = OpenStruct.new req
269
-
270
- sys_cmd = cmd.join ' '
271
-
272
- @@logger.debug sys_cmd
273
-
274
- req_id = SecureRandom.uuid()[0..5]
275
-
276
- req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
277
- req['headers'].each do |header|
278
- req_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
279
- end if req['headers']
280
- req_log += req['body'] if req['body'] != nil and not req['body'].empty?
281
-
282
- @@logger.info req_log
283
-
284
- start_time = Time.now
285
-
286
- stdin, stdout, stderr, wait_thr = Open3.popen3(sys_cmd)
287
-
288
- end_time = Time.now
289
-
290
- output = stdout.gets(nil)
291
- stdout.close
292
-
293
- debug_log = stderr.gets(nil)
294
- stderr.close
295
-
296
- # debug_log.lines.each { |x| @@logger.debug x unless x.empty? }
297
-
298
- raise "Unable to request #{uri}. Please check if this service is reachable." unless output
299
-
300
- @@logger.debug "[<] #{req_id} stdout:\n#{output}"
301
-
302
- header, body = output.split /\r?\n\r?\n/
303
-
304
- result = header.lines.first
305
-
306
- exit_code = wait_thr.value.exitstatus
307
-
308
- raise Exception.new "An error occured while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
309
-
310
- # Parse protocol, version, status code and status message from response
311
- match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
312
-
313
- raise "Unexpected result from curl request:\n#{result}" unless match
314
-
315
- res_headers = header.lines[1..-1]
316
- .map { |x| /^(?<key>[A-Za-z0-9-]+):\s*(?<value>.*)$/.match x }
317
- .select { |x| x != nil }
318
- .map { |x| [x[:key].downcase, x[:value]] }
319
-
320
- res = {
321
- protocol: match[:protocol],
322
- version: match[:version],
323
- code: match[:code].to_i,
324
- message: match[:message],
325
- headers: Hash[res_headers],
326
- body: body
327
- }
328
-
329
- # Call all registered modules
330
- @@modules.each do |mod|
331
- mod.on_res(res, output) if mod.respond_to? :on_res
332
- end
333
-
334
- res_log = "[<] #{req_id} #{res[:code]} #{res[:message]} (#{end_time - start_time}s)\n"
335
- res_headers.each do |header|
336
- res_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
337
- end
338
-
339
- if res[:body] != nil and not res[:body].empty?
340
- res_log += try_format_json(res[:body], pretty: true)
341
- end
342
-
343
- @@logger.info res_log
344
-
345
- @@response = SpectreHttpResponse.new res
346
-
347
- raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
348
-
349
- @@response
350
- end
351
- end
352
-
353
- Spectre.register do |config|
354
- @@logger = ::Logger.new config['log_file'], progname: 'spectre/curl'
355
-
356
- @@curl_path = config['curl_path'] || 'curl'
357
-
358
- if config.key? 'http'
359
- @@http_cfg = {}
360
-
361
- config['http'].each do |name, cfg|
362
- @@http_cfg[name] = cfg
363
- end
364
- end
365
- end
366
-
367
- Spectre.delegate :curl, :curl_response, :curl_request, to: self
368
- end
1
+ require_relative '../spectre'
2
+
3
+ require 'open3'
4
+ require 'ostruct'
5
+
6
+
7
+ module Spectre::Curl
8
+ class SpectreHttpRequest < Spectre::DslClass
9
+ def initialize request
10
+ @__req = request
11
+ end
12
+
13
+ def method method_name
14
+ @__req['method'] = method_name.upcase
15
+ end
16
+
17
+ def url base_url
18
+ @__req['base_url'] = base_url
19
+ end
20
+
21
+ def path url_path
22
+ @__req['path'] = url_path
23
+ end
24
+
25
+ def header name, value
26
+ @__req['headers'] = [] unless @__req['headers']
27
+ @__req['headers'].append [name, value.to_s.strip]
28
+ end
29
+
30
+ def param name, value
31
+ @__req['query'] = [] unless @__req['query']
32
+ @__req['query'].append [name, value.to_s.strip]
33
+ end
34
+
35
+ def content_type media_type
36
+ @__req['headers'] = [] unless @__req['headers']
37
+ @__req['headers'].append ['Content-Type', media_type]
38
+ end
39
+
40
+ def json data
41
+ body JSON.pretty_generate(data)
42
+ content_type 'application/json'
43
+ end
44
+
45
+ def body body_content
46
+ @__req['body'] = body_content
47
+ end
48
+
49
+ def ensure_success!
50
+ @__req['ensure_success'] = true
51
+ end
52
+
53
+ def ensure_success?
54
+ @__req['ensure_success']
55
+ end
56
+
57
+ def authenticate method
58
+ @__req['auth'] = method
59
+ end
60
+
61
+ def certificate path
62
+ @__req['cert'] = path
63
+ use_ssl!
64
+ end
65
+
66
+ def use_ssl!
67
+ @__req['use_ssl'] = true
68
+ end
69
+
70
+ alias_method :auth, :authenticate
71
+ alias_method :cert, :certificate
72
+ alias_method :media_type, :content_type
73
+ end
74
+
75
+ class SpectreHttpHeader
76
+ def initialize headers
77
+ @headers = headers || {}
78
+ end
79
+
80
+ def [] key
81
+ @headers[key.downcase]
82
+ end
83
+ end
84
+
85
+ class SpectreHttpResponse
86
+ def initialize res
87
+ @res = res
88
+ @data = nil
89
+ end
90
+
91
+ def code
92
+ @res[:code]
93
+ end
94
+
95
+ def message
96
+ @res[:message]
97
+ end
98
+
99
+ def protocol
100
+ @res[:protocol]
101
+ end
102
+
103
+ def version
104
+ @res[:version]
105
+ end
106
+
107
+ def headers
108
+ SpectreHttpHeader.new @res[:headers]
109
+ end
110
+
111
+ def body
112
+ @res[:body]
113
+ end
114
+
115
+ def json
116
+ return nil unless @res[:body]
117
+
118
+ if @data == nil
119
+ begin
120
+ @data = JSON.parse(@res[:body], object_class: OpenStruct)
121
+ rescue
122
+ raise 'invalid json'
123
+ end
124
+ end
125
+
126
+ @data
127
+ end
128
+
129
+ def success?
130
+ @res[:code] < 400
131
+ end
132
+
133
+ def pretty
134
+ @res.pretty
135
+ end
136
+ end
137
+
138
+ # DEFAULT_HTTP_REQUEST = {
139
+ # 'method' => 'GET', # -X, --request <cmd>
140
+ # 'base_url' => nil,
141
+ # 'path' => nil,
142
+ # 'headers' => nil, # -H, --header <header/@file>
143
+ # 'query' => nil,
144
+ # 'body' => nil, # -d, --data <data>
145
+ # 'cert' => nil, # --cacert
146
+ # 'follow' => false, # -L, --location
147
+ # 'username' => nil, # -u, --user <user:password>
148
+ # 'password' => nil,
149
+ # 'use_ssl' => false, # -k
150
+ # }
151
+
152
+
153
+ class << self
154
+ @@http_cfg = {}
155
+ @@response = nil
156
+ @@request = nil
157
+ @@modules = []
158
+
159
+ def curl name, secure: false, &block
160
+ req = {
161
+ 'use_ssl' => secure,
162
+ }
163
+
164
+ if @@http_cfg.key? name
165
+ req.merge! @@http_cfg[name]
166
+ raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." unless req['base_url']
167
+ else
168
+ req['base_url'] = name
169
+ end
170
+
171
+ SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
172
+
173
+ invoke(req)
174
+ end
175
+
176
+ def curl_request
177
+ raise 'No request has been invoked yet' unless @@request
178
+
179
+ @@request
180
+ end
181
+
182
+ def curl_response
183
+ raise 'There is no response. No request has been invoked yet.' unless @@response
184
+
185
+ @@response
186
+ end
187
+
188
+ def register mod
189
+ raise 'Module must not be nil' unless mod
190
+
191
+ @@modules << mod
192
+ end
193
+
194
+ private
195
+
196
+ def try_format_json str, pretty: false
197
+ return str unless str or str.empty?
198
+
199
+ begin
200
+ json = JSON.parse(str)
201
+ json.obfuscate!(@@secure_keys) unless @@debug
202
+
203
+ if pretty
204
+ str = JSON.pretty_generate(json)
205
+ else
206
+ str = JSON.dump(json)
207
+ end
208
+ rescue
209
+ # do nothing
210
+ end
211
+
212
+ str
213
+ end
214
+
215
+ def secure? key
216
+ @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
217
+ end
218
+
219
+ def header_to_s headers
220
+ s = ''
221
+
222
+ return s unless headers
223
+
224
+ headers.each do |header|
225
+ key = header[0].to_s
226
+ value = header[1].to_s
227
+ value = '*****' if secure?(key) and not @@debug
228
+ s += "#{key.ljust(30, '.')}: #{value}\n"
229
+ end
230
+
231
+ s
232
+ end
233
+
234
+ def invoke req
235
+ cmd = [@@curl_path]
236
+
237
+ if req['cert'] or req['use_ssl']
238
+ scheme = 'https'
239
+ else
240
+ scheme = 'http'
241
+ end
242
+
243
+ uri = req['base_url']
244
+
245
+ unless uri.match /http(?:s)?:\/\//
246
+ uri = scheme + '://' + uri
247
+ end
248
+
249
+ if req['path']
250
+ uri += '/' unless uri.end_with? '/'
251
+ uri += req['path']
252
+ end
253
+
254
+ if req['query']
255
+ uri += '?'
256
+ uri += req['query']
257
+ .map { |x| x.join '='}
258
+ .join('&')
259
+ end
260
+
261
+ cmd.append('"' + uri + '"')
262
+ cmd.append('-X', req['method']) unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
263
+
264
+ # Call all registered modules
265
+ @@modules.each do |mod|
266
+ mod.on_req(req, cmd) if mod.respond_to? :on_req
267
+ end
268
+
269
+ # Add headers to curl command
270
+ req['headers'].each do |header|
271
+ cmd.append('-H', '"' + header.join(':') + '"')
272
+ end if req['headers']
273
+
274
+ # Add request body
275
+ if req['body'] != nil and not req['body'].empty?
276
+ req_body = try_format_json(req['body']).gsub(/"/, '\\"')
277
+ cmd.append('-d', '"' + req_body + '"')
278
+ elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
279
+ cmd.append('-d', '"\n"')
280
+ end
281
+
282
+ # Add certificate path if one if given
283
+ if req['cert']
284
+ raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
285
+
286
+ cmd.append('--cacert', req['cert'])
287
+ elsif req['use_ssl'] or uri.start_with? 'https'
288
+ cmd.append('-k')
289
+ end
290
+
291
+ cmd.append('-i')
292
+ cmd.append('-v')
293
+
294
+ @@request = OpenStruct.new(req)
295
+
296
+ sys_cmd = cmd.join(' ')
297
+
298
+ @@logger.debug(sys_cmd)
299
+
300
+ req_id = SecureRandom.uuid()[0..5]
301
+
302
+ req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
303
+ req_log += header_to_s(req['headers'])
304
+ req_log += try_format_json(req['body'], pretty: true)
305
+
306
+ @@logger.info(req_log)
307
+
308
+ start_time = Time.now
309
+
310
+ stdin, stdout, stderr, wait_thr = Open3.popen3(sys_cmd)
311
+
312
+ end_time = Time.now
313
+
314
+ output = stdout.gets(nil)
315
+ stdout.close
316
+
317
+ debug_log = stderr.gets(nil)
318
+ stderr.close
319
+
320
+ # debug_log.lines.each { |x| @@logger.debug x unless x.empty? }
321
+
322
+ raise "Unable to request #{uri}. Please check if this URL is correctly configured and reachable." unless output
323
+
324
+ @@logger.debug("[<] #{req_id} stdout:\n#{output}")
325
+
326
+ header, body = output.split /\r?\n\r?\n/
327
+
328
+ result = header.lines.first
329
+
330
+ exit_code = wait_thr.value.exitstatus
331
+
332
+ raise Exception.new "An error occured while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
333
+
334
+ # Parse protocol, version, status code and status message from response
335
+ match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
336
+
337
+ raise "Unexpected result from curl request:\n#{result}" unless match
338
+
339
+ res_headers = header.lines[1..-1]
340
+ .map { |x| /^(?<key>[A-Za-z0-9-]+):\s*(?<value>.*)$/.match x }
341
+ .select { |x| x != nil }
342
+ .map { |x| [x[:key].downcase, x[:value]] }
343
+
344
+ res = {
345
+ protocol: match[:protocol],
346
+ version: match[:version],
347
+ code: match[:code].to_i,
348
+ message: match[:message],
349
+ headers: Hash[res_headers],
350
+ body: body,
351
+ }
352
+
353
+ # Call all registered modules
354
+ @@modules.each do |mod|
355
+ mod.on_res(res, output) if mod.respond_to? :on_res
356
+ end
357
+
358
+ res_log = "[<] #{req_id} #{res[:code]} #{res[:message]} (#{end_time - start_time}s)\n"
359
+ res_headers.each do |header|
360
+ res_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
361
+ end
362
+
363
+ if res[:body] != nil and not res[:body].empty?
364
+ res_log += try_format_json(res[:body], pretty: true)
365
+ end
366
+
367
+ @@logger.info res_log
368
+
369
+ @@response = SpectreHttpResponse.new(res)
370
+
371
+ raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
372
+
373
+ @@response
374
+ end
375
+ end
376
+
377
+ Spectre.register do |config|
378
+ @@debug = config['debug']
379
+
380
+ @@logger = ::Logger.new(config['log_file'], progname: 'spectre/curl')
381
+ @@logger.level = @@debug ? Logger::DEBUG : Logger::INFO
382
+
383
+ @@secure_keys = config['secure_keys'] || []
384
+
385
+ @@curl_path = config['curl_path'] || 'curl'
386
+
387
+ if config.key? 'http'
388
+ @@http_cfg = {}
389
+
390
+ config['http'].each do |name, cfg|
391
+ @@http_cfg[name] = cfg
392
+ end
393
+ end
394
+ end
395
+
396
+ Spectre.delegate :curl, :curl_response, :curl_request, to: self
397
+ end