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,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
|