hybrid_platforms_conductor 33.4.0 → 33.7.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +5 -5
  4. data/docs/config_dsl.md +7 -5
  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 +2 -2
  9. data/lib/hybrid_platforms_conductor/cmd_runner.rb +4 -4
  10. data/lib/hybrid_platforms_conductor/config.rb +2 -0
  11. data/lib/hybrid_platforms_conductor/confluence.rb +2 -2
  12. data/lib/hybrid_platforms_conductor/connector.rb +5 -2
  13. data/lib/hybrid_platforms_conductor/credentials.rb +20 -12
  14. data/lib/hybrid_platforms_conductor/deployer.rb +5 -7
  15. data/lib/hybrid_platforms_conductor/github.rb +1 -1
  16. data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +1 -1
  17. data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +27 -17
  18. data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +13 -12
  19. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/local.rb +6 -4
  20. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +1 -1
  21. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +37 -25
  22. data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +5 -6
  23. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/serverless_chef.rb +23 -14
  24. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +1 -1
  25. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +3 -2
  26. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +1 -1
  27. data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +17 -3
  28. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +30 -10
  29. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +1 -1
  30. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +1 -2
  31. data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +1 -1
  32. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +1 -2
  33. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +1 -1
  34. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +2 -2
  35. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +1 -2
  36. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +1 -2
  37. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +1 -2
  38. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +1 -1
  39. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +1 -2
  40. data/lib/hybrid_platforms_conductor/logger_helpers.rb +17 -0
  41. data/lib/hybrid_platforms_conductor/test.rb +21 -7
  42. data/lib/hybrid_platforms_conductor/tests_runner.rb +7 -6
  43. data/lib/hybrid_platforms_conductor/thycotic.rb +2 -2
  44. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  45. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +15 -0
  46. data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +32 -0
  47. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/remote_actions_spec.rb +87 -0
  48. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +30 -0
  49. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +10 -0
  50. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +38 -0
  51. data/spec/hybrid_platforms_conductor_test/api/actions_executor/helpers_spec.rb +195 -0
  52. data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +14 -0
  53. data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +11 -0
  54. data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +8 -4
  55. data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +215 -0
  56. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +49 -10
  57. data/spec/hybrid_platforms_conductor_test/api/platform_handlers/serverless_chef/services_deployment_spec.rb +64 -16
  58. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +5 -3
  59. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/chef_versions.yml +3 -0
  60. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/nodes/node.json +15 -0
  61. data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/policyfiles/test_policy.rb +3 -0
  62. data/spec/hybrid_platforms_conductor_test/shared_examples/deployer.rb +134 -0
  63. data/spec/hybrid_platforms_conductor_test/test_connector.rb +2 -2
  64. metadata +20 -2
@@ -175,6 +175,16 @@ describe HybridPlatformsConductor::ActionsExecutor do
175
175
  end
176
176
  end
177
177
 
178
+ it 'generates a simple config for a node with host_ip and a given SSH port' do
179
+ with_test_platform({ nodes: { 'node' => { meta: { host_ip: '192.168.42.42', ssh_port: 666 } } } }) do
180
+ expect(ssh_config_for('node')).to eq <<~EO_SSH_CONFIG
181
+ Host hpc.node
182
+ Hostname 192.168.42.42
183
+ Port 666
184
+ EO_SSH_CONFIG
185
+ end
186
+ end
187
+
178
188
  it 'generates a simple config for several nodes' do
179
189
  with_test_platform(
180
190
  {
@@ -13,6 +13,15 @@ describe HybridPlatformsConductor::ActionsExecutor do
13
13
  end
14
14
  end
15
15
 
16
+ it 'executes bash commands remotely from a SecretString' do
17
+ with_test_platform_for_remote_testing(
18
+ expected_cmds: [[%r{.+/ssh hpc\.node /bin/bash <<'HPC_EOF'\nbash_cmd.bash\nHPC_EOF}, proc { [0, 'Bash commands executed on node', ''] }]],
19
+ expected_stdout: 'Bash commands executed on node'
20
+ ) do
21
+ test_connector.remote_bash(SecretString.new('bash_cmd.bash', silenced_str: '__INVALID_BASH__'))
22
+ end
23
+ end
24
+
16
25
  it 'executes bash commands remotely with timeout' do
17
26
  with_test_platform_for_remote_testing(
18
27
  expected_cmds: [
@@ -88,6 +97,25 @@ describe HybridPlatformsConductor::ActionsExecutor do
88
97
  end
89
98
  end
90
99
 
100
+ it 'executes really big bash commands remotely using a SecretString' do
101
+ cmd = "echo #{'1' * 131_060}"
102
+ with_test_platform_for_remote_testing(
103
+ expected_cmds: [
104
+ [
105
+ %r{.+/hpc_temp_cmds_.+\.sh$},
106
+ proc do |received_cmd|
107
+ expect(File.read(received_cmd)).to match(%r{.+/ssh hpc\.node /bin/bash <<'HPC_EOF'\n#{Regexp.escape(cmd)}\nHPC_EOF})
108
+ [0, 'Bash commands executed on node', '']
109
+ end
110
+ ]
111
+ ],
112
+ expected_stdout: 'Bash commands executed on node'
113
+ ) do
114
+ # Use an argument that exceeds the max arg length limit
115
+ test_connector.remote_bash(SecretString.new(cmd, silenced_str: '__INVALID_BASH__'))
116
+ end
117
+ end
118
+
91
119
  it 'copies files remotely with sudo' do
92
120
  with_test_platform_for_remote_testing(
93
121
  expected_cmds: [
@@ -153,6 +181,16 @@ describe HybridPlatformsConductor::ActionsExecutor do
153
181
  end
154
182
  end
155
183
 
184
+ it 'executes bash commands remotely without Session Exec capabilities using a SecretString' do
185
+ with_test_platform_for_remote_testing(
186
+ expected_cmds: [[%r{^\{ cat \| .+/ssh hpc\.node -T; \} <<'HPC_EOF'\nbash_cmd.bash\nHPC_EOF$}, proc { [0, 'Bash commands executed on node', ''] }]],
187
+ expected_stdout: 'Bash commands executed on node',
188
+ session_exec: false
189
+ ) do
190
+ test_connector.remote_bash(SecretString.new('bash_cmd.bash', silenced_str: '__INVALID_BASH__'))
191
+ end
192
+ end
193
+
156
194
  it 'copies files remotely without Session Exec capabilities' do
157
195
  with_test_platform_for_remote_testing(
158
196
  expected_cmds: [
@@ -0,0 +1,195 @@
1
+ describe HybridPlatformsConductor::ActionsExecutor do
2
+
3
+ context 'when checking helpers' do
4
+
5
+ it 'gives access to connectors' do
6
+ with_test_platform({}) do
7
+ expect(test_actions_executor.connector(:ssh)).not_to be_nil
8
+ end
9
+ end
10
+
11
+ it 'returns if a user has privileged access on a node' do
12
+ with_test_platform({ nodes: { 'node' => {} } }) do
13
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
14
+ expect(test_actions_executor.privileged_access?('node')).to eq false
15
+ end
16
+ end
17
+
18
+ it 'returns if a user has privileged access on a node when connecting with root' do
19
+ with_test_platform({ nodes: { 'node' => {} } }) do
20
+ test_actions_executor.connector(:ssh).ssh_user = 'root'
21
+ expect(test_actions_executor.privileged_access?('node')).to eq true
22
+ end
23
+ end
24
+
25
+ it 'returns if a user has privileged access on a local node' do
26
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
27
+ with_cmd_runner_mocked [
28
+ ['whoami', proc { [0, 'test_user', ''] }]
29
+ ] do
30
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
31
+ expect(test_actions_executor.privileged_access?('node')).to eq false
32
+ end
33
+ end
34
+ end
35
+
36
+ it 'returns if a user has privileged access on a local node when local user is root' do
37
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
38
+ with_cmd_runner_mocked [
39
+ ['whoami', proc { [0, 'root', ''] }]
40
+ ] do
41
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
42
+ expect(test_actions_executor.privileged_access?('node')).to eq true
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'with connection on a remote node' do
48
+
49
+ it 'returns the correct sudo prefix' do
50
+ with_test_platform({ nodes: { 'node' => {} } }) do
51
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
52
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'sudo -u root '
53
+ end
54
+ end
55
+
56
+ it 'returns the correct sudo prefix with env forwarding' do
57
+ with_test_platform({ nodes: { 'node' => {} } }) do
58
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
59
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'sudo -u root -E '
60
+ end
61
+ end
62
+
63
+ it 'returns the correct sudo prefix when connecting as root' do
64
+ with_test_platform({ nodes: { 'node' => {} } }) do
65
+ test_actions_executor.connector(:ssh).ssh_user = 'root'
66
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
67
+ end
68
+ end
69
+
70
+ it 'returns the correct sudo prefix with a different sudo' do
71
+ with_test_platform(
72
+ { nodes: { 'node' => {} } },
73
+ additional_config: <<~'EO_CONFIG'
74
+ sudo_for { |user| "other_sudo --user #{user}" }
75
+ EO_CONFIG
76
+ ) do
77
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
78
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'other_sudo --user root '
79
+ end
80
+ end
81
+
82
+ it 'returns the correct sudo prefix with a different sudo and env forwarding' do
83
+ with_test_platform(
84
+ { nodes: { 'node' => {} } },
85
+ additional_config: <<~'EO_CONFIG'
86
+ sudo_for { |user| "other_sudo --user #{user}" }
87
+ EO_CONFIG
88
+ ) do
89
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
90
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'other_sudo --user root -E '
91
+ end
92
+ end
93
+
94
+ it 'returns the correct sudo prefix with a different sudo when connecting as root' do
95
+ with_test_platform(
96
+ { nodes: { 'node' => {} } },
97
+ additional_config: <<~'EO_CONFIG'
98
+ sudo_for { |user| "other_sudo --user #{user}" }
99
+ EO_CONFIG
100
+ ) do
101
+ test_actions_executor.connector(:ssh).ssh_user = 'root'
102
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ context 'with connection on a local node' do
109
+
110
+ it 'returns the correct sudo prefix' do
111
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
112
+ with_cmd_runner_mocked [
113
+ ['whoami', proc { [0, 'test_user', ''] }]
114
+ ] do
115
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
116
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'sudo -u root '
117
+ end
118
+ end
119
+ end
120
+
121
+ it 'returns the correct sudo prefix with env forwarding' do
122
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
123
+ with_cmd_runner_mocked [
124
+ ['whoami', proc { [0, 'test_user', ''] }]
125
+ ] do
126
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
127
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'sudo -u root -E '
128
+ end
129
+ end
130
+ end
131
+
132
+ it 'returns the correct sudo prefix when connecting as root' do
133
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true } } } }) do
134
+ with_cmd_runner_mocked [
135
+ ['whoami', proc { [0, 'root', ''] }]
136
+ ] do
137
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
138
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
139
+ end
140
+ end
141
+ end
142
+
143
+ it 'returns the correct sudo prefix with a different sudo' do
144
+ with_test_platform(
145
+ { nodes: { 'node' => { meta: { local_node: true } } } },
146
+ additional_config: <<~'EO_CONFIG'
147
+ sudo_for { |user| "other_sudo --user #{user}" }
148
+ EO_CONFIG
149
+ ) do
150
+ with_cmd_runner_mocked [
151
+ ['whoami', proc { [0, 'test_user', ''] }]
152
+ ] do
153
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
154
+ expect(test_actions_executor.sudo_prefix('node')).to eq 'other_sudo --user root '
155
+ end
156
+ end
157
+ end
158
+
159
+ it 'returns the correct sudo prefix with a different sudo and env forwarding' do
160
+ with_test_platform(
161
+ { nodes: { 'node' => { meta: { local_node: true } } } },
162
+ additional_config: <<~'EO_CONFIG'
163
+ sudo_for { |user| "other_sudo --user #{user}" }
164
+ EO_CONFIG
165
+ ) do
166
+ with_cmd_runner_mocked [
167
+ ['whoami', proc { [0, 'test_user', ''] }]
168
+ ] do
169
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
170
+ expect(test_actions_executor.sudo_prefix('node', forward_env: true)).to eq 'other_sudo --user root -E '
171
+ end
172
+ end
173
+ end
174
+
175
+ it 'returns the correct sudo prefix with a different sudo when connecting as root' do
176
+ with_test_platform(
177
+ { nodes: { 'node' => { meta: { local_node: true } } } },
178
+ additional_config: <<~'EO_CONFIG'
179
+ sudo_for { |user| "other_sudo --user #{user}" }
180
+ EO_CONFIG
181
+ ) do
182
+ with_cmd_runner_mocked [
183
+ ['whoami', proc { [0, 'root', ''] }]
184
+ ] do
185
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
186
+ expect(test_actions_executor.sudo_prefix('node')).to eq ''
187
+ end
188
+ end
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+
195
+ end
@@ -7,6 +7,13 @@ describe HybridPlatformsConductor::CmdRunner do
7
7
  end
8
8
  end
9
9
 
10
+ it 'runs a simple bash command in a SecretString' do
11
+ with_repository do |repository|
12
+ test_cmd_runner.run_cmd SecretString.new("echo TestContent >#{repository}/test_file", silenced_str: '__INVALID_BASH__')
13
+ expect(File.read("#{repository}/test_file")).to eq "TestContent\n"
14
+ end
15
+ end
16
+
10
17
  it 'runs a simple bash command and returns exit code, stdout and stderr correctly' do
11
18
  with_repository do
12
19
  expect(test_cmd_runner.run_cmd('echo TestStderr 1>&2 ; echo TestStdout')).to eq [0, "TestStdout\n", "TestStderr\n"]
@@ -20,6 +27,13 @@ describe HybridPlatformsConductor::CmdRunner do
20
27
  end
21
28
  end
22
29
 
30
+ it 'runs a simple bash command and forces usage of bash in a SecretString' do
31
+ with_repository do
32
+ # Use set -o pipefail that does not work in /bin/sh
33
+ expect(test_cmd_runner.run_cmd(SecretString.new('set -o pipefail ; echo TestStderr 1>&2 ; echo TestStdout', silenced_str: '__INVALID_BASH__'), force_bash: true)).to eq [0, "TestStdout\n", "TestStderr\n"]
34
+ end
35
+ end
36
+
23
37
  it 'runs a simple bash command and logs stdout and stderr to a file' do
24
38
  with_repository do |repository|
25
39
  test_cmd_runner.run_cmd 'echo TestStderr 1>&2 ; sleep 1 ; echo TestStdout', log_to_file: "#{repository}/test_file"
@@ -29,6 +29,17 @@ describe HybridPlatformsConductor::Config do
29
29
  end
30
30
  end
31
31
 
32
+ it 'can check if we are in debug mode' do
33
+ with_platforms(
34
+ <<~EO_CONFIG
35
+ os_image :image_1, '/path/to/image_1' if log_debug?
36
+ os_image :image_2, '/path/to/image_2'
37
+ EO_CONFIG
38
+ ) do
39
+ expect(test_config.known_os_images.sort).to eq %i[image_2].sort
40
+ end
41
+ end
42
+
32
43
  it 'returns the tests provisioner correctly' do
33
44
  with_platforms 'tests_provisioner :test_provisioner' do
34
45
  expect(test_config.tests_provisioner_id).to eq :test_provisioner
@@ -15,15 +15,19 @@ describe HybridPlatformsConductor::Credentials do
15
15
  # * *resource* (String or nil): The resource for which we query the credentials, or nil if none [default: nil]
16
16
  def expect_credentials_to_be(expected_user, expected_password, resource: nil)
17
17
  creds = {}
18
+ password_class = nil
18
19
  credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
19
20
  with_credentials_for(:test_credential, resource: resource) do |user, password|
21
+ password_class = password.class
20
22
  creds = {
21
23
  user: user,
22
24
  # We clone the value as for security reasons it is removed when exiting the block
23
- password: password.clone
25
+ password: password&.to_unprotected.clone
24
26
  }
25
27
  end
26
28
  end
29
+ # Make sure we always return a SecretString for the password
30
+ expect(password_class).to be SecretString unless password_class == NilClass
27
31
  expect(creds).to eq(
28
32
  user: expected_user,
29
33
  password: expected_password
@@ -64,7 +68,7 @@ describe HybridPlatformsConductor::Credentials do
64
68
  leaked_password = password
65
69
  end
66
70
  end
67
- expect(leaked_password).to eq 'gotyou!' * 100
71
+ expect(leaked_password.to_unprotected).to eq "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
68
72
  ensure
69
73
  ENV.delete('hpc_user_for_test_credential')
70
74
  ENV.delete('hpc_password_for_test_credential')
@@ -96,7 +100,7 @@ describe HybridPlatformsConductor::Credentials do
96
100
  end
97
101
  end
98
102
 
99
- it 'erases the value of the password taken from netrc' do
103
+ it 'erases the value of the password taken from netrc after usage' do
100
104
  with_platforms '' do
101
105
  netrc_data = [['mocked_data']]
102
106
  expect(::Netrc).to receive(:read) do
@@ -111,7 +115,7 @@ describe HybridPlatformsConductor::Credentials do
111
115
  leaked_password = password
112
116
  end
113
117
  end
114
- expect(leaked_password).to eq 'gotyou!' * 100
118
+ expect(leaked_password).to eq "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
115
119
  expect(netrc_data).to eq [['GotYou!!!' * 100]]
116
120
  end
117
121
  end
@@ -129,6 +129,127 @@ describe HybridPlatformsConductor::Deployer do
129
129
  end
130
130
  end
131
131
 
132
+ it 'returns actions to save logs on a local node' do
133
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true }, services: %w[service1 service2] } } }, additional_config: 'send_logs_to :remote_fs') do
134
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
135
+ expect_services_handler_to_deploy('node' => %w[service1 service2])
136
+ expect_actions_executor_runs [
137
+ # First run, we expect the mutex to be setup, and the deployment actions to be run
138
+ proc do |actions_per_nodes|
139
+ expect_actions_to_deploy_on(
140
+ actions_per_nodes,
141
+ 'node',
142
+ mocked_result: { 'node' => [0, 'Deploy successful stdout', 'Deploy successful stderr'] }
143
+ )
144
+ end,
145
+ # Second run, we expect the mutex to be released
146
+ proc { |actions_per_nodes| expect_actions_to_unlock(actions_per_nodes, 'node') },
147
+ # Third run, we expect logs to be uploaded on the node
148
+ proc do |actions_per_nodes|
149
+ expect(actions_per_nodes['node'].size).to eq 3
150
+ expect(actions_per_nodes['node'][0].keys.sort).to eq %i[ruby remote_bash].sort
151
+ expect(actions_per_nodes['node'][0][:remote_bash]).to eq 'sudo -u root mkdir -p /var/log/deployments && sudo -u root chmod 600 /var/log/deployments'
152
+ expect(actions_per_nodes['node'][1].keys.sort).to eq %i[scp].sort
153
+ expect(actions_per_nodes['node'][1][:scp].delete(:sudo)).to eq true
154
+ expect(actions_per_nodes['node'][1][:scp].delete(:owner)).to eq 'root'
155
+ expect(actions_per_nodes['node'][1][:scp].delete(:group)).to eq 'root'
156
+ expect(actions_per_nodes['node'][1][:scp].size).to eq 1
157
+ tmp_log_file = actions_per_nodes['node'][1][:scp].first[0]
158
+ expect(actions_per_nodes['node'][1][:scp].first[1]).to eq '/var/log/deployments'
159
+ expect(actions_per_nodes['node'][2].keys.sort).to eq %i[ruby remote_bash].sort
160
+ expect(actions_per_nodes['node'][2][:remote_bash]).to eq "sudo -u root chmod 600 /var/log/deployments/#{File.basename(tmp_log_file)}"
161
+ # Call the Ruby codes to be tested
162
+ actions_per_nodes['node'][0][:ruby].call
163
+ expect(File.exist?(tmp_log_file)).to eq true
164
+ file_content_regexp = Regexp.new <<~EOREGEXP
165
+ repo_name_0: platform
166
+ commit_id_0: 123456
167
+ commit_message_0: Test commit for node: service1, service2
168
+ date: \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}
169
+ user: test_user
170
+ debug: No
171
+ services: service1, service2
172
+ exit_status: 0
173
+ ===== STDOUT =====
174
+ Deploy successful stdout
175
+ ===== STDERR =====
176
+ Deploy successful stderr
177
+ EOREGEXP
178
+ expect(File.read(tmp_log_file)).to match file_content_regexp
179
+ actions_per_nodes['node'][2][:ruby].call
180
+ # Check temporary log file gets deleted for security reasons
181
+ expect(File.exist?(tmp_log_file)).to eq false
182
+ end
183
+ ]
184
+ with_cmd_runner_mocked [
185
+ ['whoami', proc { [0, 'test_user', ''] }]
186
+ ] do
187
+ expect(test_deployer.deploy_on('node')).to eq('node' => [0, 'Deploy successful stdout', 'Deploy successful stderr'])
188
+ end
189
+ end
190
+ end
191
+
192
+ it 'returns actions to save logs on a local node as root' do
193
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true }, services: %w[service1 service2] } } }, additional_config: 'send_logs_to :remote_fs') do
194
+ test_actions_executor.connector(:ssh).ssh_user = 'test_user'
195
+ expect_services_handler_to_deploy('node' => %w[service1 service2])
196
+ expect_actions_executor_runs [
197
+ # First run, we expect the mutex to be setup, and the deployment actions to be run
198
+ proc do |actions_per_nodes|
199
+ expect_actions_to_deploy_on(
200
+ actions_per_nodes,
201
+ 'node',
202
+ sudo: nil,
203
+ mocked_result: { 'node' => [0, 'Deploy successful stdout', 'Deploy successful stderr'] }
204
+ )
205
+ end,
206
+ # Second run, we expect the mutex to be released
207
+ proc { |actions_per_nodes| expect_actions_to_unlock(actions_per_nodes, 'node', sudo: nil) },
208
+ # Third run, we expect logs to be uploaded on the node
209
+ proc do |actions_per_nodes|
210
+ expect(actions_per_nodes['node'].size).to eq 3
211
+ expect(actions_per_nodes['node'][0].keys.sort).to eq %i[ruby remote_bash].sort
212
+ expect(actions_per_nodes['node'][0][:remote_bash]).to eq 'mkdir -p /var/log/deployments && chmod 600 /var/log/deployments'
213
+ expect(actions_per_nodes['node'][1].keys.sort).to eq %i[scp].sort
214
+ expect(actions_per_nodes['node'][1][:scp].delete(:sudo)).to eq false
215
+ expect(actions_per_nodes['node'][1][:scp].delete(:owner)).to eq 'root'
216
+ expect(actions_per_nodes['node'][1][:scp].delete(:group)).to eq 'root'
217
+ expect(actions_per_nodes['node'][1][:scp].size).to eq 1
218
+ tmp_log_file = actions_per_nodes['node'][1][:scp].first[0]
219
+ expect(actions_per_nodes['node'][1][:scp].first[1]).to eq '/var/log/deployments'
220
+ expect(actions_per_nodes['node'][2].keys.sort).to eq %i[ruby remote_bash].sort
221
+ expect(actions_per_nodes['node'][2][:remote_bash]).to eq "chmod 600 /var/log/deployments/#{File.basename(tmp_log_file)}"
222
+ # Call the Ruby codes to be tested
223
+ actions_per_nodes['node'][0][:ruby].call
224
+ expect(File.exist?(tmp_log_file)).to eq true
225
+ file_content_regexp = Regexp.new <<~EOREGEXP
226
+ repo_name_0: platform
227
+ commit_id_0: 123456
228
+ commit_message_0: Test commit for node: service1, service2
229
+ date: \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}
230
+ user: test_user
231
+ debug: No
232
+ services: service1, service2
233
+ exit_status: 0
234
+ ===== STDOUT =====
235
+ Deploy successful stdout
236
+ ===== STDERR =====
237
+ Deploy successful stderr
238
+ EOREGEXP
239
+ expect(File.read(tmp_log_file)).to match file_content_regexp
240
+ actions_per_nodes['node'][2][:ruby].call
241
+ # Check temporary log file gets deleted for security reasons
242
+ expect(File.exist?(tmp_log_file)).to eq false
243
+ end
244
+ ]
245
+ with_cmd_runner_mocked [
246
+ ['whoami', proc { [0, 'root', ''] }]
247
+ ] do
248
+ expect(test_deployer.deploy_on('node')).to eq('node' => [0, 'Deploy successful stdout', 'Deploy successful stderr'])
249
+ end
250
+ end
251
+ end
252
+
132
253
  it 'reads logs' do
133
254
  with_test_platform_for_remote_fs do
134
255
  expect_actions_executor_runs [
@@ -216,6 +337,100 @@ describe HybridPlatformsConductor::Deployer do
216
337
  end
217
338
  end
218
339
 
340
+ it 'reads logs on a local node' do
341
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true }, services: %w[service1 service2] } } }, additional_config: 'send_logs_to :remote_fs') do
342
+ expect_actions_executor_runs [
343
+ # Expect the actions to get log files
344
+ proc do |actions_per_nodes|
345
+ expect(actions_per_nodes).to eq('node' => [{ remote_bash: 'sudo -u root cat /var/log/deployments/`sudo -u root ls -t /var/log/deployments/ | head -1`' }])
346
+ { 'node' => [0, <<~EO_STDOUT, ''] }
347
+ repo_name_0: platform
348
+ commit_id_0: 123456
349
+ commit_message_0: Test commit for node: service1, service2
350
+ diff_files_0: file1, file2, file3
351
+ date: 2017-11-23 18:43:01
352
+ user: test_user
353
+ debug: Yes
354
+ services: service1, service2, service3
355
+ exit_status: 0
356
+ ===== STDOUT =====
357
+ Deploy successful stdout
358
+ ===== STDERR =====
359
+ Deploy successful stderr
360
+ EO_STDOUT
361
+ end
362
+ ]
363
+ with_cmd_runner_mocked [
364
+ ['whoami', proc { [0, 'test_user', ''] }]
365
+ ] do
366
+ expect(test_deployer.deployment_info_from('node')).to eq(
367
+ 'node' => {
368
+ deployment_info: {
369
+ repo_name_0: 'platform',
370
+ commit_id_0: '123456',
371
+ commit_message_0: 'Test commit for node: service1, service2',
372
+ diff_files_0: %w[file1 file2 file3],
373
+ date: Time.parse('2017-11-23 18:43:01 UTC'),
374
+ debug: true,
375
+ user: 'test_user'
376
+ },
377
+ exit_status: 0,
378
+ services: %w[service1 service2 service3],
379
+ stderr: 'Deploy successful stderr',
380
+ stdout: 'Deploy successful stdout'
381
+ }
382
+ )
383
+ end
384
+ end
385
+ end
386
+
387
+ it 'reads logs on a local node as root' do
388
+ with_test_platform({ nodes: { 'node' => { meta: { local_node: true }, services: %w[service1 service2] } } }, additional_config: 'send_logs_to :remote_fs') do
389
+ expect_actions_executor_runs [
390
+ # Expect the actions to get log files
391
+ proc do |actions_per_nodes|
392
+ expect(actions_per_nodes).to eq('node' => [{ remote_bash: 'cat /var/log/deployments/`ls -t /var/log/deployments/ | head -1`' }])
393
+ { 'node' => [0, <<~EO_STDOUT, ''] }
394
+ repo_name_0: platform
395
+ commit_id_0: 123456
396
+ commit_message_0: Test commit for node: service1, service2
397
+ diff_files_0: file1, file2, file3
398
+ date: 2017-11-23 18:43:01
399
+ user: test_user
400
+ debug: Yes
401
+ services: service1, service2, service3
402
+ exit_status: 0
403
+ ===== STDOUT =====
404
+ Deploy successful stdout
405
+ ===== STDERR =====
406
+ Deploy successful stderr
407
+ EO_STDOUT
408
+ end
409
+ ]
410
+ with_cmd_runner_mocked [
411
+ ['whoami', proc { [0, 'root', ''] }]
412
+ ] do
413
+ expect(test_deployer.deployment_info_from('node')).to eq(
414
+ 'node' => {
415
+ deployment_info: {
416
+ repo_name_0: 'platform',
417
+ commit_id_0: '123456',
418
+ commit_message_0: 'Test commit for node: service1, service2',
419
+ diff_files_0: %w[file1 file2 file3],
420
+ date: Time.parse('2017-11-23 18:43:01 UTC'),
421
+ debug: true,
422
+ user: 'test_user'
423
+ },
424
+ exit_status: 0,
425
+ services: %w[service1 service2 service3],
426
+ stderr: 'Deploy successful stderr',
427
+ stdout: 'Deploy successful stdout'
428
+ }
429
+ )
430
+ end
431
+ end
432
+ end
433
+
219
434
  end
220
435
 
221
436
  end