nucleus 0.2.0 → 0.3.1
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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +9 -0
- data/README.md +43 -72
- data/lib/nucleus/adapter_resolver.rb +3 -3
- data/lib/nucleus/adapters/base_adapter.rb +109 -109
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/application.rb +111 -111
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/cloud_foundry_v2.rb +141 -141
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/data.rb +97 -97
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +5 -5
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/lifecycle.rb +41 -41
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +6 -6
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/regions.rb +33 -33
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +6 -6
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/vars.rb +80 -80
- data/lib/nucleus/adapters/v1/heroku/app_states.rb +57 -57
- data/lib/nucleus/adapters/v1/heroku/data.rb +78 -78
- data/lib/nucleus/adapters/v1/heroku/heroku.rb +146 -146
- data/lib/nucleus/adapters/v1/heroku/lifecycle.rb +51 -51
- data/lib/nucleus/adapters/v1/heroku/logs.rb +2 -2
- data/lib/nucleus/adapters/v1/heroku/regions.rb +42 -42
- data/lib/nucleus/adapters/v1/heroku/services.rb +168 -168
- data/lib/nucleus/adapters/v1/heroku/vars.rb +65 -65
- data/lib/nucleus/adapters/v1/openshift_v2/app_states.rb +68 -68
- data/lib/nucleus/adapters/v1/openshift_v2/application.rb +1 -1
- data/lib/nucleus/adapters/v1/openshift_v2/data.rb +96 -96
- data/lib/nucleus/adapters/v1/openshift_v2/lifecycle.rb +60 -60
- data/lib/nucleus/adapters/v1/openshift_v2/logs.rb +106 -106
- data/lib/nucleus/adapters/v1/openshift_v2/openshift_v2.rb +125 -125
- data/lib/nucleus/adapters/v1/openshift_v2/regions.rb +58 -58
- data/lib/nucleus/adapters/v1/openshift_v2/services.rb +173 -173
- data/lib/nucleus/adapters/v1/openshift_v2/vars.rb +49 -49
- data/lib/nucleus/adapters/v1/stub_adapter.rb +464 -464
- data/lib/nucleus/core/adapter_extensions/auth/auth_client.rb +44 -44
- data/lib/nucleus/core/adapter_extensions/auth/expiring_token_auth_client.rb +53 -53
- data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +3 -3
- data/lib/nucleus/core/adapter_extensions/auth/o_auth2_auth_client.rb +95 -95
- data/lib/nucleus/core/adapter_extensions/auth/token_auth_client.rb +36 -36
- data/lib/nucleus/core/adapter_extensions/http_client.rb +5 -5
- data/lib/nucleus/core/common/files/archive_extractor.rb +1 -1
- data/lib/nucleus/core/common/files/archiver.rb +2 -2
- data/lib/nucleus/core/file_handling/file_manager.rb +64 -64
- data/lib/nucleus/core/file_handling/git_deployer.rb +133 -133
- data/lib/nucleus/core/import/adapter_configuration.rb +53 -53
- data/lib/nucleus/scripts/initialize_config_defaults.rb +26 -26
- data/lib/nucleus/version.rb +1 -1
- data/nucleus.gemspec +2 -2
- data/spec/integration/api/auth_spec.rb +3 -3
- data/spec/spec_helper.rb +98 -98
- data/spec/test_suites.rake +1 -1
- data/spec/unit/adapters/git_deployer_spec.rb +262 -262
- data/spec/unit/common/helpers/auth_helper_spec.rb +1 -1
- data/tasks/evaluation.rake +1 -1
- data/wiki/adapter_tests.md +0 -7
- data/wiki/implement_new_adapter.md +1 -1
- metadata +4 -20
- data/config/adapters/cloud_control.yml +0 -32
- data/lib/nucleus/adapters/v1/cloud_control/application.rb +0 -108
- data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +0 -27
- data/lib/nucleus/adapters/v1/cloud_control/buildpacks.rb +0 -23
- data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +0 -153
- data/lib/nucleus/adapters/v1/cloud_control/data.rb +0 -76
- data/lib/nucleus/adapters/v1/cloud_control/domains.rb +0 -68
- data/lib/nucleus/adapters/v1/cloud_control/lifecycle.rb +0 -27
- data/lib/nucleus/adapters/v1/cloud_control/log_poller.rb +0 -71
- data/lib/nucleus/adapters/v1/cloud_control/logs.rb +0 -103
- data/lib/nucleus/adapters/v1/cloud_control/regions.rb +0 -32
- data/lib/nucleus/adapters/v1/cloud_control/scaling.rb +0 -17
- data/lib/nucleus/adapters/v1/cloud_control/semantic_errors.rb +0 -31
- data/lib/nucleus/adapters/v1/cloud_control/services.rb +0 -162
- data/lib/nucleus/adapters/v1/cloud_control/token.rb +0 -17
- data/lib/nucleus/adapters/v1/cloud_control/vars.rb +0 -88
@@ -1,57 +1,57 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class Heroku < Stub
|
5
|
-
# AppStates for Heroku, or the logic to determine the current application state
|
6
|
-
module AppStates
|
7
|
-
private
|
8
|
-
|
9
|
-
def application_state(app, retrieved_dynos = nil)
|
10
|
-
# 1: created, both repo and slug are nil
|
11
|
-
return Enums::ApplicationStates::CREATED unless repo_or_slug_content?(app)
|
12
|
-
|
13
|
-
# all subsequent states require dynos to be determined
|
14
|
-
dynos = retrieved_dynos ? retrieved_dynos : dynos(app[:id])
|
15
|
-
|
16
|
-
# 2: deployed if no dynos assigned
|
17
|
-
return Enums::ApplicationStates::DEPLOYED if dynos.empty?
|
18
|
-
|
19
|
-
# 3: stopped if maintenance
|
20
|
-
return Enums::ApplicationStates::STOPPED if app[:maintenance] || dynos_not_running?(dynos)
|
21
|
-
|
22
|
-
# 4: running if no maintenance (checked above) and at least ony dyno is up
|
23
|
-
return Enums::ApplicationStates::RUNNING if dyno_states(dynos).include?('up')
|
24
|
-
|
25
|
-
# 5: idle if all dynos are idling
|
26
|
-
return Enums::ApplicationStates::IDLE if dynos_idle?(dynos)
|
27
|
-
|
28
|
-
# arriving here the above states do not catch all states of the Heroku app, which should not happen ;-)
|
29
|
-
log.debug("Faild to determine state for: #{app}, #{dynos}")
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
def repo_or_slug_content?(app)
|
34
|
-
return true if !app[:repo_size].nil? && app[:repo_size].to_i > 0
|
35
|
-
return true if !app[:slug_size].nil? && app[:slug_size].to_i > 0
|
36
|
-
false
|
37
|
-
end
|
38
|
-
|
39
|
-
def dyno_states(dynos)
|
40
|
-
dynos.collect { |dyno| dyno[:state] }.compact.uniq
|
41
|
-
end
|
42
|
-
|
43
|
-
def dynos_idle?(dynos)
|
44
|
-
dyno_states = dyno_states(dynos)
|
45
|
-
dyno_states.length == 1 && dyno_states[0] == 'idle'
|
46
|
-
end
|
47
|
-
|
48
|
-
def dynos_not_running?(dynos)
|
49
|
-
dynos.empty? || dyno_states(dynos).reject do |state|
|
50
|
-
%w(crashed down starting).include?(state)
|
51
|
-
end.empty?
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class Heroku < Stub
|
5
|
+
# AppStates for Heroku, or the logic to determine the current application state
|
6
|
+
module AppStates
|
7
|
+
private
|
8
|
+
|
9
|
+
def application_state(app, retrieved_dynos = nil)
|
10
|
+
# 1: created, both repo and slug are nil
|
11
|
+
return Enums::ApplicationStates::CREATED unless repo_or_slug_content?(app)
|
12
|
+
|
13
|
+
# all subsequent states require dynos to be determined
|
14
|
+
dynos = retrieved_dynos ? retrieved_dynos : dynos(app[:id])
|
15
|
+
|
16
|
+
# 2: deployed if no dynos assigned
|
17
|
+
return Enums::ApplicationStates::DEPLOYED if dynos.empty?
|
18
|
+
|
19
|
+
# 3: stopped if maintenance
|
20
|
+
return Enums::ApplicationStates::STOPPED if app[:maintenance] || dynos_not_running?(dynos)
|
21
|
+
|
22
|
+
# 4: running if no maintenance (checked above) and at least ony dyno is up
|
23
|
+
return Enums::ApplicationStates::RUNNING if dyno_states(dynos).include?('up')
|
24
|
+
|
25
|
+
# 5: idle if all dynos are idling
|
26
|
+
return Enums::ApplicationStates::IDLE if dynos_idle?(dynos)
|
27
|
+
|
28
|
+
# arriving here the above states do not catch all states of the Heroku app, which should not happen ;-)
|
29
|
+
log.debug("Faild to determine state for: #{app}, #{dynos}")
|
30
|
+
raise Errors::UnknownAdapterCallError, 'Could not determine app state. Please verify the Heroku adapter'
|
31
|
+
end
|
32
|
+
|
33
|
+
def repo_or_slug_content?(app)
|
34
|
+
return true if !app[:repo_size].nil? && app[:repo_size].to_i > 0
|
35
|
+
return true if !app[:slug_size].nil? && app[:slug_size].to_i > 0
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def dyno_states(dynos)
|
40
|
+
dynos.collect { |dyno| dyno[:state] }.compact.uniq
|
41
|
+
end
|
42
|
+
|
43
|
+
def dynos_idle?(dynos)
|
44
|
+
dyno_states = dyno_states(dynos)
|
45
|
+
dyno_states.length == 1 && dyno_states[0] == 'idle'
|
46
|
+
end
|
47
|
+
|
48
|
+
def dynos_not_running?(dynos)
|
49
|
+
dynos.empty? || dyno_states(dynos).reject do |state|
|
50
|
+
%w(crashed down starting).include?(state)
|
51
|
+
end.empty?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,78 +1,78 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class Heroku < Stub
|
5
|
-
module Data
|
6
|
-
# @see Stub#deploy
|
7
|
-
def deploy(application_id, file, file_compression_format)
|
8
|
-
app = get("/apps/#{application_id}").body
|
9
|
-
account = get('/account').body
|
10
|
-
repo_name = "nucleus.app.repo.heroku.deploy.#{application_id}.#{SecureRandom.uuid}"
|
11
|
-
# clone, extract, push and finally delete cloned repository (sync)
|
12
|
-
with_ssh_key do
|
13
|
-
GitDeployer.new(repo_name, app[:git_url], account[:email]).deploy(file, file_compression_format)
|
14
|
-
end
|
15
|
-
|
16
|
-
return unless application_state(app) == Enums::ApplicationStates::CREATED
|
17
|
-
# instantly remove all initially added dynos to keep the 'deployed' state on first deployment
|
18
|
-
log.debug 'state before deployment was \'created\', scale web to 0'
|
19
|
-
scale_web(application_id, 0)
|
20
|
-
end
|
21
|
-
|
22
|
-
# @see Stub#download
|
23
|
-
def download(application_id, compression_format)
|
24
|
-
# Only possible with git, not with HTTP builds
|
25
|
-
app = get("/apps/#{application_id}").body
|
26
|
-
if application_state(app) == Enums::ApplicationStates::CREATED
|
27
|
-
|
28
|
-
end
|
29
|
-
# compress files to archive but exclude the .git repo
|
30
|
-
repo_name = "nucleus.app.repo.heroku.download.#{application_id}.#{SecureRandom.uuid}"
|
31
|
-
with_ssh_key do
|
32
|
-
GitDeployer.new(repo_name, app[:git_url], nil).download(compression_format, true)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# @see Stub#rebuild
|
37
|
-
def rebuild(application_id)
|
38
|
-
app = get("/apps/#{application_id}").body
|
39
|
-
if application_state(app) == Enums::ApplicationStates::CREATED
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
account = get('/account').body
|
44
|
-
repo_name = "nucleus.app.repo.heroku.rebuild.#{application_id}.#{SecureRandom.uuid}"
|
45
|
-
|
46
|
-
with_ssh_key do
|
47
|
-
GitDeployer.new(repo_name, app[:git_url], account[:email]).trigger_build
|
48
|
-
end
|
49
|
-
|
50
|
-
# return with updated application
|
51
|
-
application(application_id)
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def with_ssh_key
|
57
|
-
# load ssh key into cloud control
|
58
|
-
matches = nucleus_config.ssh.handler.public_key.match(/(.*)\s{1}(.*)\s{1}(.*)/)
|
59
|
-
key_id = register_key(matches[1], matches[2])
|
60
|
-
return yield
|
61
|
-
ensure
|
62
|
-
# unload ssh key, allow 404 if the key couldn't be registered at first
|
63
|
-
delete("/account/keys/#{key_id}") if key_id
|
64
|
-
end
|
65
|
-
|
66
|
-
def register_key(type, key)
|
67
|
-
# skip if the key is already registered
|
68
|
-
installed_keys = get('/account/keys').body
|
69
|
-
return nil if installed_keys.any? { |installed_key| installed_key[:public_key].include?(key) }
|
70
|
-
|
71
|
-
key_name = "nucleus-#{SecureRandom.uuid}"
|
72
|
-
post('/account/keys', body: { public_key: [type, key, key_name].join(' ') }).body[:id]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class Heroku < Stub
|
5
|
+
module Data
|
6
|
+
# @see Stub#deploy
|
7
|
+
def deploy(application_id, file, file_compression_format)
|
8
|
+
app = get("/apps/#{application_id}").body
|
9
|
+
account = get('/account').body
|
10
|
+
repo_name = "nucleus.app.repo.heroku.deploy.#{application_id}.#{SecureRandom.uuid}"
|
11
|
+
# clone, extract, push and finally delete cloned repository (sync)
|
12
|
+
with_ssh_key do
|
13
|
+
GitDeployer.new(repo_name, app[:git_url], account[:email]).deploy(file, file_compression_format)
|
14
|
+
end
|
15
|
+
|
16
|
+
return unless application_state(app) == Enums::ApplicationStates::CREATED
|
17
|
+
# instantly remove all initially added dynos to keep the 'deployed' state on first deployment
|
18
|
+
log.debug 'state before deployment was \'created\', scale web to 0'
|
19
|
+
scale_web(application_id, 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see Stub#download
|
23
|
+
def download(application_id, compression_format)
|
24
|
+
# Only possible with git, not with HTTP builds
|
25
|
+
app = get("/apps/#{application_id}").body
|
26
|
+
if application_state(app) == Enums::ApplicationStates::CREATED
|
27
|
+
raise Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be downloaded'
|
28
|
+
end
|
29
|
+
# compress files to archive but exclude the .git repo
|
30
|
+
repo_name = "nucleus.app.repo.heroku.download.#{application_id}.#{SecureRandom.uuid}"
|
31
|
+
with_ssh_key do
|
32
|
+
GitDeployer.new(repo_name, app[:git_url], nil).download(compression_format, true)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see Stub#rebuild
|
37
|
+
def rebuild(application_id)
|
38
|
+
app = get("/apps/#{application_id}").body
|
39
|
+
if application_state(app) == Enums::ApplicationStates::CREATED
|
40
|
+
raise Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be rebuild'
|
41
|
+
end
|
42
|
+
|
43
|
+
account = get('/account').body
|
44
|
+
repo_name = "nucleus.app.repo.heroku.rebuild.#{application_id}.#{SecureRandom.uuid}"
|
45
|
+
|
46
|
+
with_ssh_key do
|
47
|
+
GitDeployer.new(repo_name, app[:git_url], account[:email]).trigger_build
|
48
|
+
end
|
49
|
+
|
50
|
+
# return with updated application
|
51
|
+
application(application_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def with_ssh_key
|
57
|
+
# load ssh key into cloud control
|
58
|
+
matches = nucleus_config.ssh.handler.public_key.match(/(.*)\s{1}(.*)\s{1}(.*)/)
|
59
|
+
key_id = register_key(matches[1], matches[2])
|
60
|
+
return yield
|
61
|
+
ensure
|
62
|
+
# unload ssh key, allow 404 if the key couldn't be registered at first
|
63
|
+
delete("/account/keys/#{key_id}") if key_id
|
64
|
+
end
|
65
|
+
|
66
|
+
def register_key(type, key)
|
67
|
+
# skip if the key is already registered
|
68
|
+
installed_keys = get('/account/keys').body
|
69
|
+
return nil if installed_keys.any? { |installed_key| installed_key[:public_key].include?(key) }
|
70
|
+
|
71
|
+
key_name = "nucleus-#{SecureRandom.uuid}"
|
72
|
+
post('/account/keys', body: { public_key: [type, key, key_name].join(' ') }).body[:id]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,146 +1,146 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
# The {Heroku} adapter is designed to support the Heroku platform API.<br>
|
5
|
-
# <br>
|
6
|
-
# The Nucleus API is fully supported, there are no known issues.
|
7
|
-
# @see https://devcenter.heroku.com/articles/platform-api-reference Heroku Platform API
|
8
|
-
class Heroku < Stub
|
9
|
-
include Nucleus::Logging
|
10
|
-
include Nucleus::Adapters::V1::Heroku::Authentication
|
11
|
-
include Nucleus::Adapters::V1::Heroku::Application
|
12
|
-
include Nucleus::Adapters::V1::Heroku::AppStates
|
13
|
-
include Nucleus::Adapters::V1::Heroku::Buildpacks
|
14
|
-
include Nucleus::Adapters::V1::Heroku::Data
|
15
|
-
include Nucleus::Adapters::V1::Heroku::Domains
|
16
|
-
include Nucleus::Adapters::V1::Heroku::Logs
|
17
|
-
include Nucleus::Adapters::V1::Heroku::Lifecycle
|
18
|
-
include Nucleus::Adapters::V1::Heroku::Regions
|
19
|
-
include Nucleus::Adapters::V1::Heroku::Scaling
|
20
|
-
include Nucleus::Adapters::V1::Heroku::Services
|
21
|
-
include Nucleus::Adapters::V1::Heroku::SemanticErrors
|
22
|
-
include Nucleus::Adapters::V1::Heroku::Vars
|
23
|
-
|
24
|
-
def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
|
25
|
-
super(endpoint_url, endpoint_app_domain, check_certificates)
|
26
|
-
end
|
27
|
-
|
28
|
-
def handle_error(error_response)
|
29
|
-
handle_422(error_response)
|
30
|
-
if error_response.status == 404 && error_response.body[:id] == 'not_found'
|
31
|
-
|
32
|
-
elsif error_response.status == 503
|
33
|
-
|
34
|
-
end
|
35
|
-
# error still unhandled, will result in a 500, server error
|
36
|
-
log.warn "Heroku error still unhandled: #{error_response}"
|
37
|
-
end
|
38
|
-
|
39
|
-
def handle_422(error_response)
|
40
|
-
return unless error_response.status == 422
|
41
|
-
if error_response.body[:id] == 'invalid_params'
|
42
|
-
|
43
|
-
elsif error_response.body[:id] == 'verification_required'
|
44
|
-
fail_with(:need_verification, [error_response.body[:message]])
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def install_runtimes(application_id, runtimes)
|
51
|
-
runtime_instructions = runtimes.collect { |buildpack_url| { buildpack: buildpack_url } }
|
52
|
-
log.debug "Install runtimes: #{runtime_instructions}"
|
53
|
-
buildpack_instructions = { updates: runtime_instructions }
|
54
|
-
put("/apps/#{application_id}/buildpack-installations", body: buildpack_instructions)
|
55
|
-
end
|
56
|
-
|
57
|
-
def runtimes_to_install(application)
|
58
|
-
return [] unless application[:runtimes]
|
59
|
-
runtimes_to_install = []
|
60
|
-
application[:runtimes].each do |runtime_identifier|
|
61
|
-
# we do not need to install native buildpacks
|
62
|
-
# TODO: 2 options for heroku runtime handling
|
63
|
-
# a) skip native, fails when native required and not in list
|
64
|
-
# b) (current) use native, fails when others (additional) are in the list
|
65
|
-
# next if native_runtime?(runtime_identifier)
|
66
|
-
runtime_is_url = runtime_identifier =~ /\A#{URI.regexp}\z/
|
67
|
-
runtime_url = find_runtime(runtime_identifier)
|
68
|
-
runtime_is_valid = runtime_url || runtime_is_url
|
69
|
-
fail_with(:invalid_runtime, [runtime_identifier]) unless runtime_is_valid
|
70
|
-
# if runtime identifier is valid, we need to install the runtime
|
71
|
-
runtimes_to_install.push(runtime_is_url ? runtime_identifier : runtime_url)
|
72
|
-
end
|
73
|
-
# heroku does not know the 'runtimes' property and would crash if present
|
74
|
-
application.delete :runtimes
|
75
|
-
runtimes_to_install
|
76
|
-
end
|
77
|
-
|
78
|
-
def heroku_api
|
79
|
-
::Heroku::API.new(headers: headers)
|
80
|
-
end
|
81
|
-
|
82
|
-
def headers
|
83
|
-
super.merge(
|
84
|
-
'Accept' => 'application/vnd.heroku+json; version=3',
|
85
|
-
'Content-Type' => 'application/json'
|
86
|
-
)
|
87
|
-
end
|
88
|
-
|
89
|
-
def installed_buildpacks(application_id)
|
90
|
-
buildpacks = get("/apps/#{application_id}/buildpack-installations").body
|
91
|
-
return [] if buildpacks.empty?
|
92
|
-
buildpacks.collect do |buildpack|
|
93
|
-
buildpack[:buildpack][:url]
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def application_instances(application_id)
|
98
|
-
formations = get("/apps/#{application_id}/formation").body
|
99
|
-
web_formation = formations.find { |formation| formation[:type] == 'web' }
|
100
|
-
return web_formation[:quantity] unless web_formation.nil?
|
101
|
-
# if no web formation was detected, there is no instance available
|
102
|
-
0
|
103
|
-
end
|
104
|
-
|
105
|
-
def dynos(application_id)
|
106
|
-
get("/apps/#{application_id}/dynos").body
|
107
|
-
end
|
108
|
-
|
109
|
-
def web_dynos(application_id, retrieved_dynos = nil)
|
110
|
-
all_dynos = retrieved_dynos ? retrieved_dynos : dynos(application_id)
|
111
|
-
all_dynos.find_all do |dyno|
|
112
|
-
dyno[:type] == 'web'
|
113
|
-
end.compact
|
114
|
-
end
|
115
|
-
|
116
|
-
def latest_release(application_id, retrieved_dynos = nil)
|
117
|
-
dynos = web_dynos(application_id, retrieved_dynos)
|
118
|
-
if dynos.nil? || dynos.empty?
|
119
|
-
log.debug 'no dynos for build detection, fallback to latest release version'
|
120
|
-
# this approach might be wrong if the app is rolled-back to a previous release
|
121
|
-
# However, if no dyno is active, this is the only option to identify the current release
|
122
|
-
latest_version = 0
|
123
|
-
latest_version_id = nil
|
124
|
-
get("/apps/#{application_id}/releases").body.each do |release|
|
125
|
-
if release[:version] > latest_version
|
126
|
-
latest_version = release[:version]
|
127
|
-
latest_version_id = release[:id]
|
128
|
-
end
|
129
|
-
end
|
130
|
-
else
|
131
|
-
latest_version = 0
|
132
|
-
latest_version_id = nil
|
133
|
-
dynos.each do |dyno|
|
134
|
-
if dyno[:release][:version] > latest_version
|
135
|
-
latest_version = dyno[:release][:version]
|
136
|
-
latest_version_id = dyno[:release][:id]
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
latest_version_id
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
# The {Heroku} adapter is designed to support the Heroku platform API.<br>
|
5
|
+
# <br>
|
6
|
+
# The Nucleus API is fully supported, there are no known issues.
|
7
|
+
# @see https://devcenter.heroku.com/articles/platform-api-reference Heroku Platform API
|
8
|
+
class Heroku < Stub
|
9
|
+
include Nucleus::Logging
|
10
|
+
include Nucleus::Adapters::V1::Heroku::Authentication
|
11
|
+
include Nucleus::Adapters::V1::Heroku::Application
|
12
|
+
include Nucleus::Adapters::V1::Heroku::AppStates
|
13
|
+
include Nucleus::Adapters::V1::Heroku::Buildpacks
|
14
|
+
include Nucleus::Adapters::V1::Heroku::Data
|
15
|
+
include Nucleus::Adapters::V1::Heroku::Domains
|
16
|
+
include Nucleus::Adapters::V1::Heroku::Logs
|
17
|
+
include Nucleus::Adapters::V1::Heroku::Lifecycle
|
18
|
+
include Nucleus::Adapters::V1::Heroku::Regions
|
19
|
+
include Nucleus::Adapters::V1::Heroku::Scaling
|
20
|
+
include Nucleus::Adapters::V1::Heroku::Services
|
21
|
+
include Nucleus::Adapters::V1::Heroku::SemanticErrors
|
22
|
+
include Nucleus::Adapters::V1::Heroku::Vars
|
23
|
+
|
24
|
+
def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
|
25
|
+
super(endpoint_url, endpoint_app_domain, check_certificates)
|
26
|
+
end
|
27
|
+
|
28
|
+
def handle_error(error_response)
|
29
|
+
handle_422(error_response)
|
30
|
+
if error_response.status == 404 && error_response.body[:id] == 'not_found'
|
31
|
+
raise Errors::AdapterResourceNotFoundError, error_response.body[:message]
|
32
|
+
elsif error_response.status == 503
|
33
|
+
raise Errors::PlatformUnavailableError, 'The Heroku API is currently not responding'
|
34
|
+
end
|
35
|
+
# error still unhandled, will result in a 500, server error
|
36
|
+
log.warn "Heroku error still unhandled: #{error_response}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_422(error_response)
|
40
|
+
return unless error_response.status == 422
|
41
|
+
if error_response.body[:id] == 'invalid_params'
|
42
|
+
raise Errors::SemanticAdapterRequestError, error_response.body[:message]
|
43
|
+
elsif error_response.body[:id] == 'verification_required'
|
44
|
+
fail_with(:need_verification, [error_response.body[:message]])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def install_runtimes(application_id, runtimes)
|
51
|
+
runtime_instructions = runtimes.collect { |buildpack_url| { buildpack: buildpack_url } }
|
52
|
+
log.debug "Install runtimes: #{runtime_instructions}"
|
53
|
+
buildpack_instructions = { updates: runtime_instructions }
|
54
|
+
put("/apps/#{application_id}/buildpack-installations", body: buildpack_instructions)
|
55
|
+
end
|
56
|
+
|
57
|
+
def runtimes_to_install(application)
|
58
|
+
return [] unless application[:runtimes]
|
59
|
+
runtimes_to_install = []
|
60
|
+
application[:runtimes].each do |runtime_identifier|
|
61
|
+
# we do not need to install native buildpacks
|
62
|
+
# TODO: 2 options for heroku runtime handling
|
63
|
+
# a) skip native, fails when native required and not in list
|
64
|
+
# b) (current) use native, fails when others (additional) are in the list
|
65
|
+
# next if native_runtime?(runtime_identifier)
|
66
|
+
runtime_is_url = runtime_identifier =~ /\A#{URI.regexp}\z/
|
67
|
+
runtime_url = find_runtime(runtime_identifier)
|
68
|
+
runtime_is_valid = runtime_url || runtime_is_url
|
69
|
+
fail_with(:invalid_runtime, [runtime_identifier]) unless runtime_is_valid
|
70
|
+
# if runtime identifier is valid, we need to install the runtime
|
71
|
+
runtimes_to_install.push(runtime_is_url ? runtime_identifier : runtime_url)
|
72
|
+
end
|
73
|
+
# heroku does not know the 'runtimes' property and would crash if present
|
74
|
+
application.delete :runtimes
|
75
|
+
runtimes_to_install
|
76
|
+
end
|
77
|
+
|
78
|
+
def heroku_api
|
79
|
+
::Heroku::API.new(headers: headers)
|
80
|
+
end
|
81
|
+
|
82
|
+
def headers
|
83
|
+
super.merge(
|
84
|
+
'Accept' => 'application/vnd.heroku+json; version=3',
|
85
|
+
'Content-Type' => 'application/json'
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
def installed_buildpacks(application_id)
|
90
|
+
buildpacks = get("/apps/#{application_id}/buildpack-installations").body
|
91
|
+
return [] if buildpacks.empty?
|
92
|
+
buildpacks.collect do |buildpack|
|
93
|
+
buildpack[:buildpack][:url]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def application_instances(application_id)
|
98
|
+
formations = get("/apps/#{application_id}/formation").body
|
99
|
+
web_formation = formations.find { |formation| formation[:type] == 'web' }
|
100
|
+
return web_formation[:quantity] unless web_formation.nil?
|
101
|
+
# if no web formation was detected, there is no instance available
|
102
|
+
0
|
103
|
+
end
|
104
|
+
|
105
|
+
def dynos(application_id)
|
106
|
+
get("/apps/#{application_id}/dynos").body
|
107
|
+
end
|
108
|
+
|
109
|
+
def web_dynos(application_id, retrieved_dynos = nil)
|
110
|
+
all_dynos = retrieved_dynos ? retrieved_dynos : dynos(application_id)
|
111
|
+
all_dynos.find_all do |dyno|
|
112
|
+
dyno[:type] == 'web'
|
113
|
+
end.compact
|
114
|
+
end
|
115
|
+
|
116
|
+
def latest_release(application_id, retrieved_dynos = nil)
|
117
|
+
dynos = web_dynos(application_id, retrieved_dynos)
|
118
|
+
if dynos.nil? || dynos.empty?
|
119
|
+
log.debug 'no dynos for build detection, fallback to latest release version'
|
120
|
+
# this approach might be wrong if the app is rolled-back to a previous release
|
121
|
+
# However, if no dyno is active, this is the only option to identify the current release
|
122
|
+
latest_version = 0
|
123
|
+
latest_version_id = nil
|
124
|
+
get("/apps/#{application_id}/releases").body.each do |release|
|
125
|
+
if release[:version] > latest_version
|
126
|
+
latest_version = release[:version]
|
127
|
+
latest_version_id = release[:id]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
else
|
131
|
+
latest_version = 0
|
132
|
+
latest_version_id = nil
|
133
|
+
dynos.each do |dyno|
|
134
|
+
if dyno[:release][:version] > latest_version
|
135
|
+
latest_version = dyno[:release][:version]
|
136
|
+
latest_version_id = dyno[:release][:id]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
latest_version_id
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|