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,26 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
module HttpTailClient
|
|
4
|
+
# Executes a request to the given URL and expects a streaming response.<br>
|
|
5
|
+
# Each new chunk (usually lines) will be forwarded to the client via the api_stream.
|
|
6
|
+
#
|
|
7
|
+
# @param [String] url url to call
|
|
8
|
+
# @param [Nucleus::API::StreamCallback] api_stream stream to which new chunks will be forwarded
|
|
9
|
+
# @param [Symbol] http_method HTTP method to use
|
|
10
|
+
def tail_http_response(url, api_stream, http_method = :get)
|
|
11
|
+
http_connection = EventMachine::HttpRequest.new(url, inactivity_timeout: 0)
|
|
12
|
+
http_client = http_connection.send(http_method, keepalive: true)
|
|
13
|
+
|
|
14
|
+
# close stream on error
|
|
15
|
+
http_client.on_error do
|
|
16
|
+
log.debug('HttpTailClient detected an error, close stream...')
|
|
17
|
+
api_stream.close
|
|
18
|
+
end
|
|
19
|
+
# tail and immediately push the results to the stream
|
|
20
|
+
http_client.stream { |chunk| api_stream.send_message(chunk) }
|
|
21
|
+
# return object that responds to :stop and cancels the tailing request
|
|
22
|
+
TailStopper.new(http_connection, :close)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
module Adapters
|
|
3
|
+
# The TailStopper can be used to cancel a timer or an ongoing HTTP request,
|
|
4
|
+
# e.g. when the underlying connection was terminated.
|
|
5
|
+
class TailStopper
|
|
6
|
+
include Nucleus::Logging
|
|
7
|
+
|
|
8
|
+
def initialize(polling, method_to_stop)
|
|
9
|
+
@polling = polling
|
|
10
|
+
@method_to_stop = method_to_stop
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Stop the tailing
|
|
14
|
+
# @return [void]
|
|
15
|
+
def stop
|
|
16
|
+
log.debug('Stop tail updates, connection was closed')
|
|
17
|
+
begin
|
|
18
|
+
@polling.method(@method_to_stop).call
|
|
19
|
+
rescue
|
|
20
|
+
log.debug('Ignore error while closing connection')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
class StartupError < StandardError
|
|
3
|
+
# exit code to use when exiting the application due to this error
|
|
4
|
+
attr_accessor :exit_code
|
|
5
|
+
|
|
6
|
+
# initialize with default exit code of ExitCodes::STARTUP_ERROR
|
|
7
|
+
def initialize(message, exit_code = ExitCodes::STARTUP_ERROR)
|
|
8
|
+
super(message)
|
|
9
|
+
@exit_code = exit_code
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
# ExitCodes describe why the application failed and was stopped.
|
|
3
|
+
#
|
|
4
|
+
# Codes beginning with ___ stand for ___:
|
|
5
|
+
#
|
|
6
|
+
# 1xx => Startup failed
|
|
7
|
+
#
|
|
8
|
+
module ExitCodes
|
|
9
|
+
##########################
|
|
10
|
+
### Startup Exceptions ###
|
|
11
|
+
##########################
|
|
12
|
+
|
|
13
|
+
# Unidentified startup error
|
|
14
|
+
STARTUP_ERROR = 100
|
|
15
|
+
|
|
16
|
+
# Custom SSH key was specified in the options but could not be loaded
|
|
17
|
+
INVALID_SSH_KEY_FILE = 101
|
|
18
|
+
|
|
19
|
+
# Invalid key, invalid or not of format ssh-rsa OpenSSH
|
|
20
|
+
INVALID_SSH_KEY = 102
|
|
21
|
+
|
|
22
|
+
# Invalid private key, we can only accept private keys without a passphrase
|
|
23
|
+
INVALID_SSH_KEY_FILE_PROTECTED = 103
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
class ApplicationRepoSanitizer
|
|
3
|
+
include Nucleus::Logging
|
|
4
|
+
|
|
5
|
+
# Create a new instance of the object.
|
|
6
|
+
# @param [Boolean] exclude_git if true the '.git' directory won't be moved up, but will be ignored.
|
|
7
|
+
def initialize(exclude_git = true)
|
|
8
|
+
@exclude_git = exclude_git
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Sanitizing the repository_dir will check if the repository has more than one file / directory besides the git DB.
|
|
12
|
+
# If there is only one directory, all files in this directory are going to be moved one level up.
|
|
13
|
+
# If there was:
|
|
14
|
+
#
|
|
15
|
+
# .git
|
|
16
|
+
# wordfinder
|
|
17
|
+
#
|
|
18
|
+
# All contents of `wordfinder` will be moved one level up, resulting in:
|
|
19
|
+
#
|
|
20
|
+
# config
|
|
21
|
+
# lib
|
|
22
|
+
# public
|
|
23
|
+
# specs
|
|
24
|
+
# views
|
|
25
|
+
# README.md
|
|
26
|
+
# server.js
|
|
27
|
+
# ...
|
|
28
|
+
#
|
|
29
|
+
# @param [String] repository_dir path to the git repository that is going to be sanitized
|
|
30
|
+
def sanitize(repository_dir)
|
|
31
|
+
# no sanitizing for files
|
|
32
|
+
return unless File.directory?(repository_dir)
|
|
33
|
+
repo_entries = sanitized_dir_entries(repository_dir)
|
|
34
|
+
return unless repo_entries.length == 1
|
|
35
|
+
|
|
36
|
+
log.debug 'Uploaded application is wrapped in folder, fixing now by moving all contents one level up...'
|
|
37
|
+
dir = File.join(repository_dir, repo_entries[0])
|
|
38
|
+
dir_entries = sanitized_dir_entries(dir).map { |name| File.join(dir, name) }
|
|
39
|
+
FileUtils.mv(dir_entries, repository_dir)
|
|
40
|
+
# Now delete the usually empty directory
|
|
41
|
+
FileUtils.rm_r dir
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def sanitized_dir_entries(dir)
|
|
47
|
+
Dir.entries(dir).reject do |entry|
|
|
48
|
+
entry == '.DS_Store' || (@exclude_git && entry == '.git') || entry == '.' || entry == '..'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
class ArchiveExtractor
|
|
3
|
+
def initialize(exclude_git = true)
|
|
4
|
+
@exclude_git = exclude_git
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Extract the file to the destination path.
|
|
8
|
+
# The compression format indicates which method must be used to extract the archive.
|
|
9
|
+
# @param [IO] file in-memory archive file to extract
|
|
10
|
+
# @param [String] destination_path where the archive is going to be extracted to
|
|
11
|
+
# @param [String] compression_format represented by well-known file extensions, e.g. zip or tar.gz
|
|
12
|
+
# @raise [StandardError] if the compression_format is not supported and can't be extracted
|
|
13
|
+
# @return [Integer] number of extracted files
|
|
14
|
+
def extract(file, destination_path, compression_format)
|
|
15
|
+
compression_method = compression_format_method_name(compression_format)
|
|
16
|
+
fail StandardError, 'Unsupported compression format' unless respond_to?(compression_method, true)
|
|
17
|
+
|
|
18
|
+
# be sure that directory exists
|
|
19
|
+
FileUtils.mkdir_p(destination_path, verbose: false)
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
send(compression_method, file, destination_path)
|
|
23
|
+
rescue Zip::Error, Zlib::GzipFile::Error
|
|
24
|
+
raise API::Errors::ApplicationArchiveError, "Failed to extract #{compression_format} archive"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Checks if the compression format is supported and an archive of this type could be extracted.
|
|
29
|
+
# @param [String] compression_format represented by well-known file extensions, e.g. zip or tar.gz
|
|
30
|
+
# @return [Boolean] true if format is supported, false if not
|
|
31
|
+
def supports?(compression_format)
|
|
32
|
+
compression_method = compression_format_method_name(compression_format)
|
|
33
|
+
respond_to?(compression_method, true)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def compression_format_method_name(compression_format)
|
|
39
|
+
"un_#{compression_format.downcase.gsub(/\./, '_').underscore}".to_sym
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def un_zip(file, destination_path)
|
|
43
|
+
extracted = 0
|
|
44
|
+
Zip::File.open(file) do |zip_file|
|
|
45
|
+
# Handle entries one by one
|
|
46
|
+
zip_file.each do |entry|
|
|
47
|
+
next if @exclude_git && entry.name.start_with?('.git')
|
|
48
|
+
dest = File.join(destination_path, entry.name)
|
|
49
|
+
if entry.name_is_directory?
|
|
50
|
+
FileUtils.mkdir_p(dest) unless File.exist?(dest)
|
|
51
|
+
else
|
|
52
|
+
# make sure parent directory exists
|
|
53
|
+
FileUtils.mkdir_p(File.expand_path('..', dest))
|
|
54
|
+
|
|
55
|
+
entry.extract(dest) unless File.exist?(dest)
|
|
56
|
+
# increase count
|
|
57
|
+
extracted += 1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
extracted
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def un_tar_gz(file, destination_path)
|
|
65
|
+
extracted = 0
|
|
66
|
+
# unzip the archive into the repo, closes resource automatically
|
|
67
|
+
# Thanks to Draco Ater: http://stackoverflow.com/a/19139114/1009436
|
|
68
|
+
Gem::Package::TarReader.new(Zlib::GzipReader.open(file)) do |tar|
|
|
69
|
+
dest = nil
|
|
70
|
+
tar.each do |entry|
|
|
71
|
+
# Process Longlinks and skip to next entry
|
|
72
|
+
if entry.full_name == '././@LongLink'
|
|
73
|
+
dest = File.join(destination_path, entry.read.strip)
|
|
74
|
+
next
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Process default entry types (dir, file, symlink)
|
|
78
|
+
full_name = entry.full_name.sub(%r{(\.\/)?}, '')
|
|
79
|
+
dest ||= File.join(destination_path, full_name)
|
|
80
|
+
next if tar_git_entry? full_name
|
|
81
|
+
if entry.directory?
|
|
82
|
+
write_tar_dir_entry(entry, dest)
|
|
83
|
+
elsif entry.file?
|
|
84
|
+
write_tar_file_entry(entry, dest)
|
|
85
|
+
# increase count
|
|
86
|
+
extracted += 1
|
|
87
|
+
elsif entry.header.typeflag == '2'
|
|
88
|
+
# handle symlinks
|
|
89
|
+
File.symlink(entry.header.linkname, dest)
|
|
90
|
+
end
|
|
91
|
+
dest = nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
alias_method :un_tgz, :un_tar_gz
|
|
96
|
+
|
|
97
|
+
def tar_git_entry?(full_name)
|
|
98
|
+
@exclude_git && (full_name.start_with?('._.git') || full_name.start_with?('.git'))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def write_tar_file_entry(entry, dest)
|
|
102
|
+
FileUtils.rm_rf(dest) if File.directory?(dest)
|
|
103
|
+
File.open(dest, 'wb') { |f| f.print entry.read }
|
|
104
|
+
FileUtils.chmod(entry.header.mode, dest, verbose: false)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def write_tar_dir_entry(entry, dest)
|
|
108
|
+
File.delete(dest) if File.file?(dest)
|
|
109
|
+
FileUtils.mkdir_p(dest, mode: entry.header.mode, verbose: false)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
class Archiver
|
|
3
|
+
def initialize(exclude_git = true)
|
|
4
|
+
@exclude_git = exclude_git
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Compress the files of the path into an archive, using the compression format,
|
|
8
|
+
# which indicates which method must be used to compress the archive.
|
|
9
|
+
# @param [String] path which directory's contents are going to be compressed into the archive
|
|
10
|
+
# @param [String] compression_format represented by well-known file extensions, e.g. zip or tar.gz
|
|
11
|
+
# @raise [StandardError] if the compression_format is not supported and the directory can't be compressed
|
|
12
|
+
# @return [StringIO] compressed data of the given input path
|
|
13
|
+
def compress(path, compression_format)
|
|
14
|
+
compression_method = compression_format.downcase.gsub(/\./, '_').underscore.to_sym
|
|
15
|
+
fail StandardError,
|
|
16
|
+
"Unsupported compression format #{compression_format}" unless self.respond_to?(compression_method, true)
|
|
17
|
+
send(compression_method, path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def tar(path)
|
|
23
|
+
string_io = StringIO.new('')
|
|
24
|
+
Gem::Package::TarWriter.new(string_io) do |tar|
|
|
25
|
+
Find.find(path) do |file|
|
|
26
|
+
# do not include the git files
|
|
27
|
+
next if @exclude_git && file.start_with?("#{path}/.git")
|
|
28
|
+
|
|
29
|
+
mode = File.stat(file).mode
|
|
30
|
+
relative_file = file.sub(%r{^#{Regexp.escape path}\/?}, '')
|
|
31
|
+
|
|
32
|
+
if File.directory?(file)
|
|
33
|
+
tar.mkdir relative_file, mode
|
|
34
|
+
else
|
|
35
|
+
tar.add_file relative_file, mode do |tf|
|
|
36
|
+
File.open(file, 'rb') { |f| tf.write f.read }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
string_io.rewind
|
|
42
|
+
string_io
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def tar_gz(path)
|
|
46
|
+
tar_file = tar(path)
|
|
47
|
+
begin
|
|
48
|
+
gz = StringIO.new('')
|
|
49
|
+
z = Zlib::GzipWriter.new(gz)
|
|
50
|
+
z.write tar_file.string
|
|
51
|
+
ensure
|
|
52
|
+
z.close unless z.nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# z was closed to write the gzip footer, so
|
|
56
|
+
# now we need a new StringIO
|
|
57
|
+
StringIO.new gz.string
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def zip(path)
|
|
61
|
+
string_io = Zip::OutputStream.write_buffer do |zio|
|
|
62
|
+
Find.find(path) do |file|
|
|
63
|
+
# do not process directories && do not include the Git DB files
|
|
64
|
+
next if File.directory?(file) || (@exclude_git && file.start_with?("#{path}/.git"))
|
|
65
|
+
|
|
66
|
+
relative_file = file.sub(%r{^#{Regexp.escape path}\/?}, '')
|
|
67
|
+
zio.put_next_entry(relative_file)
|
|
68
|
+
File.open(file, 'rb') do |f|
|
|
69
|
+
zio.write f.read
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
string_io.rewind
|
|
74
|
+
string_io
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def write_zip_entries(path, sub_path, io)
|
|
78
|
+
search_path = File.join(path, sub_path)
|
|
79
|
+
Find.find(path) do |file|
|
|
80
|
+
zip_file_path = file.sub(%r{^#{Regexp.escape search_path}\/?}, '')
|
|
81
|
+
next if @exclude_git && zip_file_path.start_with?('.git')
|
|
82
|
+
if File.directory?(file)
|
|
83
|
+
io.mkdir(zip_file_path)
|
|
84
|
+
write_zip_entries(path, zip_file_path, io)
|
|
85
|
+
else
|
|
86
|
+
io.get_output_stream(zip_file_path) { |f| f.print(File.open(file, 'rb').read) }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
class LinkGenerator
|
|
3
|
+
def initialize(env, api_version)
|
|
4
|
+
@env = env
|
|
5
|
+
@version = api_version
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Generate the link that references the resource.
|
|
9
|
+
# @param [Array<String>] namespaces nested namespaces that must be joined to access the resource
|
|
10
|
+
# @param [String] id id of the resource
|
|
11
|
+
# @return [String] URL to the resource
|
|
12
|
+
def resource(namespaces, id)
|
|
13
|
+
# resource can only exist for an API version
|
|
14
|
+
link = api_root
|
|
15
|
+
# combine namespace and entity ID
|
|
16
|
+
link << namespace(namespaces)
|
|
17
|
+
link << "/#{id}" unless id.nil? || id.empty?
|
|
18
|
+
# return the created link
|
|
19
|
+
link
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Create a link to the API root node.
|
|
23
|
+
#
|
|
24
|
+
# @return [String] link to the API root
|
|
25
|
+
def api_root
|
|
26
|
+
root_url << '/api'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get the root URL of the Nucleus API (scheme + host)
|
|
30
|
+
def root_url
|
|
31
|
+
"#{@env['rack.url_scheme']}://#{@env['HTTP_HOST']}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def namespace(namespaces)
|
|
37
|
+
if namespaces.is_a?(String) && !namespaces.empty?
|
|
38
|
+
"/#{namespaces}"
|
|
39
|
+
elsif !namespaces.nil? && !namespaces.empty?
|
|
40
|
+
"/#{namespaces.join('/')}"
|
|
41
|
+
else
|
|
42
|
+
''
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Nucleus
|
|
2
|
+
# Logging module for Nucleus.
|
|
3
|
+
# Include via
|
|
4
|
+
# include Nucleus::Logging
|
|
5
|
+
# and then log your messages:
|
|
6
|
+
# log.info('This is a test log message')
|
|
7
|
+
#
|
|
8
|
+
# @author Willem Buys
|
|
9
|
+
# Idea by Willem 'Jacob' Buys, as seen on http://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes
|
|
10
|
+
module Logging
|
|
11
|
+
def log
|
|
12
|
+
@log ||= Logging.logger_for(self.class.name)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Use a hash class-ivar to cache a unique Logger per class:
|
|
16
|
+
@loggers = {}
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def logger_for(classname)
|
|
20
|
+
@loggers[classname] ||= configure_logger_for(classname)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def configure_logger_for(classname)
|
|
24
|
+
# prepare logging dir
|
|
25
|
+
log_dir = nucleus_config.logging.path
|
|
26
|
+
log_file = File.join(log_dir, 'nucleus.log')
|
|
27
|
+
# prepare path and create missing directories
|
|
28
|
+
FileUtils.mkdir_p(log_dir) unless File.directory?(log_dir)
|
|
29
|
+
# create the loggers
|
|
30
|
+
std_log = Logger.new(STDOUT)
|
|
31
|
+
# use rotation for x days
|
|
32
|
+
file_log = Logger.new(log_file, 'daily', 7)
|
|
33
|
+
|
|
34
|
+
# include custom log format that includes the request id
|
|
35
|
+
formatter = Nucleus::Logging::Formatter.new
|
|
36
|
+
|
|
37
|
+
[file_log, std_log].each do |logger|
|
|
38
|
+
# apply format
|
|
39
|
+
logger.formatter = formatter
|
|
40
|
+
# apply the classname
|
|
41
|
+
logger.progname = classname
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# apply the log level from the app. configuration
|
|
45
|
+
multi_logger = MultiLogger.new(
|
|
46
|
+
level: nucleus_config.logging.key?(:level) ? nucleus_config.logging.level : Logger::Severity::WARN,
|
|
47
|
+
loggers: [std_log, file_log])
|
|
48
|
+
multi_logger
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|