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,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 AdapterRequestError < AdapterError
4
+ # initialize with default error to be 400
5
+ def initialize(message, ui_error = ErrorMessages::ENDPOINT_BAD_REQUEST)
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 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,10 @@
1
+ module Nucleus
2
+ module Errors
3
+ class PlatformTimeoutError < AdapterError
4
+ # initialize with default error to be 504
5
+ def initialize(message)
6
+ super(message, ErrorMessages::PLATFORM_GATEWAY_TIMEOUT)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Nucleus
2
+ module Errors
3
+ class PlatformUnavailableError < AdapterError
4
+ # initialize with default error to be 503
5
+ def initialize(message)
6
+ super(message, ErrorMessages::UNAVAILABLE)
7
+ end
8
+ end
9
+ end
10
+ 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,10 @@
1
+ module Nucleus
2
+ module Errors
3
+ class UnknownAdapterCallError < AdapterError
4
+ # initialize with default error to be 500
5
+ def initialize(message, ui_error = ErrorMessages::RESCUED_ADAPTER_CALL)
6
+ super(message, ui_error)
7
+ end
8
+ end
9
+ end
10
+ 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