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,109 @@
1
+ module Nucleus
2
+ # The {Adapters} module combines all application logic to communicate with the different vendor platforms
3
+ # and created the unified API.
4
+ module Adapters
5
+ # The {BaseAdapter} is an abstract class that shall be extended by all actual Adapters.
6
+ # It provides methods to common functionality:<br>
7
+ # * authentication (+cache)
8
+ # * http client with general error handling
9
+ # * native platform API calls
10
+ # @abstract
11
+ class BaseAdapter
12
+ include HttpClient
13
+ include HttpTailClient
14
+ include Logging
15
+
16
+ attr_reader :endpoint_url
17
+
18
+ def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
19
+ fail ArgumentError, "'endpoint_url' must be a valid URL" unless endpoint_url =~ /\A#{URI.regexp(['https'])}\z/
20
+ @endpoint_url = endpoint_url
21
+ @endpoint_app_domain = endpoint_app_domain
22
+ @check_certificates = check_certificates
23
+ end
24
+
25
+ # thread-based cache for the api authorization headers
26
+ thread_config_accessor :auth_objects_cache, default: {}
27
+
28
+ # Cache the auth information.
29
+ # @param [String] key cache key
30
+ # @param [Nucleus::Adapters::AuthClient] auth_object authentication client to be cached
31
+ # @return [void]
32
+ def cache(key, auth_object)
33
+ auth_objects_cache[key] = auth_object
34
+ end
35
+
36
+ # Are there cached information for this key?
37
+ # @param [String] key cache key
38
+ # @return [true, false] true if has cached auth info, else false
39
+ def cache?(key)
40
+ auth_objects_cache.key? key
41
+ end
42
+
43
+ # Get the currently cached authentication object.
44
+ # @param [String] key cache key
45
+ # @return [Hash<String,String>, Nucleus::Adapters::AuthClient] cached authentication client
46
+ def cached(key)
47
+ return nil unless cache?(key)
48
+ auth_objects_cache[key]
49
+ end
50
+
51
+ # Create the cache key for the username / password combination and save it in the {::RequestStore} to make it
52
+ # available throughout the current request.
53
+ # @param [String] username the username for the authentication
54
+ # @param [String] password the password for the authentication
55
+ # @return [String] calculated hash key for the input values
56
+ def cache_key(username, password)
57
+ # calculate the cache only once per request
58
+ return RequestStore.store[:cache_key] if RequestStore.exist?(:cache_key)
59
+ key = Digest::SHA256.hexdigest "#{endpoint_url}#{username}:#{password}"
60
+ RequestStore.store[:cache_key] = key
61
+ key
62
+ end
63
+
64
+ # Get the cached authentication object and retrieve the presumably valid authentication header.
65
+ # @return [Hash<String,String>] hash including a valid authentication header
66
+ def headers
67
+ auth_object = auth_objects_cache[RequestStore.store[:cache_key]]
68
+ # AuthClient, generates the header for us
69
+ auth_object.auth_header
70
+ end
71
+
72
+ # Execute an API call, targeted directly against the vendors API.
73
+ # @param [Symbol] method http method to use, one of: [:GET, :POST, :DELETE, :PUT, :PATCH]
74
+ # @param [String] path url path to append to the endpoint's URL
75
+ # @param [Hash] params body params to use for PATCH, :PUT and :POST requests
76
+ # @return [Object] the actual response body of the vendor platform
77
+ def endpoint_call(method, path, params)
78
+ case method
79
+ when :GET
80
+ get(path, native_call: true).body
81
+ when :POST
82
+ post(path, native_call: true, body: params).body
83
+ when :DELETE
84
+ delete(path, native_call: true).body
85
+ when :PUT
86
+ put(path, native_call: true, body: params).body
87
+ when :PATCH
88
+ patch(path, native_call: true, body: params).body
89
+ else
90
+ fail AdapterRequestError, 'Unsupported adapter call method. Allowed are: GET, POST, PATCH, PUT, DELETE'
91
+ end
92
+ end
93
+
94
+ # Fail with a {Errors::PlatformSpecificSemanticError} error and format the error message to include the values
95
+ # that are passed in the params. Requires the adapter to provide a +semantic_error_messages+ method, which shall
96
+ # return a Hash with the platform specific semantic errors.
97
+ # @param [Symbol] error_name error that shall be returned
98
+ # @param [Array<String>] params values that are to be included in the error message template
99
+ # @raise [Errors::PlatformSpecificSemanticError]
100
+ def fail_with(error_name, params = nil)
101
+ unless respond_to?(:semantic_error_messages)
102
+ fail StandardError 'Invalid adapter implementation, no :semantic_error_messages method provided'
103
+ end
104
+ error = semantic_error_messages[error_name]
105
+ fail Errors::PlatformSpecificSemanticError.new(error[:message] % params, error[:code])
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,79 @@
1
+ module Nucleus
2
+ module Adapters
3
+ # The {BuildpackTranslator} provides convenience methods for the user to handle the installation
4
+ # of application runtimes. Common runtime names can be applied and are automatically translated to a
5
+ # platform native runtime name / url (if applicable).
6
+ module BuildpackTranslator
7
+ # List of common buildpacks that are available GitHub.
8
+ # However, they are not guaranteed to work with every platform that utilized buildpacks.
9
+ PUBLIC_BUILDPACKS = {
10
+ 'c' => 'https://github.com/atris/heroku-buildpack-c',
11
+ 'common_lisp' => 'https://github.com/mtravers/heroku-buildpack-cl',
12
+ 'core_data' => 'https://github.com/heroku/heroku-buildpack-core-data',
13
+ 'dart' => 'https://github.com/igrigorik/heroku-buildpack-dart',
14
+ 'eiffel' => 'https://github.com/mbustosorg/heroku-buildpack-eiffel',
15
+ 'elixir' => 'https://github.com/hashnuke/heroku-buildpack-elixir',
16
+ 'emacs' => 'https://github.com/technomancy/heroku-buildpack-emacs',
17
+ 'embedded_proxy' => 'https://github.com/ryanbrainard/heroku-buildpack-embedded-proxy',
18
+ 'erlang' => 'https://github.com/archaelus/heroku-buildpack-erlang',
19
+ 'factor' => 'https://github.com/ryanbrainard/heroku-buildpack-factor',
20
+ 'fakesu' => 'https://github.com/fabiokung/heroku-buildpack-fakesu',
21
+ 'geodjango' => 'https://github.com/cirlabs/heroku-buildpack-geodjango',
22
+ 'go' => 'https://github.com/kr/heroku-buildpack-go',
23
+ 'haskell' => 'https://github.com/mietek/haskell-on-heroku',
24
+ 'inline' => 'https://github.com/kr/heroku-buildpack-inline',
25
+ 'java_ant' => 'https://github.com/dennisg/heroku-buildpack-ant',
26
+ # introduced by IBM Bluemix, shall also work in Heroku
27
+ 'java_liberty' => 'https://github.com/cloudfoundry/ibm-websphere-liberty-buildpack',
28
+ 'jekyll' => 'https://github.com/mattmanning/heroku-buildpack-ruby-jekyll',
29
+ 'lua' => 'https://github.com/leafo/heroku-buildpack-lua',
30
+ 'luvit' => 'https://github.com/skomski/heroku-buildpack-luvit',
31
+ 'meteor' => 'https://github.com/jordansissel/heroku-buildpack-meteor',
32
+ 'middleman' => 'https://github.com/hashicorp/heroku-buildpack-middleman',
33
+ 'monit' => 'https://github.com/k33l0r/monit-buildpack',
34
+ 'multi' => 'https://github.com/heroku/heroku-buildpack-multi',
35
+ 'nanoc' => 'https://github.com/bobthecow/heroku-buildpack-nanoc',
36
+ 'dot_net' => 'https://github.com/friism/heroku-buildpack-mono',
37
+ 'null' => 'https://github.com/ryandotsmith/null-buildpack',
38
+ 'opa' => 'https://github.com/tsloughter/heroku-buildpack-opa',
39
+ 'perl' => 'https://github.com/miyagawa/heroku-buildpack-perl',
40
+ 'phantomjs' => 'https://github.com/stomita/heroku-buildpack-phantomjs',
41
+ 'phing' => 'https://github.com/ryanbrainard/heroku-buildpack-phing',
42
+ 'r' => 'https://github.com/virtualstaticvoid/heroku-buildpack-r',
43
+ 'rust' => 'https://github.com/emk/heroku-buildpack-rust',
44
+ 'redline' => 'https://github.com/will/heroku-buildpack-redline',
45
+ 'silex' => 'https://github.com/klaussilveira/heroku-buildpack-silex',
46
+ 'sphinx' => 'https://github.com/kennethreitz/sphinx-buildpack',
47
+ 'test' => 'https://github.com/ddollar/buildpack-test',
48
+ 'testing' => 'https://github.com/ryanbrainard/heroku-buildpack-testrunner'
49
+ }
50
+
51
+ # Search the list of known buildpacks, both vendor specific and public, to match the desires runtime name.
52
+ # @param [String] name of the runtime to look out for
53
+ # @return [Boolean] returns true if a vendor specific or public buildpack was found for the runtime
54
+ def find_runtime(name)
55
+ if respond_to? :vendor_specific_runtimes
56
+ runtime = vendor_specific_runtimes[name.downcase.underscore]
57
+ return runtime unless runtime.nil?
58
+ end
59
+
60
+ # if no vendor specific runtime was found, use the general definitions
61
+ PUBLIC_BUILDPACKS[name.downcase.underscore]
62
+ end
63
+
64
+ # Checks if the name of the runtime is matching a vendor specific runtime / buildpack.
65
+ # @param [String] name of the runtime for which to check if it matches a vendor specific runtime
66
+ # @return [Boolean] returns true if the name matches a vendor specific runtime, false otherwise
67
+ def native_runtime?(name)
68
+ if respond_to? :vendor_specific_runtimes
69
+ # case A: name is a key
70
+ return true if vendor_specific_runtimes.keys.include? name
71
+ # case B: name is a specific runtime name or a URL and one of the values
72
+ return vendor_specific_runtimes.values.include? name
73
+ end
74
+ # cant be native if there are no vendor specific runtimes
75
+ false
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,108 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudControl < Stub
5
+ # cloud control, CRUD operations for the application object
6
+ module Application
7
+ # @see Stub#applications
8
+ def applications
9
+ response = get('/app')
10
+ apps = []
11
+ response.body.each do |application|
12
+ apps << to_nucleus_app(application, default_deployment(application[:name]))
13
+ end
14
+ apps
15
+ end
16
+
17
+ # @see Stub#application
18
+ def application(application_id)
19
+ response = get("/app/#{application_id}").body
20
+ to_nucleus_app(response, default_deployment(response[:name]))
21
+ end
22
+
23
+ # @see Stub#create_application
24
+ def create_application(application)
25
+ if application.key? :region
26
+ unless application[:region].casecmp('default') == 0
27
+ fail Errors::SemanticAdapterRequestError,
28
+ "Region '#{application[:region]}' does not exist at the endpoint. "\
29
+ 'Please check which regions are actually available on this endpoint.'
30
+ end
31
+ # there is no region in cloudControl --> remove from request
32
+ application.delete :region
33
+ end
34
+
35
+ apply_buildpack(application)
36
+
37
+ # force the use of repository type 'git', unless overridden by the params
38
+ default_params = { repository_type: 'git' }
39
+ cc_application = default_params.merge(application)
40
+
41
+ create_app_response = post('/app', body: cc_application).body
42
+
43
+ # create the default deployment, name will automatically become 'default'
44
+ created_deployment = post("/app/#{create_app_response[:name]}/deployment", body: { name: 'nucleus' }).body
45
+
46
+ # activate the variables addon. However, the activation explicitly requires an initial key value pair...
47
+ post("/app/#{create_app_response[:name]}/deployment/#{NUCLEUS_DEPLOYMENT}/addon",
48
+ body: { addon: 'config.free', options: "{\"nucleus-initialized\": \"true\"}" }).body
49
+ # ...now delete the initial key value pair to have the desired clean setup
50
+ delete_env_var(create_app_response[:name], 'nucleus-initialized')
51
+
52
+ to_nucleus_app(create_app_response, created_deployment)
53
+ end
54
+
55
+ # @see Stub#delete_application
56
+ def delete_application(application_id)
57
+ # delete all deployments first
58
+ deployments = get("/app/#{application_id}/deployment").body
59
+ deployments.each do |deployment|
60
+ deployment_name = %r{(\w+)\/(\w+)}.match(deployment[:name])[2]
61
+ delete("/app/#{application_id}/deployment/#{deployment_name}")
62
+ end
63
+ delete("/app/#{application_id}")
64
+ end
65
+
66
+ private
67
+
68
+ def apply_buildpack(application)
69
+ runtimes = application.delete(:runtimes)
70
+ return unless runtimes
71
+ fail_with(:only_one_runtime) if runtimes.length > 1
72
+ buildpack = find_runtime(runtimes[0])
73
+ if native_runtime?(buildpack)
74
+ application[:type] = buildpack
75
+ elsif buildpack
76
+ application[:type] = 'custom'
77
+ application[:buildpack_url] = buildpack
78
+ else
79
+ # 3rd party buildpack must be a valid URL
80
+ unless Regexp::PERFECT_URL_PATTERN =~ runtimes[0]
81
+ fail Errors::SemanticAdapterRequestError,
82
+ "Invalid buildpack: '#{runtimes[0]}'. Please provide a valid buildpack URL for all "\
83
+ 'custom buildpacks that are not provided by cloud control.'
84
+ end
85
+ application[:type] = 'custom'
86
+ application[:buildpack_url] = runtimes[0]
87
+ end
88
+ end
89
+
90
+ def to_nucleus_app(app, deployment)
91
+ app[:id] = app[:name]
92
+ app[:created_at] = app.delete :date_created
93
+ app[:updated_at] = app.delete :date_modified
94
+ app[:state] = application_state(deployment)
95
+ app[:web_url] = "http://#{deployment[:default_subdomain]}"
96
+ app[:autoscaled] = false
97
+ app[:region] = 'default'
98
+ app[:instances] = deployment[:min_boxes]
99
+ app[:active_runtime] = app[:type][:name] == 'custom' ? app[:buildpack_url] : app[:type][:name]
100
+ app[:runtimes] = [app[:active_runtime]]
101
+ app[:release_version] = deployment[:version] != '-1' ? deployment[:version] : nil
102
+ app
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,27 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudControl < Stub
5
+ # Authentication functionality to support the cloudControl API
6
+ module Authentication
7
+ # @see Stub#auth_client
8
+ def auth_client
9
+ Token.new @check_certificates do |_verify_ssl, username, password|
10
+ auth_headers = { 'Authorization' => 'Basic ' + ["#{username}:#{password}"].pack('m*').gsub(/\n/, '') }
11
+ begin
12
+ # ssl verification is implemented by the HttpClient itself
13
+ response = post('/token', headers: auth_headers)
14
+ # parse to retrieve the token and expiration date
15
+ expires = Time.parse(response.body[:expires])
16
+ [response.body[:token], expires]
17
+ rescue Errors::AdapterError
18
+ # ignore the error, return nil for failed authentication
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudControl < Stub
5
+ # cloud control specific buildpacks
6
+ module Buildpacks
7
+ include Nucleus::Adapters::BuildpackTranslator
8
+
9
+ # @see BuildpackTranslator#vendor_specific_runtimes
10
+ def vendor_specific_runtimes
11
+ {
12
+ 'java' => 'java',
13
+ 'nodejs' => 'nodejs',
14
+ 'ruby' => 'ruby',
15
+ 'php' => 'php',
16
+ 'python' => 'python'
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,153 @@
1
+ require 'net/ssh'
2
+
3
+ module Nucleus
4
+ module Adapters
5
+ module V1
6
+ class CloudControl < Stub
7
+ include Nucleus::Logging
8
+ include Nucleus::Adapters::V1::CloudControl::Authentication
9
+ include Nucleus::Adapters::V1::CloudControl::Application
10
+ include Nucleus::Adapters::V1::CloudControl::Buildpacks
11
+ include Nucleus::Adapters::V1::CloudControl::Domains
12
+ include Nucleus::Adapters::V1::CloudControl::Data
13
+ include Nucleus::Adapters::V1::CloudControl::Lifecycle
14
+ include Nucleus::Adapters::V1::CloudControl::Logs
15
+ include Nucleus::Adapters::V1::CloudControl::Regions
16
+ include Nucleus::Adapters::V1::CloudControl::Scaling
17
+ include Nucleus::Adapters::V1::CloudControl::SemanticErrors
18
+ include Nucleus::Adapters::V1::CloudControl::Services
19
+ include Nucleus::Adapters::V1::CloudControl::Vars
20
+
21
+ # The default deployment name of cloud control applications that is used by Nucleus
22
+ NUCLEUS_DEPLOYMENT = 'nucleus'
23
+ # Error messages of semantic errors that are platform specific
24
+ CC_EXCLUSIVE_SEMANTIC_ERROR_MSGS = ['cannot use this name', 'may only contain', 'this field has no more than']
25
+ # Error messages of common semantic errors
26
+ CC_SEMANTIC_ERROR_MSGS = ['must be unique', 'already exists',
27
+ 'not a valid addon name', 'not a valid addon option']
28
+ CC_CONFLICT_ERROR_MSGS = ['Addon already exists']
29
+
30
+ def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
31
+ super(endpoint_url, endpoint_app_domain, check_certificates)
32
+ end
33
+
34
+ def handle_error(error_response)
35
+ message = error_response.body.match(/{(.*?)}/)
36
+ message = message[1] if message
37
+
38
+ # cloud control responds almost every time with 400...
39
+ if error_response.status == 400
40
+ handle_400(message)
41
+ elsif error_response.status == 409 && CC_CONFLICT_ERROR_MSGS.any? { |msg| message.include? msg }
42
+ fail Errors::SemanticAdapterRequestError, message
43
+ elsif error_response.status == 410
44
+ fail Errors::AdapterResourceNotFoundError, 'Resource not found'
45
+ elsif error_response.status == 503
46
+ fail Errors::PlatformUnavailableError, 'The cloudControl API is currently not available'
47
+ end
48
+ # error still unhandled, will result in a 500, server error
49
+ log.warn "cloudControl error still unhandled: #{error_response}"
50
+ end
51
+
52
+ private
53
+
54
+ def handle_400(message)
55
+ fail Errors::AdapterResourceNotFoundError, 'Resource not found' if message.nil?
56
+
57
+ if message.include?('Billing account required')
58
+ fail_with(:billing_required, [message])
59
+ elsif CC_EXCLUSIVE_SEMANTIC_ERROR_MSGS.any? { |msg| message.include? msg }
60
+ # all these errors are limited to cloud control, e.g. the allowed name characters and max name length
61
+ fail_with(:bad_name, [message])
62
+ elsif CC_SEMANTIC_ERROR_MSGS.any? { |msg| message.include? msg }
63
+ fail Errors::SemanticAdapterRequestError, message
64
+ end
65
+ fail Errors::AdapterRequestError, message
66
+ end
67
+
68
+ def username
69
+ get('/user').body.first[:username]
70
+ end
71
+
72
+ def data_uploaded?(deployment)
73
+ application_id = deployment[:name].split(%r{/})[0]
74
+ repo_host = URI.parse(deployment[:branch]).host
75
+ repo_path = URI.parse(deployment[:branch]).path.gsub(%r{^/}, '').chomp('.git')
76
+ attempts = 0
77
+ with_ssh_key do
78
+ loop do
79
+ begin
80
+ return GitRepoAnalyzer.any_branch?(repo_host, repo_path, application_id)
81
+ rescue Net::SSH::AuthenticationFailed => e
82
+ attempts += 1
83
+ # wait up to 30 seconds
84
+ raise e if attempts >= 15
85
+ log.debug('SSH authentication failed, sleep and repeat')
86
+ # authentication is not yet ready, wait a short time
87
+ sleep(2.0)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def application_state(deployment)
94
+ # With cloud control not supporting the Nucleus application lifecycle, only 3 actual states remain:<br>
95
+ # * created, when no data deployment (not to confuse with cloud control deployment object) has been made yet
96
+ # * deployed, when only the data has been pushed into the repository (no build)
97
+ # * running, if a data deployment was pushed
98
+ if deployment[:version] == '-1'
99
+ return Enums::ApplicationStates::DEPLOYED if data_uploaded?(deployment)
100
+ return Enums::ApplicationStates::CREATED
101
+ end
102
+ return Enums::ApplicationStates::IDLE if deployment[:state] == 'idle'
103
+ Enums::ApplicationStates::RUNNING
104
+ # return Enums::ApplicationStates::STOPPED
105
+
106
+ # arriving here the above states do not catch all states of the cloudControl app, which should not happen ;-)
107
+ # fail Errors::UnknownAdapterCallError, 'Could not determine application state. '\
108
+ # 'Please verify the cloudControl adapter'
109
+ end
110
+
111
+ def default_deployment(application_id)
112
+ # get and return nucleus deployment, but catch arising 404 errors
113
+ return get("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}").body
114
+ rescue Errors::AdapterResourceNotFoundError
115
+ # if 404, list all deployments
116
+ all_deployments = get("/app/#{application_id}/deployment").body
117
+
118
+ # fail 422 (platform specific) if no deployment is available at all
119
+ fail_with(:no_deployment) if all_deployments.length == 0
120
+
121
+ # return deployment[0] if collection size is 1
122
+ return all_deployments[0] if all_deployments.length == 1
123
+
124
+ # return 'default' if more than 1 deployment and 'default' is included
125
+ def_deployment = all_deployments.find { |d| d[:name].split(%r{/})[1].downcase == 'default' }
126
+ return def_deployment if def_deployment
127
+
128
+ # return 'nucleus' if more than 1 deployment, but no 'default' is included
129
+ nucleus_deployment = all_deployments.find { |d| d[:name].split(%r{/})[1].downcase == 'nucleus' }
130
+ return nucleus_deployment if nucleus_deployment
131
+
132
+ # fail 422 if no 'default', no 'nucleus', and more than 1 deployment is available (could not identify default)
133
+ fail_with(:ambiguous_deployments)
134
+ end
135
+
136
+ def headers
137
+ super.merge('Content-Type' => 'application/json')
138
+ end
139
+
140
+ def with_ssh_key
141
+ user = username
142
+ # load ssh key into cloud control
143
+ matches = nucleus_config.ssh.handler.public_key.match(/(.*)\s{1}(.*)\s{1}(.*)/)
144
+ key_id = register_key(user, matches[1], matches[2])
145
+ return yield
146
+ ensure
147
+ # unload ssh key, allow 404 if the key couldn't be registered at first
148
+ delete("/user/#{user}/key/#{key_id}") if key_id
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end