hybrid_platforms_conductor 33.3.0 → 33.7.0

Sign up to get free protection for your applications and to get access to all the features.
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