maitre_d 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/Gemfile +0 -5
  4. data/README.textile +13 -31
  5. data/addon-manifest.json +11 -4
  6. data/lib/maitre_d.rb +4 -1
  7. data/lib/maitre_d/api.rb +30 -0
  8. data/lib/maitre_d/api/authenticated.rb +45 -0
  9. data/lib/maitre_d/api/change_plan.rb +15 -0
  10. data/lib/maitre_d/api/create.rb +10 -0
  11. data/lib/maitre_d/api/delete.rb +13 -0
  12. data/lib/maitre_d/api/sso.rb +65 -0
  13. data/lib/maitre_d/cloud_control.rb +4 -3
  14. data/lib/maitre_d/heroku.rb +4 -4
  15. data/maitre_d.gemspec +7 -4
  16. data/spec/api/cloud_control/provisioning_spec.rb +18 -18
  17. data/spec/api/cloud_control/single_sign_on_spec.rb +13 -13
  18. data/spec/api/heroku/provisioning_spec.rb +31 -23
  19. data/spec/api/heroku/single_sign_on_spec.rb +15 -15
  20. data/spec/internal/app/listeners/cloud_control_listener.rb +1 -1
  21. data/spec/internal/app/listeners/heroku_listener.rb +1 -1
  22. data/spec/internal/config/initializers/heroku.rb +2 -2
  23. data/spec/internal/config/routes.rb +3 -5
  24. data/spec/spec_helper.rb +1 -2
  25. metadata +50 -42
  26. data/lib/maitre_d/broadstack.rb +0 -40
  27. data/lib/maitre_d/broadstack/api.rb +0 -49
  28. data/lib/maitre_d/broadstack/api_helpers.rb +0 -44
  29. data/lib/maitre_d/cloud_control/api.rb +0 -47
  30. data/lib/maitre_d/cloud_control/api_helpers.rb +0 -28
  31. data/lib/maitre_d/heroku/api.rb +0 -47
  32. data/lib/maitre_d/heroku/api_helpers.rb +0 -46
  33. data/lib/maitre_d/heroku/listener.rb +0 -17
  34. data/lib/maitre_d/opperator.rb +0 -25
  35. data/lib/maitre_d/opperator/api.rb +0 -25
  36. data/lib/maitre_d/opperator/api_helpers.rb +0 -11
  37. data/lib/maitre_d/opperator/listener.rb +0 -13
  38. data/spec/api/broadstack/provisioning_spec.rb +0 -94
  39. data/spec/api/broadstack/single_sign_on_spec.rb +0 -49
  40. data/spec/api/opperator/provisioning_spec.rb +0 -55
  41. data/spec/internal/app/listeners/broadstack_listener.rb +0 -30
  42. data/spec/internal/app/listeners/opperator_listener.rb +0 -16
  43. data/spec/internal/config/initializers/broadstack.rb +0 -8
  44. data/spec/internal/config/initializers/opperator.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f3c4e61ea06b3875e6b2a365f0acabad42b242a9
4
- data.tar.gz: 3767ddfb5f8b6e7d2f044ba6b978a4a9eb88b58e
3
+ metadata.gz: b27a7d6903d91dea29a32af183b05a2b87689190
4
+ data.tar.gz: 511c60706f0fd6d560d8930afb2eeac0815bb33e
5
5
  SHA512:
6
- metadata.gz: e4c934c994c138437397a82f584a25e2631aaea6182b09a97d2a1952f1c0687894750d48e79b44b7ae863ce92cfb260de731cbd26d9d310d7f72bfee5a84e566
7
- data.tar.gz: 184847e1566082599f09efead691839955baca8f6100b0472b4c1a09b880d20089a9982c07a3614e34c6b445594d5ebacda971ba1424d14f2309c6305442fe2d
6
+ metadata.gz: 894af66d98c2dd376de02fd6925d330fad5dc40c1dc223e0c06bc470c67120c6f1a68ae685b4f8b6987f5a372a85c739b27e4216be447a77fef71db4f9625d13
7
+ data.tar.gz: d01c6f9f505f9fe2a129ffac826eebb074139cfa678a0cc0a6eca961b6094d12590cf0b88b56b4b0ce936416400d94302fe7edc74870da9bf469910d4c0a4a3b
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
+ - 2.2.0
4
5
  script: bundle exec rspec spec
data/Gemfile CHANGED
@@ -1,8 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
-
5
- # Until a new Grape release is out:
6
- gem 'grape',
7
- :git => 'git://github.com/intridea/grape.git',
8
- :ref => '212c96cdfb253a59bc93790808c568e559d04468'
data/README.textile CHANGED
@@ -2,31 +2,28 @@ h1. Maître d'
2
2
 
3
3
  "!https://secure.travis-ci.org/flying-sphinx/maitre_d.png!":http://travis-ci.org/flying-sphinx/maitre_d
4
4
 
5
- Rack APIs powered by Grape for managing Heroku, CloudControl and Opperator add-ons.
5
+ Rack APIs powered by Grape for managing Heroku and CloudControl add-ons.
6
6
 
7
7
  Maître d' manages all the authorisation checking for API requests and provides simple hooks for you to write just the code you need to handle provisioning, plan changes, deprovisioning and single-sign-on (SSO) requests.
8
8
 
9
9
  h2. Installing
10
10
 
11
- Add the following to your Gemfile - and take note that we need the git repository reference for Grape, as it includes some needed commits that aren't in the latest gem release.
11
+ Add the following to your Gemfile:
12
12
 
13
- <pre><code>gem 'grape',
14
- :git => 'git://github.com/intridea/grape.git',
15
- :ref => '212c96cdfb253a59bc93790808c568e559d04468'
16
- gem 'maitre_d', '~> 0.4.0'</code></pre>
13
+ <pre><code>gem 'maitre_d', '~> 0.5.0'</code></pre>
17
14
 
18
15
  h3. With Rails
19
16
 
20
17
  Add the appropriate Rack apps to your routes file:
21
18
 
22
- <pre><code>mount MaitreD::Broadstack::API => '/broadstack'
23
- mount MaitreD::Heroku::API => '/heroku'
24
- mount MaitreD::CloudControl::API => '/cloudcontrol'
25
- mount MaitreD::Opperator::API => '/opperator'</code></pre>
19
+ <pre><code># if you're supporting Heroku
20
+ mount MaitreD::API.new(MaitreD::Heroku) => '/heroku'
21
+ # if you're supporting CloudControl
22
+ mount MaitreD::API.new(MaitreD::CloudControl) => '/cloudcontrol'</code></pre>
26
23
 
27
24
  h3. Without Rails
28
25
 
29
- This library provides two APIs as mountable Rack applications for you - so you'll want to mount them at the appropriate paths: @MaitreD::Heroku::API@ at @/heroku@, @MaitreD::CloudControl::API@ at @/cloudcontrol@, and/or @MaitreD::Opperator::API@ at @/opperator@.
26
+ As shown above, you can use Maître d' Rack API for both Heroku and Cloud Control. Mount them to wherever you see fit. Once upon a time Heroku expected its endpoints to be at /heroku, but now things are a little more flexible.
30
27
 
31
28
  h2. Configuration
32
29
 
@@ -48,28 +45,21 @@ MaitreD::CloudControl.configure do |config|
48
45
  config.password = 'random'
49
46
  config.sso_salt = 'gibberish'
50
47
  config.listener = CloudControlListener
51
- end
52
-
53
- require 'maitre_d/opperator'
54
-
55
- MaitreD::Opperator.configure do |config|
56
- config.shared_secret = 'something-special'
57
- config.listener = OpperatorListener
58
48
  end</code></pre>
59
49
 
60
50
  The listeners that are mentioned in the code above are classes, which will handle valid API requests. Read on for more details on how to set them up.
61
51
 
62
- h2. Heroku Listener
52
+ h2. Listeners
63
53
 
64
- Your Heroku listener class should handle the following four methods:
54
+ Your listener classes should handle the following four methods:
65
55
 
66
56
  h3. @provision(heroku_id, plan, region, callback_url, logplex_token, options)@
67
57
 
68
- This gets called when Heroku's requesting an app be provisioned within your service, and expects a hash to be returned with the following keys:
58
+ This gets called when the provider is requesting an app be provisioned within your service, and expects a hash to be returned with the following keys:
69
59
 
70
60
  <dl>
71
61
  <dt><code>id</code></dt>
72
- <dd>Your local resource id, which Heroku will use in related requests (to change plans, deprovision or initialise single-sign-on).</dd>
62
+ <dd>Your local resource id, which the provider will use in related requests (to change plans, deprovision or initialise single-sign-on).</dd>
73
63
  <dt><code>config</code></dt>
74
64
  <dd>A hash of the newly provisioned resource's configuration values (that are provided as environment variables to the app in question).</dd>
75
65
  <dt><code>message</code></dt>
@@ -147,14 +137,6 @@ end</code></pre>
147
137
 
148
138
  You can have the listener class wherever you like - as long as it's available within the context of your Rails/Rack site, it'll work as expected.
149
139
 
150
- h2. CloudControl Listener
151
-
152
- CloudControl is pretty much the same as Heroku - it has the same expectations for a listener, so perhaps you can use the same class if you like. Anything that Heroku sends through as the heroku_id becomes cloudcontrol_id - but that's just a matter of treating the given parameter appropriately.
153
-
154
- h2. Opperator Listener
155
-
156
- This listener is currently in progress as the Opperator API is established.
157
-
158
140
  h2. Contributing
159
141
 
160
142
  Contributions are very much welcome - but keep in mind the following:
@@ -165,4 +147,4 @@ Contributions are very much welcome - but keep in mind the following:
165
147
 
166
148
  h2. Credits
167
149
 
168
- Copyright (c) 2011-2014, Maître d' is developed and maintained by Pat Allan, and is released under the open MIT Licence.
150
+ Copyright (c) 2011-2015, Maître d' is developed and maintained by Pat Allan, and is released under the open MIT Licence.
data/addon-manifest.json CHANGED
@@ -2,9 +2,16 @@
2
2
  "id": "foo",
3
3
  "api": {
4
4
  "config_vars": [ "FOO_PROVISIONED" ],
5
- "password": "bar",
6
- "sso_salt": "sea salt",
7
- "production": "https://yourapp.com/",
8
- "test": "http://localhost:9292/"
5
+ "regions": [ "us" ],
6
+ "password": "056e8ff3d55ea6e1a7cc4aa7afeaf3ac",
7
+ "sso_salt": "db0ce73d4bd2dfa1d66c713a9aaf8f76",
8
+ "production": {
9
+ "base_url": "https://yourapp.com/heroku/resources",
10
+ "sso_url": "https://yourapp.com/sso/login"
11
+ },
12
+ "test": {
13
+ "base_url": "http://localhost:9292/heroku/resources",
14
+ "sso_url": "http://localhost:9292/heroku/resources/sso"
15
+ }
9
16
  }
10
17
  }
data/lib/maitre_d.rb CHANGED
@@ -1,4 +1,7 @@
1
- require 'grape'
1
+ require 'sliver'
2
+ require 'multi_json'
2
3
 
3
4
  module MaitreD
4
5
  end
6
+
7
+ require 'maitre_d/api'
@@ -0,0 +1,30 @@
1
+ class MaitreD::API
2
+ def initialize(configuration)
3
+ @configuration = configuration
4
+ end
5
+
6
+ def call(environment)
7
+ environment['maitre_d.configuration'] = configuration
8
+
9
+ endpoints.call environment
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :configuration
15
+
16
+ def endpoints
17
+ @endpoints ||= Sliver::API.new do |api|
18
+ api.connect :post, '/resources/sso', MaitreD::API::SSO
19
+ api.connect :post, '/resources', MaitreD::API::Create
20
+ api.connect :put, %r{/resources/\d+}, MaitreD::API::ChangePlan
21
+ api.connect :delete, %r{/resources/\d+}, MaitreD::API::Delete
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'maitre_d/api/authenticated'
27
+ require 'maitre_d/api/change_plan'
28
+ require 'maitre_d/api/create'
29
+ require 'maitre_d/api/delete'
30
+ require 'maitre_d/api/sso'
@@ -0,0 +1,45 @@
1
+ class MaitreD::API::Authenticated
2
+ include Sliver::Action
3
+
4
+ def skip?
5
+ return false if valid_authorization?
6
+
7
+ response.body = ['401 Unauthorized']
8
+ response.status = 401
9
+ true
10
+ end
11
+
12
+ def call
13
+ response.body = [MultiJson.dump(response.body)]
14
+ response.status ||= 200
15
+ end
16
+
17
+ private
18
+
19
+ def configuration
20
+ environment['maitre_d.configuration']
21
+ end
22
+
23
+ def listener
24
+ configuration.listener.new
25
+ end
26
+
27
+ def params
28
+ @params ||= MultiJson.load request.body.read
29
+ end
30
+
31
+ def provider_id
32
+ configuration.provider_id_from params
33
+ end
34
+
35
+ def valid_authorization?
36
+ valid_authorization.strip == environment['HTTP_AUTHORIZATION'].strip
37
+ end
38
+
39
+ def valid_authorization
40
+ encoded_authorization = Base64.encode64(
41
+ "#{configuration.id}:#{configuration.password}"
42
+ )
43
+ "Basic #{encoded_authorization}"
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ class MaitreD::API::ChangePlan < MaitreD::API::Authenticated
2
+ def call
3
+ response.body = listener.plan_change(
4
+ resource_id, provider_id, params['plan']
5
+ )
6
+
7
+ super
8
+ end
9
+
10
+ private
11
+
12
+ def resource_id
13
+ request.path[%r{resources/(\d+)}, 1]
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ class MaitreD::API::Create < MaitreD::API::Authenticated
2
+ def call
3
+ response.body = listener.provision(
4
+ provider_id, params['plan'], params['region'],
5
+ params['callback_url'], params['logplex_token'], params['options']
6
+ )
7
+
8
+ super
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ class MaitreD::API::Delete < MaitreD::API::Authenticated
2
+ def call
3
+ response.body = listener.deprovision resource_id
4
+
5
+ super
6
+ end
7
+
8
+ private
9
+
10
+ def resource_id
11
+ request.path[%r{resources/(\d+)}, 1]
12
+ end
13
+ end
@@ -0,0 +1,65 @@
1
+ class MaitreD::API::SSO
2
+ include Sliver::Action
3
+
4
+ def skip?
5
+ return false if valid_token? && valid_timestamp?
6
+
7
+ response.status = 403
8
+ response.body = ['403 Forbidden']
9
+ response.headers['Content-Length'] = response.body.first.length.to_s
10
+
11
+ true
12
+ end
13
+
14
+ def call
15
+ hash = listener.single_sign_on params['id']
16
+
17
+ hash[:session] ||= {}
18
+ hash[:session].each { |key, value| session[key] = value }
19
+
20
+ if environment['action_dispatch.cookies']
21
+ environment['action_dispatch.cookies']['heroku-nav-data'] =
22
+ params['nav-data']
23
+ else
24
+ Rack::Utils.set_cookie_header! response.headers, 'heroku-nav-data',
25
+ :value => params['nav-data']
26
+ end
27
+
28
+ response.status = 302
29
+ response.body = ["Redirect to #{hash[:uri]}"]
30
+ response.headers ||= {}
31
+ response.headers['Location'] = hash[:uri]
32
+ end
33
+
34
+ private
35
+
36
+ def configuration
37
+ environment['maitre_d.configuration']
38
+ end
39
+
40
+ def expected_token
41
+ @expected_token ||= Digest::SHA1.hexdigest(
42
+ "#{params['id']}:#{configuration.sso_salt}:#{params['timestamp']}"
43
+ ).to_s
44
+ end
45
+
46
+ def listener
47
+ configuration.listener.new
48
+ end
49
+
50
+ def params
51
+ request.params
52
+ end
53
+
54
+ def session
55
+ environment['rack.session'] ||= {}
56
+ end
57
+
58
+ def valid_timestamp?
59
+ params['timestamp'].to_i >= (Time.now - 5*60).to_i
60
+ end
61
+
62
+ def valid_token?
63
+ expected_token == params['token']
64
+ end
65
+ end
@@ -34,7 +34,8 @@ module MaitreD::CloudControl
34
34
  def self.sso_salt=(salt)
35
35
  @sso_salt = salt
36
36
  end
37
- end
38
37
 
39
- require 'maitre_d/cloud_control/api_helpers'
40
- require 'maitre_d/cloud_control/api'
38
+ def self.provider_id_from(params)
39
+ params['cloudcontrol_id']
40
+ end
41
+ end
@@ -34,8 +34,8 @@ module MaitreD::Heroku
34
34
  def self.sso_salt=(salt)
35
35
  @sso_salt = salt
36
36
  end
37
- end
38
37
 
39
- require 'maitre_d/heroku/api_helpers'
40
- require 'maitre_d/heroku/api'
41
- require 'maitre_d/heroku/listener'
38
+ def self.provider_id_from(params)
39
+ params['heroku_id']
40
+ end
41
+ end
data/maitre_d.gemspec CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path('../lib', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'maitre_d'
6
- s.version = '0.4.0'
6
+ s.version = '0.5.0'
7
7
  s.authors = ['Pat Allan']
8
8
  s.email = ['pat@freelancing-gods.com']
9
9
  s.homepage = 'http://github.com/flying-sphinx/maitre_d'
@@ -17,8 +17,11 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ['lib']
19
19
 
20
+ s.add_runtime_dependency 'sliver', '~> 0.0.4'
21
+ s.add_runtime_dependency 'multi_json', '>= 1.3.0'
22
+
20
23
  s.add_development_dependency 'combustion', '0.5.1'
21
- s.add_development_dependency 'kensa', '1.3.0'
22
- s.add_development_dependency 'rails', '3.1.3'
23
- s.add_development_dependency 'rspec-rails', '2.7.0'
24
+ s.add_development_dependency 'kensa', '2.1.0'
25
+ s.add_development_dependency 'rails', '~> 4.1.0'
26
+ s.add_development_dependency 'rspec-rails', '~> 3.1.0'
24
27
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'CloudControl Provisioning API' do
3
+ describe 'CloudControl Provisioning API', :type => :request do
4
4
  let(:authorisation) { "Basic #{Base64.encode64('baz:qux')}" }
5
5
  let(:json_response) { JSON.parse response.body }
6
6
 
@@ -14,31 +14,31 @@ describe 'CloudControl Provisioning API' do
14
14
  }
15
15
 
16
16
  it "returns a 401 if the HTTP authorisation does not match" do
17
- post '/cloudcontrol/resources', params,
17
+ post '/cloudcontrol/resources', JSON.dump(params),
18
18
  {'HTTP_AUTHORIZATION' => 'Basic foobarbaz'}
19
19
 
20
- response.status.should == 401
20
+ expect(response.status).to eq(401)
21
21
  end
22
22
 
23
23
  it "returns the resource id" do
24
- post '/cloudcontrol/resources', params,
24
+ post '/cloudcontrol/resources', JSON.dump(params),
25
25
  {'HTTP_AUTHORIZATION' => authorisation}
26
26
 
27
- json_response['id'].should == '123'
27
+ expect(json_response['id']).to eq('123')
28
28
  end
29
29
 
30
30
  it "returns the resource configuration" do
31
- post '/cloudcontrol/resources', params,
31
+ post '/cloudcontrol/resources', JSON.dump(params),
32
32
  {'HTTP_AUTHORIZATION' => authorisation}
33
33
 
34
- json_response['config'].should == {'FOO_PROVISIONED' => "true"}
34
+ expect(json_response['config']).to eq('FOO_PROVISIONED' => "true")
35
35
  end
36
36
 
37
37
  it "returns a custom message" do
38
- post '/cloudcontrol/resources', params,
38
+ post '/cloudcontrol/resources', JSON.dump(params),
39
39
  {'HTTP_AUTHORIZATION' => authorisation}
40
40
 
41
- json_response['message'].should == 'Add-on provisioned!'
41
+ expect(json_response['message']).to eq('Add-on provisioned!')
42
42
  end
43
43
  end
44
44
 
@@ -48,24 +48,24 @@ describe 'CloudControl Provisioning API' do
48
48
  }
49
49
 
50
50
  it "returns a 401 if the HTTP authorisation does not match" do
51
- put '/cloudcontrol/resources/7', params,
51
+ put '/cloudcontrol/resources/7', JSON.dump(params),
52
52
  {'HTTP_AUTHORIZATION' => 'Basic foobarbaz'}
53
53
 
54
- response.status.should == 401
54
+ expect(response.status).to eq(401)
55
55
  end
56
56
 
57
57
  it "returns the new resource configuration" do
58
- put '/cloudcontrol/resources/7', params,
58
+ put '/cloudcontrol/resources/7', JSON.dump(params),
59
59
  {'HTTP_AUTHORIZATION' => authorisation}
60
60
 
61
- json_response['config'].should == {'FOO_PROVISIONED' => "false"}
61
+ expect(json_response['config']).to eq('FOO_PROVISIONED' => "false")
62
62
  end
63
63
 
64
64
  it "returns a custom message" do
65
- put '/cloudcontrol/resources/7', params,
65
+ put '/cloudcontrol/resources/7', JSON.dump(params),
66
66
  {'HTTP_AUTHORIZATION' => authorisation}
67
67
 
68
- json_response['message'].should == 'Add-on upgraded or downgraded.'
68
+ expect(json_response['message']).to eq('Add-on upgraded or downgraded.')
69
69
  end
70
70
  end
71
71
 
@@ -74,21 +74,21 @@ describe 'CloudControl Provisioning API' do
74
74
  delete '/cloudcontrol/resources/28', {},
75
75
  {'HTTP_AUTHORIZATION' => 'Basic foobarbaz'}
76
76
 
77
- response.status.should == 401
77
+ expect(response.status).to eq(401)
78
78
  end
79
79
 
80
80
  it "returns with a status of 200" do
81
81
  delete '/cloudcontrol/resources/28', {},
82
82
  {'HTTP_AUTHORIZATION' => authorisation}
83
83
 
84
- response.status.should == 200
84
+ expect(response.status).to eq(200)
85
85
  end
86
86
 
87
87
  it "returns a custom message" do
88
88
  delete '/cloudcontrol/resources/28', {},
89
89
  {'HTTP_AUTHORIZATION' => authorisation}
90
90
 
91
- json_response['message'].should == 'Add-on removed.'
91
+ expect(json_response['message']).to eq('Add-on removed.')
92
92
  end
93
93
  end
94
94
  end