kensa 1.1.4 → 1.2.0rc1

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.
@@ -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