hybrid_platforms_conductor 32.18.0 → 33.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +3 -3
  4. data/docs/config_dsl.md +23 -1
  5. data/docs/executables.md +6 -7
  6. data/docs/executables/check-node.md +3 -3
  7. data/docs/executables/deploy.md +3 -3
  8. data/docs/executables/dump_nodes_json.md +3 -3
  9. data/docs/executables/test.md +3 -3
  10. data/docs/executables/topograph.md +3 -3
  11. data/docs/plugins.md +21 -0
  12. data/docs/plugins/secrets_reader/cli.md +31 -0
  13. data/docs/plugins/secrets_reader/thycotic.md +46 -0
  14. data/lib/hybrid_platforms_conductor/deployer.rb +96 -36
  15. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/cli.rb +75 -0
  16. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/my_secrets_reader_plugin.rb.sample +46 -0
  17. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +87 -0
  18. data/lib/hybrid_platforms_conductor/plugins.rb +1 -0
  19. data/lib/hybrid_platforms_conductor/secrets_reader.rb +31 -0
  20. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  21. data/spec/hybrid_platforms_conductor_test.rb +5 -0
  22. data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +22 -0
  23. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/cli_spec.rb +63 -0
  24. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +253 -0
  25. data/spec/hybrid_platforms_conductor_test/executables/options/deployer_spec.rb +0 -182
  26. data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +249 -13
  27. data/spec/hybrid_platforms_conductor_test/test_secrets_reader_plugin.rb +45 -0
  28. metadata +13 -2
@@ -12,188 +12,6 @@ describe 'executables\' Deployer options' do
12
12
  end
13
13
  end
14
14
 
15
- # Mock calls being made to a Thycotic SOAP API using Savon
16
- #
17
- # Parameters::
18
- # * *url* (String): Mocked URL
19
- # * *secret_id* (String): The mocked secret ID
20
- # * *mocked_secrets_file* (String or nil): The mocked secrets file stored in Thycotic, or nil to mock a missing secret
21
- # * *user* (String or nil): The user to be expected, or nil if it should be read from netrc [default: nil]
22
- # * *password* (String or nil): The password to be expected, or nil if it should be read from netrc [default: nil]
23
- def mock_thycotic_file_download_on(url, secret_id, mocked_secrets_file, user: nil, password: nil)
24
- if user.nil?
25
- user = 'thycotic_user_from_netrc'
26
- password = 'thycotic_password_from_netrc'
27
- expect(HybridPlatformsConductor::Credentials).to receive(:with_credentials_for) do |id, _logger, _logger_stderr, url: nil, &client_code|
28
- expect(id).to eq :thycotic
29
- expect(url).to eq url
30
- client_code.call user, password
31
- end
32
- end
33
- # Mock the Savon calls
34
- mocked_savon_client = double 'Mocked Savon client'
35
- expect(Savon).to receive(:client) do |params|
36
- expect(params[:wsdl]).to eq "#{url}/webservices/SSWebservice.asmx?wsdl"
37
- expect(params[:ssl_verify_mode]).to eq :none
38
- mocked_savon_client
39
- end
40
- expect(mocked_savon_client).to receive(:call).with(
41
- :authenticate,
42
- message: {
43
- username: user,
44
- password: password,
45
- domain: 'thycotic_auth_domain'
46
- }
47
- ) do
48
- { authenticate_response: { authenticate_result: { token: 'soap_token' } } }
49
- end
50
- expect(mocked_savon_client).to receive(:call).with(
51
- :get_secret,
52
- message: {
53
- token: 'soap_token',
54
- secretId: secret_id
55
- }
56
- ) do
57
- {
58
- get_secret_response: {
59
- get_secret_result:
60
- if mocked_secrets_file
61
- { secret: { items: { secret_item: { id: '4242' } } } }
62
- else
63
- { errors: { string: 'Access Denied'}, secret_error: { error_code: 'LOAD', error_message: 'Access Denied', allows_response: false } }
64
- end
65
- }
66
- }
67
- end
68
- if mocked_secrets_file
69
- expect(mocked_savon_client).to receive(:call).with(
70
- :download_file_attachment_by_item_id,
71
- message: {
72
- token: 'soap_token',
73
- secretId: secret_id,
74
- secretItemId: '4242'
75
- }
76
- ) do
77
- {
78
- download_file_attachment_by_item_id_response: {
79
- download_file_attachment_by_item_id_result: {
80
- file_attachment: Base64.encode64(mocked_secrets_file)
81
- }
82
- }
83
- }
84
- end
85
- end
86
- ENV['hpc_domain_for_thycotic'] = 'thycotic_auth_domain'
87
- end
88
-
89
- it 'gets secrets from a file' do
90
- with_test_platform_for_deployer_options do |repository|
91
- secrets_file = "#{repository}/my_secrets.json"
92
- File.write(secrets_file, '{ "secret_name": "secret_value" }')
93
- expect(test_deployer).to receive(:deploy_on).with(['node']) do
94
- expect(test_deployer.secrets).to eq [{ 'secret_name' => 'secret_value' }]
95
- {}
96
- end
97
- exit_code, stdout, stderr = run 'deploy', '--node', 'node', '--secrets', secrets_file
98
- expect(exit_code).to eq 0
99
- expect(stderr).to eq ''
100
- end
101
- end
102
-
103
- it 'gets secrets from several files' do
104
- with_test_platform_for_deployer_options do |repository|
105
- secrets_file1 = "#{repository}/my_secrets1.json"
106
- File.write(secrets_file1, '{ "secret1": "value1" }')
107
- secrets_file2 = "#{repository}/my_secrets2.json"
108
- File.write(secrets_file2, '{ "secret2": "value2" }')
109
- expect(test_deployer).to receive(:deploy_on).with(['node']) do
110
- expect(test_deployer.secrets).to eq [{ 'secret1' => 'value1' }, { 'secret2' => 'value2' }]
111
- {}
112
- end
113
- exit_code, stdout, stderr = run 'deploy', '--node', 'node', '--secrets', secrets_file1, '--secrets', secrets_file2
114
- expect(exit_code).to eq 0
115
- expect(stderr).to eq ''
116
- end
117
- end
118
-
119
- it 'fails to get secrets from a missing file' do
120
- with_test_platform_for_deployer_options do
121
- expect do
122
- run 'deploy', '--node', 'node', '--secrets', 'unknown_file.json'
123
- end.to raise_error 'Missing secret file: unknown_file.json'
124
- end
125
- end
126
-
127
- it 'gets secrets from a Thycotic Secret Server' do
128
- with_test_platform_for_deployer_options do
129
- expect(test_deployer).to receive(:deploy_on).with(['node']) do
130
- expect(test_deployer.secrets).to eq [{ 'secret_name' => 'secret_value' }]
131
- {}
132
- end
133
- mock_thycotic_file_download_on('https://my_thycotic.domain.com/SecretServer', '1107', '{ "secret_name": "secret_value" }')
134
- exit_code, stdout, stderr = run 'deploy', '--node', 'node', '--secrets', 'https://my_thycotic.domain.com/SecretServer:1107'
135
- expect(exit_code).to eq 0
136
- expect(stderr).to eq ''
137
- end
138
- end
139
-
140
- it 'gets secrets from a Thycotic Secret Server using env variables' do
141
- with_test_platform_for_deployer_options do
142
- expect(test_deployer).to receive(:deploy_on).with(['node']) do
143
- expect(test_deployer.secrets).to eq [{ 'secret_name' => 'secret_value' }]
144
- {}
145
- end
146
- mock_thycotic_file_download_on(
147
- 'https://my_thycotic.domain.com/SecretServer',
148
- '1107',
149
- '{ "secret_name": "secret_value" }',
150
- user: 'thycotic_user_from_env',
151
- password: 'thycotic_password_from_env'
152
- )
153
- ENV['hpc_user_for_thycotic'] = 'thycotic_user_from_env'
154
- ENV['hpc_password_for_thycotic'] = 'thycotic_password_from_env'
155
- exit_code, stdout, stderr = run 'deploy', '--node', 'node', '--secrets', 'https://my_thycotic.domain.com/SecretServer:1107'
156
- expect(exit_code).to eq 0
157
- expect(stderr).to eq ''
158
- end
159
- end
160
-
161
- it 'gets secrets from several Thycotic Secret Servers and files' do
162
- with_test_platform_for_deployer_options do |repository|
163
- secrets_file1 = "#{repository}/my_secrets1.json"
164
- File.write(secrets_file1, '{ "secret1": "value1" }')
165
- secrets_file3 = "#{repository}/my_secrets3.json"
166
- File.write(secrets_file3, '{ "secret3": "value3" }')
167
- expect(test_deployer).to receive(:deploy_on).with(['node']) do
168
- expect(test_deployer.secrets).to eq [
169
- { 'secret1' => 'value1' },
170
- { 'secret2' => 'value2' },
171
- { 'secret3' => 'value3' },
172
- { 'secret4' => 'value4' }
173
- ]
174
- {}
175
- end
176
- mock_thycotic_file_download_on('https://my_thycotic2.domain.com/SecretServer', '110702', '{ "secret2": "value2" }')
177
- mock_thycotic_file_download_on('https://my_thycotic4.domain.com/SecretServer', '110704', '{ "secret4": "value4" }')
178
- exit_code, stdout, stderr = run 'deploy', '--node', 'node',
179
- '--secrets', secrets_file1,
180
- '--secrets', 'https://my_thycotic2.domain.com/SecretServer:110702',
181
- '--secrets', secrets_file3,
182
- '--secrets', 'https://my_thycotic4.domain.com/SecretServer:110704'
183
- expect(exit_code).to eq 0
184
- expect(stderr).to eq ''
185
- end
186
- end
187
-
188
- it 'fails to get secrets from a missing Thycotic Secret Server' do
189
- with_test_platform_for_deployer_options do
190
- mock_thycotic_file_download_on('https://my_thycotic.domain.com/SecretServer', '1107', nil)
191
- expect do
192
- run 'deploy', '--node', 'node', '--secrets', 'https://my_thycotic.domain.com/SecretServer:1107'
193
- end.to raise_error 'Unable to fetch secret file ID https://my_thycotic.domain.com/SecretServer:1107'
194
- end
195
- end
196
-
197
15
  it 'uses parallel mode' do
198
16
  with_test_platform_for_deployer_options do |repository|
199
17
  expect(test_deployer).to receive(:deploy_on).with(['node']) do
@@ -78,6 +78,8 @@ module HybridPlatformsConductorTest
78
78
  #
79
79
  # Parameters::
80
80
  # * *nodes_info* (Hash): Node info to give the platform [default: 1 node having 1 service]
81
+ # * *expect_services_to_deploy* (Hash<String,Array<String>>): Expected services to be deployed [default: all services from nodes_info]
82
+ # * *expect_deploy_allowed* (Boolean): Should we expect the call to deploy_allowed? [default: true]
81
83
  # * *expect_package* (Boolean): Should we expect packaging? [default: true]
82
84
  # * *expect_prepare_for_deploy* (Boolean): Should we expect calls to prepare for deploy? [default: true]
83
85
  # * *expect_connections_to_nodes* (Boolean): Should we expect connections to nodes? [default: true]
@@ -95,6 +97,8 @@ module HybridPlatformsConductorTest
95
97
  # * *repository* (String): Path to the repository
96
98
  def with_platform_to_deploy(
97
99
  nodes_info: { nodes: { 'node' => { services: %w[service] } } },
100
+ expect_services_to_deploy: Hash[nodes_info[:nodes].map { |node, node_info| [node, node_info[:services]] }],
101
+ expect_deploy_allowed: true,
98
102
  expect_package: true,
99
103
  expect_prepare_for_deploy: true,
100
104
  expect_connections_to_nodes: true,
@@ -111,10 +115,7 @@ module HybridPlatformsConductorTest
111
115
  platform_name = check_mode ? 'platform' : 'my_remote_platform'
112
116
  with_test_platform(nodes_info, !check_mode, additional_config + "\nsend_logs_to :test_log") do |repository|
113
117
  # Mock the ServicesHandler accesses
114
- expect_services_to_deploy = Hash[nodes_info[:nodes].map do |node, node_info|
115
- [node, node_info[:services]]
116
- end]
117
- unless check_mode
118
+ if !check_mode && expect_deploy_allowed
118
119
  expect(test_services_handler).to receive(:deploy_allowed?).with(
119
120
  services: expect_services_to_deploy,
120
121
  secrets: expect_secrets,
@@ -144,7 +145,7 @@ module HybridPlatformsConductorTest
144
145
  end
145
146
  test_deployer.use_why_run = true if check_mode
146
147
  if expect_connections_to_nodes
147
- with_connections_mocked_on(nodes_info[:nodes].keys) do
148
+ with_connections_mocked_on(expect_services_to_deploy.keys) do
148
149
  expect_actions_executor_runs(expected_actions_for_deploy_on(
149
150
  services: expect_services_to_deploy,
150
151
  check_mode: check_mode,
@@ -218,14 +219,7 @@ module HybridPlatformsConductorTest
218
219
 
219
220
  it 'deploys on 1 node using 1 secret' do
220
221
  with_platform_to_deploy(expect_secrets: { 'secret1' => 'password1' }) do
221
- test_deployer.secrets = [{ 'secret1' => 'password1' }]
222
- expect(test_deployer.deploy_on('node')).to eq('node' => expected_deploy_result)
223
- end
224
- end
225
-
226
- it 'deploys on 1 node using several secrets' do
227
- with_platform_to_deploy(expect_secrets: { 'secret1' => 'password1', 'secret2' => 'password2' }) do
228
- test_deployer.secrets = [{ 'secret1' => 'password1' }, { 'secret2' => 'password2' }]
222
+ test_deployer.override_secrets('secret1' => 'password1')
229
223
  expect(test_deployer.deploy_on('node')).to eq('node' => expected_deploy_result)
230
224
  end
231
225
  end
@@ -901,6 +895,248 @@ module HybridPlatformsConductorTest
901
895
 
902
896
  end
903
897
 
898
+ context 'checking secrets handling' do
899
+
900
+ it 'calls secrets readers only for nodes and services to be deployed and merges their secrets' do
901
+ register_plugins(
902
+ :secrets_reader,
903
+ {
904
+ secrets_reader1: HybridPlatformsConductorTest::TestSecretsReaderPlugin,
905
+ secrets_reader2: HybridPlatformsConductorTest::TestSecretsReaderPlugin,
906
+ secrets_reader3: HybridPlatformsConductorTest::TestSecretsReaderPlugin
907
+ }
908
+ )
909
+ with_platform_to_deploy(
910
+ nodes_info: {
911
+ nodes: {
912
+ 'node1' => { services: %w[service1 service2] },
913
+ 'node2' => { services: %w[service2 service3] },
914
+ 'node3' => { services: %w[service3] },
915
+ 'node4' => { services: %w[service1 service3] }
916
+ }
917
+ },
918
+ expect_services_to_deploy: {
919
+ 'node1' => %w[service1 service2],
920
+ 'node2' => %w[service2 service3],
921
+ 'node3' => %w[service3]
922
+ },
923
+ expect_secrets: {
924
+ 'node1' => {
925
+ 'service1' => {
926
+ 'secrets_reader1' => 'Secret value',
927
+ 'secrets_reader2' => 'Secret value'
928
+ },
929
+ 'service2' => {
930
+ 'secrets_reader1' => 'Secret value',
931
+ 'secrets_reader2' => 'Secret value'
932
+ }
933
+ },
934
+ 'node2' => {
935
+ 'service2' => {
936
+ 'secrets_reader1' => 'Secret value',
937
+ 'secrets_reader2' => 'Secret value',
938
+ 'secrets_reader3' => 'Secret value'
939
+ },
940
+ 'service3' => {
941
+ 'secrets_reader1' => 'Secret value',
942
+ 'secrets_reader2' => 'Secret value',
943
+ 'secrets_reader3' => 'Secret value'
944
+ }
945
+ },
946
+ 'node3' => {
947
+ 'service3' => {
948
+ 'secrets_reader1' => 'Secret value',
949
+ 'secrets_reader2' => 'Secret value'
950
+ }
951
+ }
952
+ },
953
+ additional_config: <<~EOS
954
+ read_secrets_from %i[secrets_reader1 secrets_reader2]
955
+ for_nodes('node2') { read_secrets_from :secrets_reader3 }
956
+ EOS
957
+ ) do
958
+ TestSecretsReaderPlugin.deployer = test_deployer
959
+ expect(test_deployer.deploy_on(%w[node1 node2 node3])).to eq(
960
+ 'node1' => expected_deploy_result,
961
+ 'node2' => expected_deploy_result,
962
+ 'node3' => expected_deploy_result
963
+ )
964
+ expect(HybridPlatformsConductorTest::TestSecretsReaderPlugin.calls).to eq [
965
+ { instance: :secrets_reader1, node: 'node1', service: 'service1' },
966
+ { instance: :secrets_reader1, node: 'node1', service: 'service2' },
967
+ { instance: :secrets_reader2, node: 'node1', service: 'service1' },
968
+ { instance: :secrets_reader2, node: 'node1', service: 'service2' },
969
+ { instance: :secrets_reader1, node: 'node2', service: 'service2' },
970
+ { instance: :secrets_reader1, node: 'node2', service: 'service3' },
971
+ { instance: :secrets_reader2, node: 'node2', service: 'service2' },
972
+ { instance: :secrets_reader2, node: 'node2', service: 'service3' },
973
+ { instance: :secrets_reader3, node: 'node2', service: 'service2' },
974
+ { instance: :secrets_reader3, node: 'node2', service: 'service3' },
975
+ { instance: :secrets_reader1, node: 'node3', service: 'service3' },
976
+ { instance: :secrets_reader2, node: 'node3', service: 'service3' }
977
+ ]
978
+ end
979
+ end
980
+
981
+ it 'merges secrets having same values' do
982
+ register_plugins(
983
+ :secrets_reader,
984
+ {
985
+ secrets_reader1: HybridPlatformsConductorTest::TestSecretsReaderPlugin,
986
+ secrets_reader2: HybridPlatformsConductorTest::TestSecretsReaderPlugin
987
+ }
988
+ )
989
+ with_platform_to_deploy(
990
+ nodes_info: {
991
+ nodes: {
992
+ 'node1' => { services: %w[service1] },
993
+ 'node2' => { services: %w[service2] }
994
+ }
995
+ },
996
+ expect_secrets: {
997
+ 'global1' => 'value1',
998
+ 'global2' => 'value2',
999
+ 'global3' => 'value3',
1000
+ 'global4' => 'value4'
1001
+ },
1002
+ additional_config: <<~EOS
1003
+ read_secrets_from :secrets_reader1
1004
+ for_nodes('node2') { read_secrets_from :secrets_reader2 }
1005
+ EOS
1006
+ ) do
1007
+ TestSecretsReaderPlugin.deployer = test_deployer
1008
+ TestSecretsReaderPlugin.mocked_secrets = {
1009
+ 'node1' => {
1010
+ 'service1' => {
1011
+ secrets_reader1: {
1012
+ 'global1' => 'value1',
1013
+ 'global2' => 'value2'
1014
+ }
1015
+ }
1016
+ },
1017
+ 'node2' => {
1018
+ 'service2' => {
1019
+ secrets_reader1: {
1020
+ 'global2' => 'value2',
1021
+ 'global3' => 'value3'
1022
+ },
1023
+ secrets_reader2: {
1024
+ 'global3' => 'value3',
1025
+ 'global4' => 'value4'
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ expect(test_deployer.deploy_on(%w[node1 node2])).to eq(
1031
+ 'node1' => expected_deploy_result,
1032
+ 'node2' => expected_deploy_result
1033
+ )
1034
+ expect(HybridPlatformsConductorTest::TestSecretsReaderPlugin.calls).to eq [
1035
+ { instance: :secrets_reader1, node: 'node1', service: 'service1' },
1036
+ { instance: :secrets_reader1, node: 'node2', service: 'service2' },
1037
+ { instance: :secrets_reader2, node: 'node2', service: 'service2' }
1038
+ ]
1039
+ end
1040
+ end
1041
+
1042
+ it 'fails when merging secrets having different values' do
1043
+ register_plugins(
1044
+ :secrets_reader,
1045
+ {
1046
+ secrets_reader1: HybridPlatformsConductorTest::TestSecretsReaderPlugin,
1047
+ secrets_reader2: HybridPlatformsConductorTest::TestSecretsReaderPlugin
1048
+ }
1049
+ )
1050
+ with_platform_to_deploy(
1051
+ nodes_info: {
1052
+ nodes: {
1053
+ 'node1' => { services: %w[service1] },
1054
+ 'node2' => { services: %w[service2] }
1055
+ }
1056
+ },
1057
+ expect_deploy_allowed: false,
1058
+ expect_package: false,
1059
+ expect_prepare_for_deploy: false,
1060
+ expect_connections_to_nodes: false,
1061
+ additional_config: <<~EOS
1062
+ read_secrets_from :secrets_reader1
1063
+ for_nodes('node2') { read_secrets_from :secrets_reader2 }
1064
+ EOS
1065
+ ) do
1066
+ TestSecretsReaderPlugin.deployer = test_deployer
1067
+ TestSecretsReaderPlugin.mocked_secrets = {
1068
+ 'node1' => {
1069
+ 'service1' => {
1070
+ secrets_reader1: {
1071
+ 'global1' => 'value1',
1072
+ 'global2' => 'value2'
1073
+ }
1074
+ }
1075
+ },
1076
+ 'node2' => {
1077
+ 'service2' => {
1078
+ secrets_reader1: {
1079
+ 'global2' => 'value2',
1080
+ 'global3' => {
1081
+ 'sub_key' => 'value3'
1082
+ }
1083
+ },
1084
+ secrets_reader2: {
1085
+ 'global3' => {
1086
+ 'sub_key' => 'Other value'
1087
+ },
1088
+ 'global4' => 'value4'
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ expect { test_deployer.deploy_on(%w[node1 node2]) }.to raise_error 'Secret set at path global3->sub_key by secrets_reader2 for service service2 on node node2 has conflicting values (set debug for value details).'
1094
+ expect(HybridPlatformsConductorTest::TestSecretsReaderPlugin.calls).to eq [
1095
+ { instance: :secrets_reader1, node: 'node1', service: 'service1' },
1096
+ { instance: :secrets_reader1, node: 'node2', service: 'service2' },
1097
+ { instance: :secrets_reader2, node: 'node2', service: 'service2' }
1098
+ ]
1099
+ end
1100
+ end
1101
+
1102
+ it 'does not call secrets readers when secrets are overridden' do
1103
+ register_plugins(
1104
+ :secrets_reader,
1105
+ {
1106
+ secrets_reader1: HybridPlatformsConductorTest::TestSecretsReaderPlugin,
1107
+ secrets_reader2: HybridPlatformsConductorTest::TestSecretsReaderPlugin,
1108
+ secrets_reader3: HybridPlatformsConductorTest::TestSecretsReaderPlugin
1109
+ }
1110
+ )
1111
+ with_platform_to_deploy(
1112
+ nodes_info: {
1113
+ nodes: {
1114
+ 'node1' => { services: %w[service1] },
1115
+ 'node2' => { services: %w[service2] },
1116
+ 'node3' => { services: %w[service3] }
1117
+ }
1118
+ },
1119
+ expect_secrets: {
1120
+ 'overridden_secrets' => 'value'
1121
+ },
1122
+ additional_config: <<~EOS
1123
+ read_secrets_from %i[secrets_reader1 secrets_reader2]
1124
+ for_nodes('node2') { read_secrets_from :secrets_reader3 }
1125
+ EOS
1126
+ ) do
1127
+ TestSecretsReaderPlugin.deployer = test_deployer
1128
+ test_deployer.override_secrets('overridden_secrets' => 'value')
1129
+ expect(test_deployer.deploy_on(%w[node1 node2 node3])).to eq(
1130
+ 'node1' => expected_deploy_result,
1131
+ 'node2' => expected_deploy_result,
1132
+ 'node3' => expected_deploy_result
1133
+ )
1134
+ expect(HybridPlatformsConductorTest::TestSecretsReaderPlugin.calls).to eq []
1135
+ end
1136
+ end
1137
+
1138
+ end
1139
+
904
1140
  end
905
1141
 
906
1142
  end