nucleus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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