hybrid_platforms_conductor 32.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (244) hide show
  1. checksums.yaml +7 -0
  2. data/bin/check-node +24 -0
  3. data/bin/deploy +12 -0
  4. data/bin/dump_nodes_json +12 -0
  5. data/bin/free_ips +23 -0
  6. data/bin/free_veids +17 -0
  7. data/bin/get_impacted_nodes +43 -0
  8. data/bin/last_deploys +56 -0
  9. data/bin/nodes_to_deploy +104 -0
  10. data/bin/report +10 -0
  11. data/bin/run +39 -0
  12. data/bin/setup +11 -0
  13. data/bin/ssh_config +14 -0
  14. data/bin/test +13 -0
  15. data/bin/topograph +54 -0
  16. data/lib/hybrid_platforms_conductor/action.rb +82 -0
  17. data/lib/hybrid_platforms_conductor/actions_executor.rb +307 -0
  18. data/lib/hybrid_platforms_conductor/bitbucket.rb +123 -0
  19. data/lib/hybrid_platforms_conductor/cmd_runner.rb +188 -0
  20. data/lib/hybrid_platforms_conductor/cmdb.rb +34 -0
  21. data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +78 -0
  22. data/lib/hybrid_platforms_conductor/common_config_dsl/confluence.rb +43 -0
  23. data/lib/hybrid_platforms_conductor/common_config_dsl/file_system_tests.rb +110 -0
  24. data/lib/hybrid_platforms_conductor/common_config_dsl/idempotence_tests.rb +38 -0
  25. data/lib/hybrid_platforms_conductor/config.rb +263 -0
  26. data/lib/hybrid_platforms_conductor/confluence.rb +119 -0
  27. data/lib/hybrid_platforms_conductor/connector.rb +84 -0
  28. data/lib/hybrid_platforms_conductor/credentials.rb +127 -0
  29. data/lib/hybrid_platforms_conductor/current_dir_monitor.rb +42 -0
  30. data/lib/hybrid_platforms_conductor/deployer.rb +598 -0
  31. data/lib/hybrid_platforms_conductor/executable.rb +145 -0
  32. data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +44 -0
  33. data/lib/hybrid_platforms_conductor/hpc_plugins/action/interactive.rb +44 -0
  34. data/lib/hybrid_platforms_conductor/hpc_plugins/action/my_action.rb.sample +79 -0
  35. data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +63 -0
  36. data/lib/hybrid_platforms_conductor/hpc_plugins/action/ruby.rb +69 -0
  37. data/lib/hybrid_platforms_conductor/hpc_plugins/action/scp.rb +61 -0
  38. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/config.rb +78 -0
  39. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_ip.rb +104 -0
  40. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +114 -0
  41. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/my_cmdb.rb.sample +129 -0
  42. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/platform_handlers.rb +66 -0
  43. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +156 -0
  44. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +702 -0
  45. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/platform_handler_plugin.rb.sample +292 -0
  46. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +148 -0
  47. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/my_provisioner.rb.sample +103 -0
  48. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/podman.rb +125 -0
  49. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +522 -0
  50. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/proxmox_waiter.rb +707 -0
  51. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox/reserve_proxmox_container +122 -0
  52. data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +69 -0
  53. data/lib/hybrid_platforms_conductor/hpc_plugins/report/mediawiki.rb +164 -0
  54. data/lib/hybrid_platforms_conductor/hpc_plugins/report/my_report_plugin.rb.sample +88 -0
  55. data/lib/hybrid_platforms_conductor/hpc_plugins/report/stdout.rb +61 -0
  56. data/lib/hybrid_platforms_conductor/hpc_plugins/report/templates/confluence_inventory.html.erb +33 -0
  57. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +137 -0
  58. data/lib/hybrid_platforms_conductor/hpc_plugins/test/can_be_checked.rb +21 -0
  59. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +112 -0
  60. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_from_scratch.rb +35 -0
  61. data/lib/hybrid_platforms_conductor/hpc_plugins/test/connection.rb +28 -0
  62. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +44 -0
  63. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_from_scratch.rb +36 -0
  64. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +49 -0
  65. data/lib/hybrid_platforms_conductor/hpc_plugins/test/divergence.rb +25 -0
  66. data/lib/hybrid_platforms_conductor/hpc_plugins/test/executables.rb +46 -0
  67. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +45 -0
  68. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system_hdfs.rb +45 -0
  69. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +25 -0
  70. data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +77 -0
  71. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +38 -0
  72. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +56 -0
  73. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +54 -0
  74. data/lib/hybrid_platforms_conductor/hpc_plugins/test/linear_strategy.rb +47 -0
  75. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +82 -0
  76. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +120 -0
  77. data/lib/hybrid_platforms_conductor/hpc_plugins/test/my_test_plugin.rb.sample +143 -0
  78. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +74 -0
  79. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ports.rb +85 -0
  80. data/lib/hybrid_platforms_conductor/hpc_plugins/test/private_ips.rb +38 -0
  81. data/lib/hybrid_platforms_conductor/hpc_plugins/test/public_ips.rb +38 -0
  82. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre-meltdown-checker.sh +1930 -0
  83. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +56 -0
  84. data/lib/hybrid_platforms_conductor/hpc_plugins/test/veids.rb +31 -0
  85. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +159 -0
  86. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +122 -0
  87. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/my_test_report.rb.sample +48 -0
  88. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/stdout.rb +120 -0
  89. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/_confluence_errors_status.html.erb +46 -0
  90. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/_confluence_gauge.html.erb +49 -0
  91. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/confluence.html.erb +242 -0
  92. data/lib/hybrid_platforms_conductor/io_router.rb +70 -0
  93. data/lib/hybrid_platforms_conductor/json_dumper.rb +88 -0
  94. data/lib/hybrid_platforms_conductor/logger_helpers.rb +319 -0
  95. data/lib/hybrid_platforms_conductor/mutex_dir +76 -0
  96. data/lib/hybrid_platforms_conductor/nodes_handler.rb +597 -0
  97. data/lib/hybrid_platforms_conductor/parallel_threads.rb +97 -0
  98. data/lib/hybrid_platforms_conductor/platform_handler.rb +188 -0
  99. data/lib/hybrid_platforms_conductor/platforms_handler.rb +118 -0
  100. data/lib/hybrid_platforms_conductor/plugin.rb +53 -0
  101. data/lib/hybrid_platforms_conductor/plugins.rb +101 -0
  102. data/lib/hybrid_platforms_conductor/provisioner.rb +181 -0
  103. data/lib/hybrid_platforms_conductor/report.rb +31 -0
  104. data/lib/hybrid_platforms_conductor/reports_handler.rb +84 -0
  105. data/lib/hybrid_platforms_conductor/services_handler.rb +274 -0
  106. data/lib/hybrid_platforms_conductor/test.rb +141 -0
  107. data/lib/hybrid_platforms_conductor/test_by_service.rb +22 -0
  108. data/lib/hybrid_platforms_conductor/test_report.rb +282 -0
  109. data/lib/hybrid_platforms_conductor/tests_runner.rb +590 -0
  110. data/lib/hybrid_platforms_conductor/thycotic.rb +92 -0
  111. data/lib/hybrid_platforms_conductor/topographer.rb +859 -0
  112. data/lib/hybrid_platforms_conductor/topographer/plugin.rb +20 -0
  113. data/lib/hybrid_platforms_conductor/topographer/plugins/graphviz.rb +127 -0
  114. data/lib/hybrid_platforms_conductor/topographer/plugins/json.rb +72 -0
  115. data/lib/hybrid_platforms_conductor/topographer/plugins/my_topographer_output_plugin.rb.sample +37 -0
  116. data/lib/hybrid_platforms_conductor/topographer/plugins/svg.rb +30 -0
  117. data/lib/hybrid_platforms_conductor/version.rb +5 -0
  118. data/spec/hybrid_platforms_conductor_test.rb +159 -0
  119. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +43 -0
  120. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/interactive_spec.rb +18 -0
  121. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +102 -0
  122. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/ruby_spec.rb +108 -0
  123. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/scp_spec.rb +79 -0
  124. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions_spec.rb +199 -0
  125. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connection_spec.rb +212 -0
  126. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/cli_options_spec.rb +125 -0
  127. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +50 -0
  128. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connectable_nodes_spec.rb +28 -0
  129. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +448 -0
  130. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +313 -0
  131. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/node_helpers_spec.rb +32 -0
  132. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +134 -0
  133. data/spec/hybrid_platforms_conductor_test/api/actions_executor/logging_spec.rb +256 -0
  134. data/spec/hybrid_platforms_conductor_test/api/actions_executor/parallel_spec.rb +338 -0
  135. data/spec/hybrid_platforms_conductor_test/api/actions_executor/timeout_spec.rb +101 -0
  136. data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +165 -0
  137. data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +238 -0
  138. data/spec/hybrid_platforms_conductor_test/api/deployer/check_spec.rb +9 -0
  139. data/spec/hybrid_platforms_conductor_test/api/deployer/deploy_spec.rb +243 -0
  140. data/spec/hybrid_platforms_conductor_test/api/deployer/parse_deploy_output_spec.rb +104 -0
  141. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioner_spec.rb +131 -0
  142. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/docker/Dockerfile +10 -0
  143. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/docker_spec.rb +123 -0
  144. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/podman_spec.rb +211 -0
  145. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/config_dsl_spec.rb +126 -0
  146. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/create_spec.rb +290 -0
  147. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/destroy_spec.rb +43 -0
  148. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/ip_spec.rb +60 -0
  149. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/proxmox.json +3 -0
  150. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/destroy_vm_spec.rb +82 -0
  151. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/expired_containers_spec.rb +786 -0
  152. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/ips_assignment_spec.rb +112 -0
  153. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/other_lxc_containers_resources_spec.rb +190 -0
  154. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/pve_node_resources_spec.rb +200 -0
  155. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/retries_spec.rb +35 -0
  156. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/reserve_proxmox_container/vm_ids_assignment_spec.rb +67 -0
  157. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/start_spec.rb +79 -0
  158. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/state_spec.rb +28 -0
  159. data/spec/hybrid_platforms_conductor_test/api/deployer/provisioners/proxmox/stop_spec.rb +41 -0
  160. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/config_spec.rb +33 -0
  161. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_ip_spec.rb +64 -0
  162. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +133 -0
  163. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/platform_handlers_spec.rb +19 -0
  164. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs_plugins_api_spec.rb +446 -0
  165. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/common_spec.rb +127 -0
  166. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/git_diff_impacts_spec.rb +318 -0
  167. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/nodes_selectors_spec.rb +132 -0
  168. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/platform_handlers_plugins_api_spec.rb +60 -0
  169. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/several_platforms_spec.rb +58 -0
  170. data/spec/hybrid_platforms_conductor_test/api/platform_handler_spec.rb +97 -0
  171. data/spec/hybrid_platforms_conductor_test/api/platforms_handler_spec.rb +104 -0
  172. data/spec/hybrid_platforms_conductor_test/api/plugins_spec.rb +243 -0
  173. data/spec/hybrid_platforms_conductor_test/api/reports_handler_spec.rb +44 -0
  174. data/spec/hybrid_platforms_conductor_test/api/services_handler/actions_to_deploy_spec.rb +121 -0
  175. data/spec/hybrid_platforms_conductor_test/api/services_handler/deploy_allowed_spec.rb +142 -0
  176. data/spec/hybrid_platforms_conductor_test/api/services_handler/log_info_spec.rb +101 -0
  177. data/spec/hybrid_platforms_conductor_test/api/services_handler/package_spec.rb +388 -0
  178. data/spec/hybrid_platforms_conductor_test/api/services_handler/parse_deploy_output_spec.rb +274 -0
  179. data/spec/hybrid_platforms_conductor_test/api/services_handler/prepare_for_deploy_spec.rb +264 -0
  180. data/spec/hybrid_platforms_conductor_test/api/tests_runner/common_spec.rb +194 -0
  181. data/spec/hybrid_platforms_conductor_test/api/tests_runner/global_spec.rb +37 -0
  182. data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_check_spec.rb +194 -0
  183. data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_spec.rb +137 -0
  184. data/spec/hybrid_platforms_conductor_test/api/tests_runner/node_ssh_spec.rb +257 -0
  185. data/spec/hybrid_platforms_conductor_test/api/tests_runner/platform_spec.rb +110 -0
  186. data/spec/hybrid_platforms_conductor_test/api/tests_runner/reports_spec.rb +367 -0
  187. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +111 -0
  188. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_reports_plugins/confluence_spec.rb +29 -0
  189. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb.rb +166 -0
  190. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb2.rb +93 -0
  191. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb_others.rb +60 -0
  192. data/spec/hybrid_platforms_conductor_test/cmdb_plugins/test_cmdb_others2.rb +58 -0
  193. data/spec/hybrid_platforms_conductor_test/executables/check-node_spec.rb +35 -0
  194. data/spec/hybrid_platforms_conductor_test/executables/deploy_spec.rb +35 -0
  195. data/spec/hybrid_platforms_conductor_test/executables/get_impacted_nodes_spec.rb +158 -0
  196. data/spec/hybrid_platforms_conductor_test/executables/last_deploys_spec.rb +173 -0
  197. data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +283 -0
  198. data/spec/hybrid_platforms_conductor_test/executables/options/actions_executor_spec.rb +28 -0
  199. data/spec/hybrid_platforms_conductor_test/executables/options/cmd_runner_spec.rb +28 -0
  200. data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +67 -0
  201. data/spec/hybrid_platforms_conductor_test/executables/options/deployer_spec.rb +251 -0
  202. data/spec/hybrid_platforms_conductor_test/executables/options/nodes_handler_spec.rb +111 -0
  203. data/spec/hybrid_platforms_conductor_test/executables/options/nodes_selectors_spec.rb +71 -0
  204. data/spec/hybrid_platforms_conductor_test/executables/options/reports_handler_spec.rb +54 -0
  205. data/spec/hybrid_platforms_conductor_test/executables/options/tests_runner_spec.rb +139 -0
  206. data/spec/hybrid_platforms_conductor_test/executables/report_spec.rb +60 -0
  207. data/spec/hybrid_platforms_conductor_test/executables/run_spec.rb +173 -0
  208. data/spec/hybrid_platforms_conductor_test/executables/ssh_config_spec.rb +35 -0
  209. data/spec/hybrid_platforms_conductor_test/executables/test_spec.rb +41 -0
  210. data/spec/hybrid_platforms_conductor_test/helpers/actions_executor_helpers.rb +98 -0
  211. data/spec/hybrid_platforms_conductor_test/helpers/cmd_runner_helpers.rb +92 -0
  212. data/spec/hybrid_platforms_conductor_test/helpers/cmdb_helpers.rb +37 -0
  213. data/spec/hybrid_platforms_conductor_test/helpers/config_helpers.rb +20 -0
  214. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +130 -0
  215. data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +149 -0
  216. data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +812 -0
  217. data/spec/hybrid_platforms_conductor_test/helpers/executables_helpers.rb +96 -0
  218. data/spec/hybrid_platforms_conductor_test/helpers/nodes_handler_helpers.rb +20 -0
  219. data/spec/hybrid_platforms_conductor_test/helpers/platform_handler_helpers.rb +35 -0
  220. data/spec/hybrid_platforms_conductor_test/helpers/platforms_handler_helpers.rb +127 -0
  221. data/spec/hybrid_platforms_conductor_test/helpers/plugins_helpers.rb +48 -0
  222. data/spec/hybrid_platforms_conductor_test/helpers/provisioner_proxmox_helpers.rb +789 -0
  223. data/spec/hybrid_platforms_conductor_test/helpers/reports_handler_helpers.rb +29 -0
  224. data/spec/hybrid_platforms_conductor_test/helpers/services_handler_helpers.rb +20 -0
  225. data/spec/hybrid_platforms_conductor_test/helpers/tests_runner_helpers.rb +38 -0
  226. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem/hpc_plugins/test_plugin_type/test_plugin_id1.rb +22 -0
  227. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem/hpc_plugins/test_plugin_type/test_plugin_id2.rb +22 -0
  228. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem2/sub_dir/hpc_plugins/test_plugin_type/test_plugin_id3.rb +26 -0
  229. data/spec/hybrid_platforms_conductor_test/mocked_lib/my_test_gem2/sub_dir/hpc_plugins/test_plugin_type2/test_plugin_id4.rb +26 -0
  230. data/spec/hybrid_platforms_conductor_test/platform_handler_plugins/test.rb +225 -0
  231. data/spec/hybrid_platforms_conductor_test/platform_handler_plugins/test2.rb +11 -0
  232. data/spec/hybrid_platforms_conductor_test/report_plugin.rb +35 -0
  233. data/spec/hybrid_platforms_conductor_test/test_action.rb +66 -0
  234. data/spec/hybrid_platforms_conductor_test/test_connector.rb +151 -0
  235. data/spec/hybrid_platforms_conductor_test/test_plugins/global.rb +30 -0
  236. data/spec/hybrid_platforms_conductor_test/test_plugins/node.rb +53 -0
  237. data/spec/hybrid_platforms_conductor_test/test_plugins/node_check.rb +47 -0
  238. data/spec/hybrid_platforms_conductor_test/test_plugins/node_ssh.rb +42 -0
  239. data/spec/hybrid_platforms_conductor_test/test_plugins/platform.rb +50 -0
  240. data/spec/hybrid_platforms_conductor_test/test_plugins/several_checks.rb +50 -0
  241. data/spec/hybrid_platforms_conductor_test/test_provisioner.rb +95 -0
  242. data/spec/hybrid_platforms_conductor_test/tests_report_plugin.rb +49 -0
  243. data/spec/spec_helper.rb +111 -0
  244. metadata +566 -0
@@ -0,0 +1,319 @@
1
+ require 'colorize'
2
+ require 'logger'
3
+ require 'ruby-progressbar'
4
+
5
+ module HybridPlatformsConductor
6
+
7
+ # Gives easy logging methods to any class including this module, such as log_error, log_debug...
8
+ # Also define methods for UI (meaning text that should be displayed as interface, and not as logging).
9
+ module LoggerHelpers
10
+
11
+ # Small custom log device that can use a progress bar currently in use.
12
+ class ProgressBarLogDevice
13
+
14
+ # Constructor
15
+ #
16
+ # Parameters::
17
+ # * *progress_bar* (ProgressBar): The progress bar to be used for logging
18
+ # * *stream* (IO): Stream to be used for logging (like $stdout...)
19
+ def initialize(progress_bar, stream)
20
+ @progress_bar = progress_bar
21
+ @stream = stream
22
+ # Store the current line in case it wasn't finished by \n
23
+ @current_line = nil
24
+ end
25
+
26
+ # Write a message
27
+ #
28
+ # Parameters::
29
+ # * *msg* (String): Message to log
30
+ def write(msg)
31
+ if msg.end_with?("\n")
32
+ @progress_bar.clear
33
+ if @current_line.nil?
34
+ # New messages to be displayed
35
+ @stream << msg
36
+ else
37
+ # Ending previous line
38
+ @stream << (@current_line + msg)
39
+ @current_line = nil
40
+ end
41
+ @progress_bar.refresh(force: true) unless @progress_bar.stopped?
42
+ elsif @current_line.nil?
43
+ # Beginning new line
44
+ @current_line = msg
45
+ else
46
+ # Continuing current line
47
+ @current_line << msg
48
+ end
49
+ end
50
+
51
+ # Close the log device
52
+ # This method is needed for Ruby loggers to detect this class as a log device.
53
+ def close
54
+ end
55
+
56
+ # Make sure if the current line is not flushed we still do it
57
+ def flush
58
+ unless @current_line.nil?
59
+ @stream << @current_line
60
+ @current_line = nil
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ class << self
67
+ attr_reader :progress_bar_semaphore
68
+ end
69
+ # Make sure the progress bar setting is protected by a Mutex
70
+ @progress_bar_semaphore = Mutex.new
71
+
72
+ # Sorted list of levels and their corresponding modifiers.
73
+ LEVELS_MODIFIERS = {
74
+ fatal: [:red, :bold],
75
+ error: [:red, :bold],
76
+ warn: [:yellow, :bold],
77
+ info: [:white],
78
+ debug: [:white],
79
+ unknown: [:white]
80
+ }
81
+
82
+ # List of levels that will output on stderr
83
+ LEVELS_TO_STDERR = %i[warn error fatal]
84
+
85
+ LEVELS_MODIFIERS.keys.each do |level|
86
+ define_method("log_#{level}") do |message|
87
+ (LEVELS_TO_STDERR.include?(level) ? @logger_stderr : @logger).send(
88
+ level,
89
+ defined?(@log_component) ? @log_component : self.class.name.split('::').last
90
+ ) { message }
91
+ end
92
+ end
93
+
94
+ # Initialize loggers
95
+ #
96
+ # Parameters::
97
+ # * *logger* (Logger): Logger used for stdout
98
+ # * *logger_stderr* (Logger): Logger used for stderr
99
+ def init_loggers(logger, logger_stderr)
100
+ @logger = logger
101
+ @logger_stderr = logger_stderr
102
+ set_loggers_format
103
+ end
104
+
105
+ # Set loggers to the desired format
106
+ def set_loggers_format
107
+ [@logger, @logger_stderr].each do |logger|
108
+ logger.formatter = proc do |severity, datetime, progname, msg|
109
+ # If the message already has control characters, don't colorize it
110
+ keep_original_color = msg.include? "\u001b"
111
+ message = "[#{Time.now.utc.strftime('%F %T')} (PID #{$$} / TID #{Thread.current.object_id})] #{severity.rjust(5)} - [ #{progname} ] - "
112
+ message << "#{msg}\n" unless keep_original_color
113
+ LEVELS_MODIFIERS[severity.downcase.to_sym].each do |modifier|
114
+ message = message.send(modifier)
115
+ end
116
+ message << "#{msg}\n" if keep_original_color
117
+ message
118
+ end
119
+ end
120
+ end
121
+
122
+ # Set log level
123
+ #
124
+ # Parameters::
125
+ # * *level* (Symbol): Log level (used directly with the logger)
126
+ def log_level=(level)
127
+ @logger.level = level
128
+ end
129
+
130
+ # Are we in debug level?
131
+ #
132
+ # Result::
133
+ # * Boolean: Are we in debug level?
134
+ def log_debug?
135
+ @logger.debug?
136
+ end
137
+
138
+ # Set the logging component name, to be prepend in any log message, or nil if none.
139
+ # By default the component is the class name.
140
+ #
141
+ # Parameters::
142
+ # * *component* (String or nil): Logging component, or nil for none
143
+ def log_component=(component)
144
+ @log_component = component
145
+ end
146
+
147
+ # Print a string to the command-line UI.
148
+ # This is different from logging because this is the UI of the CLI.
149
+ # Use sections indentation for better clarity.
150
+ #
151
+ # Parameters::
152
+ # * *message* (String): Message to be printed out [default = '']
153
+ def out(message = '')
154
+ @out_sections = [] unless defined?(@out_sections)
155
+ message = "#{' ' * @out_sections.size}#{message}"
156
+ # log_debug "<Output> - #{message}"
157
+ message = "#{message}\n" unless message.end_with?("\n")
158
+ @logger << message
159
+ end
160
+
161
+ # Print an error string to the command-line UI.
162
+ # This is different from logging because this is the UI of the CLI.
163
+ # Use sections indentation for better clarity.
164
+ #
165
+ # Parameters::
166
+ # * *message* (String): Message to be printed out [default = '']
167
+ def err(message = '')
168
+ @out_sections = [] unless defined?(@out_sections)
169
+ message = "#{' ' * @out_sections.size}#{message}"
170
+ # log_debug "<Output> - #{message}"
171
+ message = "#{message}\n" unless message.end_with?("\n")
172
+ @logger_stderr << message
173
+ end
174
+
175
+ # Display a new section in the UI, used to group a set of operations
176
+ #
177
+ # Parameters::
178
+ # * *name* (String): Section name
179
+ # * Proc: Code called in the section
180
+ def section(name)
181
+ out "===== #{name} ==== Begin..."
182
+ @out_sections = [] unless defined?(@out_sections)
183
+ @out_sections << name
184
+ begin
185
+ yield
186
+ ensure
187
+ @out_sections.pop
188
+ out "===== #{name} ==== ...End"
189
+ out
190
+ end
191
+ end
192
+
193
+ # Get the stdout device
194
+ #
195
+ # Result::
196
+ # * Object: The stdout log device
197
+ def stdout_device
198
+ # TODO: Find a more elegant way to access the internal log device
199
+ @logger.instance_variable_get(:@logdev).dev
200
+ end
201
+
202
+ # Set the stdout device
203
+ #
204
+ # Parameters::
205
+ # * *log_device* (Object): The stdout log device to set
206
+ def stdout_device=(log_device)
207
+ # TODO: Find a more elegant way to access the internal log device
208
+ @logger.instance_variable_get(:@logdev).send(:set_dev, log_device)
209
+ end
210
+
211
+ # Get the stderr device
212
+ #
213
+ # Result::
214
+ # * IO or String: The stdout IO or file name
215
+ def stderr_device
216
+ # TODO: Find a more elegant way to access the internal log device
217
+ @logger_stderr.instance_variable_get(:@logdev).dev
218
+ end
219
+
220
+ # Set the stderr device
221
+ #
222
+ # Parameters::
223
+ # * *log_device* (Object): The stdout log device to set
224
+ def stderr_device=(log_device)
225
+ # TODO: Find a more elegant way to access the internal log device
226
+ @logger_stderr.instance_variable_get(:@logdev).send(:set_dev, log_device)
227
+ end
228
+
229
+ # Is stdout really getting to the terminal display?
230
+ #
231
+ # Result::
232
+ # * Boolean: Is stdout really getting to the terminal stdout?
233
+ def stdout_displayed?
234
+ log_device = stdout_device
235
+ log_device == $stdout || log_device == $stderr || log_device.is_a?(ProgressBarLogDevice)
236
+ end
237
+
238
+ # Is stderr really getting to the terminal display?
239
+ #
240
+ # Result::
241
+ # * Boolean: Is stderr really getting to the terminal stdout?
242
+ def stderr_displayed?
243
+ log_device = stderr_device
244
+ log_device == $stderr || log_device == $stdout || log_device.is_a?(ProgressBarLogDevice)
245
+ end
246
+
247
+ # Create a UI progress bar and call some code with it.
248
+ # Ensure logging done with the progress bar does not conflict in stdout.
249
+ #
250
+ # Parameters::
251
+ # * *nbr_total* (Integer): Total value of the progress bar
252
+ # * *name* (String or nil): Name to put on the progress bar, or nil for no name [default: nil]
253
+ # * Proc: Code block called with the progress bar
254
+ # * Parameters::
255
+ # * *progress_bar* (ProgressBar): The progress bar
256
+ def with_progress_bar(nbr_total, name: nil)
257
+ previous_stdout_device = nil
258
+ previous_stderr_device = nil
259
+ progress_bar = nil
260
+ LoggerHelpers.progress_bar_semaphore.synchronize do
261
+ stdout_log_device = stdout_device
262
+ progress_bar = ProgressBar.create(
263
+ output: stdout_log_device.is_a?(ProgressBarLogDevice) ? $stdout : stdout_log_device,
264
+ title: 'Initializing...',
265
+ total: nbr_total,
266
+ format: "#{name ? "#{name} " : ''}[%j%%] - |%bC%i| - [ %t ]",
267
+ progress_mark: ' ',
268
+ remainder_mark: '-'
269
+ )
270
+ if stdout_displayed? && !stdout_log_device.is_a?(ProgressBarLogDevice)
271
+ # Change the current logger so that when its logdev calls write it redirects to our ProgressBar#log
272
+ previous_stdout_device = stdout_device
273
+ self.stdout_device = ProgressBarLogDevice.new(progress_bar, previous_stdout_device)
274
+ end
275
+ if stderr_displayed? && !stderr_device.is_a?(ProgressBarLogDevice)
276
+ # Change the current logger so that when its logdev calls write it redirects to our ProgressBar#log
277
+ previous_stderr_device = stderr_device
278
+ self.stderr_device = ProgressBarLogDevice.new(progress_bar, previous_stderr_device)
279
+ end
280
+ end
281
+ begin
282
+ yield progress_bar
283
+ ensure
284
+ LoggerHelpers.progress_bar_semaphore.synchronize do
285
+ self.stdout_device.flush
286
+ self.stderr_device.flush
287
+ self.stdout_device = previous_stdout_device unless previous_stdout_device.nil?
288
+ self.stderr_device = previous_stderr_device unless previous_stderr_device.nil?
289
+ end
290
+ end
291
+ end
292
+
293
+ # Return a string describing the stdout and stderr if they were logged into files or StringIO.
294
+ # Useful for debugging.
295
+ #
296
+ # Result::
297
+ # * String: The corresponding stdout and stderr info, or nil if none
298
+ def stdouts_to_s
299
+ messages = []
300
+ {
301
+ 'STDOUT' => self.stdout_device,
302
+ 'STDERR' => self.stderr_device
303
+ }.each do |name, device|
304
+ if device.is_a?(File)
305
+ if File.exist?(device.path)
306
+ content = File.read(device.path).strip
307
+ messages << "----- #{name} BEGIN - #{device.path} -----\n#{content}\n----- #{name} END - #{device.path} -----" unless content.empty?
308
+ end
309
+ elsif device.is_a?(StringIO)
310
+ content = device.string
311
+ messages << "----- #{name} BEGIN -----\n#{content}\n----- #{name} END -----" unless content.empty?
312
+ end
313
+ end
314
+ messages.empty? ? nil : messages.join("\n")
315
+ end
316
+
317
+ end
318
+
319
+ end
@@ -0,0 +1,76 @@
1
+ #!/bin/bash
2
+ # Grzegorz Wierzowiecki
3
+ # "mutex_dir.sh"
4
+ # GNU GPL or BSD - as you like
5
+ # Script inspiered by:
6
+ # http://www.bash-hackers.org/wiki/doku.php/howto/mutex
7
+
8
+ function print_help(){
9
+ cat << HELP_INFO
10
+ Usage:
11
+ $0 lock lock-dir calling_app_pid
12
+ $0 unlock lock-dir
13
+ Return:
14
+ 0 - on success
15
+ 1 - on general fail
16
+ 2 - when locking failed
17
+ 3 - when received signal
18
+ HELP_INFO
19
+ exit
20
+ }
21
+
22
+ # lock dirs/files
23
+ lock_dir="$2"
24
+ app_pid="$3"
25
+ pid_file="${lock_dir}/pid"
26
+ user_file="${lock_dir}/user"
27
+
28
+ # exit codes
29
+ ENO_SUCCESS=0
30
+ ENO_GENERAL=1
31
+ ENO_LOCKFAIL=2
32
+ ENO_RECVSIG=3
33
+
34
+ function lock(){
35
+ if ! kill -0 $app_pid &>/dev/null; then
36
+ echo 'Calling app pid (='$app_pid')is not responding.'
37
+ return 1
38
+ fi
39
+ local lock_dir="$1"
40
+ if mkdir "${lock_dir}" &>/dev/null; then
41
+ # lock succeeded, store the PID
42
+ echo "$app_pid" >"${pid_file}"
43
+ # Dump the user having this lock in another file
44
+ echo "${SUDO_USER}" >"${user_file}"
45
+ return ${ENO_SUCCESS}
46
+ else
47
+ # lock failed, now check if the other PID is alive
48
+ other_pid="$(cat "${pid_file}" 2>/dev/null)"
49
+ if [ $? != 0 ]; then
50
+ # Pid file does not exists - propably directory is beeing deleted
51
+ return ${ENO_LOCKFAIL}
52
+ fi
53
+ if ! kill -0 $other_pid &>/dev/null; then
54
+ # lock is stale, remove it and restart
55
+ unlock "$lock_dir"
56
+ lock "$lock_dir"
57
+ return $?
58
+ else
59
+ # lock is valid and OTHERPID is active - exit, we're locked!
60
+ #echo "lock failed, PID ${OTHERPID} is active" >&2
61
+ return ${ENO_LOCKFAIL}
62
+ fi
63
+ fi
64
+ return 0
65
+ }
66
+
67
+ function unlock(){
68
+ rm -r "$1" &>/dev/null
69
+ return $?
70
+ }
71
+
72
+ case "$1" in
73
+ lock) lock "$lock_dir" ; exit $? ;;
74
+ unlock) unlock "$lock_dir"; exit $? ;;
75
+ *) print_help ;;
76
+ esac
@@ -0,0 +1,597 @@
1
+ require 'hybrid_platforms_conductor/cmd_runner'
2
+ require 'hybrid_platforms_conductor/cmdb'
3
+ require 'hybrid_platforms_conductor/logger_helpers'
4
+ require 'hybrid_platforms_conductor/parallel_threads'
5
+ require 'hybrid_platforms_conductor/platform_handler'
6
+
7
+ module HybridPlatformsConductor
8
+
9
+ # API to get information on our inventory: nodes and their metadata
10
+ class NodesHandler
11
+
12
+ # Extend the Config DSL
13
+ module ConfigDSLExtension
14
+
15
+ # List of CMDB masters. Each info has the following properties:
16
+ # * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule.
17
+ # * *cmdb_masters* (Hash< Symbol, Array<Symbol> >): List of metadata properties per CMDB name considered as master for those properties.
18
+ # Array< Hash<Symbol, Object> >
19
+ attr_reader :cmdb_masters
20
+
21
+ # Mixin initializer
22
+ def init_nodes_handler_config
23
+ @cmdb_masters = []
24
+ end
25
+
26
+ # Set CMDB masters
27
+ #
28
+ # Parameters::
29
+ # * *master_cmdbs_info* (Hash< Symbol, Symbol or Array<Symbol> >): List of metadata properties (or single one) per CMDB name considered as master for those properties.
30
+ def master_cmdbs(master_cmdbs_info)
31
+ @cmdb_masters << {
32
+ cmdb_masters: Hash[master_cmdbs_info.map { |cmdb, properties| [cmdb, properties.is_a?(Array) ? properties : [properties]] }],
33
+ nodes_selectors_stack: current_nodes_selectors_stack
34
+ }
35
+ end
36
+
37
+ end
38
+
39
+ Config.extend_config_dsl_with ConfigDSLExtension, :init_nodes_handler_config
40
+
41
+ include LoggerHelpers, ParallelThreads
42
+
43
+ # Constructor
44
+ #
45
+ # Parameters::
46
+ # * *logger* (Logger): Logger to be used [default: Logger.new(STDOUT)]
47
+ # * *logger_stderr* (Logger): Logger to be used for stderr [default: Logger.new(STDERR)]
48
+ # * *config* (Config): Config to be used. [default: Config.new]
49
+ # * *cmd_runner* (CmdRunner): Command executor to be used. [default: CmdRunner.new]
50
+ # * *platforms_handler* (PlatformsHandler): Platforms Handler to be used. [default: PlatformsHandler.new]
51
+ def initialize(
52
+ logger: Logger.new(STDOUT),
53
+ logger_stderr: Logger.new(STDERR),
54
+ config: Config.new,
55
+ cmd_runner: CmdRunner.new,
56
+ platforms_handler: PlatformsHandler.new
57
+ )
58
+ init_loggers(logger, logger_stderr)
59
+ @config = config
60
+ @cmd_runner = cmd_runner
61
+ @platforms_handler = platforms_handler
62
+ # List of platform handler per known node
63
+ # Hash<String, PlatformHandler>
64
+ @nodes_platform = {}
65
+ # List of platform handler per known nodes list
66
+ # Hash<String, PlatformHandler>
67
+ @nodes_list_platform = {}
68
+ # List of CMDBs getting a property, per property name
69
+ # Hash<Symbol, Array<Cmdb> >
70
+ @cmdbs_per_property = {}
71
+ # List of CMDBs having the get_others method
72
+ # Array< Cmdb >
73
+ @cmdbs_others = []
74
+ @cmdbs = Plugins.new(
75
+ :cmdb,
76
+ logger: @logger,
77
+ logger_stderr: @logger_stderr,
78
+ init_plugin: proc do |plugin_class|
79
+ cmdb = plugin_class.new(
80
+ logger: @logger,
81
+ logger_stderr: @logger_stderr,
82
+ config: @config,
83
+ cmd_runner: @cmd_runner,
84
+ platforms_handler: @platforms_handler,
85
+ nodes_handler: self
86
+ )
87
+ @cmdbs_others << cmdb if cmdb.respond_to?(:get_others)
88
+ cmdb.methods.each do |method|
89
+ if method.to_s =~ /^get_(.*)$/
90
+ property = $1.to_sym
91
+ @cmdbs_per_property[property] = [] unless @cmdbs_per_property.key?(property)
92
+ @cmdbs_per_property[property] << cmdb
93
+ end
94
+ end
95
+ cmdb
96
+ end
97
+ )
98
+ # Cache of metadata per node
99
+ # Hash<String, Hash<Symbol, Object> >
100
+ @metadata = {}
101
+ # The metadata update is protected by a mutex to make it thread-safe
102
+ @metadata_mutex = Mutex.new
103
+ # Cache of CMDB masters, per property, per node
104
+ # Hash< String, Hash< Symbol, Cmdb > >
105
+ @cmdb_masters_cache = {}
106
+ # Read all platforms from the config
107
+ @platforms_handler.known_platforms.each do |platform|
108
+ # Register all known nodes for this platform
109
+ platform.known_nodes.each do |node|
110
+ raise "Can't register #{node} to platform #{platform.repository_path}, as it is already defined in platform #{@nodes_platform[node].repository_path}." if @nodes_platform.key?(node)
111
+ @nodes_platform[node] = platform
112
+ end
113
+ # Register all known nodes lists
114
+ platform.known_nodes_lists.each do |nodes_list|
115
+ raise "Can't register nodes list #{nodes_list} to platform #{platform.repository_path}, as it is already defined in platform #{@nodes_list_platform[nodes_list].repository_path}." if @nodes_list_platform.key?(nodes_list)
116
+ @nodes_list_platform[nodes_list] = platform
117
+ end if platform.respond_to?(:known_nodes_lists)
118
+ end
119
+ end
120
+
121
+ # Complete an option parser with options meant to control this Nodes Handler
122
+ #
123
+ # Parameters::
124
+ # * *options_parser* (OptionParser): The option parser to complete
125
+ def options_parse(options_parser, parallel: true)
126
+ options_parser.separator ''
127
+ options_parser.separator 'Nodes handler options:'
128
+ options_parser.on('-o', '--show-nodes', 'Display the list of possible nodes and exit') do
129
+ out "* Known platforms:\n#{
130
+ @platforms_handler.known_platforms.map do |platform|
131
+ "#{platform.name} - Type: #{platform.platform_type} - Location: #{platform.repository_path}"
132
+ end.sort.join("\n")
133
+ }"
134
+ out
135
+ out "* Known nodes lists:\n#{known_nodes_lists.sort.join("\n")}"
136
+ out
137
+ out "* Known services:\n#{known_services.sort.join("\n")}"
138
+ out
139
+ out "* Known nodes:\n#{known_nodes.sort.join("\n")}"
140
+ out
141
+ out "* Known nodes with description:\n#{
142
+ prefetch_metadata_of known_nodes, %i[hostname host_ip private_ips services description]
143
+ known_nodes.map do |node|
144
+ "#{node} (#{
145
+ if get_hostname_of node
146
+ get_hostname_of node
147
+ elsif get_host_ip_of node
148
+ get_host_ip_of node
149
+ elsif get_private_ips_of node
150
+ get_private_ips_of(node).first
151
+ else
152
+ 'No connection'
153
+ end
154
+ }) - #{(get_services_of(node) || []).join(', ')} - #{get_description_of(node) || ''}"
155
+ end.sort.join("\n")
156
+ }"
157
+ out
158
+ exit 0
159
+ end
160
+ end
161
+
162
+ # Complete an option parser with ways to select nodes in parameters
163
+ #
164
+ # Parameters::
165
+ # * *options_parser* (OptionParser): The option parser to complete
166
+ # * *nodes_selectors* (Array): The list of nodes selectors that will be populated by parsing the options
167
+ def options_parse_nodes_selectors(options_parser, nodes_selectors)
168
+ platform_names = @platforms_handler.known_platforms.map(&:name).sort
169
+ options_parser.separator ''
170
+ options_parser.separator 'Nodes selection options:'
171
+ options_parser.on('-a', '--all-nodes', 'Select all nodes') do
172
+ nodes_selectors << { all: true }
173
+ end
174
+ options_parser.on('-b', '--nodes-platform PLATFORM', "Select nodes belonging to a given platform name. Available platforms are: #{platform_names.join(', ')} (can be used several times)") do |platform|
175
+ nodes_selectors << { platform: platform }
176
+ end
177
+ options_parser.on('-l', '--nodes-list LIST', 'Select nodes defined in a nodes list (can be used several times)') do |nodes_list|
178
+ nodes_selectors << { list: nodes_list }
179
+ end
180
+ options_parser.on('-n', '--node NODE', 'Select a specific node. Can be a regular expression to select several nodes if used with enclosing "/" characters. (can be used several times).') do |node|
181
+ nodes_selectors << node
182
+ end
183
+ options_parser.on('-r', '--nodes-service SERVICE', 'Select nodes implementing a given service (can be used several times)') do |service|
184
+ nodes_selectors << { service: service }
185
+ end
186
+ options_parser.on(
187
+ '--nodes-git-impact GIT_IMPACT',
188
+ 'Select nodes impacted by a git diff from a platform (can be used several times).',
189
+ 'GIT_IMPACT has the format PLATFORM:FROM_COMMIT:TO_COMMIT:FLAGS',
190
+ "* PLATFORM: Name of the platform to check git diff from. Available platforms are: #{platform_names.join(', ')}",
191
+ '* FROM_COMMIT: Commit ID or refspec from which we perform the diff. If ommitted, defaults to master',
192
+ '* TO_COMMIT: Commit ID ot refspec to which we perform the diff. If ommitted, defaults to the currently checked-out files',
193
+ '* FLAGS: Extra comma-separated flags. The following flags are supported:',
194
+ ' - min: If specified then each impacted service will select only 1 node implementing this service. If not specified then all nodes implementing the impacted services will be selected.'
195
+ ) do |nodes_git_impact|
196
+ platform_name, from_commit, to_commit, flags = nodes_git_impact.split(':')
197
+ flags = (flags || '').split(',')
198
+ raise "Invalid platform in --nodes-git-impact: #{platform_name}. Possible values are: #{platform_names.join(', ')}." unless platform_names.include?(platform_name)
199
+ nodes_selector = { platform: platform_name }
200
+ nodes_selector[:from_commit] = from_commit if from_commit && !from_commit.empty?
201
+ nodes_selector[:to_commit] = to_commit if to_commit && !to_commit.empty?
202
+ nodes_selector[:smallest_set] = true if flags.include?('min')
203
+ nodes_selectors << { git_diff: nodes_selector }
204
+ end
205
+ end
206
+
207
+ # Get the list of known nodes
208
+ #
209
+ # Result::
210
+ # * Array<String>: List of nodes
211
+ def known_nodes
212
+ @nodes_platform.keys
213
+ end
214
+
215
+ # Get the list of known nodes lists
216
+ #
217
+ # Result::
218
+ # * Array<String>: List of nodes lists' names
219
+ def known_nodes_lists
220
+ @nodes_list_platform.keys
221
+ end
222
+
223
+ # Get the list of nodes (resolved) belonging to a nodes list
224
+ #
225
+ # Parameters::
226
+ # * *nodes_list* (String): Nodes list name
227
+ # * *ignore_unknowns* (Boolean): Do we ignore unknown nodes? [default = false]
228
+ # Result::
229
+ # * Array<String>: List of nodes
230
+ def nodes_from_list(nodes_list, ignore_unknowns: false)
231
+ select_nodes(@nodes_list_platform[nodes_list].nodes_selectors_from_nodes_list(nodes_list), ignore_unknowns: ignore_unknowns)
232
+ end
233
+
234
+ # Get the list of known service names
235
+ #
236
+ # Result::
237
+ # * Array<String>: List of service names
238
+ def known_services
239
+ prefetch_metadata_of known_nodes, :services
240
+ known_nodes.map { |node| get_services_of node }.flatten.compact.uniq.sort
241
+ end
242
+
243
+ # Get a metadata property for a given node
244
+ #
245
+ # Parameters::
246
+ # * *node* (String): Node
247
+ # * *property* (Symbol): The property name
248
+ # Result::
249
+ # * Object or nil: The node's metadata value for this property, or nil if none
250
+ def metadata_of(node, property)
251
+ prefetch_metadata_of([node], property) unless @metadata.key?(node) && @metadata[node].key?(property)
252
+ @metadata[node][property]
253
+ end
254
+
255
+ # Override a metadata property for a given node
256
+ #
257
+ # Parameters::
258
+ # * *node* (String): Node
259
+ # * *property* (Symbol): The property name
260
+ # * *value* (Object): The property value
261
+ def override_metadata_of(node, property, value)
262
+ @metadata_mutex.synchronize do
263
+ @metadata[node] = {} unless @metadata.key?(node)
264
+ @metadata[node][property] = value
265
+ end
266
+ end
267
+
268
+ # Invalidate a metadata property for a given node
269
+ #
270
+ # Parameters::
271
+ # * *node* (String): Node
272
+ # * *property* (Symbol): The property name
273
+ def invalidate_metadata_of(node, property)
274
+ @metadata_mutex.synchronize do
275
+ @metadata[node].delete(property) if @metadata.key?(node)
276
+ end
277
+ end
278
+
279
+ # Define a method to get a metadata property of a node.
280
+ # This is like a factory of method shortcuts for properties.
281
+ # The method will be named get_<property>_of.
282
+ # This way instead of calling
283
+ # metadata_of node, :host_ip
284
+ # we can call
285
+ # get_host_ip_of node
286
+ # Readability wins :D
287
+ #
288
+ # Parameters::
289
+ # * *property* (Symbol): The property name
290
+ def define_property_method_for(property)
291
+ define_singleton_method("get_#{property}_of".to_sym) { |node| metadata_of(node, property) }
292
+ end
293
+
294
+ # Accept any method of name get_<property>_of to get the metadata property of a given node.
295
+ # Here is the magic of accepting method names that are not statically defined.
296
+ #
297
+ # Parameters::
298
+ # * *method* (Symbol): The missing method name
299
+ # * *args* (Array<Object>): Arguments given to the call
300
+ # * *block* (Proc): Code block given to the call
301
+ def method_missing(method, *args, &block)
302
+ if method.to_s =~ /^get_(.*)_of$/
303
+ property = $1.to_sym
304
+ # Define the method so that we don't go trough method_missing next time (more efficient).
305
+ define_property_method_for(property)
306
+ # Then call it
307
+ send("get_#{property}_of".to_sym, *args, &block)
308
+ else
309
+ # We really don't know this method.
310
+ # Call original implementation of method_missing that will raise an exception.
311
+ super
312
+ end
313
+ end
314
+
315
+ # Prefetch some metadata properties for a given list of nodes.
316
+ # Useful for performance reasons when clients know they will need to use a lot of properties on nodes.
317
+ # Keep a thread-safe memory cache of it.
318
+ #
319
+ # Parameters::
320
+ # * *nodes* (Array<String>): Nodes to read metadata for
321
+ # * *properties* (Symbol or Array<Symbol>): Metadata properties (or single one) to read
322
+ def prefetch_metadata_of(nodes, properties)
323
+ (properties.is_a?(Symbol) ? [properties] : properties).each do |property|
324
+ # Gather the list of nodes missing this property
325
+ missing_nodes = nodes.select { |node| !@metadata.key?(node) || !@metadata[node].key?(property) }
326
+ unless missing_nodes.empty?
327
+ # Query the CMDBs having first the get_<property> method, then the ones having the get_others method till we have our property set for all missing nodes
328
+ # Metadata being retrieved by the different CMDBs, per node
329
+ # Hash< String, Object >
330
+ updated_metadata = {}
331
+ (
332
+ (@cmdbs_per_property.key?(property) ? @cmdbs_per_property[property] : []).map { |cmdb| [cmdb, property] } +
333
+ @cmdbs_others.map { |cmdb| [cmdb, :others] }
334
+ ).each do |(cmdb, cmdb_property)|
335
+ # If among the missing nodes some of them have some master CMDB declared for this property, filter them out unless we are dealing with their master CMDB.
336
+ nodes_to_query = missing_nodes.select do |node|
337
+ master_cmdb = cmdb_master_for(node, property)
338
+ master_cmdb.nil? || master_cmdb == cmdb
339
+ end
340
+ unless nodes_to_query.empty?
341
+ # Check first if this property depends on other ones for this cmdb
342
+ if cmdb.respond_to?(:property_dependencies)
343
+ property_deps = cmdb.property_dependencies
344
+ prefetch_metadata_of nodes_to_query, property_deps[property] if property_deps.key?(property)
345
+ end
346
+ # Property values, per node name
347
+ # Hash< String, Object >
348
+ metadata_from_cmdb = Hash[
349
+ cmdb.send("get_#{cmdb_property}".to_sym, nodes_to_query, @metadata.slice(*nodes_to_query)).map do |node, cmdb_result|
350
+ [node, cmdb_property == :others ? cmdb_result[property] : cmdb_result]
351
+ end
352
+ ].compact
353
+ cmdb_log_header = "[CMDB #{cmdb.class.name.split('::').last}.#{cmdb_property}] -"
354
+ log_debug "#{cmdb_log_header} Query property #{property} for #{nodes_to_query.size} nodes (#{nodes_to_query[0..7].join(', ')}...) => Found metadata for #{metadata_from_cmdb.size} nodes."
355
+ updated_metadata.merge!(metadata_from_cmdb) do |node, existing_value, new_value|
356
+ raise "#{cmdb_log_header} Returned a conflicting value for metadata #{property} of node #{node}: #{new_value} whereas the value was already set to #{existing_value}" if !existing_value.nil? && new_value != existing_value
357
+ new_value
358
+ end
359
+ end
360
+ end
361
+ # Avoid conflicts in metadata while merging and make sure this update is thread-safe
362
+ # As @metadata is only appending data and never deleting it, protecting the update only is enough.
363
+ # At worst several threads will query several times the same CMDBs to update the same data several times.
364
+ # If we also want to be thread-safe in this regard, we should protect the whole CMDB call with mutexes, at the granularity of the node + property bein read.
365
+ @metadata_mutex.synchronize do
366
+ missing_nodes.each do |node|
367
+ @metadata[node] = {} unless @metadata.key?(node)
368
+ # Here, explicitely store nil if nothing has been found for a node because we know there is no value to be fetched.
369
+ # This way we won't query again all CMDBs thanks to the cache.
370
+ @metadata[node][property] = updated_metadata[node]
371
+ end
372
+ end
373
+ end
374
+ end
375
+ end
376
+
377
+ # Resolve a list of nodes selectors into a real list of known nodes.
378
+ # A node selector can be:
379
+ # * String: Node name, or a node regexp if enclosed within '/' character (ex: '/.+worker.+/')
380
+ # * Hash<Symbol,Object>: More complete information that can contain the following keys:
381
+ # * *all* (Boolean): If true, specify that we want all known nodes.
382
+ # * *list* (String): Name of a nodes list.
383
+ # * *platform* (String): Name of a platform containing nodes.
384
+ # * *service* (String): Name of a service implemented by nodes.
385
+ # * *git_diff* (Hash<Symbol,Object>): Info about a git diff that impacts nodes:
386
+ # * *platform* (String): Name of the platform on which checking the git diff
387
+ # * *from_commit* (String): Commit ID to check from [default: 'master']
388
+ # * *to_commit* (String or nil): Commit ID to check to, or nil for currently checked-out files [default: nil]
389
+ # * *smallest_set* (Boolean): Smallest set of impacted nodes? [default: false]
390
+ #
391
+ # Parameters::
392
+ # * *nodes_selectors* (Array<Object>): List of node selectors (can be a single element).
393
+ # * *ignore_unknowns* (Boolean): Do we ignore unknown nodes? [default = false]
394
+ # Result::
395
+ # * Array<String>: List of nodes
396
+ def select_nodes(*nodes_selectors, ignore_unknowns: false)
397
+ nodes_selectors = nodes_selectors.flatten
398
+ # 1. Check for the presence of all
399
+ return known_nodes if nodes_selectors.any? { |nodes_selector| nodes_selector.is_a?(Hash) && nodes_selector.key?(:all) && nodes_selector[:all] }
400
+ # 2. Expand the nodes lists, platforms and services contents
401
+ string_nodes = []
402
+ nodes_selectors.each do |nodes_selector|
403
+ if nodes_selector.is_a?(String)
404
+ string_nodes << nodes_selector
405
+ else
406
+ if nodes_selector.key?(:list)
407
+ platform = @nodes_list_platform[nodes_selector[:list]]
408
+ raise "Unknown nodes list: #{nodes_selector[:list]}" if platform.nil?
409
+ string_nodes.concat(platform.nodes_selectors_from_nodes_list(nodes_selector[:list]))
410
+ end
411
+ string_nodes.concat(@platforms_handler.platform(nodes_selector[:platform]).known_nodes) if nodes_selector.key?(:platform)
412
+ if nodes_selector.key?(:service)
413
+ prefetch_metadata_of known_nodes, :services
414
+ string_nodes.concat(known_nodes.select { |node| (get_services_of(node) || []).include?(nodes_selector[:service]) })
415
+ end
416
+ if nodes_selector.key?(:git_diff)
417
+ # Default values
418
+ git_diff_info = {
419
+ from_commit: 'master',
420
+ to_commit: nil,
421
+ smallest_set: false
422
+ }.merge(nodes_selector[:git_diff])
423
+ all_impacted_nodes, _impacted_nodes, _impacted_services, _impact_global = impacted_nodes_from_git_diff(
424
+ git_diff_info[:platform],
425
+ from_commit: git_diff_info[:from_commit],
426
+ to_commit: git_diff_info[:to_commit],
427
+ smallest_set: git_diff_info[:smallest_set]
428
+ )
429
+ string_nodes.concat(all_impacted_nodes)
430
+ end
431
+ end
432
+ end
433
+ # 3. Expand the Regexps
434
+ real_nodes = []
435
+ string_nodes.each do |node|
436
+ if node =~ /^\/(.+)\/$/
437
+ node_regexp = Regexp.new($1)
438
+ real_nodes.concat(known_nodes.select { |known_node| known_node[node_regexp] })
439
+ else
440
+ real_nodes << node
441
+ end
442
+ end
443
+ # 4. Sort them unique
444
+ real_nodes.uniq!
445
+ real_nodes.sort!
446
+ # Some sanity checks
447
+ unless ignore_unknowns
448
+ unknown_nodes = real_nodes - known_nodes
449
+ raise "Unknown nodes: #{unknown_nodes.join(', ')}" unless unknown_nodes.empty?
450
+ end
451
+ real_nodes
452
+ end
453
+
454
+ # Iterate over a list of nodes.
455
+ # Provide a mechanism to multithread this iteration (in such case the iterating code has to be thread-safe).
456
+ # In case of multithreaded run, a progress bar is being displayed.
457
+ #
458
+ # Parameters::
459
+ # * *nodes* (Array<String>): List of nodes to iterate over
460
+ # * *parallel* (Boolean): Iterate in a multithreaded way? [default: false]
461
+ # * *nbr_threads_max* (Integer or nil): Maximum number of threads to be used in case of parallel, or nil for no limit [default: nil]
462
+ # * *progress* (String or nil): Name of a progress bar to follow the progression, or nil for no progress bar [default: 'Processing nodes']
463
+ # * Proc: The code called for each node being iterated on.
464
+ # * Parameters::
465
+ # * *node* (String): The node name
466
+ def for_each_node_in(nodes, parallel: false, nbr_threads_max: nil, progress: 'Processing nodes')
467
+ for_each_element_in(nodes.sort, parallel: parallel, nbr_threads_max: nbr_threads_max, progress: progress) do |node|
468
+ yield node
469
+ end
470
+ end
471
+
472
+ # Get the list of impacted nodes from a git diff on a platform
473
+ #
474
+ # Parameters::
475
+ # * *platform_name* (String): The platform's name
476
+ # * *from_commit* (String): Commit ID to check from [default: 'master']
477
+ # * *to_commit* (String or nil): Commit ID to check to, or nil for currently checked-out files [default: nil]
478
+ # * *smallest_set* (Boolean): Smallest set of impacted nodes? [default: false]
479
+ # Result::
480
+ # * Array<String>: The list of nodes impacted by this diff (counting direct impacts, services and global files impacted)
481
+ # * Array<String>: The list of nodes directly impacted by this diff
482
+ # * Array<String>: The list of services impacted by this diff
483
+ # * Boolean: Are there some files that have a global impact (meaning all nodes are potentially impacted by this diff)?
484
+ def impacted_nodes_from_git_diff(platform_name, from_commit: 'master', to_commit: nil, smallest_set: false)
485
+ platform = @platforms_handler.platform(platform_name)
486
+ raise "Unkown platform #{platform_name}. Possible platforms are #{@platforms_handler.known_platforms.map(&:name).sort.join(', ')}" if platform.nil?
487
+ _exit_status, stdout, _stderr = @cmd_runner.run_cmd "cd #{platform.repository_path} && git --no-pager diff --no-color #{from_commit} #{to_commit.nil? ? '' : to_commit}", log_to_stdout: log_debug?
488
+ # Parse the git diff output to create a structured diff
489
+ # Hash< String, Hash< Symbol, Object > >: List of diffs info, per file name having a diff. Diffs info have the following properties:
490
+ # * *moved_to* (String): The new file path, in case it has been moved [optional]
491
+ # * *diff* (String): The diff content
492
+ files_diffs = {}
493
+ current_file_diff = nil
494
+ stdout.split("\n").each do |line|
495
+ case line
496
+ when /^diff --git a\/(.+) b\/(.+)$/
497
+ # A new file diff
498
+ from, to = $1, $2
499
+ current_file_diff = {
500
+ diff: ''
501
+ }
502
+ current_file_diff[:moved_to] = to unless from == to
503
+ files_diffs[from] = current_file_diff
504
+ else
505
+ current_file_diff[:diff] << "#{current_file_diff[:diff].empty? ? '' : "\n"}#{line}" unless current_file_diff.nil?
506
+ end
507
+ end
508
+ impacted_nodes, impacted_services, impact_global = platform.impacts_from files_diffs
509
+ impacted_services.sort!
510
+ impacted_services.uniq!
511
+ impacted_nodes.sort!
512
+ impacted_nodes.uniq!
513
+ [
514
+ if impact_global
515
+ platform.known_nodes.sort
516
+ else
517
+ (
518
+ impacted_nodes + impacted_services.map do |service|
519
+ service_nodes = select_nodes([{ service: service }])
520
+ smallest_set ? [service_nodes.first].compact : service_nodes
521
+ end
522
+ ).flatten.sort.uniq
523
+ end,
524
+ impacted_nodes,
525
+ impacted_services,
526
+ impact_global
527
+ ]
528
+ end
529
+
530
+ # Select the configs applicable to a given node.
531
+ #
532
+ # Parameters::
533
+ # * *node* (String): The node for which we select configurations
534
+ # * *configs* (Array< Hash<Symbol,Object> >): Configuration properties. Each configuration is selected based on the nodes_selectors_stack property.
535
+ # Result::
536
+ # * Array< Hash<Symbol,Object> >: The selected configurations
537
+ def select_confs_for_node(node, configs)
538
+ configs.select { |config_info| select_from_nodes_selector_stack(config_info[:nodes_selectors_stack]).include?(node) }
539
+ end
540
+
541
+ # Select the configs applicable to a given platform.
542
+ #
543
+ # Parameters::
544
+ # * *platform_name* (String): The platform for which we select configurations
545
+ # * *configs* (Array< Hash<Symbol,Object> >): Configuration properties. Each configuration is selected based on the nodes_selectors_stack property.
546
+ # Result::
547
+ # * Array< Hash<Symbol,Object> >: The selected configurations
548
+ def select_confs_for_platform(platform_name, configs)
549
+ platform_nodes = @platforms_handler.platform(platform_name).known_nodes
550
+ configs.select { |config_info| (platform_nodes - select_from_nodes_selector_stack(config_info[:nodes_selectors_stack])).empty? }
551
+ end
552
+
553
+ # Get the list of nodes impacted by a nodes selector stack.
554
+ # The result is the intersection of every nodes set in the stack.
555
+ #
556
+ # Parameters::
557
+ # * *nodes_selector_stack* (Array): The nodes selector stack
558
+ # Result::
559
+ # * Array<String>: List of nodes
560
+ def select_from_nodes_selector_stack(nodes_selector_stack)
561
+ nodes_selector_stack.inject(known_nodes) { |selected_nodes, nodes_selector| selected_nodes & select_nodes(nodes_selector) }
562
+ end
563
+
564
+ private
565
+
566
+ # Get the CMDB master for a given property.
567
+ # Keep a cache of the masters for performance, as this can be called very often and multi-threaded.
568
+ #
569
+ # Parameters::
570
+ # * *node* (String): Node for which we want the CMDB master
571
+ # * *property* (Symbol): The property for which we search a CMDB master
572
+ # Result::
573
+ # * Cmdb or nil: CMDB, or nil if none
574
+ def cmdb_master_for(node, property)
575
+ unless @cmdb_masters_cache.key?(node)
576
+ # CMDB master per property name
577
+ # Hash< Symbol, Cmdb >
578
+ cmdb_masters_cache = {}
579
+ select_confs_for_node(node, @config.cmdb_masters).each do |cmdb_masters_info|
580
+ cmdb_masters_info[:cmdb_masters].each do |cmdb, properties|
581
+ properties.each do |property|
582
+ raise "Property #{property} have conflicting CMDB masters for #{node} declared in the configuration: #{cmdb_masters_cache[property].class.name} and #{@cmdbs[cmdb].class.name}" if cmdb_masters_cache.key?(property) && cmdb_masters_cache[property] != @cmdbs[cmdb]
583
+ log_debug "CMDB master for #{node} / #{property}: #{cmdb}"
584
+ raise "CMDB #{cmdb} is configured as a master for property #{property} on node #{node} but it does not implement the needed API to retrieve it" unless (@cmdbs_per_property[property] || []).include?(@cmdbs[cmdb]) || @cmdbs_others.include?(@cmdbs[cmdb])
585
+ cmdb_masters_cache[property] = @cmdbs[cmdb]
586
+ end
587
+ end
588
+ end
589
+ # Set the instance variable as an atomic operation to ensure multi-threading here.
590
+ @cmdb_masters_cache[node] = cmdb_masters_cache
591
+ end
592
+ @cmdb_masters_cache[node][property]
593
+ end
594
+
595
+ end
596
+
597
+ end