kensa 0.4.2 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,9 +1,10 @@
1
+ desc 'Run all unit tests'
1
2
  task :test do
2
3
  fork do
3
- exec "ruby test/resources/test_server.rb > /dev/null 2>&1"
4
+ exec "ruby test/resources/server.rb > /dev/null 2>&1"
4
5
  end
5
6
  system "turn"
6
- system "ps -ax | grep test_server | grep -v grep | awk '{print $1}' | xargs kill"
7
+ system "ps -ax | grep test/resources/server.rb | grep -v grep | awk '{print $1}' | xargs kill"
7
8
  end
8
9
 
9
10
  task :default => :test
@@ -20,14 +21,15 @@ begin
20
21
 
21
22
  gemspec.add_development_dependency(%q<turn>, [">= 0"])
22
23
  gemspec.add_development_dependency(%q<contest>, [">= 0"])
24
+ gemspec.add_development_dependency(%q<timecop>, [">= 0.3.5"])
23
25
  gemspec.add_dependency(%q<sinatra>, ["~> 0.9"])
24
- gemspec.add_dependency(%q<rest-client>, ["~> 1.2.0"])
26
+ gemspec.add_dependency(%q<rest-client>, ["~> 1.4.0"])
25
27
  gemspec.add_dependency(%q<yajl-ruby>, ["~> 0.6"])
26
28
  gemspec.add_dependency(%q<term-ansicolor>, ["~> 1.0"])
27
29
  gemspec.add_dependency(%q<launchy>, [">= 0.3.2"])
28
30
  gemspec.add_dependency(%q<mechanize>, ["~> 1.0.0"])
29
31
 
30
- gemspec.version = '0.4.2'
32
+ gemspec.version = '1.0.0.beta1'
31
33
  end
32
34
  rescue LoadError
33
35
  puts "Jeweler not available. Install it with: gem install jeweler"
data/bin/kensa CHANGED
@@ -1,115 +1,32 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'optparse'
4
- require 'term/ansicolor'
5
- require 'launchy'
6
4
  require 'heroku/kensa'
5
+ require 'heroku/kensa/client'
6
+
7
+ $stdout.sync = true
7
8
 
8
- fn = "addon-manifest.json"
9
9
  options = {
10
- :async => false,
11
- :env => "test",
10
+ :filename => 'addon-manifest.json',
11
+ :env => "test",
12
+ :async => false,
12
13
  }
13
14
 
14
15
  ARGV.options do |o|
15
- o.on("-f file", "--file") {|filename| fn = filename }
16
+ o.on("-f file", "--file") {|filename| options[:filename] = filename }
16
17
  o.on("--async") { options[:async] = true }
17
18
  o.on("--production") { options[:env] = "production" }
18
19
  o.on("--plan PLANID") { |plan| options[:plan] = plan }
20
+ o.on("--without-sso") { options[:sso] = false }
19
21
  o.on("-h", "--help") { command = "help" }
20
22
  o.parse!
21
23
  end
22
24
 
23
- command = ARGV.shift
24
-
25
- $stdout.sync = true
26
-
27
- class Screen
28
- include Term::ANSIColor
29
-
30
- def test(msg)
31
- $stdout.puts
32
- $stdout.puts
33
- $stdout.print "Testing #{msg}"
34
- end
35
-
36
- def check(msg)
37
- $stdout.puts
38
- $stdout.print " Check #{msg}"
39
- end
40
-
41
- def error(msg)
42
- $stdout.print "\n", magenta(" ! #{msg}")
43
- end
44
-
45
- def result(status)
46
- msg = status ? bold("[PASS]") : red(bold("[FAIL]"))
47
- $stdout.print " #{msg}"
48
- end
49
-
50
- def message(msg)
51
- $stdout.puts msg
52
- end
25
+ include Heroku::Kensa
53
26
 
54
- def finish
55
- $stdout.puts
56
- $stdout.puts
57
- $stdout.puts "done."
58
- end
59
-
60
- end
61
-
62
- def resolve_manifest(fn)
63
- if File.exists?(fn)
64
- File.read(fn)
65
- else
66
- abort("fatal: #{fn} not found")
67
- end
68
- end
69
-
70
- def run(klass, fn, extras={})
71
- screen = Screen.new
72
- data = Yajl::Parser.parse(resolve_manifest(fn))
73
- check = klass.new(data.merge(extras), screen)
74
- check.call
75
- screen.finish
76
- end
77
-
78
- include Heroku::Sensei
79
-
80
- case command
81
- when "init"
82
- Manifest.init(fn)
83
- Screen.new.message "Initialized new addon manifest in #{fn}"
84
- when "test"
85
- case check = ARGV.shift
86
- when "manifest"
87
- run ManifestCheck, fn
88
- when "provision"
89
- run ManifestCheck, fn
90
- run ProvisionCheck, fn, options
91
- when "deprovision"
92
- id = ARGV.shift || abort("! no id specified; see usage")
93
- run ManifestCheck, fn
94
- run DeprovisionCheck, fn, options.merge(:id => id)
95
- when "sso"
96
- id = ARGV.shift || abort("! no id specified; see usage")
97
- run ManifestCheck, fn
98
- run SsoCheck, fn, options.merge(:id => id)
99
- else
100
- abort "! Unknown test '#{check}'; see usage"
101
- end
102
- when "run"
103
- abort "! missing command to run; see usage" if ARGV.empty?
104
- run ManifestCheck, fn
105
- run AllCheck, fn, options.merge(:args => ARGV)
106
- when "sso"
107
- id = ARGV.shift || abort("! no id specified; see usage")
108
- data = Yajl::Parser.parse(resolve_manifest(fn)).merge(:id => id)
109
- sso = Sso.new(data.merge(options))
110
- puts "Opening #{sso.full_url}"
111
- Launchy.open sso.full_url
112
- else
27
+ begin
28
+ Client.new(ARGV, options).run!
29
+ rescue Client::CommandInvalid
113
30
  abort File.read(__FILE__).split('__END__').last
114
31
  end
115
32
 
@@ -133,6 +50,9 @@ OPTIONS
133
50
  --plan plan-id
134
51
  Use the identified plan when doing provision calls
135
52
 
53
+ --without-sso
54
+ Skip single sign-on authentication when doing provision calls
55
+
136
56
  COMMANDS
137
57
 
138
58
  init Creates a skeleton manifest
@@ -143,6 +63,8 @@ COMMANDS
143
63
 
144
64
  sso <id> Launches the browser on a Heroku session for the specified id
145
65
 
66
+ push Send the manifest to Heroku
67
+
146
68
  TEST TYPES
147
69
 
148
70
  provision
data/kensa.gemspec CHANGED
@@ -1,39 +1,42 @@
1
1
  # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{kensa}
8
- s.version = "0.4.2"
8
+ s.version = "1.0.0.beta1"
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Blake Mizerany", "Pedro Belo", "Adam Wiggins"]
12
- s.date = %q{2010-05-21}
12
+ s.date = %q{2010-07-19}
13
13
  s.default_executable = %q{kensa}
14
14
  s.description = %q{}
15
15
  s.email = %q{pedro@heroku.com}
16
16
  s.executables = ["kensa"]
17
- s.extra_rdoc_files = [
18
- "TODO"
19
- ]
20
17
  s.files = [
21
18
  ".gitignore",
22
19
  "Rakefile",
23
- "TODO",
24
- "a-server.rb",
25
20
  "bin/kensa",
26
21
  "kensa.gemspec",
27
22
  "lib/heroku/kensa.rb",
28
- "server.rb",
23
+ "lib/heroku/kensa/check.rb",
24
+ "lib/heroku/kensa/client.rb",
25
+ "lib/heroku/kensa/http.rb",
26
+ "lib/heroku/kensa/manifest.rb",
27
+ "lib/heroku/kensa/sso.rb",
29
28
  "set-env.sh",
29
+ "test/all_check_test.rb",
30
30
  "test/deprovision_check.rb",
31
31
  "test/helper.rb",
32
32
  "test/manifest_check_test.rb",
33
+ "test/manifest_test.rb",
33
34
  "test/provision_check_test.rb",
34
35
  "test/provision_response_check_test.rb",
35
- "test/resources/test_server.rb",
36
- "test/sso_check_test.rb"
36
+ "test/resources/runner.rb",
37
+ "test/resources/server.rb",
38
+ "test/sso_check_test.rb",
39
+ "test/sso_test.rb"
37
40
  ]
38
41
  s.homepage = %q{http://heroku.com}
39
42
  s.rdoc_options = ["--charset=UTF-8"]
@@ -41,13 +44,17 @@ Gem::Specification.new do |s|
41
44
  s.rubygems_version = %q{1.3.6}
42
45
  s.summary = %q{}
43
46
  s.test_files = [
44
- "test/deprovision_check.rb",
47
+ "test/all_check_test.rb",
48
+ "test/deprovision_check.rb",
45
49
  "test/helper.rb",
46
50
  "test/manifest_check_test.rb",
51
+ "test/manifest_test.rb",
47
52
  "test/provision_check_test.rb",
48
53
  "test/provision_response_check_test.rb",
49
- "test/resources/test_server.rb",
50
- "test/sso_check_test.rb"
54
+ "test/resources/runner.rb",
55
+ "test/resources/server.rb",
56
+ "test/sso_check_test.rb",
57
+ "test/sso_test.rb"
51
58
  ]
52
59
 
53
60
  if s.respond_to? :specification_version then
@@ -57,8 +64,9 @@ Gem::Specification.new do |s|
57
64
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
65
  s.add_development_dependency(%q<turn>, [">= 0"])
59
66
  s.add_development_dependency(%q<contest>, [">= 0"])
67
+ s.add_development_dependency(%q<timecop>, [">= 0.3.5"])
60
68
  s.add_runtime_dependency(%q<sinatra>, ["~> 0.9"])
61
- s.add_runtime_dependency(%q<rest-client>, ["~> 1.2.0"])
69
+ s.add_runtime_dependency(%q<rest-client>, ["~> 1.4.0"])
62
70
  s.add_runtime_dependency(%q<yajl-ruby>, ["~> 0.6"])
63
71
  s.add_runtime_dependency(%q<term-ansicolor>, ["~> 1.0"])
64
72
  s.add_runtime_dependency(%q<launchy>, [">= 0.3.2"])
@@ -66,8 +74,9 @@ Gem::Specification.new do |s|
66
74
  else
67
75
  s.add_dependency(%q<turn>, [">= 0"])
68
76
  s.add_dependency(%q<contest>, [">= 0"])
77
+ s.add_dependency(%q<timecop>, [">= 0.3.5"])
69
78
  s.add_dependency(%q<sinatra>, ["~> 0.9"])
70
- s.add_dependency(%q<rest-client>, ["~> 1.2.0"])
79
+ s.add_dependency(%q<rest-client>, ["~> 1.4.0"])
71
80
  s.add_dependency(%q<yajl-ruby>, ["~> 0.6"])
72
81
  s.add_dependency(%q<term-ansicolor>, ["~> 1.0"])
73
82
  s.add_dependency(%q<launchy>, [">= 0.3.2"])
@@ -76,8 +85,9 @@ Gem::Specification.new do |s|
76
85
  else
77
86
  s.add_dependency(%q<turn>, [">= 0"])
78
87
  s.add_dependency(%q<contest>, [">= 0"])
88
+ s.add_dependency(%q<timecop>, [">= 0.3.5"])
79
89
  s.add_dependency(%q<sinatra>, ["~> 0.9"])
80
- s.add_dependency(%q<rest-client>, ["~> 1.2.0"])
90
+ s.add_dependency(%q<rest-client>, ["~> 1.4.0"])
81
91
  s.add_dependency(%q<yajl-ruby>, ["~> 0.6"])
82
92
  s.add_dependency(%q<term-ansicolor>, ["~> 1.0"])
83
93
  s.add_dependency(%q<launchy>, [">= 0.3.2"])
data/lib/heroku/kensa.rb CHANGED
@@ -1,589 +1,4 @@
1
- require 'yajl'
2
- require 'restclient'
3
- require 'socket'
4
- require 'timeout'
5
- require 'uri'
6
- require 'mechanize'
7
-
8
- module Heroku
9
-
10
- module Sensei
11
-
12
- module Manifest
13
-
14
- def self.init(filename)
15
- open(filename, 'w') {|f| f << skeleton_str }
16
- end
17
-
18
- def self.skeleton
19
- Yajl::Parser.parse(skeleton_str)
20
- end
21
-
22
- def self.skeleton_str
23
- return <<EOJSON
24
- {
25
- "id": "myaddon",
26
- "name": "My Addon",
27
- "plans": [
28
- {
29
- "id": "basic",
30
- "name": "Basic",
31
- "price": "0",
32
- "price_unit": "month"
33
- }
34
- ],
35
- "api": {
36
- "config_vars": [
37
- "MYADDON_URL"
38
- ],
39
- "production": "https://yourapp.com/",
40
- "test": "http://localhost:4567/",
41
- "username": "heroku",
42
- "password": "#{generate_password(16)}",
43
- "sso_salt": "#{generate_password(16)}"
44
- }
45
- }
46
- EOJSON
47
- end
48
-
49
- PasswordChars = chars = ['a'..'z', 'A'..'Z', '0'..'9'].map { |r| r.to_a }.flatten
50
- def self.generate_password(size=16)
51
- Array.new(size) { PasswordChars[rand(PasswordChars.size)] }.join
52
- end
53
-
54
- end
55
-
56
-
57
- class NilScreen
58
-
59
- def test(msg)
60
- end
61
-
62
- def check(msg)
63
- end
64
-
65
- def error(msg)
66
- end
67
-
68
- def result(status)
69
- end
70
-
71
- end
72
-
73
-
74
- class Check
75
- attr_accessor :screen, :data
76
-
77
- class CheckError < StandardError ; end
78
-
79
- def initialize(data, screen=NilScreen.new)
80
- @data = data
81
- @screen = screen
82
- end
83
-
84
- def test(msg)
85
- screen.test msg
86
- end
87
-
88
- def check(msg)
89
- screen.check(msg)
90
- if yield
91
- screen.result(true)
92
- else
93
- raise CheckError
94
- end
95
- end
96
-
97
- def run(klass, data)
98
- c = klass.new(data, screen)
99
- instance_eval(&c)
100
- end
101
-
102
- def error(msg)
103
- raise CheckError, msg
104
- end
105
-
106
- def call
107
- call!
108
- true
109
- rescue CheckError => boom
110
- screen.result(false)
111
- screen.error boom.message if boom.message != boom.class.name
112
-
113
- false
114
- end
115
-
116
- def to_proc
117
- me = self
118
- Proc.new { me.call! }
119
- end
120
-
121
- end
122
-
123
-
124
- class ManifestCheck < Check
125
-
126
- ValidPriceUnits = %w[month dyno_hour]
127
-
128
- def call!
129
- test "manifest id key"
130
- check "if exists" do
131
- data.has_key?("id")
132
- end
133
- check "is a string" do
134
- data["id"].is_a?(String)
135
- end
136
- check "is not blank" do
137
- !data["id"].empty?
138
- end
139
-
140
- test "manifest name key"
141
- check "if exists" do
142
- data.has_key?("name")
143
- end
144
- check "is a string" do
145
- data["name"].is_a?(String)
146
- end
147
- check "is not blank" do
148
- !data["name"].empty?
149
- end
150
-
151
- test "manifest api key"
152
- check "if exists" do
153
- data.has_key?("api")
154
- end
155
- check "is a hash" do
156
- data["api"].is_a?(Hash)
157
- end
158
- check "contains username" do
159
- data["api"].has_key?("username") && data["api"]["username"] != ""
160
- end
161
- check "contains password" do
162
- data["api"].has_key?("password") && data["api"]["password"] != ""
163
- end
164
- check "contains test url" do
165
- data["api"].has_key?("test")
166
- end
167
- check "contains production url" do
168
- data["api"].has_key?("production")
169
- end
170
- check "production url uses SSL" do
171
- data["api"]["production"] =~ /^https:/
172
- end
173
- check "contains config_vars array" do
174
- data["api"].has_key?("config_vars") && data["api"]["config_vars"].is_a?(Array)
175
- end
176
- check "containst at least one config var" do
177
- !data["api"]["config_vars"].empty?
178
- end
179
- check "all config vars are uppercase strings" do
180
- data["api"]["config_vars"].each do |k, v|
181
- if k =~ /^[A-Z][0-9A-Z_]+$/
182
- true
183
- else
184
- error "#{k.inspect} is not a valid ENV key"
185
- end
186
- end
187
- end
188
- check "all config vars are prefixed with the addon id" do
189
- data["api"]["config_vars"].each do |k|
190
- if k =~ /^#{data['id'].upcase}_/
191
- true
192
- else
193
- error "#{k} is not a valid ENV key - must be prefixed with #{data['id'].upcase}_"
194
- end
195
- end
196
- end
197
-
198
- test "plans"
199
- check "key must exist" do
200
- data.has_key?("plans")
201
- end
202
- check "is an array" do
203
- data["plans"].is_a?(Array)
204
- end
205
- check "contains at least one plan" do
206
- !data["plans"].empty?
207
- end
208
- check "all plans are a hash" do
209
- data["plans"].all? {|plan| plan.is_a?(Hash) }
210
- end
211
- check "all plans must have an id" do
212
- data["plans"].all? {|plan| plan.has_key?("id") }
213
- end
214
- check "all plans have an unique id" do
215
- ids = data["plans"].map {|plan| plan["id"] }
216
- ids.size == ids.uniq.size
217
- end
218
- check "all plans have a name" do
219
- data["plans"].all? {|plan| plan.has_key?("name") }
220
- end
221
- check "all plans have a unique name" do
222
- names = data["plans"].map {|plan| plan["name"] }
223
- names.size == names.uniq.size
224
- end
225
-
226
- data["plans"].each do |plan|
227
- check "#{plan["name"]} has a valid price" do
228
- if plan["price"] !~ /^\d+$/
229
- error "expected an integer"
230
- else
231
- true
232
- end
233
- end
234
-
235
- check "#{plan["name"]} has a valid price_unit" do
236
- if ValidPriceUnits.include?(plan["price_unit"])
237
- true
238
- else
239
- error "expected #{ValidPriceUnits.join(" or ")} but got #{plan["price_unit"].inspect}"
240
- end
241
- end
242
- end
243
- end
244
-
245
- end
246
-
247
-
248
- class ProvisionResponseCheck < Check
249
-
250
- def call!
251
- response = data[:provision_response]
252
- test "response"
253
- check "contains an id" do
254
- response.is_a?(Hash) && response.has_key?("id")
255
- end
256
-
257
- if response.has_key?("config")
258
- test "config data"
259
- check "is a hash" do
260
- response["config"].is_a?(Hash)
261
- end
262
-
263
- check "all config keys were previously defined in the manifest" do
264
- response["config"].keys.each do |key|
265
- error "#{key} is not in the manifest" unless data["api"]["config_vars"].include?(key)
266
- end
267
- true
268
- end
269
-
270
- check "all config values are strings" do
271
- response["config"].each do |k, v|
272
- if v.is_a?(String)
273
- true
274
- else
275
- error "the key #{k} doesn't contain a string (#{v.inspect})"
276
- end
277
- end
278
- end
279
-
280
- check "URL configs vars" do
281
- response["config"].each do |key, value|
282
- next unless key =~ /_URL$/
283
- begin
284
- uri = URI.parse(value)
285
- error "#{value} is not a valid URI - missing host" unless uri.host
286
- error "#{value} is not a valid URI - missing scheme" unless uri.scheme
287
- error "#{value} is not a valid URI - pointing to localhost" if @data[:env] == 'production' && uri.host == 'localhost'
288
- rescue URI::Error
289
- error "#{value} is not a valid URI"
290
- end
291
- end
292
- end
293
-
294
- end
295
- end
296
-
297
- end
298
-
299
-
300
- module HTTP
301
-
302
- def get(path, params={})
303
- path = "#{path}?" + params.map { |k, v| "#{k}=#{v}" }.join("&") unless params.empty?
304
- request(:get, [], path)
305
- end
306
-
307
- def post(credentials, path, payload=nil)
308
- request(:post, credentials, path, payload)
309
- end
310
-
311
- def delete(credentials, path, payload=nil)
312
- request(:delete, credentials, path, payload)
313
- end
314
-
315
- def request(meth, credentials, path, payload=nil)
316
- code = nil
317
- body = nil
318
-
319
- begin
320
- args = [
321
- (Yajl::Encoder.encode(payload) if payload),
322
- {
323
- :accept => "application/json",
324
- :content_type => "application/json"
325
- }
326
- ].compact
327
-
328
- user, pass = credentials
329
- body = RestClient::Resource.new(url, user, pass)[path].send(
330
- meth,
331
- *args
332
- ).to_s
333
-
334
- code = 200
335
- rescue RestClient::ExceptionWithResponse => boom
336
- code = boom.http_code
337
- body = boom.http_body
338
- rescue Errno::ECONNREFUSED
339
- code = -1
340
- body = nil
341
- end
342
-
343
- [code, body]
344
- end
345
-
346
- end
347
-
348
- class ApiCheck < Check
349
- def url
350
- env = data[:env] || 'test'
351
- data["api"][env].chomp("/")
352
- end
353
-
354
- def credentials
355
- %w( username password ).map { |attr| data["api"][attr] }
356
- end
357
- end
358
-
359
- class ProvisionCheck < ApiCheck
360
- include HTTP
361
-
362
- READLEN = 1024 * 10
363
- APPID = "app#{rand(10000)}@kensa.heroku.com"
364
- APPNAME = "myapp"
365
-
366
- def call!
367
- json = nil
368
- response = nil
369
-
370
- code = nil
371
- json = nil
372
- path = "/heroku/resources"
373
- callback = "http://localhost:7779/callback/999"
374
- reader, writer = nil
375
-
376
- payload = {
377
- :heroku_id => APPID,
378
- :plan => @data[:plan] || @data['plans'].first['id'],
379
- :callback_url => callback
380
- }
381
-
382
- if data[:async]
383
- reader, writer = IO.pipe
384
- end
385
-
386
- test "POST /heroku/resources"
387
- check "response" do
388
- if data[:async]
389
- child = fork do
390
- Timeout.timeout(10) do
391
- reader.close
392
- server = TCPServer.open(7779)
393
- client = server.accept
394
- writer.write(client.readpartial(READLEN))
395
- client.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
396
- client.close
397
- writer.close
398
- end
399
- end
400
- sleep(1)
401
- end
402
-
403
- code, json = post(credentials, path, payload)
404
-
405
- if code == 200
406
- # noop
407
- elsif code == -1
408
- error("unable to connect to #{url}")
409
- else
410
- error("expected 200, got #{code}")
411
- end
412
-
413
- true
414
- end
415
-
416
- if data[:async]
417
- check "async response to PUT #{callback}" do
418
- out = reader.readpartial(READLEN)
419
- _, json = out.split("\r\n\r\n")
420
- end
421
- end
422
-
423
- check "valid JSON" do
424
- begin
425
- response = Yajl::Parser.parse(json)
426
- rescue Yajl::ParseError => boom
427
- error boom.message
428
- end
429
- true
430
- end
431
-
432
- check "authentication" do
433
- wrong_credentials = ['wrong', 'secret']
434
- code, _ = post(wrong_credentials, path, payload)
435
- error("expected 401, got #{code}") if code != 401
436
- true
437
- end
438
-
439
- data[:provision_response] = response
440
-
441
- run ProvisionResponseCheck, data
442
- end
443
-
444
- ensure
445
- reader.close rescue nil
446
- writer.close rescue nil
447
- end
448
-
449
-
450
- class DeprovisionCheck < ApiCheck
451
- include HTTP
452
-
453
- def call!
454
- id = data[:id]
455
- raise ArgumentError, "No id specified" if id.nil?
456
-
457
- path = "/heroku/resources/#{CGI::escape(id.to_s)}"
458
-
459
- test "DELETE #{path}"
460
- check "response" do
461
- code, _ = delete(credentials, path, nil)
462
- if code == 200
463
- true
464
- elsif code == -1
465
- error("unable to connect to #{url}")
466
- else
467
- error("expected 200, got #{code}")
468
- end
469
- end
470
-
471
- check "authentication" do
472
- wrong_credentials = ['wrong', 'secret']
473
- code, _ = delete(wrong_credentials, path, nil)
474
- error("expected 401, got #{code}") if code != 401
475
- true
476
- end
477
-
478
- end
479
-
480
- end
481
-
482
-
483
- class Sso
484
- attr_accessor :id, :url
485
-
486
- def initialize(data)
487
- @id = data[:id]
488
- @salt = data['api']['sso_salt']
489
- env = data[:env] || 'test'
490
- @url = data["api"][env].chomp('/')
491
- end
492
-
493
- def path
494
- "/heroku/resources/#{id}"
495
- end
496
-
497
- def full_url
498
- t = Time.now.to_i
499
- "#{url}#{path}?token=#{make_token(t)}&timestamp=#{t}"
500
- end
501
-
502
- def make_token(t)
503
- Digest::SHA1.hexdigest([@id, @salt, t].join(':'))
504
- end
505
- end
506
-
507
-
508
- class SsoCheck < ApiCheck
509
- include HTTP
510
-
511
- def mechanize_get url
512
- agent = Mechanize.new
513
- page = agent.get(url)
514
- return page, 200
515
- rescue Mechanize::ResponseCodeError => error
516
- return nil, error.response_code.to_i
517
- rescue Errno::ECONNREFUSED
518
- error("connection refused to #{url}")
519
- end
520
-
521
- def call!
522
- sso = Sso.new(data)
523
- t = Time.now.to_i
524
-
525
- test "GET #{sso.path}"
526
- check "validates token" do
527
- page, respcode = mechanize_get sso.url + sso.path + "?token=invalid&timestamp=#{t}"
528
- error("expected 403, got 200") unless respcode == 403
529
- true
530
- end
531
-
532
- check "validates timestamp" do
533
- prev = (Time.now - 60*6).to_i
534
- page, respcode = mechanize_get sso.url + sso.path + "?token=#{sso.make_token(prev)}&timestamp=#{prev}"
535
- error("expected 403, got 200") unless respcode == 403
536
- true
537
- end
538
-
539
- check "logs in" do
540
- page, respcode = mechanize_get sso.url + sso.path + "?token=#{sso.make_token(t)}&timestamp=#{t}"
541
- error("expected 200, got #{respcode}") unless respcode == 200
542
- true
543
- end
544
- end
545
- end
546
-
547
-
548
- ##
549
- # On Testing:
550
- # I've opted to not write tests for this
551
- # due to the simple nature it's currently in.
552
- # If this becomes more complex in even the
553
- # least amount, find me (blake) and I'll
554
- # help get tests in.
555
- class AllCheck < Check
556
-
557
- def call!
558
- args = data[:args]
559
- run ProvisionCheck, data
560
-
561
- response = data[:provision_response]
562
- data.merge!(:id => response["id"])
563
- config = response["config"] || Hash.new
564
-
565
- if args
566
- screen.message "\n\n"
567
- screen.message "Starting #{args.first}..."
568
- screen.message ""
569
-
570
- run_in_env(config) { system(*args) }
571
-
572
- screen.message ""
573
- screen.message "End of #{args.first}"
574
- end
575
-
576
- run DeprovisionCheck, data
577
- end
578
-
579
- def run_in_env(env)
580
- env.each {|key, value| ENV[key] = value }
581
- yield
582
- env.keys.each {|key| ENV.delete(key) }
583
- end
584
-
585
- end
586
-
587
- end
588
-
589
- end
1
+ require 'heroku/kensa/http'
2
+ require 'heroku/kensa/manifest'
3
+ require 'heroku/kensa/check'
4
+ require 'heroku/kensa/sso'