nucleus 0.1.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 +7 -0
- data/.gitattributes +1 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +13 -0
- data/Gemfile +16 -0
- data/Guardfile +22 -0
- data/LICENSE +21 -0
- data/README.md +675 -0
- data/Rakefile +137 -0
- data/bin/nucleus +91 -0
- data/bin/nucleus.bat +1 -0
- data/config.ru +18 -0
- data/config/adapters/cloud_control.yml +32 -0
- data/config/adapters/cloud_foundry_v2.yml +61 -0
- data/config/adapters/heroku.yml +13 -0
- data/config/adapters/openshift_v2.yml +20 -0
- data/config/nucleus_config.rb +47 -0
- data/lib/nucleus.rb +13 -0
- data/lib/nucleus/adapter_resolver.rb +115 -0
- data/lib/nucleus/adapters/base_adapter.rb +109 -0
- data/lib/nucleus/adapters/buildpack_translator.rb +79 -0
- data/lib/nucleus/adapters/v1/cloud_control/application.rb +108 -0
- data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_control/buildpacks.rb +23 -0
- data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +153 -0
- data/lib/nucleus/adapters/v1/cloud_control/data.rb +76 -0
- data/lib/nucleus/adapters/v1/cloud_control/domains.rb +68 -0
- data/lib/nucleus/adapters/v1/cloud_control/lifecycle.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_control/log_poller.rb +71 -0
- data/lib/nucleus/adapters/v1/cloud_control/logs.rb +103 -0
- data/lib/nucleus/adapters/v1/cloud_control/regions.rb +32 -0
- data/lib/nucleus/adapters/v1/cloud_control/scaling.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_control/semantic_errors.rb +31 -0
- data/lib/nucleus/adapters/v1/cloud_control/services.rb +162 -0
- data/lib/nucleus/adapters/v1/cloud_control/token.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_control/vars.rb +88 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/app_states.rb +28 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/application.rb +111 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/authentication.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/buildpacks.rb +23 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/cloud_foundry_v2.rb +141 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/data.rb +97 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +149 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/lifecycle.rb +41 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +303 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/regions.rb +33 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/scaling.rb +15 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/semantic_errors.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +286 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/vars.rb +80 -0
- data/lib/nucleus/adapters/v1/heroku/app_states.rb +57 -0
- data/lib/nucleus/adapters/v1/heroku/application.rb +93 -0
- data/lib/nucleus/adapters/v1/heroku/authentication.rb +27 -0
- data/lib/nucleus/adapters/v1/heroku/buildpacks.rb +27 -0
- data/lib/nucleus/adapters/v1/heroku/data.rb +78 -0
- data/lib/nucleus/adapters/v1/heroku/domains.rb +43 -0
- data/lib/nucleus/adapters/v1/heroku/heroku.rb +146 -0
- data/lib/nucleus/adapters/v1/heroku/lifecycle.rb +51 -0
- data/lib/nucleus/adapters/v1/heroku/logs.rb +108 -0
- data/lib/nucleus/adapters/v1/heroku/regions.rb +42 -0
- data/lib/nucleus/adapters/v1/heroku/scaling.rb +28 -0
- data/lib/nucleus/adapters/v1/heroku/semantic_errors.rb +23 -0
- data/lib/nucleus/adapters/v1/heroku/services.rb +168 -0
- data/lib/nucleus/adapters/v1/heroku/vars.rb +65 -0
- data/lib/nucleus/adapters/v1/openshift_v2/app_states.rb +68 -0
- data/lib/nucleus/adapters/v1/openshift_v2/application.rb +108 -0
- data/lib/nucleus/adapters/v1/openshift_v2/authentication.rb +21 -0
- data/lib/nucleus/adapters/v1/openshift_v2/data.rb +96 -0
- data/lib/nucleus/adapters/v1/openshift_v2/domains.rb +37 -0
- data/lib/nucleus/adapters/v1/openshift_v2/lifecycle.rb +60 -0
- data/lib/nucleus/adapters/v1/openshift_v2/logs.rb +106 -0
- data/lib/nucleus/adapters/v1/openshift_v2/openshift_v2.rb +125 -0
- data/lib/nucleus/adapters/v1/openshift_v2/regions.rb +58 -0
- data/lib/nucleus/adapters/v1/openshift_v2/scaling.rb +39 -0
- data/lib/nucleus/adapters/v1/openshift_v2/semantic_errors.rb +40 -0
- data/lib/nucleus/adapters/v1/openshift_v2/services.rb +173 -0
- data/lib/nucleus/adapters/v1/openshift_v2/vars.rb +49 -0
- data/lib/nucleus/adapters/v1/stub_adapter.rb +464 -0
- data/lib/nucleus/core/adapter_authentication_inductor.rb +62 -0
- data/lib/nucleus/core/adapter_extensions/auth/auth_client.rb +44 -0
- data/lib/nucleus/core/adapter_extensions/auth/authentication_retry_wrapper.rb +79 -0
- data/lib/nucleus/core/adapter_extensions/auth/expiring_token_auth_client.rb +53 -0
- data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +37 -0
- data/lib/nucleus/core/adapter_extensions/auth/o_auth2_auth_client.rb +95 -0
- data/lib/nucleus/core/adapter_extensions/auth/token_auth_client.rb +36 -0
- data/lib/nucleus/core/adapter_extensions/http_client.rb +177 -0
- data/lib/nucleus/core/adapter_extensions/http_tail_client.rb +26 -0
- data/lib/nucleus/core/adapter_extensions/tail_stopper.rb +25 -0
- data/lib/nucleus/core/common/errors/ambiguous_adapter_error.rb +7 -0
- data/lib/nucleus/core/common/errors/file_existence_error.rb +7 -0
- data/lib/nucleus/core/common/errors/startup_error.rb +12 -0
- data/lib/nucleus/core/common/exit_codes.rb +25 -0
- data/lib/nucleus/core/common/files/application_repo_sanitizer.rb +52 -0
- data/lib/nucleus/core/common/files/archive_extractor.rb +112 -0
- data/lib/nucleus/core/common/files/archiver.rb +91 -0
- data/lib/nucleus/core/common/link_generator.rb +46 -0
- data/lib/nucleus/core/common/logging/logging.rb +52 -0
- data/lib/nucleus/core/common/logging/multi_logger.rb +59 -0
- data/lib/nucleus/core/common/logging/request_log_formatter.rb +48 -0
- data/lib/nucleus/core/common/ssh_handler.rb +108 -0
- data/lib/nucleus/core/common/stream_callback.rb +27 -0
- data/lib/nucleus/core/common/thread_config_accessor.rb +85 -0
- data/lib/nucleus/core/common/url_converter.rb +28 -0
- data/lib/nucleus/core/enums/application_states.rb +26 -0
- data/lib/nucleus/core/enums/logfile_types.rb +28 -0
- data/lib/nucleus/core/error_messages.rb +127 -0
- data/lib/nucleus/core/errors/adapter_error.rb +13 -0
- data/lib/nucleus/core/errors/adapter_missing_implementation_error.rb +12 -0
- data/lib/nucleus/core/errors/adapter_request_error.rb +10 -0
- data/lib/nucleus/core/errors/adapter_resource_not_found_error.rb +10 -0
- data/lib/nucleus/core/errors/endpoint_authentication_error.rb +10 -0
- data/lib/nucleus/core/errors/platform_specific_semantic_error.rb +12 -0
- data/lib/nucleus/core/errors/platform_timeout_error.rb +10 -0
- data/lib/nucleus/core/errors/platform_unavailable_error.rb +10 -0
- data/lib/nucleus/core/errors/semantic_adapter_request_error.rb +19 -0
- data/lib/nucleus/core/errors/unknown_adapter_call_error.rb +10 -0
- data/lib/nucleus/core/file_handling/archive_converter.rb +29 -0
- data/lib/nucleus/core/file_handling/file_manager.rb +64 -0
- data/lib/nucleus/core/file_handling/git_deployer.rb +133 -0
- data/lib/nucleus/core/file_handling/git_repo_analyzer.rb +23 -0
- data/lib/nucleus/core/import/adapter_configuration.rb +53 -0
- data/lib/nucleus/core/import/vendor_parser.rb +28 -0
- data/lib/nucleus/core/import/version_detector.rb +18 -0
- data/lib/nucleus/core/models/abstract_model.rb +29 -0
- data/lib/nucleus/core/models/endpoint.rb +30 -0
- data/lib/nucleus/core/models/provider.rb +26 -0
- data/lib/nucleus/core/models/vendor.rb +22 -0
- data/lib/nucleus/ext/kernel.rb +5 -0
- data/lib/nucleus/ext/regexp.rb +49 -0
- data/lib/nucleus/os.rb +15 -0
- data/lib/nucleus/root_dir.rb +13 -0
- data/lib/nucleus/scripts/finalize.rb +8 -0
- data/lib/nucleus/scripts/initialize.rb +9 -0
- data/lib/nucleus/scripts/initialize_config_defaults.rb +26 -0
- data/lib/nucleus/scripts/load.rb +17 -0
- data/lib/nucleus/scripts/load_dependencies.rb +43 -0
- data/lib/nucleus/scripts/setup_config.rb +28 -0
- data/lib/nucleus/scripts/shutdown.rb +11 -0
- data/lib/nucleus/version.rb +3 -0
- data/nucleus.gemspec +88 -0
- data/public/robots.txt +2 -0
- data/public/swagger-ui/css/reset.css +125 -0
- data/public/swagger-ui/css/screen.css +1224 -0
- data/public/swagger-ui/images/apple-touch-icon-114x114.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-120x120.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-144x144.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-152x152.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-57x57.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-60x60.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-72x72.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-76x76.png +0 -0
- data/public/swagger-ui/images/explorer_icons.png +0 -0
- data/public/swagger-ui/images/favicon-128.png +0 -0
- data/public/swagger-ui/images/favicon-16x16.png +0 -0
- data/public/swagger-ui/images/favicon-196x196.png +0 -0
- data/public/swagger-ui/images/favicon-32x32.png +0 -0
- data/public/swagger-ui/images/favicon-96x96.png +0 -0
- data/public/swagger-ui/images/favicon.ico +0 -0
- data/public/swagger-ui/images/logo_small.png +0 -0
- data/public/swagger-ui/images/mstile-144x144.png +0 -0
- data/public/swagger-ui/images/mstile-150x150.png +0 -0
- data/public/swagger-ui/images/mstile-310x150.png +0 -0
- data/public/swagger-ui/images/mstile-310x310.png +0 -0
- data/public/swagger-ui/images/mstile-70x70.png +0 -0
- data/public/swagger-ui/images/pet_store_api.png +0 -0
- data/public/swagger-ui/images/throbber.gif +0 -0
- data/public/swagger-ui/images/wordnik_api.png +0 -0
- data/public/swagger-ui/index.html +107 -0
- data/public/swagger-ui/lib/backbone-min.js +38 -0
- data/public/swagger-ui/lib/handlebars-1.0.0.js +2278 -0
- data/public/swagger-ui/lib/highlight.7.3.pack.js +1 -0
- data/public/swagger-ui/lib/jquery-1.8.0.min.js +2 -0
- data/public/swagger-ui/lib/jquery.ba-bbq.min.js +18 -0
- data/public/swagger-ui/lib/jquery.slideto.min.js +1 -0
- data/public/swagger-ui/lib/jquery.wiggle.min.js +8 -0
- data/public/swagger-ui/lib/shred.bundle.js +2765 -0
- data/public/swagger-ui/lib/shred/content.js +193 -0
- data/public/swagger-ui/lib/swagger-oauth.js +211 -0
- data/public/swagger-ui/lib/swagger.js +1653 -0
- data/public/swagger-ui/lib/underscore-min.js +32 -0
- data/public/swagger-ui/o2c.html +15 -0
- data/public/swagger-ui/redirect.html +14 -0
- data/public/swagger-ui/swagger-ui.js +2324 -0
- data/public/swagger-ui/swagger-ui.min.js +1 -0
- data/schemas/api.adapter.schema.yml +31 -0
- data/schemas/api.requirements.schema.yml +17 -0
- data/spec/factories/models.rb +61 -0
- data/spec/integration/api/auth_spec.rb +58 -0
- data/spec/integration/api/endpoints_spec.rb +167 -0
- data/spec/integration/api/errors_spec.rb +47 -0
- data/spec/integration/api/providers_spec.rb +157 -0
- data/spec/integration/api/swagger_schema_spec.rb +64 -0
- data/spec/integration/api/vendors_spec.rb +45 -0
- data/spec/integration/integration_spec_helper.rb +27 -0
- data/spec/integration/test_data_generator.rb +55 -0
- data/spec/nucleus_git_key.pem +51 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/shared_example_request_types.rb +99 -0
- data/spec/test_suites.rake +31 -0
- data/spec/unit/adapters/archive_converter_spec.rb +25 -0
- data/spec/unit/adapters/file_manager_spec.rb +93 -0
- data/spec/unit/adapters/git_deployer_spec.rb +262 -0
- data/spec/unit/adapters/v1/stub_spec.rb +14 -0
- data/spec/unit/common/helpers/auth_helper_spec.rb +73 -0
- data/spec/unit/common/oauth2_auth_client_spec.rb +108 -0
- data/spec/unit/common/regexp_spec.rb +33 -0
- data/spec/unit/common/request_log_formatter_spec.rb +108 -0
- data/spec/unit/common/thread_config_accessor_spec.rb +97 -0
- data/spec/unit/models/endpoint_spec.rb +83 -0
- data/spec/unit/models/provider_spec.rb +102 -0
- data/spec/unit/models/vendor_spec.rb +100 -0
- data/spec/unit/schemas/adapter_schema_spec.rb +16 -0
- data/spec/unit/schemas/adapter_validation_spec.rb +56 -0
- data/spec/unit/schemas/requirements_schema_spec.rb +16 -0
- data/spec/unit/unit_spec_helper.rb +11 -0
- data/tasks/compatibility.rake +113 -0
- data/tasks/evaluation.rake +162 -0
- data/wiki/adapter_tests.md +99 -0
- data/wiki/implement_new_adapter.md +155 -0
- metadata +836 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class Heroku < Stub
|
|
5
|
+
module Application
|
|
6
|
+
# @see Stub#applications
|
|
7
|
+
def applications
|
|
8
|
+
response = get('/apps')
|
|
9
|
+
apps = []
|
|
10
|
+
response.body.each do |application|
|
|
11
|
+
apps << to_nucleus_app(application)
|
|
12
|
+
end
|
|
13
|
+
apps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Stub#application
|
|
17
|
+
def application(application_id)
|
|
18
|
+
response = get("/apps/#{application_id}")
|
|
19
|
+
to_nucleus_app response.body
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @see Stub#delete_application
|
|
23
|
+
def delete_application(application_id)
|
|
24
|
+
# returns the application, but we do not want any output
|
|
25
|
+
delete("/apps/#{application_id}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @see Stub#create_application
|
|
29
|
+
def create_application(application)
|
|
30
|
+
# updates the application with a valid region identity
|
|
31
|
+
retrieve_region(application)
|
|
32
|
+
|
|
33
|
+
# Heroku does not support autoscale
|
|
34
|
+
fail_with(:no_autoscale) if application[:autoscaled]
|
|
35
|
+
application.delete :autoscaled
|
|
36
|
+
|
|
37
|
+
# can fail if runtime URLs are invalid or names do not exist on this platform
|
|
38
|
+
runtimes = runtimes_to_install(application)
|
|
39
|
+
|
|
40
|
+
# create the actual application
|
|
41
|
+
created_app = post('/apps', body: application).body
|
|
42
|
+
|
|
43
|
+
# when application has been created, process runtime information if applicable
|
|
44
|
+
unless runtimes.nil? || runtimes.empty?
|
|
45
|
+
begin
|
|
46
|
+
install_runtimes(created_app[:id], runtimes)
|
|
47
|
+
rescue
|
|
48
|
+
# if buildpack fails, make sure app gets deleted (!)
|
|
49
|
+
log.debug 'Runtime installation failed, rollback...'
|
|
50
|
+
delete_application(created_app[:id])
|
|
51
|
+
log.debug '...application successfully rolled back'
|
|
52
|
+
raise Errors::AdapterRequestError,
|
|
53
|
+
'Heroku requires a buildpack URL when the runtime shall be specified manually.'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
to_nucleus_app(created_app)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# TODO: make update transactional in case sub-task (app, buildpacks) fails
|
|
60
|
+
# @see Stub#update_application
|
|
61
|
+
def update_application(application_id, application)
|
|
62
|
+
# start updating the buildpacks
|
|
63
|
+
if application.key? :runtimes
|
|
64
|
+
# can fail if runtime URLs are invalid or names do not exist on this platform
|
|
65
|
+
runtimes = runtimes_to_install(application)
|
|
66
|
+
install_runtimes(application_id, runtimes)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# now change the app name and all other direct properties, which are in the body
|
|
70
|
+
response = patch("/apps/#{application_id}", body: application)
|
|
71
|
+
to_nucleus_app(response.body)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def to_nucleus_app(heroku_application)
|
|
77
|
+
# load dynos only once
|
|
78
|
+
dynos = dynos(heroku_application[:id])
|
|
79
|
+
# add missing fields to the application representation
|
|
80
|
+
heroku_application[:autoscaled] = false
|
|
81
|
+
heroku_application[:state] = application_state(heroku_application, dynos)
|
|
82
|
+
heroku_application[:instances] = application_instances(heroku_application[:id])
|
|
83
|
+
heroku_application[:active_runtime] = heroku_application.delete(:buildpack_provided_description)
|
|
84
|
+
heroku_application[:runtimes] = installed_buildpacks(heroku_application[:id])
|
|
85
|
+
heroku_application[:release_version] = latest_release(heroku_application[:id], dynos)
|
|
86
|
+
heroku_application[:region] = heroku_application[:region][:name]
|
|
87
|
+
heroku_application
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class Heroku < Stub
|
|
5
|
+
# Authentication functionality to support the Heroku API
|
|
6
|
+
module Authentication
|
|
7
|
+
# @see Stub#auth_client
|
|
8
|
+
def auth_client
|
|
9
|
+
log.debug "Authenticate @ #{@endpoint_url}"
|
|
10
|
+
TokenAuthClient.new @check_certificates do |verify_ssl, username, password|
|
|
11
|
+
response = Excon.post("#{@endpoint_url}/login", query: { username: username, password: password },
|
|
12
|
+
ssl_verify_peer: verify_ssl)
|
|
13
|
+
# Heroku returns 404 for invalid credentials, then we do not return an API token
|
|
14
|
+
if response.status == 404
|
|
15
|
+
nil
|
|
16
|
+
else
|
|
17
|
+
# extract the token
|
|
18
|
+
response_parsed = JSON.parse(response.body)
|
|
19
|
+
response_parsed['api_key']
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class Heroku < Stub
|
|
5
|
+
module Buildpacks
|
|
6
|
+
include Nucleus::Adapters::BuildpackTranslator
|
|
7
|
+
|
|
8
|
+
# @see BuildpackTranslator#vendor_specific_runtimes
|
|
9
|
+
def vendor_specific_runtimes
|
|
10
|
+
{
|
|
11
|
+
'ruby' => 'https://github.com/heroku/heroku-buildpack-ruby',
|
|
12
|
+
'nodejs' => 'https://github.com/heroku/heroku-buildpack-nodejs',
|
|
13
|
+
'clojure' => 'https://github.com/heroku/heroku-buildpack-clojure',
|
|
14
|
+
'python' => 'https://github.com/heroku/heroku-buildpack-python',
|
|
15
|
+
'java' => 'https://github.com/heroku/heroku-buildpack-java',
|
|
16
|
+
'gradle' => 'https://github.com/heroku/heroku-buildpack-gradle',
|
|
17
|
+
'grails' => 'https://github.com/heroku/heroku-buildpack-grails',
|
|
18
|
+
'scala' => 'https://github.com/heroku/heroku-buildpack-scala',
|
|
19
|
+
'play' => 'https://github.com/heroku/heroku-buildpack-play',
|
|
20
|
+
'php' => 'https://github.com/heroku/heroku-buildpack-php'
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +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
|
+
fail 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
|
+
fail 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
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class Heroku < Stub
|
|
5
|
+
module Domains
|
|
6
|
+
# As of now, there is no update functionality on Heroku as on most other platforms (!)
|
|
7
|
+
|
|
8
|
+
# @see Stub#domains
|
|
9
|
+
def domains(application_id)
|
|
10
|
+
domains = get("/apps/#{application_id}/domains").body
|
|
11
|
+
# exclude web_url by domain, otherwise we would need to fire an additional query and get the application
|
|
12
|
+
domains.delete_if { |domain| domain[:hostname].end_with? ".#{@endpoint_app_domain}" }
|
|
13
|
+
domains.collect { |domain| to_nucleus_domain(domain) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Stub#domain
|
|
17
|
+
def domain(application_id, domain_id)
|
|
18
|
+
domain = get("/apps/#{application_id}/domains/#{domain_id}").body
|
|
19
|
+
to_nucleus_domain(domain)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @see Stub#create_domain
|
|
23
|
+
def create_domain(application_id, domain)
|
|
24
|
+
domain = post("/apps/#{application_id}/domains", body: { hostname: domain[:name] }).body
|
|
25
|
+
to_nucleus_domain(domain)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @see Stub#delete_domain
|
|
29
|
+
def delete_domain(application_id, domain_id)
|
|
30
|
+
delete("/apps/#{application_id}/domains/#{domain_id}")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def to_nucleus_domain(domain)
|
|
36
|
+
domain[:name] = domain.delete :hostname
|
|
37
|
+
domain
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +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
|
+
fail Errors::AdapterResourceNotFoundError, error_response.body[:message]
|
|
32
|
+
elsif error_response.status == 503
|
|
33
|
+
fail 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
|
+
fail 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
|
+
latest_version_id
|
|
131
|
+
else
|
|
132
|
+
latest_version = 0
|
|
133
|
+
latest_version_id = nil
|
|
134
|
+
dynos.each do |dyno|
|
|
135
|
+
if dyno[:release][:version] > latest_version
|
|
136
|
+
latest_version = dyno[:release][:version]
|
|
137
|
+
latest_version_id = dyno[:release][:id]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
latest_version_id
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class Heroku < Stub
|
|
5
|
+
module Lifecycle
|
|
6
|
+
# Lifecycle:
|
|
7
|
+
# A) via maintenance - workers are still active
|
|
8
|
+
# B) via formation - loose scaling information
|
|
9
|
+
|
|
10
|
+
# @see Stub#start
|
|
11
|
+
def start(application_id)
|
|
12
|
+
log.debug "Start @ #{@endpoint_url}"
|
|
13
|
+
|
|
14
|
+
app = application(application_id)
|
|
15
|
+
if app[:state] == Enums::ApplicationStates::DEPLOYED
|
|
16
|
+
# add web dyno if there currently are no dynos (state == deployed)
|
|
17
|
+
scale_web(application_id, 1)
|
|
18
|
+
elsif app[:state] == Enums::ApplicationStates::CREATED
|
|
19
|
+
# fail if there is no deployment
|
|
20
|
+
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be started'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
scale_worker(application_id, 1)
|
|
24
|
+
update_application(application_id, maintenance: false)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @see Stub#stop
|
|
28
|
+
def stop(application_id)
|
|
29
|
+
log.debug "Stop @ #{@endpoint_url}"
|
|
30
|
+
|
|
31
|
+
# fail if there is no deployment
|
|
32
|
+
app = application(application_id)
|
|
33
|
+
if app[:state] == Enums::ApplicationStates::CREATED
|
|
34
|
+
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be stopped'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
scale_worker(application_id, 0)
|
|
38
|
+
update_application(application_id, maintenance: true)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @see Stub#restart
|
|
42
|
+
def restart(application_id)
|
|
43
|
+
log.debug "Restart @ #{@endpoint_url}"
|
|
44
|
+
stop(application_id)
|
|
45
|
+
start(application_id)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|