spectre-core 1.12.0 → 1.12.1

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,397 +1,397 @@
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
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