spectre-core 1.8.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.
@@ -0,0 +1,19 @@
1
+ require 'ostruct'
2
+
3
+ module Spectre
4
+ module Bag
5
+ class << self
6
+ @@bag
7
+
8
+ def bag
9
+ @@bag
10
+ end
11
+ end
12
+
13
+ Spectre.register do |config|
14
+ @@bag = OpenStruct.new
15
+ end
16
+
17
+ Spectre.delegate :bag, to: Bag
18
+ end
19
+ end
@@ -0,0 +1,368 @@
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.has_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.has_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
@@ -0,0 +1,78 @@
1
+ require 'pg'
2
+
3
+
4
+ class PG::Result
5
+ alias :count :ntuples
6
+ end
7
+
8
+
9
+ module Spectre
10
+ module Database
11
+ module Postgres
12
+ @@modules = []
13
+
14
+ class SQLStatement
15
+ attr_accessor :query, :params
16
+
17
+ def initialize
18
+ @query = nil
19
+ @params = []
20
+ end
21
+
22
+ def statement query
23
+ @query = query
24
+ end
25
+
26
+ def param val
27
+ @params << val
28
+ end
29
+ end
30
+
31
+
32
+ class << self
33
+ def postgres name, &block
34
+ raise "postgres '#{name}' not configured" unless @@db_cfg.has_key? name
35
+
36
+ statement = SQLStatement.new
37
+ statement.instance_eval &block
38
+
39
+ cfg = @@db_cfg[name]
40
+
41
+ begin
42
+ con = PG.connect({
43
+ host: cfg['host'],
44
+ port: cfg['port'],
45
+ dbname: cfg['database'],
46
+ user: cfg['username'],
47
+ password: cfg['username'],
48
+ })
49
+
50
+ if statement.params
51
+ @@result = con.exec_params(statement.query, statement.params)
52
+ else
53
+ @@result = con.exec(statement.query)
54
+ end
55
+
56
+ ensure
57
+ con.close if con
58
+ end
59
+ end
60
+
61
+
62
+ def result
63
+ @@result
64
+ end
65
+ end
66
+
67
+ Spectre.register do |config|
68
+ @@db_cfg = {}
69
+
70
+ config['postgres'].each do |name, cfg|
71
+ @@db_cfg[name] = cfg
72
+ end
73
+ end
74
+
75
+ Spectre.delegate :postgres, :result, to: self
76
+ end
77
+ end
78
+ end