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.
- checksums.yaml +7 -0
- data/exe/spectre +487 -0
- data/lib/spectre.rb +434 -0
- data/lib/spectre/assertion.rb +277 -0
- data/lib/spectre/bag.rb +19 -0
- data/lib/spectre/curl.rb +368 -0
- data/lib/spectre/database/postgres.rb +78 -0
- data/lib/spectre/diagnostic.rb +29 -0
- data/lib/spectre/environment.rb +26 -0
- data/lib/spectre/ftp.rb +195 -0
- data/lib/spectre/helpers.rb +64 -0
- data/lib/spectre/http.rb +343 -0
- data/lib/spectre/http/basic_auth.rb +22 -0
- data/lib/spectre/http/keystone.rb +98 -0
- data/lib/spectre/logger.rb +144 -0
- data/lib/spectre/logger/console.rb +142 -0
- data/lib/spectre/logger/file.rb +96 -0
- data/lib/spectre/mixin.rb +41 -0
- data/lib/spectre/mysql.rb +97 -0
- data/lib/spectre/reporter/console.rb +103 -0
- data/lib/spectre/reporter/junit.rb +98 -0
- data/lib/spectre/resources.rb +46 -0
- data/lib/spectre/ssh.rb +149 -0
- metadata +140 -0
data/lib/spectre/bag.rb
ADDED
data/lib/spectre/curl.rb
ADDED
@@ -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
|