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,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