kensa 1.2.0rc7 → 1.2.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.
- data/.gitignore +1 -1
- data/Gemfile +5 -4
- data/Gemfile.lock +21 -27
- data/README.md +1 -1
- data/Rakefile +18 -10
- data/bin/kensa +10 -30
- data/kensa.gemspec +27 -20
- data/lib/heroku/kensa.rb +7 -9
- data/lib/heroku/kensa/check.rb +499 -0
- data/lib/heroku/kensa/client.rb +67 -89
- data/lib/heroku/kensa/git.rb +39 -0
- data/lib/heroku/kensa/manifest.rb +41 -8
- data/lib/heroku/kensa/screen.rb +37 -0
- data/lib/heroku/kensa/sso.rb +22 -22
- data/lib/heroku/kensa/version.rb +1 -2
- data/test/all_check_test.rb +25 -0
- data/test/create_test.rb +40 -6
- data/test/deprovision_check_test.rb +39 -0
- data/test/helper.rb +74 -11
- data/test/init_test.rb +54 -0
- data/test/manifest_check_test.rb +94 -0
- data/test/manifest_test.rb +37 -33
- data/test/plan_change_check_test.rb +31 -0
- data/test/provision_check_test.rb +51 -0
- data/test/provision_response_check_test.rb +81 -0
- data/test/resources/runner.rb +1 -0
- data/test/resources/server.rb +227 -0
- data/test/sso_check_test.rb +58 -0
- data/test/sso_test.rb +113 -53
- metadata +97 -91
- data/test.rb +0 -1
- data/test/deprovision_test.rb +0 -30
- data/test/lib/dependencies.rb +0 -12
- data/test/lib/formatter.rb +0 -84
- data/test/lib/http.rb +0 -60
- data/test/lib/response.rb +0 -12
- data/test/manifest_generation_test.rb +0 -32
- data/test/plan_change_test.rb +0 -30
- data/test/provision_test.rb +0 -84
- data/test/resources/provider_server.rb +0 -81
- data/test/resources/views/index.haml +0 -6
- data/test/sso_launch_test.rb +0 -130
data/lib/heroku/kensa/client.rb
CHANGED
@@ -1,64 +1,17 @@
|
|
1
1
|
require 'restclient'
|
2
2
|
require 'term/ansicolor'
|
3
3
|
require 'launchy'
|
4
|
+
require 'optparse'
|
4
5
|
|
5
6
|
module Heroku
|
6
|
-
class Git
|
7
|
-
class << self
|
8
|
-
def verify_create(app_name, template)
|
9
|
-
raise CommandInvalid.new("template #{clone_url(template)} does not exist") unless template_exists?(template)
|
10
|
-
raise CommandInvalid.new("Need git to clone repository") unless git_installed?
|
11
|
-
end
|
12
|
-
|
13
|
-
def template_exists?(template)
|
14
|
-
true
|
15
|
-
end
|
16
|
-
|
17
|
-
def git_installed?
|
18
|
-
`git` rescue false
|
19
|
-
end
|
20
|
-
|
21
|
-
def clone(app_name, template)
|
22
|
-
verify_create(app_name, template)
|
23
|
-
cmd = "git clone #{clone_url(template)} #{app_name}"
|
24
|
-
puts cmd
|
25
|
-
`#{cmd}`
|
26
|
-
raise Exception.new("couldn't clone the repository from #{clone_url(template)}") unless File.directory?("#{app_name}")
|
27
|
-
puts "Created #{app_name} from #{template} template"
|
28
|
-
end
|
29
|
-
|
30
|
-
def clone_url(name)
|
31
|
-
prefix = ENV['REPO_PREFIX'] || "heroku"
|
32
|
-
if name.include? "://" #its a full url
|
33
|
-
return name
|
34
|
-
elsif name.include? "/" #its a non-heroku repo
|
35
|
-
name = "#{prefix}/#{name}"
|
36
|
-
else #its one of ours
|
37
|
-
name = "#{prefix}/kensa-create-#{name}"
|
38
|
-
end
|
39
|
-
|
40
|
-
"git://github.com/#{name}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
7
|
module Kensa
|
46
|
-
class UserError < RuntimeError; end
|
47
|
-
|
48
8
|
class Client
|
49
|
-
def initialize(args, options)
|
9
|
+
def initialize(args, options = {})
|
50
10
|
@args = args
|
51
|
-
@options = options
|
11
|
+
@options = OptParser.parse(args).merge(options)
|
52
12
|
end
|
53
13
|
|
54
|
-
def filename
|
55
|
-
@options[:filename]
|
56
|
-
end
|
57
|
-
|
58
|
-
#you fucked up
|
59
14
|
class CommandInvalid < Exception; end
|
60
|
-
#we fucked up
|
61
|
-
class CommandFailed < Exception; end
|
62
15
|
|
63
16
|
def run!
|
64
17
|
command = @args.shift || @options[:command]
|
@@ -67,63 +20,44 @@ module Heroku
|
|
67
20
|
end
|
68
21
|
|
69
22
|
def init
|
70
|
-
Manifest.new(
|
71
|
-
|
23
|
+
Manifest.new(@options).write
|
24
|
+
screen.message "Initialized new addon manifest in #{filename}\n"
|
25
|
+
if @options[:foreman]
|
26
|
+
screen.message "Initialized new .env file for foreman\n"
|
27
|
+
end
|
72
28
|
end
|
73
29
|
|
74
30
|
def create
|
75
31
|
app_name = @args.shift
|
76
32
|
template = @options[:template]
|
77
|
-
raise CommandInvalid.new("Need
|
78
|
-
raise CommandInvalid.new("Need
|
79
|
-
|
33
|
+
raise CommandInvalid.new("Need to supply an application name") unless app_name
|
34
|
+
raise CommandInvalid.new("Need to supply a template") unless template
|
80
35
|
begin
|
81
|
-
Git.clone(app_name, template)
|
36
|
+
Git.clone(app_name, template) and screen.message "Created #{app_name} from #{template} template\n"
|
37
|
+
Dir.chdir(app_name)
|
38
|
+
@options[:foreman] = true
|
39
|
+
init
|
82
40
|
rescue Exception => e
|
83
|
-
raise
|
41
|
+
raise CommandInvalid.new("error cloning #{Git.clone_url(template)} into #{app_name}")
|
84
42
|
end
|
85
|
-
|
86
|
-
Dir.chdir("./#{app_name}")
|
87
|
-
`./after_clone #{app_name}` if File.exist?('./after_clone')
|
88
43
|
end
|
89
44
|
|
90
|
-
|
91
45
|
def test
|
92
46
|
case check = @args.shift
|
93
47
|
when "manifest"
|
94
|
-
|
95
|
-
require 'test/unit/ui/console/testrunner'
|
96
|
-
$manifest = Yajl::Parser.parse(resolve_manifest)
|
97
|
-
Test::Unit.run = true
|
98
|
-
Test::Unit::UI::Console::TestRunner.new(ManifestTest.suite).start
|
48
|
+
run_check ManifestCheck
|
99
49
|
when "provision"
|
100
|
-
|
101
|
-
require 'test/unit/ui/console/testrunner'
|
102
|
-
$manifest = Yajl::Parser.parse(resolve_manifest)
|
103
|
-
Test::Unit.run = true
|
104
|
-
Test::Unit::UI::Console::TestRunner.new(ProvisionTest.suite).start
|
50
|
+
run_check ManifestCheck, ProvisionCheck
|
105
51
|
when "deprovision"
|
106
52
|
id = @args.shift || abort("! no id specified; see usage")
|
107
|
-
|
108
|
-
require 'test/unit/ui/console/testrunner'
|
109
|
-
$manifest = Yajl::Parser.parse(resolve_manifest).merge("user_id" => id)
|
110
|
-
Test::Unit.run = true
|
111
|
-
Test::Unit::UI::Console::TestRunner.new(DeprovisionTest.suite).start
|
53
|
+
run_check ManifestCheck, DeprovisionCheck, :id => id
|
112
54
|
when "planchange"
|
113
55
|
id = @args.shift || abort("! no id specified; see usage")
|
114
56
|
plan = @args.shift || abort("! no plan specified; see usage")
|
115
|
-
|
116
|
-
require 'test/unit/ui/console/testrunner'
|
117
|
-
$manifest = Yajl::Parser.parse(resolve_manifest).merge("user_id" => id)
|
118
|
-
Test::Unit.run = true
|
119
|
-
Test::Unit::UI::Console::TestRunner.new(PlanChangeTest.suite).start
|
57
|
+
run_check ManifestCheck, PlanChangeCheck, :id => id, :plan => plan
|
120
58
|
when "sso"
|
121
59
|
id = @args.shift || abort("! no id specified; see usage")
|
122
|
-
|
123
|
-
require 'test/unit/ui/console/testrunner'
|
124
|
-
$manifest = Yajl::Parser.parse(resolve_manifest).merge("user_id" => id)
|
125
|
-
Test::Unit.run = true
|
126
|
-
Test::Unit::UI::Console::TestRunner.new(SsoTest.suite).start
|
60
|
+
run_check ManifestCheck, SsoCheck, :id => id
|
127
61
|
else
|
128
62
|
abort "! Unknown test '#{check}'; see usage"
|
129
63
|
end
|
@@ -177,12 +111,20 @@ module Heroku
|
|
177
111
|
end
|
178
112
|
|
179
113
|
def version
|
180
|
-
puts "Kensa #{
|
114
|
+
puts "Kensa #{VERSION}"
|
181
115
|
end
|
182
116
|
|
183
117
|
private
|
118
|
+
def filename
|
119
|
+
@options[:filename]
|
120
|
+
end
|
121
|
+
|
122
|
+
def screen
|
123
|
+
@screen ||= @options[:silent] ? NilScreen.new : Screen.new
|
124
|
+
end
|
125
|
+
|
184
126
|
def headers
|
185
|
-
{ :accept => :json, "X-Kensa-Version" => "1", "User-Agent" => "kensa/#{
|
127
|
+
{ :accept => :json, "X-Kensa-Version" => "1", "User-Agent" => "kensa/#{VERSION}" }
|
186
128
|
end
|
187
129
|
|
188
130
|
def heroku_host
|
@@ -206,7 +148,6 @@ module Heroku
|
|
206
148
|
options = args.pop if args.last.is_a?(Hash)
|
207
149
|
|
208
150
|
args.each do |klass|
|
209
|
-
screen = Screen.new
|
210
151
|
data = Yajl::Parser.parse(resolve_manifest)
|
211
152
|
check = klass.new(data.merge(@options.merge(options)), screen)
|
212
153
|
result = check.call
|
@@ -298,6 +239,43 @@ module Heroku
|
|
298
239
|
$stdout.puts "done."
|
299
240
|
end
|
300
241
|
end
|
242
|
+
|
243
|
+
|
244
|
+
class OptParser
|
245
|
+
def self.defaults
|
246
|
+
{
|
247
|
+
:filename => 'addon-manifest.json',
|
248
|
+
:env => "test",
|
249
|
+
:async => false,
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
def self.parse(args)
|
254
|
+
self.defaults.tap do |options|
|
255
|
+
OptionParser.new do |o|
|
256
|
+
o.on("-f file", "--file") { |filename| options[:filename] = filename }
|
257
|
+
o.on("--async") { options[:async] = true }
|
258
|
+
o.on("--production") { options[:env] = "production" }
|
259
|
+
o.on("--without-sso") { options[:sso] = false }
|
260
|
+
o.on("-h", "--help") { command = "help" }
|
261
|
+
o.on("-p plan", "--plan") { |plan| options[:plan] = plan }
|
262
|
+
o.on("-v", "--version") { options[:command] = "version" }
|
263
|
+
o.on("-s sso", "--sso") { |method| options[:method] = method }
|
264
|
+
o.on("--foreman") { options[:foreman] = true }
|
265
|
+
o.on("-t name", "--template") do |template|
|
266
|
+
options[:template] = template
|
267
|
+
end
|
268
|
+
|
269
|
+
begin
|
270
|
+
o.parse!(args)
|
271
|
+
rescue OptionParser::InvalidOption
|
272
|
+
#skip over invalid options
|
273
|
+
retry
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
301
279
|
end
|
302
280
|
end
|
303
281
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Kensa
|
3
|
+
class Git
|
4
|
+
class << self
|
5
|
+
def verify_create(app_name, template)
|
6
|
+
raise CommandInvalid.new("Need git to clone repository") unless git_installed?
|
7
|
+
end
|
8
|
+
|
9
|
+
def git_installed?
|
10
|
+
`git` rescue false
|
11
|
+
end
|
12
|
+
|
13
|
+
def clone(app_name, template)
|
14
|
+
verify_create(app_name, template)
|
15
|
+
run("git clone #{clone_url(template)} #{app_name}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(cmd)
|
19
|
+
puts cmd
|
20
|
+
system(cmd)
|
21
|
+
end
|
22
|
+
|
23
|
+
def heroku_prefix
|
24
|
+
ENV["REPO_PREFIX"] || "heroku/kensa-create-"
|
25
|
+
end
|
26
|
+
|
27
|
+
def clone_url(name)
|
28
|
+
if name.include? "://" #its a full url not on github
|
29
|
+
return name
|
30
|
+
elsif !name.include? "/" #its one of ours
|
31
|
+
name = heroku_prefix + name
|
32
|
+
end
|
33
|
+
|
34
|
+
"git://github.com/#{name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -2,28 +2,59 @@ module Heroku
|
|
2
2
|
module Kensa
|
3
3
|
class Manifest
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
5
|
+
def initialize(options = {})
|
6
|
+
@method = options.fetch(:method, 'post').to_sym
|
7
|
+
@filename = options.fetch(:filename, 'addon-manifest.json')
|
8
|
+
@options = options
|
7
9
|
end
|
8
10
|
|
9
11
|
def skeleton_json
|
12
|
+
@password = generate_password(16)
|
13
|
+
@port = @options[:foreman] ? 5000 : 4567
|
14
|
+
(@method == :get) ? get_skeleton : post_skeleton
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_skeleton
|
18
|
+
<<-JSON
|
19
|
+
{
|
20
|
+
"id": "myaddon",
|
21
|
+
"api": {
|
22
|
+
"config_vars": [ "MYADDON_URL" ],
|
23
|
+
"password": "#{@password}",#{ sso_key }
|
24
|
+
"production": "https://yourapp.com/",
|
25
|
+
"test": "http://localhost:#{@port}/"
|
26
|
+
}
|
27
|
+
}
|
28
|
+
JSON
|
29
|
+
end
|
30
|
+
|
31
|
+
def post_skeleton
|
10
32
|
<<-JSON
|
11
33
|
{
|
12
34
|
"id": "myaddon",
|
13
35
|
"api": {
|
14
|
-
"config_vars": [ "
|
15
|
-
"password": "#{
|
36
|
+
"config_vars": [ "MYADDON_URL" ],
|
37
|
+
"password": "#{@password}",#{ sso_key }
|
16
38
|
"production": {
|
17
39
|
"base_url": "https://yourapp.com/heroku/resources",
|
18
40
|
"sso_url": "https://yourapp.com/sso/login"
|
19
41
|
},
|
20
42
|
"test": {
|
21
|
-
"base_url": "http://localhost
|
22
|
-
"sso_url": "http://localhost
|
43
|
+
"base_url": "http://localhost:#{@port}/heroku/resources",
|
44
|
+
"sso_url": "http://localhost:#{@port}/sso/login"
|
23
45
|
}
|
24
46
|
}
|
25
47
|
}
|
26
48
|
JSON
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def foreman
|
53
|
+
<<-ENV
|
54
|
+
SSO_SALT=#{@sso_salt}
|
55
|
+
HEROKU_USERNAME=myaddon
|
56
|
+
HEROKU_PASSWORD=#{@password}
|
57
|
+
ENV
|
27
58
|
end
|
28
59
|
|
29
60
|
def skeleton
|
@@ -31,14 +62,16 @@ JSON
|
|
31
62
|
end
|
32
63
|
|
33
64
|
def write
|
34
|
-
open(@filename, 'w') { |f| f << skeleton_json }
|
65
|
+
File.open(@filename, 'w') { |f| f << skeleton_json }
|
66
|
+
File.open('.env', 'w') { |f| f << foreman } if @options[:foreman]
|
35
67
|
end
|
36
68
|
|
37
69
|
private
|
38
70
|
|
39
71
|
def sso_key
|
72
|
+
@sso_salt = generate_password(16)
|
40
73
|
unless @options[:sso] === false
|
41
|
-
%{\n "sso_salt": #{
|
74
|
+
%{\n "sso_salt": "#{@sso_salt}",}
|
42
75
|
end
|
43
76
|
end
|
44
77
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Heroku
|
2
|
+
module Kensa
|
3
|
+
|
4
|
+
class NilScreen
|
5
|
+
def test(msg)
|
6
|
+
end
|
7
|
+
|
8
|
+
def check(msg)
|
9
|
+
end
|
10
|
+
|
11
|
+
def error(msg)
|
12
|
+
end
|
13
|
+
|
14
|
+
def result(status)
|
15
|
+
end
|
16
|
+
|
17
|
+
def message(msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class IOScreen
|
22
|
+
attr_accessor :output
|
23
|
+
|
24
|
+
def initialize(io)
|
25
|
+
@output = io
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@output.closed_read? ? '' : @output.tap{|o| o.rewind }.read
|
30
|
+
end
|
31
|
+
|
32
|
+
[:test, :check, :error, :result, :message].each do |method|
|
33
|
+
eval %{ def #{method}(*args)\n @output.puts *args\n end }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/heroku/kensa/sso.rb
CHANGED
@@ -1,43 +1,40 @@
|
|
1
1
|
require 'restclient'
|
2
|
+
require 'uri'
|
2
3
|
|
3
4
|
module Heroku
|
4
5
|
module Kensa
|
5
6
|
class Sso
|
6
|
-
attr_accessor :id, :url, :proxy_port, :timestamp, :token
|
7
|
+
attr_accessor :id, :url, :proxy_port, :timestamp, :token
|
7
8
|
|
8
9
|
def initialize(data)
|
9
10
|
@id = data[:id]
|
10
11
|
@salt = data['api']['sso_salt']
|
11
12
|
|
12
13
|
env = data.fetch :env, 'test'
|
13
|
-
if data[
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@use_post = true
|
14
|
+
if @url = data['api'][env]['sso_url']
|
15
|
+
@use_post = true
|
16
|
+
@proxy_port = find_available_port
|
17
17
|
else
|
18
18
|
@url = data["api"][env].chomp('/')
|
19
|
-
|
20
|
-
end
|
21
|
-
@proxy_port = find_available_port
|
19
|
+
end
|
22
20
|
@timestamp = Time.now.to_i
|
23
21
|
@token = make_token(@timestamp)
|
24
22
|
end
|
25
23
|
|
26
24
|
def path
|
27
|
-
|
28
|
-
|
29
|
-
self.sso_url
|
25
|
+
if self.POST?
|
26
|
+
URI.parse(url).path
|
30
27
|
else
|
31
28
|
"/heroku/resources/#{id}"
|
32
29
|
end
|
33
30
|
end
|
34
31
|
|
35
|
-
def
|
32
|
+
def POST?
|
36
33
|
@use_post
|
37
34
|
end
|
38
35
|
|
39
36
|
def sso_url
|
40
|
-
if self.
|
37
|
+
if self.POST?
|
41
38
|
"http://localhost:#{@proxy_port}/"
|
42
39
|
else
|
43
40
|
full_url
|
@@ -50,13 +47,13 @@ module Heroku
|
|
50
47
|
alias get_url full_url
|
51
48
|
|
52
49
|
def post_url
|
53
|
-
|
50
|
+
url
|
54
51
|
end
|
55
52
|
|
56
53
|
def timestamp=(other)
|
57
54
|
@timestamp = other
|
58
55
|
@token = make_token(@timestamp)
|
59
|
-
end
|
56
|
+
end
|
60
57
|
|
61
58
|
def make_token(t)
|
62
59
|
Digest::SHA1.hexdigest([@id, @salt, t].join(':'))
|
@@ -64,7 +61,7 @@ module Heroku
|
|
64
61
|
|
65
62
|
def querystring
|
66
63
|
return '' unless @salt
|
67
|
-
'?' + query_data
|
64
|
+
'?' + query_data
|
68
65
|
end
|
69
66
|
|
70
67
|
def query_data
|
@@ -72,10 +69,13 @@ module Heroku
|
|
72
69
|
end
|
73
70
|
|
74
71
|
def query_params
|
75
|
-
{ 'token'
|
72
|
+
{ 'token' => @token,
|
76
73
|
'timestamp' => @timestamp.to_s,
|
77
|
-
'nav-data'
|
78
|
-
'
|
74
|
+
'nav-data' => sample_nav_data,
|
75
|
+
'email' => 'username@example.com'
|
76
|
+
}.tap do |params|
|
77
|
+
params.merge!('id' => @id) if self.POST?
|
78
|
+
end
|
79
79
|
end
|
80
80
|
|
81
81
|
def sample_nav_data
|
@@ -97,7 +97,7 @@ module Heroku
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def message
|
100
|
-
if self.
|
100
|
+
if self.POST?
|
101
101
|
"POSTing #{query_data} to #{post_url} via proxy on port #{@proxy_port}"
|
102
102
|
else
|
103
103
|
"Opening #{full_url}"
|
@@ -117,13 +117,13 @@ module Heroku
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def run_proxy
|
120
|
-
return unless self.
|
120
|
+
return unless self.POST?
|
121
121
|
server = PostProxy.new self
|
122
122
|
@proxy = server
|
123
123
|
|
124
124
|
trap("INT") { server.stop }
|
125
125
|
pid = fork do
|
126
|
-
server.start
|
126
|
+
server.start
|
127
127
|
end
|
128
128
|
at_exit { server.stop; Process.waitpid pid }
|
129
129
|
end
|