hybrid_platforms_conductor 32.10.0 → 32.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1103 -0
  3. data/LICENSE.md +31 -0
  4. data/README.md +395 -0
  5. data/bin/setup +1 -1
  6. data/docs/api.md +349 -0
  7. data/docs/config_dsl.md +315 -0
  8. data/docs/executables.md +226 -0
  9. data/docs/executables/check-node.md +155 -0
  10. data/docs/executables/deploy.md +198 -0
  11. data/docs/executables/dump_nodes_json.md +110 -0
  12. data/docs/executables/free_ips.md +93 -0
  13. data/docs/executables/free_veids.md +73 -0
  14. data/docs/executables/get_impacted_nodes.md +94 -0
  15. data/docs/executables/last_deploys.md +114 -0
  16. data/docs/executables/nodes_to_deploy.md +139 -0
  17. data/docs/executables/report.md +159 -0
  18. data/docs/executables/run.md +126 -0
  19. data/docs/executables/setup.md +92 -0
  20. data/docs/executables/ssh_config.md +151 -0
  21. data/docs/executables/test.md +213 -0
  22. data/docs/executables/topograph.md +139 -0
  23. data/docs/gen/mermaid/README.md-0.png +0 -0
  24. data/docs/gen/mermaid/docs/executables/check-node.md-0.png +0 -0
  25. data/docs/gen/mermaid/docs/executables/deploy.md-0.png +0 -0
  26. data/docs/gen/mermaid/docs/executables/free_ips.md-0.png +0 -0
  27. data/docs/gen/mermaid/docs/executables/free_veids.md-0.png +0 -0
  28. data/docs/gen/mermaid/docs/executables/get_impacted_nodes.md-0.png +0 -0
  29. data/docs/gen/mermaid/docs/executables/last_deploys.md-0.png +0 -0
  30. data/docs/gen/mermaid/docs/executables/nodes_to_deploy.md-0.png +0 -0
  31. data/docs/gen/mermaid/docs/executables/report.md-0.png +0 -0
  32. data/docs/gen/mermaid/docs/executables/run.md-0.png +0 -0
  33. data/docs/gen/mermaid/docs/executables/setup.md-0.png +0 -0
  34. data/docs/gen/mermaid/docs/executables/ssh_config.md-0.png +0 -0
  35. data/docs/gen/mermaid/docs/executables/test.md-0.png +0 -0
  36. data/docs/install.md +161 -0
  37. data/docs/plugins.md +215 -0
  38. data/docs/plugins/action/bash.md +37 -0
  39. data/docs/plugins/action/interactive.md +37 -0
  40. data/docs/plugins/action/remote_bash.md +67 -0
  41. data/docs/plugins/action/ruby.md +69 -0
  42. data/docs/plugins/action/scp.md +61 -0
  43. data/docs/plugins/cmdb/config.md +46 -0
  44. data/docs/plugins/cmdb/host_ip.md +33 -0
  45. data/docs/plugins/cmdb/host_keys.md +33 -0
  46. data/docs/plugins/cmdb/platform_handlers.md +33 -0
  47. data/docs/plugins/connector/local.md +28 -0
  48. data/docs/plugins/connector/ssh.md +95 -0
  49. data/docs/plugins/platform_handler/yaml_inventory.md +105 -0
  50. data/docs/plugins/provisioner/docker.md +27 -0
  51. data/docs/plugins/provisioner/podman.md +27 -0
  52. data/docs/plugins/provisioner/proxmox.md +115 -0
  53. data/docs/plugins/report/confluence.md +49 -0
  54. data/docs/plugins/report/mediawiki.md +28 -0
  55. data/docs/plugins/report/stdout.md +32 -0
  56. data/docs/plugins/test/bitbucket_conf.md +97 -0
  57. data/docs/plugins/test/can_be_checked.md +27 -0
  58. data/docs/plugins/test/check_deploy_and_idempotence.md +61 -0
  59. data/docs/plugins/test/check_from_scratch.md +28 -0
  60. data/docs/plugins/test/connection.md +27 -0
  61. data/docs/plugins/test/deploy_freshness.md +27 -0
  62. data/docs/plugins/test/deploy_from_scratch.md +28 -0
  63. data/docs/plugins/test/deploy_removes_root_access.md +29 -0
  64. data/docs/plugins/test/divergence.md +41 -0
  65. data/docs/plugins/test/executables.md +26 -0
  66. data/docs/plugins/test/file_system.md +49 -0
  67. data/docs/plugins/test/file_system_hdfs.md +65 -0
  68. data/docs/plugins/test/hostname.md +27 -0
  69. data/docs/plugins/test/idempotence.md +56 -0
  70. data/docs/plugins/test/ip.md +28 -0
  71. data/docs/plugins/test/jenkins_ci_conf.md +54 -0
  72. data/docs/plugins/test/jenkins_ci_masters_ok.md +54 -0
  73. data/docs/plugins/test/linear_strategy.md +26 -0
  74. data/docs/plugins/test/local_users.md +48 -0
  75. data/docs/plugins/test/mounts.md +55 -0
  76. data/docs/plugins/test/orphan_files.md +38 -0
  77. data/docs/plugins/test/ports.md +50 -0
  78. data/docs/plugins/test/private_ips.md +27 -0
  79. data/docs/plugins/test/public_ips.md +27 -0
  80. data/docs/plugins/test/spectre.md +26 -0
  81. data/docs/plugins/test/veids.md +27 -0
  82. data/docs/plugins/test/vulnerabilities.md +65 -0
  83. data/docs/plugins/test_report/confluence.md +43 -0
  84. data/docs/plugins/test_report/stdout.md +26 -0
  85. data/docs/plugins_create.md +135 -0
  86. data/docs/tutorial.md +57 -0
  87. data/docs/tutorial/01_installation.md +129 -0
  88. data/docs/tutorial/02_first_node.md +466 -0
  89. data/docs/tutorial/03_scale.md +876 -0
  90. data/docs/tutorial/04_test.md +965 -0
  91. data/docs/tutorial/05_extend_with_plugins.md +1132 -0
  92. data/examples/bare/Gemfile +4 -0
  93. data/examples/bare/hpc_config.rb +2 -0
  94. data/examples/localhost/Gemfile +4 -0
  95. data/examples/localhost/hpc_config.rb +2 -0
  96. data/examples/localhost/inventory.yaml +4 -0
  97. data/lib/hybrid_platforms_conductor/actions_executor.rb +1 -0
  98. data/lib/hybrid_platforms_conductor/common_config_dsl/idempotence_tests.rb +23 -1
  99. data/lib/hybrid_platforms_conductor/deployer.rb +3 -2
  100. data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +29 -13
  101. data/lib/hybrid_platforms_conductor/hpc_plugins/action/scp.rb +1 -1
  102. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/local.rb +98 -0
  103. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +2 -2
  104. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +15 -4
  105. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/platform_handler_plugin.rb.sample +5 -5
  106. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/yaml_inventory.rb +140 -0
  107. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +6 -3
  108. data/lib/hybrid_platforms_conductor/hpc_plugins/report/templates/confluence_inventory.html.erb +1 -1
  109. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -4
  110. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +4 -1
  111. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +1 -1
  112. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +19 -17
  113. data/lib/hybrid_platforms_conductor/hpc_plugins/test/divergence.rb +19 -1
  114. data/lib/hybrid_platforms_conductor/hpc_plugins/test/executables.rb +27 -13
  115. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +2 -1
  116. data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +4 -1
  117. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +2 -1
  118. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +2 -1
  119. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +4 -3
  120. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +2 -1
  121. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +1 -1
  122. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +8 -7
  123. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +1 -1
  124. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/templates/confluence.html.erb +1 -1
  125. data/lib/hybrid_platforms_conductor/json_dumper.rb +1 -1
  126. data/lib/hybrid_platforms_conductor/platform_handler.rb +1 -1
  127. data/lib/hybrid_platforms_conductor/services_handler.rb +18 -16
  128. data/lib/hybrid_platforms_conductor/tests_runner.rb +0 -1
  129. data/lib/hybrid_platforms_conductor/topographer.rb +0 -1
  130. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  131. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +16 -0
  132. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/connectable_nodes_spec.rb +30 -0
  133. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/remote_actions_spec.rb +113 -0
  134. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/cli_options_spec.rb +6 -2
  135. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +38 -1
  136. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +37 -4
  137. data/spec/hybrid_platforms_conductor_test/docs_spec.rb +10 -0
  138. data/tools/check_md +89 -0
  139. data/tools/generate_mermaid +75 -0
  140. metadata +207 -12
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Orchestrate all the platforms with Hybrid Platforms Conductor
4
+ gem 'hybrid_platforms_conductor', path: '../..'
@@ -0,0 +1,2 @@
1
+ # Just a bare configuration.
2
+ # It should work already like this.
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Orchestrate all the platforms with Hybrid Platforms Conductor
4
+ gem 'hybrid_platforms_conductor', path: '../..'
@@ -0,0 +1,2 @@
1
+ # This configuration file just defines 1 node being localhost
2
+ yaml_inventory_platform path: '.'
@@ -0,0 +1,4 @@
1
+ ---
2
+ local:
3
+ metadata:
4
+ host_ip: 127.0.0.1
@@ -5,6 +5,7 @@ require 'securerandom'
5
5
  require 'tmpdir'
6
6
  require 'hybrid_platforms_conductor/action'
7
7
  require 'hybrid_platforms_conductor/cmd_runner'
8
+ require 'hybrid_platforms_conductor/config'
8
9
  require 'hybrid_platforms_conductor/connector'
9
10
  require 'hybrid_platforms_conductor/io_router'
10
11
  require 'hybrid_platforms_conductor/logger_helpers'
@@ -11,13 +11,24 @@ module HybridPlatformsConductor
11
11
  # Array< Hash<Symbol, Object> >
12
12
  attr_reader :ignored_idempotence_tasks
13
13
 
14
- # Initialize the DSL
14
+ # List of ignored tasks info. Each info has the following properties:
15
+ # * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule
16
+ # * *ignored_tasks* (Hash<String, String>): List of task names for which we ignore divergence errors, with the corresponding descriptive reason for ignore.
17
+ # Array< Hash<Symbol, Object> >
18
+ attr_reader :ignored_divergent_tasks
19
+
20
+ # Initialize the DSL
15
21
  def init_idempotence_tests
16
22
  # List of ignored tasks info. Each info has the following properties:
17
23
  # * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule
18
24
  # * *ignored_tasks* (Hash<String, String>): List of task names for which we ignore idempotence errors, with the corresponding descriptive reason for ignore.
19
25
  # Array< Hash<Symbol, Object> >
20
26
  @ignored_idempotence_tasks = []
27
+ # List of ignored tasks info. Each info has the following properties:
28
+ # * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule
29
+ # * *ignored_tasks* (Hash<String, String>): List of task names for which we ignore divergence errors, with the corresponding descriptive reason for ignore.
30
+ # Array< Hash<Symbol, Object> >
31
+ @ignored_divergent_tasks = []
21
32
  end
22
33
 
23
34
  # Ignore idempotence errors on a set of tasks
@@ -31,6 +42,17 @@ module HybridPlatformsConductor
31
42
  }
32
43
  end
33
44
 
45
+ # Ignore idempotence errors on a set of tasks
46
+ #
47
+ # Parameters::
48
+ # * *tasks_to_ignore* (Hash<String, String>): Set of tasks to ignore, along with the reason
49
+ def ignore_divergent_tasks(tasks_to_ignore)
50
+ @ignored_divergent_tasks << {
51
+ ignored_tasks: tasks_to_ignore,
52
+ nodes_selectors_stack: current_nodes_selectors_stack,
53
+ }
54
+ end
55
+
34
56
  end
35
57
 
36
58
  end
@@ -41,7 +41,7 @@ module HybridPlatformsConductor
41
41
 
42
42
  include LoggerHelpers
43
43
 
44
- Config.extend_config_dsl_with ConfigDSLExtension, :init_nodes_handler_config
44
+ Config.extend_config_dsl_with ConfigDSLExtension, :init_deployer_config
45
45
 
46
46
  # Do we use why-run mode while deploying? [default = false]
47
47
  # Boolean
@@ -336,7 +336,8 @@ module HybridPlatformsConductor
336
336
  actions_executor.connector(:ssh).passwords[node] = 'root_pwd'
337
337
  deployer.local_environment = true
338
338
  # Ignore secrets that might have been given: in Docker containers we always use dummy secrets
339
- deployer.secrets = [JSON.parse(File.read("#{@config.hybrid_platforms_dir}/dummy_secrets.json"))]
339
+ dummy_secrets_file = "#{@config.hybrid_platforms_dir}/dummy_secrets.json"
340
+ deployer.secrets = File.exist?(dummy_secrets_file) ? [JSON.parse(File.read(dummy_secrets_file))] : []
340
341
  yield deployer, instance
341
342
  end
342
343
  rescue
@@ -14,16 +14,33 @@ module HybridPlatformsConductor
14
14
  # [API] - @actions_executor is accessible
15
15
  #
16
16
  # Parameters::
17
- # * *remote_bash* (Array< Hash<Symbol, Object> or Array<String> or String>): List of bash actions to execute. Each action can have the following properties:
18
- # * *commands* (Array<String> or String): List of bash commands to execute (can be a single one). This is the default property also that allows to not use the Hash form for brevity.
19
- # * *file* (String): Name of file from which commands should be taken.
20
- # * *env* (Hash<String, String>): Environment variables to be set before executing those commands.
17
+ # * *remote_bash* (Array or Object): List of commands (or single command) to be executed. Each command can be the following:
18
+ # * String: Simple bash command.
19
+ # * Hash<Symbol, Object>: Information about the commands to execute. Can have the following properties:
20
+ # * *commands* (Array<String> or String): List of bash commands to execute (can be a single one) [default: ''].
21
+ # * *file* (String): Name of file from which commands should be taken [optional].
22
+ # * *env* (Hash<String, String>): Environment variables to be set before executing those commands [default: {}].
21
23
  def setup(remote_bash)
22
- @remote_bash = remote_bash
23
- # Normalize the parameters
24
- @remote_bash = [@remote_bash] if @remote_bash.is_a?(String)
25
- @remote_bash = { commands: @remote_bash } if @remote_bash.is_a?(Array)
26
- @remote_bash[:commands] = [@remote_bash[:commands]] if @remote_bash[:commands].is_a?(String)
24
+ # Normalize the parameters.
25
+ # Array< Hash<Symbol,Object> >: Simple array of info:
26
+ # * *commands* (Array<String>): List of bash commands to execute.
27
+ # * *env* (Hash<String, String>): Environment variables to be set before executing those commands.
28
+ @remote_bash = (remote_bash.is_a?(Array) ? remote_bash : [remote_bash]).map do |cmd_info|
29
+ if cmd_info.is_a?(String)
30
+ {
31
+ commands: [cmd_info],
32
+ env: {}
33
+ }
34
+ else
35
+ commands = []
36
+ commands.concat(cmd_info[:commands].is_a?(String) ? [cmd_info[:commands]] : cmd_info[:commands]) if cmd_info[:commands]
37
+ commands << File.read(cmd_info[:file]) if cmd_info[:file]
38
+ {
39
+ commands: commands,
40
+ env: cmd_info[:env] || {}
41
+ }
42
+ end
43
+ end
27
44
  end
28
45
 
29
46
  # Do we need a connector to execute this action on a node?
@@ -46,10 +63,9 @@ module HybridPlatformsConductor
46
63
  # [API] - @stderr_io can be used to log stderr messages
47
64
  # [API] - run_cmd(String) method can be used to execute a command. See CmdRunner#run_cmd to know about the result's signature.
48
65
  def execute
49
- bash_commands = (@remote_bash[:env] || {}).map { |var_name, var_value| "export #{var_name}='#{var_value}'" }
50
- bash_commands.concat(@remote_bash[:commands].clone) if @remote_bash.key?(:commands)
51
- bash_commands << File.read(@remote_bash[:file]) if @remote_bash.key?(:file)
52
- bash_str = bash_commands.join("\n")
66
+ bash_str = @remote_bash.map do |cmd_info|
67
+ (cmd_info[:env].map { |var_name, var_value| "export #{var_name}='#{var_value}'" } + cmd_info[:commands]).join("\n")
68
+ end.join("\n")
53
69
  log_debug "[#{@node}] - Execute remote Bash commands \"#{bash_str}\"..."
54
70
  @connector.remote_bash bash_str
55
71
  end
@@ -16,7 +16,7 @@ module HybridPlatformsConductor
16
16
  # Parameters::
17
17
  # * *mappings* (Hash<String or Symbol, Object>): Set of couples source => destination_dir to copy files or directories from the local file system to the remote file system.
18
18
  # The following properties can also be used:
19
- # * *sudo* (Boolean): Do we use sudo to make the copy? [default: false]
19
+ # * *sudo* (Boolean): Do we use sudo on the remote to make the copy? [default: false]
20
20
  # * *owner* (String or nil): Owner to use for files, or nil to use current one [default: nil]
21
21
  # * *group* (String or nil): Group to use for files, or nil to use current one [default: nil]
22
22
  def setup(mappings)
@@ -0,0 +1,98 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+
4
+ module HybridPlatformsConductor
5
+
6
+ module HpcPlugins
7
+
8
+ module Connector
9
+
10
+ # Connector executing remote commands on the local environment in dedicated workspaces (/tmp/hpc_local_workspaces)
11
+ class Local < HybridPlatformsConductor::Connector
12
+
13
+ # Select nodes where this connector can connect.
14
+ # [API] - This method is mandatory
15
+ # [API] - @cmd_runner can be used
16
+ # [API] - @nodes_handler can be used
17
+ #
18
+ # Parameters::
19
+ # * *nodes* (Array<String>): List of candidate nodes
20
+ # Result::
21
+ # * Array<String>: List of nodes we can connect to from the candidates
22
+ def connectable_nodes_from(nodes)
23
+ @nodes_handler.prefetch_metadata_of nodes, :local_node
24
+ nodes.select { |node| @nodes_handler.get_local_node_of(node) }
25
+ end
26
+
27
+ # Run bash commands on a given node.
28
+ # [API] - This method is mandatory
29
+ # [API] - If defined, then with_connection_to has been called before this method.
30
+ # [API] - @cmd_runner can be used
31
+ # [API] - @nodes_handler can be used
32
+ # [API] - @node can be used to access the node on which we execute the remote bash
33
+ # [API] - @timeout can be used to know when the action should fail
34
+ # [API] - @stdout_io can be used to send stdout output
35
+ # [API] - @stderr_io can be used to send stderr output
36
+ #
37
+ # Parameters::
38
+ # * *bash_cmds* (String): Bash commands to execute
39
+ def remote_bash(bash_cmds)
40
+ run_cmd "cd #{workspace_for(@node)} ; #{bash_cmds}"
41
+ end
42
+
43
+ # Execute an interactive shell on the remote node
44
+ # [API] - This method is mandatory
45
+ # [API] - If defined, then with_connection_to has been called before this method.
46
+ # [API] - @cmd_runner can be used
47
+ # [API] - @nodes_handler can be used
48
+ # [API] - @node can be used to access the node on which we execute the remote bash
49
+ # [API] - @timeout can be used to know when the action should fail
50
+ # [API] - @stdout_io can be used to send stdout output
51
+ # [API] - @stderr_io can be used to send stderr output
52
+ def remote_interactive
53
+ system "cd #{workspace_for(@node)} ; /bin/bash"
54
+ end
55
+
56
+ # Copy a file to the remote node in a directory
57
+ # [API] - This method is mandatory
58
+ # [API] - If defined, then with_connection_to has been called before this method.
59
+ # [API] - @cmd_runner can be used
60
+ # [API] - @nodes_handler can be used
61
+ # [API] - @node can be used to access the node on which we execute the remote bash
62
+ # [API] - @timeout can be used to know when the action should fail
63
+ # [API] - @stdout_io can be used to send stdout output
64
+ # [API] - @stderr_io can be used to send stderr output
65
+ #
66
+ # Parameters::
67
+ # * *from* (String): Local file to copy
68
+ # * *to* (String): Remote directory to copy to
69
+ # * *sudo* (Boolean): Do we use sudo to copy? [default: false]
70
+ # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
71
+ # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
72
+ def remote_copy(from, to, sudo: false, owner: nil, group: nil)
73
+ # If the destination is a relative path, prepend the workspace dir to it.
74
+ to = "#{workspace_for(@node)}/#{to}" unless to.start_with?('/')
75
+ FileUtils.cp_r from, to
76
+ end
77
+
78
+ private
79
+
80
+ # Create or reuse a dedicated workspace for a node
81
+ #
82
+ # Parameters::
83
+ # * *node* (String): Node for which we want a dedicated workspace
84
+ # Result::
85
+ # * String: Dedicated workspace path
86
+ def workspace_for(node)
87
+ workspace = "#{Dir.tmpdir}/hpc_local_workspaces/#{node}"
88
+ FileUtils.mkdir_p workspace
89
+ workspace
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ end
@@ -73,7 +73,7 @@ module HybridPlatformsConductor
73
73
  # Result::
74
74
  # * Array<String>: List of nodes we can connect to from the candidates
75
75
  def connectable_nodes_from(nodes)
76
- nodes.select { |node| @nodes_handler.get_ip_of(node) =~ /^192\.168\..+$/ }
76
+ nodes.select { |node| @nodes_handler.get_host_ip_of(node) =~ /^192\.168\..+$/ }
77
77
  end
78
78
 
79
79
  # Prepare connections to a given set of nodes.
@@ -135,7 +135,7 @@ module HybridPlatformsConductor
135
135
  # Parameters::
136
136
  # * *from* (String): Local file to copy
137
137
  # * *to* (String): Remote directory to copy to
138
- # * *sudo* (Boolean): Do we use sudo to copy? [default: false]
138
+ # * *sudo* (Boolean): Do we use sudo on the remote to copy? [default: false]
139
139
  # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
140
140
  # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
141
141
  def remote_copy(from, to, sudo: false, owner: nil, group: nil)
@@ -131,6 +131,10 @@ module HybridPlatformsConductor
131
131
  # Default values
132
132
  @ssh_user = ENV['hpc_ssh_user']
133
133
  @ssh_user = ENV['USER'] if @ssh_user.nil? || @ssh_user.empty?
134
+ if @ssh_user.nil? || @ssh_user.empty?
135
+ _exit_status, stdout = @cmd_runner.run_cmd 'whoami', log_to_stdout: log_debug?
136
+ @ssh_user = stdout.strip
137
+ end
134
138
  @ssh_use_control_master = true
135
139
  @ssh_strict_host_key_checking = true
136
140
  @passwords = {}
@@ -236,9 +240,9 @@ module HybridPlatformsConductor
236
240
  ssh_cmd =
237
241
  if @nodes_handler.get_ssh_session_exec_of(@node) == 'false'
238
242
  # When ExecSession is disabled we need to use stdin directly
239
- "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'EOF'\n#{bash_cmds}\nEOF"
243
+ "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'HPC_EOF'\n#{bash_cmds}\nHPC_EOF"
240
244
  else
241
- "#{ssh_exec} #{ssh_url} /bin/bash <<'EOF'\n#{bash_cmds}\nEOF"
245
+ "#{ssh_exec} #{ssh_url} /bin/bash <<'HPC_EOF'\n#{bash_cmds}\nHPC_EOF"
242
246
  end
243
247
  # Due to a limitation of Process.spawn, each individual argument is limited to 128KB of size.
244
248
  # Therefore we need to make sure that if bash_cmds exceeds MAX_CMD_ARG_LENGTH bytes (considering EOF chars) then we use an intermediary shell script to store the commands.
@@ -292,13 +296,20 @@ module HybridPlatformsConductor
292
296
  # Parameters::
293
297
  # * *from* (String): Local file to copy
294
298
  # * *to* (String): Remote directory to copy to
295
- # * *sudo* (Boolean): Do we use sudo to copy? [default: false]
299
+ # * *sudo* (Boolean): Do we use sudo on the remote to copy? [default: false]
296
300
  # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
297
301
  # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
298
302
  def remote_copy(from, to, sudo: false, owner: nil, group: nil)
299
303
  if @nodes_handler.get_ssh_session_exec_of(@node) == 'false'
300
304
  # We don't have ExecSession, so don't use ssh, but scp instead.
301
- run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:#{to}"
305
+ if sudo
306
+ # We need to first copy the file in an accessible directory, and then sudo mv
307
+ remote_bash('mkdir -p hpc_tmp_scp')
308
+ run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:./hpc_tmp_scp"
309
+ remote_bash("#{@nodes_handler.sudo_on(@node)} mv ./hpc_tmp_scp/#{File.basename(from)} #{to}")
310
+ else
311
+ run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:#{to}"
312
+ end
302
313
  else
303
314
  run_cmd <<~EOS
304
315
  cd #{File.dirname(from)} && \
@@ -69,7 +69,7 @@ module HybridPlatformsConductor
69
69
  end
70
70
 
71
71
  # Setup the platform, install dependencies...
72
- # [API] - This method is mandatory.
72
+ # [API] - This method is optional.
73
73
  # [API] - @cmd_runner is accessible.
74
74
  def setup
75
75
  # This method is called by the setup executable.
@@ -163,7 +163,7 @@ module HybridPlatformsConductor
163
163
  end
164
164
 
165
165
  # Package the repository, ready to be deployed on artefacts or directly to a node.
166
- # [API] - This method is mandatory.
166
+ # [API] - This method is optional.
167
167
  # [API] - @cmd_runner is accessible.
168
168
  # [API] - @actions_executor is accessible.
169
169
  #
@@ -212,7 +212,7 @@ module HybridPlatformsConductor
212
212
  # This method returns all the actions to execute to deploy on a node.
213
213
  # The use_why_run switch is on if the deployment should just be simulated.
214
214
  # Those actions (bash commands, scp of files, ruby code...) should be thread safe as they can be executed in parallel with other deployment actions for other nodes in case of a concurrent deployment on several nodes.
215
- # The complete description of an action can be found in actions_executor.rb file, in the execute_actions_on method description.
215
+ # The complete description of an action can be found in the action plugins' documentation.
216
216
  [
217
217
  {
218
218
  scp: {
@@ -229,7 +229,7 @@ module HybridPlatformsConductor
229
229
  end
230
230
 
231
231
  # Prepare a why-run deployment so that a JSON file describing the nodes will be output in the run_logs.
232
- # [API] - This method is mandatory.
232
+ # [API] - This method is optional.
233
233
  # [API] - @cmd_runner is accessible.
234
234
  # [API] - @actions_executor is accessible.
235
235
  # [API] - @deployer is accessible.
@@ -247,7 +247,7 @@ module HybridPlatformsConductor
247
247
  # Result::
248
248
  # * Array< Hash<Symbol,Object> >: List of task properties. The following properties should be returned, among free ones:
249
249
  # * *name* (String): Task name
250
- # * *status* (Symbol): Task status. Should be on of:
250
+ # * *status* (Symbol): Task status. Should be one of:
251
251
  # * *:changed*: The task has been changed
252
252
  # * *:identical*: The task has not been changed
253
253
  # * *diffs* (String): Differences, if any
@@ -0,0 +1,140 @@
1
+ require 'yaml'
2
+ require 'hybrid_platforms_conductor/platform_handler'
3
+
4
+ module HybridPlatformsConductor
5
+
6
+ module HpcPlugins
7
+
8
+ module PlatformHandler
9
+
10
+ # Basic platform handler, reading inventory and metadata from simple Yaml files.
11
+ class YamlInventory < HybridPlatformsConductor::PlatformHandler
12
+
13
+ # Initialize a new instance of this platform handler.
14
+ # [API] - This method is optional.
15
+ # [API] - @cmd_runner is accessible.
16
+ def init
17
+ # This method is called when initializing a new instance of this platform handler, for a given repository.
18
+ inv_file = "#{@repository_path}/inventory.yaml"
19
+ @inventory = File.exist?(inv_file) ? YAML.load(File.read(inv_file)) : {}
20
+ end
21
+
22
+ # Get the list of known nodes.
23
+ # [API] - This method is mandatory.
24
+ #
25
+ # Result::
26
+ # * Array<String>: List of node names
27
+ def known_nodes
28
+ @inventory.keys
29
+ end
30
+
31
+ # Get the metadata of a given node.
32
+ # [API] - This method is mandatory.
33
+ #
34
+ # Parameters::
35
+ # * *node* (String): Node to read metadata from
36
+ # Result::
37
+ # * Hash<Symbol,Object>: The corresponding metadata
38
+ def metadata_for(node)
39
+ (@inventory[node]['metadata'] || {}).transform_keys(&:to_sym)
40
+ end
41
+
42
+ # Return the services for a given node
43
+ # [API] - This method is mandatory.
44
+ #
45
+ # Parameters::
46
+ # * *node* (String): node to read configuration from
47
+ # Result::
48
+ # * Array<String>: The corresponding services
49
+ def services_for(node)
50
+ @inventory[node]['services'] || []
51
+ end
52
+
53
+ # Get the list of services we can deploy
54
+ # [API] - This method is mandatory.
55
+ #
56
+ # Result::
57
+ # * Array<String>: The corresponding services
58
+ def deployable_services
59
+ Dir.glob("#{@repository_path}/service_*.rb").map { |file| File.basename(file).match(/^service_(.*)\.rb$/)[1] }
60
+ end
61
+
62
+ # Get the list of actions to perform to deploy on a given node.
63
+ # Those actions can be executed in parallel with other deployments on other nodes. They must be thread safe.
64
+ # [API] - This method is mandatory.
65
+ # [API] - @cmd_runner is accessible.
66
+ # [API] - @actions_executor is accessible.
67
+ #
68
+ # Parameters::
69
+ # * *node* (String): Node to deploy on
70
+ # * *service* (String): Service to be deployed
71
+ # * *use_why_run* (Boolean): Do we use a why-run mode? [default = true]
72
+ # Result::
73
+ # * Array< Hash<Symbol,Object> >: List of actions to be done
74
+ def actions_to_deploy_on(node, service, use_why_run: true)
75
+ # Load the check and deploy methods in a temporary class for encapsulation
76
+ service_file = "#{@repository_path}/service_#{service}.rb"
77
+ Class.new do
78
+
79
+ include LoggerHelpers
80
+
81
+ # Constructor
82
+ #
83
+ # Parameters::
84
+ # * *platform_handler* (PlatformHandler): PlatformHandler needing this service to be deployed
85
+ # * *logger* (Logger): Logger to be used [default: Logger.new(STDOUT)]
86
+ # * *logger_stderr* (Logger): Logger to be used for stderr [default: Logger.new(STDERR)]
87
+ # * *config* (Config): Config to be used. [default: Config.new]
88
+ # * *nodes_handler* (NodesHandler): NodesHandler to be used [default: NodesHandler.new]
89
+ # * *cmd_runner* (CmdRunner): CmdRunner to be used [default: CmdRunner.new]
90
+ def initialize(
91
+ platform_handler,
92
+ logger: Logger.new(STDOUT),
93
+ logger_stderr: Logger.new(STDERR),
94
+ config: Config.new,
95
+ nodes_handler: NodesHandler.new,
96
+ cmd_runner: CmdRunner.new
97
+ )
98
+ init_loggers(logger, logger_stderr)
99
+ @platform_handler = platform_handler
100
+ @config = config
101
+ @nodes_handler = nodes_handler
102
+ @cmd_runner = cmd_runner
103
+ end
104
+
105
+ class_eval(File.read(service_file))
106
+
107
+ end.new(
108
+ self,
109
+ logger: @logger,
110
+ logger_stderr: @logger_stderr,
111
+ config: @config,
112
+ nodes_handler: @nodes_handler,
113
+ cmd_runner: @cmd_runner
114
+ ).send(use_why_run ? :check : :deploy, node)
115
+ end
116
+
117
+ # Parse stdout and stderr of a given deploy run and get the list of tasks with their status
118
+ # [API] - This method is mandatory.
119
+ #
120
+ # Parameters::
121
+ # * *stdout* (String): stdout to be parsed
122
+ # * *stderr* (String): stderr to be parsed
123
+ # Result::
124
+ # * Array< Hash<Symbol,Object> >: List of task properties. The following properties should be returned, among free ones:
125
+ # * *name* (String): Task name
126
+ # * *status* (Symbol): Task status. Should be one of:
127
+ # * *:changed*: The task has been changed
128
+ # * *:identical*: The task has not been changed
129
+ # * *diffs* (String): Differences, if any
130
+ def parse_deploy_output(stdout, stderr)
131
+ []
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+
140
+ end