nucleus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +1 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +13 -0
- data/Gemfile +16 -0
- data/Guardfile +22 -0
- data/LICENSE +21 -0
- data/README.md +675 -0
- data/Rakefile +137 -0
- data/bin/nucleus +91 -0
- data/bin/nucleus.bat +1 -0
- data/config.ru +18 -0
- data/config/adapters/cloud_control.yml +32 -0
- data/config/adapters/cloud_foundry_v2.yml +61 -0
- data/config/adapters/heroku.yml +13 -0
- data/config/adapters/openshift_v2.yml +20 -0
- data/config/nucleus_config.rb +47 -0
- data/lib/nucleus.rb +13 -0
- data/lib/nucleus/adapter_resolver.rb +115 -0
- data/lib/nucleus/adapters/base_adapter.rb +109 -0
- data/lib/nucleus/adapters/buildpack_translator.rb +79 -0
- data/lib/nucleus/adapters/v1/cloud_control/application.rb +108 -0
- data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_control/buildpacks.rb +23 -0
- data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +153 -0
- data/lib/nucleus/adapters/v1/cloud_control/data.rb +76 -0
- data/lib/nucleus/adapters/v1/cloud_control/domains.rb +68 -0
- data/lib/nucleus/adapters/v1/cloud_control/lifecycle.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_control/log_poller.rb +71 -0
- data/lib/nucleus/adapters/v1/cloud_control/logs.rb +103 -0
- data/lib/nucleus/adapters/v1/cloud_control/regions.rb +32 -0
- data/lib/nucleus/adapters/v1/cloud_control/scaling.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_control/semantic_errors.rb +31 -0
- data/lib/nucleus/adapters/v1/cloud_control/services.rb +162 -0
- data/lib/nucleus/adapters/v1/cloud_control/token.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_control/vars.rb +88 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/app_states.rb +28 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/application.rb +111 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/authentication.rb +17 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/buildpacks.rb +23 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/cloud_foundry_v2.rb +141 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/data.rb +97 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +149 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/lifecycle.rb +41 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +303 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/regions.rb +33 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/scaling.rb +15 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/semantic_errors.rb +27 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +286 -0
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/vars.rb +80 -0
- data/lib/nucleus/adapters/v1/heroku/app_states.rb +57 -0
- data/lib/nucleus/adapters/v1/heroku/application.rb +93 -0
- data/lib/nucleus/adapters/v1/heroku/authentication.rb +27 -0
- data/lib/nucleus/adapters/v1/heroku/buildpacks.rb +27 -0
- data/lib/nucleus/adapters/v1/heroku/data.rb +78 -0
- data/lib/nucleus/adapters/v1/heroku/domains.rb +43 -0
- data/lib/nucleus/adapters/v1/heroku/heroku.rb +146 -0
- data/lib/nucleus/adapters/v1/heroku/lifecycle.rb +51 -0
- data/lib/nucleus/adapters/v1/heroku/logs.rb +108 -0
- data/lib/nucleus/adapters/v1/heroku/regions.rb +42 -0
- data/lib/nucleus/adapters/v1/heroku/scaling.rb +28 -0
- data/lib/nucleus/adapters/v1/heroku/semantic_errors.rb +23 -0
- data/lib/nucleus/adapters/v1/heroku/services.rb +168 -0
- data/lib/nucleus/adapters/v1/heroku/vars.rb +65 -0
- data/lib/nucleus/adapters/v1/openshift_v2/app_states.rb +68 -0
- data/lib/nucleus/adapters/v1/openshift_v2/application.rb +108 -0
- data/lib/nucleus/adapters/v1/openshift_v2/authentication.rb +21 -0
- data/lib/nucleus/adapters/v1/openshift_v2/data.rb +96 -0
- data/lib/nucleus/adapters/v1/openshift_v2/domains.rb +37 -0
- data/lib/nucleus/adapters/v1/openshift_v2/lifecycle.rb +60 -0
- data/lib/nucleus/adapters/v1/openshift_v2/logs.rb +106 -0
- data/lib/nucleus/adapters/v1/openshift_v2/openshift_v2.rb +125 -0
- data/lib/nucleus/adapters/v1/openshift_v2/regions.rb +58 -0
- data/lib/nucleus/adapters/v1/openshift_v2/scaling.rb +39 -0
- data/lib/nucleus/adapters/v1/openshift_v2/semantic_errors.rb +40 -0
- data/lib/nucleus/adapters/v1/openshift_v2/services.rb +173 -0
- data/lib/nucleus/adapters/v1/openshift_v2/vars.rb +49 -0
- data/lib/nucleus/adapters/v1/stub_adapter.rb +464 -0
- data/lib/nucleus/core/adapter_authentication_inductor.rb +62 -0
- data/lib/nucleus/core/adapter_extensions/auth/auth_client.rb +44 -0
- data/lib/nucleus/core/adapter_extensions/auth/authentication_retry_wrapper.rb +79 -0
- data/lib/nucleus/core/adapter_extensions/auth/expiring_token_auth_client.rb +53 -0
- data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +37 -0
- data/lib/nucleus/core/adapter_extensions/auth/o_auth2_auth_client.rb +95 -0
- data/lib/nucleus/core/adapter_extensions/auth/token_auth_client.rb +36 -0
- data/lib/nucleus/core/adapter_extensions/http_client.rb +177 -0
- data/lib/nucleus/core/adapter_extensions/http_tail_client.rb +26 -0
- data/lib/nucleus/core/adapter_extensions/tail_stopper.rb +25 -0
- data/lib/nucleus/core/common/errors/ambiguous_adapter_error.rb +7 -0
- data/lib/nucleus/core/common/errors/file_existence_error.rb +7 -0
- data/lib/nucleus/core/common/errors/startup_error.rb +12 -0
- data/lib/nucleus/core/common/exit_codes.rb +25 -0
- data/lib/nucleus/core/common/files/application_repo_sanitizer.rb +52 -0
- data/lib/nucleus/core/common/files/archive_extractor.rb +112 -0
- data/lib/nucleus/core/common/files/archiver.rb +91 -0
- data/lib/nucleus/core/common/link_generator.rb +46 -0
- data/lib/nucleus/core/common/logging/logging.rb +52 -0
- data/lib/nucleus/core/common/logging/multi_logger.rb +59 -0
- data/lib/nucleus/core/common/logging/request_log_formatter.rb +48 -0
- data/lib/nucleus/core/common/ssh_handler.rb +108 -0
- data/lib/nucleus/core/common/stream_callback.rb +27 -0
- data/lib/nucleus/core/common/thread_config_accessor.rb +85 -0
- data/lib/nucleus/core/common/url_converter.rb +28 -0
- data/lib/nucleus/core/enums/application_states.rb +26 -0
- data/lib/nucleus/core/enums/logfile_types.rb +28 -0
- data/lib/nucleus/core/error_messages.rb +127 -0
- data/lib/nucleus/core/errors/adapter_error.rb +13 -0
- data/lib/nucleus/core/errors/adapter_missing_implementation_error.rb +12 -0
- data/lib/nucleus/core/errors/adapter_request_error.rb +10 -0
- data/lib/nucleus/core/errors/adapter_resource_not_found_error.rb +10 -0
- data/lib/nucleus/core/errors/endpoint_authentication_error.rb +10 -0
- data/lib/nucleus/core/errors/platform_specific_semantic_error.rb +12 -0
- data/lib/nucleus/core/errors/platform_timeout_error.rb +10 -0
- data/lib/nucleus/core/errors/platform_unavailable_error.rb +10 -0
- data/lib/nucleus/core/errors/semantic_adapter_request_error.rb +19 -0
- data/lib/nucleus/core/errors/unknown_adapter_call_error.rb +10 -0
- data/lib/nucleus/core/file_handling/archive_converter.rb +29 -0
- data/lib/nucleus/core/file_handling/file_manager.rb +64 -0
- data/lib/nucleus/core/file_handling/git_deployer.rb +133 -0
- data/lib/nucleus/core/file_handling/git_repo_analyzer.rb +23 -0
- data/lib/nucleus/core/import/adapter_configuration.rb +53 -0
- data/lib/nucleus/core/import/vendor_parser.rb +28 -0
- data/lib/nucleus/core/import/version_detector.rb +18 -0
- data/lib/nucleus/core/models/abstract_model.rb +29 -0
- data/lib/nucleus/core/models/endpoint.rb +30 -0
- data/lib/nucleus/core/models/provider.rb +26 -0
- data/lib/nucleus/core/models/vendor.rb +22 -0
- data/lib/nucleus/ext/kernel.rb +5 -0
- data/lib/nucleus/ext/regexp.rb +49 -0
- data/lib/nucleus/os.rb +15 -0
- data/lib/nucleus/root_dir.rb +13 -0
- data/lib/nucleus/scripts/finalize.rb +8 -0
- data/lib/nucleus/scripts/initialize.rb +9 -0
- data/lib/nucleus/scripts/initialize_config_defaults.rb +26 -0
- data/lib/nucleus/scripts/load.rb +17 -0
- data/lib/nucleus/scripts/load_dependencies.rb +43 -0
- data/lib/nucleus/scripts/setup_config.rb +28 -0
- data/lib/nucleus/scripts/shutdown.rb +11 -0
- data/lib/nucleus/version.rb +3 -0
- data/nucleus.gemspec +88 -0
- data/public/robots.txt +2 -0
- data/public/swagger-ui/css/reset.css +125 -0
- data/public/swagger-ui/css/screen.css +1224 -0
- data/public/swagger-ui/images/apple-touch-icon-114x114.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-120x120.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-144x144.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-152x152.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-57x57.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-60x60.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-72x72.png +0 -0
- data/public/swagger-ui/images/apple-touch-icon-76x76.png +0 -0
- data/public/swagger-ui/images/explorer_icons.png +0 -0
- data/public/swagger-ui/images/favicon-128.png +0 -0
- data/public/swagger-ui/images/favicon-16x16.png +0 -0
- data/public/swagger-ui/images/favicon-196x196.png +0 -0
- data/public/swagger-ui/images/favicon-32x32.png +0 -0
- data/public/swagger-ui/images/favicon-96x96.png +0 -0
- data/public/swagger-ui/images/favicon.ico +0 -0
- data/public/swagger-ui/images/logo_small.png +0 -0
- data/public/swagger-ui/images/mstile-144x144.png +0 -0
- data/public/swagger-ui/images/mstile-150x150.png +0 -0
- data/public/swagger-ui/images/mstile-310x150.png +0 -0
- data/public/swagger-ui/images/mstile-310x310.png +0 -0
- data/public/swagger-ui/images/mstile-70x70.png +0 -0
- data/public/swagger-ui/images/pet_store_api.png +0 -0
- data/public/swagger-ui/images/throbber.gif +0 -0
- data/public/swagger-ui/images/wordnik_api.png +0 -0
- data/public/swagger-ui/index.html +107 -0
- data/public/swagger-ui/lib/backbone-min.js +38 -0
- data/public/swagger-ui/lib/handlebars-1.0.0.js +2278 -0
- data/public/swagger-ui/lib/highlight.7.3.pack.js +1 -0
- data/public/swagger-ui/lib/jquery-1.8.0.min.js +2 -0
- data/public/swagger-ui/lib/jquery.ba-bbq.min.js +18 -0
- data/public/swagger-ui/lib/jquery.slideto.min.js +1 -0
- data/public/swagger-ui/lib/jquery.wiggle.min.js +8 -0
- data/public/swagger-ui/lib/shred.bundle.js +2765 -0
- data/public/swagger-ui/lib/shred/content.js +193 -0
- data/public/swagger-ui/lib/swagger-oauth.js +211 -0
- data/public/swagger-ui/lib/swagger.js +1653 -0
- data/public/swagger-ui/lib/underscore-min.js +32 -0
- data/public/swagger-ui/o2c.html +15 -0
- data/public/swagger-ui/redirect.html +14 -0
- data/public/swagger-ui/swagger-ui.js +2324 -0
- data/public/swagger-ui/swagger-ui.min.js +1 -0
- data/schemas/api.adapter.schema.yml +31 -0
- data/schemas/api.requirements.schema.yml +17 -0
- data/spec/factories/models.rb +61 -0
- data/spec/integration/api/auth_spec.rb +58 -0
- data/spec/integration/api/endpoints_spec.rb +167 -0
- data/spec/integration/api/errors_spec.rb +47 -0
- data/spec/integration/api/providers_spec.rb +157 -0
- data/spec/integration/api/swagger_schema_spec.rb +64 -0
- data/spec/integration/api/vendors_spec.rb +45 -0
- data/spec/integration/integration_spec_helper.rb +27 -0
- data/spec/integration/test_data_generator.rb +55 -0
- data/spec/nucleus_git_key.pem +51 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/shared_example_request_types.rb +99 -0
- data/spec/test_suites.rake +31 -0
- data/spec/unit/adapters/archive_converter_spec.rb +25 -0
- data/spec/unit/adapters/file_manager_spec.rb +93 -0
- data/spec/unit/adapters/git_deployer_spec.rb +262 -0
- data/spec/unit/adapters/v1/stub_spec.rb +14 -0
- data/spec/unit/common/helpers/auth_helper_spec.rb +73 -0
- data/spec/unit/common/oauth2_auth_client_spec.rb +108 -0
- data/spec/unit/common/regexp_spec.rb +33 -0
- data/spec/unit/common/request_log_formatter_spec.rb +108 -0
- data/spec/unit/common/thread_config_accessor_spec.rb +97 -0
- data/spec/unit/models/endpoint_spec.rb +83 -0
- data/spec/unit/models/provider_spec.rb +102 -0
- data/spec/unit/models/vendor_spec.rb +100 -0
- data/spec/unit/schemas/adapter_schema_spec.rb +16 -0
- data/spec/unit/schemas/adapter_validation_spec.rb +56 -0
- data/spec/unit/schemas/requirements_schema_spec.rb +16 -0
- data/spec/unit/unit_spec_helper.rb +11 -0
- data/tasks/compatibility.rake +113 -0
- data/tasks/evaluation.rake +162 -0
- data/wiki/adapter_tests.md +99 -0
- data/wiki/implement_new_adapter.md +155 -0
- metadata +836 -0
@@ -0,0 +1,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
|