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,13 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Errors
|
3
|
+
class AdapterError < StandardError
|
4
|
+
# error constant to use when exiting the application due to this error
|
5
|
+
attr_accessor :ui_error
|
6
|
+
|
7
|
+
def initialize(message, ui_error)
|
8
|
+
super(message)
|
9
|
+
@ui_error = ui_error
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Errors
|
3
|
+
# The {AdapterMissingImplementationError} shall be thrown when the API requests an adapter to execute an action,
|
4
|
+
# e.g. update an application, but the adapter does not (yet) support this functionality.
|
5
|
+
class AdapterMissingImplementationError < AdapterError
|
6
|
+
# initialize with default error to be 501
|
7
|
+
def initialize(message, ui_error = ErrorMessages::MISSING_IMPLEMENTATION)
|
8
|
+
super(message, ui_error)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Errors
|
3
|
+
class AdapterResourceNotFoundError < AdapterError
|
4
|
+
# initialize with default error to be 404, endpoint resource not found
|
5
|
+
def initialize(message, ui_error = ErrorMessages::ENDPOINT_NOT_FOUND)
|
6
|
+
super(message, ui_error)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Errors
|
3
|
+
class EndpointAuthenticationError < AdapterError
|
4
|
+
# initialize with default error to be 401, authentication failed
|
5
|
+
def initialize(message, ui_error = ErrorMessages::AUTH_UNAUTHORIZED)
|
6
|
+
super(message, ui_error)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Errors
|
3
|
+
class PlatformSpecificSemanticError < AdapterError
|
4
|
+
# initialize with default error to be 422
|
5
|
+
def initialize(message, error_code = nil, ui_error = ErrorMessages::PLATFORM_SPECIFIC_ERROR_ENTITY)
|
6
|
+
# allow to customize the error code
|
7
|
+
ui_error[:error_code] = error_code unless error_code.nil?
|
8
|
+
super(message, ui_error)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Errors
|
3
|
+
# The {SemanticAdapterRequestError} shall be thrown if the user request could not be executed due to logical errors.
|
4
|
+
# <br>
|
5
|
+
# Examples for semantic errors are:
|
6
|
+
# - name already used
|
7
|
+
# - quota violations
|
8
|
+
# <br>
|
9
|
+
# These errors are clearly to be distinguished from malformed requests.
|
10
|
+
class SemanticAdapterRequestError < AdapterError
|
11
|
+
# initialize with default error to be 422
|
12
|
+
def initialize(message, error_code = nil, ui_error = ErrorMessages::BAD_REQUEST_ENTITY)
|
13
|
+
# allow to customize the error code
|
14
|
+
ui_error[:error_code] = error_code unless error_code.nil?
|
15
|
+
super(message, ui_error)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
# The {ArchiveConverter} shall be used within the adapters to prepare application containers for deployment
|
4
|
+
# on the endpoint, by converting archives, e.g. from +tar.gz+ to +zip+, to match the endpoint APIs requirements.
|
5
|
+
module ArchiveConverter
|
6
|
+
extend Nucleus::Logging
|
7
|
+
|
8
|
+
# Convert an archived application, the +file+, from the +current_format+ to the +destination_format+.
|
9
|
+
# @param [IO] file archive file that shall be converted
|
10
|
+
# @param [String] current_format represented by well-known file extensions, e.g. zip or tar.gz
|
11
|
+
# @param [String] destination_format represented by well-known file extensions, e.g. zip or tar.gz
|
12
|
+
# @param [Boolean] sanitize if true, the application will be sanitized, meaning if all data is in one folder
|
13
|
+
# it will be moved one level up so that the application data is not nested in another directory
|
14
|
+
# @return [StringIO] the data of the input file in a new archive matching the destination format
|
15
|
+
def self.convert(file, current_format, destination_format, sanitize = false)
|
16
|
+
extraction_dir = "#{Dir.tmpdir}/nucleus.app.convert.cf.deploy.#{SecureRandom.uuid}"
|
17
|
+
ArchiveExtractor.new.extract(file, extraction_dir, current_format)
|
18
|
+
|
19
|
+
# sanitize if desired
|
20
|
+
ApplicationRepoSanitizer.new.sanitize(extraction_dir) if sanitize
|
21
|
+
|
22
|
+
return Archiver.new.compress(extraction_dir, destination_format)
|
23
|
+
ensure
|
24
|
+
# now delete the tmp directory again
|
25
|
+
FileUtils.rm_rf(extraction_dir) if extraction_dir
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module FileManager
|
4
|
+
extend Nucleus::Logging
|
5
|
+
|
6
|
+
# Load the contents of the file.
|
7
|
+
# @param [String] file absolute path of the file to read
|
8
|
+
# @raise [Nucleus::FileExistenceError] if the file does not exist
|
9
|
+
# @return [StringIO] binary contents of the file, rewinded
|
10
|
+
def self.load_file(file)
|
11
|
+
io = StringIO.new('')
|
12
|
+
File.open(file, 'r') do |opened_file|
|
13
|
+
opened_file.binmode
|
14
|
+
io.write opened_file.read
|
15
|
+
end
|
16
|
+
io.rewind
|
17
|
+
io
|
18
|
+
end
|
19
|
+
|
20
|
+
# Save the data from within the {::Data} object to the file.
|
21
|
+
# By default, this replaces already existing files.
|
22
|
+
# If force is set to false, the method call will fail if there already is a file at the destination.
|
23
|
+
# If force is false, but expected_file_md5_hex is specified, the file will be replaced as long as
|
24
|
+
# the hexdigest of the current file is equal to the expected_file_md5_hex param.
|
25
|
+
#
|
26
|
+
# @param [String] file absolute path of the file to write to
|
27
|
+
# @param [Data] io data to write to the file
|
28
|
+
# @param [Boolean] force if true file is replaced, else write fails
|
29
|
+
# @param [String] expected_file_md5_hex MD5 hexdigest of the expected file to be replaced.
|
30
|
+
# If nil, file is not replaced as long as force == false
|
31
|
+
# @raise [Nucleus::FileExistenceError] if the file already existed
|
32
|
+
# @raise [ArgumentError] if expected_file_md5_hex did not match the MD5 hexdigest of the current file
|
33
|
+
# in the repository
|
34
|
+
# @return [void]
|
35
|
+
def self.save_file_from_data(file, io, force = true, expected_file_md5_hex = nil)
|
36
|
+
if File.exist? file
|
37
|
+
unless force
|
38
|
+
# fail if file exists, but shall not be replaced
|
39
|
+
fail Nucleus::FileExistenceError, 'File already exists' if expected_file_md5_hex.nil?
|
40
|
+
|
41
|
+
# do only replace if file is as expected
|
42
|
+
actual_hex = Digest::MD5.file(file).hexdigest
|
43
|
+
unless actual_hex == expected_file_md5_hex
|
44
|
+
fail ArgumentError, "File to replace does exist, but hash sum is different than expected: #{actual_hex}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# rewind IO
|
50
|
+
io.rewind if io.respond_to? :rewind
|
51
|
+
|
52
|
+
# create parent directory
|
53
|
+
dirname = File.dirname(file)
|
54
|
+
FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
|
55
|
+
|
56
|
+
# write file and replace existing
|
57
|
+
File.open(file, 'w') do |opened_file|
|
58
|
+
opened_file.binmode
|
59
|
+
opened_file.write io.read
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
class GitDeployer
|
4
|
+
include Nucleus::Logging
|
5
|
+
|
6
|
+
# Initialize a new instance of the GitDeployer
|
7
|
+
# @param [String] user_email email address of the user, used as author of commits
|
8
|
+
# @param [String] repo_url address where the repository can be retrieved
|
9
|
+
# @param [String] repo_name name of the directory for the repository that shall be created in the tmp dir
|
10
|
+
# @param [String] repo_branch branch to push to
|
11
|
+
def initialize(repo_name, repo_url, user_email, repo_branch = 'master')
|
12
|
+
@repo_name = repo_name
|
13
|
+
@repo_url = repo_url
|
14
|
+
@repo_branch = repo_branch
|
15
|
+
@user_email = user_email
|
16
|
+
end
|
17
|
+
|
18
|
+
# Force a build using the latest git commit.
|
19
|
+
# To enforce the new build, a file 'nucleus-rebuild-trigger'
|
20
|
+
# gets created or updated in the repository and the changes will be pushed.
|
21
|
+
# @return [void]
|
22
|
+
def trigger_build
|
23
|
+
push_repository_changes do |repo_dir|
|
24
|
+
# add a custom file that always changes the data and triggers a new build
|
25
|
+
build_trigger_file = File.join(repo_dir, 'nucleus-rebuild-trigger')
|
26
|
+
current_md5 = File.exist?(build_trigger_file) ? Digest::MD5.file(build_trigger_file).hexdigest : nil
|
27
|
+
data = StringIO.new("Nucleus rebuild, triggered at #{Time.now}")
|
28
|
+
FileManager.save_file_from_data(build_trigger_file, data, false, current_md5)
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# Deploys the contents of the archive file to the repository that resides at the repo_url.
|
34
|
+
#
|
35
|
+
# @param [File] file archive file whose contents shall be deployed to the repository
|
36
|
+
# @param [String] file_compression_format compression format of the application archive, e.g. 'zip' or 'tar.gz'
|
37
|
+
# @return [void]
|
38
|
+
def deploy(file, file_compression_format)
|
39
|
+
extractor = Nucleus::ArchiveExtractor.new
|
40
|
+
fail Errors::AdapterRequestError,
|
41
|
+
'Unsupported format of the application archive' unless extractor.supports? file_compression_format
|
42
|
+
|
43
|
+
push_repository_changes do |repo_dir|
|
44
|
+
# now remove all current files, except the git db
|
45
|
+
Find.find(repo_dir) do |f|
|
46
|
+
next if f.start_with?("#{repo_dir}/.git") || f == repo_dir
|
47
|
+
FileUtils.rm_rf(f) if File.directory?(f)
|
48
|
+
FileUtils.rm_f(f) if File.file?(f)
|
49
|
+
end
|
50
|
+
|
51
|
+
# uncompress and extract to
|
52
|
+
extracted = extractor.extract(file, repo_dir, file_compression_format)
|
53
|
+
fail Errors::AdapterRequestError, 'Invalid application: Archive did not contain any files' if extracted == 0
|
54
|
+
|
55
|
+
# if the application was wrapped within a directory, move all 1st level files and dirs to the root
|
56
|
+
sanitizer = Nucleus::ApplicationRepoSanitizer.new
|
57
|
+
sanitizer.sanitize(repo_dir)
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Download the contents of a git repository in the requested format.
|
63
|
+
# @param [String] format compression to be used for the download e.g. 'zip' or 'tar.gz'
|
64
|
+
# @return [StringIO] data requested to be downloaded
|
65
|
+
def download(format, exclude_git = true)
|
66
|
+
with_repository do |repo_dir|
|
67
|
+
# TODO: maybe we can do this directly via SSH and prevent the disk writes?
|
68
|
+
# download files temporarily from the repo
|
69
|
+
Nucleus::Archiver.new(exclude_git).compress(repo_dir, format)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def with_repository
|
76
|
+
tmp_dir = Dir.tmpdir
|
77
|
+
repo_dir = "#{tmp_dir}/#{@repo_name}"
|
78
|
+
begin
|
79
|
+
repository = Git.clone(@repo_url, @repo_name, path: tmp_dir)
|
80
|
+
# checkout custom branch
|
81
|
+
unless @repo_branch == 'master'
|
82
|
+
begin
|
83
|
+
repository.checkout(repository.branch(@repo_branch))
|
84
|
+
rescue StandardError
|
85
|
+
# catch errors, might occur if no commit has been made and we try to switch the branch
|
86
|
+
repository.checkout(@repo_branch, new_branch: true)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# now execute the actual actions on the repository
|
91
|
+
yield repo_dir, repository
|
92
|
+
ensure
|
93
|
+
# now delete the tmp directory again
|
94
|
+
FileUtils.rm_rf(repo_dir)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def push_repository_changes
|
99
|
+
with_repository do |repo_dir, repository|
|
100
|
+
repository.config('user.name', 'Nucleus')
|
101
|
+
repository.config('user.email', @user_email)
|
102
|
+
# update files
|
103
|
+
yield repo_dir
|
104
|
+
# push changes
|
105
|
+
push(repository)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Push all contents of the repository to the default remote 'origin'.
|
110
|
+
# The repository will also be pushed if none of the files did change and no new commit was made.
|
111
|
+
# @param [Git::Lib] repository repository whose contents are to be pushed
|
112
|
+
# @return [void]
|
113
|
+
def push(repository)
|
114
|
+
# add all files to the repository
|
115
|
+
repository.add(all: true)
|
116
|
+
|
117
|
+
# commit, but be aware: current version could be identical to previous version resulting in an error
|
118
|
+
begin
|
119
|
+
repository.commit('Application deployment via Nucleus')
|
120
|
+
rescue Git::GitExecuteError => e
|
121
|
+
# usually indicates that no files could be committed, repository is up to date
|
122
|
+
log.debug("Git commit failed: #{e}")
|
123
|
+
end
|
124
|
+
|
125
|
+
# repack to enhance compression
|
126
|
+
repository.repack
|
127
|
+
|
128
|
+
# force push, so that the push is executed even when all files remain unchanged
|
129
|
+
repository.push('origin', @repo_branch, force: true)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module GitRepoAnalyzer
|
4
|
+
# Is the repository having any branch?
|
5
|
+
# @param [String] repo_host repository host where the repository can be retrieved
|
6
|
+
# @param [String] repo_name name of the directory for the repository that shall be created in the tmp dir
|
7
|
+
# @param [String] username user to authenticate with
|
8
|
+
# @return [TrueClass, FalseClass] true if the repository has any non-empty branch, e.g. 'master'
|
9
|
+
def self.any_branch?(repo_host, repo_name, username)
|
10
|
+
detected_branch = false
|
11
|
+
options = { forward_agent: true, auth_methods: ['publickey'],
|
12
|
+
keys: [nucleus_config.ssh.handler.key_file], keys_only: true }
|
13
|
+
Net::SSH.start(repo_host, username, options) do |ssh|
|
14
|
+
ssh.exec! "git-upload-pack '/#{repo_name}.git'" do |ch, stream, data|
|
15
|
+
detected_branch = (detected_branch || data != '0000') unless stream == :stderr
|
16
|
+
ch.close
|
17
|
+
end
|
18
|
+
end
|
19
|
+
detected_branch
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
extend Nucleus::Logging
|
4
|
+
|
5
|
+
# Get all adapter configuration files that are included in the application.
|
6
|
+
# The config files must be located at the +config/adapters+ directory.
|
7
|
+
#
|
8
|
+
# @return [Array<File>] all adapter configuration files
|
9
|
+
def self.configuration_files
|
10
|
+
return @configuration_files if @configuration_files
|
11
|
+
adapter_dir = "#{Nucleus.root}/config/adapters"
|
12
|
+
files = Dir[File.join(adapter_dir, '*.yml')] | Dir[File.join(adapter_dir, '*.yaml')]
|
13
|
+
files = files.flatten.compact
|
14
|
+
files.collect! { |file| File.expand_path(file) }
|
15
|
+
log.debug "... found #{files.size} adapter config file(s)"
|
16
|
+
@configuration_files = files
|
17
|
+
@configuration_files
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the clazz to the adapter file that matches the adapter_config and api_version.
|
21
|
+
#
|
22
|
+
# @param [String] adapter_config adapter configuration that indicates the adapter's name
|
23
|
+
# @param [String] api_version API version to load the adapter for
|
24
|
+
# @return [String] clazz name of the adapter
|
25
|
+
def self.adapter_clazz(adapter_config, api_version)
|
26
|
+
adapter_file = adapter_file(adapter_config, api_version)
|
27
|
+
return if adapter_file.nil?
|
28
|
+
# transform path to clazz and load an instance
|
29
|
+
adapter_clazz = "Nucleus::Adapters::#{api_version.upcase}::#{File.basename(adapter_file, '.rb').capitalize}"
|
30
|
+
adapter_clazz.camelize.split('::').inject(Object) { |a, e| a.const_get e }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the path to the adapter's class file by translation from the adapter configuration's name.
|
34
|
+
# If the adapter configuration is called 'abc-vendor.yml', then the adapter's source file must
|
35
|
+
# be found below +app/adapters/#{api_version}/+ with the name +abc-vendor_adapter.rb+.
|
36
|
+
#
|
37
|
+
# @param [String] adapter_config adapter configuration that indicates the adapter's name
|
38
|
+
# @param [String] api_version API version to load the adapter for
|
39
|
+
# @raise [Nucleus::AmbiguousAdapterError] if more than one adapter was found for an adapter configuration
|
40
|
+
# @return [String] path to the adapter's class file
|
41
|
+
def self.adapter_file(adapter_config, api_version)
|
42
|
+
log.debug "... trying to resolve adapter for config #{adapter_config} and API #{api_version}..."
|
43
|
+
adapter_name = File.basename(adapter_config).sub(/.[^.]+\z/, '.rb')
|
44
|
+
file_search_path = "#{Nucleus.root}/lib/nucleus/adapters/#{api_version}/*/#{adapter_name}"
|
45
|
+
adapter_file = Dir.glob(file_search_path)
|
46
|
+
fail AmbiguousAdapterError, "More than 1 adapter file found for #{adapter_name}" unless adapter_file.size <= 1
|
47
|
+
|
48
|
+
return if adapter_file.empty?
|
49
|
+
log.debug "... found '#{adapter_file.first}'"
|
50
|
+
adapter_file.first
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Nucleus
|
2
|
+
module VendorParser
|
3
|
+
extend Nucleus::Logging
|
4
|
+
|
5
|
+
# Get a parsed vendor instance from the adapter_config file
|
6
|
+
#
|
7
|
+
# @param [File, String, Path] adapter_config path to the adapter configuration file to be parsed
|
8
|
+
# @return [Nucleus::Vendor] the parsed Vendor instance
|
9
|
+
def self.parse(adapter_config)
|
10
|
+
schema_file = "#{Nucleus.root}/schemas/api.adapter.schema.yml"
|
11
|
+
schema = Kwalify::Yaml.load_file(schema_file, untabify: true, preceding_alias: true)
|
12
|
+
validator = Kwalify::Validator.new(schema)
|
13
|
+
config_parser = Kwalify::Yaml::Parser.new(validator, data_binding: true, preceding_alias: true)
|
14
|
+
|
15
|
+
vendor = config_parser.parse_file(adapter_config)
|
16
|
+
errors = config_parser.errors
|
17
|
+
# show errors
|
18
|
+
if errors && !errors.empty?
|
19
|
+
errors.each do |e|
|
20
|
+
log.error "[#{e.path}] #{e.message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# vendor is not valid and shall not be returned
|
24
|
+
return nil if errors && !errors.empty?
|
25
|
+
vendor
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|