devise_saml_authenticatable 1.3.1 → 1.6.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.
- checksums.yaml +5 -5
- data/.gitignore +0 -2
- data/.travis.yml +37 -22
- data/Gemfile +2 -10
- data/README.md +127 -44
- data/app/controllers/devise/saml_sessions_controller.rb +38 -7
- data/devise_saml_authenticatable.gemspec +2 -1
- data/lib/devise_saml_authenticatable.rb +70 -0
- data/lib/devise_saml_authenticatable/default_attribute_map_resolver.rb +26 -0
- data/lib/devise_saml_authenticatable/default_idp_entity_id_reader.rb +10 -2
- data/lib/devise_saml_authenticatable/exception.rb +1 -1
- data/lib/devise_saml_authenticatable/model.rb +20 -32
- data/lib/devise_saml_authenticatable/routes.rb +17 -6
- data/lib/devise_saml_authenticatable/saml_mapped_attributes.rb +38 -0
- data/lib/devise_saml_authenticatable/saml_response.rb +16 -0
- data/lib/devise_saml_authenticatable/strategy.rb +10 -2
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/spec/controllers/devise/saml_sessions_controller_spec.rb +118 -11
- data/spec/devise_saml_authenticatable/default_attribute_map_resolver_spec.rb +58 -0
- data/spec/devise_saml_authenticatable/default_idp_entity_id_reader_spec.rb +34 -4
- data/spec/devise_saml_authenticatable/model_spec.rb +199 -5
- data/spec/devise_saml_authenticatable/saml_mapped_attributes_spec.rb +50 -0
- data/spec/devise_saml_authenticatable/strategy_spec.rb +18 -0
- data/spec/features/saml_authentication_spec.rb +45 -21
- data/spec/rails_helper.rb +6 -2
- data/spec/routes/routes_spec.rb +102 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/Gemfile.rails4 +24 -6
- data/spec/support/Gemfile.rails5 +25 -0
- data/spec/support/Gemfile.rails5.1 +25 -0
- data/spec/support/Gemfile.rails5.2 +25 -0
- data/spec/support/attribute-map.yml +12 -0
- data/spec/support/attribute_map_resolver.rb.erb +14 -0
- data/spec/support/idp_settings_adapter.rb.erb +5 -5
- data/spec/support/idp_template.rb +8 -1
- data/spec/support/rails_app.rb +110 -16
- data/spec/support/saml_idp_controller.rb.erb +22 -10
- data/spec/support/sp_template.rb +52 -21
- metadata +26 -10
- data/spec/support/Gemfile.ruby-saml-1.3 +0 -23
data/spec/rails_helper.rb
CHANGED
@@ -3,12 +3,16 @@ ENV["RAILS_ENV"] ||= 'test'
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
create_app('sp', 'USE_SUBJECT_TO_AUTHENTICATE' => "false")
|
6
|
-
require
|
6
|
+
require "#{working_directory}/sp/config/environment"
|
7
7
|
require 'rspec/rails'
|
8
8
|
|
9
9
|
ActiveRecord::Migration.verbose = false
|
10
10
|
ActiveRecord::Base.logger = Logger.new(nil)
|
11
|
-
ActiveRecord::
|
11
|
+
if ActiveRecord::Base.connection.respond_to?(:migration_context)
|
12
|
+
ActiveRecord::Base.connection.migration_context.migrate
|
13
|
+
else
|
14
|
+
ActiveRecord::Migrator.migrate("#{working_directory}/sp/db/migrate/")
|
15
|
+
end
|
12
16
|
|
13
17
|
RSpec.configure do |config|
|
14
18
|
config.use_transactional_fixtures = true
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe 'SamlAuthenticatable Routes', type: :routing do
|
4
|
+
describe 'GET /users/saml/sign_in (login)' do
|
5
|
+
it 'routes to Devise::SamlSessionsController#new' do
|
6
|
+
expect(get: '/users/saml/sign_in').to route_to(controller: 'devise/saml_sessions', action: 'new')
|
7
|
+
expect(get: new_user_session_path).to route_to(controller: 'devise/saml_sessions', action: 'new')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'POST /users/saml/auth (session creation)' do
|
12
|
+
it 'routes to Devise::SamlSessionsController#create' do
|
13
|
+
expect(post: '/users/saml/auth').to route_to(controller: 'devise/saml_sessions', action: 'create')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'DELETE /users/sign_out (logout)' do
|
18
|
+
it 'routes to Devise::SamlSessionsController#destroy' do
|
19
|
+
expect(delete: '/users/sign_out').to route_to(controller: 'devise/saml_sessions', action: 'destroy')
|
20
|
+
expect(delete: destroy_user_session_path).to route_to(controller: 'devise/saml_sessions', action: 'destroy')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'GET /users/saml/metadata' do
|
25
|
+
it 'routes to Devise::SamlSessionsController#metadata' do
|
26
|
+
expect(get: '/users/saml/metadata').to route_to(controller: 'devise/saml_sessions', action: 'metadata')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'GET /users/saml/idp_sign_out (IdP-initiated logout)' do
|
31
|
+
it 'routes to Devise::SamlSessionsController#idp_sign_out' do
|
32
|
+
expect(get: '/users/saml/idp_sign_out').to route_to(controller: 'devise/saml_sessions', action: 'idp_sign_out')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'POST /users/saml/idp_sign_out (IdP-initiated logout)' do
|
37
|
+
it 'routes to Devise::SamlSessionsController#idp_sign_out' do
|
38
|
+
expect(post: '/users/saml/idp_sign_out').to route_to(controller: 'devise/saml_sessions', action: 'idp_sign_out')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when saml_route_helper_prefix is "sso"' do
|
43
|
+
before(:all) do
|
44
|
+
::Devise.saml_route_helper_prefix = 'sso'
|
45
|
+
|
46
|
+
# A very simple Rails engine
|
47
|
+
module SamlRouteHelperPrefixEngine
|
48
|
+
class Engine < ::Rails::Engine
|
49
|
+
isolate_namespace SamlRouteHelperPrefixEngine
|
50
|
+
end
|
51
|
+
|
52
|
+
Engine.routes.draw do
|
53
|
+
devise_for :users, module: :devise
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
after(:all) do
|
58
|
+
::Devise.saml_route_helper_prefix = nil
|
59
|
+
end
|
60
|
+
routes { SamlRouteHelperPrefixEngine::Engine.routes }
|
61
|
+
|
62
|
+
describe 'GET /users/saml/sign_in (login)' do
|
63
|
+
it 'routes to Devise::SamlSessionsController#new' do
|
64
|
+
expect(get: '/users/saml/sign_in').to route_to(controller: 'devise/saml_sessions', action: 'new')
|
65
|
+
expect(get: new_sso_user_session_path).to route_to(controller: 'devise/saml_sessions', action: 'new')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'POST /users/saml/auth (session creation)' do
|
70
|
+
it 'routes to Devise::SamlSessionsController#create' do
|
71
|
+
expect(post: '/users/saml/auth').to route_to(controller: 'devise/saml_sessions', action: 'create')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'DELETE /users/sign_out (logout)' do
|
76
|
+
it 'routes to Devise::SamlSessionsController#destroy' do
|
77
|
+
expect(delete: '/users/sign_out').to route_to(controller: 'devise/saml_sessions', action: 'destroy')
|
78
|
+
expect(delete: destroy_sso_user_session_path).to route_to(controller: 'devise/saml_sessions', action: 'destroy')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'GET /users/saml/metadata' do
|
83
|
+
it 'routes to Devise::SamlSessionsController#metadata' do
|
84
|
+
expect(get: '/users/saml/metadata').to route_to(controller: 'devise/saml_sessions', action: 'metadata')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'GET /users/saml/idp_sign_out (IdP-initiated logout)' do
|
89
|
+
it 'routes to Devise::SamlSessionsController#idp_sign_out' do
|
90
|
+
expect(get: '/users/saml/idp_sign_out').to route_to(controller: 'devise/saml_sessions', action: 'idp_sign_out')
|
91
|
+
expect(get: idp_destroy_sso_user_session_path).to route_to(controller: 'devise/saml_sessions', action: 'idp_sign_out')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'POST /users/saml/idp_sign_out (IdP-initiated logout)' do
|
96
|
+
it 'routes to Devise::SamlSessionsController#idp_sign_out' do
|
97
|
+
expect(post: '/users/saml/idp_sign_out').to route_to(controller: 'devise/saml_sessions', action: 'idp_sign_out')
|
98
|
+
expect(post: idp_destroy_sso_user_session_path).to route_to(controller: 'devise/saml_sessions', action: 'idp_sign_out')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
1
3
|
RSpec.configure do |config|
|
2
4
|
config.run_all_when_everything_filtered = true
|
3
5
|
config.filter_run :focus
|
@@ -28,8 +30,13 @@ RSpec.configure do |config|
|
|
28
30
|
Devise.saml_session_index_key = @original_saml_session_index_key
|
29
31
|
Devise.idp_settings_adapter = nil
|
30
32
|
end
|
33
|
+
|
34
|
+
config.after :suite do
|
35
|
+
FileUtils.rm_rf($working_directory) if $working_directory
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
require 'support/rails_app'
|
34
40
|
|
41
|
+
require "action_controller" # https://github.com/heartcombo/responders/pull/95
|
35
42
|
require 'devise_saml_authenticatable'
|
data/spec/support/Gemfile.rails4
CHANGED
@@ -4,20 +4,38 @@ source 'https://rubygems.org'
|
|
4
4
|
gemspec path: '../..'
|
5
5
|
|
6
6
|
group :test do
|
7
|
-
gem 'rake'
|
8
7
|
gem 'rspec', '~> 3.0'
|
9
8
|
gem 'rails', '~> 4.0'
|
10
|
-
gem 'rspec-rails'
|
11
|
-
gem 'sqlite3'
|
9
|
+
gem 'rspec-rails', '~> 3.9'
|
10
|
+
gem 'sqlite3', '~> 1.3.6'
|
12
11
|
gem 'capybara'
|
13
12
|
gem 'poltergeist'
|
14
13
|
|
15
14
|
# Lock down versions of gems for older versions of Ruby
|
16
|
-
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.
|
17
|
-
gem '
|
18
|
-
|
15
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
|
16
|
+
gem 'rake', '~> 12.2'
|
17
|
+
else
|
18
|
+
gem 'rake'
|
19
19
|
end
|
20
|
+
|
20
21
|
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
|
21
22
|
gem 'devise', '~> 3.5'
|
23
|
+
gem 'minitest', '~> 5.11.0'
|
24
|
+
gem 'nokogiri', '~> 1.6.8'
|
25
|
+
gem 'public_suffix', '~> 2.0.5'
|
26
|
+
end
|
27
|
+
|
28
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
|
29
|
+
gem 'responders', '~> 1.0'
|
30
|
+
elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
31
|
+
gem 'responders', '~> 2.0'
|
32
|
+
end
|
33
|
+
|
34
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.2")
|
35
|
+
gem 'byebug', '~> 9.0'
|
36
|
+
elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
|
37
|
+
gem 'byebug', '~> 10.0'
|
38
|
+
elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
39
|
+
gem 'byebug', '~> 11.0.0'
|
22
40
|
end
|
23
41
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in devise_saml_authenticatable.gemspec
|
4
|
+
gemspec path: '../..'
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem 'rake'
|
8
|
+
gem 'rspec', '~> 3.0'
|
9
|
+
gem 'rails', '~> 5.0.0'
|
10
|
+
gem 'rspec-rails', '~> 3.9'
|
11
|
+
gem 'sqlite3', '~> 1.3.6'
|
12
|
+
gem 'capybara'
|
13
|
+
gem 'poltergeist'
|
14
|
+
|
15
|
+
# Lock down versions of gems for older versions of Ruby
|
16
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
17
|
+
gem 'responders', '~> 2.4'
|
18
|
+
end
|
19
|
+
|
20
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
|
21
|
+
gem 'byebug', '~> 10.0'
|
22
|
+
elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
23
|
+
gem 'byebug', '~> 11.0.0'
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in devise_saml_authenticatable.gemspec
|
4
|
+
gemspec path: '../..'
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem 'rake'
|
8
|
+
gem 'rspec', '~> 3.0'
|
9
|
+
gem 'rails', '~> 5.1.0'
|
10
|
+
gem 'rspec-rails', '~> 3.9'
|
11
|
+
gem 'sqlite3', '~> 1.3.6'
|
12
|
+
gem 'capybara'
|
13
|
+
gem 'poltergeist'
|
14
|
+
|
15
|
+
# Lock down versions of gems for older versions of Ruby
|
16
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
17
|
+
gem 'responders', '~> 2.4'
|
18
|
+
end
|
19
|
+
|
20
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
|
21
|
+
gem 'byebug', '~> 10.0'
|
22
|
+
elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
23
|
+
gem 'byebug', '~> 11.0.0'
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in devise_saml_authenticatable.gemspec
|
4
|
+
gemspec path: '../..'
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem 'rake'
|
8
|
+
gem 'rspec', '~> 3.0'
|
9
|
+
gem 'rails', '~> 5.2'
|
10
|
+
gem 'rspec-rails', '~> 3.9'
|
11
|
+
gem 'sqlite3', '~> 1.3.6'
|
12
|
+
gem 'capybara'
|
13
|
+
gem 'poltergeist'
|
14
|
+
|
15
|
+
# Lock down versions of gems for older versions of Ruby
|
16
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
17
|
+
gem 'responders', '~> 2.4'
|
18
|
+
end
|
19
|
+
|
20
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.3")
|
21
|
+
gem 'byebug', '~> 10.0'
|
22
|
+
elsif Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.4")
|
23
|
+
gem 'byebug', '~> 11.0.0'
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"urn:mace:dir:attribute-def:first_name": "first_name"
|
2
|
+
"first_name": "first_name"
|
3
|
+
"firstName": "first_name"
|
4
|
+
"firstname": "first_name"
|
5
|
+
"urn:mace:dir:attribute-def:last_name": "last_name"
|
6
|
+
"last_name": "last_name"
|
7
|
+
"lastName": "last_name"
|
8
|
+
"lastname": "last_name"
|
9
|
+
"urn:mace:dir:attribute-def:email": "email"
|
10
|
+
"email_address": "email"
|
11
|
+
"emailAddress": "email"
|
12
|
+
"email": "email"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class AttributeMapResolver < DeviseSamlAuthenticatable::DefaultAttributeMapResolver
|
2
|
+
def attribute_map
|
3
|
+
issuer = saml_response.issuers.first
|
4
|
+
Rails.logger.info("[#{self.class.name}] issuer=#{issuer.inspect}")
|
5
|
+
if issuer == "http://localhost:8009/saml/auth"
|
6
|
+
{
|
7
|
+
"myemailaddress" => "email",
|
8
|
+
"myname" => "name",
|
9
|
+
}
|
10
|
+
else
|
11
|
+
{}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -2,15 +2,15 @@ class IdpSettingsAdapter
|
|
2
2
|
def self.settings(idp_entity_id)
|
3
3
|
if idp_entity_id == "http://localhost:8020/saml/metadata"
|
4
4
|
{
|
5
|
-
assertion_consumer_service_url: "
|
5
|
+
assertion_consumer_service_url: "http://localhost:8020/users/saml/auth",
|
6
6
|
assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
7
|
-
name_identifier_format: "urn:oasis:names:tc:SAML:
|
7
|
+
name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
|
8
8
|
issuer: "sp_issuer",
|
9
9
|
idp_entity_id: "http://localhost:8020/saml/metadata",
|
10
10
|
authn_context: "",
|
11
|
-
idp_slo_target_url: "http://
|
12
|
-
idp_sso_target_url: "http://
|
13
|
-
|
11
|
+
idp_slo_target_url: "http://localhost:8010/saml/logout",
|
12
|
+
idp_sso_target_url: "http://localhost:8010/saml/auth",
|
13
|
+
idp_cert_fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
|
14
14
|
}
|
15
15
|
else
|
16
16
|
{}
|
@@ -1,9 +1,15 @@
|
|
1
1
|
# Set up a SAML IdP
|
2
2
|
|
3
|
+
@email_address_attribute_key = ENV.fetch("EMAIL_ADDRESS_ATTRIBUTE_KEY", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")
|
4
|
+
@name_attribute_key = ENV.fetch("NAME_ATTRIBUTE_KEY", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
|
3
5
|
@include_subject_in_attributes = ENV.fetch('INCLUDE_SUBJECT_IN_ATTRIBUTES')
|
4
6
|
@valid_destination = ENV.fetch('VALID_DESTINATION', "true")
|
5
7
|
|
6
|
-
|
8
|
+
if Rails::VERSION::MAJOR < 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR < 2)
|
9
|
+
gsub_file 'config/secrets.yml', /secret_key_base:.*$/, 'secret_key_base: "34814fd41f91c493b89aa01ac73c44d241a31245b5bc5542fa4b7317525e1dcfa60ba947b3d085e4e229456fdee0d8af6aac6a63cf750d807ea6fe5d853dff4a"'
|
10
|
+
end
|
11
|
+
|
12
|
+
gem 'ruby-saml-idp', '~> 0.3.3'
|
7
13
|
gem 'thin'
|
8
14
|
|
9
15
|
insert_into_file('Gemfile', after: /\z/) {
|
@@ -11,6 +17,7 @@ insert_into_file('Gemfile', after: /\z/) {
|
|
11
17
|
# Lock down versions of gems for older versions of Ruby
|
12
18
|
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("2.1")
|
13
19
|
gem 'devise', '~> 3.5'
|
20
|
+
gem 'nokogiri', '~> 1.6.8'
|
14
21
|
end
|
15
22
|
GEMFILE
|
16
23
|
}
|
data/spec/support/rails_app.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
require
|
1
|
+
require "open3"
|
2
|
+
require "socket"
|
3
|
+
require "tempfile"
|
4
|
+
require "timeout"
|
5
|
+
|
6
|
+
APP_READY_TIMEOUT ||= 30
|
2
7
|
|
3
8
|
def sh!(cmd)
|
4
9
|
unless system(cmd)
|
@@ -7,38 +12,127 @@ def sh!(cmd)
|
|
7
12
|
end
|
8
13
|
|
9
14
|
def app_ready?(pid, port)
|
10
|
-
Process.getpgid(pid) &&
|
11
|
-
|
15
|
+
Process.getpgid(pid) && port_open?(port)
|
16
|
+
rescue Errno::ESRCH
|
17
|
+
false
|
12
18
|
end
|
13
19
|
|
14
20
|
def create_app(name, env = {})
|
15
|
-
|
16
|
-
rails_new_options
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
puts "[#{name}] Creating Rails app"
|
22
|
+
rails_new_options = %w[-T -J -S --skip-spring --skip-listen --skip-bootsnap]
|
23
|
+
rails_new_options << "-O" if name == "idp"
|
24
|
+
with_clean_env do
|
25
|
+
Dir.chdir(working_directory) do
|
26
|
+
FileUtils.rm_rf(name)
|
27
|
+
puts("rails _#{Rails.version}_ new #{name} #{rails_new_options.join(" ")} -m #{File.expand_path("../#{name}_template.rb", __FILE__)}")
|
28
|
+
system(env, "rails", "_#{Rails.version}_", "new", name, *rails_new_options, "-m", File.expand_path("../#{name}_template.rb", __FILE__))
|
29
|
+
end
|
20
30
|
end
|
21
31
|
end
|
22
32
|
|
23
33
|
def start_app(name, port, options = {})
|
34
|
+
puts "[#{name}] Starting Rails app"
|
24
35
|
pid = nil
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
36
|
+
app_bundle_install(name)
|
37
|
+
|
38
|
+
with_clean_env do
|
39
|
+
Dir.chdir(app_dir(name)) do
|
40
|
+
pid = Process.spawn(app_env(name), "bundle exec rails server -p #{port} -e production", chdir: app_dir(name), out: "log/#{name}.log", err: "log/#{name}.err.log")
|
41
|
+
begin
|
42
|
+
Timeout.timeout(APP_READY_TIMEOUT) do
|
43
|
+
sleep 1 until app_ready?(pid, port)
|
44
|
+
end
|
45
|
+
if app_ready?(pid, port)
|
46
|
+
puts "[#{name}] Launched #{name} on port #{port} (pid #{pid})..."
|
47
|
+
else
|
48
|
+
raise "#{name} failed after starting"
|
49
|
+
end
|
50
|
+
rescue Timeout::Error
|
32
51
|
raise "#{name} failed to start"
|
33
52
|
end
|
34
53
|
end
|
35
54
|
end
|
36
55
|
pid
|
56
|
+
rescue RuntimeError => e
|
57
|
+
warn "=== #{name}"
|
58
|
+
Dir.chdir(app_dir(name)) do
|
59
|
+
warn File.read("log/#{name}.log") if File.exist?("log/#{name}.log")
|
60
|
+
warn File.read("log/#{name}.err.log") if File.exist?("log/#{name}.err.log")
|
61
|
+
end
|
62
|
+
raise e
|
37
63
|
end
|
38
64
|
|
39
|
-
def stop_app(pid)
|
65
|
+
def stop_app(name, pid)
|
40
66
|
if pid
|
41
67
|
Process.kill(:INT, pid)
|
42
68
|
Process.wait(pid)
|
43
69
|
end
|
70
|
+
Dir.chdir(app_dir(name)) do
|
71
|
+
if File.exist?("log/#{name}.log")
|
72
|
+
puts "=== [#{name}] stdout"
|
73
|
+
puts File.read("log/#{name}.log")
|
74
|
+
end
|
75
|
+
if File.exist?("log/#{name}.err.log")
|
76
|
+
warn "=== [#{name}] stderr"
|
77
|
+
warn File.read("log/#{name}.err.log")
|
78
|
+
end
|
79
|
+
if File.exist?("log/production.log")
|
80
|
+
puts "=== [#{name}] Rails logs"
|
81
|
+
puts File.read("log/production.log")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def port_open?(port)
|
87
|
+
Timeout::timeout(1) do
|
88
|
+
begin
|
89
|
+
s = TCPSocket.new('localhost', port)
|
90
|
+
s.close
|
91
|
+
return true
|
92
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EADDRNOTAVAIL
|
93
|
+
# try 127.0.0.1
|
94
|
+
end
|
95
|
+
begin
|
96
|
+
s = TCPSocket.new('127.0.0.1', port)
|
97
|
+
s.close
|
98
|
+
return true
|
99
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
rescue Timeout::Error
|
104
|
+
false
|
105
|
+
end
|
106
|
+
|
107
|
+
def app_bundle_install(name)
|
108
|
+
with_clean_env do
|
109
|
+
Open3.popen3(app_env(name), "bundle install", chdir: app_dir(name)) do |stdin, stdout, stderr, thread|
|
110
|
+
stdin.close
|
111
|
+
exit_status = thread.value
|
112
|
+
|
113
|
+
puts stdout.read
|
114
|
+
warn stderr.read
|
115
|
+
raise "bundle install failed" unless exit_status.success?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def app_dir(name)
|
121
|
+
File.join(working_directory, name)
|
122
|
+
end
|
123
|
+
|
124
|
+
def app_env(name)
|
125
|
+
{"BUNDLE_GEMFILE" => File.join(app_dir(name), "Gemfile"), "RAILS_ENV" => "production"}
|
126
|
+
end
|
127
|
+
|
128
|
+
def working_directory
|
129
|
+
$working_directory ||= Dir.mktmpdir("dsa_test")
|
130
|
+
end
|
131
|
+
|
132
|
+
def with_clean_env(&blk)
|
133
|
+
if Bundler.respond_to?(:with_original_env)
|
134
|
+
Bundler.with_original_env(&blk)
|
135
|
+
else
|
136
|
+
Bundler.with_clean_env(&blk)
|
137
|
+
end
|
44
138
|
end
|