hybrid_platforms_conductor 33.3.0 → 33.7.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +31 -2
  4. data/docs/config_dsl.md +45 -0
  5. data/docs/plugins/cmdb/host_keys.md +3 -1
  6. data/docs/plugins/connector/ssh.md +1 -0
  7. data/lib/hybrid_platforms_conductor/actions_executor.rb +29 -1
  8. data/lib/hybrid_platforms_conductor/bitbucket.rb +134 -90
  9. data/lib/hybrid_platforms_conductor/cmd_runner.rb +4 -4
  10. data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +12 -44
  11. data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +9 -31
  12. data/lib/hybrid_platforms_conductor/config.rb +2 -0
  13. data/lib/hybrid_platforms_conductor/confluence.rb +93 -88
  14. data/lib/hybrid_platforms_conductor/connector.rb +5 -2
  15. data/lib/hybrid_platforms_conductor/credentials.rb +122 -97
  16. data/lib/hybrid_platforms_conductor/deployer.rb +7 -9
  17. data/lib/hybrid_platforms_conductor/github.rb +39 -0
  18. data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +1 -1
  19. data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +27 -17
  20. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +13 -12
  21. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/local.rb +6 -4
  22. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +1 -1
  23. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +37 -25
  24. data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +5 -6
  25. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/serverless_chef.rb +1 -1
  26. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +1 -1
  27. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +7 -4
  28. data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +3 -1
  29. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +3 -2
  30. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +3 -1
  31. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -1
  32. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +17 -3
  33. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +30 -10
  34. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +1 -1
  35. data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +4 -1
  36. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +1 -2
  37. data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +1 -1
  38. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +1 -2
  39. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +7 -3
  40. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +8 -4
  41. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +1 -2
  42. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +1 -2
  43. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +1 -2
  44. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +1 -1
  45. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +1 -2
  46. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +3 -1
  47. data/lib/hybrid_platforms_conductor/logger_helpers.rb +24 -1
  48. data/lib/hybrid_platforms_conductor/test.rb +21 -7
  49. data/lib/hybrid_platforms_conductor/tests_runner.rb +7 -6
  50. data/lib/hybrid_platforms_conductor/thycotic.rb +80 -75
  51. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  52. data/spec/hybrid_platforms_conductor_test.rb +6 -0
  53. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +15 -0
  54. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +32 -0
  55. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/remote_actions_spec.rb +87 -0
  56. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +30 -0
  57. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +10 -0
  58. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +38 -0
  59. data/spec/hybrid_platforms_conductor_test/api/actions_executor/helpers_spec.rb +195 -0
  60. data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +14 -0
  61. data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +11 -0
  62. data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +251 -0
  63. data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +215 -0
  64. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +280 -319
  65. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +2 -2
  66. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +49 -10
  67. data/spec/hybrid_platforms_conductor_test/api/platform_handlers/serverless_chef/services_deployment_spec.rb +38 -0
  68. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +49 -69
  69. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +29 -39
  70. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +5 -3
  71. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/chef_versions.yml +3 -0
  72. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/nodes/node.json +15 -0
  73. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/policyfiles/test_policy.rb +3 -0
  74. data/spec/hybrid_platforms_conductor_test/shared_examples/deployer.rb +134 -0
  75. data/spec/hybrid_platforms_conductor_test/test_connector.rb +2 -2
  76. metadata +36 -2
@@ -106,10 +106,10 @@ module HybridPlatformsConductor
106
106
 
107
107
  end
108
108
 
109
- include LoggerHelpers
110
-
111
109
  Config.extend_config_dsl_with ConfigDSLExtension, :init_deployer_config
112
110
 
111
+ include LoggerHelpers
112
+
113
113
  # Do we use why-run mode while deploying? [default = false]
114
114
  # Boolean
115
115
  attr_accessor :use_why_run
@@ -544,15 +544,13 @@ module HybridPlatformsConductor
544
544
  # Result::
545
545
  # * Hash<String, [Integer or Symbol, String, String]>: Exit status code (or Symbol in case of error or dry run), standard output and error for each node.
546
546
  def deploy(services)
547
- # Get the ssh user directly from the connector
548
- ssh_user = @actions_executor.connector(:ssh).ssh_user
549
-
550
547
  # Deploy for real
551
548
  @nodes_handler.prefetch_metadata_of services.keys, :image
552
549
  outputs = @actions_executor.execute_actions(
553
550
  services.map do |node, node_services|
554
551
  image_id = @nodes_handler.get_image_of(node)
555
- sudo = (ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} ")
552
+ need_sudo = !@actions_executor.privileged_access?(node)
553
+ sudo = @actions_executor.sudo_prefix(node)
556
554
  # Install corporate certificates if present
557
555
  certificate_actions =
558
556
  if @local_environment && ENV['hpc_certificates']
@@ -568,7 +566,7 @@ module HybridPlatformsConductor
568
566
  {
569
567
  scp: {
570
568
  ENV['hpc_certificates'] => '/usr/local/share/ca-certificates',
571
- :sudo => ssh_user != 'root'
569
+ :sudo => need_sudo
572
570
  },
573
571
  remote_bash: "#{sudo}update-ca-certificates"
574
572
  }
@@ -584,7 +582,7 @@ module HybridPlatformsConductor
584
582
  cert_file,
585
583
  '/etc/pki/ca-trust/source/anchors'
586
584
  ]
587
- end.to_h.merge(sudo: ssh_user != 'root'),
585
+ end.to_h.merge(sudo: need_sudo),
588
586
  remote_bash: [
589
587
  "#{sudo}update-ca-trust enable",
590
588
  "#{sudo}update-ca-trust extract"
@@ -619,7 +617,7 @@ module HybridPlatformsConductor
619
617
  services.keys.map do |node|
620
618
  [
621
619
  node,
622
- { remote_bash: "#{ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} "}./mutex_dir unlock /tmp/hybrid_platforms_conductor_deploy_lock" }
620
+ { remote_bash: "#{@actions_executor.sudo_prefix(node)}./mutex_dir unlock /tmp/hybrid_platforms_conductor_deploy_lock" }
623
621
  ]
624
622
  end.to_h,
625
623
  timeout: 10,
@@ -0,0 +1,39 @@
1
+ require 'octokit'
2
+ require 'hybrid_platforms_conductor/credentials'
3
+
4
+ module HybridPlatformsConductor
5
+
6
+ # Mixin used to access Github API
7
+ module Github
8
+
9
+ include Credentials
10
+
11
+ # Iterate over each Github repository
12
+ #
13
+ # Parameters::
14
+ # * Proc: Code called for each Github repository:
15
+ # * Parameters::
16
+ # * *github* (Octokit::Client): The client instance accessing the Github API
17
+ # * *repo_info* (Hash<Symbol, Object>): The repository info:
18
+ # * *name* (String): Repository name.
19
+ # * *slug* (String): Repository slug.
20
+ def for_each_github_repo
21
+ @config.known_github_repos.each do |repo_info|
22
+ Octokit.configure do |c|
23
+ c.api_endpoint = repo_info[:url]
24
+ end
25
+ with_credentials_for(:github, resource: repo_info[:url]) do |_github_user, github_token|
26
+ client = Octokit::Client.new(access_token: github_token&.to_unprotected)
27
+ (repo_info[:repos] == :all ? client.repositories(repo_info[:user]).map { |repo| repo[:name] } : repo_info[:repos]).each do |name|
28
+ yield client, {
29
+ name: name,
30
+ slug: "#{repo_info[:user]}/#{name}"
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -14,7 +14,7 @@ module HybridPlatformsConductor
14
14
  # [API] - @actions_executor is accessible
15
15
  #
16
16
  # Parameters::
17
- # * *cmd* (String): The bash command to execute
17
+ # * *cmd* (String or SecretString): The bash command to execute
18
18
  def setup(cmd)
19
19
  @cmd = cmd
20
20
  end
@@ -15,30 +15,30 @@ module HybridPlatformsConductor
15
15
  #
16
16
  # Parameters::
17
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.
18
+ # * String or SecretString: Simple bash command.
19
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: ''].
20
+ # * *commands* (Array<String or SecretString> or String or SecretString): List of bash commands to execute (can be a single one) [default: ''].
21
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: {}].
22
+ # * *env* (Hash<String, String or SecretString>): Environment variables to be set before executing those commands [default: {}].
23
23
  def setup(remote_bash)
24
24
  # Normalize the parameters.
25
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.
26
+ # * *commands* (Array<String or SecretString>): List of bash commands to execute.
27
+ # * *env* (Hash<String, String or SecretString>): Environment variables to be set before executing those commands.
28
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
29
+ if cmd_info.is_a?(Hash)
35
30
  commands = []
36
- commands.concat(cmd_info[:commands].is_a?(String) ? [cmd_info[:commands]] : cmd_info[:commands]) if cmd_info[:commands]
31
+ commands.concat(cmd_info[:commands].is_a?(Array) ? cmd_info[:commands] : [cmd_info[:commands]]) if cmd_info[:commands]
37
32
  commands << File.read(cmd_info[:file]) if cmd_info[:file]
38
33
  {
39
34
  commands: commands,
40
35
  env: cmd_info[:env] || {}
41
36
  }
37
+ else
38
+ {
39
+ commands: [cmd_info],
40
+ env: {}
41
+ }
42
42
  end
43
43
  end
44
44
  end
@@ -63,11 +63,21 @@ module HybridPlatformsConductor
63
63
  # [API] - @stderr_io can be used to log stderr messages
64
64
  # [API] - run_cmd(String) method can be used to execute a command. See CmdRunner#run_cmd to know about the result's signature.
65
65
  def execute
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")
69
- log_debug "[#{@node}] - Execute remote Bash commands \"#{bash_str}\"..."
70
- @connector.remote_bash bash_str
66
+ # The commands or ENV variables can contain secrets, so make sure to protect all strings from secrets leaking
67
+ bash_cmds = @remote_bash.map do |cmd_info|
68
+ cmd_info[:env].map do |var_name, var_value|
69
+ SecretString.new("export #{var_name}='#{var_value.to_unprotected}'", silenced_str: "export #{var_name}='#{var_value}'")
70
+ end + cmd_info[:commands]
71
+ end.flatten
72
+ begin
73
+ SecretString.protect(bash_cmds.map(&:to_unprotected).join("\n"), silenced_str: bash_cmds.join("\n")) do |bash_str|
74
+ log_debug "[#{@node}] - Execute remote Bash commands \"#{bash_str}\"..."
75
+ @connector.remote_bash bash_str
76
+ end
77
+ ensure
78
+ # Make sure we erase all secret strings
79
+ bash_cmds.each(&:erase)
80
+ end
71
81
  end
72
82
 
73
83
  end
@@ -24,7 +24,7 @@ module HybridPlatformsConductor
24
24
  # * Hash<Symbol, Symbol or Array<Symbol> >: The list of necessary properties (or single one) that should be set, per property name (:others can also be used here)
25
25
  def property_dependencies
26
26
  {
27
- host_keys: %i[hostname host_ip]
27
+ host_keys: %i[hostname host_ip ssh_port]
28
28
  }
29
29
  end
30
30
 
@@ -41,19 +41,20 @@ module HybridPlatformsConductor
41
41
  # Nodes for which the property can't be fetched can be ommitted.
42
42
  def get_host_keys(_nodes, metadata)
43
43
  updated_metadata = {}
44
- # Get the list of nodes, per hostname (just in case several nodes share the same hostname)
45
- # Hash<String, Array<String> >
44
+ # Get the list of nodes, per [hostname, port] (just in case several nodes share the same hostname and port)
45
+ # Hash<[String, Integer], Array<String> >
46
46
  hostnames = Hash.new { |hash, key| hash[key] = [] }
47
47
  metadata.each do |node, node_metadata|
48
+ ssh_port = node_metadata[:ssh_port] || 22
48
49
  if node_metadata[:host_ip]
49
- hostnames[node_metadata[:host_ip]] << node
50
+ hostnames[[node_metadata[:host_ip], ssh_port]] << node
50
51
  elsif node_metadata[:hostname]
51
- hostnames[node_metadata[:hostname]] << node
52
+ hostnames[[node_metadata[:hostname], ssh_port]] << node
52
53
  end
53
54
  end
54
55
  unless hostnames.empty?
55
- host_keys_for(*hostnames.keys).each do |hostname, ip|
56
- hostnames[hostname].each do |node|
56
+ host_keys_for(*hostnames.keys).each do |host_id, ip|
57
+ hostnames[host_id].each do |node|
57
58
  updated_metadata[node] = ip
58
59
  end
59
60
  end
@@ -71,7 +72,7 @@ module HybridPlatformsConductor
71
72
  # Discover the host keys associated to a list of hosts.
72
73
  #
73
74
  # Parameters::
74
- # * *hosts* (Array<String>): The hosts to check for
75
+ # * *hosts* (Array<[String, Integer]>): The hosts to check for ([hostname, port])
75
76
  # Result::
76
77
  # * Hash<String, Array<String> >: The corresponding host keys, per host name
77
78
  def host_keys_for(*hosts)
@@ -82,9 +83,9 @@ module HybridPlatformsConductor
82
83
  parallel: true,
83
84
  nbr_threads_max: MAX_THREADS_SSH_KEY_SCAN,
84
85
  progress: log_debug? ? 'Gather host keys' : nil
85
- ) do |host|
86
+ ) do |(host, ssh_port)|
86
87
  exit_status, stdout, _stderr = @cmd_runner.run_cmd(
87
- "ssh-keyscan #{host}",
88
+ "ssh-keyscan -p #{ssh_port} #{host}",
88
89
  timeout: TIMEOUT_SSH_KEYSCAN,
89
90
  log_to_stdout: log_debug?,
90
91
  no_exception: true
@@ -97,9 +98,9 @@ module HybridPlatformsConductor
97
98
  found_keys << "#{type} #{key}"
98
99
  end
99
100
  end
100
- results[host] = found_keys.sort unless found_keys.empty?
101
+ results[[host, ssh_port]] = found_keys.sort unless found_keys.empty?
101
102
  else
102
- log_warn "Unable to get host key for #{host}. Ignoring it. Accessing #{host} might require manual acceptance of its host key."
103
+ log_warn "Unable to get host key for #{host} (port #{ssh_port}). Ignoring it. Accessing #{host} might require manual acceptance of its host key."
103
104
  end
104
105
  end
105
106
  results
@@ -35,9 +35,11 @@ module HybridPlatformsConductor
35
35
  # [API] - @stderr_io can be used to send stderr output
36
36
  #
37
37
  # Parameters::
38
- # * *bash_cmds* (String): Bash commands to execute
38
+ # * *bash_cmds* (String or SecretString): Bash commands to execute. Use #to_unprotected to access the real content (otherwise secrets are obfuscated).
39
39
  def remote_bash(bash_cmds)
40
- run_cmd "cd #{workspace_for(@node)} ; #{bash_cmds}", force_bash: true
40
+ SecretString.protect("cd #{workspace_for(@node)} ; #{bash_cmds.to_unprotected}", silenced_str: "cd #{workspace_for(@node)} ; #{bash_cmds}") do |cmd|
41
+ run_cmd cmd, force_bash: true
42
+ end
41
43
  end
42
44
 
43
45
  # Execute an interactive shell on the remote node
@@ -75,8 +77,8 @@ module HybridPlatformsConductor
75
77
  def remote_copy(from, to, sudo: false, owner: nil, group: nil)
76
78
  # If the destination is a relative path, prepend the workspace dir to it.
77
79
  to = "#{workspace_for(@node)}/#{to}" unless to.start_with?('/')
78
- if sudo
79
- run_cmd "#{@nodes_handler.sudo_on(@node)} cp -r \"#{from}\" \"#{to}\""
80
+ if sudo && !@actions_executor.privileged_access?(@node)
81
+ run_cmd "#{@actions_executor.sudo_prefix(@node)}cp -r \"#{from}\" \"#{to}\""
80
82
  else
81
83
  FileUtils.cp_r from, to unless @cmd_runner.dry_run
82
84
  end
@@ -104,7 +104,7 @@ module HybridPlatformsConductor
104
104
  # [API] - @stderr_io can be used to send stderr output
105
105
  #
106
106
  # Parameters::
107
- # * *bash_cmds* (String): Bash commands to execute
107
+ # * *bash_cmds* (String or SecretString): Bash commands to execute. Use #to_unprotected to access the real content (otherwise secrets are obfuscated).
108
108
  def remote_bash(bash_cmds)
109
109
  MyConnectLib.connect_to(@nodes_handler.get_host_ip_of(@node)).run_bash(bash_cmds)
110
110
  end
@@ -236,31 +236,40 @@ module HybridPlatformsConductor
236
236
  # [API] - @stderr_io can be used to send stderr output
237
237
  #
238
238
  # Parameters::
239
- # * *bash_cmds* (String): Bash commands to execute
239
+ # * *bash_cmds* (String or SecretString): Bash commands to execute. Use #to_unprotected to access the real content (otherwise secrets are obfuscated).
240
240
  def remote_bash(bash_cmds)
241
- ssh_cmd =
241
+ SecretString.protect(
242
242
  if @nodes_handler.get_ssh_session_exec_of(@node) == false
243
243
  # When ExecSession is disabled we need to use stdin directly
244
- "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'HPC_EOF'\n#{bash_cmds}\nHPC_EOF"
244
+ "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'HPC_EOF'\n#{bash_cmds.to_unprotected}\nHPC_EOF"
245
245
  else
246
- "#{ssh_exec} #{ssh_url} /bin/bash <<'HPC_EOF'\n#{bash_cmds}\nHPC_EOF"
247
- end
248
- # Due to a limitation of Process.spawn, each individual argument is limited to 128KB of size.
249
- # 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.
250
- if bash_cmds.size > MAX_CMD_ARG_LENGTH
251
- # Write the commands in a file
252
- temp_file = "#{Dir.tmpdir}/hpc_temp_cmds_#{Digest::MD5.hexdigest(bash_cmds)}.sh"
253
- File.open(temp_file, 'w+') do |file|
254
- file.write ssh_cmd
255
- file.chmod 0o700
256
- end
257
- begin
258
- run_cmd(temp_file)
259
- ensure
260
- File.unlink(temp_file)
246
+ "#{ssh_exec} #{ssh_url} /bin/bash <<'HPC_EOF'\n#{bash_cmds.to_unprotected}\nHPC_EOF"
247
+ end,
248
+ silenced_str:
249
+ if @nodes_handler.get_ssh_session_exec_of(@node) == false
250
+ # When ExecSession is disabled we need to use stdin directly
251
+ "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'HPC_EOF'\n#{bash_cmds}\nHPC_EOF"
252
+ else
253
+ "#{ssh_exec} #{ssh_url} /bin/bash <<'HPC_EOF'\n#{bash_cmds}\nHPC_EOF"
254
+ end
255
+ ) do |ssh_cmd|
256
+ # Due to a limitation of Process.spawn, each individual argument is limited to 128KB of size.
257
+ # 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.
258
+ if bash_cmds.to_unprotected.size > MAX_CMD_ARG_LENGTH
259
+ # Write the commands in a file
260
+ temp_file = "#{Dir.tmpdir}/hpc_temp_cmds_#{Digest::MD5.hexdigest(bash_cmds.to_unprotected)}.sh"
261
+ File.open(temp_file, 'w+') do |file|
262
+ file.write ssh_cmd.to_unprotected
263
+ file.chmod 0o700
264
+ end
265
+ begin
266
+ run_cmd(temp_file)
267
+ ensure
268
+ File.unlink(temp_file)
269
+ end
270
+ else
271
+ run_cmd ssh_cmd
261
272
  end
262
- else
263
- run_cmd ssh_cmd
264
273
  end
265
274
  end
266
275
 
@@ -301,13 +310,14 @@ module HybridPlatformsConductor
301
310
  # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
302
311
  # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
303
312
  def remote_copy(from, to, sudo: false, owner: nil, group: nil)
313
+ need_sudo = sudo && !@actions_executor.privileged_access?(@node)
304
314
  if @nodes_handler.get_ssh_session_exec_of(@node) == false
305
315
  # We don't have ExecSession, so don't use ssh, but scp instead.
306
- if sudo
316
+ if need_sudo
307
317
  # We need to first copy the file in an accessible directory, and then sudo mv
308
318
  remote_bash('mkdir -p hpc_tmp_scp')
309
319
  run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:./hpc_tmp_scp"
310
- remote_bash("#{@nodes_handler.sudo_on(@node)} mv ./hpc_tmp_scp/#{File.basename(from)} #{to}")
320
+ remote_bash("#{@actions_executor.sudo_prefix(@node)}mv ./hpc_tmp_scp/#{File.basename(from)} #{to}")
311
321
  else
312
322
  run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:#{to}"
313
323
  end
@@ -323,7 +333,7 @@ module HybridPlatformsConductor
323
333
  #{File.basename(from)} | \
324
334
  #{ssh_exec} \
325
335
  #{ssh_url} \
326
- \"#{sudo ? "#{@nodes_handler.sudo_on(@node)} " : ''}tar \
336
+ \"#{need_sudo ? @actions_executor.sudo_prefix(@node) : ''}tar \
327
337
  --extract \
328
338
  --gunzip \
329
339
  --file - \
@@ -374,16 +384,18 @@ module HybridPlatformsConductor
374
384
 
375
385
  # Add each node
376
386
  # Query for the metadata of all nodes at once
377
- @nodes_handler.prefetch_metadata_of nodes, %i[private_ips hostname host_ip description]
387
+ @nodes_handler.prefetch_metadata_of nodes, %i[private_ips hostname host_ip description ssh_port]
378
388
  nodes.sort.each do |node|
379
389
  # Generate the conf for the node
380
390
  connection, connection_user, gateway, gateway_user = connection_info_for(node, no_exception: true)
381
391
  if connection.nil?
382
392
  config_content << "# #{node} - Not connectable using SSH - #{@nodes_handler.get_description_of(node) || ''}\n"
383
393
  else
394
+ ssh_port = @nodes_handler.get_ssh_port_of(node)
384
395
  config_content << "# #{node} - #{connection} - #{@nodes_handler.get_description_of(node) || ''}\n"
385
396
  config_content << "Host #{ssh_aliases_for(node).join(' ')}\n"
386
397
  config_content << " Hostname #{connection}\n"
398
+ config_content << " Port #{ssh_port}\n" unless ssh_port.nil?
387
399
  config_content << " User \"#{connection_user}\"\n" if connection_user != @ssh_user
388
400
  config_content << " ProxyCommand #{ssh_exec} -q -W %h:%p #{gateway_user}@#{gateway}\n" unless gateway.nil?
389
401
  if @passwords.key?(node)
@@ -648,7 +660,7 @@ module HybridPlatformsConductor
648
660
  existing_users = File.exist?(control_master_users_file) ? File.read(control_master_users_file).split("\n") : []
649
661
  ssh_url = "hpc.#{node}"
650
662
  connection, connection_user, _gateway, _gateway_user = connection_info_for(node)
651
- control_path_file = control_master_file(connection, '22', connection_user)
663
+ control_path_file = control_master_file(connection, @nodes_handler.get_ssh_port_of(node) || 22, connection_user)
652
664
  if existing_users.empty?
653
665
  # Make sure there is no stale one.
654
666
  if File.exist?(control_path_file)
@@ -29,10 +29,9 @@ module HybridPlatformsConductor
29
29
  # Result::
30
30
  # * Array< Hash<Symbol,Object> >: List of actions to be done
31
31
  def actions_to_save_logs(node, services, deployment_info, exit_status, stdout, stderr)
32
- # Create a log file to be scp with all relevant info
33
- ssh_user = @actions_executor.connector(:ssh).ssh_user
34
- sudo_prefix = ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} "
35
- log_file = "#{Dir.tmpdir}/hpc_deploy_logs/#{node}_#{Time.now.utc.strftime('%F_%H%M%S')}_#{ssh_user}"
32
+ # Create a log file to be scp-ed with all relevant info
33
+ sudo_prefix = @actions_executor.sudo_prefix(node)
34
+ log_file = "#{Dir.tmpdir}/hpc_deploy_logs/#{node}_#{Time.now.utc.strftime('%F_%H%M%S')}_#{@actions_executor.connector(:ssh).ssh_user}"
36
35
  [
37
36
  {
38
37
  ruby: proc do
@@ -56,7 +55,7 @@ module HybridPlatformsConductor
56
55
  {
57
56
  scp: {
58
57
  log_file => '/var/log/deployments',
59
- :sudo => ssh_user != 'root',
58
+ :sudo => !@actions_executor.privileged_access?(node),
60
59
  :owner => 'root',
61
60
  :group => 'root'
62
61
  }
@@ -85,7 +84,7 @@ module HybridPlatformsConductor
85
84
  # Result::
86
85
  # * Array< Hash<Symbol,Object> >: List of actions to be done
87
86
  def actions_to_read_logs(node)
88
- sudo_prefix = @actions_executor.connector(:ssh).ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} "
87
+ sudo_prefix = @actions_executor.sudo_prefix(node)
89
88
  [
90
89
  { remote_bash: "#{sudo_prefix}cat /var/log/deployments/`#{sudo_prefix}ls -t /var/log/deployments/ | head -1`" }
91
90
  ]
@@ -263,7 +263,7 @@ module HybridPlatformsConductor
263
263
  raise "Missing file #{chef_versions_file} specifying the Chef Infra Client version to be deployed" unless File.exist?(chef_versions_file)
264
264
 
265
265
  required_chef_client_version = YAML.load_file(chef_versions_file)['client']
266
- sudo = (@actions_executor.connector(:ssh).ssh_user == 'root' ? '' : "#{@nodes_handler.sudo_on(node)} -E ")
266
+ sudo = @actions_executor.sudo_prefix(node, forward_env: true)
267
267
  [
268
268
  {
269
269
  # Install dependencies
@@ -21,7 +21,7 @@ module HybridPlatformsConductor
21
21
  ::Docker.validate_version!
22
22
  docker_ok = true
23
23
  rescue
24
- log_error "[ #{@node}/#{@environment} ] - Docker is not installed correctly. Please install it. Error: #{$ERROR_INFO}"
24
+ log_error "Docker is not installed correctly. Please install it. Error: #{$ERROR_INFO}"
25
25
  end
26
26
  docker_ok
27
27
  end