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.
@@ -0,0 +1,136 @@
1
+ require 'restclient'
2
+ require 'term/ansicolor'
3
+ require 'launchy'
4
+
5
+ module Heroku
6
+ module Kensa
7
+ class Client
8
+
9
+ def initialize(args, options)
10
+ @args = args
11
+ @options = options
12
+ end
13
+
14
+ def filename
15
+ @options[:filename]
16
+ end
17
+
18
+ class CommandInvalid < Exception; end
19
+
20
+ def run!
21
+ command = @args.shift
22
+ raise CommandInvalid unless command && respond_to?(command)
23
+ send(command)
24
+ end
25
+
26
+ def init
27
+ Manifest.new(filename, @options).write
28
+ Screen.new.message "Initialized new addon manifest in #{filename}"
29
+ end
30
+
31
+ def test
32
+ case check = @args.shift
33
+ when "manifest"
34
+ run_check ManifestCheck
35
+ when "provision"
36
+ run_check ManifestCheck
37
+ run_check ProvisionCheck
38
+ when "deprovision"
39
+ id = ARGV.shift || abort("! no id specified; see usage")
40
+ run_check ManifestCheck
41
+ run_check DeprovisionCheck, :id => id
42
+ when "sso"
43
+ id = ARGV.shift || abort("! no id specified; see usage")
44
+ run_check ManifestCheck
45
+ run_check SsoCheck, :id => id
46
+ else
47
+ abort "! Unknown test '#{check}'; see usage"
48
+ end
49
+ end
50
+
51
+ def run
52
+ abort "! missing command to run; see usage" if ARGV.empty?
53
+ run_check ManifestCheck
54
+ run_check AllCheck, :args => ARGV
55
+ end
56
+
57
+ def sso
58
+ id = ARGV.shift || abort("! no id specified; see usage")
59
+ data = Yajl::Parser.parse(resolve_manifest).merge(:id => id)
60
+ sso = Sso.new(data.merge(@options))
61
+ puts "Opening #{sso.full_url}"
62
+ Launchy.open sso.full_url
63
+ end
64
+
65
+ def push
66
+ require_heroku
67
+ client = Heroku::Command.run "auth:client", ['--ignore-keys']
68
+ host = ENV['ADDONS_HOST'] || 'https://addons.heroku.com'
69
+ resource = RestClient::Resource.new(host, client.user, client.password)
70
+ resource['provider/addons'].post(resolve_manifest)
71
+ puts "Manifest pushed succesfully"
72
+ end
73
+
74
+ private
75
+
76
+ def resolve_manifest
77
+ if File.exists?(filename)
78
+ File.read(filename)
79
+ else
80
+ abort("fatal: #{filename} not found")
81
+ end
82
+ end
83
+
84
+ def run_check(klass, args={})
85
+ screen = Screen.new
86
+ data = Yajl::Parser.parse(resolve_manifest)
87
+ check = klass.new(data.merge(@options.merge(args)), screen)
88
+ check.call
89
+ screen.finish
90
+ end
91
+
92
+ def require_heroku
93
+ require 'heroku'
94
+ require 'heroku/command'
95
+ require 'heroku/commands/auth'
96
+ rescue LoadError
97
+ abort("fatal: Could not load the Heroku gem. Plase make sure the Heroku gem is available and up to date")
98
+ end
99
+
100
+
101
+ class Screen
102
+ include Term::ANSIColor
103
+
104
+ def test(msg)
105
+ $stdout.puts
106
+ $stdout.puts
107
+ $stdout.print "Testing #{msg}"
108
+ end
109
+
110
+ def check(msg)
111
+ $stdout.puts
112
+ $stdout.print " Check #{msg}"
113
+ end
114
+
115
+ def error(msg)
116
+ $stdout.print "\n", magenta(" ! #{msg}")
117
+ end
118
+
119
+ def result(status)
120
+ msg = status ? bold("[PASS]") : red(bold("[FAIL]"))
121
+ $stdout.print " #{msg}"
122
+ end
123
+
124
+ def message(msg)
125
+ $stdout.puts msg
126
+ end
127
+
128
+ def finish
129
+ $stdout.puts
130
+ $stdout.puts
131
+ $stdout.puts "done."
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,53 @@
1
+ require 'restclient'
2
+
3
+ module Heroku
4
+ module Kensa
5
+ module HTTP
6
+
7
+ def get(path, params={})
8
+ path = "#{path}?" + params.map { |k, v| "#{k}=#{v}" }.join("&") unless params.empty?
9
+ request(:get, [], path)
10
+ end
11
+
12
+ def post(credentials, path, payload=nil)
13
+ request(:post, credentials, path, payload)
14
+ end
15
+
16
+ def delete(credentials, path, payload=nil)
17
+ request(:delete, credentials, path, payload)
18
+ end
19
+
20
+ def request(meth, credentials, path, payload=nil)
21
+ code = nil
22
+ body = nil
23
+
24
+ begin
25
+ args = [
26
+ (Yajl::Encoder.encode(payload) if payload),
27
+ {
28
+ :accept => "application/json",
29
+ :content_type => "application/json"
30
+ }
31
+ ].compact
32
+
33
+ user, pass = credentials
34
+ body = RestClient::Resource.new(url, user, pass)[path].send(
35
+ meth,
36
+ *args
37
+ ).to_s
38
+
39
+ code = 200
40
+ rescue RestClient::ExceptionWithResponse => boom
41
+ code = boom.http_code
42
+ body = boom.http_body
43
+ rescue Errno::ECONNREFUSED
44
+ code = -1
45
+ body = nil
46
+ end
47
+
48
+ [code, body]
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,52 @@
1
+ module Heroku
2
+ module Kensa
3
+ class Manifest
4
+
5
+ def initialize(filename = 'addon-manifest.json', options = {})
6
+ @filename, @options = filename, options
7
+ end
8
+
9
+ def skeleton_json
10
+ <<-JSON
11
+ {
12
+ "id": "myaddon",
13
+ "plans": [
14
+ { "id": "basic" },
15
+ { "id": "premium" }
16
+ ],
17
+ "api": {
18
+ "config_vars": [ "MYADDON_URL" ],
19
+ "username": "heroku",
20
+ "password": "b1EWrHYXE1R5J71D",#{ sso_key }
21
+ "production": "https://yourapp.com/",
22
+ "test": "http://localhost:4567/"
23
+ }
24
+ }
25
+ JSON
26
+ end
27
+
28
+ def skeleton
29
+ Yajl::Parser.parse skeleton_json
30
+ end
31
+
32
+ def write
33
+ open(@filename, 'w') { |f| f << skeleton_json }
34
+ end
35
+
36
+ private
37
+
38
+ def sso_key
39
+ unless @options[:sso] === false
40
+ %{\n "sso_salt": #{ generate_password(16).inspect },}
41
+ end
42
+ end
43
+
44
+ PasswordChars = chars = ['a'..'z', 'A'..'Z', '0'..'9'].map { |r| r.to_a }.flatten
45
+ def generate_password(size=16)
46
+ Array.new(size) { PasswordChars[rand(PasswordChars.size)] }.join
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,55 @@
1
+ require 'restclient'
2
+
3
+ module Heroku
4
+ module Kensa
5
+ class Sso
6
+ attr_accessor :id, :url
7
+
8
+ def initialize(data)
9
+ @id = data[:id]
10
+ @salt = data['api']['sso_salt']
11
+
12
+ env = data.fetch :env, 'test'
13
+ @url = data["api"][env].chomp('/')
14
+ end
15
+
16
+ def path
17
+ "/heroku/resources/#{id}"
18
+ end
19
+
20
+ def full_url
21
+ [ url, path, querystring ].join
22
+ end
23
+
24
+ def make_token(t)
25
+ Digest::SHA1.hexdigest([@id, @salt, t].join(':'))
26
+ end
27
+
28
+ def querystring
29
+ return '' unless @salt
30
+
31
+ t = Time.now.to_i
32
+ "?token=#{make_token(t)}&timestamp=#{t}&nav-data=#{sample_nav_data}"
33
+ end
34
+
35
+ def sample_nav_data
36
+ json = Yajl::Encoder.encode({
37
+ :addon => 'Your Addon',
38
+ :appname => 'myapp',
39
+ :addons => [
40
+ { :slug => 'cron', :name => 'Cron'},
41
+ { :slug => 'custom_domains+wildcard', :name => 'Custom Domains + Wildcard'},
42
+ { :slug => 'youraddon', :name => 'Your Addon'},
43
+ ]
44
+ })
45
+ base64_url_variant(json)
46
+ end
47
+
48
+ def base64_url_variant(text)
49
+ base64 = [text].pack('m').gsub('=', '').gsub("\n", '')
50
+ base64.tr('+/','-_')
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,26 @@
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']['username'] = 'test'
9
+ @data['api']['password'] = 'secret'
10
+ @data['api']['test'] += "working"
11
+ @file = File.dirname(__FILE__) + "/resources/runner.rb"
12
+ end
13
+
14
+ def check; AllCheck; end
15
+
16
+ test "valid on script exit 0" do
17
+ @data[:args] = "ruby #{@file}"
18
+ assert_valid
19
+ end
20
+
21
+ test "invalid on script exit non 0" do
22
+ @data[:args] = "ruby #{@file} fail"
23
+ assert_invalid
24
+ end
25
+
26
+ end
@@ -1,10 +1,10 @@
1
- require File.dirname(__FILE__) + "/helper"
1
+ require 'test/helper'
2
2
 
3
3
  class DeprovisionCheckTest < Test::Unit::TestCase
4
- include Heroku::Sensei
4
+ include Heroku::Kensa
5
5
 
6
6
  setup do
7
- @data = Manifest.skeleton.merge :id => 123
7
+ @data = Manifest.new.skeleton.merge :id => 123
8
8
  @responses = [
9
9
  [200, ""],
10
10
  [401, ""],
data/test/helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'heroku/kensa'
2
2
  require 'contest'
3
+ require 'timecop'
3
4
 
4
5
  class Test::Unit::TestCase
5
6
 
@@ -1,12 +1,12 @@
1
- require File.dirname(__FILE__) + "/helper"
1
+ require 'test/helper'
2
2
 
3
3
  class ManifestCheckTest < Test::Unit::TestCase
4
- include Heroku::Sensei
4
+ include Heroku::Kensa
5
5
 
6
6
  def check ; ManifestCheck ; end
7
7
 
8
8
  setup do
9
- @data = Manifest.skeleton
9
+ @data = Manifest.new.skeleton
10
10
  @data["plans"] << {
11
11
  "id" => "advanced",
12
12
  "name" => "Advanced",
@@ -24,11 +24,6 @@ class ManifestCheckTest < Test::Unit::TestCase
24
24
  assert_invalid
25
25
  end
26
26
 
27
- test "has a name" do
28
- @data.delete("name")
29
- assert_invalid
30
- end
31
-
32
27
  test "api key exists" do
33
28
  @data.delete("api")
34
29
  assert_invalid
@@ -114,29 +109,4 @@ class ManifestCheckTest < Test::Unit::TestCase
114
109
  assert_invalid
115
110
  end
116
111
 
117
- test "all plans have a name" do
118
- @data["plans"].first.delete("name")
119
- assert_invalid
120
- end
121
-
122
- test "all plans have a unique name" do
123
- @data["plans"].first["name"] = @data["plans"].last["name"]
124
- assert_invalid
125
- end
126
-
127
- test "plans have a price" do
128
- @data["plans"].first.delete("price")
129
- assert_invalid
130
- end
131
-
132
- test "plans have an integer price" do
133
- @data["plans"].first["price"] = "fiddy cent"
134
- assert_invalid
135
- end
136
-
137
- test "plans have a valid price_unit" do
138
- @data["plans"].first["price_unit"] = "first ov da munth"
139
- assert_invalid
140
- end
141
-
142
112
  end
@@ -0,0 +1,24 @@
1
+ require 'test/helper'
2
+
3
+ class ManifestTest < Test::Unit::TestCase
4
+ include Heroku::Kensa
5
+
6
+ context 'manifest' do
7
+ setup { @manifest = Manifest.new }
8
+
9
+ test 'have sso salt' do
10
+ assert_not_nil @manifest.skeleton['api']['sso_salt']
11
+ end
12
+ end
13
+
14
+ context 'manifest without sso' do
15
+ setup do
16
+ options = { :sso => false }
17
+ @manifest = Manifest.new 'test.txt', options
18
+ end
19
+
20
+ test 'exclude sso salt' do
21
+ assert_nil @manifest.skeleton['api']['sso_salt']
22
+ end
23
+ end
24
+ end
@@ -1,10 +1,10 @@
1
- require File.dirname(__FILE__) + "/helper"
1
+ require 'test/helper'
2
2
 
3
3
  class ProvisionCheckTest < Test::Unit::TestCase
4
- include Heroku::Sensei
4
+ include Heroku::Kensa
5
5
 
6
6
  setup do
7
- @data = Manifest.skeleton
7
+ @data = Manifest.new.skeleton
8
8
  @data['api']['username'] = 'test'
9
9
  @data['api']['password'] = 'secret'
10
10
  end