maitre_d 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -1,2 +1,5 @@
1
+ 0.1.0 - January 11th 2012
2
+ * Tentative Heroku support.
3
+
1
4
  0.0.1 - December 27th 2011
2
5
  * Initial release - nothing particularly functional, just claiming the name on rubygems.org.
data/README.textile CHANGED
@@ -2,6 +2,127 @@ h1. Maître d'
2
2
 
3
3
  Rack APIs powered by Grape for managing Heroku and Opperator add-ons. If used within a Rails application, it'll automatically be mounted as a Rails engine at the appropriate paths.
4
4
 
5
+ 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.
6
+
7
+ h2. Installing
8
+
9
+ h3. With Rails
10
+
11
+ Because there's a Rails engine as part of Maître d', the API routing is set up automatically. Jump forward to the configuration and listener setup below.
12
+
13
+ h3. Without Rack
14
+
15
+ 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@, and/or @MaitreD::Opperator::API@ at @/opperator@.
16
+
17
+ h2. Configuration
18
+
19
+ You'll need to provide Maître d' with the appropriate provider credentials - in a Rails app, this would go in an initializer, but for Rack/Sinatra apps just get the values set before the routing is defined.
20
+
21
+ <pre><code>MaitreD::Heroku.configure do |config|
22
+ config.id = 'addon-id'
23
+ config.password = 'random'
24
+ config.sso_salt = 'gibberish'
25
+ config.listener = HerokuListener
26
+ end
27
+
28
+ MaitreD::Opperator.configure do |config|
29
+ config.shared_secret = 'something-special'
30
+ config.listener = OpperatorListener
31
+ end</code></pre>
32
+
33
+ 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.
34
+
35
+ h2. Heroku Listener
36
+
37
+ Your Heroku listener class should be a subclass of @MaitreD::Heroku::Listener@, and handle the following four methods:
38
+
39
+ h3. @provision(heroku_id, plan, callback_url, logplex_token, options)@
40
+
41
+ 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:
42
+
43
+ <dl>
44
+ <dt><code>id</code></dt>
45
+ <dd>Your local resource id, which Heroku will use in related requests (to change plans, deprovision or initialise single-sign-on).</dd>
46
+ <dt><code>config</code></dt>
47
+ <dd>A hash of the newly provisioned resource's configuration values (that are provided as environment variables to the app in question).</dd>
48
+ <dt><code>message</code></dt>
49
+ <dd>An optional message that will be displayed when your add-on is added via the command-line.</dd>
50
+ </dl>
51
+
52
+ h3. @plan_change(resource_id, heroku_id, plan)@
53
+
54
+ This gets called when an app is upgrading or downgrading from their current plan. You need to return a hash with the following keys:
55
+
56
+ <dl>
57
+ <dt><code>config</code></dt>
58
+ <dd>A hash of the modified resource's configuration values (that are provided as environment variables to the app in question).</dd>
59
+ <dt><code>message</code></dt>
60
+ <dd>An optional message that will be displayed when an app using your add-on is upgraded or downgraded via the command-line.</dd>
61
+ </dl>
62
+
63
+ h3. @deprovision(resource_id)@
64
+
65
+ This gets called when an app is removing your add-on from itself. You don't have to return anything in particular for this, though Heroku may pass through the @message@ argument like it does for the @provision@ and @plan_change@ calls.
66
+
67
+ h3. @single_sign_on(resource_id)@
68
+
69
+ Maître d' will check the token and timestamp provided, and sets up the nav-data cookie, but you'll need to decide where the user gets redirected to and what other details you wish to track via their session. To do this, just return a hash with the following keys:
70
+
71
+ <dl>
72
+ <dt><code>uri</code></dt>
73
+ <dd>The URI to redirect the user to, now that you've signed them in.</dd>
74
+ <dt><code>session</code></dt>
75
+ <dd>A hash of any session values you wish to be set.</dd>
76
+ </dl>
77
+
78
+ Here's a very basic example:
79
+
80
+ <pre><code>class HerokuListener < MaitreD::Heroku::Listener
81
+ def provision(heroku_id, plan, callback_url, logplex_token, options)
82
+ plan = Plan.find_by_name plan
83
+ widget = Widget.create(
84
+ :heroku_id => heroku_id,
85
+ :callback_url => callback_url,
86
+ :plan => plan
87
+ )
88
+
89
+ {
90
+ :id => widget.id,
91
+ :config => {'WIDGET_KEY' => widget.key},
92
+ :message => 'Add-on provisioned!'
93
+ }
94
+ end
95
+
96
+ def plan_change(resource_id, heroku_id, plan)
97
+ plan = Plan.find_by_name plan
98
+ widget = Widget.find resource_id
99
+ widget.plan = plan
100
+ widget.save
101
+
102
+ {:config => {'WIDGET_KEY' => widget.key}}
103
+ end
104
+
105
+ def deprovision(resource_id)
106
+ widget = Widget.find resource_id
107
+ widget.destroy
108
+ end
109
+
110
+ def single_sign_on(resource_id)
111
+ widget = Widget.find resource_id
112
+
113
+ {
114
+ :uri => '/my/dashboard',
115
+ :session => {:widget_id => widget.id}
116
+ }
117
+ end
118
+ end</code></pre>
119
+
120
+ 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.
121
+
122
+ h2. Opperator Listener
123
+
124
+ This listener is currently in progress as the Opperator API is established.
125
+
5
126
  h2. Contributing
6
127
 
7
128
  Contributions are very much welcome - but keep in mind the following:
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "foo",
3
+ "api": {
4
+ "config_vars": [ "FOO_PROVISIONED" ],
5
+ "password": "bar",
6
+ "sso_salt": "sea salt",
7
+ "production": "https://yourapp.com/",
8
+ "test": "http://localhost:9292/"
9
+ }
10
+ }
data/config/routes.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  Rails.application.routes.draw do
2
+ mount MaitreD::Heroku::API => '/heroku'
2
3
  mount MaitreD::Opperator::API => '/opperator'
3
4
  end
@@ -0,0 +1,42 @@
1
+ class MaitreD::Heroku::API < Grape::API
2
+ helpers MaitreD::Heroku::APIHelpers
3
+
4
+ resources :resources do
5
+ get ':id' do
6
+ error!('403 Forbidden', 403) unless valid_token? && valid_timestamp?
7
+
8
+ hash = MaitreD::Heroku.listener.new.single_sign_on(params[:id])
9
+
10
+ hash[:session] ||= {}
11
+ hash[:session].each { |key, value| session[key] = value }
12
+
13
+ status 302
14
+ header 'Location', hash[:uri]
15
+ Rack::Utils.set_cookie_header! header, 'heroku-nav-data',
16
+ :value => params['nav-data']
17
+ end
18
+
19
+ post do
20
+ authenticate!
21
+
22
+ MaitreD::Heroku.listener.new.provision(
23
+ params[:heroku_id], params[:plan], params[:callback_url],
24
+ params[:logplex_token], params[:options]
25
+ )
26
+ end
27
+
28
+ put ':id' do
29
+ authenticate!
30
+
31
+ MaitreD::Heroku.listener.new.plan_change(
32
+ params[:id], params[:heroku_id], params[:plan]
33
+ )
34
+ end
35
+
36
+ delete ':id' do
37
+ authenticate!
38
+
39
+ MaitreD::Heroku.listener.new.deprovision params[:id]
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ module MaitreD::Heroku::APIHelpers
2
+ def authenticate!
3
+ error!('401 Unauthorized', 401) unless valid_authorization?
4
+ end
5
+
6
+ def session
7
+ env['rack.session']
8
+ end
9
+
10
+ def valid_timestamp?
11
+ params[:timestamp].to_i >= (Time.now - 5*60).to_i
12
+ end
13
+
14
+ def valid_token?
15
+ expected_token == params[:token]
16
+ end
17
+
18
+ private
19
+
20
+ def expected_token
21
+ @expected_token ||= Digest::SHA1.hexdigest(
22
+ "#{params[:id]}:#{MaitreD::Heroku.sso_salt}:#{params[:timestamp]}"
23
+ ).to_s
24
+ end
25
+
26
+ def valid_authorization?
27
+ valid_authorization.strip == env['HTTP_AUTHORIZATION'].strip
28
+ end
29
+
30
+ def valid_authorization
31
+ encoded_authorization = Base64.encode64(
32
+ "#{MaitreD::Heroku.id}:#{MaitreD::Heroku.password}"
33
+ )
34
+ "Basic #{encoded_authorization}"
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ class MaitreD::Heroku::Listener
2
+ def provision(heroku_id, plan, callback_url, logplex_token, options)
3
+ #
4
+ end
5
+
6
+ def plan_change(resource_id, heroku_id, plan)
7
+ #
8
+ end
9
+
10
+ def deprovision(resource_id)
11
+ #
12
+ end
13
+
14
+ def single_sign_on(resource_id)
15
+ #
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ module MaitreD::Heroku
2
+ def self.configure
3
+ yield self
4
+ end
5
+
6
+ def self.listener
7
+ @listener
8
+ end
9
+
10
+ def self.listener=(listener)
11
+ @listener = listener
12
+ end
13
+
14
+ def self.id
15
+ @id
16
+ end
17
+
18
+ def self.id=(id)
19
+ @id = id
20
+ end
21
+
22
+ def self.password
23
+ @password
24
+ end
25
+
26
+ def self.password=(password)
27
+ @password = password
28
+ end
29
+
30
+ def self.sso_salt
31
+ @sso_salt
32
+ end
33
+
34
+ def self.sso_salt=(salt)
35
+ @sso_salt = salt
36
+ end
37
+ end
38
+
39
+ require 'maitre_d/heroku/api_helpers'
40
+ require 'maitre_d/heroku/api'
41
+ require 'maitre_d/heroku/listener'
@@ -5,15 +5,15 @@ class MaitreD::Opperator::API < Grape::API
5
5
 
6
6
  resources :instances do
7
7
  post do
8
- MaitreD::Opperator.subscribe params[:features]
8
+ MaitreD::Opperator.listener.new.provision params[:features]
9
9
  end
10
10
 
11
11
  delete ':id' do
12
- # MaitreD::Opperator.cancel params[:id]
12
+ MaitreD::Opperator.listener.new.deprovision params[:id]
13
13
  end
14
14
 
15
15
  put ':id' do
16
- # MaitreD::Opperator.update params[:id], params[:features]
16
+ MaitreD::Opperator.listener.new.change_plan params[:id], params[:features]
17
17
  end
18
18
  end
19
19
 
@@ -0,0 +1,13 @@
1
+ class MaitreD::Opperator::Listener
2
+ def provision(features)
3
+ #
4
+ end
5
+
6
+ def deprovision(resource_id)
7
+ #
8
+ end
9
+
10
+ def change_plan(resource_id, features)
11
+ #
12
+ end
13
+ end
@@ -1,4 +1,16 @@
1
1
  module MaitreD::Opperator
2
+ def self.configure
3
+ yield self
4
+ end
5
+
6
+ def self.listener
7
+ @listener
8
+ end
9
+
10
+ def self.listener=(listener)
11
+ @listener = listener
12
+ end
13
+
2
14
  def self.shared_secret
3
15
  @shared_secret
4
16
  end
@@ -6,14 +18,8 @@ module MaitreD::Opperator
6
18
  def self.shared_secret=(secret)
7
19
  @shared_secret = secret
8
20
  end
9
-
10
- def self.subscribe(features = {})
11
- {
12
- 'id' => '321',
13
- 'config' => {}
14
- }
15
- end
16
21
  end
17
22
 
18
23
  require 'maitre_d/opperator/api_helpers'
19
24
  require 'maitre_d/opperator/api'
25
+ require 'maitre_d/opperator/listener'
@@ -1,3 +1,3 @@
1
1
  module MaitreD
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
data/lib/maitre_d.rb CHANGED
@@ -3,6 +3,7 @@ require 'grape'
3
3
  module MaitreD
4
4
  end
5
5
 
6
+ require 'maitre_d/heroku'
6
7
  require 'maitre_d/opperator'
7
8
  require 'maitre_d/version'
8
9
 
data/maitre_d.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "maitre_d/version"
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'maitre_d/version'
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = 'maitre_d'
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ['lib']
20
20
 
21
+ s.add_development_dependency 'kensa', '1.3.0'
21
22
  s.add_development_dependency 'rails', '3.1.3'
22
23
  s.add_development_dependency 'rspec-rails', '2.7.0'
23
24
  end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Heroku Provisioning API' do
4
+ let(:authorisation) { "Basic #{Base64.encode64('foo:bar')}" }
5
+ let(:json_response) { JSON.parse response.body }
6
+
7
+ describe 'Provisioning' do
8
+ let(:params) {
9
+ {
10
+ :plan => 'basic',
11
+ :callback_url => 'https://domain/vendor/apps/app123%40heroku.com',
12
+ :heroku_id => 'app123@heroku.com'
13
+ }
14
+ }
15
+
16
+ it "returns a 401 if the HTTP authorisation does not match" do
17
+ post '/heroku/resources', params,
18
+ {'HTTP_AUTHORIZATION' => 'Basic foobarbaz'}
19
+
20
+ response.status.should == 401
21
+ end
22
+
23
+ it "returns the resource id" do
24
+ post '/heroku/resources', params, {'HTTP_AUTHORIZATION' => authorisation}
25
+
26
+ json_response['id'].should == '123'
27
+ end
28
+
29
+ it "returns the resource configuration" do
30
+ post '/heroku/resources', params, {'HTTP_AUTHORIZATION' => authorisation}
31
+
32
+ json_response['config'].should == {'FOO_PROVISIONED' => "true"}
33
+ end
34
+
35
+ it "returns a custom message" do
36
+ post '/heroku/resources', params, {'HTTP_AUTHORIZATION' => authorisation}
37
+
38
+ json_response['message'].should == 'Add-on provisioned!'
39
+ end
40
+ end
41
+
42
+ describe 'Changing Plans' do
43
+ let(:params) {
44
+ {:heroku_id => 'app123@heroku.com', :plan => 'premium'}
45
+ }
46
+
47
+ it "returns a 401 if the HTTP authorisation does not match" do
48
+ put '/heroku/resources/7', params,
49
+ {'HTTP_AUTHORIZATION' => 'Basic foobarbaz'}
50
+
51
+ response.status.should == 401
52
+ end
53
+
54
+ it "returns the new resource configuration" do
55
+ put '/heroku/resources/7', params, {'HTTP_AUTHORIZATION' => authorisation}
56
+
57
+ json_response['config'].should == {'FOO_PROVISIONED' => "false"}
58
+ end
59
+
60
+ it "returns a custom message" do
61
+ put '/heroku/resources/7', params, {'HTTP_AUTHORIZATION' => authorisation}
62
+
63
+ json_response['message'].should == 'Add-on upgraded or downgraded.'
64
+ end
65
+ end
66
+
67
+ describe 'Deprovisioning' do
68
+ it "returns a 401 if the HTTP authorisation does not match" do
69
+ delete '/heroku/resources/28', {},
70
+ {'HTTP_AUTHORIZATION' => 'Basic foobarbaz'}
71
+
72
+ response.status.should == 401
73
+ end
74
+
75
+ it "returns with a status of 200" do
76
+ delete '/heroku/resources/28', {}, {'HTTP_AUTHORIZATION' => authorisation}
77
+
78
+ response.status.should == 200
79
+ end
80
+
81
+ it "returns a custom message" do
82
+ delete '/heroku/resources/28', {}, {'HTTP_AUTHORIZATION' => authorisation}
83
+
84
+ json_response['message'].should == 'Add-on removed.'
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Heroku SSO API' do
4
+ let(:timestamp) { Time.now.to_i }
5
+ let(:nav_data) { 'heroku-nav-data-goes-here' }
6
+ let(:token) {
7
+ pre_token = "789:sea salt:#{timestamp.to_s}"
8
+ Digest::SHA1.hexdigest(pre_token).to_s
9
+ }
10
+
11
+ it "renders a 403 if the token is incorrect" do
12
+ get '/heroku/resources/789', :token => 'foo',
13
+ :timestamp => timestamp, 'nav-data' => nav_data
14
+
15
+ response.status.should == 403
16
+ end
17
+
18
+ it "renders a 403 if the timestamp is older than 5 minutes" do
19
+ timestamp = 5.minutes.ago.to_i - 1
20
+ pre_token = "789:sea salt:#{timestamp.to_s}"
21
+ token = Digest::SHA1.hexdigest(pre_token).to_s
22
+
23
+ get '/heroku/resources/789', :token => token,
24
+ :timestamp => timestamp, 'nav-data' => nav_data
25
+
26
+ response.status.should == 403
27
+ end
28
+
29
+ it "sets the heroku nav data cookie" do
30
+ get '/heroku/resources/789', :token => token,
31
+ :timestamp => timestamp, 'nav-data' => nav_data
32
+
33
+ cookies['heroku-nav-data'].should == nav_data
34
+ end
35
+
36
+ it "redirects to the appropriate URL" do
37
+ get '/heroku/resources/789', :token => token,
38
+ :timestamp => timestamp, 'nav-data' => nav_data
39
+
40
+ response.should redirect_to('/my/dashboard')
41
+ end
42
+
43
+ it "should set the provided session variables" do
44
+ get '/heroku/resources/789', :token => token,
45
+ :timestamp => timestamp, 'nav-data' => nav_data
46
+
47
+ session[:app_id].should == '789'
48
+ end
49
+ end
@@ -22,7 +22,7 @@ describe 'Opperator Provisioning API' do
22
22
  post '/opperator/instances', {'features' => {}},
23
23
  {'X-Opperator-Shared-Secret' => 'something-special'}
24
24
 
25
- json_response['config'].should be_a(Hash)
25
+ json_response['config'].should == {'provisioned_for' => 'opperator'}
26
26
  end
27
27
  end
28
28
 
@@ -0,0 +1,29 @@
1
+ class HerokuListener < MaitreD::Heroku::Listener
2
+ def provision(heroku_id, plan, callback_url, logplex_token, options)
3
+ {
4
+ :id => '123',
5
+ :config => {'FOO_PROVISIONED' => 'true'},
6
+ :message => 'Add-on provisioned!'
7
+ }
8
+ end
9
+
10
+ def plan_change(resource_id, heroku_id, plan)
11
+ {
12
+ :config => {'FOO_PROVISIONED' => 'false'},
13
+ :message => 'Add-on upgraded or downgraded.'
14
+ }
15
+ end
16
+
17
+ def deprovision(resource_id)
18
+ {
19
+ :message => 'Add-on removed.'
20
+ }
21
+ end
22
+
23
+ def single_sign_on(resource_id)
24
+ {
25
+ :uri => '/my/dashboard',
26
+ :session => {:app_id => resource_id}
27
+ }
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ class OpperatorListener < MaitreD::Opperator::Listener
2
+ def provision(features)
3
+ {
4
+ 'id' => '321',
5
+ 'config' => {'provisioned_for' => 'opperator'}
6
+ }
7
+ end
8
+
9
+ def deprovision(resource_id)
10
+ #
11
+ end
12
+
13
+ def change_plan(resource_id, features)
14
+ #
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ MaitreD::Heroku.configure do |config|
2
+ config.id = 'foo'
3
+ config.password = 'bar'
4
+ config.sso_salt = 'sea salt'
5
+ config.listener = HerokuListener
6
+ end
@@ -1 +1,4 @@
1
- MaitreD::Opperator.shared_secret = 'something-special'
1
+ MaitreD::Opperator.configure do |config|
2
+ config.shared_secret = 'something-special'
3
+ config.listener = OpperatorListener
4
+ end
@@ -0,0 +1,9 @@
1
+ Rails.application.routes.draw do
2
+ class Dashboard
3
+ def self.call(env)
4
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => '9'},
5
+ ['Dashboard']]
6
+ end
7
+ end
8
+ match '/my/dashboard' => Dashboard
9
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maitre_d
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-27 00:00:00.000000000 Z
12
+ date: 2012-01-11 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: kensa
16
+ requirement: &70319103578580 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70319103578580
14
25
  - !ruby/object:Gem::Dependency
15
26
  name: rails
16
- requirement: &70351599375300 !ruby/object:Gem::Requirement
27
+ requirement: &70319103578080 !ruby/object:Gem::Requirement
17
28
  none: false
18
29
  requirements:
19
30
  - - =
@@ -21,10 +32,10 @@ dependencies:
21
32
  version: 3.1.3
22
33
  type: :development
23
34
  prerelease: false
24
- version_requirements: *70351599375300
35
+ version_requirements: *70319103578080
25
36
  - !ruby/object:Gem::Dependency
26
37
  name: rspec-rails
27
- requirement: &70351599374160 !ruby/object:Gem::Requirement
38
+ requirement: &70319103577620 !ruby/object:Gem::Requirement
28
39
  none: false
29
40
  requirements:
30
41
  - - =
@@ -32,7 +43,7 @@ dependencies:
32
43
  version: 2.7.0
33
44
  type: :development
34
45
  prerelease: false
35
- version_requirements: *70351599374160
46
+ version_requirements: *70319103577620
36
47
  description: A Rack API (through Grape) for Heroku and Opperator add-on providers
37
48
  - which can also be attached as a Rails Engine.
38
49
  email:
@@ -47,20 +58,31 @@ files:
47
58
  - LICENCE
48
59
  - README.textile
49
60
  - Rakefile
61
+ - addon-manifest.json
50
62
  - config.ru
51
63
  - config/routes.rb
52
64
  - lib/maitre_d.rb
53
65
  - lib/maitre_d/engine.rb
66
+ - lib/maitre_d/heroku.rb
67
+ - lib/maitre_d/heroku/api.rb
68
+ - lib/maitre_d/heroku/api_helpers.rb
69
+ - lib/maitre_d/heroku/listener.rb
54
70
  - lib/maitre_d/opperator.rb
55
71
  - lib/maitre_d/opperator/api.rb
56
72
  - lib/maitre_d/opperator/api_helpers.rb
73
+ - lib/maitre_d/opperator/listener.rb
57
74
  - lib/maitre_d/version.rb
58
75
  - maitre_d.gemspec
76
+ - spec/api/heroku/provisioning_spec.rb
77
+ - spec/api/heroku/single_sign_on_spec.rb
59
78
  - spec/api/opperator/provisioning_spec.rb
79
+ - spec/internal/app/listeners/heroku_listener.rb
80
+ - spec/internal/app/listeners/opperator_listener.rb
81
+ - spec/internal/config/initializers/heroku.rb
60
82
  - spec/internal/config/initializers/opperator.rb
83
+ - spec/internal/config/routes.rb
61
84
  - spec/internal/log/.gitignore
62
85
  - spec/internal/public/favicon.ico
63
- - spec/spec/internal/log/test.log
64
86
  - spec/spec_helper.rb
65
87
  homepage: http://github.com/flying-sphinx/maitre_d
66
88
  licenses: []
@@ -87,10 +109,15 @@ signing_key:
87
109
  specification_version: 3
88
110
  summary: Rack API and Rails Engine for Heroku and Opperator add-ons
89
111
  test_files:
112
+ - spec/api/heroku/provisioning_spec.rb
113
+ - spec/api/heroku/single_sign_on_spec.rb
90
114
  - spec/api/opperator/provisioning_spec.rb
115
+ - spec/internal/app/listeners/heroku_listener.rb
116
+ - spec/internal/app/listeners/opperator_listener.rb
117
+ - spec/internal/config/initializers/heroku.rb
91
118
  - spec/internal/config/initializers/opperator.rb
119
+ - spec/internal/config/routes.rb
92
120
  - spec/internal/log/.gitignore
93
121
  - spec/internal/public/favicon.ico
94
- - spec/spec/internal/log/test.log
95
122
  - spec/spec_helper.rb
96
123
  has_rdoc:
File without changes