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,108 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class OpenshiftV2 < Stub
|
|
5
|
+
module Application
|
|
6
|
+
# @see Stub#applications
|
|
7
|
+
def applications
|
|
8
|
+
get('/applications').body[:data].collect { |application| to_nucleus_app(application) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @see Stub#application
|
|
12
|
+
def application(application_id)
|
|
13
|
+
to_nucleus_app(get("/application/#{app_id_by_name(application_id)}").body[:data])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Creates the Openshift application and enables scaling by default.
|
|
17
|
+
# @see Stub#create_application
|
|
18
|
+
def create_application(application_entity)
|
|
19
|
+
# handle runtimes / cartridges
|
|
20
|
+
fail_with(:only_one_runtime) if application_entity[:runtimes].length > 1
|
|
21
|
+
fail_with(:must_have_runtime) if application_entity[:runtimes].length == 0
|
|
22
|
+
application_entity[:cartridge] = cartridge(application_entity.delete(:runtimes)[0])
|
|
23
|
+
|
|
24
|
+
# updates the application with a valid region identity
|
|
25
|
+
retrieve_region(application_entity) if application_entity.key?(:region)
|
|
26
|
+
|
|
27
|
+
# enable application scaling by default
|
|
28
|
+
application_entity[:scale] = true unless application_entity.key?(:scale)
|
|
29
|
+
created_application = post("/domains/#{app_domain}/applications", body: application_entity).body
|
|
30
|
+
# now make sure we keep at least 2 deployments, allows proper identification of application state
|
|
31
|
+
updated_application = put("/application/#{created_application[:data][:id]}",
|
|
32
|
+
body: { keep_deployments: 2, auto_deploy: false }).body
|
|
33
|
+
to_nucleus_app(updated_application[:data])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @see Stub#delete_application
|
|
37
|
+
def delete_application(application_id)
|
|
38
|
+
delete("/applications/#{app_id_by_name(application_id)}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def app_id_by_name(application_name_or_id)
|
|
44
|
+
unless application_name_or_id.length == 24 && application_name_or_id.match(/[0-9a-f]{24}/)
|
|
45
|
+
response = get("/domains/#{app_domain}/applications/#{application_name_or_id}", expects: [200, 404])
|
|
46
|
+
return response.body[:data][:id] if response.status == 200
|
|
47
|
+
end
|
|
48
|
+
# presumably already is an application id
|
|
49
|
+
application_name_or_id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cartridge(runtime)
|
|
53
|
+
cartridges = get('/cartridges').body[:data]
|
|
54
|
+
matched_cartridges, partial_matches = matching_cartridges(cartridges, runtime)
|
|
55
|
+
|
|
56
|
+
fail_with(:ambiguous_runtime, [runtime, matched_cartridges]) if matched_cartridges.length > 1
|
|
57
|
+
return matched_cartridges[0][:name] unless matched_cartridges.empty?
|
|
58
|
+
fail_with(:invalid_runtime, [runtime]) if partial_matches.empty?
|
|
59
|
+
|
|
60
|
+
latest = -1
|
|
61
|
+
partial_matches.each { |v| latest = v if v.to_f > latest.to_f }
|
|
62
|
+
matched_cartridges.push(cartridges.find { |cartridge| cartridge[:name] == "#{runtime}-#{latest}" })
|
|
63
|
+
log.info("Selected cartridge '#{matched_cartridges.last[:name]}' to match '#{runtime}'")
|
|
64
|
+
matched_cartridges.last[:name]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def matching_cartridges(cartridges, runtime)
|
|
68
|
+
partial_matches = []
|
|
69
|
+
matches = cartridges.find_all do |cartridge|
|
|
70
|
+
if cartridge[:type] != 'standalone'
|
|
71
|
+
false
|
|
72
|
+
elsif cartridge[:name] == runtime
|
|
73
|
+
true
|
|
74
|
+
else
|
|
75
|
+
# is the name partially valid?
|
|
76
|
+
matches = cartridge[:name].match(/(\w+)-([\.\d]+)/)
|
|
77
|
+
# push the version so that we can finally choose the latest version
|
|
78
|
+
partial_matches.push(matches[2]) if matches[1] == runtime
|
|
79
|
+
# nevertheless at first the cartridge is invalid
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
[matches, partial_matches]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def to_nucleus_app(app, gear_groups = nil, deployments = nil)
|
|
87
|
+
gear_groups = load_gears(app[:id]) unless gear_groups
|
|
88
|
+
deployments = load_deployments(app[:id]) unless deployments
|
|
89
|
+
|
|
90
|
+
app[:release_version] = active_deployment(app, deployments)[:sha1]
|
|
91
|
+
app[:state] = application_state(app, gear_groups, deployments)
|
|
92
|
+
app[:web_url] = app.delete :app_url
|
|
93
|
+
app[:autoscaled] = app.delete :scalable
|
|
94
|
+
app[:region] = gear_groups[0][:gears][0][:region]
|
|
95
|
+
app[:instances] = app.delete :gear_count
|
|
96
|
+
app[:created_at] = app.delete :creation_time
|
|
97
|
+
# applications can't be updated, use creation timestamp
|
|
98
|
+
app[:updated_at] = app[:created_at]
|
|
99
|
+
app[:active_runtime] = app.delete :framework
|
|
100
|
+
# no additional runtimes, only one fixed (active) runtime per application
|
|
101
|
+
app[:runtimes] = [app[:active_runtime]]
|
|
102
|
+
app
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class OpenshiftV2 < Stub
|
|
5
|
+
# Authentication functionality to support the Openshift V2 API
|
|
6
|
+
module Authentication
|
|
7
|
+
# @see Stub#auth_client
|
|
8
|
+
def auth_client
|
|
9
|
+
HttpBasicAuthClient.new @check_certificates do |verify_ssl, headers|
|
|
10
|
+
# auth verification block
|
|
11
|
+
headers['Accept'] = 'application/json; version=1.7'
|
|
12
|
+
result = Excon.new("#{@endpoint_url}/user", ssl_verify_peer: verify_ssl).get(headers: headers)
|
|
13
|
+
# Openshift returns 401 for invalid credentials --> auth failed, return false
|
|
14
|
+
result.status != 401
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class OpenshiftV2 < Stub
|
|
5
|
+
module Data
|
|
6
|
+
# @see Stub#deploy
|
|
7
|
+
def deploy(application_id, file, file_compression_format)
|
|
8
|
+
app_id = app_id_by_name(application_id)
|
|
9
|
+
app = get("/application/#{app_id}").body[:data]
|
|
10
|
+
app_state = application_state(app)
|
|
11
|
+
account = get('/user').body[:data]
|
|
12
|
+
repo_name = "nucleus.app.repo.openshift_v2.deploy.#{application_id}.#{SecureRandom.uuid}"
|
|
13
|
+
# clone, extract, push and finally delete cloned repository (sync)
|
|
14
|
+
with_ssh_key do
|
|
15
|
+
GitDeployer.new(repo_name, app[:git_url], account[:email]).deploy(file, file_compression_format)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# auto deployment could be active for applications not created with Nucleus
|
|
19
|
+
return if app[:auto_deploy]
|
|
20
|
+
|
|
21
|
+
build_deployment(app_id)
|
|
22
|
+
|
|
23
|
+
return unless app_state == Enums::ApplicationStates::CREATED
|
|
24
|
+
|
|
25
|
+
# and finally stop so we don't get to see the sample application and switch to the deployed state
|
|
26
|
+
send_event(application_id, 'stop')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @see Stub#download
|
|
30
|
+
def download(application_id, compression_format)
|
|
31
|
+
# Only possible with git
|
|
32
|
+
app = get("/application/#{app_id_by_name(application_id)}").body[:data]
|
|
33
|
+
if application_state(app) == Enums::ApplicationStates::CREATED
|
|
34
|
+
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be downloaded'
|
|
35
|
+
end
|
|
36
|
+
# compress files to archive but exclude the .git repo
|
|
37
|
+
repo_name = "nucleus.app.repo.openshift_v2.download.#{application_id}.#{SecureRandom.uuid}"
|
|
38
|
+
with_ssh_key do
|
|
39
|
+
GitDeployer.new(repo_name, app[:git_url], nil).download(compression_format, true)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @see Stub#rebuild
|
|
44
|
+
def rebuild(application_id)
|
|
45
|
+
app_id = app_id_by_name(application_id)
|
|
46
|
+
app = get("/application/#{app_id}").body[:data]
|
|
47
|
+
if application_state(app) == Enums::ApplicationStates::CREATED
|
|
48
|
+
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be rebuild'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
account = get('/user').body[:data]
|
|
52
|
+
repo_name = "nucleus.app.repo.openshift_v2.rebuild.#{application_id}.#{SecureRandom.uuid}"
|
|
53
|
+
|
|
54
|
+
with_ssh_key do
|
|
55
|
+
GitDeployer.new(repo_name, app[:git_url], account[:email]).trigger_build
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# if auto deployment ist disabled, we must also trigger a clean build
|
|
59
|
+
build_deployment(app_id) unless app[:auto_deploy]
|
|
60
|
+
|
|
61
|
+
# return with updated application
|
|
62
|
+
application(application_id)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def build_deployment(app_id)
|
|
68
|
+
# deploy
|
|
69
|
+
post("/application/#{app_id}/deployments", body: { force_clean_build: true })
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def with_ssh_key
|
|
73
|
+
# 409 Conflict:
|
|
74
|
+
# - 120: SSH key with name #{name} already exists. Use a different name or delete conflicting key and retry
|
|
75
|
+
# - 121: Given public key is already in use. Use different key or delete conflicting key and retry.
|
|
76
|
+
|
|
77
|
+
# load ssh key into Openshift
|
|
78
|
+
matches = nucleus_config.ssh.handler.public_key.match(/(.*)\s{1}(.*)\s{1}(.*)/)
|
|
79
|
+
key_name = register_key(matches[1], matches[2])
|
|
80
|
+
return yield
|
|
81
|
+
ensure
|
|
82
|
+
# unload ssh key, allow 404 if the key couldn't be registered at first
|
|
83
|
+
delete("/user/keys/#{key_name}", expects: [200, 204, 404]) if key_name
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def register_key(type, key)
|
|
87
|
+
key_name = "nucleus-#{SecureRandom.uuid}"
|
|
88
|
+
# ignore if the key was already assigned to a different name (status == 409 && exit_code == 121)
|
|
89
|
+
post('/user/keys', body: { name: key_name, type: type, content: key }, expects: [201, 409])
|
|
90
|
+
key_name
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class OpenshiftV2 < Stub
|
|
5
|
+
module Domains
|
|
6
|
+
# @see Stub#domains
|
|
7
|
+
def domains(application_id)
|
|
8
|
+
domains = get("/application/#{app_id_by_name(application_id)}/aliases").body[:data]
|
|
9
|
+
domains.collect { |domain| to_nucleus_domain(domain) }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @see Stub#domain
|
|
13
|
+
def domain(application_id, domain_id)
|
|
14
|
+
to_nucleus_domain get("/application/#{app_id_by_name(application_id)}/alias/#{domain_id}").body[:data]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @see Stub#create_domain
|
|
18
|
+
def create_domain(application_id, domain_entity)
|
|
19
|
+
to_nucleus_domain post("/application/#{app_id_by_name(application_id)}/aliases",
|
|
20
|
+
body: { id: domain_entity[:name] }).body[:data]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @see Stub#delete_domain
|
|
24
|
+
def delete_domain(application_id, domain_id)
|
|
25
|
+
delete("/application/#{app_id_by_name(application_id)}/alias/#{domain_id}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def to_nucleus_domain(domain)
|
|
31
|
+
{ id: domain[:id], name: domain[:id], created_at: nil, updated_at: nil }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
class OpenshiftV2 < Stub
|
|
5
|
+
module Lifecycle
|
|
6
|
+
# @see Stub#start
|
|
7
|
+
def start(application_id)
|
|
8
|
+
# if app is only deployed, we must first restore the latest deployment
|
|
9
|
+
id = app_id_by_name(application_id)
|
|
10
|
+
validate_start_requirements(id, 'start')
|
|
11
|
+
to_nucleus_app(send_event(id, 'start'))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @see Stub#stop
|
|
15
|
+
def stop(application_id)
|
|
16
|
+
id = app_id_by_name(application_id)
|
|
17
|
+
unless deployed?(id)
|
|
18
|
+
fail Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be stopped'
|
|
19
|
+
end
|
|
20
|
+
to_nucleus_app(send_event(id, 'stop'))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @see Stub#restart
|
|
24
|
+
def restart(application_id)
|
|
25
|
+
id = app_id_by_name(application_id)
|
|
26
|
+
validate_start_requirements(id, 'restart')
|
|
27
|
+
to_nucleus_app(send_event(id, 'restart'))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def validate_start_requirements(id, action)
|
|
33
|
+
state = application_state(get("/application/#{id}").body[:data])
|
|
34
|
+
if state == Enums::ApplicationStates::DEPLOYED
|
|
35
|
+
activate(id, latest_deployment(id)[:id])
|
|
36
|
+
elsif state == Enums::ApplicationStates::CREATED
|
|
37
|
+
fail Errors::SemanticAdapterRequestError, "Application must be deployed before it can be #{action}ed"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def deployed?(application_id)
|
|
42
|
+
app = get("/application/#{app_id_by_name(application_id)}").body[:data]
|
|
43
|
+
application_state(app) != Enums::ApplicationStates::CREATED
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Send the event and trigger an action.
|
|
47
|
+
# @return [Hash] Openshift application data
|
|
48
|
+
def send_event(application_id, event, options = {})
|
|
49
|
+
options[:event] = event
|
|
50
|
+
post("/application/#{app_id_by_name(application_id)}/events", body: options).body[:data]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def activate(application_id, deployment_id)
|
|
54
|
+
send_event(application_id, 'activate', deployment_id: deployment_id)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
require 'net/ssh'
|
|
3
|
+
|
|
4
|
+
module Nucleus
|
|
5
|
+
module Adapters
|
|
6
|
+
module V1
|
|
7
|
+
class OpenshiftV2 < Stub
|
|
8
|
+
module Logs
|
|
9
|
+
# @see Stub#logs
|
|
10
|
+
def logs(application_id)
|
|
11
|
+
# fails with 404 if application is not available
|
|
12
|
+
app = get("/application/#{app_id_by_name(application_id)}").body[:data]
|
|
13
|
+
# ssh uri
|
|
14
|
+
uri = ssh_uri(app)
|
|
15
|
+
|
|
16
|
+
with_ssh_key do
|
|
17
|
+
remote_log_files(uri, app[:creation_time])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @see Stub#log?
|
|
22
|
+
def log?(application_id, log_id)
|
|
23
|
+
# fails with 404 if application is not available
|
|
24
|
+
app = get("/application/#{app_id_by_name(application_id)}").body[:data]
|
|
25
|
+
# ssh uri
|
|
26
|
+
uri = ssh_uri(app)
|
|
27
|
+
|
|
28
|
+
with_ssh_key do
|
|
29
|
+
remote_log_file?(uri)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @see Stub#tail
|
|
34
|
+
def tail(application_id, log_id, stream)
|
|
35
|
+
# TODO: implement me
|
|
36
|
+
# remote_cmd = "tail#{options.opts ? ' --opts ' + Base64::encode64(options.opts).chomp : ''} #{file_glob}"
|
|
37
|
+
# ssh_cmd = "ssh -t #{uuid}@#{host} '#{remote_cmd}'"
|
|
38
|
+
fail NOT_IMPLEMENTED_ERROR
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @see Stub#log_entries
|
|
42
|
+
def log_entries(application_id, log_id)
|
|
43
|
+
# fails with 404 if application is not available
|
|
44
|
+
app = get("/application/#{app_id_by_name(application_id)}").body[:data]
|
|
45
|
+
# ssh uri
|
|
46
|
+
uri = ssh_uri(app)
|
|
47
|
+
|
|
48
|
+
with_ssh_key do
|
|
49
|
+
remote_log_entries(uri, application_id, log_id)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def ssh_uri(application)
|
|
56
|
+
URI.parse(application[:ssh_url])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def remote_log_files(uri, app_creation_time)
|
|
60
|
+
available_log_files = []
|
|
61
|
+
# ssh into main instance
|
|
62
|
+
Net::SSH.start(uri.host, uri.user, keys: [nucleus_config.ssh.handler.key_file]) do |ssh|
|
|
63
|
+
# https://developers.openshift.com/en/managing-log-files.html#log-location
|
|
64
|
+
log_files = ssh.exec!('ls $OPENSHIFT_LOG_DIR')
|
|
65
|
+
|
|
66
|
+
log_files.split("\n").each do |file|
|
|
67
|
+
updated_at = ssh.exec!("date -r $OPENSHIFT_LOG_DIR/#{file}")
|
|
68
|
+
updated_at = Time.parse(updated_at).utc.iso8601
|
|
69
|
+
# TODO: no unified naming among cartridges: ApplicationLogfileType::APPLICATION by default.
|
|
70
|
+
available_log_files.push(id: File.basename(file, '.*'), name: file,
|
|
71
|
+
type: Enums::ApplicationLogfileType::APPLICATION,
|
|
72
|
+
created_at: app_creation_time, updated_at: updated_at)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
available_log_files
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def remote_log_file?(uri)
|
|
79
|
+
Net::SSH.start(uri.host, uri.user, keys: [nucleus_config.ssh.handler.key_file]) do |ssh|
|
|
80
|
+
remote_file_exists?(ssh, "#{log_id}.log")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def remote_log_entries(uri, app_id, log_id)
|
|
85
|
+
Net::SSH.start(uri.host, uri.user, keys: [nucleus_config.ssh.handler.key_file]) do |ssh|
|
|
86
|
+
# log exists?
|
|
87
|
+
unless remote_file_exists?(ssh, "#{log_id}.log")
|
|
88
|
+
fail Errors::AdapterResourceNotFoundError,
|
|
89
|
+
"Invalid log file '#{log_id}', not available for application '#{app_id}'"
|
|
90
|
+
end
|
|
91
|
+
# process log
|
|
92
|
+
log = ssh.exec!("cat $OPENSHIFT_LOG_DIR/#{log_id}.log")
|
|
93
|
+
log.split("\n")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def remote_file_exists?(connection, file)
|
|
98
|
+
# file exists? 1 : 0
|
|
99
|
+
exists = connection.exec!("[ ! -f $OPENSHIFT_LOG_DIR/#{file} ]; echo $?").strip
|
|
100
|
+
exists == '1'
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module V1
|
|
4
|
+
# @see https://access.redhat.com/documentation/en-US/OpenShift/2.0/html/REST_API_Guide The Openshift V2
|
|
5
|
+
# API documentation
|
|
6
|
+
class OpenshiftV2 < Stub
|
|
7
|
+
include Nucleus::Logging
|
|
8
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Authentication
|
|
9
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Application
|
|
10
|
+
include Nucleus::Adapters::V1::OpenshiftV2::AppStates
|
|
11
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Data
|
|
12
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Domains
|
|
13
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Lifecycle
|
|
14
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Logs
|
|
15
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Regions
|
|
16
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Scaling
|
|
17
|
+
include Nucleus::Adapters::V1::OpenshiftV2::SemanticErrors
|
|
18
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Services
|
|
19
|
+
include Nucleus::Adapters::V1::OpenshiftV2::Vars
|
|
20
|
+
|
|
21
|
+
def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
|
|
22
|
+
super(endpoint_url, endpoint_app_domain, check_certificates)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def handle_error(error_response)
|
|
26
|
+
# some error messages do not have the proper error message format
|
|
27
|
+
errors = openshift_errors(error_response)
|
|
28
|
+
if error_response.status == 404 && errors.any? { |e| e[:text].include?('not found') }
|
|
29
|
+
fail Errors::AdapterResourceNotFoundError, errors.collect { |e| e[:text] }.join(' ')
|
|
30
|
+
elsif error_response.status == 422
|
|
31
|
+
fail Errors::SemanticAdapterRequestError, errors.collect { |e| e[:text] }.join(' ')
|
|
32
|
+
elsif error_response.status == 503
|
|
33
|
+
fail Errors::PlatformUnavailableError, 'The Openshift API is currently not available'
|
|
34
|
+
elsif error_response.status == 504
|
|
35
|
+
fail Errors::PlatformTimeoutError, 'The Openshift API did not receive information from it\'s slaves. '\
|
|
36
|
+
'Most likely the request is still being executed. Please make sure to analyse whether the request '\
|
|
37
|
+
'was successful before invoking further actions.'
|
|
38
|
+
end
|
|
39
|
+
# error still unhandled, will result in a 500, server error
|
|
40
|
+
log.warn "Openshift error still unhandled: #{error_response}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def openshift_errors(error_response)
|
|
44
|
+
if error_response.body.is_a?(Hash) && error_response.body.key?(:messages)
|
|
45
|
+
error_response.body[:messages].collect { |error| { field: error[:field], text: error[:text] } }
|
|
46
|
+
else
|
|
47
|
+
[]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def headers
|
|
54
|
+
super.merge('Accept' => 'application/json; version=1.7', 'Content-Type' => 'application/json')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def app_domain
|
|
58
|
+
# A user always has only 1 domain as described on:
|
|
59
|
+
# https://access.redhat.com/documentation/en-US/OpenShift/2.0/html/REST_API_Guide/chap-API_Guide-Domains.html
|
|
60
|
+
user_domains = get('/domains').body[:data]
|
|
61
|
+
fail_with(:no_user_domain) if user_domains.empty?
|
|
62
|
+
user_domains.first[:name]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def original_deployment(app, deployments = nil)
|
|
66
|
+
# TODO: this is actually quite scary, could easily fail with wrong timing
|
|
67
|
+
# What are the alternatives?
|
|
68
|
+
# 1) Clone git repo and lookup commits --> insanely slow
|
|
69
|
+
# 2) Identify initial commits by sha1 key --> would require collection of allowed values, which may change!
|
|
70
|
+
deployments = load_deployments(app[:id]) unless deployments
|
|
71
|
+
deployments.find do |deployment|
|
|
72
|
+
diff = (Time.parse(deployment[:created_at]).to_i - Time.parse(app[:creation_time]).to_i).abs
|
|
73
|
+
log.debug "OS deployment time diff: #{diff}"
|
|
74
|
+
diff < 20 && deployment[:force_clean_build] == false &&
|
|
75
|
+
deployment[:hot_deploy] == false && deployment[:ref] == 'master'
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def latest_deployment(application_id, deployments = nil)
|
|
80
|
+
deployments = load_deployments(application_id) unless deployments
|
|
81
|
+
latest = nil
|
|
82
|
+
latest_ts = nil
|
|
83
|
+
deployments.each do |deployment|
|
|
84
|
+
ts = Time.parse(deployment[:created_at]).to_i
|
|
85
|
+
if latest.nil? || ts > latest_ts
|
|
86
|
+
latest = deployment
|
|
87
|
+
latest_ts = ts
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
latest
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def active_deployment(app, deployments = nil)
|
|
94
|
+
deployments = load_deployments(app[:id]) unless deployments
|
|
95
|
+
active = nil
|
|
96
|
+
active_ts = nil
|
|
97
|
+
deployments.each do |deployment|
|
|
98
|
+
ts = Time.parse(last_activation(deployment[:activations])).to_i
|
|
99
|
+
if active.nil? || ts > active_ts
|
|
100
|
+
active = deployment
|
|
101
|
+
active_ts = ts
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
active
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def last_activation(activations)
|
|
108
|
+
latest = nil
|
|
109
|
+
activations.each do |activation|
|
|
110
|
+
latest = activation if latest.nil? || Time.parse(activation).to_i > Time.parse(latest).to_i
|
|
111
|
+
end
|
|
112
|
+
latest
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def load_deployments(application_id)
|
|
116
|
+
get("/application/#{application_id}/deployments").body[:data]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def load_gears(application_id)
|
|
120
|
+
get("/application/#{application_id}/gear_groups").body[:data]
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|