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,17 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudControl < Stub
5
+ # cloud control specific token, which requires a specific +cc_auth_token+ value in the +Authorization+ header.
6
+ class Token < Nucleus::Adapters::ExpiringTokenAuthClient
7
+ # @see Nucleus::Adapters::ExpiringTokenAuthClient#auth_header
8
+ def auth_header
9
+ # call super for error checks, but ignore returned header
10
+ super
11
+ { 'Authorization' => "cc_auth_token=\"#{api_token}\"" }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,88 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudControl < Stub
5
+ # cloud control, CRUD operations for the application's environment variable object,
6
+ # which is referred to as +config addon+ on the platform.
7
+ module Vars
8
+ # @see Stub#env_vars
9
+ def env_vars(application_id)
10
+ cc_vars = cc_vars(application_id)
11
+ cc_vars.collect do |key, value|
12
+ { id: key, key: key, value: value }
13
+ end
14
+ end
15
+
16
+ # @see Stub#env_var
17
+ def env_var(application_id, env_var_key)
18
+ cc_vars = cc_vars(application_id)
19
+ fail Errors::AdapterResourceNotFoundError,
20
+ "Env. var key '#{env_var_key}' does not exist" unless env_var?(application_id, env_var_key, cc_vars)
21
+ { id: env_var_key, key: env_var_key, value: cc_vars[env_var_key.to_sym] }
22
+ end
23
+
24
+ # @see Stub#create_env_var
25
+ def create_env_var(application_id, env_var)
26
+ cc_vars = cc_vars(application_id)
27
+ fail Errors::SemanticAdapterRequestError,
28
+ "Env. var key '#{env_var[:key]}' already taken" if env_var?(application_id, env_var[:key], cc_vars)
29
+ set_var(application_id, env_var[:key], env_var[:value])
30
+ end
31
+
32
+ # @see Stub#update_env_var
33
+ def update_env_var(application_id, env_var_key, env_var)
34
+ cc_vars = cc_vars(application_id)
35
+ fail Errors::AdapterResourceNotFoundError,
36
+ "Env. var key '#{env_var_key}' does not exist" unless env_var?(application_id, env_var_key, cc_vars)
37
+ set_var(application_id, env_var_key, env_var[:value])
38
+ end
39
+
40
+ # @see Stub#delete_env_var
41
+ def delete_env_var(application_id, env_var_key)
42
+ cc_vars = cc_vars(application_id)
43
+ fail Errors::AdapterResourceNotFoundError,
44
+ "Env. var key '#{env_var_key}' does not exist" unless env_var?(application_id, env_var_key, cc_vars)
45
+ set_var(application_id, env_var_key, nil)
46
+ end
47
+
48
+ private
49
+
50
+ def cc_vars(application_id)
51
+ cc_vars_response = get("app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon/config.free")
52
+ cc_vars_response.body[:settings][:CONFIG_VARS]
53
+ end
54
+
55
+ # Set the variable value (create or update)
56
+ # @param [String] application_id id of the cloud control application
57
+ # @param [Symbol] key variable key name
58
+ # @param [String, Integer, Float, Double] value value to apply to the variable
59
+ # @return [Hash] Nucleus representation of the modified variable
60
+ def set_var(application_id, key, value)
61
+ if value.nil?
62
+ # delete the var, set to 'null'
63
+ settings = "{\"#{key}\":null}"
64
+ else
65
+ settings = "{\"#{key}\":\"#{value}\"}"
66
+ end
67
+ response = put("/app/#{application_id}/deployment/#{NUCLEUS_DEPLOYMENT}/addon/config.free",
68
+ body: { addon: 'config.free',
69
+ settings: settings,
70
+ force: true })
71
+ all_vars = response.body[:settings][:CONFIG_VARS]
72
+ { id: key, key: key, value: all_vars[key.to_sym] }
73
+ end
74
+
75
+ # Checks if a variable with the env_var_key already exists.
76
+ # @param [String] application_id id of the cloud control application
77
+ # @param [Symbol] env_var_key key name that shall be checked for existence
78
+ # @param [Hash] all_vars collection of currently existing variables for the application
79
+ # @return [Boolean] true if there is a variable with the env_var_key, otherwise false
80
+ def env_var?(application_id, env_var_key, all_vars = nil)
81
+ all_vars = cc_vars(application_id) if all_vars.nil?
82
+ all_vars.key? env_var_key.to_sym
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,28 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudFoundryV2 < Stub
5
+ # AppStates for Cloud Foundry V2, or the logic to determine the current application state
6
+ module AppStates
7
+ private
8
+
9
+ def application_state(app_resource)
10
+ if app_resource[:entity][:state] == 'STARTED'
11
+ # 1: crashed
12
+ return Enums::ApplicationStates::CRASHED if app_resource[:entity][:package_state] == 'FAILED'
13
+ # 1: started
14
+ return Enums::ApplicationStates::RUNNING if app_resource[:entity][:package_state] == 'STAGED'
15
+ end
16
+
17
+ # 4: stopped if there is a detected buildpack
18
+ return Enums::ApplicationStates::STOPPED unless app_resource[:entity][:staging_task_id].nil?
19
+ # 3: deployed if stopped but no data can be downloaded
20
+ return Enums::ApplicationStates::DEPLOYED if deployed?(app_resource[:metadata][:guid])
21
+ # 2: created if stopped and no buildpack detected
22
+ Enums::ApplicationStates::CREATED
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,111 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudFoundryV2 < Stub
5
+ module Application
6
+ # @see Stub#applications
7
+ def applications
8
+ response = get('/v2/apps')
9
+ apps = []
10
+ response.body[:resources].each do |application_resource|
11
+ apps << to_nucleus_app(application_resource)
12
+ end
13
+ apps
14
+ end
15
+
16
+ # @see Stub#application
17
+ def application(application_name_or_id)
18
+ app_guid = app_guid(application_name_or_id)
19
+ get_response = get("/v2/apps/#{app_guid}")
20
+ to_nucleus_app(get_response.body)
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 Cloud Foundry --> remove from request
32
+ application.delete :region
33
+ end
34
+
35
+ apply_buildpack(application)
36
+
37
+ # apply default values, if not overridden by custom params
38
+ default_params = { space_guid: user_space_guid }
39
+ application = default_params.merge(application)
40
+
41
+ # WORKAROUND: requires numeric input, but rack-test provides characters :/
42
+ application['memory'] = application['memory'].to_i if application.key?('memory')
43
+
44
+ response = post('/v2/apps', body: application).body
45
+
46
+ # now create the default route (similar to when using an UI, e.g. Pivotal, Stackato or Bluemix) == web_url
47
+ create_cf_domain(response[:metadata][:guid], @endpoint_app_domain, response[:metadata][:guid])
48
+
49
+ # finally build the application response
50
+ application(response[:metadata][:guid])
51
+ end
52
+
53
+ # @see Stub#update_application
54
+ def update_application(application_name_or_id, application_form)
55
+ app_guid = app_guid(application_name_or_id)
56
+ apply_buildpack(application_form)
57
+ # update by guid
58
+ update_response = put("/v2/apps/#{app_guid}", body: application_form)
59
+ to_nucleus_app(update_response.body)
60
+ end
61
+
62
+ # @see Stub#delete_application
63
+ def delete_application(application_name_or_id)
64
+ app_guid = app_guid(application_name_or_id)
65
+ # first delete all service bindings
66
+ remove_all_services(app_guid)
67
+ # then delete the default route (otherwise it would remain as orphaned route)
68
+ routes = get("/v2/apps/#{app_guid}/routes?q=host:#{app_guid}&inline-relations-depth=1").body[:resources]
69
+ default_route = routes.find { |route| route[:entity][:domain][:entity][:name] == @endpoint_app_domain }
70
+ delete("/v2/routes/#{default_route[:metadata][:guid]}") if default_route
71
+ # and finally delete the app
72
+ delete("/v2/apps/#{app_guid}")
73
+ end
74
+
75
+ private
76
+
77
+ def apply_buildpack(application)
78
+ # handle desired runtime(s)
79
+ runtimes = application.delete(:runtimes)
80
+ return unless runtimes
81
+ fail_with(:only_one_runtime) if runtimes.length > 1
82
+
83
+ buildpack = find_runtime(runtimes[0])
84
+ # use the translated buildpack name if available, otherwise pass on the given runtime name
85
+ application[:buildpack] = buildpack ? buildpack : runtimes[0]
86
+ end
87
+
88
+ def to_nucleus_app(app_resource)
89
+ metadata = app_resource[:metadata]
90
+ app = app_resource[:entity]
91
+
92
+ app[:id] = metadata[:guid]
93
+ app[:created_at] = metadata[:created_at]
94
+ app[:updated_at] = metadata[:updated_at] || metadata[:created_at]
95
+ app[:state] = application_state(app_resource)
96
+ app[:web_url] = "http://#{app_web_url(metadata[:guid])}"
97
+ # route could have been deleted by the user
98
+ app[:web_url] = nil unless domain?(metadata[:guid], app[:web_url])
99
+ # Stackato does support autoscaling
100
+ app[:autoscaled] = app.delete(:autoscale_enabled) || false
101
+ app[:region] = 'default'
102
+ app[:active_runtime] = app[:detected_buildpack]
103
+ app[:runtimes] = app[:buildpack] ? [app[:buildpack]] : []
104
+ app[:release_version] = app.delete(:version)
105
+ app
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,17 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudFoundryV2 < Stub
5
+ # Authentication functionality to support the Cloud Foundry API
6
+ module Authentication
7
+ # @see Stub#auth_client
8
+ def auth_client
9
+ auth_endpoint = endpoint_info[:authorization_endpoint]
10
+ log.debug "Authenticate @ #{auth_endpoint}/oauth/token"
11
+ OAuth2AuthClient.new("#{auth_endpoint}/oauth/token", @check_certificates)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudFoundryV2 < Stub
5
+ module Buildpacks
6
+ include Nucleus::Adapters::BuildpackTranslator
7
+
8
+ # @see BuildpackTranslator#vendor_specific_runtimes
9
+ def vendor_specific_runtimes
10
+ {
11
+ 'ruby' => 'ruby_buildpack',
12
+ 'nodejs' => 'nodejs_buildpack',
13
+ 'python' => 'python_buildpack',
14
+ 'java' => 'java_buildpack',
15
+ 'go' => 'go_buildpack',
16
+ 'php' => 'php_buildpack'
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,141 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ # The {CloudFoundryV2} adapter is designed to support the Cloud Foundry API and uses only commands
5
+ # of the API version 2.<br>
6
+ # <br>
7
+ # Besides native Cloud Foundry installations, this adapter shall also work with forks, such as Stackato 3.4.2.<br>
8
+ # <br>
9
+ # The Nucleus API is fully supported, there are no known issues.
10
+ # @see http://apidocs.cloudfoundry.org The latest Cloud Foundry API documentation
11
+ class CloudFoundryV2 < Stub
12
+ include Nucleus::Logging
13
+ include Nucleus::Adapters::V1::CloudFoundryV2::Authentication
14
+ include Nucleus::Adapters::V1::CloudFoundryV2::AppStates
15
+ include Nucleus::Adapters::V1::CloudFoundryV2::Buildpacks
16
+ include Nucleus::Adapters::V1::CloudFoundryV2::Application
17
+ include Nucleus::Adapters::V1::CloudFoundryV2::Domains
18
+ include Nucleus::Adapters::V1::CloudFoundryV2::Data
19
+ include Nucleus::Adapters::V1::CloudFoundryV2::Lifecycle
20
+ include Nucleus::Adapters::V1::CloudFoundryV2::Logs
21
+ include Nucleus::Adapters::V1::CloudFoundryV2::Regions
22
+ include Nucleus::Adapters::V1::CloudFoundryV2::Scaling
23
+ include Nucleus::Adapters::V1::CloudFoundryV2::SemanticErrors
24
+ include Nucleus::Adapters::V1::CloudFoundryV2::Services
25
+ include Nucleus::Adapters::V1::CloudFoundryV2::Vars
26
+
27
+ def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
28
+ super(endpoint_url, endpoint_app_domain, check_certificates)
29
+ end
30
+
31
+ def handle_error(error)
32
+ cf_error = error.body.is_a?(Hash) ? error.body[:code] : nil
33
+ case error.status
34
+ when 400
35
+ handle_400_error(error, cf_error)
36
+ when 404
37
+ fail Errors::AdapterResourceNotFoundError, error.body[:description] if cf_error > 10_000
38
+ else
39
+ if [1001].include? cf_error
40
+ fail Errors::AdapterRequestError, "#{error.body[:description]} (#{cf_error} - #{error.body[:error_code]})"
41
+ elsif [10_002].include?(cf_error) || error.status == 401
42
+ fail Errors::EndpointAuthenticationError, 'Endpoint authentication failed with OAuth2 token'
43
+ end
44
+ end
45
+ # error still unhandled, will result in a 500, server error
46
+ log.warn "Cloud Foundry error still unhandled: #{error}"
47
+ end
48
+
49
+ private
50
+
51
+ def handle_400_error(error, cf_error)
52
+ if cf_error == 150_001 || cf_error == 160_001 || cf_error > 100_000 && cf_error < 109_999
53
+ # Indicating semantically invalid parameters
54
+ fail Errors::SemanticAdapterRequestError, error.body[:description]
55
+ elsif cf_error == 170_002
56
+ fail_with(:build_in_progress)
57
+ elsif cf_error == 60_002
58
+ fail Errors::SemanticAdapterRequestError, 'Service is already assigned to the application'
59
+ end
60
+ end
61
+
62
+ def guid?(name_or_id)
63
+ Regexp::UUID_PATTERN.match(name_or_id) ? true : false
64
+ end
65
+
66
+ def default_organization_guid
67
+ get("/v2/spaces/#{user_space_guid}").body[:entity][:organization_guid]
68
+ end
69
+
70
+ def app_guid(app_name_or_id)
71
+ # app name is a UUID and therefore most likely the CF GUID
72
+ return app_name_or_id if guid?(app_name_or_id)
73
+ find_app_guid_by_name(app_name_or_id)
74
+ end
75
+
76
+ def find_app_guid_by_name(application_name)
77
+ filtered_list_response = get('/v2/apps', query: { q: "name:#{application_name}" })
78
+ if filtered_list_response.body[:resources].length == 0
79
+ fail Errors::AdapterResourceNotFoundError,
80
+ "Couldn't find app with name '#{application_name}' on the platform"
81
+ end
82
+ # return the found guid
83
+ filtered_list_response.body[:resources][0][:metadata][:guid]
84
+ end
85
+
86
+ def find_app_id_by_name(application_name, previous_response)
87
+ filtered_list_response = get('/v2/apps', query: { q: "name:#{application_name}" })
88
+ # fail as expected if the app can also not be found by its name
89
+ fail Errors::AdapterResourceNotFoundError,
90
+ previous_response.body[:description] if filtered_list_response.body[:resources].length == 0
91
+ # return the found guid
92
+ filtered_list_response.body[:resources][0][:metadata][:guid]
93
+ end
94
+
95
+ def endpoint_info
96
+ get('/v2/info', headers: {}).body
97
+ end
98
+
99
+ def user_info
100
+ get("#{endpoint_info[:authorization_endpoint]}/userinfo").body
101
+ end
102
+
103
+ def user
104
+ get("/v2/users/#{user_info[:user_id]}").body
105
+ end
106
+
107
+ def user_space_guid
108
+ users_spaces = get('/v2/spaces').body[:resources]
109
+ # only once space accessible
110
+ return users_spaces[0][:metadata][:guid] if users_spaces.length == 1
111
+ # use default space (stackato feature)
112
+ default_space = users_spaces.detect { |space_resource| space_resource[:entity][:is_default] == true }
113
+ return default_space[:metadata][:guid] if default_space
114
+ # check the users spaces for default
115
+ user_default_space_guid = user[:entity][:default_space_guid]
116
+ return user_default_space_guid if user_default_space_guid
117
+ # TODO: find a more suitable approach to detect the right space !?
118
+ # multiple spaces and no default space (dammit), choose the first one...
119
+ return users_spaces[0][:metadata][:guid] if users_spaces
120
+ # user has no space assigned, fail since we cant determine a space guid
121
+ fail_with(:no_space_assigned)
122
+ end
123
+
124
+ def headers
125
+ super.merge('Basic' => 'Y2Y6', 'Content-Type' => 'application/json')
126
+ end
127
+
128
+ def deployed?(application_guid)
129
+ response = head("/v2/apps/#{application_guid}/download", follow_redirects: false, expects: [200, 302, 404])
130
+ return true if response.status == 200 || response.status == 302
131
+ return false if response.status == 404
132
+ # if the response is neither one of the codes, the call fails anyway...
133
+ end
134
+
135
+ def app_web_url(app_guid)
136
+ "#{app_guid}.#{@endpoint_app_domain}" if @endpoint_app_domain
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,97 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class CloudFoundryV2 < Stub
5
+ module Data
6
+ # @see Stub#deploy
7
+ def deploy(application_name_or_id, file, file_compression_format)
8
+ # could be made async, too
9
+ # resources: [] says that no previous data shall be reused, see also:
10
+ # http://apidocs.cloudfoundry.org/202/apps/uploads_the_bits_for_an_app.html
11
+
12
+ app_guid = app_guid(application_name_or_id)
13
+ # deploy by guid
14
+ # deploy_response = put("/v2/apps/#{app_guid}/bits", body: { resources: [], application: file.read },
15
+ # headers: { 'Content-Type' => 'multipart/form-data; '\
16
+ # 'boundary=nucleus-cloud-foundry-adapter-file-upload-boundary' })
17
+
18
+ begin
19
+ # convert all archives to .zip archives
20
+ converted_file = ArchiveConverter.convert(file, file_compression_format, 'zip', true)
21
+ unless converted_file.respond_to?(:path) && converted_file.respond_to?(:read)
22
+ tmpfile = Tempfile.new(["nucleus-cf-deploy-upload-#{app_guid}", '.zip'])
23
+ tmpfile.binmode
24
+ tmpfile.write converted_file.read
25
+ tmpfile.rewind
26
+ converted_file = tmpfile
27
+ end
28
+
29
+ # TODO: this is only a temporary solution until excon supports multipart requests
30
+ # See also: https://github.com/excon/excon/issues/353
31
+ url = "#{@endpoint_url}/v2/apps/#{app_guid}/bits"
32
+ request_body = { multipart: true, application: converted_file, async: false, resources: '[]' }
33
+ begin
34
+ RestClient::Request.execute(method: :put, url: url, payload: request_body,
35
+ headers: headers, verify_ssl: @check_certificates)
36
+ rescue RestClient::BadRequest => e
37
+ raise Errors::AdapterRequestError, e.http_body
38
+ end
39
+ ensure
40
+ if tmpfile
41
+ tmpfile.close
42
+ tmpfile.unlink # deletes this temporary file
43
+ end
44
+ end
45
+ end
46
+
47
+ # @see Stub#download
48
+ def download(application_name_or_id, compression_format)
49
+ app_guid = app_guid(application_name_or_id)
50
+ # fail if there is no deployment
51
+ unless deployed?(app_guid)
52
+ fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be downloaded'
53
+ end
54
+
55
+ download_response = get("/v2/apps/#{app_guid}/download", follow_redirects: false, expects: [200, 302])
56
+ if download_response.status == 200
57
+ data = download_response.body
58
+ else
59
+ download_location = download_response.headers[:Location]
60
+ # if IBM f*cked with the download URL, fix the address
61
+ download_location = download_location.gsub(/objectstorage.service.networklayer.com/, 'objectstorage.softlayer.net')
62
+ # omit_default_port: https://github.com/excon/excon/issues/475
63
+ data = Excon.new(download_location, omit_default_port: true).get.body
64
+ end
65
+
66
+ # write data to tmpfile so that it can be converted
67
+ downloaded_application_archive = Tempfile.new(["nucleus-cf-deployment-download-#{app_guid}", '.zip'])
68
+ downloaded_application_archive.binmode
69
+ downloaded_application_archive.write StringIO.new(data).read
70
+ downloaded_application_archive.rewind
71
+
72
+ # convert from current format (which is always a zip archive) to the destination format
73
+ ArchiveConverter.convert(downloaded_application_archive, 'zip', compression_format, false)
74
+ ensure
75
+ if downloaded_application_archive
76
+ downloaded_application_archive.close
77
+ downloaded_application_archive.unlink
78
+ end
79
+ end
80
+
81
+ # @see Stub#rebuild
82
+ def rebuild(application_name_or_id)
83
+ app_guid = app_guid(application_name_or_id)
84
+ # fail if there is no deployment
85
+ unless deployed?(app_guid)
86
+ fail Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be rebuild'
87
+ end
88
+
89
+ # rebuild by name or id
90
+ rebuild_response = post("/v2/apps/#{app_guid}/restage")
91
+ to_nucleus_app(rebuild_response.body)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end