kensa 1.1.4 → 1.2.0rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,485 +0,0 @@
1
- require 'yajl'
2
- require 'mechanize'
3
- require 'socket'
4
- require 'timeout'
5
- require 'uri'
6
-
7
- module Heroku
8
- module Kensa
9
-
10
- class NilScreen
11
- def test(msg)
12
- end
13
-
14
- def check(msg)
15
- end
16
-
17
- def error(msg)
18
- end
19
-
20
- def result(status)
21
- end
22
-
23
- def message(msg)
24
- end
25
- end
26
-
27
- class STDOUTScreen
28
- [:test, :check, :error, :result, :message].each do |method|
29
- eval %{ def #{method}(*args)\n STDOUT.puts *args\n end }
30
- end
31
- end
32
-
33
- class Check
34
- attr_accessor :screen, :data
35
-
36
- class CheckError < StandardError ; end
37
-
38
- def initialize(data, screen=NilScreen.new)
39
- @data = data
40
- @screen = screen
41
- end
42
-
43
- def test(msg)
44
- screen.test msg
45
- end
46
-
47
- def check(msg)
48
- screen.check(msg)
49
- if yield
50
- screen.result(true)
51
- else
52
- raise CheckError
53
- end
54
- end
55
-
56
- def run(klass, data)
57
- c = klass.new(data, screen)
58
- instance_eval(&c)
59
- end
60
-
61
- def error(msg)
62
- raise CheckError, msg
63
- end
64
-
65
- def call
66
- call!
67
- true
68
- rescue CheckError => boom
69
- screen.result(false)
70
- screen.error boom.message if boom.message != boom.class.name
71
-
72
- false
73
- end
74
-
75
- def to_proc
76
- me = self
77
- Proc.new { me.call! }
78
- end
79
-
80
- end
81
-
82
-
83
- class ManifestCheck < Check
84
-
85
- ValidPriceUnits = %w[month dyno_hour]
86
-
87
- def call!
88
- test "manifest id key"
89
- check "if exists" do
90
- data.has_key?("id")
91
- end
92
- check "is a string" do
93
- data["id"].is_a?(String)
94
- end
95
- check "is not blank" do
96
- !data["id"].empty?
97
- end
98
-
99
- test "manifest api key"
100
- check "if exists" do
101
- data.has_key?("api")
102
- end
103
- check "is a hash" do
104
- data["api"].is_a?(Hash)
105
- end
106
- check "contains password" do
107
- data["api"].has_key?("password") && data["api"]["password"] != ""
108
- end
109
- check "contains test url" do
110
- data["api"].has_key?("test")
111
- end
112
- check "contains production url" do
113
- data["api"].has_key?("production")
114
- end
115
- check "production url uses SSL" do
116
- data["api"]["production"] =~ /^https:/
117
- end
118
- check "contains config_vars array" do
119
- data["api"].has_key?("config_vars") && data["api"]["config_vars"].is_a?(Array)
120
- end
121
- check "containst at least one config var" do
122
- !data["api"]["config_vars"].empty?
123
- end
124
- check "all config vars are uppercase strings" do
125
- data["api"]["config_vars"].each do |k, v|
126
- if k =~ /^[A-Z][0-9A-Z_]+$/
127
- true
128
- else
129
- error "#{k.inspect} is not a valid ENV key"
130
- end
131
- end
132
- end
133
- check "all config vars are prefixed with the addon id" do
134
- data["api"]["config_vars"].each do |k|
135
- addon_key = data['id'].upcase.gsub('-', '_')
136
- if k =~ /^#{addon_key}_/
137
- true
138
- else
139
- error "#{k} is not a valid ENV key - must be prefixed with #{addon_key}_"
140
- end
141
- end
142
- end
143
- check "deprecated fields" do
144
- if data["api"].has_key?("username")
145
- error "username is deprecated: Please authenticate using the add-on id."
146
- end
147
- true
148
- end
149
- end
150
-
151
- end
152
-
153
-
154
- class ProvisionResponseCheck < Check
155
-
156
- def call!
157
- response = data[:provision_response]
158
- test "response"
159
-
160
- check "contains an id" do
161
- response.is_a?(Hash) && response.has_key?("id")
162
- end
163
-
164
- screen.message " (id #{response['id']})"
165
-
166
- if response.has_key?("config")
167
- test "config data"
168
- check "is a hash" do
169
- response["config"].is_a?(Hash)
170
- end
171
-
172
- check "all config keys were previously defined in the manifest" do
173
- response["config"].keys.each do |key|
174
- error "#{key} is not in the manifest" unless data["api"]["config_vars"].include?(key)
175
- end
176
- true
177
- end
178
-
179
- check "all config values are strings" do
180
- response["config"].each do |k, v|
181
- if v.is_a?(String)
182
- true
183
- else
184
- error "the key #{k} doesn't contain a string (#{v.inspect})"
185
- end
186
- end
187
- end
188
-
189
- check "URL configs vars" do
190
- response["config"].each do |key, value|
191
- next unless key =~ /_URL$/
192
- begin
193
- uri = URI.parse(value)
194
- error "#{value} is not a valid URI - missing host" unless uri.host
195
- error "#{value} is not a valid URI - missing scheme" unless uri.scheme
196
- error "#{value} is not a valid URI - pointing to localhost" if @data[:env] == 'production' && uri.host == 'localhost'
197
- rescue URI::Error
198
- error "#{value} is not a valid URI"
199
- end
200
- end
201
- end
202
-
203
- end
204
- end
205
-
206
- end
207
-
208
-
209
- class ApiCheck < Check
210
- def url
211
- env = data[:env] || 'test'
212
- data["api"][env].chomp("/")
213
- end
214
-
215
- def credentials
216
- [ data['id'], data['api']['password'] ]
217
- end
218
- end
219
-
220
- class ProvisionCheck < ApiCheck
221
- include HTTP
222
-
223
- READLEN = 1024 * 10
224
- APPID = "app#{rand(10000)}@kensa.heroku.com"
225
- APPNAME = "myapp"
226
-
227
- def call!
228
- json = nil
229
- response = nil
230
-
231
- code = nil
232
- json = nil
233
- path = "/heroku/resources"
234
- callback = "http://localhost:7779/callback/999"
235
- reader, writer = nil
236
-
237
- payload = {
238
- :heroku_id => APPID,
239
- :plan => data[:plan] || 'test',
240
- :callback_url => callback
241
- }
242
-
243
- if data[:async]
244
- reader, writer = IO.pipe
245
- end
246
-
247
- test "POST /heroku/resources"
248
- check "response" do
249
- if data[:async]
250
- child = fork do
251
- reader.close
252
- server = TCPServer.open(7779)
253
- client = server.accept
254
- writer.write(client.readpartial(READLEN))
255
- client.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
256
- client.close
257
- writer.close
258
- end
259
- sleep(1)
260
- end
261
-
262
- code, json = post(credentials, path, payload)
263
-
264
- if code == 200
265
- # noop
266
- elsif code == -1
267
- error("unable to connect to #{url}")
268
- else
269
- error("expected 200, got #{code}")
270
- end
271
-
272
- true
273
- end
274
-
275
- if data[:async]
276
- check "async response to PUT #{callback}" do
277
- out = reader.readpartial(READLEN)
278
- _, json = out.split("\r\n\r\n")
279
- end
280
- end
281
-
282
- check "valid JSON" do
283
- begin
284
- response = Yajl::Parser.parse(json)
285
- rescue Yajl::ParseError => boom
286
- error boom.message
287
- end
288
- true
289
- end
290
-
291
- check "authentication" do
292
- wrong_credentials = ['wrong', 'secret']
293
- code, _ = post(wrong_credentials, path, payload)
294
- error("expected 401, got #{code}") if code != 401
295
- true
296
- end
297
-
298
- data[:provision_response] = response
299
-
300
- run ProvisionResponseCheck, data
301
- end
302
-
303
- ensure
304
- reader.close rescue nil
305
- writer.close rescue nil
306
- end
307
-
308
-
309
- class DeprovisionCheck < ApiCheck
310
- include HTTP
311
-
312
- def call!
313
- id = data[:id]
314
- raise ArgumentError, "No id specified" if id.nil?
315
-
316
- path = "/heroku/resources/#{CGI::escape(id.to_s)}"
317
-
318
- test "DELETE #{path}"
319
- check "response" do
320
- code, _ = delete(credentials, path, nil)
321
- if code == 200
322
- true
323
- elsif code == -1
324
- error("unable to connect to #{url}")
325
- else
326
- error("expected 200, got #{code}")
327
- end
328
- end
329
-
330
- check "authentication" do
331
- wrong_credentials = ['wrong', 'secret']
332
- code, _ = delete(wrong_credentials, path, nil)
333
- error("expected 401, got #{code}") if code != 401
334
- true
335
- end
336
-
337
- end
338
-
339
- end
340
-
341
-
342
- class PlanChangeCheck < ApiCheck
343
- include HTTP
344
-
345
- def call!
346
- id = data[:id]
347
- raise ArgumentError, "No id specified" if id.nil?
348
-
349
- new_plan = data[:plan]
350
- raise ArgumentError, "No plan specified" if new_plan.nil?
351
-
352
- path = "/heroku/resources/#{CGI::escape(id.to_s)}"
353
-
354
- test "PUT #{path}"
355
- check "response" do
356
- code, _ = put(credentials, path, { :plan => new_plan})
357
- if code == 200
358
- true
359
- elsif code == -1
360
- error("unable to connect to #{url}")
361
- else
362
- error("expected 200, got #{code}")
363
- end
364
- end
365
-
366
- check "authentication" do
367
- wrong_credentials = ['wrong', 'secret']
368
- code, _ = delete(wrong_credentials, path, nil)
369
- error("expected 401, got #{code}") if code != 401
370
- true
371
- end
372
- end
373
- end
374
-
375
-
376
- class SsoCheck < ApiCheck
377
- include HTTP
378
-
379
- def agent
380
- @agent ||= Mechanize.new
381
- end
382
-
383
- def mechanize_get
384
- if @sso.POST?
385
- page = agent.post(@sso.post_url, @sso.query_params)
386
- else
387
- page = agent.get(@sso.get_url)
388
- end
389
- return page, 200
390
- rescue Mechanize::ResponseCodeError => error
391
- return nil, error.response_code.to_i
392
- rescue Errno::ECONNREFUSED
393
- error("connection refused to #{url}")
394
- end
395
-
396
- def check(msg)
397
- @sso = Sso.new(data)
398
- super
399
- end
400
-
401
- def call!
402
- error("need an sso salt to perform sso test") unless data['api']['sso_salt']
403
-
404
- sso = Sso.new(data)
405
- verb = sso.POST? ? 'POST' : 'GET'
406
- test "#{verb} #{sso.path}"
407
-
408
- check "validates token" do
409
- @sso.token = 'invalid'
410
- page, respcode = mechanize_get
411
- error("expected 403, got #{respcode}") unless respcode == 403
412
- true
413
- end
414
-
415
- check "validates timestamp" do
416
- @sso.timestamp = (Time.now - 60*6).to_i
417
- page, respcode = mechanize_get
418
- error("expected 403, got #{respcode}") unless respcode == 403
419
- true
420
- end
421
-
422
- page_logged_in = nil
423
- check "logs in" do
424
- page_logged_in, respcode = mechanize_get
425
- error("expected 200, got #{respcode}") unless respcode == 200
426
- true
427
- end
428
-
429
- check "creates the heroku-nav-data cookie" do
430
- cookie = agent.cookie_jar.cookies(URI.parse(@sso.full_url)).detect { |c| c.name == 'heroku-nav-data' }
431
- error("could not find cookie heroku-nav-data") unless cookie
432
- error("expected #{@sso.sample_nav_data}, got #{cookie.value}") unless cookie.value == @sso.sample_nav_data
433
- true
434
- end
435
-
436
- check "displays the heroku layout" do
437
- error("could not find Heroku layout") if page_logged_in.search('div#heroku-header').empty?
438
- true
439
- end
440
- end
441
- end
442
-
443
-
444
- ##
445
- # On Testing:
446
- # I've opted to not write tests for this
447
- # due to the simple nature it's currently in.
448
- # If this becomes more complex in even the
449
- # least amount, find me (blake) and I'll
450
- # help get tests in.
451
- class AllCheck < Check
452
-
453
- def call!
454
- args = data[:args]
455
- run ProvisionCheck, data
456
-
457
- response = data[:provision_response]
458
- data.merge!(:id => response["id"])
459
- config = response["config"] || Hash.new
460
-
461
- if args
462
- screen.message "\n\n"
463
- screen.message "Starting #{args.first}..."
464
- screen.message "\n\n"
465
-
466
- run_in_env(config) { system(*args) }
467
- error("run exited abnormally, expected 0, got #{$?.to_i}") unless $?.to_i == 0
468
-
469
- screen.message "\n"
470
- screen.message "End of #{args.first}\n"
471
- end
472
-
473
- run DeprovisionCheck, data
474
- end
475
-
476
- def run_in_env(env)
477
- env.each {|key, value| ENV[key] = value }
478
- yield
479
- env.keys.each {|key| ENV.delete(key) }
480
- end
481
-
482
- end
483
-
484
- end
485
- end
@@ -1,25 +0,0 @@
1
- require 'test/helper'
2
-
3
- class AllCheckTest < Test::Unit::TestCase
4
- include Heroku::Kensa
5
-
6
- setup do
7
- @data = Manifest.new.skeleton
8
- @data['api']['password'] = 'secret'
9
- @data['api']['test'] += "working"
10
- @file = File.dirname(__FILE__) + "/resources/runner.rb"
11
- end
12
-
13
- def check; AllCheck; end
14
-
15
- test "valid on script exit 0" do
16
- @data[:args] = "ruby #{@file}"
17
- assert_valid
18
- end
19
-
20
- test "invalid on script exit non 0" do
21
- @data[:args] = "ruby #{@file} fail"
22
- assert_invalid
23
- end
24
-
25
- end
@@ -1,36 +0,0 @@
1
- require 'test/helper'
2
-
3
- class DeprovisionCheckTest < Test::Unit::TestCase
4
- include Heroku::Kensa
5
-
6
- setup do
7
- @data = Manifest.new.skeleton.merge :id => 123
8
- @responses = [
9
- [200, ""],
10
- [401, ""],
11
- ]
12
- end
13
-
14
- def check ; DeprovisionCheck ; end
15
-
16
- test "valid on 200" do
17
- assert_valid do |check|
18
- kensa_stub :delete, check, @responses
19
- end
20
- end
21
-
22
- test "status other than 200" do
23
- @responses[0] = [500, ""]
24
- assert_invalid do |check|
25
- kensa_stub :delete, check, @responses
26
- end
27
- end
28
-
29
- test "runs auth check" do
30
- @responses[1] = [200, ""]
31
- assert_invalid do |check|
32
- kensa_stub :delete, check, @responses
33
- end
34
- end
35
-
36
- end
@@ -1,79 +0,0 @@
1
- require 'test/helper'
2
-
3
- class ManifestCheckTest < Test::Unit::TestCase
4
- include Heroku::Kensa
5
-
6
- def check ; ManifestCheck ; end
7
-
8
- setup { @data = Manifest.new.skeleton }
9
-
10
- test "is valid if no errors" do
11
- assert_valid
12
- end
13
-
14
- test "has an id" do
15
- @data.delete("id")
16
- assert_invalid
17
- end
18
-
19
- test "api key exists" do
20
- @data.delete("api")
21
- assert_invalid
22
- end
23
-
24
- test "api is a Hash" do
25
- @data["api"] = ""
26
- assert_invalid
27
- end
28
-
29
- test "api has a password" do
30
- @data["api"].delete("password")
31
- assert_invalid
32
- end
33
-
34
- test "api contains test" do
35
- @data["api"].delete("test")
36
- assert_invalid
37
- end
38
-
39
- test "api contains production" do
40
- @data["api"].delete("production")
41
- assert_invalid
42
- end
43
-
44
- test "api contains production of https" do
45
- @data["api"]["production"] = "http://foo.com"
46
- assert_invalid
47
- end
48
-
49
- test "api contains config_vars array" do
50
- @data["api"]["config_vars"] = "test"
51
- assert_invalid
52
- end
53
-
54
- test "api contains at least one config var" do
55
- @data["api"]["config_vars"].clear
56
- assert_invalid
57
- end
58
-
59
- test "all config vars are in upper case" do
60
- @data["api"]["config_vars"] << 'MYADDON_invalid_var'
61
- assert_invalid
62
- end
63
-
64
- test "assert config var prefixes match addon id" do
65
- @data["api"]["config_vars"] << 'MONGO_URL'
66
- assert_invalid
67
- end
68
-
69
- test "replaces dashes for underscores on the config var check" do
70
- @data["id"] = "MY-ADDON"
71
- @data["api"]["config_vars"] = ["MY_ADDON_URL"]
72
- assert_valid
73
- end
74
-
75
- test "username is deprecated" do
76
- @data["api"]["username"] = "heroku"
77
- assert_invalid
78
- end
79
- end
@@ -1,27 +0,0 @@
1
- require 'test/helper'
2
-
3
- class PlanChangeCheckTest < Test::Unit::TestCase
4
- include Heroku::Kensa
5
-
6
- setup do
7
- @data = Manifest.new.skeleton.merge :id => 123, :plan => 'premium'
8
- @data['api']['password'] = 'secret'
9
- end
10
-
11
- def check ; PlanChangeCheck ; end
12
-
13
- test "working plan change call" do
14
- @data['api']['test'] += "working"
15
- assert_valid
16
- end
17
-
18
- test "detects invalid status" do
19
- @data['api']['test'] += "invalid-status"
20
- assert_invalid
21
- end
22
-
23
- test "detects missing auth" do
24
- @data['api']['test'] += "invalid-missing-auth"
25
- assert_invalid
26
- end
27
- end
@@ -1,43 +0,0 @@
1
- require 'test/helper'
2
-
3
- class ProvisionCheckTest < Test::Unit::TestCase
4
- include Heroku::Kensa
5
-
6
- setup do
7
- @data = Manifest.new.skeleton
8
- @data['api']['password'] = 'secret'
9
- end
10
-
11
- def check ; ProvisionCheck ; end
12
-
13
- test "working provision call" do
14
- @data['api']['test'] += "working"
15
- assert_valid
16
- end
17
-
18
- test "detects invalid JSON" do
19
- @data['api']['test'] += "invalid-json"
20
- assert_invalid
21
- end
22
-
23
- test "detects invalid response" do
24
- @data['api']['test'] += "invalid-response"
25
- assert_invalid
26
- end
27
-
28
- test "detects invalid status" do
29
- @data['api']['test'] += "invalid-status"
30
- assert_invalid
31
- end
32
-
33
- test "detects missing id" do
34
- @data['api']['test'] += "invalid-missing-id"
35
- assert_invalid
36
- end
37
-
38
- test "detects missing auth" do
39
- @data['api']['test'] += "invalid-missing-auth"
40
- assert_invalid
41
- end
42
-
43
- end