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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +31 -2
- data/docs/config_dsl.md +45 -0
- data/docs/plugins/cmdb/host_keys.md +3 -1
- data/docs/plugins/connector/ssh.md +1 -0
- data/lib/hybrid_platforms_conductor/actions_executor.rb +29 -1
- data/lib/hybrid_platforms_conductor/bitbucket.rb +134 -90
- data/lib/hybrid_platforms_conductor/cmd_runner.rb +4 -4
- data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +12 -44
- data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +9 -31
- data/lib/hybrid_platforms_conductor/config.rb +2 -0
- data/lib/hybrid_platforms_conductor/confluence.rb +93 -88
- data/lib/hybrid_platforms_conductor/connector.rb +5 -2
- data/lib/hybrid_platforms_conductor/credentials.rb +122 -97
- data/lib/hybrid_platforms_conductor/deployer.rb +7 -9
- data/lib/hybrid_platforms_conductor/github.rb +39 -0
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/bash.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/action/remote_bash.rb +27 -17
- data/lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb +13 -12
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/local.rb +6 -4
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/my_connector.rb.sample +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +37 -25
- data/lib/hybrid_platforms_conductor/hpc_plugins/log/remote_fs.rb +5 -6
- data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/serverless_chef.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/docker.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +7 -4
- data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +3 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +3 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +17 -3
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_removes_root_access.rb +30 -10
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +4 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/idempotence.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +7 -3
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +8 -4
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +1 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +1 -2
- data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +3 -1
- data/lib/hybrid_platforms_conductor/logger_helpers.rb +24 -1
- data/lib/hybrid_platforms_conductor/test.rb +21 -7
- data/lib/hybrid_platforms_conductor/tests_runner.rb +7 -6
- data/lib/hybrid_platforms_conductor/thycotic.rb +80 -75
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test.rb +6 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/bash_spec.rb +15 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/actions/remote_bash_spec.rb +32 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/local/remote_actions_spec.rb +87 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +30 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +10 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +38 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/helpers_spec.rb +195 -0
- data/spec/hybrid_platforms_conductor_test/api/cmd_runner_spec.rb +14 -0
- data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +11 -0
- data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +251 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/log_plugins/remote_fs_spec.rb +215 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +280 -319
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +2 -2
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs/host_keys_spec.rb +49 -10
- data/spec/hybrid_platforms_conductor_test/api/platform_handlers/serverless_chef/services_deployment_spec.rb +38 -0
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +49 -69
- data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +29 -39
- data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +5 -3
- data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/chef_versions.yml +3 -0
- data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/nodes/node.json +15 -0
- data/spec/hybrid_platforms_conductor_test/serverless_chef_repositories/1_local_node/policyfiles/test_policy.rb +3 -0
- data/spec/hybrid_platforms_conductor_test/shared_examples/deployer.rb +134 -0
- data/spec/hybrid_platforms_conductor_test/test_connector.rb +2 -2
- metadata +36 -2
data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb
CHANGED
|
@@ -132,6 +132,36 @@ describe HybridPlatformsConductor::ActionsExecutor do
|
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
+
it 'creates SSH master to several nodes differing only by the SSH port' do
|
|
136
|
+
with_test_platform(
|
|
137
|
+
{
|
|
138
|
+
nodes: {
|
|
139
|
+
'node1' => { meta: { host_ip: '192.168.42.1', ssh_port: 6661 } },
|
|
140
|
+
'node2' => { meta: { host_ip: '192.168.42.1', ssh_port: 6662 } },
|
|
141
|
+
'node3' => { meta: { host_ip: '192.168.42.1', ssh_port: 6663 } }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
) do
|
|
145
|
+
with_cmd_runner_mocked(
|
|
146
|
+
[
|
|
147
|
+
['which env', proc { [0, "/usr/bin/env\n", ''] }],
|
|
148
|
+
['ssh -V 2>&1', proc { [0, "OpenSSH_7.4p1 Debian-10+deb9u7, OpenSSL 1.0.2u 20 Dec 2019\n", ''] }]
|
|
149
|
+
] + ssh_expected_commands_for(
|
|
150
|
+
{
|
|
151
|
+
'node1' => { connection: '192.168.42.1', user: 'test_user', port: 6661 },
|
|
152
|
+
'node2' => { connection: '192.168.42.1', user: 'test_user', port: 6662 },
|
|
153
|
+
'node3' => { connection: '192.168.42.1', user: 'test_user', port: 6663 }
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
) do
|
|
157
|
+
test_connector.ssh_user = 'test_user'
|
|
158
|
+
test_connector.with_connection_to(%w[node1 node2 node3]) do |connected_nodes|
|
|
159
|
+
expect(connected_nodes.sort).to eq %w[node1 node2 node3].sort
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
135
165
|
it 'creates SSH master to several nodes with ssh connections transformed' do
|
|
136
166
|
with_test_platform(
|
|
137
167
|
{ nodes: {
|
data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb
CHANGED
|
@@ -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
|
{
|
data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
describe HybridPlatformsConductor::Credentials do
|
|
2
|
+
|
|
3
|
+
# Create a container class for the credential Mixin to be tested, as a plugin as credentials can be used in any plugin.
|
|
4
|
+
let(:credential_tester_class) do
|
|
5
|
+
Class.new(HybridPlatformsConductor::Plugin) do
|
|
6
|
+
include HybridPlatformsConductor::Credentials
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Expect credentials to be as a given user and password
|
|
11
|
+
#
|
|
12
|
+
# Parameters::
|
|
13
|
+
# * *expected_user* (String or nil): The expected user
|
|
14
|
+
# * *expected_password* (String or nil): The expected password
|
|
15
|
+
# * *resource* (String or nil): The resource for which we query the credentials, or nil if none [default: nil]
|
|
16
|
+
def expect_credentials_to_be(expected_user, expected_password, resource: nil)
|
|
17
|
+
creds = {}
|
|
18
|
+
password_class = nil
|
|
19
|
+
credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
|
|
20
|
+
with_credentials_for(:test_credential, resource: resource) do |user, password|
|
|
21
|
+
password_class = password.class
|
|
22
|
+
creds = {
|
|
23
|
+
user: user,
|
|
24
|
+
# We clone the value as for security reasons it is removed when exiting the block
|
|
25
|
+
password: password&.to_unprotected.clone
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
# Make sure we always return a SecretString for the password
|
|
30
|
+
expect(password_class).to be SecretString unless password_class == NilClass
|
|
31
|
+
expect(creds).to eq(
|
|
32
|
+
user: expected_user,
|
|
33
|
+
password: expected_password
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'returns no credentials when they are not set' do
|
|
38
|
+
with_platforms '' do
|
|
39
|
+
# Check that .netrc won't be read
|
|
40
|
+
expect(::Netrc).not_to receive(:read)
|
|
41
|
+
expect_credentials_to_be nil, nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'returns credentials taken from environment variables' do
|
|
46
|
+
with_platforms '' do
|
|
47
|
+
ENV['hpc_user_for_test_credential'] = 'env_test_user'
|
|
48
|
+
ENV['hpc_password_for_test_credential'] = 'env_test_password'
|
|
49
|
+
begin
|
|
50
|
+
# Check that .netrc won't be read
|
|
51
|
+
expect(::Netrc).not_to receive(:read)
|
|
52
|
+
expect_credentials_to_be 'env_test_user', 'env_test_password'
|
|
53
|
+
ensure
|
|
54
|
+
ENV.delete('hpc_user_for_test_credential')
|
|
55
|
+
ENV.delete('hpc_password_for_test_credential')
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'erases the value of the password taken from environment variable after usage' do
|
|
61
|
+
with_platforms '' do
|
|
62
|
+
ENV['hpc_user_for_test_credential'] = 'env_test_user'
|
|
63
|
+
ENV['hpc_password_for_test_credential'] = 'env_test_password'
|
|
64
|
+
begin
|
|
65
|
+
leaked_password = nil
|
|
66
|
+
credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
|
|
67
|
+
with_credentials_for(:test_credential) do |_user, password|
|
|
68
|
+
leaked_password = password
|
|
69
|
+
end
|
|
70
|
+
end
|
|
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"
|
|
72
|
+
ensure
|
|
73
|
+
ENV.delete('hpc_user_for_test_credential')
|
|
74
|
+
ENV.delete('hpc_password_for_test_credential')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'returns credentials taken from .netrc when a resource is specified' do
|
|
80
|
+
with_platforms '' do
|
|
81
|
+
expect(::Netrc).to receive(:read) do
|
|
82
|
+
mocked_netrc = instance_double(::Netrc)
|
|
83
|
+
expect(mocked_netrc).to receive(:[]).with('my_domain.com').and_return %w[test_user test_password]
|
|
84
|
+
expect(mocked_netrc).to receive(:instance_variable_get).with(:@data).and_return []
|
|
85
|
+
mocked_netrc
|
|
86
|
+
end
|
|
87
|
+
expect_credentials_to_be 'test_user', 'test_password', resource: 'http://My_Domain.com/path/to/resource'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'returns credentials taken from .netrc when a non-URL resource is specified' do
|
|
92
|
+
with_platforms '' do
|
|
93
|
+
expect(::Netrc).to receive(:read) do
|
|
94
|
+
mocked_netrc = instance_double(::Netrc)
|
|
95
|
+
expect(mocked_netrc).to receive(:[]).with('This is:not/ a URL!').and_return %w[test_user test_password]
|
|
96
|
+
expect(mocked_netrc).to receive(:instance_variable_get).with(:@data).and_return []
|
|
97
|
+
mocked_netrc
|
|
98
|
+
end
|
|
99
|
+
expect_credentials_to_be 'test_user', 'test_password', resource: 'This is:not/ a URL!'
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'erases the value of the password taken from netrc after usage' do
|
|
104
|
+
with_platforms '' do
|
|
105
|
+
netrc_data = [['mocked_data']]
|
|
106
|
+
expect(::Netrc).to receive(:read) do
|
|
107
|
+
mocked_netrc = instance_double(::Netrc)
|
|
108
|
+
expect(mocked_netrc).to receive(:[]).with('my_domain.com').and_return %w[test_user test_password]
|
|
109
|
+
expect(mocked_netrc).to receive(:instance_variable_get).with(:@data).and_return netrc_data
|
|
110
|
+
mocked_netrc
|
|
111
|
+
end
|
|
112
|
+
leaked_password = nil
|
|
113
|
+
credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
|
|
114
|
+
with_credentials_for(:test_credential, resource: 'http://My_Domain.com/path/to/resource') do |_user, password|
|
|
115
|
+
leaked_password = password
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
expect(leaked_password).to eq "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
119
|
+
expect(netrc_data).to eq [['GotYou!!!' * 100]]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'returns credentials taken from config' do
|
|
124
|
+
with_platforms(
|
|
125
|
+
<<~'EO_CONFIG'
|
|
126
|
+
credentials_for(:test_credential) do |resource, requester|
|
|
127
|
+
requester.call "user_for_#{resource}", "password_for_#{resource}"
|
|
128
|
+
end
|
|
129
|
+
EO_CONFIG
|
|
130
|
+
) do
|
|
131
|
+
# Check that netrc is not called when config is used, and that env vars are ignored
|
|
132
|
+
ENV['hpc_user_for_test_credential'] = 'env_test_user'
|
|
133
|
+
ENV['hpc_password_for_test_credential'] = 'env_test_password'
|
|
134
|
+
begin
|
|
135
|
+
# Check that .netrc won't be read
|
|
136
|
+
expect(::Netrc).not_to receive(:read)
|
|
137
|
+
expect_credentials_to_be 'user_for_', 'password_for_'
|
|
138
|
+
ensure
|
|
139
|
+
ENV.delete('hpc_user_for_test_credential')
|
|
140
|
+
ENV.delete('hpc_password_for_test_credential')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'returns credentials taken from config for a given resource' do
|
|
146
|
+
with_platforms(
|
|
147
|
+
<<~'EO_CONFIG'
|
|
148
|
+
credentials_for(:test_credential) do |resource, requester|
|
|
149
|
+
requester.call "user_for_#{resource}", "password_for_#{resource}"
|
|
150
|
+
end
|
|
151
|
+
EO_CONFIG
|
|
152
|
+
) do
|
|
153
|
+
# Check that netrc is not called when config is used, and that env vars are ignored
|
|
154
|
+
ENV['hpc_user_for_test_credential'] = 'env_test_user'
|
|
155
|
+
ENV['hpc_password_for_test_credential'] = 'env_test_password'
|
|
156
|
+
begin
|
|
157
|
+
# Check that .netrc won't be read
|
|
158
|
+
expect(::Netrc).not_to receive(:read)
|
|
159
|
+
expect_credentials_to_be 'user_for_test_resource', 'password_for_test_resource', resource: 'test_resource'
|
|
160
|
+
ensure
|
|
161
|
+
ENV.delete('hpc_user_for_test_credential')
|
|
162
|
+
ENV.delete('hpc_password_for_test_credential')
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'returns credentials taken from config for a given resource even when they are nil' do
|
|
168
|
+
with_platforms(
|
|
169
|
+
<<~'EO_CONFIG'
|
|
170
|
+
credentials_for(:test_credential) do |resource, requester|
|
|
171
|
+
requester.call nil, nil
|
|
172
|
+
end
|
|
173
|
+
EO_CONFIG
|
|
174
|
+
) do
|
|
175
|
+
# Check that netrc is not called when config is used, and that env vars are ignored
|
|
176
|
+
ENV['hpc_user_for_test_credential'] = 'env_test_user'
|
|
177
|
+
ENV['hpc_password_for_test_credential'] = 'env_test_password'
|
|
178
|
+
begin
|
|
179
|
+
# Check that .netrc won't be read
|
|
180
|
+
expect(::Netrc).not_to receive(:read)
|
|
181
|
+
expect_credentials_to_be nil, nil, resource: 'test_resource'
|
|
182
|
+
ensure
|
|
183
|
+
ENV.delete('hpc_user_for_test_credential')
|
|
184
|
+
ENV.delete('hpc_password_for_test_credential')
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'returns credentials taken from config after filtering the resource name' do
|
|
190
|
+
with_platforms(
|
|
191
|
+
<<~'EO_CONFIG'
|
|
192
|
+
credentials_for(:test_credential, resource: 'another_resource') do |resource, requester|
|
|
193
|
+
requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
|
|
194
|
+
end
|
|
195
|
+
credentials_for(:test_credential, resource: /test_.*/) do |resource, requester|
|
|
196
|
+
requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
|
|
197
|
+
end
|
|
198
|
+
credentials_for(:test_credential, resource: /_resource/) do |resource, requester|
|
|
199
|
+
requester.call "correct_user_for_#{resource}", "correct_password_for_#{resource}"
|
|
200
|
+
end
|
|
201
|
+
credentials_for(:test_credential, resource: 'test_resource2') do |resource, requester|
|
|
202
|
+
requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
|
|
203
|
+
end
|
|
204
|
+
EO_CONFIG
|
|
205
|
+
) do
|
|
206
|
+
expect_credentials_to_be 'correct_user_for_test_resource', 'correct_password_for_test_resource', resource: 'test_resource'
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'returns credentials taken from config after filtering the resource name when no resource is given' do
|
|
211
|
+
with_platforms(
|
|
212
|
+
<<~'EO_CONFIG'
|
|
213
|
+
credentials_for(:test_credential, resource: 'another_resource') do |resource, requester|
|
|
214
|
+
requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
|
|
215
|
+
end
|
|
216
|
+
credentials_for(:test_credential, resource: /test_.*/) do |resource, requester|
|
|
217
|
+
requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
|
|
218
|
+
end
|
|
219
|
+
credentials_for(:test_credential) do |resource, requester|
|
|
220
|
+
requester.call "correct_user_for_#{resource}", "correct_password_for_#{resource}"
|
|
221
|
+
end
|
|
222
|
+
credentials_for(:test_credential, resource: /_resource/) do |resource, requester|
|
|
223
|
+
requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
|
|
224
|
+
end
|
|
225
|
+
credentials_for(:test_credential, resource: 'test_resource2') do |resource, requester|
|
|
226
|
+
requester.call "wrong_user_for_#{resource}", "wrong_password_for_#{resource}"
|
|
227
|
+
end
|
|
228
|
+
EO_CONFIG
|
|
229
|
+
) do
|
|
230
|
+
expect_credentials_to_be 'correct_user_for_', 'correct_password_for_'
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it 'fails if the requester is not called from config' do
|
|
235
|
+
with_platforms(
|
|
236
|
+
<<~'EO_CONFIG'
|
|
237
|
+
credentials_for(:test_credential) do |resource, requester|
|
|
238
|
+
end
|
|
239
|
+
EO_CONFIG
|
|
240
|
+
) do
|
|
241
|
+
expect do
|
|
242
|
+
credential_tester_class.new(logger: logger, logger_stderr: logger, config: test_config).instance_exec do
|
|
243
|
+
with_credentials_for(:test_credential) do |_user, _password|
|
|
244
|
+
nil
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end.to raise_error 'Requester not called by the credentials provider for test_credential (resource: ) - Please check the credentials_for code in your configuration.'
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
end
|