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,108 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class OpenshiftV2 < Stub
5
+ module Application
6
+ # @see Stub#applications
7
+ def applications
8
+ get('/applications').body[:data].collect { |application| to_nucleus_app(application) }
9
+ end
10
+
11
+ # @see Stub#application
12
+ def application(application_id)
13
+ to_nucleus_app(get("/application/#{app_id_by_name(application_id)}").body[:data])
14
+ end
15
+
16
+ # Creates the Openshift application and enables scaling by default.
17
+ # @see Stub#create_application
18
+ def create_application(application_entity)
19
+ # handle runtimes / cartridges
20
+ fail_with(:only_one_runtime) if application_entity[:runtimes].length > 1
21
+ fail_with(:must_have_runtime) if application_entity[:runtimes].length == 0
22
+ application_entity[:cartridge] = cartridge(application_entity.delete(:runtimes)[0])
23
+
24
+ # updates the application with a valid region identity
25
+ retrieve_region(application_entity) if application_entity.key?(:region)
26
+
27
+ # enable application scaling by default
28
+ application_entity[:scale] = true unless application_entity.key?(:scale)
29
+ created_application = post("/domains/#{app_domain}/applications", body: application_entity).body
30
+ # now make sure we keep at least 2 deployments, allows proper identification of application state
31
+ updated_application = put("/application/#{created_application[:data][:id]}",
32
+ body: { keep_deployments: 2, auto_deploy: false }).body
33
+ to_nucleus_app(updated_application[:data])
34
+ end
35
+
36
+ # @see Stub#delete_application
37
+ def delete_application(application_id)
38
+ delete("/applications/#{app_id_by_name(application_id)}")
39
+ end
40
+
41
+ private
42
+
43
+ def app_id_by_name(application_name_or_id)
44
+ unless application_name_or_id.length == 24 && application_name_or_id.match(/[0-9a-f]{24}/)
45
+ response = get("/domains/#{app_domain}/applications/#{application_name_or_id}", expects: [200, 404])
46
+ return response.body[:data][:id] if response.status == 200
47
+ end
48
+ # presumably already is an application id
49
+ application_name_or_id
50
+ end
51
+
52
+ def cartridge(runtime)
53
+ cartridges = get('/cartridges').body[:data]
54
+ matched_cartridges, partial_matches = matching_cartridges(cartridges, runtime)
55
+
56
+ fail_with(:ambiguous_runtime, [runtime, matched_cartridges]) if matched_cartridges.length > 1
57
+ return matched_cartridges[0][:name] unless matched_cartridges.empty?
58
+ fail_with(:invalid_runtime, [runtime]) if partial_matches.empty?
59
+
60
+ latest = -1
61
+ partial_matches.each { |v| latest = v if v.to_f > latest.to_f }
62
+ matched_cartridges.push(cartridges.find { |cartridge| cartridge[:name] == "#{runtime}-#{latest}" })
63
+ log.info("Selected cartridge '#{matched_cartridges.last[:name]}' to match '#{runtime}'")
64
+ matched_cartridges.last[:name]
65
+ end
66
+
67
+ def matching_cartridges(cartridges, runtime)
68
+ partial_matches = []
69
+ matches = cartridges.find_all do |cartridge|
70
+ if cartridge[:type] != 'standalone'
71
+ false
72
+ elsif cartridge[:name] == runtime
73
+ true
74
+ else
75
+ # is the name partially valid?
76
+ matches = cartridge[:name].match(/(\w+)-([\.\d]+)/)
77
+ # push the version so that we can finally choose the latest version
78
+ partial_matches.push(matches[2]) if matches[1] == runtime
79
+ # nevertheless at first the cartridge is invalid
80
+ false
81
+ end
82
+ end
83
+ [matches, partial_matches]
84
+ end
85
+
86
+ def to_nucleus_app(app, gear_groups = nil, deployments = nil)
87
+ gear_groups = load_gears(app[:id]) unless gear_groups
88
+ deployments = load_deployments(app[:id]) unless deployments
89
+
90
+ app[:release_version] = active_deployment(app, deployments)[:sha1]
91
+ app[:state] = application_state(app, gear_groups, deployments)
92
+ app[:web_url] = app.delete :app_url
93
+ app[:autoscaled] = app.delete :scalable
94
+ app[:region] = gear_groups[0][:gears][0][:region]
95
+ app[:instances] = app.delete :gear_count
96
+ app[:created_at] = app.delete :creation_time
97
+ # applications can't be updated, use creation timestamp
98
+ app[:updated_at] = app[:created_at]
99
+ app[:active_runtime] = app.delete :framework
100
+ # no additional runtimes, only one fixed (active) runtime per application
101
+ app[:runtimes] = [app[:active_runtime]]
102
+ app
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,21 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class OpenshiftV2 < Stub
5
+ # Authentication functionality to support the Openshift V2 API
6
+ module Authentication
7
+ # @see Stub#auth_client
8
+ def auth_client
9
+ HttpBasicAuthClient.new @check_certificates do |verify_ssl, headers|
10
+ # auth verification block
11
+ headers['Accept'] = 'application/json; version=1.7'
12
+ result = Excon.new("#{@endpoint_url}/user", ssl_verify_peer: verify_ssl).get(headers: headers)
13
+ # Openshift returns 401 for invalid credentials --> auth failed, return false
14
+ result.status != 401
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,96 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class OpenshiftV2 < Stub
5
+ module Data
6
+ # @see Stub#deploy
7
+ def deploy(application_id, file, file_compression_format)
8
+ app_id = app_id_by_name(application_id)
9
+ app = get("/application/#{app_id}").body[:data]
10
+ app_state = application_state(app)
11
+ account = get('/user').body[:data]
12
+ repo_name = "nucleus.app.repo.openshift_v2.deploy.#{application_id}.#{SecureRandom.uuid}"
13
+ # clone, extract, push and finally delete cloned repository (sync)
14
+ with_ssh_key do
15
+ GitDeployer.new(repo_name, app[:git_url], account[:email]).deploy(file, file_compression_format)
16
+ end
17
+
18
+ # auto deployment could be active for applications not created with Nucleus
19
+ return if app[:auto_deploy]
20
+
21
+ build_deployment(app_id)
22
+
23
+ return unless app_state == Enums::ApplicationStates::CREATED
24
+
25
+ # and finally stop so we don't get to see the sample application and switch to the deployed state
26
+ send_event(application_id, 'stop')
27
+ end
28
+
29
+ # @see Stub#download
30
+ def download(application_id, compression_format)
31
+ # Only possible with git
32
+ app = get("/application/#{app_id_by_name(application_id)}").body[:data]
33
+ if application_state(app) == Enums::ApplicationStates::CREATED
34
+ fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be downloaded'
35
+ end
36
+ # compress files to archive but exclude the .git repo
37
+ repo_name = "nucleus.app.repo.openshift_v2.download.#{application_id}.#{SecureRandom.uuid}"
38
+ with_ssh_key do
39
+ GitDeployer.new(repo_name, app[:git_url], nil).download(compression_format, true)
40
+ end
41
+ end
42
+
43
+ # @see Stub#rebuild
44
+ def rebuild(application_id)
45
+ app_id = app_id_by_name(application_id)
46
+ app = get("/application/#{app_id}").body[:data]
47
+ if application_state(app) == Enums::ApplicationStates::CREATED
48
+ fail Errors::SemanticAdapterRequestError, 'Application must be deployed before data can be rebuild'
49
+ end
50
+
51
+ account = get('/user').body[:data]
52
+ repo_name = "nucleus.app.repo.openshift_v2.rebuild.#{application_id}.#{SecureRandom.uuid}"
53
+
54
+ with_ssh_key do
55
+ GitDeployer.new(repo_name, app[:git_url], account[:email]).trigger_build
56
+ end
57
+
58
+ # if auto deployment ist disabled, we must also trigger a clean build
59
+ build_deployment(app_id) unless app[:auto_deploy]
60
+
61
+ # return with updated application
62
+ application(application_id)
63
+ end
64
+
65
+ private
66
+
67
+ def build_deployment(app_id)
68
+ # deploy
69
+ post("/application/#{app_id}/deployments", body: { force_clean_build: true })
70
+ end
71
+
72
+ def with_ssh_key
73
+ # 409 Conflict:
74
+ # - 120: SSH key with name #{name} already exists. Use a different name or delete conflicting key and retry
75
+ # - 121: Given public key is already in use. Use different key or delete conflicting key and retry.
76
+
77
+ # load ssh key into Openshift
78
+ matches = nucleus_config.ssh.handler.public_key.match(/(.*)\s{1}(.*)\s{1}(.*)/)
79
+ key_name = register_key(matches[1], matches[2])
80
+ return yield
81
+ ensure
82
+ # unload ssh key, allow 404 if the key couldn't be registered at first
83
+ delete("/user/keys/#{key_name}", expects: [200, 204, 404]) if key_name
84
+ end
85
+
86
+ def register_key(type, key)
87
+ key_name = "nucleus-#{SecureRandom.uuid}"
88
+ # ignore if the key was already assigned to a different name (status == 409 && exit_code == 121)
89
+ post('/user/keys', body: { name: key_name, type: type, content: key }, expects: [201, 409])
90
+ key_name
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,37 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class OpenshiftV2 < Stub
5
+ module Domains
6
+ # @see Stub#domains
7
+ def domains(application_id)
8
+ domains = get("/application/#{app_id_by_name(application_id)}/aliases").body[:data]
9
+ domains.collect { |domain| to_nucleus_domain(domain) }
10
+ end
11
+
12
+ # @see Stub#domain
13
+ def domain(application_id, domain_id)
14
+ to_nucleus_domain get("/application/#{app_id_by_name(application_id)}/alias/#{domain_id}").body[:data]
15
+ end
16
+
17
+ # @see Stub#create_domain
18
+ def create_domain(application_id, domain_entity)
19
+ to_nucleus_domain post("/application/#{app_id_by_name(application_id)}/aliases",
20
+ body: { id: domain_entity[:name] }).body[:data]
21
+ end
22
+
23
+ # @see Stub#delete_domain
24
+ def delete_domain(application_id, domain_id)
25
+ delete("/application/#{app_id_by_name(application_id)}/alias/#{domain_id}")
26
+ end
27
+
28
+ private
29
+
30
+ def to_nucleus_domain(domain)
31
+ { id: domain[:id], name: domain[:id], created_at: nil, updated_at: nil }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ class OpenshiftV2 < Stub
5
+ module Lifecycle
6
+ # @see Stub#start
7
+ def start(application_id)
8
+ # if app is only deployed, we must first restore the latest deployment
9
+ id = app_id_by_name(application_id)
10
+ validate_start_requirements(id, 'start')
11
+ to_nucleus_app(send_event(id, 'start'))
12
+ end
13
+
14
+ # @see Stub#stop
15
+ def stop(application_id)
16
+ id = app_id_by_name(application_id)
17
+ unless deployed?(id)
18
+ fail Errors::SemanticAdapterRequestError, 'Application must be deployed before it can be stopped'
19
+ end
20
+ to_nucleus_app(send_event(id, 'stop'))
21
+ end
22
+
23
+ # @see Stub#restart
24
+ def restart(application_id)
25
+ id = app_id_by_name(application_id)
26
+ validate_start_requirements(id, 'restart')
27
+ to_nucleus_app(send_event(id, 'restart'))
28
+ end
29
+
30
+ private
31
+
32
+ def validate_start_requirements(id, action)
33
+ state = application_state(get("/application/#{id}").body[:data])
34
+ if state == Enums::ApplicationStates::DEPLOYED
35
+ activate(id, latest_deployment(id)[:id])
36
+ elsif state == Enums::ApplicationStates::CREATED
37
+ fail Errors::SemanticAdapterRequestError, "Application must be deployed before it can be #{action}ed"
38
+ end
39
+ end
40
+
41
+ def deployed?(application_id)
42
+ app = get("/application/#{app_id_by_name(application_id)}").body[:data]
43
+ application_state(app) != Enums::ApplicationStates::CREATED
44
+ end
45
+
46
+ # Send the event and trigger an action.
47
+ # @return [Hash] Openshift application data
48
+ def send_event(application_id, event, options = {})
49
+ options[:event] = event
50
+ post("/application/#{app_id_by_name(application_id)}/events", body: options).body[:data]
51
+ end
52
+
53
+ def activate(application_id, deployment_id)
54
+ send_event(application_id, 'activate', deployment_id: deployment_id)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,106 @@
1
+ require 'time'
2
+ require 'net/ssh'
3
+
4
+ module Nucleus
5
+ module Adapters
6
+ module V1
7
+ class OpenshiftV2 < Stub
8
+ module Logs
9
+ # @see Stub#logs
10
+ def logs(application_id)
11
+ # fails with 404 if application is not available
12
+ app = get("/application/#{app_id_by_name(application_id)}").body[:data]
13
+ # ssh uri
14
+ uri = ssh_uri(app)
15
+
16
+ with_ssh_key do
17
+ remote_log_files(uri, app[:creation_time])
18
+ end
19
+ end
20
+
21
+ # @see Stub#log?
22
+ def log?(application_id, log_id)
23
+ # fails with 404 if application is not available
24
+ app = get("/application/#{app_id_by_name(application_id)}").body[:data]
25
+ # ssh uri
26
+ uri = ssh_uri(app)
27
+
28
+ with_ssh_key do
29
+ remote_log_file?(uri)
30
+ end
31
+ end
32
+
33
+ # @see Stub#tail
34
+ def tail(application_id, log_id, stream)
35
+ # TODO: implement me
36
+ # remote_cmd = "tail#{options.opts ? ' --opts ' + Base64::encode64(options.opts).chomp : ''} #{file_glob}"
37
+ # ssh_cmd = "ssh -t #{uuid}@#{host} '#{remote_cmd}'"
38
+ fail NOT_IMPLEMENTED_ERROR
39
+ end
40
+
41
+ # @see Stub#log_entries
42
+ def log_entries(application_id, log_id)
43
+ # fails with 404 if application is not available
44
+ app = get("/application/#{app_id_by_name(application_id)}").body[:data]
45
+ # ssh uri
46
+ uri = ssh_uri(app)
47
+
48
+ with_ssh_key do
49
+ remote_log_entries(uri, application_id, log_id)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def ssh_uri(application)
56
+ URI.parse(application[:ssh_url])
57
+ end
58
+
59
+ def remote_log_files(uri, app_creation_time)
60
+ available_log_files = []
61
+ # ssh into main instance
62
+ Net::SSH.start(uri.host, uri.user, keys: [nucleus_config.ssh.handler.key_file]) do |ssh|
63
+ # https://developers.openshift.com/en/managing-log-files.html#log-location
64
+ log_files = ssh.exec!('ls $OPENSHIFT_LOG_DIR')
65
+
66
+ log_files.split("\n").each do |file|
67
+ updated_at = ssh.exec!("date -r $OPENSHIFT_LOG_DIR/#{file}")
68
+ updated_at = Time.parse(updated_at).utc.iso8601
69
+ # TODO: no unified naming among cartridges: ApplicationLogfileType::APPLICATION by default.
70
+ available_log_files.push(id: File.basename(file, '.*'), name: file,
71
+ type: Enums::ApplicationLogfileType::APPLICATION,
72
+ created_at: app_creation_time, updated_at: updated_at)
73
+ end
74
+ end
75
+ available_log_files
76
+ end
77
+
78
+ def remote_log_file?(uri)
79
+ Net::SSH.start(uri.host, uri.user, keys: [nucleus_config.ssh.handler.key_file]) do |ssh|
80
+ remote_file_exists?(ssh, "#{log_id}.log")
81
+ end
82
+ end
83
+
84
+ def remote_log_entries(uri, app_id, log_id)
85
+ Net::SSH.start(uri.host, uri.user, keys: [nucleus_config.ssh.handler.key_file]) do |ssh|
86
+ # log exists?
87
+ unless remote_file_exists?(ssh, "#{log_id}.log")
88
+ fail Errors::AdapterResourceNotFoundError,
89
+ "Invalid log file '#{log_id}', not available for application '#{app_id}'"
90
+ end
91
+ # process log
92
+ log = ssh.exec!("cat $OPENSHIFT_LOG_DIR/#{log_id}.log")
93
+ log.split("\n")
94
+ end
95
+ end
96
+
97
+ def remote_file_exists?(connection, file)
98
+ # file exists? 1 : 0
99
+ exists = connection.exec!("[ ! -f $OPENSHIFT_LOG_DIR/#{file} ]; echo $?").strip
100
+ exists == '1'
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,125 @@
1
+ module Nucleus
2
+ module Adapters
3
+ module V1
4
+ # @see https://access.redhat.com/documentation/en-US/OpenShift/2.0/html/REST_API_Guide The Openshift V2
5
+ # API documentation
6
+ class OpenshiftV2 < Stub
7
+ include Nucleus::Logging
8
+ include Nucleus::Adapters::V1::OpenshiftV2::Authentication
9
+ include Nucleus::Adapters::V1::OpenshiftV2::Application
10
+ include Nucleus::Adapters::V1::OpenshiftV2::AppStates
11
+ include Nucleus::Adapters::V1::OpenshiftV2::Data
12
+ include Nucleus::Adapters::V1::OpenshiftV2::Domains
13
+ include Nucleus::Adapters::V1::OpenshiftV2::Lifecycle
14
+ include Nucleus::Adapters::V1::OpenshiftV2::Logs
15
+ include Nucleus::Adapters::V1::OpenshiftV2::Regions
16
+ include Nucleus::Adapters::V1::OpenshiftV2::Scaling
17
+ include Nucleus::Adapters::V1::OpenshiftV2::SemanticErrors
18
+ include Nucleus::Adapters::V1::OpenshiftV2::Services
19
+ include Nucleus::Adapters::V1::OpenshiftV2::Vars
20
+
21
+ def initialize(endpoint_url, endpoint_app_domain = nil, check_certificates = true)
22
+ super(endpoint_url, endpoint_app_domain, check_certificates)
23
+ end
24
+
25
+ def handle_error(error_response)
26
+ # some error messages do not have the proper error message format
27
+ errors = openshift_errors(error_response)
28
+ if error_response.status == 404 && errors.any? { |e| e[:text].include?('not found') }
29
+ fail Errors::AdapterResourceNotFoundError, errors.collect { |e| e[:text] }.join(' ')
30
+ elsif error_response.status == 422
31
+ fail Errors::SemanticAdapterRequestError, errors.collect { |e| e[:text] }.join(' ')
32
+ elsif error_response.status == 503
33
+ fail Errors::PlatformUnavailableError, 'The Openshift API is currently not available'
34
+ elsif error_response.status == 504
35
+ fail Errors::PlatformTimeoutError, 'The Openshift API did not receive information from it\'s slaves. '\
36
+ 'Most likely the request is still being executed. Please make sure to analyse whether the request '\
37
+ 'was successful before invoking further actions.'
38
+ end
39
+ # error still unhandled, will result in a 500, server error
40
+ log.warn "Openshift error still unhandled: #{error_response}"
41
+ end
42
+
43
+ def openshift_errors(error_response)
44
+ if error_response.body.is_a?(Hash) && error_response.body.key?(:messages)
45
+ error_response.body[:messages].collect { |error| { field: error[:field], text: error[:text] } }
46
+ else
47
+ []
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def headers
54
+ super.merge('Accept' => 'application/json; version=1.7', 'Content-Type' => 'application/json')
55
+ end
56
+
57
+ def app_domain
58
+ # A user always has only 1 domain as described on:
59
+ # https://access.redhat.com/documentation/en-US/OpenShift/2.0/html/REST_API_Guide/chap-API_Guide-Domains.html
60
+ user_domains = get('/domains').body[:data]
61
+ fail_with(:no_user_domain) if user_domains.empty?
62
+ user_domains.first[:name]
63
+ end
64
+
65
+ def original_deployment(app, deployments = nil)
66
+ # TODO: this is actually quite scary, could easily fail with wrong timing
67
+ # What are the alternatives?
68
+ # 1) Clone git repo and lookup commits --> insanely slow
69
+ # 2) Identify initial commits by sha1 key --> would require collection of allowed values, which may change!
70
+ deployments = load_deployments(app[:id]) unless deployments
71
+ deployments.find do |deployment|
72
+ diff = (Time.parse(deployment[:created_at]).to_i - Time.parse(app[:creation_time]).to_i).abs
73
+ log.debug "OS deployment time diff: #{diff}"
74
+ diff < 20 && deployment[:force_clean_build] == false &&
75
+ deployment[:hot_deploy] == false && deployment[:ref] == 'master'
76
+ end
77
+ end
78
+
79
+ def latest_deployment(application_id, deployments = nil)
80
+ deployments = load_deployments(application_id) unless deployments
81
+ latest = nil
82
+ latest_ts = nil
83
+ deployments.each do |deployment|
84
+ ts = Time.parse(deployment[:created_at]).to_i
85
+ if latest.nil? || ts > latest_ts
86
+ latest = deployment
87
+ latest_ts = ts
88
+ end
89
+ end
90
+ latest
91
+ end
92
+
93
+ def active_deployment(app, deployments = nil)
94
+ deployments = load_deployments(app[:id]) unless deployments
95
+ active = nil
96
+ active_ts = nil
97
+ deployments.each do |deployment|
98
+ ts = Time.parse(last_activation(deployment[:activations])).to_i
99
+ if active.nil? || ts > active_ts
100
+ active = deployment
101
+ active_ts = ts
102
+ end
103
+ end
104
+ active
105
+ end
106
+
107
+ def last_activation(activations)
108
+ latest = nil
109
+ activations.each do |activation|
110
+ latest = activation if latest.nil? || Time.parse(activation).to_i > Time.parse(latest).to_i
111
+ end
112
+ latest
113
+ end
114
+
115
+ def load_deployments(application_id)
116
+ get("/application/#{application_id}/deployments").body[:data]
117
+ end
118
+
119
+ def load_gears(application_id)
120
+ get("/application/#{application_id}/gear_groups").body[:data]
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end