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,33 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
module Regions
|
|
6
|
+
# @see Stub#regions
|
|
7
|
+
def regions
|
|
8
|
+
[default_region]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @see Stub#region
|
|
12
|
+
def region(region_name)
|
|
13
|
+
unless region_name.casecmp('default') == 0
|
|
14
|
+
fail Errors::AdapterResourceNotFoundError, "Region '#{region_name}' does not exist at the endpoint"
|
|
15
|
+
end
|
|
16
|
+
default_region
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def default_region
|
|
22
|
+
{
|
|
23
|
+
id: 'default',
|
|
24
|
+
description: 'Default region, Cloud Foundry does not support multi regions yet.',
|
|
25
|
+
created_at: Time.at(0).to_datetime,
|
|
26
|
+
updated_at: Time.at(0).to_datetime
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
module Scaling
|
|
6
|
+
# @see Stub#scale
|
|
7
|
+
def scale(application_name_or_id, instances)
|
|
8
|
+
# update the number of instances on the application
|
|
9
|
+
update_application(application_name_or_id, instances: instances)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
# Semantic error messages that are specific for Cloud Foundry V2
|
|
6
|
+
module SemanticErrors
|
|
7
|
+
# Get all Cloud Foundry V2 specific semantic error definitions.
|
|
8
|
+
# @return [Hash<Symbol,Hash<Symbol,String>>] the error message definitions, including the error +code+,
|
|
9
|
+
# e.g. +422_200_1+ and the +message+ that shall be formatted when used.
|
|
10
|
+
def semantic_error_messages
|
|
11
|
+
{
|
|
12
|
+
only_one_runtime: { code: 422_200_1, message: 'Cloud Foundry V2 only allows 1 runtime per application' },
|
|
13
|
+
build_in_progrss: { code: 422_200_2, message: 'Application build is still in progress' },
|
|
14
|
+
no_space_assigned: { code: 422_200_3, message: 'User is not assigned to any space' },
|
|
15
|
+
service_not_bindable: { code: 422_200_4, message: "Can't add service '%s' to the application: "\
|
|
16
|
+
'The service does not allow to be bound to applications.' },
|
|
17
|
+
service_not_active: { code: 422_200_5, message: "Can't add service '%s' to the application: "\
|
|
18
|
+
'The service does is not active, thus does not allow to create a new instance to bind to the app.' },
|
|
19
|
+
service_not_updateable: { code: 422_200_6, message: "Can't change service '%s' to the new plan: "\
|
|
20
|
+
'The service does not allow to update the plan.' }
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
# Cloud Foundry, operations for the application's addons
|
|
6
|
+
module Services
|
|
7
|
+
# @see Stub#services
|
|
8
|
+
def services
|
|
9
|
+
get('/v2/services?inline-relations-depth=1').body[:resources].collect do |service|
|
|
10
|
+
# show only services that are both, active and bindable
|
|
11
|
+
next unless service[:entity][:active] && service[:entity][:bindable]
|
|
12
|
+
to_nucleus_service(service)
|
|
13
|
+
end.compact
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see Stub#service
|
|
17
|
+
def service(service_id_or_name)
|
|
18
|
+
service_guid = service_guid(service_id_or_name)
|
|
19
|
+
to_nucleus_service(get("/v2/services/#{service_guid}?inline-relations-depth=1").body)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @see Stub#service_plans
|
|
23
|
+
def service_plans(service_id_or_name)
|
|
24
|
+
service_guid = service_guid(service_id_or_name)
|
|
25
|
+
load_plans(service_guid).collect { |plan| to_nucleus_plan(plan) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @see Stub#service_plan
|
|
29
|
+
def service_plan(service_id_or_name, plan_id)
|
|
30
|
+
service_guid = service_guid(service_id_or_name)
|
|
31
|
+
plan_guid = plan_guid(service_guid, plan_id, Errors::AdapterResourceNotFoundError)
|
|
32
|
+
to_nucleus_plan(get("/v2/service_plans/#{plan_guid}").body)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @see Stub#installed_services
|
|
36
|
+
def installed_services(application_name_or_id)
|
|
37
|
+
app_guid = app_guid(application_name_or_id)
|
|
38
|
+
get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources].collect do |binding|
|
|
39
|
+
to_nucleus_installed_service(binding)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @see Stub#installed_service
|
|
44
|
+
def installed_service(application_name_or_id, service_id_or_name)
|
|
45
|
+
app_guid = app_guid(application_name_or_id)
|
|
46
|
+
service_guid = service_guid(service_id_or_name)
|
|
47
|
+
cf_binding = binding(app_guid, service_guid)
|
|
48
|
+
# make sure there is a binding
|
|
49
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
50
|
+
"No such service '#{service_id_or_name}' for application '#{application_name_or_id}'" unless cf_binding
|
|
51
|
+
to_nucleus_installed_service(cf_binding)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @see Stub#add_service
|
|
55
|
+
def add_service(application_name_or_id, service_entity, plan_entity)
|
|
56
|
+
app_guid = app_guid(application_name_or_id)
|
|
57
|
+
service_guid = service_guid(service_entity[:id], Errors::SemanticAdapterRequestError)
|
|
58
|
+
cf_service = load_allowed_service(service_entity, service_guid)
|
|
59
|
+
|
|
60
|
+
# get the plan, throws 422 if the plan could not be found
|
|
61
|
+
plan_guid = plan_guid(service_guid, plan_entity[:id])
|
|
62
|
+
|
|
63
|
+
# create new service instance
|
|
64
|
+
instance_request_body = { space_guid: user_space_guid, service_plan_guid: plan_guid,
|
|
65
|
+
name: "#{cf_service[:entity][:label]}-#{application_name_or_id}-nucleus" }
|
|
66
|
+
cf_instance = post('/v2/service_instances', body: instance_request_body).body
|
|
67
|
+
|
|
68
|
+
# bind the created service instance to the application
|
|
69
|
+
binding_request_body = { service_instance_guid: cf_instance[:metadata][:guid], app_guid: app_guid }
|
|
70
|
+
cf_binding = post('/v2/service_bindings', body: binding_request_body).body
|
|
71
|
+
|
|
72
|
+
# created service presentation
|
|
73
|
+
to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @see Stub#change_service
|
|
77
|
+
def change_service(application_name_or_id, service_id, plan_entity)
|
|
78
|
+
app_guid = app_guid(application_name_or_id)
|
|
79
|
+
service_guid = service_guid(service_id)
|
|
80
|
+
cf_service = get("/v2/services/#{service_guid}").body
|
|
81
|
+
fail_with(:service_not_updateable, [service_id]) unless cf_service[:entity][:plan_updateable]
|
|
82
|
+
|
|
83
|
+
cf_binding = binding(app_guid, service_guid)
|
|
84
|
+
|
|
85
|
+
# get the plan, throws 422 if the plan could not be found
|
|
86
|
+
plan_guid = plan_guid(service_guid, plan_entity[:id])
|
|
87
|
+
cf_instance = put("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}",
|
|
88
|
+
body: { service_plan_guid: plan_guid }).body
|
|
89
|
+
to_nucleus_installed_service(cf_binding, cf_service, cf_instance)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @see Stub#remove_service
|
|
93
|
+
def remove_service(application_name_or_id, service_id)
|
|
94
|
+
app_guid = app_guid(application_name_or_id)
|
|
95
|
+
service_guid = service_guid(service_id)
|
|
96
|
+
# sadly we can't resolve the binding and instance from the service_id with ease
|
|
97
|
+
# we therefore setup a chain to resolve the binding and instance from the active pla
|
|
98
|
+
binding = binding(app_guid, service_guid)
|
|
99
|
+
|
|
100
|
+
# now remove the binding from the application
|
|
101
|
+
delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
|
|
102
|
+
# and finally delete the service instance
|
|
103
|
+
delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def load_allowed_service(service_entity, service_guid)
|
|
109
|
+
begin
|
|
110
|
+
cf_service = get("/v2/services/#{service_guid}").body
|
|
111
|
+
rescue Errors::AdapterResourceNotFoundError
|
|
112
|
+
# convert to semantic error with the service being a body, not a path entity
|
|
113
|
+
raise Errors::SemanticAdapterRequestError,
|
|
114
|
+
"Invalid service: Could not find service with the ID '#{service_entity[:id]}'"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# must be active and bindable?
|
|
118
|
+
# currently we focus only on bindable services
|
|
119
|
+
fail_with(:service_not_bindable, [service_entity[:id]]) unless cf_service[:entity][:bindable]
|
|
120
|
+
# service must be active, otherwise we can't create the instance
|
|
121
|
+
fail_with(:service_not_active, [service_entity[:id]]) unless cf_service[:entity][:active]
|
|
122
|
+
# service seems to be valid, return
|
|
123
|
+
cf_service
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def remove_all_services(app_guid)
|
|
127
|
+
get("/v2/apps/#{app_guid}/service_bindings").body[:resources].collect do |binding|
|
|
128
|
+
# remove the binding from the application
|
|
129
|
+
delete("/v2/apps/#{app_guid}/service_bindings/#{binding[:metadata][:guid]}", expects: [201])
|
|
130
|
+
# and delete the service instance to prevent orphans
|
|
131
|
+
delete("/v2/service_instances/#{binding[:entity][:service_instance_guid]}")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def service_guid(service_id_or_name, error_class = Errors::AdapterResourceNotFoundError)
|
|
136
|
+
return service_id_or_name if guid?(service_id_or_name)
|
|
137
|
+
# list all available services
|
|
138
|
+
services = get('/v2/services').body[:resources]
|
|
139
|
+
# find a match and use the service's guid
|
|
140
|
+
service_match = services.find { |service| service[:entity][:label] == service_id_or_name }
|
|
141
|
+
fail error_class,
|
|
142
|
+
"Invalid service: Could not find service with name '#{service_id_or_name}'" unless service_match
|
|
143
|
+
service_match[:metadata][:guid]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def binding(app_guid, service_id)
|
|
147
|
+
service_plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
|
|
148
|
+
service_plan_ids = service_plans.collect { |plan| plan[:metadata][:guid] }
|
|
149
|
+
app_bindings = get("/v2/apps/#{app_guid}/service_bindings?inline-relations-depth=1").body[:resources]
|
|
150
|
+
# the plan must be bound to the app via an instance
|
|
151
|
+
app_bindings.find do |binding|
|
|
152
|
+
service_plan_ids.include?(binding[:entity][:service_instance][:entity][:service_plan_guid])
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def plan_guid(service_id, plan_name_or_id, error_class = Errors::SemanticAdapterRequestError)
|
|
157
|
+
return plan_name_or_id if guid?(plan_name_or_id)
|
|
158
|
+
# list all plans for the service
|
|
159
|
+
plans = get("/v2/services/#{service_id}/service_plans").body[:resources]
|
|
160
|
+
# find a match and use the plan's guid
|
|
161
|
+
plan_match = plans.find { |plan| plan[:entity][:name] == plan_name_or_id }
|
|
162
|
+
fail error_class,
|
|
163
|
+
"Invalid plan: No such plan '#{plan_name_or_id}' for service '#{service_id}'" unless plan_match
|
|
164
|
+
plan_match[:metadata][:guid]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def load_plans(service_id)
|
|
168
|
+
get("/v2/services/#{service_id}/service_plans").body[:resources]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Memoize this detection.
|
|
172
|
+
# The information is not critical, but takes some time to evaluate.
|
|
173
|
+
# Values are not expected to change often.
|
|
174
|
+
def free_plan?(service_id, plans = nil)
|
|
175
|
+
@free_plans ||= {}
|
|
176
|
+
return @free_plans[service_id] if @free_plans.key?(service_id)
|
|
177
|
+
plans = load_plans(service_id) unless plans
|
|
178
|
+
@free_plans[service_id] = plans.any? { |plan| plan[:entity][:free] }
|
|
179
|
+
@free_plans[service_id]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def to_nucleus_plan(cf_plan)
|
|
183
|
+
plan = cf_plan[:entity]
|
|
184
|
+
plan[:id] = cf_plan[:metadata][:guid]
|
|
185
|
+
plan[:created_at] = cf_plan[:metadata][:created_at]
|
|
186
|
+
plan[:updated_at] = cf_plan[:metadata][:updated_at]
|
|
187
|
+
plan[:costs] = []
|
|
188
|
+
plan
|
|
189
|
+
|
|
190
|
+
# TODO: determine prices for CF services
|
|
191
|
+
# we know how IBM handles the costs, but can't determine the country
|
|
192
|
+
# we know how Pivotal IO handles the costs
|
|
193
|
+
# but what do the others???
|
|
194
|
+
|
|
195
|
+
# extra = Oj.load(plan[:extra])
|
|
196
|
+
# if plan[:free]
|
|
197
|
+
# plan[:costs] = { period: '', per_instance: false, price: { amount: 0.00, currency: nil} }
|
|
198
|
+
# elsif endpoint_url.include?('pivotal.io')
|
|
199
|
+
# # show prices for Pivotal Web Services
|
|
200
|
+
# # see for an explanation: http://docs.pivotal.io/pivotalcf/services/catalog-metadata.html
|
|
201
|
+
# plan[:costs] = extra[:costs].collect do |cost|
|
|
202
|
+
# prices = cost[:amount].collect { |currency, amount| { currency: currency, amount: amount } }
|
|
203
|
+
# { per_instance: false, period: cost[:unit], price: prices }
|
|
204
|
+
# end
|
|
205
|
+
# elsif endpoint_url.include?('bluemix.net')
|
|
206
|
+
# # show prices for IBM Bluemix
|
|
207
|
+
# else
|
|
208
|
+
# # fallback, unknown CF system
|
|
209
|
+
# end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def to_nucleus_service(cf_service)
|
|
213
|
+
service = cf_service[:entity]
|
|
214
|
+
service = apply_metadata(service, cf_service)
|
|
215
|
+
service[:name] = service.delete(:label)
|
|
216
|
+
service[:release] = service.delete(:version)
|
|
217
|
+
if cf_service[:entity].key?(:service_plans)
|
|
218
|
+
# use preloaded plans if available
|
|
219
|
+
service[:free_plan] = free_plan?(service[:id], cf_service[:entity][:service_plans])
|
|
220
|
+
else
|
|
221
|
+
service[:free_plan] = free_plan?(service[:id])
|
|
222
|
+
end
|
|
223
|
+
# CF does not have service dependencies
|
|
224
|
+
service[:required_services] = service.delete(:requires)
|
|
225
|
+
# description and documentation_url should already be set
|
|
226
|
+
service
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Show the installed service. Therefore we need:<br>
|
|
230
|
+
# <ul>
|
|
231
|
+
# <li>the classic service for the basic information (version, name, ...)</li>
|
|
232
|
+
# <li>the binding for the properties and metadata (id, timestamps, ...)</li>
|
|
233
|
+
# <li>the bound instance for the active plan and web_url</li>
|
|
234
|
+
# </ul>
|
|
235
|
+
def to_nucleus_installed_service(cf_binding, cf_service = nil, cf_instance = nil)
|
|
236
|
+
# load if not provided
|
|
237
|
+
cf_instance = load_instance(cf_binding) unless cf_instance
|
|
238
|
+
cf_service = load_service(cf_instance) unless cf_service
|
|
239
|
+
# load if not provided
|
|
240
|
+
unless cf_service
|
|
241
|
+
cf_service = get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
|
|
242
|
+
'?inline-relations-depth=1').body[:entity][:service]
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# active_plan, web_url, properties
|
|
246
|
+
service = to_nucleus_service(cf_service)
|
|
247
|
+
# use the metadata of the binding, is more future proof than instance metadata
|
|
248
|
+
apply_metadata(service, cf_binding)
|
|
249
|
+
service[:active_plan] = cf_instance[:entity][:service_plan_guid]
|
|
250
|
+
service[:web_url] = cf_instance[:entity][:dashboard_url]
|
|
251
|
+
service[:properties] = binding_properties(cf_binding)
|
|
252
|
+
service
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def load_instance(cf_binding)
|
|
256
|
+
if cf_binding[:entity].key?(:service_instance)
|
|
257
|
+
# use if nested property is available
|
|
258
|
+
cf_binding[:entity][:service_instance]
|
|
259
|
+
else
|
|
260
|
+
get("/v2/service_instances/#{cf_binding[:entity][:service_instance_guid]}").body
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def load_service(cf_instance)
|
|
265
|
+
get("/v2/service_plans/#{cf_instance[:entity][:service_plan_guid]}"\
|
|
266
|
+
'?inline-relations-depth=1').body[:entity][:service]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def binding_properties(binding)
|
|
270
|
+
# in the credentials there are information such as: hostname, username, password, license keys, ...
|
|
271
|
+
binding[:entity][:credentials].collect do |key, value|
|
|
272
|
+
{ key: key, value: value, description: nil }
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def apply_metadata(apply_to, cf_object)
|
|
277
|
+
apply_to[:id] = cf_object[:metadata][:guid]
|
|
278
|
+
apply_to[:created_at] = cf_object[:metadata][:created_at]
|
|
279
|
+
apply_to[:updated_at] = cf_object[:metadata][:updated_at]
|
|
280
|
+
apply_to
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class CloudFoundryV2 < Stub
|
|
5
|
+
module Vars
|
|
6
|
+
# @see Stub#env_vars
|
|
7
|
+
def env_vars(application_name_or_id)
|
|
8
|
+
app_guid = app_guid(application_name_or_id)
|
|
9
|
+
app_vars = get("/v2/apps/#{app_guid}/env").body[:environment_json]
|
|
10
|
+
formatted_vars = []
|
|
11
|
+
app_vars.each do |key, value|
|
|
12
|
+
formatted_vars.push(id: key, key: key, value: value)
|
|
13
|
+
end
|
|
14
|
+
formatted_vars
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @see Stub#env_var
|
|
18
|
+
def env_var(application_name_or_id, env_var_key)
|
|
19
|
+
app_guid = app_guid(application_name_or_id)
|
|
20
|
+
all_vars = get("/v2/apps/#{app_guid}/env").body[:environment_json]
|
|
21
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
22
|
+
"Env. var key '#{env_var_key}' does not exist" unless env_var?(app_guid, env_var_key, all_vars)
|
|
23
|
+
{ id: env_var_key, key: env_var_key, value: all_vars[env_var_key.to_sym] }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @see Stub#create_env_var
|
|
27
|
+
def create_env_var(application_name_or_id, env_var)
|
|
28
|
+
app_guid = app_guid(application_name_or_id)
|
|
29
|
+
fail Errors::SemanticAdapterRequestError,
|
|
30
|
+
"Env. var key '#{env_var[:key]}' already taken" if env_var?(app_guid, env_var[:key])
|
|
31
|
+
set_var(app_guid, env_var[:key].to_sym, env_var[:value])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @see Stub#update_env_var
|
|
35
|
+
def update_env_var(application_name_or_id, env_var_key, env_var)
|
|
36
|
+
app_guid = app_guid(application_name_or_id)
|
|
37
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
38
|
+
"Env. var key '#{env_var_key}' does not exist" unless env_var?(app_guid, env_var_key)
|
|
39
|
+
set_var(app_guid, env_var_key.to_sym, env_var[:value])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @see Stub#delete_env_var
|
|
43
|
+
def delete_env_var(application_name_or_id, env_var_key)
|
|
44
|
+
app_guid = app_guid(application_name_or_id)
|
|
45
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
46
|
+
"Env. var key '#{env_var_key}' does not exist" unless env_var?(app_guid, env_var_key)
|
|
47
|
+
request_body = get("/v2/apps/#{app_guid}/env").body[:environment_json]
|
|
48
|
+
request_body.delete(env_var_key.to_sym)
|
|
49
|
+
put("/v2/apps/#{app_guid}", body: { environment_json: request_body }).body[:entity][:environment_json]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Set the variable value (create or update) and include all already existing variables
|
|
55
|
+
# to protect them from being deleted.
|
|
56
|
+
# @param [String] app_guid GUID of the CF 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(app_guid, key, value)
|
|
61
|
+
request_body = get("/v2/apps/#{app_guid}/env").body[:environment_json]
|
|
62
|
+
request_body[key] = value
|
|
63
|
+
vars = put("/v2/apps/#{app_guid}", body: { environment_json: request_body }).body[:entity]
|
|
64
|
+
{ id: key, key: key, value: vars[:environment_json][key] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Checks if a variable with the env_var_key already exists.
|
|
68
|
+
# @param [String] app_guid GUID of the CF application
|
|
69
|
+
# @param [Symbol] env_var_key key name that shall be checked for existence
|
|
70
|
+
# @param [Hash] all_vars collection of currently existing variables for the application
|
|
71
|
+
# @return [Boolean] true if there is a variable with the env_var_key, otherwise false
|
|
72
|
+
def env_var?(app_guid, env_var_key, all_vars = nil)
|
|
73
|
+
all_vars = get("/v2/apps/#{app_guid}/env").body[:environment_json] if all_vars.nil?
|
|
74
|
+
all_vars.key? env_var_key.to_sym
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class Heroku < Stub
|
|
5
|
+
# AppStates for Heroku, or the logic to determine the current application state
|
|
6
|
+
module AppStates
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def application_state(app, retrieved_dynos = nil)
|
|
10
|
+
# 1: created, both repo and slug are nil
|
|
11
|
+
return Enums::ApplicationStates::CREATED unless repo_or_slug_content?(app)
|
|
12
|
+
|
|
13
|
+
# all subsequent states require dynos to be determined
|
|
14
|
+
dynos = retrieved_dynos ? retrieved_dynos : dynos(app[:id])
|
|
15
|
+
|
|
16
|
+
# 2: deployed if no dynos assigned
|
|
17
|
+
return Enums::ApplicationStates::DEPLOYED if dynos.empty?
|
|
18
|
+
|
|
19
|
+
# 3: stopped if maintenance
|
|
20
|
+
return Enums::ApplicationStates::STOPPED if app[:maintenance] || dynos_not_running?(dynos)
|
|
21
|
+
|
|
22
|
+
# 4: running if no maintenance (checked above) and at least ony dyno is up
|
|
23
|
+
return Enums::ApplicationStates::RUNNING if dyno_states(dynos).include?('up')
|
|
24
|
+
|
|
25
|
+
# 5: idle if all dynos are idling
|
|
26
|
+
return Enums::ApplicationStates::IDLE if dynos_idle?(dynos)
|
|
27
|
+
|
|
28
|
+
# arriving here the above states do not catch all states of the Heroku app, which should not happen ;-)
|
|
29
|
+
log.debug("Faild to determine state for: #{app}, #{dynos}")
|
|
30
|
+
fail Errors::UnknownAdapterCallError, 'Could not determine app state. Please verify the Heroku adapter'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def repo_or_slug_content?(app)
|
|
34
|
+
return true if !app[:repo_size].nil? && app[:repo_size].to_i > 0
|
|
35
|
+
return true if !app[:slug_size].nil? && app[:slug_size].to_i > 0
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def dyno_states(dynos)
|
|
40
|
+
dynos.collect { |dyno| dyno[:state] }.compact.uniq
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def dynos_idle?(dynos)
|
|
44
|
+
dyno_states = dyno_states(dynos)
|
|
45
|
+
dyno_states.length == 1 && dyno_states[0] == 'idle'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def dynos_not_running?(dynos)
|
|
49
|
+
dynos.empty? || dyno_states(dynos).reject do |state|
|
|
50
|
+
%w(crashed down starting).include?(state)
|
|
51
|
+
end.empty?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|