kensa 0.4.2 → 1.0.0.beta1

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