nucleus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,109 @@
|
|
1
|
+
module Nucleus
|
2
|
+
# The {Adapters} module combines all application logic to communicate with the different vendor platforms
|
3
|
+
# and created the unified API.
|
4
|
+
module Adapters
|
5
|
+
# The {BaseAdapter} is an abstract class that shall be extended by all actual Adapters.
|
6
|
+
# It provides methods to common functionality:<br>
|
7
|
+
# * authentication (+cache)
|
8
|
+
# * http client with general error handling
|
9
|
+
# * native platform API calls
|
10
|
+
# @abstract
|
11
|
+
class BaseAdapter
|
12
|
+
include HttpClient
|
13
|
+
include HttpTailClient
|
14
|
+
include Logging
|
15
|
+
|
16
|
+
attr_reader :endpoint_url
|
17
|
+
|
18
|
+
def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
|
19
|
+
fail ArgumentError, "'endpoint_url' must be a valid URL" unless endpoint_url =~ /\A#{URI.regexp(['https'])}\z/
|
20
|
+
@endpoint_url = endpoint_url
|
21
|
+
@endpoint_app_domain = endpoint_app_domain
|
22
|
+
@check_certificates = check_certificates
|
23
|
+
end
|
24
|
+
|
25
|
+
# thread-based cache for the api authorization headers
|
26
|
+
thread_config_accessor :auth_objects_cache, default: {}
|
27
|
+
|
28
|
+
# Cache the auth information.
|
29
|
+
# @param [String] key cache key
|
30
|
+
# @param [Nucleus::Adapters::AuthClient] auth_object authentication client to be cached
|
31
|
+
# @return [void]
|
32
|
+
def cache(key, auth_object)
|
33
|
+
auth_objects_cache[key] = auth_object
|
34
|
+
end
|
35
|
+
|
36
|
+
# Are there cached information for this key?
|
37
|
+
# @param [String] key cache key
|
38
|
+
# @return [true, false] true if has cached auth info, else false
|
39
|
+
def cache?(key)
|
40
|
+
auth_objects_cache.key? key
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the currently cached authentication object.
|
44
|
+
# @param [String] key cache key
|
45
|
+
# @return [Hash<String,String>, Nucleus::Adapters::AuthClient] cached authentication client
|
46
|
+
def cached(key)
|
47
|
+
return nil unless cache?(key)
|
48
|
+
auth_objects_cache[key]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Create the cache key for the username / password combination and save it in the {::RequestStore} to make it
|
52
|
+
# available throughout the current request.
|
53
|
+
# @param [String] username the username for the authentication
|
54
|
+
# @param [String] password the password for the authentication
|
55
|
+
# @return [String] calculated hash key for the input values
|
56
|
+
def cache_key(username, password)
|
57
|
+
# calculate the cache only once per request
|
58
|
+
return RequestStore.store[:cache_key] if RequestStore.exist?(:cache_key)
|
59
|
+
key = Digest::SHA256.hexdigest "#{endpoint_url}#{username}:#{password}"
|
60
|
+
RequestStore.store[:cache_key] = key
|
61
|
+
key
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get the cached authentication object and retrieve the presumably valid authentication header.
|
65
|
+
# @return [Hash<String,String>] hash including a valid authentication header
|
66
|
+
def headers
|
67
|
+
auth_object = auth_objects_cache[RequestStore.store[:cache_key]]
|
68
|
+
# AuthClient, generates the header for us
|
69
|
+
auth_object.auth_header
|
70
|
+
end
|
71
|
+
|
72
|
+
# Execute an API call, targeted directly against the vendors API.
|
73
|
+
# @param [Symbol] method http method to use, one of: [:GET, :POST, :DELETE, :PUT, :PATCH]
|
74
|
+
# @param [String] path url path to append to the endpoint's URL
|
75
|
+
# @param [Hash] params body params to use for PATCH, :PUT and :POST requests
|
76
|
+
# @return [Object] the actual response body of the vendor platform
|
77
|
+
def endpoint_call(method, path, params)
|
78
|
+
case method
|
79
|
+
when :GET
|
80
|
+
get(path, native_call: true).body
|
81
|
+
when :POST
|
82
|
+
post(path, native_call: true, body: params).body
|
83
|
+
when :DELETE
|
84
|
+
delete(path, native_call: true).body
|
85
|
+
when :PUT
|
86
|
+
put(path, native_call: true, body: params).body
|
87
|
+
when :PATCH
|
88
|
+
patch(path, native_call: true, body: params).body
|
89
|
+
else
|
90
|
+
fail AdapterRequestError, 'Unsupported adapter call method. Allowed are: GET, POST, PATCH, PUT, DELETE'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Fail with a {Errors::PlatformSpecificSemanticError} error and format the error message to include the values
|
95
|
+
# that are passed in the params. Requires the adapter to provide a +semantic_error_messages+ method, which shall
|
96
|
+
# return a Hash with the platform specific semantic errors.
|
97
|
+
# @param [Symbol] error_name error that shall be returned
|
98
|
+
# @param [Array<String>] params values that are to be included in the error message template
|
99
|
+
# @raise [Errors::PlatformSpecificSemanticError]
|
100
|
+
def fail_with(error_name, params = nil)
|
101
|
+
unless respond_to?(:semantic_error_messages)
|
102
|
+
fail StandardError 'Invalid adapter implementation, no :semantic_error_messages method provided'
|
103
|
+
end
|
104
|
+
error = semantic_error_messages[error_name]
|
105
|
+
fail Errors::PlatformSpecificSemanticError.new(error[:message] % params, error[:code])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
# The {BuildpackTranslator} provides convenience methods for the user to handle the installation
|
4
|
+
# of application runtimes. Common runtime names can be applied and are automatically translated to a
|
5
|
+
# platform native runtime name / url (if applicable).
|
6
|
+
module BuildpackTranslator
|
7
|
+
# List of common buildpacks that are available GitHub.
|
8
|
+
# However, they are not guaranteed to work with every platform that utilized buildpacks.
|
9
|
+
PUBLIC_BUILDPACKS = {
|
10
|
+
'c' => 'https://github.com/atris/heroku-buildpack-c',
|
11
|
+
'common_lisp' => 'https://github.com/mtravers/heroku-buildpack-cl',
|
12
|
+
'core_data' => 'https://github.com/heroku/heroku-buildpack-core-data',
|
13
|
+
'dart' => 'https://github.com/igrigorik/heroku-buildpack-dart',
|
14
|
+
'eiffel' => 'https://github.com/mbustosorg/heroku-buildpack-eiffel',
|
15
|
+
'elixir' => 'https://github.com/hashnuke/heroku-buildpack-elixir',
|
16
|
+
'emacs' => 'https://github.com/technomancy/heroku-buildpack-emacs',
|
17
|
+
'embedded_proxy' => 'https://github.com/ryanbrainard/heroku-buildpack-embedded-proxy',
|
18
|
+
'erlang' => 'https://github.com/archaelus/heroku-buildpack-erlang',
|
19
|
+
'factor' => 'https://github.com/ryanbrainard/heroku-buildpack-factor',
|
20
|
+
'fakesu' => 'https://github.com/fabiokung/heroku-buildpack-fakesu',
|
21
|
+
'geodjango' => 'https://github.com/cirlabs/heroku-buildpack-geodjango',
|
22
|
+
'go' => 'https://github.com/kr/heroku-buildpack-go',
|
23
|
+
'haskell' => 'https://github.com/mietek/haskell-on-heroku',
|
24
|
+
'inline' => 'https://github.com/kr/heroku-buildpack-inline',
|
25
|
+
'java_ant' => 'https://github.com/dennisg/heroku-buildpack-ant',
|
26
|
+
# introduced by IBM Bluemix, shall also work in Heroku
|
27
|
+
'java_liberty' => 'https://github.com/cloudfoundry/ibm-websphere-liberty-buildpack',
|
28
|
+
'jekyll' => 'https://github.com/mattmanning/heroku-buildpack-ruby-jekyll',
|
29
|
+
'lua' => 'https://github.com/leafo/heroku-buildpack-lua',
|
30
|
+
'luvit' => 'https://github.com/skomski/heroku-buildpack-luvit',
|
31
|
+
'meteor' => 'https://github.com/jordansissel/heroku-buildpack-meteor',
|
32
|
+
'middleman' => 'https://github.com/hashicorp/heroku-buildpack-middleman',
|
33
|
+
'monit' => 'https://github.com/k33l0r/monit-buildpack',
|
34
|
+
'multi' => 'https://github.com/heroku/heroku-buildpack-multi',
|
35
|
+
'nanoc' => 'https://github.com/bobthecow/heroku-buildpack-nanoc',
|
36
|
+
'dot_net' => 'https://github.com/friism/heroku-buildpack-mono',
|
37
|
+
'null' => 'https://github.com/ryandotsmith/null-buildpack',
|
38
|
+
'opa' => 'https://github.com/tsloughter/heroku-buildpack-opa',
|
39
|
+
'perl' => 'https://github.com/miyagawa/heroku-buildpack-perl',
|
40
|
+
'phantomjs' => 'https://github.com/stomita/heroku-buildpack-phantomjs',
|
41
|
+
'phing' => 'https://github.com/ryanbrainard/heroku-buildpack-phing',
|
42
|
+
'r' => 'https://github.com/virtualstaticvoid/heroku-buildpack-r',
|
43
|
+
'rust' => 'https://github.com/emk/heroku-buildpack-rust',
|
44
|
+
'redline' => 'https://github.com/will/heroku-buildpack-redline',
|
45
|
+
'silex' => 'https://github.com/klaussilveira/heroku-buildpack-silex',
|
46
|
+
'sphinx' => 'https://github.com/kennethreitz/sphinx-buildpack',
|
47
|
+
'test' => 'https://github.com/ddollar/buildpack-test',
|
48
|
+
'testing' => 'https://github.com/ryanbrainard/heroku-buildpack-testrunner'
|
49
|
+
}
|
50
|
+
|
51
|
+
# Search the list of known buildpacks, both vendor specific and public, to match the desires runtime name.
|
52
|
+
# @param [String] name of the runtime to look out for
|
53
|
+
# @return [Boolean] returns true if a vendor specific or public buildpack was found for the runtime
|
54
|
+
def find_runtime(name)
|
55
|
+
if respond_to? :vendor_specific_runtimes
|
56
|
+
runtime = vendor_specific_runtimes[name.downcase.underscore]
|
57
|
+
return runtime unless runtime.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
# if no vendor specific runtime was found, use the general definitions
|
61
|
+
PUBLIC_BUILDPACKS[name.downcase.underscore]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks if the name of the runtime is matching a vendor specific runtime / buildpack.
|
65
|
+
# @param [String] name of the runtime for which to check if it matches a vendor specific runtime
|
66
|
+
# @return [Boolean] returns true if the name matches a vendor specific runtime, false otherwise
|
67
|
+
def native_runtime?(name)
|
68
|
+
if respond_to? :vendor_specific_runtimes
|
69
|
+
# case A: name is a key
|
70
|
+
return true if vendor_specific_runtimes.keys.include? name
|
71
|
+
# case B: name is a specific runtime name or a URL and one of the values
|
72
|
+
return vendor_specific_runtimes.values.include? name
|
73
|
+
end
|
74
|
+
# cant be native if there are no vendor specific runtimes
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class CloudControl < Stub
|
5
|
+
# cloud control, CRUD operations for the application object
|
6
|
+
module Application
|
7
|
+
# @see Stub#applications
|
8
|
+
def applications
|
9
|
+
response = get('/app')
|
10
|
+
apps = []
|
11
|
+
response.body.each do |application|
|
12
|
+
apps << to_nucleus_app(application, default_deployment(application[:name]))
|
13
|
+
end
|
14
|
+
apps
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see Stub#application
|
18
|
+
def application(application_id)
|
19
|
+
response = get("/app/#{application_id}").body
|
20
|
+
to_nucleus_app(response, default_deployment(response[:name]))
|
21
|
+
end
|
22
|
+
|
23
|
+
# @see Stub#create_application
|
24
|
+
def create_application(application)
|
25
|
+
if application.key? :region
|
26
|
+
unless application[:region].casecmp('default') == 0
|
27
|
+
fail Errors::SemanticAdapterRequestError,
|
28
|
+
"Region '#{application[:region]}' does not exist at the endpoint. "\
|
29
|
+
'Please check which regions are actually available on this endpoint.'
|
30
|
+
end
|
31
|
+
# there is no region in cloudControl --> remove from request
|
32
|
+
application.delete :region
|
33
|
+
end
|
34
|
+
|
35
|
+
apply_buildpack(application)
|
36
|
+
|
37
|
+
# force the use of repository type 'git', unless overridden by the params
|
38
|
+
default_params = { repository_type: 'git' }
|
39
|
+
cc_application = default_params.merge(application)
|
40
|
+
|
41
|
+
create_app_response = post('/app', body: cc_application).body
|
42
|
+
|
43
|
+
# create the default deployment, name will automatically become 'default'
|
44
|
+
created_deployment = post("/app/#{create_app_response[:name]}/deployment", body: { name: 'nucleus' }).body
|
45
|
+
|
46
|
+
# activate the variables addon. However, the activation explicitly requires an initial key value pair...
|
47
|
+
post("/app/#{create_app_response[:name]}/deployment/#{NUCLEUS_DEPLOYMENT}/addon",
|
48
|
+
body: { addon: 'config.free', options: "{\"nucleus-initialized\": \"true\"}" }).body
|
49
|
+
# ...now delete the initial key value pair to have the desired clean setup
|
50
|
+
delete_env_var(create_app_response[:name], 'nucleus-initialized')
|
51
|
+
|
52
|
+
to_nucleus_app(create_app_response, created_deployment)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @see Stub#delete_application
|
56
|
+
def delete_application(application_id)
|
57
|
+
# delete all deployments first
|
58
|
+
deployments = get("/app/#{application_id}/deployment").body
|
59
|
+
deployments.each do |deployment|
|
60
|
+
deployment_name = %r{(\w+)\/(\w+)}.match(deployment[:name])[2]
|
61
|
+
delete("/app/#{application_id}/deployment/#{deployment_name}")
|
62
|
+
end
|
63
|
+
delete("/app/#{application_id}")
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def apply_buildpack(application)
|
69
|
+
runtimes = application.delete(:runtimes)
|
70
|
+
return unless runtimes
|
71
|
+
fail_with(:only_one_runtime) if runtimes.length > 1
|
72
|
+
buildpack = find_runtime(runtimes[0])
|
73
|
+
if native_runtime?(buildpack)
|
74
|
+
application[:type] = buildpack
|
75
|
+
elsif buildpack
|
76
|
+
application[:type] = 'custom'
|
77
|
+
application[:buildpack_url] = buildpack
|
78
|
+
else
|
79
|
+
# 3rd party buildpack must be a valid URL
|
80
|
+
unless Regexp::PERFECT_URL_PATTERN =~ runtimes[0]
|
81
|
+
fail Errors::SemanticAdapterRequestError,
|
82
|
+
"Invalid buildpack: '#{runtimes[0]}'. Please provide a valid buildpack URL for all "\
|
83
|
+
'custom buildpacks that are not provided by cloud control.'
|
84
|
+
end
|
85
|
+
application[:type] = 'custom'
|
86
|
+
application[:buildpack_url] = runtimes[0]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_nucleus_app(app, deployment)
|
91
|
+
app[:id] = app[:name]
|
92
|
+
app[:created_at] = app.delete :date_created
|
93
|
+
app[:updated_at] = app.delete :date_modified
|
94
|
+
app[:state] = application_state(deployment)
|
95
|
+
app[:web_url] = "http://#{deployment[:default_subdomain]}"
|
96
|
+
app[:autoscaled] = false
|
97
|
+
app[:region] = 'default'
|
98
|
+
app[:instances] = deployment[:min_boxes]
|
99
|
+
app[:active_runtime] = app[:type][:name] == 'custom' ? app[:buildpack_url] : app[:type][:name]
|
100
|
+
app[:runtimes] = [app[:active_runtime]]
|
101
|
+
app[:release_version] = deployment[:version] != '-1' ? deployment[:version] : nil
|
102
|
+
app
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class CloudControl < Stub
|
5
|
+
# Authentication functionality to support the cloudControl API
|
6
|
+
module Authentication
|
7
|
+
# @see Stub#auth_client
|
8
|
+
def auth_client
|
9
|
+
Token.new @check_certificates do |_verify_ssl, username, password|
|
10
|
+
auth_headers = { 'Authorization' => 'Basic ' + ["#{username}:#{password}"].pack('m*').gsub(/\n/, '') }
|
11
|
+
begin
|
12
|
+
# ssl verification is implemented by the HttpClient itself
|
13
|
+
response = post('/token', headers: auth_headers)
|
14
|
+
# parse to retrieve the token and expiration date
|
15
|
+
expires = Time.parse(response.body[:expires])
|
16
|
+
[response.body[:token], expires]
|
17
|
+
rescue Errors::AdapterError
|
18
|
+
# ignore the error, return nil for failed authentication
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class CloudControl < Stub
|
5
|
+
# cloud control specific buildpacks
|
6
|
+
module Buildpacks
|
7
|
+
include Nucleus::Adapters::BuildpackTranslator
|
8
|
+
|
9
|
+
# @see BuildpackTranslator#vendor_specific_runtimes
|
10
|
+
def vendor_specific_runtimes
|
11
|
+
{
|
12
|
+
'java' => 'java',
|
13
|
+
'nodejs' => 'nodejs',
|
14
|
+
'ruby' => 'ruby',
|
15
|
+
'php' => 'php',
|
16
|
+
'python' => 'python'
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
module Nucleus
|
4
|
+
module Adapters
|
5
|
+
module V1
|
6
|
+
class CloudControl < Stub
|
7
|
+
include Nucleus::Logging
|
8
|
+
include Nucleus::Adapters::V1::CloudControl::Authentication
|
9
|
+
include Nucleus::Adapters::V1::CloudControl::Application
|
10
|
+
include Nucleus::Adapters::V1::CloudControl::Buildpacks
|
11
|
+
include Nucleus::Adapters::V1::CloudControl::Domains
|
12
|
+
include Nucleus::Adapters::V1::CloudControl::Data
|
13
|
+
include Nucleus::Adapters::V1::CloudControl::Lifecycle
|
14
|
+
include Nucleus::Adapters::V1::CloudControl::Logs
|
15
|
+
include Nucleus::Adapters::V1::CloudControl::Regions
|
16
|
+
include Nucleus::Adapters::V1::CloudControl::Scaling
|
17
|
+
include Nucleus::Adapters::V1::CloudControl::SemanticErrors
|
18
|
+
include Nucleus::Adapters::V1::CloudControl::Services
|
19
|
+
include Nucleus::Adapters::V1::CloudControl::Vars
|
20
|
+
|
21
|
+
# The default deployment name of cloud control applications that is used by Nucleus
|
22
|
+
NUCLEUS_DEPLOYMENT = 'nucleus'
|
23
|
+
# Error messages of semantic errors that are platform specific
|
24
|
+
CC_EXCLUSIVE_SEMANTIC_ERROR_MSGS = ['cannot use this name', 'may only contain', 'this field has no more than']
|
25
|
+
# Error messages of common semantic errors
|
26
|
+
CC_SEMANTIC_ERROR_MSGS = ['must be unique', 'already exists',
|
27
|
+
'not a valid addon name', 'not a valid addon option']
|
28
|
+
CC_CONFLICT_ERROR_MSGS = ['Addon already exists']
|
29
|
+
|
30
|
+
def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
|
31
|
+
super(endpoint_url, endpoint_app_domain, check_certificates)
|
32
|
+
end
|
33
|
+
|
34
|
+
def handle_error(error_response)
|
35
|
+
message = error_response.body.match(/{(.*?)}/)
|
36
|
+
message = message[1] if message
|
37
|
+
|
38
|
+
# cloud control responds almost every time with 400...
|
39
|
+
if error_response.status == 400
|
40
|
+
handle_400(message)
|
41
|
+
elsif error_response.status == 409 && CC_CONFLICT_ERROR_MSGS.any? { |msg| message.include? msg }
|
42
|
+
fail Errors::SemanticAdapterRequestError, message
|
43
|
+
elsif error_response.status == 410
|
44
|
+
fail Errors::AdapterResourceNotFoundError, 'Resource not found'
|
45
|
+
elsif error_response.status == 503
|
46
|
+
fail Errors::PlatformUnavailableError, 'The cloudControl API is currently not available'
|
47
|
+
end
|
48
|
+
# error still unhandled, will result in a 500, server error
|
49
|
+
log.warn "cloudControl error still unhandled: #{error_response}"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def handle_400(message)
|
55
|
+
fail Errors::AdapterResourceNotFoundError, 'Resource not found' if message.nil?
|
56
|
+
|
57
|
+
if message.include?('Billing account required')
|
58
|
+
fail_with(:billing_required, [message])
|
59
|
+
elsif CC_EXCLUSIVE_SEMANTIC_ERROR_MSGS.any? { |msg| message.include? msg }
|
60
|
+
# all these errors are limited to cloud control, e.g. the allowed name characters and max name length
|
61
|
+
fail_with(:bad_name, [message])
|
62
|
+
elsif CC_SEMANTIC_ERROR_MSGS.any? { |msg| message.include? msg }
|
63
|
+
fail Errors::SemanticAdapterRequestError, message
|
64
|
+
end
|
65
|
+
fail Errors::AdapterRequestError, message
|
66
|
+
end
|
67
|
+
|
68
|
+
def username
|
69
|
+
get('/user').body.first[:username]
|
70
|
+
end
|
71
|
+
|
72
|
+
def data_uploaded?(deployment)
|
73
|
+
application_id = deployment[:name].split(%r{/})[0]
|
74
|
+
repo_host = URI.parse(deployment[:branch]).host
|
75
|
+
repo_path = URI.parse(deployment[:branch]).path.gsub(%r{^/}, '').chomp('.git')
|
76
|
+
attempts = 0
|
77
|
+
with_ssh_key do
|
78
|
+
loop do
|
79
|
+
begin
|
80
|
+
return GitRepoAnalyzer.any_branch?(repo_host, repo_path, application_id)
|
81
|
+
rescue Net::SSH::AuthenticationFailed => e
|
82
|
+
attempts += 1
|
83
|
+
# wait up to 30 seconds
|
84
|
+
raise e if attempts >= 15
|
85
|
+
log.debug('SSH authentication failed, sleep and repeat')
|
86
|
+
# authentication is not yet ready, wait a short time
|
87
|
+
sleep(2.0)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def application_state(deployment)
|
94
|
+
# With cloud control not supporting the Nucleus application lifecycle, only 3 actual states remain:<br>
|
95
|
+
# * created, when no data deployment (not to confuse with cloud control deployment object) has been made yet
|
96
|
+
# * deployed, when only the data has been pushed into the repository (no build)
|
97
|
+
# * running, if a data deployment was pushed
|
98
|
+
if deployment[:version] == '-1'
|
99
|
+
return Enums::ApplicationStates::DEPLOYED if data_uploaded?(deployment)
|
100
|
+
return Enums::ApplicationStates::CREATED
|
101
|
+
end
|
102
|
+
return Enums::ApplicationStates::IDLE if deployment[:state] == 'idle'
|
103
|
+
Enums::ApplicationStates::RUNNING
|
104
|
+
# return Enums::ApplicationStates::STOPPED
|
105
|
+
|
106
|
+
# arriving here the above states do not catch all states of the cloudControl app, which should not happen ;-)
|
107
|
+
# fail Errors::UnknownAdapterCallError, 'Could not determine application state. '\
|
108
|
+
# 'Please verify the cloudControl adapter'
|
109
|
+
end
|
110
|
+
|
111
|
+
def default_deployment(application_id)
|
112
|
+
# get and return nucleus deployment, but catch arising 404 errors
|
113
|
+
return get("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}").body
|
114
|
+
rescue Errors::AdapterResourceNotFoundError
|
115
|
+
# if 404, list all deployments
|
116
|
+
all_deployments = get("/app/#{application_id}/deployment").body
|
117
|
+
|
118
|
+
# fail 422 (platform specific) if no deployment is available at all
|
119
|
+
fail_with(:no_deployment) if all_deployments.length == 0
|
120
|
+
|
121
|
+
# return deployment[0] if collection size is 1
|
122
|
+
return all_deployments[0] if all_deployments.length == 1
|
123
|
+
|
124
|
+
# return 'default' if more than 1 deployment and 'default' is included
|
125
|
+
def_deployment = all_deployments.find { |d| d[:name].split(%r{/})[1].downcase == 'default' }
|
126
|
+
return def_deployment if def_deployment
|
127
|
+
|
128
|
+
# return 'nucleus' if more than 1 deployment, but no 'default' is included
|
129
|
+
nucleus_deployment = all_deployments.find { |d| d[:name].split(%r{/})[1].downcase == 'nucleus' }
|
130
|
+
return nucleus_deployment if nucleus_deployment
|
131
|
+
|
132
|
+
# fail 422 if no 'default', no 'nucleus', and more than 1 deployment is available (could not identify default)
|
133
|
+
fail_with(:ambiguous_deployments)
|
134
|
+
end
|
135
|
+
|
136
|
+
def headers
|
137
|
+
super.merge('Content-Type' => 'application/json')
|
138
|
+
end
|
139
|
+
|
140
|
+
def with_ssh_key
|
141
|
+
user = username
|
142
|
+
# load ssh key into cloud control
|
143
|
+
matches = nucleus_config.ssh.handler.public_key.match(/(.*)\s{1}(.*)\s{1}(.*)/)
|
144
|
+
key_id = register_key(user, matches[1], matches[2])
|
145
|
+
return yield
|
146
|
+
ensure
|
147
|
+
# unload ssh key, allow 404 if the key couldn't be registered at first
|
148
|
+
delete("/user/#{user}/key/#{key_id}") if key_id
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|