hybrid_platforms_conductor 32.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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