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.
Files changed (224) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +1 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +44 -0
  6. data/.travis.yml +21 -0
  7. data/CHANGELOG.md +19 -0
  8. data/CONTRIBUTING.md +13 -0
  9. data/Gemfile +16 -0
  10. data/Guardfile +22 -0
  11. data/LICENSE +21 -0
  12. data/README.md +675 -0
  13. data/Rakefile +137 -0
  14. data/bin/nucleus +91 -0
  15. data/bin/nucleus.bat +1 -0
  16. data/config.ru +18 -0
  17. data/config/adapters/cloud_control.yml +32 -0
  18. data/config/adapters/cloud_foundry_v2.yml +61 -0
  19. data/config/adapters/heroku.yml +13 -0
  20. data/config/adapters/openshift_v2.yml +20 -0
  21. data/config/nucleus_config.rb +47 -0
  22. data/lib/nucleus.rb +13 -0
  23. data/lib/nucleus/adapter_resolver.rb +115 -0
  24. data/lib/nucleus/adapters/base_adapter.rb +109 -0
  25. data/lib/nucleus/adapters/buildpack_translator.rb +79 -0
  26. data/lib/nucleus/adapters/v1/cloud_control/application.rb +108 -0
  27. data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +27 -0
  28. data/lib/nucleus/adapters/v1/cloud_control/buildpacks.rb +23 -0
  29. data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +153 -0
  30. data/lib/nucleus/adapters/v1/cloud_control/data.rb +76 -0
  31. data/lib/nucleus/adapters/v1/cloud_control/domains.rb +68 -0
  32. data/lib/nucleus/adapters/v1/cloud_control/lifecycle.rb +27 -0
  33. data/lib/nucleus/adapters/v1/cloud_control/log_poller.rb +71 -0
  34. data/lib/nucleus/adapters/v1/cloud_control/logs.rb +103 -0
  35. data/lib/nucleus/adapters/v1/cloud_control/regions.rb +32 -0
  36. data/lib/nucleus/adapters/v1/cloud_control/scaling.rb +17 -0
  37. data/lib/nucleus/adapters/v1/cloud_control/semantic_errors.rb +31 -0
  38. data/lib/nucleus/adapters/v1/cloud_control/services.rb +162 -0
  39. data/lib/nucleus/adapters/v1/cloud_control/token.rb +17 -0
  40. data/lib/nucleus/adapters/v1/cloud_control/vars.rb +88 -0
  41. data/lib/nucleus/adapters/v1/cloud_foundry_v2/app_states.rb +28 -0
  42. data/lib/nucleus/adapters/v1/cloud_foundry_v2/application.rb +111 -0
  43. data/lib/nucleus/adapters/v1/cloud_foundry_v2/authentication.rb +17 -0
  44. data/lib/nucleus/adapters/v1/cloud_foundry_v2/buildpacks.rb +23 -0
  45. data/lib/nucleus/adapters/v1/cloud_foundry_v2/cloud_foundry_v2.rb +141 -0
  46. data/lib/nucleus/adapters/v1/cloud_foundry_v2/data.rb +97 -0
  47. data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +149 -0
  48. data/lib/nucleus/adapters/v1/cloud_foundry_v2/lifecycle.rb +41 -0
  49. data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +303 -0
  50. data/lib/nucleus/adapters/v1/cloud_foundry_v2/regions.rb +33 -0
  51. data/lib/nucleus/adapters/v1/cloud_foundry_v2/scaling.rb +15 -0
  52. data/lib/nucleus/adapters/v1/cloud_foundry_v2/semantic_errors.rb +27 -0
  53. data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +286 -0
  54. data/lib/nucleus/adapters/v1/cloud_foundry_v2/vars.rb +80 -0
  55. data/lib/nucleus/adapters/v1/heroku/app_states.rb +57 -0
  56. data/lib/nucleus/adapters/v1/heroku/application.rb +93 -0
  57. data/lib/nucleus/adapters/v1/heroku/authentication.rb +27 -0
  58. data/lib/nucleus/adapters/v1/heroku/buildpacks.rb +27 -0
  59. data/lib/nucleus/adapters/v1/heroku/data.rb +78 -0
  60. data/lib/nucleus/adapters/v1/heroku/domains.rb +43 -0
  61. data/lib/nucleus/adapters/v1/heroku/heroku.rb +146 -0
  62. data/lib/nucleus/adapters/v1/heroku/lifecycle.rb +51 -0
  63. data/lib/nucleus/adapters/v1/heroku/logs.rb +108 -0
  64. data/lib/nucleus/adapters/v1/heroku/regions.rb +42 -0
  65. data/lib/nucleus/adapters/v1/heroku/scaling.rb +28 -0
  66. data/lib/nucleus/adapters/v1/heroku/semantic_errors.rb +23 -0
  67. data/lib/nucleus/adapters/v1/heroku/services.rb +168 -0
  68. data/lib/nucleus/adapters/v1/heroku/vars.rb +65 -0
  69. data/lib/nucleus/adapters/v1/openshift_v2/app_states.rb +68 -0
  70. data/lib/nucleus/adapters/v1/openshift_v2/application.rb +108 -0
  71. data/lib/nucleus/adapters/v1/openshift_v2/authentication.rb +21 -0
  72. data/lib/nucleus/adapters/v1/openshift_v2/data.rb +96 -0
  73. data/lib/nucleus/adapters/v1/openshift_v2/domains.rb +37 -0
  74. data/lib/nucleus/adapters/v1/openshift_v2/lifecycle.rb +60 -0
  75. data/lib/nucleus/adapters/v1/openshift_v2/logs.rb +106 -0
  76. data/lib/nucleus/adapters/v1/openshift_v2/openshift_v2.rb +125 -0
  77. data/lib/nucleus/adapters/v1/openshift_v2/regions.rb +58 -0
  78. data/lib/nucleus/adapters/v1/openshift_v2/scaling.rb +39 -0
  79. data/lib/nucleus/adapters/v1/openshift_v2/semantic_errors.rb +40 -0
  80. data/lib/nucleus/adapters/v1/openshift_v2/services.rb +173 -0
  81. data/lib/nucleus/adapters/v1/openshift_v2/vars.rb +49 -0
  82. data/lib/nucleus/adapters/v1/stub_adapter.rb +464 -0
  83. data/lib/nucleus/core/adapter_authentication_inductor.rb +62 -0
  84. data/lib/nucleus/core/adapter_extensions/auth/auth_client.rb +44 -0
  85. data/lib/nucleus/core/adapter_extensions/auth/authentication_retry_wrapper.rb +79 -0
  86. data/lib/nucleus/core/adapter_extensions/auth/expiring_token_auth_client.rb +53 -0
  87. data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +37 -0
  88. data/lib/nucleus/core/adapter_extensions/auth/o_auth2_auth_client.rb +95 -0
  89. data/lib/nucleus/core/adapter_extensions/auth/token_auth_client.rb +36 -0
  90. data/lib/nucleus/core/adapter_extensions/http_client.rb +177 -0
  91. data/lib/nucleus/core/adapter_extensions/http_tail_client.rb +26 -0
  92. data/lib/nucleus/core/adapter_extensions/tail_stopper.rb +25 -0
  93. data/lib/nucleus/core/common/errors/ambiguous_adapter_error.rb +7 -0
  94. data/lib/nucleus/core/common/errors/file_existence_error.rb +7 -0
  95. data/lib/nucleus/core/common/errors/startup_error.rb +12 -0
  96. data/lib/nucleus/core/common/exit_codes.rb +25 -0
  97. data/lib/nucleus/core/common/files/application_repo_sanitizer.rb +52 -0
  98. data/lib/nucleus/core/common/files/archive_extractor.rb +112 -0
  99. data/lib/nucleus/core/common/files/archiver.rb +91 -0
  100. data/lib/nucleus/core/common/link_generator.rb +46 -0
  101. data/lib/nucleus/core/common/logging/logging.rb +52 -0
  102. data/lib/nucleus/core/common/logging/multi_logger.rb +59 -0
  103. data/lib/nucleus/core/common/logging/request_log_formatter.rb +48 -0
  104. data/lib/nucleus/core/common/ssh_handler.rb +108 -0
  105. data/lib/nucleus/core/common/stream_callback.rb +27 -0
  106. data/lib/nucleus/core/common/thread_config_accessor.rb +85 -0
  107. data/lib/nucleus/core/common/url_converter.rb +28 -0
  108. data/lib/nucleus/core/enums/application_states.rb +26 -0
  109. data/lib/nucleus/core/enums/logfile_types.rb +28 -0
  110. data/lib/nucleus/core/error_messages.rb +127 -0
  111. data/lib/nucleus/core/errors/adapter_error.rb +13 -0
  112. data/lib/nucleus/core/errors/adapter_missing_implementation_error.rb +12 -0
  113. data/lib/nucleus/core/errors/adapter_request_error.rb +10 -0
  114. data/lib/nucleus/core/errors/adapter_resource_not_found_error.rb +10 -0
  115. data/lib/nucleus/core/errors/endpoint_authentication_error.rb +10 -0
  116. data/lib/nucleus/core/errors/platform_specific_semantic_error.rb +12 -0
  117. data/lib/nucleus/core/errors/platform_timeout_error.rb +10 -0
  118. data/lib/nucleus/core/errors/platform_unavailable_error.rb +10 -0
  119. data/lib/nucleus/core/errors/semantic_adapter_request_error.rb +19 -0
  120. data/lib/nucleus/core/errors/unknown_adapter_call_error.rb +10 -0
  121. data/lib/nucleus/core/file_handling/archive_converter.rb +29 -0
  122. data/lib/nucleus/core/file_handling/file_manager.rb +64 -0
  123. data/lib/nucleus/core/file_handling/git_deployer.rb +133 -0
  124. data/lib/nucleus/core/file_handling/git_repo_analyzer.rb +23 -0
  125. data/lib/nucleus/core/import/adapter_configuration.rb +53 -0
  126. data/lib/nucleus/core/import/vendor_parser.rb +28 -0
  127. data/lib/nucleus/core/import/version_detector.rb +18 -0
  128. data/lib/nucleus/core/models/abstract_model.rb +29 -0
  129. data/lib/nucleus/core/models/endpoint.rb +30 -0
  130. data/lib/nucleus/core/models/provider.rb +26 -0
  131. data/lib/nucleus/core/models/vendor.rb +22 -0
  132. data/lib/nucleus/ext/kernel.rb +5 -0
  133. data/lib/nucleus/ext/regexp.rb +49 -0
  134. data/lib/nucleus/os.rb +15 -0
  135. data/lib/nucleus/root_dir.rb +13 -0
  136. data/lib/nucleus/scripts/finalize.rb +8 -0
  137. data/lib/nucleus/scripts/initialize.rb +9 -0
  138. data/lib/nucleus/scripts/initialize_config_defaults.rb +26 -0
  139. data/lib/nucleus/scripts/load.rb +17 -0
  140. data/lib/nucleus/scripts/load_dependencies.rb +43 -0
  141. data/lib/nucleus/scripts/setup_config.rb +28 -0
  142. data/lib/nucleus/scripts/shutdown.rb +11 -0
  143. data/lib/nucleus/version.rb +3 -0
  144. data/nucleus.gemspec +88 -0
  145. data/public/robots.txt +2 -0
  146. data/public/swagger-ui/css/reset.css +125 -0
  147. data/public/swagger-ui/css/screen.css +1224 -0
  148. data/public/swagger-ui/images/apple-touch-icon-114x114.png +0 -0
  149. data/public/swagger-ui/images/apple-touch-icon-120x120.png +0 -0
  150. data/public/swagger-ui/images/apple-touch-icon-144x144.png +0 -0
  151. data/public/swagger-ui/images/apple-touch-icon-152x152.png +0 -0
  152. data/public/swagger-ui/images/apple-touch-icon-57x57.png +0 -0
  153. data/public/swagger-ui/images/apple-touch-icon-60x60.png +0 -0
  154. data/public/swagger-ui/images/apple-touch-icon-72x72.png +0 -0
  155. data/public/swagger-ui/images/apple-touch-icon-76x76.png +0 -0
  156. data/public/swagger-ui/images/explorer_icons.png +0 -0
  157. data/public/swagger-ui/images/favicon-128.png +0 -0
  158. data/public/swagger-ui/images/favicon-16x16.png +0 -0
  159. data/public/swagger-ui/images/favicon-196x196.png +0 -0
  160. data/public/swagger-ui/images/favicon-32x32.png +0 -0
  161. data/public/swagger-ui/images/favicon-96x96.png +0 -0
  162. data/public/swagger-ui/images/favicon.ico +0 -0
  163. data/public/swagger-ui/images/logo_small.png +0 -0
  164. data/public/swagger-ui/images/mstile-144x144.png +0 -0
  165. data/public/swagger-ui/images/mstile-150x150.png +0 -0
  166. data/public/swagger-ui/images/mstile-310x150.png +0 -0
  167. data/public/swagger-ui/images/mstile-310x310.png +0 -0
  168. data/public/swagger-ui/images/mstile-70x70.png +0 -0
  169. data/public/swagger-ui/images/pet_store_api.png +0 -0
  170. data/public/swagger-ui/images/throbber.gif +0 -0
  171. data/public/swagger-ui/images/wordnik_api.png +0 -0
  172. data/public/swagger-ui/index.html +107 -0
  173. data/public/swagger-ui/lib/backbone-min.js +38 -0
  174. data/public/swagger-ui/lib/handlebars-1.0.0.js +2278 -0
  175. data/public/swagger-ui/lib/highlight.7.3.pack.js +1 -0
  176. data/public/swagger-ui/lib/jquery-1.8.0.min.js +2 -0
  177. data/public/swagger-ui/lib/jquery.ba-bbq.min.js +18 -0
  178. data/public/swagger-ui/lib/jquery.slideto.min.js +1 -0
  179. data/public/swagger-ui/lib/jquery.wiggle.min.js +8 -0
  180. data/public/swagger-ui/lib/shred.bundle.js +2765 -0
  181. data/public/swagger-ui/lib/shred/content.js +193 -0
  182. data/public/swagger-ui/lib/swagger-oauth.js +211 -0
  183. data/public/swagger-ui/lib/swagger.js +1653 -0
  184. data/public/swagger-ui/lib/underscore-min.js +32 -0
  185. data/public/swagger-ui/o2c.html +15 -0
  186. data/public/swagger-ui/redirect.html +14 -0
  187. data/public/swagger-ui/swagger-ui.js +2324 -0
  188. data/public/swagger-ui/swagger-ui.min.js +1 -0
  189. data/schemas/api.adapter.schema.yml +31 -0
  190. data/schemas/api.requirements.schema.yml +17 -0
  191. data/spec/factories/models.rb +61 -0
  192. data/spec/integration/api/auth_spec.rb +58 -0
  193. data/spec/integration/api/endpoints_spec.rb +167 -0
  194. data/spec/integration/api/errors_spec.rb +47 -0
  195. data/spec/integration/api/providers_spec.rb +157 -0
  196. data/spec/integration/api/swagger_schema_spec.rb +64 -0
  197. data/spec/integration/api/vendors_spec.rb +45 -0
  198. data/spec/integration/integration_spec_helper.rb +27 -0
  199. data/spec/integration/test_data_generator.rb +55 -0
  200. data/spec/nucleus_git_key.pem +51 -0
  201. data/spec/spec_helper.rb +98 -0
  202. data/spec/support/shared_example_request_types.rb +99 -0
  203. data/spec/test_suites.rake +31 -0
  204. data/spec/unit/adapters/archive_converter_spec.rb +25 -0
  205. data/spec/unit/adapters/file_manager_spec.rb +93 -0
  206. data/spec/unit/adapters/git_deployer_spec.rb +262 -0
  207. data/spec/unit/adapters/v1/stub_spec.rb +14 -0
  208. data/spec/unit/common/helpers/auth_helper_spec.rb +73 -0
  209. data/spec/unit/common/oauth2_auth_client_spec.rb +108 -0
  210. data/spec/unit/common/regexp_spec.rb +33 -0
  211. data/spec/unit/common/request_log_formatter_spec.rb +108 -0
  212. data/spec/unit/common/thread_config_accessor_spec.rb +97 -0
  213. data/spec/unit/models/endpoint_spec.rb +83 -0
  214. data/spec/unit/models/provider_spec.rb +102 -0
  215. data/spec/unit/models/vendor_spec.rb +100 -0
  216. data/spec/unit/schemas/adapter_schema_spec.rb +16 -0
  217. data/spec/unit/schemas/adapter_validation_spec.rb +56 -0
  218. data/spec/unit/schemas/requirements_schema_spec.rb +16 -0
  219. data/spec/unit/unit_spec_helper.rb +11 -0
  220. data/tasks/compatibility.rake +113 -0
  221. data/tasks/evaluation.rake +162 -0
  222. data/wiki/adapter_tests.md +99 -0
  223. data/wiki/implement_new_adapter.md +155 -0
  224. 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,7 @@
1
+ module Nucleus
2
+ class AmbiguousAdapterError < StartupError
3
+ def initialize(message)
4
+ super(message, ExitCodes::STARTUP_ERROR)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Nucleus
2
+ class FileExistenceError < StandardError
3
+ def initialize(message)
4
+ super(message)
5
+ end
6
+ end
7
+ 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