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,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
|