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,92 @@
1
+ require 'base64'
2
+ require 'savon'
3
+ require 'hybrid_platforms_conductor/credentials'
4
+ require 'hybrid_platforms_conductor/logger_helpers'
5
+
6
+ module HybridPlatformsConductor
7
+
8
+ # Gives ways to query the Thycotic SOAP API at a given URL
9
+ class Thycotic
10
+
11
+ include LoggerHelpers
12
+
13
+ # Provide a Thycotic connector, and make sure the password is being cleaned when exiting.
14
+ #
15
+ # Parameters::
16
+ # * *thycotic_url* (String): The Thycotic URL
17
+ # * *logger* (Logger): Logger to be used
18
+ # * *logger_stderr* (Logger): Logger to be used for stderr
19
+ # * *domain* (String): Domain to use for authentication to Thycotic [default: ENV['hpc_domain_for_thycotic']]
20
+ # * Proc: Code called with the Thyctotic instance.
21
+ # * *thycotic* (Thyctotic): The Thyctotic instance to use.
22
+ def self.with_thycotic(thycotic_url, logger, logger_stderr, domain: ENV['hpc_domain_for_thycotic'])
23
+ Credentials.with_credentials_for(:thycotic, logger, logger_stderr, url: thycotic_url) do |thycotic_user, thycotic_password|
24
+ yield Thycotic.new(thycotic_url, thycotic_user, thycotic_password, logger: logger, logger_stderr: logger_stderr)
25
+ end
26
+ end
27
+
28
+ # Constructor
29
+ #
30
+ # Parameters::
31
+ # * *url* (String): URL of the Thycotic Secret Server
32
+ # * *user* (String): User name to be used to connect to Thycotic
33
+ # * *password* (String): Password to be used to connect to Thycotic
34
+ # * *domain* (String): Domain to use for authentication to Thycotic [default: ENV['hpc_domain_for_thycotic']]
35
+ # * *logger* (Logger): Logger to be used [default: Logger.new(STDOUT)]
36
+ # * *logger_stderr* (Logger): Logger to be used for stderr [default: Logger.new(STDERR)]
37
+ def initialize(
38
+ url,
39
+ user,
40
+ password,
41
+ domain: ENV['hpc_domain_for_thycotic'],
42
+ logger: Logger.new(STDOUT),
43
+ logger_stderr: Logger.new(STDERR)
44
+ )
45
+ init_loggers(logger, logger_stderr)
46
+ # Get a token to this SOAP API
47
+ @client = Savon.client(
48
+ wsdl: "#{url}/webservices/SSWebservice.asmx?wsdl",
49
+ ssl_verify_mode: :none,
50
+ logger: @logger,
51
+ log: log_debug?
52
+ )
53
+ @token = @client.call(:authenticate, message: {
54
+ username: user,
55
+ password: password,
56
+ domain: domain
57
+ }).to_hash.dig(:authenticate_response, :authenticate_result, :token)
58
+ raise "Unable to get token from SOAP authentication to #{url}" if @token.nil?
59
+ end
60
+
61
+ # Return secret corresponding to a given secret ID
62
+ #
63
+ # Parameters::
64
+ # * *secret_id* (Object): The secret ID
65
+ # Result::
66
+ # * Hash: The corresponding API result
67
+ def get_secret(secret_id)
68
+ @client.call(:get_secret, message: {
69
+ token: @token,
70
+ secretId: secret_id
71
+ }).to_hash.dig(:get_secret_response, :get_secret_result)
72
+ end
73
+
74
+ # Get a file attached to a given secret
75
+ #
76
+ # Parameters::
77
+ # * *secret_id* (Object): The secret ID
78
+ # * *secret_item_id* (Object): The secret item id
79
+ # Result::
80
+ # * String or nil: The file content, or nil if none
81
+ def download_file_attachment_by_item_id(secret_id, secret_item_id)
82
+ file_in_base64 = @client.call(:download_file_attachment_by_item_id, message: {
83
+ token: @token,
84
+ secretId: secret_id,
85
+ secretItemId: secret_item_id
86
+ }).to_hash.dig(:download_file_attachment_by_item_id_response, :download_file_attachment_by_item_id_result, :file_attachment)
87
+ file_in_base64.nil? ? nil : Base64.decode64(file_in_base64)
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,859 @@
1
+ require 'logger'
2
+ require 'ipaddress'
3
+ require 'hybrid_platforms_conductor/nodes_handler'
4
+ require 'hybrid_platforms_conductor/json_dumper'
5
+ require 'hybrid_platforms_conductor/topographer/plugin'
6
+ require 'hybrid_platforms_conductor/logger_helpers'
7
+
8
+ module HybridPlatformsConductor
9
+
10
+ # Class giving an API to parse the graph of the TI network
11
+ class Topographer
12
+
13
+ include LoggerHelpers
14
+
15
+ # Give a default configuration
16
+ #
17
+ # Result::
18
+ # * Hash<Symbol,Object>: Default configuration
19
+ def self.default_config
20
+ {
21
+ # Directory from which the complete JSON files are to be read
22
+ json_files_dir: 'nodes_json',
23
+ # JSON keys to ignore when reading complete JSON files. Only leafs of this tree structure are ignored.
24
+ ignore_json_keys: {
25
+ # This should only duplicate the real configuration from the recipes, and it adds a lot of IP ranges that can be ignored.
26
+ 'network' => nil,
27
+ # Contains simple network definition. Not a connection in itself.
28
+ 'policy_xae_outproxy' => { 'local_network' => nil },
29
+ # Contains DNS entries. Not a connection in itself.
30
+ 'policy_xae_xx_cdh' => { 'dns' => nil },
31
+ # This contains firewall rules, therefore representing who connects on the host, and not who the host connects to.
32
+ 'policy_xae_xx_iptables' => nil,
33
+ # Contains the allowed network range. Not a connection in itself.
34
+ 'postfix' => { 'main' => { 'mynetworks' => nil } },
35
+ # This contains sometime IP addresses in the key comments
36
+ 'site_directory' => nil,
37
+ # This contains firewall rules, therefore representing who connects on the host, and not who the host connects to.
38
+ 'site_iptables' => nil,
39
+ # This contains some user names having IP addresses inside
40
+ 'site_xx_roles' => nil,
41
+ # This stores routes for all Proxmox instances.
42
+ 'pve' => { 'vlan' => { 'routes' => nil } }
43
+ },
44
+ # JSON keys to ignore when reading complete JSON files, whatever their position
45
+ ignore_any_json_keys: [
46
+ # Those contain cache of MAC addresses to IP addresses
47
+ 'arp',
48
+ # Those contain broadcast IP addresses
49
+ 'broadcast',
50
+ # Those contain firewall rules, therefore representing who connects on the host, and not who the host connects to.
51
+ 'firewall',
52
+ # Those contain version numbers with same format as IP addresses
53
+ 'version'
54
+ ],
55
+ # IPs to ignore while parsing complete JSON files
56
+ ignore_ips: [
57
+ /^0\./,
58
+ /^127\./,
59
+ /^255\./
60
+ ],
61
+ # Maximum level of recursion while building the graph of connected nodes (nil = no limit).
62
+ connections_max_level: nil,
63
+ # Maximum label length for a link
64
+ max_link_label_length: 128
65
+ }
66
+ end
67
+
68
+ # Some getters that can be useful for clients of the Topographer
69
+ attr_reader :nodes_graph, :config, :node_metadata
70
+
71
+ # Constructor
72
+ #
73
+ # Parameters::
74
+ # * *logger* (Logger): Logger to be used [default = Logger.new(STDOUT)]
75
+ # * *logger_stderr* (Logger): Logger to be used for stderr [default = Logger.new(STDERR)]
76
+ # * *nodes_handler* (NodesHandler): The nodes handler to be used [default = NodesHandler.new]
77
+ # * *json_dumper* (JsonDumper): The JSON Dumper to be used [default = JsonDumper.new]
78
+ # * *config* (Hash<Symbol,Object>): Some configuration parameters that can override defaults. [default = {}] Here are the possible keys:
79
+ # * *json_files_dir* (String): Directory from which JSON files are taken. [default = nodes_json]
80
+ # * *connections_max_level* (Integer or nil): Number maximal of recursive passes to get hostname connections (nil means no limit). [default = nil]
81
+ def initialize(logger: Logger.new(STDOUT), logger_stderr: Logger.new(STDERR), nodes_handler: NodesHandler.new, json_dumper: JsonDumper.new, config: {})
82
+ init_loggers(logger, logger_stderr)
83
+ @nodes_handler = nodes_handler
84
+ @json_dumper = json_dumper
85
+ @config = Topographer.default_config.merge(config)
86
+ # Get the metadata of each node, per hostname
87
+ # Hash<String,Hash>
88
+ @node_metadata = {}
89
+ # Know for each IP what is the hostname it belongs to
90
+ # Hash<String,String>
91
+ @ips_to_host = {}
92
+ # Get the connection information per node name. A node reprensents 1 element that can be connected to other elements in the graph.
93
+ # Hash< String, Hash<Symbol,Object> >
94
+ # Here are the possible information keys:
95
+ # * *type* (Symbol): Type of the node. Can be one of: :node, :cluster, :unknown.
96
+ # * *connections* (Hash< String, Array<String> >): List of labels per connected node.
97
+ # * *includes* (Array<String>): List of nodes included in this one.
98
+ # * *includes_proc* (Proc): Proc called to know if a node belongs to this cluster [only if type == :cluster]:
99
+ # * Parameters::
100
+ # * *node_name* (String): Name of the node for the inclusion test
101
+ # * Result::
102
+ # * Boolean: Does the node belongs to this cluster?
103
+ # * *ipv4* (IPAddress::IPv4): Corresponding IPv4 object [only if type == :node and a private IP exists, or type == :unknown, or type == :cluster and the cluster name is an IP range]
104
+ @nodes_graph = {}
105
+
106
+ # Default values
107
+ @from_hosts = []
108
+ @to_hosts = []
109
+ @outputs = []
110
+ @skip_run = false
111
+
112
+ # Parse plugins
113
+ root_path = File.expand_path("#{File.dirname(__FILE__)}/..")
114
+ @plugins = Hash[Dir.
115
+ glob("#{File.dirname(__FILE__)}/topographer/plugins/*.rb").
116
+ map do |file_name|
117
+ plugin_name = File.basename(file_name)[0..-4].to_sym
118
+ require file_name
119
+ [
120
+ plugin_name,
121
+ Topographer::Plugins.const_get(plugin_name.to_s.split('_').collect(&:capitalize).join.to_sym)
122
+ ]
123
+ end]
124
+
125
+ @ips_to_host = known_ips.clone
126
+
127
+ # Fill info from the metadata
128
+ metadata_properties = %i[
129
+ description
130
+ physical_node
131
+ private_ips
132
+ ]
133
+ @nodes_handler.prefetch_metadata_of @nodes_handler.known_nodes, metadata_properties
134
+ @nodes_handler.known_nodes.each do |hostname|
135
+ @node_metadata[hostname] = Hash[metadata_properties.map { |property| [property, @nodes_handler.metadata_of(hostname, property)] }]
136
+ end
137
+
138
+ # Small cache of hostnames used a lot to parse JSON
139
+ @known_nodes = Hash[@nodes_handler.known_nodes.map { |hostname| [hostname, nil] }]
140
+ # Cache of objects being used a lot in parsing for performance
141
+ @non_word_regexp = /\W+/
142
+ @ip_regexp = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/(\d{1,2})|[^\d\/]|$)/
143
+ # Cache of ignored IPs
144
+ @ips_ignored = {}
145
+ end
146
+
147
+ # Complete an option parser with ways to tune the topographer
148
+ #
149
+ # Parameters::
150
+ # * *options_parser* (OptionParser): The option parser to complete
151
+ def options_parse(options_parser)
152
+ from_hosts_opts_parser = OptionParser.new do |opts|
153
+ @nodes_handler.options_parse_nodes_selectors(opts, @from_hosts)
154
+ end
155
+ to_hosts_opts_parser = OptionParser.new do |opts|
156
+ @nodes_handler.options_parse_nodes_selectors(opts, @to_hosts)
157
+ end
158
+ options_parser.separator ''
159
+ options_parser.separator 'Topographer options:'
160
+ options_parser.on('-F', '--from HOSTS_OPTIONS', 'Specify options for the set of nodes to start from (enclose them with ""). Default: all nodes. HOSTS_OPTIONS follows the following:', *from_hosts_opts_parser.to_s.split("\n")[3..-1]) do |hosts_options|
161
+ args = hosts_options.split(' ')
162
+ from_hosts_opts_parser.parse!(args)
163
+ raise "Unknown --from options: #{args.join(' ')}" unless args.empty?
164
+ end
165
+ options_parser.on('-k', '--skip-run', "Skip the actual gathering of JSON node files. If set, the current files in #{@config[:json_files_dir]} will be used.") do
166
+ @skip_run = true
167
+ end
168
+ options_parser.on('-p', '--output FORMAT:FILE_NAME', "Specify a format and file name. Can be used several times. FORMAT can be one of #{available_plugins.sort.join(', ')}. Ex.: graphviz:graph.gv") do |output|
169
+ format_str, file_name = output.split(':')
170
+ format = format_str.to_sym
171
+ raise "Unknown format: #{format}." unless available_plugins.include?(format)
172
+ @outputs << [format, file_name]
173
+ end
174
+ options_parser.on('-T', '--to HOSTS_OPTIONS', 'Specify options for the set of nodes to get to (enclose them with ""). Default: all nodes. HOSTS_OPTIONS follows the following:', *to_hosts_opts_parser.to_s.split("\n")[3..-1]) do |hosts_options|
175
+ args = hosts_options.split(' ')
176
+ to_hosts_opts_parser.parse!(args)
177
+ raise "Unknown --to options: #{args.join(' ')}" unless args.empty?
178
+ end
179
+ end
180
+
181
+ # Validate that parsed parameters are valid
182
+ def validate_params
183
+ raise 'No output defined. Please use --output option.' if @outputs.empty?
184
+ end
185
+
186
+ # Resolve the from and to hosts descriptions
187
+ #
188
+ # Result::
189
+ # * Array<String>: The from hostnames
190
+ # * Array<String>: The to hostnames
191
+ def resolve_from_to
192
+ @from_hosts << { all: true } if @from_hosts.empty?
193
+ @to_hosts << { all: true } if @to_hosts.empty?
194
+ [
195
+ @nodes_handler.select_nodes(@from_hosts),
196
+ @nodes_handler.select_nodes(@to_hosts)
197
+ ]
198
+ end
199
+
200
+ # Generate the JSON files to be used
201
+ def get_json_files
202
+ unless @skip_run
203
+ @json_dumper.dump_dir = @config[:json_files_dir]
204
+ # Generate all the jsons, even if 1 hostname is given, as it might be useful for the rest of the graph.
205
+ @json_dumper.dump_json_for(@nodes_handler.known_nodes)
206
+ end
207
+ end
208
+
209
+ # Dump the graph in the desired outputs
210
+ def dump_outputs
211
+ @outputs.each do |(format, file_name)|
212
+ section "Write #{format} file #{file_name}" do
213
+ write_graph(file_name, format)
214
+ end
215
+ end
216
+ end
217
+
218
+ # Get the list of available plugins
219
+ #
220
+ # Result::
221
+ # * Array<Symbol>: List of plugins
222
+ def available_plugins
223
+ @plugins.keys
224
+ end
225
+
226
+ # Add to the graph a given set of hostnames and their connected nodes.
227
+ #
228
+ # Parameters::
229
+ # * *hostnames* (Array<String>): List of hostnames
230
+ def graph_for(hostnames)
231
+ # Parse connections from JSON files
232
+ hostnames.each do |hostname|
233
+ parse_connections_for(hostname, @config[:connections_max_level])
234
+ end
235
+ end
236
+
237
+ # Add to the graph a given set of nodes lists and their connected nodes.
238
+ #
239
+ # Parameters::
240
+ # * *nodes_lists* (Array<String>): List of nodes lists
241
+ # * *only_add_cluster* (Boolean): If true, then don't add missing nodes from this graph to the graph [default = false]
242
+ def graph_for_nodes_lists(nodes_lists, only_add_cluster: false)
243
+ nodes_lists.each do |nodes_list|
244
+ hosts_list = @nodes_handler.select_nodes(@nodes_handler.nodes_from_list(nodes_list))
245
+ if only_add_cluster
246
+ # Select only the hosts list we know about
247
+ hosts_list.select! { |hostname| @nodes_graph.key?(hostname) }
248
+ else
249
+ # Parse JSON for all the hosts of this cluster
250
+ hosts_list.each do |hostname|
251
+ parse_connections_for(hostname, @config[:connections_max_level])
252
+ end
253
+ end
254
+ @nodes_graph[nodes_list] = {
255
+ type: :cluster,
256
+ connections: {},
257
+ includes: [],
258
+ includes_proc: proc { |node_name| hosts_list.include?(node_name) }
259
+ } unless @nodes_graph.key?(nodes_list)
260
+ @nodes_graph[nodes_list][:includes].concat(hosts_list)
261
+ @nodes_graph[nodes_list][:includes].uniq!
262
+ end
263
+ end
264
+
265
+ # Collapse a given list of nodes.
266
+ #
267
+ # Parameters::
268
+ # * *nodes_list* (Array<String>): List of nodes to collapse
269
+ def collapse_nodes(nodes_list)
270
+ nodes_list.each do |node_name_to_collapse|
271
+ included_nodes = @nodes_graph[node_name_to_collapse][:includes]
272
+ # First collapse its included nodes if any
273
+ collapse_nodes(included_nodes)
274
+ # Then collapse this one
275
+ collapsed_connections = {}
276
+ included_nodes.each do |included_node_name|
277
+ collapsed_connections.merge!(@nodes_graph[included_node_name][:connections]) { |_connected_node, labels1, labels2| (labels1 + labels2).uniq }
278
+ end
279
+ @nodes_graph[node_name_to_collapse][:connections] = collapsed_connections
280
+ @nodes_graph[node_name_to_collapse][:includes] = []
281
+ replace_nodes(included_nodes, node_name_to_collapse)
282
+ end
283
+ end
284
+
285
+ # Remove self connections.
286
+ def remove_self_connections
287
+ @nodes_graph.each do |node_name, node_info|
288
+ node_info[:connections].delete_if { |connected_node_name, _labels| connected_node_name == node_name }
289
+ end
290
+ end
291
+
292
+ # Remove empty clusters
293
+ def remove_empty_clusters
294
+ loop do
295
+ empty_clusters = @nodes_graph.keys.select { |node_name| @nodes_graph[node_name][:type] == :cluster && @nodes_graph[node_name][:includes].empty? }
296
+ break if empty_clusters.empty?
297
+ filter_out_nodes(empty_clusters)
298
+ end
299
+ end
300
+
301
+ # Define clusters of ips with 24 bits ranges.
302
+ def define_clusters_ip_24
303
+ @nodes_graph.keys.each do |node_name|
304
+ if @nodes_graph[node_name][:type] == :node && !@node_metadata[node_name][:private_ips].nil? && !@node_metadata[node_name][:private_ips].empty?
305
+ ip_24 = "#{@node_metadata[node_name][:private_ips].first.split('.')[0..2].join('.')}.0/24"
306
+ @nodes_graph[ip_24] = ip_range_graph_info(ip_24) unless @nodes_graph.key?(ip_24)
307
+ @nodes_graph[ip_24][:includes] << node_name unless @nodes_graph[ip_24][:includes].include?(node_name)
308
+ end
309
+ end
310
+ end
311
+
312
+ # Return the list of nodes and ancestors of a given list of nodes, recursively.
313
+ # An ancestor of a node is another node connected to it, or to a group including it.
314
+ # An ancestor of a node can be:
315
+ # * Another node connected to it.
316
+ # * Another node including it.
317
+ #
318
+ # Parameters::
319
+ # * *nodes_list* (Array<String>): List of nodes for which we look for ancestors.
320
+ # Result::
321
+ # * Array<String>: List of ancestor nodes.
322
+ def ancestor_nodes(nodes_list)
323
+ ancestor_nodes_list = []
324
+ @nodes_graph.each do |node_name, node_info|
325
+ ancestor_nodes_list << node_name if !nodes_list.include?(node_name) && (!(node_info[:connections].keys & nodes_list).empty? || !(node_info[:includes] & nodes_list).empty?)
326
+ end
327
+ if ancestor_nodes_list.empty?
328
+ nodes_list
329
+ else
330
+ ancestor_nodes(nodes_list + ancestor_nodes_list)
331
+ end
332
+ end
333
+
334
+ # Return the list of nodes and children of a given list of nodes, recursively.
335
+ # A child of a node is another node connected to it, or to a group including it.
336
+ # A child of a node can be:
337
+ # * Another node that it connects to.
338
+ # * Another node that it includes.
339
+ #
340
+ # Parameters::
341
+ # * *nodes_list* (Array<String>): List of nodes for which we look for children.
342
+ # Result::
343
+ # * Array<String>: List of children nodes.
344
+ def children_nodes(nodes_list)
345
+ children_nodes_list = []
346
+ nodes_list.each do |node_name|
347
+ children_nodes_list.concat(@nodes_graph[node_name][:connections].keys + @nodes_graph[node_name][:includes])
348
+ end
349
+ children_nodes_list.uniq!
350
+ new_children_nodes = children_nodes_list - nodes_list
351
+ if new_children_nodes.empty?
352
+ children_nodes_list
353
+ else
354
+ children_nodes(children_nodes_list)
355
+ end
356
+ end
357
+
358
+ # Return the list of nodes that are clusters
359
+ #
360
+ # Result::
361
+ # * Array<String>: List of cluster nodes
362
+ def cluster_nodes
363
+ cluster_nodes_list = []
364
+ @nodes_graph.each do |node_name, node_info|
365
+ cluster_nodes_list << node_name if node_info[:type] == :cluster
366
+ end
367
+ cluster_nodes_list
368
+ end
369
+
370
+ # Remove from the graph any node that is not part of a given list
371
+ #
372
+ # Parameters::
373
+ # * *nodes_list* (Array<String>): List of nodes to keep
374
+ def filter_in_nodes(nodes_list)
375
+ new_nodes_graph = {}
376
+ @nodes_graph.each do |node_name, node_info|
377
+ new_nodes_graph[node_name] = node_info.merge(
378
+ connections: node_info[:connections].select { |connected_hostname, _labels| nodes_list.include?(connected_hostname) },
379
+ includes: node_info[:includes] & nodes_list
380
+ ) if nodes_list.include?(node_name)
381
+ end
382
+ @nodes_graph = new_nodes_graph
383
+ end
384
+
385
+ # Remove from the graph any node that is part of a given list
386
+ #
387
+ # Parameters::
388
+ # * *nodes_list* (Array<String>): List of nodes to remove
389
+ def filter_out_nodes(nodes_list)
390
+ new_nodes_graph = {}
391
+ @nodes_graph.each do |node_name, node_info|
392
+ new_nodes_graph[node_name] = node_info.merge(
393
+ connections: node_info[:connections].select { |connected_hostname, _labels| !nodes_list.include?(connected_hostname) },
394
+ includes: node_info[:includes] - nodes_list
395
+ ) unless nodes_list.include?(node_name)
396
+ end
397
+ @nodes_graph = new_nodes_graph
398
+ end
399
+
400
+ # Replace a list of nodes by a given node.
401
+ #
402
+ # Parameters::
403
+ # * *nodes_to_be_replaced* (Array<String>): Nodes to be replaced
404
+ # * *replacement_node* (String): Node that is used for replacement
405
+ def replace_nodes(nodes_to_be_replaced, replacement_node)
406
+ # Delete references to the nodes to be replaced
407
+ @nodes_graph.delete_if { |node_name, _node_info| nodes_to_be_replaced.include?(node_name) }
408
+ # Change any connection or inclusions using nodes to be replaced
409
+ @nodes_graph.each do |node_name, node_info|
410
+ node_info[:includes] = node_info[:includes].map { |included_node_name| nodes_to_be_replaced.include?(included_node_name) ? replacement_node : included_node_name }.uniq
411
+ new_connections = {}
412
+ node_info[:connections].each do |connected_node_name, labels|
413
+ if nodes_to_be_replaced.include?(connected_node_name)
414
+ new_connections[replacement_node] = [] unless new_connections.key?(replacement_node)
415
+ new_connections[replacement_node].concat(labels)
416
+ new_connections[replacement_node].uniq!
417
+ else
418
+ new_connections[connected_node_name] = labels
419
+ end
420
+ end
421
+ node_info[:connections] = new_connections
422
+ end
423
+ end
424
+
425
+ # Make sure clusters follow a strict hierarchy and that 1 node belongs to at most 1 cluster.
426
+ def force_cluster_strict_hierarchy
427
+ # Find the nodes belonging to several clusters.
428
+ loop do
429
+ # First cluster found each node name
430
+ # Hash<String, String >
431
+ cluster_per_node = {}
432
+ conflicting_clusters = nil
433
+ @nodes_graph.each do |node_name, node_info|
434
+ node_info[:includes].each do |included_node_name|
435
+ if cluster_per_node.key?(included_node_name)
436
+ # Found a conflict between 2 clusters
437
+ conflicting_clusters = [node_name, cluster_per_node[included_node_name]]
438
+ log_error "Node #{included_node_name} found in both clusters #{node_name} and #{cluster_per_node[included_node_name]}"
439
+ break
440
+ else
441
+ cluster_per_node[included_node_name] = node_name
442
+ end
443
+ end
444
+ break unless conflicting_clusters.nil?
445
+ end
446
+ if conflicting_clusters.nil?
447
+ break
448
+ else
449
+ # We have conflicting clusters to resolve
450
+ cluster_1, cluster_2 = conflicting_clusters
451
+ c1_belongs_to_c2 = @nodes_graph[cluster_1][:includes].all? { |cluster_1_node_name| @nodes_graph[cluster_2][:includes_proc].call(cluster_1_node_name) }
452
+ c2_belongs_to_c1 = @nodes_graph[cluster_2][:includes].all? { |cluster_2_node_name| @nodes_graph[cluster_1][:includes_proc].call(cluster_2_node_name) }
453
+ if c1_belongs_to_c2
454
+ if c2_belongs_to_c1
455
+ # Both clusters have the same nodes
456
+ if @nodes_graph[cluster_1][:includes_proc].call(cluster_2)
457
+ @nodes_graph[cluster_2][:includes] = (@nodes_graph[cluster_1][:includes] + @nodes_graph[cluster_2][:includes]).uniq
458
+ @nodes_graph[cluster_1][:includes] = [cluster_2]
459
+ else
460
+ @nodes_graph[cluster_1][:includes] = (@nodes_graph[cluster_1][:includes] + @nodes_graph[cluster_2][:includes]).uniq
461
+ @nodes_graph[cluster_2][:includes] = [cluster_1]
462
+ end
463
+ else
464
+ # All nodes of cluster_1 belong to cluster_2, but some nodes of cluster_2 don't belong to cluster_1
465
+ @nodes_graph[cluster_2][:includes] = @nodes_graph[cluster_2][:includes] - @nodes_graph[cluster_1][:includes] + [cluster_1]
466
+ end
467
+ elsif c2_belongs_to_c1
468
+ # All nodes of cluster_2 belong to cluster_1, but some nodes of cluster_1 don't belong to cluster_2
469
+ @nodes_graph[cluster_1][:includes] = @nodes_graph[cluster_1][:includes] - @nodes_graph[cluster_2][:includes] + [cluster_2]
470
+ else
471
+ # cluster_1 and cluster_2 have to be merged
472
+ new_cluster_name = "#{cluster_1}_&_#{cluster_2}"
473
+ # Store thos proc in those variables as the cluster_1 and cluster_2 references are going to be removed
474
+ includes_proc_1 = @nodes_graph[cluster_1][:includes_proc]
475
+ includes_proc_2 = @nodes_graph[cluster_2][:includes_proc]
476
+ @nodes_graph[new_cluster_name] = {
477
+ type: :cluster,
478
+ includes: (@nodes_graph[cluster_1][:includes] + @nodes_graph[cluster_2][:includes]).uniq,
479
+ connections: @nodes_graph[cluster_1][:connections].merge!(@nodes_graph[cluster_2][:connections]) { |_connected_node, labels1, labels2| (labels1 + labels2).uniq },
480
+ includes_proc: proc do |hostname|
481
+ includes_proc_1.call(hostname) || includes_proc_2.call(hostname)
482
+ end
483
+ }
484
+ replace_nodes([cluster_1, cluster_2], new_cluster_name)
485
+ end
486
+ end
487
+ end
488
+ end
489
+
490
+ # Is the node represented as a cluster?
491
+ #
492
+ # Parameters::
493
+ # * *node_name* (String): Node name
494
+ # Result::
495
+ # * Boolean: Is the node represented as a cluster?
496
+ def is_node_cluster?(node_name)
497
+ @nodes_graph[node_name][:type] == :cluster || !@nodes_graph[node_name][:includes].empty?
498
+ end
499
+
500
+ # Is the node a physical node?
501
+ #
502
+ # Parameters::
503
+ # * *node_name* (String): Node name
504
+ # Result::
505
+ # * Boolean: Is the node a physical node?
506
+ def is_node_physical?(node_name)
507
+ @nodes_graph[node_name][:type] == :node && @node_metadata[node_name][:physical_node]
508
+ end
509
+
510
+ # Output the graph to a given file at a given format
511
+ #
512
+ # Parameters::
513
+ # * *file_name* (String): File name to output to.
514
+ # * *output_format* (Symbol): Output format to use (should be part of the plugins).
515
+ def write_graph(file_name, output_format)
516
+ if @plugins.key?(output_format)
517
+ @plugins[output_format].new(self).write_graph(file_name)
518
+ else
519
+ raise "Unknown topographer plugin #{output_format}"
520
+ end
521
+ end
522
+
523
+ # Get the title of a given node
524
+ #
525
+ # Parameters::
526
+ # * *node_name* (String): Node name
527
+ # Result::
528
+ # * String: Node title
529
+ def title_for(node_name)
530
+ case @nodes_graph[node_name][:type]
531
+ when :node
532
+ "#{node_name} - #{@node_metadata[node_name][:private_ips].nil? || @node_metadata[node_name][:private_ips].empty? ? 'No IP' : @node_metadata[node_name][:private_ips].first}"
533
+ when :cluster
534
+ "#{node_name} (#{@nodes_graph[node_name][:includes].size} nodes)"
535
+ when :unknown
536
+ "#{node_name} - Unknown node"
537
+ end
538
+ end
539
+
540
+ # Get the description of a given node
541
+ #
542
+ # Parameters::
543
+ # * *node_name* (String): Node name
544
+ # Result::
545
+ # * String: Node description, or nil if none
546
+ def description_for(node_name)
547
+ require 'byebug'
548
+ byebug if node_name == 'xaesbghad51'
549
+ case @nodes_graph[node_name][:type]
550
+ when :node
551
+ @node_metadata[node_name][:description]
552
+ when :cluster
553
+ nil
554
+ when :unknown
555
+ nil
556
+ end
557
+ end
558
+
559
+ private
560
+
561
+ # Get the list of known IPs (private and public), and return each associated node
562
+ #
563
+ # Result::
564
+ # * Hash<String,String>: List of nodes per IP address
565
+ def known_ips
566
+ # Keep a cache of it
567
+ unless defined?(@known_ips)
568
+ @known_ips = {}
569
+ # Fill info from the metadata
570
+ @nodes_handler.prefetch_metadata_of @nodes_handler.known_nodes, %i[private_ips public_ips]
571
+ @nodes_handler.known_nodes.each do |node|
572
+ %i[private_ips public_ips].each do |ip_type|
573
+ ips = @nodes_handler.metadata_of(node, ip_type)
574
+ if ips
575
+ ips.each do |ip|
576
+ raise "Conflict: #{ip} is already associated to #{@known_ips[ip]}. Cannot associate it to #{node}." if @known_ips.key?(ip)
577
+ @known_ips[ip] = node
578
+ end
579
+ end
580
+ end
581
+ end
582
+ end
583
+ @known_ips
584
+ end
585
+
586
+ # Get the list of known IP addresses matching a given IP mask
587
+ #
588
+ # Parameters::
589
+ # * *ip_def* (String): The ip definition (without mask).
590
+ # * *ip_mask* (Integer): The IP mask in bits.
591
+ # Result::
592
+ # * Array<String>: The list of IP addresses matching this mask
593
+ def ips_matching_mask(ip_def, ip_mask)
594
+ # Keep a cache of it
595
+ # Hash<String, Hash<Integer, Array<String> > >
596
+ # Hash<ip_def, ip_mask, ip
597
+ @ips_mask = {} unless defined?(@ips_mask)
598
+ @ips_mask[ip_def] = {} unless @ips_mask.key?(ip_def)
599
+ unless @ips_mask[ip_def].key?(ip_mask)
600
+ # For performance, keep a cache of all the IPAddress::IPv4 objects
601
+ @ip_v4_cache = Hash[known_ips.keys.map { |ip, _node| [ip, IPAddress::IPv4.new(ip)] }] unless defined?(@ip_v4_cache)
602
+ ip_range = IPAddress::IPv4.new("#{ip_def}/#{ip_mask}")
603
+ @ips_mask[ip_def][ip_mask] = @ip_v4_cache.select { |_ip, ip_v4| ip_range.include?(ip_v4) }.keys
604
+ end
605
+ @ips_mask[ip_def][ip_mask]
606
+ end
607
+
608
+ # Get the list of 24 bits IP addresses matching a given IP mask
609
+ #
610
+ # Parameters::
611
+ # * *ip_def* (String): The ip definition (without mask).
612
+ # * *ip_mask* (Integer): The IP mask in bits.
613
+ # Result::
614
+ # * Array<String>: The list of 24 bits IP addresses matching this mask
615
+ def ips_24_matching_mask(ip_def, ip_mask)
616
+ # Keep a cache of it
617
+ # Hash<String, Hash<Integer, Array<String> > >
618
+ # Hash<ip_def, ip_mask, ip_24
619
+ @ips_24_mask = {} unless defined?(@ips_24_mask)
620
+ @ips_24_mask[ip_def] = {} unless @ips_24_mask.key?(ip_def)
621
+ unless @ips_24_mask[ip_def].key?(ip_mask)
622
+ ip_range = IPAddress::IPv4.new("#{ip_def}/#{ip_mask}")
623
+ @ips_24_mask[ip_def][ip_mask] = []
624
+ (0..255).each do |ip_third|
625
+ ip_24 = "172.16.#{ip_third}.0/24"
626
+ @ips_24_mask[ip_def][ip_mask] << ip_24 if ip_range.include?(IPAddress::IPv4.new(ip_24))
627
+ end
628
+ end
629
+ @ips_24_mask[ip_def][ip_mask]
630
+ end
631
+
632
+ # Create a cluster of type IP range
633
+ #
634
+ # Parameters::
635
+ # * *ip* (String): The IP
636
+ # Result::
637
+ # * Hash<Symbol,Object>: Corresponding information to be stored in the graph
638
+ def ip_range_graph_info(ip)
639
+ ipv4 = IPAddress::IPv4.new(ip)
640
+ includes_proc = proc do |node_name|
641
+ if @nodes_graph[node_name][:ipv4].nil?
642
+ if is_node_cluster?(node_name)
643
+ # Here the node is a cluster that is not an IP range.
644
+ @nodes_graph[node_name][:includes].all? { |included_node_name| includes_proc.call(included_node_name) }
645
+ else
646
+ false
647
+ end
648
+ else
649
+ ipv4.include?(@nodes_graph[node_name][:ipv4])
650
+ end
651
+ end
652
+ {
653
+ type: :cluster,
654
+ connections: {},
655
+ includes: [],
656
+ ipv4: ipv4,
657
+ includes_proc: includes_proc
658
+ }
659
+ end
660
+
661
+ # Filter a JSON object.
662
+ # Any key from the JSON that is a leaf of the filter structure will be removed.
663
+ #
664
+ # Parameters::
665
+ # * *json* (Object): The JSON object
666
+ # * *json_filter* (Object): The JSON filter (or nil if none)
667
+ # Result::
668
+ # * *Object*: The filtered JSON object
669
+ def json_filter_out(json, json_filter)
670
+ if json.is_a?(Hash) && !json_filter.nil?
671
+ filtered_json = {}
672
+ json.each do |key, value|
673
+ if !json_filter.key?(key) || !json_filter[key].nil?
674
+ # We add this key in the result
675
+ filtered_json[key] = json_filter_out(value, json_filter[key])
676
+ end
677
+ end
678
+ filtered_json
679
+ else
680
+ json
681
+ end
682
+ end
683
+
684
+ # Get the complete JSON of a node
685
+ #
686
+ # Parameters::
687
+ # * *hostname* (String): Host name to fetch the complete JSON
688
+ # Result::
689
+ # * Hash: The corresponding JSON info
690
+ def node_json_for(hostname)
691
+ json_file_name = "#{@config[:json_files_dir]}/#{hostname}.json"
692
+ if File.exist?(json_file_name)
693
+ json_filter_out(JSON.parse(File.read(json_file_name)), @config[:ignore_json_keys])
694
+ else
695
+ log_warn "Missing JSON file #{json_file_name}"
696
+ {}
697
+ end
698
+ end
699
+
700
+ # Scrape connections from a JSON object.
701
+ # For each node found, return the list of labels.
702
+ #
703
+ # Parameters::
704
+ # * *json* (Object): JSON object
705
+ # * *current_ref* (String): The current reference. nil for the root.
706
+ # Result::
707
+ # * Hash<String,Array<String>>: List of references for each node.
708
+ def connections_from_json(json, current_ref = nil)
709
+ nodes = {}
710
+ if json.is_a?(String)
711
+ # Look for any IP
712
+ json.scan(@ip_regexp).each do |(ip_def, _grp_match, ip_mask_str)|
713
+ ip_mask = ip_mask_str.nil? ? 32 : ip_mask_str.to_i
714
+ ip_str =
715
+ if ip_mask == 32
716
+ ip_def
717
+ elsif ip_mask <= 24
718
+ "#{ip_def.split('.')[0..2].join('.')}.0/#{ip_mask}"
719
+ else
720
+ "#{ip_def}/#{ip_mask}"
721
+ end
722
+ # First check that we don't ignore this IP range
723
+ unless @ips_ignored.key?(ip_str)
724
+ connected_node_name =
725
+ if @nodes_graph.key?(ip_str)
726
+ # IP group already exists
727
+ ip_str
728
+ elsif @config[:ignore_ips].any? { |ip_regexp| ip_str =~ ip_regexp }
729
+ # This IP should be ignored
730
+ @ips_ignored[ip_str] = nil
731
+ nil
732
+ else
733
+ # New group to create.
734
+ if ip_mask <= 24
735
+ # This group will include all needed ip_24 IPs.
736
+ # Compute the list of 24 bits IPs that are referenced here.
737
+ ip_24_list =
738
+ if ip_mask == 24
739
+ [ip_str]
740
+ else
741
+ ips_24_matching_mask(ip_def, ip_mask).select do |ip|
742
+ unless @ips_ignored.key?(ip_str)
743
+ # Check if we should ignore it.
744
+ @ips_ignored[ip] = nil if @config[:ignore_ips].any? { |ip_regexp| ip =~ ip_regexp }
745
+ end
746
+ !@ips_ignored.key?(ip)
747
+ end
748
+ end
749
+ if ip_24_list.empty?
750
+ # All IPs of the group are to be ignored
751
+ nil
752
+ elsif ip_24_list.size == 1
753
+ # Just create 1 group.
754
+ ip_24 = ip_24_list.first
755
+ @nodes_graph[ip_24] = ip_range_graph_info(ip_24) unless @nodes_graph.key?(ip_24)
756
+ ip_24
757
+ else
758
+ # Create all ip_24 groups.
759
+ ip_24_list.each do |included_ip_24|
760
+ @nodes_graph[included_ip_24] = ip_range_graph_info(included_ip_24) unless @nodes_graph.key?(included_ip_24)
761
+ end
762
+ # Create a super group of it
763
+ @nodes_graph[ip_str] = ip_range_graph_info(ip_str)
764
+ @nodes_graph[ip_str][:includes] = ip_24_list
765
+ ip_str
766
+ end
767
+ else
768
+ # This group will include all individual IP addresses.
769
+ ips_list =
770
+ if ip_mask == 32
771
+ [ip_def]
772
+ else
773
+ ips_matching_mask(ip_def, ip_mask).select do |ip|
774
+ unless @ips_ignored.key?(ip_str)
775
+ # Check if we should ignore it.
776
+ @ips_ignored[ip] = nil if @config[:ignore_ips].any? { |ip_regexp| ip =~ ip_regexp }
777
+ end
778
+ !@ips_ignored.key?(ip)
779
+ end
780
+ end
781
+ if ips_list.empty?
782
+ # All IPs of the group are to be ignored
783
+ nil
784
+ elsif ips_list.size == 1
785
+ # Just create 1 node.
786
+ ip = ips_list.first
787
+ if @ips_to_host.key?(ip)
788
+ # Known hostname
789
+ @ips_to_host[ip]
790
+ else
791
+ # Unknown IP that should be added.
792
+ @nodes_graph[ip] = {
793
+ type: :unknown,
794
+ connections: {},
795
+ includes: [],
796
+ ipv4: IPAddress::IPv4.new(ip)
797
+ }
798
+ ip
799
+ end
800
+ else
801
+ # Create a super group of it
802
+ @nodes_graph[ip_str] = ip_range_graph_info(ip_str)
803
+ @nodes_graph[ip_str][:includes] = ips_list.map { |included_ip| @ips_to_host[included_ip] }
804
+ ip_str
805
+ end
806
+ end
807
+ end
808
+ unless connected_node_name.nil?
809
+ nodes[connected_node_name] = [] unless nodes.key?(connected_node_name)
810
+ nodes[connected_node_name] << current_ref
811
+ end
812
+ end
813
+ end
814
+ # Look for any known hostname
815
+ json.split(@non_word_regexp).each do |hostname|
816
+ if @known_nodes.key?(hostname)
817
+ nodes[hostname] = [] unless nodes.key?(hostname)
818
+ nodes[hostname] << current_ref
819
+ end
820
+ end
821
+ elsif json.is_a?(Array)
822
+ json.each do |sub_json|
823
+ nodes.merge!(connections_from_json(sub_json, current_ref)) { |_node_name, refs1, refs2| (refs1 + refs2).uniq }
824
+ end
825
+ elsif json.is_a?(Hash)
826
+ json.each do |sub_json_1, sub_json_2|
827
+ nodes.merge!(connections_from_json(sub_json_1, current_ref)) { |_node_name, refs1, refs2| (refs1 + refs2).uniq }
828
+ key_is_str = sub_json_1.is_a?(String)
829
+ nodes.merge!(connections_from_json(sub_json_2, key_is_str ? (current_ref.nil? ? sub_json_1 : "#{current_ref}/#{sub_json_1}") : current_ref)) { |_hostname, refs1, refs2| (refs1 + refs2).uniq } if !key_is_str || !@config[:ignore_any_json_keys].include?(sub_json_1)
830
+ end
831
+ end
832
+ nodes
833
+ end
834
+
835
+ # Fill all connections of a given hostname, up to a given recursive level.
836
+ #
837
+ # Parameters::
838
+ # * *hostname* (String): Hostname to parse for connections.
839
+ # * *max_level* (Integer): Maximum level of recursive passes (nil for no limit).
840
+ def parse_connections_for(hostname, max_level)
841
+ unless @nodes_graph.key?(hostname)
842
+ @nodes_graph[hostname] = {
843
+ type: :node,
844
+ connections: connections_from_json(node_json_for(hostname)),
845
+ includes: []
846
+ }
847
+ @nodes_graph[hostname][:ipv4] = IPAddress::IPv4.new(@node_metadata[hostname][:private_ips].first) if !@node_metadata[hostname][:private_ips].nil? && !@node_metadata[hostname][:private_ips].empty?
848
+ sub_max_level = max_level.nil? ? nil : max_level - 1
849
+ if sub_max_level != -1
850
+ @nodes_graph[hostname][:connections].keys.each do |connected_hostname|
851
+ parse_connections_for(connected_hostname, sub_max_level)
852
+ end
853
+ end
854
+ end
855
+ end
856
+
857
+ end
858
+
859
+ end