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,17 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudControl < Stub
|
|
5
|
+
# cloud control specific token, which requires a specific +cc_auth_token+ value in the +Authorization+ header.
|
|
6
|
+
class Token < Nucleus::Adapters::ExpiringTokenAuthClient
|
|
7
|
+
# @see Nucleus::Adapters::ExpiringTokenAuthClient#auth_header
|
|
8
|
+
def auth_header
|
|
9
|
+
# call super for error checks, but ignore returned header
|
|
10
|
+
super
|
|
11
|
+
{ 'Authorization' => "cc_auth_token=\"#{api_token}\"" }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudControl < Stub
|
|
5
|
+
# cloud control, CRUD operations for the application's environment variable object,
|
|
6
|
+
# which is referred to as +config addon+ on the platform.
|
|
7
|
+
module Vars
|
|
8
|
+
# @see Stub#env_vars
|
|
9
|
+
def env_vars(application_id)
|
|
10
|
+
cc_vars = cc_vars(application_id)
|
|
11
|
+
cc_vars.collect do |key, value|
|
|
12
|
+
{ id: key, key: key, value: value }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Stub#env_var
|
|
17
|
+
def env_var(application_id, env_var_key)
|
|
18
|
+
cc_vars = cc_vars(application_id)
|
|
19
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
20
|
+
"Env. var key '#{env_var_key}' does not exist" unless env_var?(application_id, env_var_key, cc_vars)
|
|
21
|
+
{ id: env_var_key, key: env_var_key, value: cc_vars[env_var_key.to_sym] }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @see Stub#create_env_var
|
|
25
|
+
def create_env_var(application_id, env_var)
|
|
26
|
+
cc_vars = cc_vars(application_id)
|
|
27
|
+
fail Errors::SemanticAdapterRequestError,
|
|
28
|
+
"Env. var key '#{env_var[:key]}' already taken" if env_var?(application_id, env_var[:key], cc_vars)
|
|
29
|
+
set_var(application_id, env_var[:key], env_var[:value])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @see Stub#update_env_var
|
|
33
|
+
def update_env_var(application_id, env_var_key, env_var)
|
|
34
|
+
cc_vars = cc_vars(application_id)
|
|
35
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
36
|
+
"Env. var key '#{env_var_key}' does not exist" unless env_var?(application_id, env_var_key, cc_vars)
|
|
37
|
+
set_var(application_id, env_var_key, env_var[:value])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @see Stub#delete_env_var
|
|
41
|
+
def delete_env_var(application_id, env_var_key)
|
|
42
|
+
cc_vars = cc_vars(application_id)
|
|
43
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
44
|
+
"Env. var key '#{env_var_key}' does not exist" unless env_var?(application_id, env_var_key, cc_vars)
|
|
45
|
+
set_var(application_id, env_var_key, nil)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def cc_vars(application_id)
|
|
51
|
+
cc_vars_response = get("app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon/config.free")
|
|
52
|
+
cc_vars_response.body[:settings][:CONFIG_VARS]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Set the variable value (create or update)
|
|
56
|
+
# @param [String] application_id id of the cloud control application
|
|
57
|
+
# @param [Symbol] key variable key name
|
|
58
|
+
# @param [String, Integer, Float, Double] value value to apply to the variable
|
|
59
|
+
# @return [Hash] Nucleus representation of the modified variable
|
|
60
|
+
def set_var(application_id, key, value)
|
|
61
|
+
if value.nil?
|
|
62
|
+
# delete the var, set to 'null'
|
|
63
|
+
settings = "{\"#{key}\":null}"
|
|
64
|
+
else
|
|
65
|
+
settings = "{\"#{key}\":\"#{value}\"}"
|
|
66
|
+
end
|
|
67
|
+
response = put("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon/config.free",
|
|
68
|
+
body: { addon: 'config.free',
|
|
69
|
+
settings: settings,
|
|
70
|
+
force: true })
|
|
71
|
+
all_vars = response.body[:settings][:CONFIG_VARS]
|
|
72
|
+
{ id: key, key: key, value: all_vars[key.to_sym] }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Checks if a variable with the env_var_key already exists.
|
|
76
|
+
# @param [String] application_id id of the cloud control application
|
|
77
|
+
# @param [Symbol] env_var_key key name that shall be checked for existence
|
|
78
|
+
# @param [Hash] all_vars collection of currently existing variables for the application
|
|
79
|
+
# @return [Boolean] true if there is a variable with the env_var_key, otherwise false
|
|
80
|
+
def env_var?(application_id, env_var_key, all_vars = nil)
|
|
81
|
+
all_vars = cc_vars(application_id) if all_vars.nil?
|
|
82
|
+
all_vars.key? env_var_key.to_sym
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
# AppStates for Cloud Foundry V2, or the logic to determine the current application state
|
|
6
|
+
module AppStates
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def application_state(app_resource)
|
|
10
|
+
if app_resource[:entity][:state] == 'STARTED'
|
|
11
|
+
# 1: crashed
|
|
12
|
+
return Enums::ApplicationStates::CRASHED if app_resource[:entity][:package_state] == 'FAILED'
|
|
13
|
+
# 1: started
|
|
14
|
+
return Enums::ApplicationStates::RUNNING if app_resource[:entity][:package_state] == 'STAGED'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# 4: stopped if there is a detected buildpack
|
|
18
|
+
return Enums::ApplicationStates::STOPPED unless app_resource[:entity][:staging_task_id].nil?
|
|
19
|
+
# 3: deployed if stopped but no data can be downloaded
|
|
20
|
+
return Enums::ApplicationStates::DEPLOYED if deployed?(app_resource[:metadata][:guid])
|
|
21
|
+
# 2: created if stopped and no buildpack detected
|
|
22
|
+
Enums::ApplicationStates::CREATED
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
module Application
|
|
6
|
+
# @see Stub#applications
|
|
7
|
+
def applications
|
|
8
|
+
response = get('/v2/apps')
|
|
9
|
+
apps = []
|
|
10
|
+
response.body[:resources].each do |application_resource|
|
|
11
|
+
apps << to_nucleus_app(application_resource)
|
|
12
|
+
end
|
|
13
|
+
apps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Stub#application
|
|
17
|
+
def application(application_name_or_id)
|
|
18
|
+
app_guid = app_guid(application_name_or_id)
|
|
19
|
+
get_response = get("/v2/apps/#{app_guid}")
|
|
20
|
+
to_nucleus_app(get_response.body)
|
|
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 Cloud Foundry --> remove from request
|
|
32
|
+
application.delete :region
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
apply_buildpack(application)
|
|
36
|
+
|
|
37
|
+
# apply default values, if not overridden by custom params
|
|
38
|
+
default_params = { space_guid: user_space_guid }
|
|
39
|
+
application = default_params.merge(application)
|
|
40
|
+
|
|
41
|
+
# WORKAROUND: requires numeric input, but rack-test provides characters :/
|
|
42
|
+
application['memory'] = application['memory'].to_i if application.key?('memory')
|
|
43
|
+
|
|
44
|
+
response = post('/v2/apps', body: application).body
|
|
45
|
+
|
|
46
|
+
# now create the default route (similar to when using an UI, e.g. Pivotal, Stackato or Bluemix) == web_url
|
|
47
|
+
create_cf_domain(response[:metadata][:guid], @endpoint_app_domain, response[:metadata][:guid])
|
|
48
|
+
|
|
49
|
+
# finally build the application response
|
|
50
|
+
application(response[:metadata][:guid])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @see Stub#update_application
|
|
54
|
+
def update_application(application_name_or_id, application_form)
|
|
55
|
+
app_guid = app_guid(application_name_or_id)
|
|
56
|
+
apply_buildpack(application_form)
|
|
57
|
+
# update by guid
|
|
58
|
+
update_response = put("/v2/apps/#{app_guid}", body: application_form)
|
|
59
|
+
to_nucleus_app(update_response.body)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @see Stub#delete_application
|
|
63
|
+
def delete_application(application_name_or_id)
|
|
64
|
+
app_guid = app_guid(application_name_or_id)
|
|
65
|
+
# first delete all service bindings
|
|
66
|
+
remove_all_services(app_guid)
|
|
67
|
+
# then delete the default route (otherwise it would remain as orphaned route)
|
|
68
|
+
routes = get("/v2/apps/#{app_guid}/routes?q=host:#{app_guid}&inline-relations-depth=1").body[:resources]
|
|
69
|
+
default_route = routes.find { |route| route[:entity][:domain][:entity][:name] == @endpoint_app_domain }
|
|
70
|
+
delete("/v2/routes/#{default_route[:metadata][:guid]}") if default_route
|
|
71
|
+
# and finally delete the app
|
|
72
|
+
delete("/v2/apps/#{app_guid}")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def apply_buildpack(application)
|
|
78
|
+
# handle desired runtime(s)
|
|
79
|
+
runtimes = application.delete(:runtimes)
|
|
80
|
+
return unless runtimes
|
|
81
|
+
fail_with(:only_one_runtime) if runtimes.length > 1
|
|
82
|
+
|
|
83
|
+
buildpack = find_runtime(runtimes[0])
|
|
84
|
+
# use the translated buildpack name if available, otherwise pass on the given runtime name
|
|
85
|
+
application[:buildpack] = buildpack ? buildpack : runtimes[0]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_nucleus_app(app_resource)
|
|
89
|
+
metadata = app_resource[:metadata]
|
|
90
|
+
app = app_resource[:entity]
|
|
91
|
+
|
|
92
|
+
app[:id] = metadata[:guid]
|
|
93
|
+
app[:created_at] = metadata[:created_at]
|
|
94
|
+
app[:updated_at] = metadata[:updated_at] || metadata[:created_at]
|
|
95
|
+
app[:state] = application_state(app_resource)
|
|
96
|
+
app[:web_url] = "http://#{app_web_url(metadata[:guid])}"
|
|
97
|
+
# route could have been deleted by the user
|
|
98
|
+
app[:web_url] = nil unless domain?(metadata[:guid], app[:web_url])
|
|
99
|
+
# Stackato does support autoscaling
|
|
100
|
+
app[:autoscaled] = app.delete(:autoscale_enabled) || false
|
|
101
|
+
app[:region] = 'default'
|
|
102
|
+
app[:active_runtime] = app[:detected_buildpack]
|
|
103
|
+
app[:runtimes] = app[:buildpack] ? [app[:buildpack]] : []
|
|
104
|
+
app[:release_version] = app.delete(:version)
|
|
105
|
+
app
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
# Authentication functionality to support the Cloud Foundry API
|
|
6
|
+
module Authentication
|
|
7
|
+
# @see Stub#auth_client
|
|
8
|
+
def auth_client
|
|
9
|
+
auth_endpoint = endpoint_info[:authorization_endpoint]
|
|
10
|
+
log.debug "Authenticate @ #{auth_endpoint}/oauth/token"
|
|
11
|
+
OAuth2AuthClient.new("#{auth_endpoint}/oauth/token", @check_certificates)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
module Buildpacks
|
|
6
|
+
include Nucleus::Adapters::BuildpackTranslator
|
|
7
|
+
|
|
8
|
+
# @see BuildpackTranslator#vendor_specific_runtimes
|
|
9
|
+
def vendor_specific_runtimes
|
|
10
|
+
{
|
|
11
|
+
'ruby' => 'ruby_buildpack',
|
|
12
|
+
'nodejs' => 'nodejs_buildpack',
|
|
13
|
+
'python' => 'python_buildpack',
|
|
14
|
+
'java' => 'java_buildpack',
|
|
15
|
+
'go' => 'go_buildpack',
|
|
16
|
+
'php' => 'php_buildpack'
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
# The {CloudFoundryV2} adapter is designed to support the Cloud Foundry API and uses only commands
|
|
5
|
+
# of the API version 2.<br>
|
|
6
|
+
# <br>
|
|
7
|
+
# Besides native Cloud Foundry installations, this adapter shall also work with forks, such as Stackato 3.4.2.<br>
|
|
8
|
+
# <br>
|
|
9
|
+
# The Nucleus API is fully supported, there are no known issues.
|
|
10
|
+
# @see http://apidocs.cloudfoundry.org The latest Cloud Foundry API documentation
|
|
11
|
+
class CloudFoundryV2 < Stub
|
|
12
|
+
include Nucleus::Logging
|
|
13
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Authentication
|
|
14
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::AppStates
|
|
15
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Buildpacks
|
|
16
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Application
|
|
17
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Domains
|
|
18
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Data
|
|
19
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Lifecycle
|
|
20
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Logs
|
|
21
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Regions
|
|
22
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Scaling
|
|
23
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::SemanticErrors
|
|
24
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Services
|
|
25
|
+
include Nucleus::Adapters::V1::CloudFoundryV2::Vars
|
|
26
|
+
|
|
27
|
+
def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
|
|
28
|
+
super(endpoint_url, endpoint_app_domain, check_certificates)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def handle_error(error)
|
|
32
|
+
cf_error = error.body.is_a?(Hash) ? error.body[:code] : nil
|
|
33
|
+
case error.status
|
|
34
|
+
when 400
|
|
35
|
+
handle_400_error(error, cf_error)
|
|
36
|
+
when 404
|
|
37
|
+
fail Errors::AdapterResourceNotFoundError, error.body[:description] if cf_error > 10_000
|
|
38
|
+
else
|
|
39
|
+
if [1001].include? cf_error
|
|
40
|
+
fail Errors::AdapterRequestError, "#{error.body[:description]} (#{cf_error} - #{error.body[:error_code]})"
|
|
41
|
+
elsif [10_002].include?(cf_error) || error.status == 401
|
|
42
|
+
fail Errors::EndpointAuthenticationError, 'Endpoint authentication failed with OAuth2 token'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
# error still unhandled, will result in a 500, server error
|
|
46
|
+
log.warn "Cloud Foundry error still unhandled: #{error}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def handle_400_error(error, cf_error)
|
|
52
|
+
if cf_error == 150_001 || cf_error == 160_001 || cf_error > 100_000 && cf_error < 109_999
|
|
53
|
+
# Indicating semantically invalid parameters
|
|
54
|
+
fail Errors::SemanticAdapterRequestError, error.body[:description]
|
|
55
|
+
elsif cf_error == 170_002
|
|
56
|
+
fail_with(:build_in_progress)
|
|
57
|
+
elsif cf_error == 60_002
|
|
58
|
+
fail Errors::SemanticAdapterRequestError, 'Service is already assigned to the application'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def guid?(name_or_id)
|
|
63
|
+
Regexp::UUID_PATTERN.match(name_or_id) ? true : false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def default_organization_guid
|
|
67
|
+
get("/v2/spaces/#{user_space_guid}").body[:entity][:organization_guid]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def app_guid(app_name_or_id)
|
|
71
|
+
# app name is a UUID and therefore most likely the CF GUID
|
|
72
|
+
return app_name_or_id if guid?(app_name_or_id)
|
|
73
|
+
find_app_guid_by_name(app_name_or_id)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def find_app_guid_by_name(application_name)
|
|
77
|
+
filtered_list_response = get('/v2/apps', query: { q: "name:#{application_name}" })
|
|
78
|
+
if filtered_list_response.body[:resources].length == 0
|
|
79
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
80
|
+
"Couldn't find app with name '#{application_name}' on the platform"
|
|
81
|
+
end
|
|
82
|
+
# return the found guid
|
|
83
|
+
filtered_list_response.body[:resources][0][:metadata][:guid]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def find_app_id_by_name(application_name, previous_response)
|
|
87
|
+
filtered_list_response = get('/v2/apps', query: { q: "name:#{application_name}" })
|
|
88
|
+
# fail as expected if the app can also not be found by its name
|
|
89
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
90
|
+
previous_response.body[:description] if filtered_list_response.body[:resources].length == 0
|
|
91
|
+
# return the found guid
|
|
92
|
+
filtered_list_response.body[:resources][0][:metadata][:guid]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def endpoint_info
|
|
96
|
+
get('/v2/info', headers: {}).body
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def user_info
|
|
100
|
+
get("#{endpoint_info[:authorization_endpoint]}/userinfo").body
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def user
|
|
104
|
+
get("/v2/users/#{user_info[:user_id]}").body
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def user_space_guid
|
|
108
|
+
users_spaces = get('/v2/spaces').body[:resources]
|
|
109
|
+
# only once space accessible
|
|
110
|
+
return users_spaces[0][:metadata][:guid] if users_spaces.length == 1
|
|
111
|
+
# use default space (stackato feature)
|
|
112
|
+
default_space = users_spaces.detect { |space_resource| space_resource[:entity][:is_default] == true }
|
|
113
|
+
return default_space[:metadata][:guid] if default_space
|
|
114
|
+
# check the users spaces for default
|
|
115
|
+
user_default_space_guid = user[:entity][:default_space_guid]
|
|
116
|
+
return user_default_space_guid if user_default_space_guid
|
|
117
|
+
# TODO: find a more suitable approach to detect the right space !?
|
|
118
|
+
# multiple spaces and no default space (dammit), choose the first one...
|
|
119
|
+
return users_spaces[0][:metadata][:guid] if users_spaces
|
|
120
|
+
# user has no space assigned, fail since we cant determine a space guid
|
|
121
|
+
fail_with(:no_space_assigned)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def headers
|
|
125
|
+
super.merge('Basic' => 'Y2Y6', 'Content-Type' => 'application/json')
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def deployed?(application_guid)
|
|
129
|
+
response = head("/v2/apps/#{application_guid}/download", follow_redirects: false, expects: [200, 302, 404])
|
|
130
|
+
return true if response.status == 200 || response.status == 302
|
|
131
|
+
return false if response.status == 404
|
|
132
|
+
# if the response is neither one of the codes, the call fails anyway...
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def app_web_url(app_guid)
|
|
136
|
+
"#{app_guid}.#{@endpoint_app_domain}" if @endpoint_app_domain
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
module Data
|
|
6
|
+
# @see Stub#deploy
|
|
7
|
+
def deploy(application_name_or_id, file, file_compression_format)
|
|
8
|
+
# could be made async, too
|
|
9
|
+
# resources: [] says that no previous data shall be reused, see also:
|
|
10
|
+
# http://apidocs.cloudfoundry.org/202/apps/uploads_the_bits_for_an_app.html
|
|
11
|
+
|
|
12
|
+
app_guid = app_guid(application_name_or_id)
|
|
13
|
+
# deploy by guid
|
|
14
|
+
# deploy_response = put("/v2/apps/#{app_guid}/bits", body: { resources: [], application: file.read },
|
|
15
|
+
# headers: { 'Content-Type' => 'multipart/form-data; '\
|
|
16
|
+
# 'boundary=nucleus-cloud-foundry-adapter-file-upload-boundary' })
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
# convert all archives to .zip archives
|
|
20
|
+
converted_file = ArchiveConverter.convert(file, file_compression_format, 'zip', true)
|
|
21
|
+
unless converted_file.respond_to?(:path) && converted_file.respond_to?(:read)
|
|
22
|
+
tmpfile = Tempfile.new(["nucleus-cf-deploy-upload-#{app_guid}", '.zip'])
|
|
23
|
+
tmpfile.binmode
|
|
24
|
+
tmpfile.write converted_file.read
|
|
25
|
+
tmpfile.rewind
|
|
26
|
+
converted_file = tmpfile
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# TODO: this is only a temporary solution until excon supports multipart requests
|
|
30
|
+
# See also: https://github.com/excon/excon/issues/353
|
|
31
|
+
url = "#{@endpoint_url}/v2/apps/#{app_guid}/bits"
|
|
32
|
+
request_body = { multipart: true, application: converted_file, async: false, resources: '[]' }
|
|
33
|
+
begin
|
|
34
|
+
RestClient::Request.execute(method: :put, url: url, payload: request_body,
|
|
35
|
+
headers: headers, verify_ssl: @check_certificates)
|
|
36
|
+
rescue RestClient::BadRequest => e
|
|
37
|
+
raise Errors::AdapterRequestError, e.http_body
|
|
38
|
+
end
|
|
39
|
+
ensure
|
|
40
|
+
if tmpfile
|
|
41
|
+
tmpfile.close
|
|
42
|
+
tmpfile.unlink # deletes this temporary file
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @see Stub#download
|
|
48
|
+
def download(application_name_or_id, compression_format)
|
|
49
|
+
app_guid = app_guid(application_name_or_id)
|
|
50
|
+
# fail if there is no deployment
|
|
51
|
+
unless deployed?(app_guid)
|
|
52
|
+
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be downloaded'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
download_response = get("/v2/apps/#{app_guid}/download", follow_redirects: false, expects: [200, 302])
|
|
56
|
+
if download_response.status == 200
|
|
57
|
+
data = download_response.body
|
|
58
|
+
else
|
|
59
|
+
download_location = download_response.headers[:Location]
|
|
60
|
+
# if IBM f*cked with the download URL, fix the address
|
|
61
|
+
download_location = download_location.gsub(/objectstorage.service.networklayer.com/, 'objectstorage.softlayer.net')
|
|
62
|
+
# omit_default_port: https://github.com/excon/excon/issues/475
|
|
63
|
+
data = Excon.new(download_location, omit_default_port: true).get.body
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# write data to tmpfile so that it can be converted
|
|
67
|
+
downloaded_application_archive = Tempfile.new(["nucleus-cf-deployment-download-#{app_guid}", '.zip'])
|
|
68
|
+
downloaded_application_archive.binmode
|
|
69
|
+
downloaded_application_archive.write StringIO.new(data).read
|
|
70
|
+
downloaded_application_archive.rewind
|
|
71
|
+
|
|
72
|
+
# convert from current format (which is always a zip archive) to the destination format
|
|
73
|
+
ArchiveConverter.convert(downloaded_application_archive, 'zip', compression_format, false)
|
|
74
|
+
ensure
|
|
75
|
+
if downloaded_application_archive
|
|
76
|
+
downloaded_application_archive.close
|
|
77
|
+
downloaded_application_archive.unlink
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @see Stub#rebuild
|
|
82
|
+
def rebuild(application_name_or_id)
|
|
83
|
+
app_guid = app_guid(application_name_or_id)
|
|
84
|
+
# fail if there is no deployment
|
|
85
|
+
unless deployed?(app_guid)
|
|
86
|
+
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be rebuild'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# rebuild by name or id
|
|
90
|
+
rebuild_response = post("/v2/apps/#{app_guid}/restage")
|
|
91
|
+
to_nucleus_app(rebuild_response.body)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|