spectre-core 1.11.0 → 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,391 +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
- json.obfuscate!(@@secure_keys) if not @@debug
197
-
198
- if pretty
199
- str = JSON.pretty_generate(json)
200
- else
201
- str = JSON.dump(json)
202
- end
203
- rescue
204
- # do nothing
205
- end
206
-
207
- str
208
- end
209
-
210
- def is_secure? key
211
- @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
212
- end
213
-
214
- def header_to_s headers
215
- s = ''
216
-
217
- return s unless headers
218
-
219
- headers.each do |header|
220
- key = header[0].to_s
221
- value = header[1].to_s
222
- value = '*****' if is_secure?(key) and not @@debug
223
- s += "#{key.ljust(30, '.')}: #{value}\n"
224
- end
225
-
226
- s
227
- end
228
-
229
- def invoke req
230
- cmd = [@@curl_path]
231
-
232
- if req['cert'] or req['use_ssl']
233
- scheme = 'https'
234
- else
235
- scheme = 'http'
236
- end
237
-
238
- uri = req['base_url']
239
-
240
- if not uri.match /http(?:s)?:\/\//
241
- uri = scheme + '://' + uri
242
- end
243
-
244
- if req['path']
245
- uri += '/' if !uri.end_with? '/'
246
- uri += req['path']
247
- end
248
-
249
- if req['query']
250
- uri += '?'
251
- uri += req['query']
252
- .map { |x| x.join '='}
253
- .join('&')
254
- end
255
-
256
- cmd.append('"' + uri + '"')
257
- cmd.append('-X', req['method']) unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
258
-
259
- # Call all registered modules
260
- @@modules.each do |mod|
261
- mod.on_req(req, cmd) if mod.respond_to? :on_req
262
- end
263
-
264
- # Add headers to curl command
265
- req['headers'].each do |header|
266
- cmd.append('-H', '"' + header.join(':') + '"')
267
- end if req['headers']
268
-
269
- # Add request body
270
- if req['body'] != nil and not req['body'].empty?
271
- req_body = try_format_json(req['body']).gsub(/"/, '\\"')
272
- cmd.append('-d', '"' + req_body + '"')
273
- elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
274
- cmd.append('-d', '"\n"')
275
- end
276
-
277
- # Add certificate path if one if given
278
- if req['cert']
279
- raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
280
- cmd.append('--cacert', req['cert'])
281
- elsif req['use_ssl'] or uri.start_with? 'https'
282
- cmd.append('-k')
283
- end
284
-
285
- cmd.append('-i')
286
- cmd.append('-v')
287
-
288
- @@request = OpenStruct.new(req)
289
-
290
- sys_cmd = cmd.join(' ')
291
-
292
- @@logger.debug(sys_cmd)
293
-
294
- req_id = SecureRandom.uuid()[0..5]
295
-
296
- req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
297
- req_log += header_to_s(req['headers'])
298
- req_log += try_format_json(req['body'], pretty: true)
299
-
300
- @@logger.info(req_log)
301
-
302
- start_time = Time.now
303
-
304
- stdin, stdout, stderr, wait_thr = Open3.popen3(sys_cmd)
305
-
306
- end_time = Time.now
307
-
308
- output = stdout.gets(nil)
309
- stdout.close
310
-
311
- debug_log = stderr.gets(nil)
312
- stderr.close
313
-
314
- # debug_log.lines.each { |x| @@logger.debug x unless x.empty? }
315
-
316
- raise "Unable to request #{uri}. Please check if this service is reachable." unless output
317
-
318
- @@logger.debug("[<] #{req_id} stdout:\n#{output}")
319
-
320
- header, body = output.split /\r?\n\r?\n/
321
-
322
- result = header.lines.first
323
-
324
- exit_code = wait_thr.value.exitstatus
325
-
326
- raise Exception.new "An error occured while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
327
-
328
- # Parse protocol, version, status code and status message from response
329
- match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
330
-
331
- raise "Unexpected result from curl request:\n#{result}" unless match
332
-
333
- res_headers = header.lines[1..-1]
334
- .map { |x| /^(?<key>[A-Za-z0-9-]+):\s*(?<value>.*)$/.match x }
335
- .select { |x| x != nil }
336
- .map { |x| [x[:key].downcase, x[:value]] }
337
-
338
- res = {
339
- protocol: match[:protocol],
340
- version: match[:version],
341
- code: match[:code].to_i,
342
- message: match[:message],
343
- headers: Hash[res_headers],
344
- body: body
345
- }
346
-
347
- # Call all registered modules
348
- @@modules.each do |mod|
349
- mod.on_res(res, output) if mod.respond_to? :on_res
350
- end
351
-
352
- res_log = "[<] #{req_id} #{res[:code]} #{res[:message]} (#{end_time - start_time}s)\n"
353
- res_headers.each do |header|
354
- res_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
355
- end
356
-
357
- if res[:body] != nil and not res[:body].empty?
358
- res_log += try_format_json(res[:body], pretty: true)
359
- end
360
-
361
- @@logger.info res_log
362
-
363
- @@response = SpectreHttpResponse.new(res)
364
-
365
- raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
366
-
367
- @@response
368
- end
369
- end
370
-
371
- Spectre.register do |config|
372
- @@debug = config['debug']
373
-
374
- @@logger = ::Logger.new(config['log_file'], progname: 'spectre/curl')
375
- @@logger.level = @@debug ? Logger::DEBUG : Logger::INFO
376
-
377
- @@secure_keys = config['secure_keys'] || []
378
-
379
- @@curl_path = config['curl_path'] || 'curl'
380
-
381
- if config.key? 'http'
382
- @@http_cfg = {}
383
-
384
- config['http'].each do |name, cfg|
385
- @@http_cfg[name] = cfg
386
- end
387
- end
388
- end
389
-
390
- Spectre.delegate :curl, :curl_response, :curl_request, to: self
391
- 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