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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/Gemfile +0 -5
- data/README.textile +13 -31
- data/addon-manifest.json +11 -4
- data/lib/maitre_d.rb +4 -1
- data/lib/maitre_d/api.rb +30 -0
- data/lib/maitre_d/api/authenticated.rb +45 -0
- data/lib/maitre_d/api/change_plan.rb +15 -0
- data/lib/maitre_d/api/create.rb +10 -0
- data/lib/maitre_d/api/delete.rb +13 -0
- data/lib/maitre_d/api/sso.rb +65 -0
- data/lib/maitre_d/cloud_control.rb +4 -3
- data/lib/maitre_d/heroku.rb +4 -4
- data/maitre_d.gemspec +7 -4
- data/spec/api/cloud_control/provisioning_spec.rb +18 -18
- data/spec/api/cloud_control/single_sign_on_spec.rb +13 -13
- data/spec/api/heroku/provisioning_spec.rb +31 -23
- data/spec/api/heroku/single_sign_on_spec.rb +15 -15
- data/spec/internal/app/listeners/cloud_control_listener.rb +1 -1
- data/spec/internal/app/listeners/heroku_listener.rb +1 -1
- data/spec/internal/config/initializers/heroku.rb +2 -2
- data/spec/internal/config/routes.rb +3 -5
- data/spec/spec_helper.rb +1 -2
- metadata +50 -42
- data/lib/maitre_d/broadstack.rb +0 -40
- data/lib/maitre_d/broadstack/api.rb +0 -49
- data/lib/maitre_d/broadstack/api_helpers.rb +0 -44
- data/lib/maitre_d/cloud_control/api.rb +0 -47
- data/lib/maitre_d/cloud_control/api_helpers.rb +0 -28
- data/lib/maitre_d/heroku/api.rb +0 -47
- data/lib/maitre_d/heroku/api_helpers.rb +0 -46
- data/lib/maitre_d/heroku/listener.rb +0 -17
- data/lib/maitre_d/opperator.rb +0 -25
- data/lib/maitre_d/opperator/api.rb +0 -25
- data/lib/maitre_d/opperator/api_helpers.rb +0 -11
- data/lib/maitre_d/opperator/listener.rb +0 -13
- data/spec/api/broadstack/provisioning_spec.rb +0 -94
- data/spec/api/broadstack/single_sign_on_spec.rb +0 -49
- data/spec/api/opperator/provisioning_spec.rb +0 -55
- data/spec/internal/app/listeners/broadstack_listener.rb +0 -30
- data/spec/internal/app/listeners/opperator_listener.rb +0 -16
- data/spec/internal/config/initializers/broadstack.rb +0 -8
- data/spec/internal/config/initializers/opperator.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b27a7d6903d91dea29a32af183b05a2b87689190
|
4
|
+
data.tar.gz: 511c60706f0fd6d560d8930afb2eeac0815bb33e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 894af66d98c2dd376de02fd6925d330fad5dc40c1dc223e0c06bc470c67120c6f1a68ae685b4f8b6987f5a372a85c739b27e4216be447a77fef71db4f9625d13
|
7
|
+
data.tar.gz: d01c6f9f505f9fe2a129ffac826eebb074139cfa678a0cc0a6eca961b6094d12590cf0b88b56b4b0ce936416400d94302fe7edc74870da9bf469910d4c0a4a3b
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
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
|
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
|
11
|
+
Add the following to your Gemfile:
|
12
12
|
|
13
|
-
<pre><code>gem '
|
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
|
23
|
-
mount MaitreD::Heroku
|
24
|
-
|
25
|
-
mount MaitreD::
|
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
|
-
|
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.
|
52
|
+
h2. Listeners
|
63
53
|
|
64
|
-
Your
|
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
|
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
|
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-
|
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
|
-
"
|
6
|
-
"
|
7
|
-
"
|
8
|
-
"
|
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
data/lib/maitre_d/api.rb
ADDED
@@ -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,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
|
-
|
40
|
-
|
38
|
+
def self.provider_id_from(params)
|
39
|
+
params['cloudcontrol_id']
|
40
|
+
end
|
41
|
+
end
|
data/lib/maitre_d/heroku.rb
CHANGED
@@ -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
|
-
|
40
|
-
|
41
|
-
|
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.
|
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.
|
22
|
-
s.add_development_dependency 'rails', '
|
23
|
-
s.add_development_dependency 'rspec-rails', '
|
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.
|
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'].
|
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'].
|
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'].
|
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.
|
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'].
|
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'].
|
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.
|
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.
|
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'].
|
91
|
+
expect(json_response['message']).to eq('Add-on removed.')
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|