hybrid_platforms_conductor 33.2.4 → 33.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd578efdc520f51dc9752ace39626e73dbdda736fd4376ee5e9ce55ed34a0a4d
4
- data.tar.gz: c44c935905b7a62a4a0f3ea037b0f1e4ed8fa07bdad4802e38143fe1a448bfd1
3
+ metadata.gz: c0707386d30d5e671d5ceac5e1fec9d971cdccd2b7057c6ed3a75cb5d8bb6d85
4
+ data.tar.gz: e9e5c023662e7aa9283905f4ae54f4b8995d14f61bea1d844ef4fef32cba68dc
5
5
  SHA512:
6
- metadata.gz: f0070cf6736acaa5b9a1e36279bab3b9f6530c26ea341feec4fb4775d022e1c67e2694df832a3ab340b765e10bf510969febe82e4b34fa564e9aab82eea71314
7
- data.tar.gz: d5cc0229b32b7f75d1c7604e304e6986b0b7ab121676dfd62121d954c48e3ba93f827dabfbb8320c9f945339c334263ff9404c8c60e1be5a0b03e4b390cc044f
6
+ metadata.gz: 1b70dedf6605d48081ead37771b8f94e29d26334fb1c0f7f15ed8ed70cabc24809b145bd681af9e829cd0c968accd18cf87886987391709a7252908a00104096
7
+ data.tar.gz: cbe9033432aae4b83b4153501efad2330651696ba0fc8b487d21f5927430d481661e5d3bf2c3617ffb20c2ba7e254f82328d4225aeba4f98295873c55fe4cca9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [v33.3.0](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.2.4...v33.3.0) (2021-07-02 17:20:58)
2
+
3
+ ## Global changes
4
+ ### Patches
5
+
6
+ * [[Feature(secrets_reader_keepass)] [#79] Add Keepass secrets reader plugin to get secrets from KeePass databases](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/7ace48653a74f03ed535ee6b41b21242a6454ff3)
7
+
8
+ ## Changes for secrets_reader_keepass
9
+ ### Features
10
+
11
+ * [[Feature(secrets_reader_keepass)] [#79] Add Keepass secrets reader plugin to get secrets from KeePass databases](https://github.com/sweet-delights/hybrid-platforms-conductor/commit/7ace48653a74f03ed535ee6b41b21242a6454ff3)
12
+
1
13
  # [v33.2.4](https://github.com/sweet-delights/hybrid-platforms-conductor/compare/v33.2.3...v33.2.4) (2021-06-23 15:14:20)
2
14
 
3
15
  ## Global changes
data/docs/plugins.md CHANGED
@@ -196,6 +196,7 @@ Check the [sample plugin file](../lib/hybrid_platforms_conductor/hpc_plugins/sec
196
196
 
197
197
  Plugins shipped by default:
198
198
  * [`cli`](plugins/secrets_reader/cli.md)
199
+ * [`keepass`](plugins/secrets_reader/keepass.md)
199
200
  * [`thycotic`](plugins/secrets_reader/thycotic.md)
200
201
 
201
202
  <a name="test"></a>
@@ -0,0 +1,62 @@
1
+ # Secrets reader plugin: `keepass`
2
+
3
+ The `keepass` secrets reader plugin retrieves secrets from [KeePass](https://keepass.info/) databases, using an actual KeePass installation with the [KPScript plugin](https://keepass.info/plugins.html#kpscript).
4
+
5
+ It is configured by giving the KPScript command-line (using `use_kpscript_from` config DSL method), the KeePass databases to be read (using `secrets_from_keepass` config DSL method) and uses the `keepass` credential ID to authenticate along with extra environment variables for eventual key files or encrypted passwords.
6
+
7
+ ## Config DSL extension
8
+
9
+ ### `use_kpscript_from`
10
+
11
+ Provide the KPScript command-line to be used. If KPScript is already in your path, using `KPScript.exe` or `kpscript` should be enough, otherwise the full path to the command-line will be needed. On Windows it is needed to also include double quotes if the path contains spaces (like `"C:\Program Files\KeePass\KPScript.exe"`).
12
+
13
+ It takes a simple `String` as parameter to get the command line.
14
+
15
+ Example:
16
+ ```ruby
17
+ use_kpscript_from '/path/to/kpscript'
18
+ ```
19
+
20
+ ### `secrets_from_keepass`
21
+
22
+ Define a KeePass database to read secrets from.
23
+ A base group path of the KeePass database can also be specified to only read secrets from this group path.
24
+
25
+ All entries, attachments and sub-groups from the base group will be read as secrets.
26
+
27
+ Can be applied to subset of nodes using the [`for_nodes` DSL method](/docs/config_dsl.md#for_nodes).
28
+
29
+ It takes the following parameters:
30
+ * **database** (`String`): Database file path.
31
+ * **group_path** (`Array<String>`): Group path to extract from [default: `[]`].
32
+
33
+ Example:
34
+ ```ruby
35
+ secrets_from_keepass(
36
+ database: '/path/to/database.kdbx',
37
+ group_path: %w[Secrets Automation]
38
+ )
39
+ ```
40
+
41
+ ## Used credentials
42
+
43
+ | Credential | Usage
44
+ | --- | --- |
45
+ | `keepass` | Used to get the password to the database. No need to be set if the database opens without password. |
46
+
47
+ ## Used Metadata
48
+
49
+ | Metadata | Type | Usage
50
+ | --- | --- | --- |
51
+
52
+ ## Used environment variables
53
+
54
+ | Variable | Usage
55
+ | --- | --- |
56
+ | `hpc_key_file_for_keepass` | Optional path to the key file needed to open the database |
57
+ | `hpc_password_enc_for_keepass` | Optional encrypted password needed to open the database |
58
+
59
+ ## External tools dependencies
60
+
61
+ * [KeePass](https://keepass.info/) to open databases.
62
+ * [KPScript KeePass plugin](https://keepass.info/plugins.html#kpscript) to query KeePass API.
@@ -9,6 +9,7 @@ require 'hybrid_platforms_conductor/logger_helpers'
9
9
  require 'hybrid_platforms_conductor/nodes_handler'
10
10
  require 'hybrid_platforms_conductor/services_handler'
11
11
  require 'hybrid_platforms_conductor/plugins'
12
+ require 'hybrid_platforms_conductor/safe_merge'
12
13
 
13
14
  module HybridPlatformsConductor
14
15
 
@@ -500,34 +501,7 @@ module HybridPlatformsConductor
500
501
 
501
502
  private
502
503
 
503
- # Safe-merge 2 hashes.
504
- # Safe-merging is done by:
505
- # * Merging values that are hashes.
506
- # * Reporting errors when values conflict.
507
- # When values are conflicting, the initial hash won't modify those conflicting values and will stop the merge.
508
- #
509
- # Parameters::
510
- # * *hash* (Hash): Hash to be modified merging hash_to_merge
511
- # * *hash_to_merge* (Hash): Hash to be merged into hash
512
- # Result::
513
- # * nil or Array<Object>: nil in case of success, or the keys path leading to a conflicting value in case of error
514
- def safe_merge(hash, hash_to_merge)
515
- conflicting_path = nil
516
- hash_to_merge.each do |key, value_to_merge|
517
- if hash.key?(key)
518
- if hash[key].is_a?(Hash) && value_to_merge.is_a?(Hash)
519
- sub_conflicting_path = safe_merge(hash[key], value_to_merge)
520
- conflicting_path = [key] + sub_conflicting_path unless sub_conflicting_path.nil?
521
- elsif hash[key] != value_to_merge
522
- conflicting_path = [key]
523
- end
524
- else
525
- hash[key] = value_to_merge
526
- end
527
- break unless conflicting_path.nil?
528
- end
529
- conflicting_path
530
- end
504
+ include SafeMerge
531
505
 
532
506
  # Get the list of retriable errors a node got from deployment logs.
533
507
  # Useful to know if an error is non-deterministic (due to external and temporary factors).
@@ -0,0 +1,173 @@
1
+ require 'base64'
2
+ require 'nokogiri'
3
+ require 'tempfile'
4
+ require 'keepass_kpscript'
5
+ require 'zlib'
6
+ require 'hybrid_platforms_conductor/credentials'
7
+ require 'hybrid_platforms_conductor/safe_merge'
8
+ require 'hybrid_platforms_conductor/secrets_reader'
9
+
10
+ module HybridPlatformsConductor
11
+
12
+ module HpcPlugins
13
+
14
+ module SecretsReader
15
+
16
+ # Get secrets from a KeePass database
17
+ class Keepass < HybridPlatformsConductor::SecretsReader
18
+
19
+ include SafeMerge
20
+
21
+ # Extend the Config DSL
22
+ module ConfigDSLExtension
23
+
24
+ # List of defined KeePass secrets. Each info has the following properties:
25
+ # * *nodes_selectors_stack* (Array<Object>): Stack of nodes selectors impacted by this rule.
26
+ # * *database* (String): Database file path.
27
+ # * *group_path* (Array<String>): Group path to extract from.
28
+ # Array< Hash<Symbol, Object> >
29
+ attr_reader :keepass_secrets
30
+
31
+ # String: The KPScript command line
32
+ attr_reader :kpscript
33
+
34
+ # Mixin initializer
35
+ def init_keepass_config
36
+ @keepass_secrets = []
37
+ @kpscript = nil
38
+ end
39
+
40
+ # Set the KPScript command line
41
+ #
42
+ # Parameters::
43
+ # * *cmd* (String): KPScript command line
44
+ def use_kpscript_from(cmd)
45
+ @kpscript = cmd
46
+ end
47
+
48
+ # Set a KeePass database configuration
49
+ #
50
+ # Parameters::
51
+ # * *database* (String): Database file path.
52
+ # * *group_path* (Array<String>): Group path to extract from [default: []].
53
+ def secrets_from_keepass(database:, group_path: [])
54
+ @keepass_secrets << {
55
+ nodes_selectors_stack: current_nodes_selectors_stack,
56
+ database: database,
57
+ group_path: group_path
58
+ }
59
+ end
60
+
61
+ end
62
+
63
+ Config.extend_config_dsl_with ConfigDSLExtension, :init_keepass_config
64
+
65
+ # Return secrets for a given service to be deployed on a node.
66
+ # [API] - This method is mandatory
67
+ # [API] - The following API components are accessible:
68
+ # * *@config* (Config): Main configuration API.
69
+ # * *@cmd_runner* (CmdRunner): Command Runner API.
70
+ # * *@nodes_handler* (NodesHandler): Nodes handler API.
71
+ #
72
+ # Parameters::
73
+ # * *node* (String): Node to be deployed
74
+ # * *service* (String): Service to be deployed
75
+ # Result::
76
+ # * Hash: The secrets
77
+ def secrets_for(node, service)
78
+ secrets = {}
79
+ # As we are dealing with global secrets, cache the reading for performance between nodes and services.
80
+ # Keep secrets cache grouped by URL/ID
81
+ @secrets = {} unless defined?(@secrets)
82
+ @nodes_handler.select_confs_for_node(node, @config.keepass_secrets).each do |keepass_secrets_info|
83
+ secret_id = "#{keepass_secrets_info[:database]}:#{keepass_secrets_info[:group_path].join('/')}"
84
+ unless @secrets.key?(secret_id)
85
+ raise 'Missing KPScript configuration. Please use use_kpscript_from to set it.' if @config.kpscript.nil?
86
+
87
+ Credentials.with_credentials_for(:keepass, @logger, @logger_stderr) do |_user, password|
88
+ Tempfile.create('hpc_keepass') do |xml_file|
89
+ key_file = ENV['hpc_key_file_for_keepass']
90
+ password_enc = ENV['hpc_password_enc_for_keepass']
91
+ keepass_credentials = {}
92
+ keepass_credentials[:password] = password if password
93
+ keepass_credentials[:password_enc] = password_enc if password_enc
94
+ keepass_credentials[:key_file] = key_file if key_file
95
+ KeepassKpscript.
96
+ use(@config.kpscript, debug: log_debug?).
97
+ open(keepass_secrets_info[:database], **keepass_credentials).
98
+ export('KeePass XML (2.x)', xml_file.path, group_path: keepass_secrets_info[:group_path].empty? ? nil : keepass_secrets_info[:group_path])
99
+ @secrets[secret_id] = parse_xml_secrets(Nokogiri::XML(xml_file).at_xpath('KeePassFile/Root/Group'))
100
+ end
101
+ end
102
+ end
103
+ conflicting_path = safe_merge(secrets, @secrets[secret_id])
104
+ raise "Secret set at path #{conflicting_path.join('->')} by #{keepass_secrets_info[:database]}#{keepass_secrets_info[:group_path].empty? ? '' : " from group #{keepass_secrets_info[:group_path].join('/')}"} for service #{service} on node #{node} has conflicting values (#{log_debug? ? "#{@secrets[secret_id].dig(*conflicting_path)} != #{secrets.dig(*conflicting_path)}" : 'set debug for value details'})." unless conflicting_path.nil?
105
+ end
106
+ secrets
107
+ end
108
+
109
+ private
110
+
111
+ # List of fields to include in the secrets and their corresponding XML name
112
+ FIELDS = {
113
+ notes: 'Notes',
114
+ password: 'Password',
115
+ url: 'URL',
116
+ user_name: 'UserName'
117
+ }
118
+
119
+ # Parse XML secrets from a Nokogiri XML group node
120
+ #
121
+ # Parameters::
122
+ # * *group* (Nokogiri::XML::Element): The group to parse
123
+ # Result::
124
+ # * Hash: The JSON secrets parsed from this group
125
+ def parse_xml_secrets(group)
126
+ # Parse all entries
127
+ group.xpath('Entry').map do |entry|
128
+ [
129
+ entry.at_xpath('String/Key[contains(.,"Title")]/../Value').text,
130
+ FIELDS.map do |property, field|
131
+ value = entry.at_xpath("String/Key[contains(.,\"#{field}\")]/../Value")&.text
132
+ if value.nil? || value.empty?
133
+ nil
134
+ else
135
+ [
136
+ property.to_s,
137
+ value
138
+ ]
139
+ end
140
+ end.compact.to_h.merge(
141
+ entry.xpath('Binary').map do |binary|
142
+ binary_meta = group.document.at_xpath("KeePassFile/Meta/Binaries/Binary[@ID=#{Integer(binary.xpath('Value').attr('Ref').value)}]")
143
+ binary_content = Base64.decode64(binary_meta.text)
144
+ if binary_meta.attr('Compressed') == 'True'
145
+ gz = Zlib::GzipReader.new(StringIO.new(binary_content))
146
+ binary_content = gz.read
147
+ gz.close
148
+ end
149
+ [
150
+ binary.xpath('Key').text,
151
+ binary_content
152
+ ]
153
+ end.to_h
154
+ )
155
+ ]
156
+ end.to_h.merge(
157
+ # Add children groups
158
+ group.xpath('Group').map do |sub_group|
159
+ [
160
+ sub_group.at_xpath('Name').text,
161
+ parse_xml_secrets(sub_group)
162
+ ]
163
+ end.to_h
164
+ )
165
+ end
166
+
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+
173
+ end
@@ -1,3 +1,4 @@
1
+ require 'forwardable'
1
2
  require 'hybrid_platforms_conductor/logger_helpers'
2
3
 
3
4
  module HybridPlatformsConductor
@@ -0,0 +1,37 @@
1
+ module HybridPlatformsConductor
2
+
3
+ # Provide an easy way to safe-merge hashes
4
+ module SafeMerge
5
+
6
+ # Safe-merge 2 hashes.
7
+ # Safe-merging is done by:
8
+ # * Merging values that are hashes.
9
+ # * Reporting errors when values conflict.
10
+ # When values are conflicting, the initial hash won't modify those conflicting values and will stop the merge.
11
+ #
12
+ # Parameters::
13
+ # * *hash* (Hash): Hash to be modified merging hash_to_merge
14
+ # * *hash_to_merge* (Hash): Hash to be merged into hash
15
+ # Result::
16
+ # * nil or Array<Object>: nil in case of success, or the keys path leading to a conflicting value in case of error
17
+ def safe_merge(hash, hash_to_merge)
18
+ conflicting_path = nil
19
+ hash_to_merge.each do |key, value_to_merge|
20
+ if hash.key?(key)
21
+ if hash[key].is_a?(Hash) && value_to_merge.is_a?(Hash)
22
+ sub_conflicting_path = safe_merge(hash[key], value_to_merge)
23
+ conflicting_path = [key] + sub_conflicting_path unless sub_conflicting_path.nil?
24
+ elsif hash[key] != value_to_merge
25
+ conflicting_path = [key]
26
+ end
27
+ else
28
+ hash[key] = value_to_merge
29
+ end
30
+ break unless conflicting_path.nil?
31
+ end
32
+ conflicting_path
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -17,9 +17,11 @@ module HybridPlatformsConductor
17
17
  @topographer.force_cluster_strict_hierarchy
18
18
  # Write a Graphviz file
19
19
  File.open(file_name, 'w') do |f|
20
- f.puts 'digraph unix {
21
- size="6,6";
22
- node [style=filled];'
20
+ f.puts <<~EO_GRAPHVIZ
21
+ digraph unix {
22
+ size="6,6";
23
+ node [style=filled];
24
+ EO_GRAPHVIZ
23
25
  # First write the definition of all nodes
24
26
  # Find all nodes belonging to no cluster
25
27
  orphan_nodes = @topographer.nodes_graph.keys
@@ -1,5 +1,5 @@
1
1
  module HybridPlatformsConductor
2
2
 
3
- VERSION = '33.2.4'
3
+ VERSION = '33.3.0'
4
4
 
5
5
  end
@@ -101,6 +101,10 @@ module HybridPlatformsConductorTest
101
101
  ENV.delete 'hpc_user_for_thycotic'
102
102
  ENV.delete 'hpc_password_for_thycotic'
103
103
  ENV.delete 'hpc_domain_for_thycotic'
104
+ ENV.delete 'hpc_user_for_keepass'
105
+ ENV.delete 'hpc_password_for_keepass'
106
+ ENV.delete 'hpc_password_enc_for_keepass'
107
+ ENV.delete 'hpc_key_file_for_keepass'
104
108
  ENV.delete 'hpc_certificates'
105
109
  ENV.delete 'hpc_interactive'
106
110
  ENV.delete 'hpc_test_cookbooks_path'
@@ -13,10 +13,10 @@ describe HybridPlatformsConductor::ActionsExecutor do
13
13
  end
14
14
 
15
15
  it 'returns 1 defined gateway with its content' do
16
- ssh_gateway = '
16
+ ssh_gateway = <<~EO_CONFIG
17
17
  Host gateway
18
18
  Hostname mygateway.com
19
- '
19
+ EO_CONFIG
20
20
  with_repository do
21
21
  with_platforms "gateway :gateway_1, '#{ssh_gateway}'" do
22
22
  expect(test_config.ssh_for_gateway(:gateway_1)).to eq ssh_gateway
@@ -34,10 +34,12 @@ describe HybridPlatformsConductor::ActionsExecutor do
34
34
 
35
35
  it 'returns several defined gateways' do
36
36
  with_repository do
37
- with_platforms '
38
- gateway :gateway_1, \'\'
39
- gateway :gateway_2, \'\'
40
- ' do
37
+ with_platforms(
38
+ <<~EO_CONFIG
39
+ gateway :gateway_1, ''
40
+ gateway :gateway_2, ''
41
+ EO_CONFIG
42
+ ) do
41
43
  expect(test_config.known_gateways.sort).to eq %i[gateway_1 gateway_2].sort
42
44
  end
43
45
  end
@@ -19,10 +19,12 @@ describe HybridPlatformsConductor::Config do
19
19
  end
20
20
 
21
21
  it 'returns several defined OS images' do
22
- with_platforms '
23
- os_image :image_1, \'/path/to/image_1\'
24
- os_image :image_2, \'/path/to/image_2\'
25
- ' do
22
+ with_platforms(
23
+ <<~EO_CONFIG
24
+ os_image :image_1, '/path/to/image_1'
25
+ os_image :image_2, '/path/to/image_2'
26
+ EO_CONFIG
27
+ ) do
26
28
  expect(test_config.known_os_images.sort).to eq %i[image_1 image_2].sort
27
29
  end
28
30
  end
@@ -49,11 +51,13 @@ describe HybridPlatformsConductor::Config do
49
51
  end
50
52
 
51
53
  it 'includes several configuration files' do
52
- with_platforms '
53
- os_image :image_1, \'/path/to/image_1\'
54
- include_config_from "#{__dir__}/my_conf_1.rb"
55
- include_config_from "#{__dir__}/my_conf_2.rb"
56
- ' do |hybrid_platforms_dir|
54
+ with_platforms(
55
+ <<~'EO_CONFIG'
56
+ os_image :image_1, '/path/to/image_1'
57
+ include_config_from "#{__dir__}/my_conf_1.rb"
58
+ include_config_from "#{__dir__}/my_conf_2.rb"
59
+ EO_CONFIG
60
+ ) do |hybrid_platforms_dir|
57
61
  File.write("#{hybrid_platforms_dir}/my_conf_1.rb", <<~'EO_CONFIG')
58
62
  os_image :image_4, '/path/to/image_4'
59
63
  include_config_from "#{__dir__}/my_conf_3.rb"
@@ -65,9 +69,7 @@ describe HybridPlatformsConductor::Config do
65
69
  end
66
70
 
67
71
  it 'applies nodes specific configuration to all nodes by default' do
68
- with_platforms '
69
- expect_tests_to_fail :my_test, \'Failure reason\'
70
- ' do
72
+ with_platforms 'expect_tests_to_fail :my_test, \'Failure reason\'' do
71
73
  expect(test_config.expected_failures).to eq [
72
74
  {
73
75
  nodes_selectors_stack: [],
@@ -79,12 +81,14 @@ describe HybridPlatformsConductor::Config do
79
81
  end
80
82
 
81
83
  it 'filters nodes specific configuration to nodes sets in a scope' do
82
- with_platforms '
83
- for_nodes(%w[node1 node2 node3]) do
84
- expect_tests_to_fail :my_test_1, \'Failure reason 1\'
85
- end
86
- expect_tests_to_fail :my_test_2, \'Failure reason 2\'
87
- ' do
84
+ with_platforms(
85
+ <<~EO_CONFIG
86
+ for_nodes(%w[node1 node2 node3]) do
87
+ expect_tests_to_fail :my_test_1, 'Failure reason 1'
88
+ end
89
+ expect_tests_to_fail :my_test_2, 'Failure reason 2'
90
+ EO_CONFIG
91
+ ) do
88
92
  sort_proc = proc { |expected_failure_info| expected_failure_info[:reason] }
89
93
  expect(test_config.expected_failures.sort_by(&sort_proc)).to eq [
90
94
  {
@@ -102,14 +106,16 @@ describe HybridPlatformsConductor::Config do
102
106
  end
103
107
 
104
108
  it 'filters nodes specific configuration in a scoped stack' do
105
- with_platforms '
106
- for_nodes(%w[node1 node2 node3]) do
107
- expect_tests_to_fail :my_test_1, \'Failure reason 1\'
108
- for_nodes(%w[node2 node3 node4]) do
109
- expect_tests_to_fail :my_test_2, \'Failure reason 2\'
109
+ with_platforms(
110
+ <<~EO_CONFIG
111
+ for_nodes(%w[node1 node2 node3]) do
112
+ expect_tests_to_fail :my_test_1, 'Failure reason 1'
113
+ for_nodes(%w[node2 node3 node4]) do
114
+ expect_tests_to_fail :my_test_2, 'Failure reason 2'
115
+ end
110
116
  end
111
- end
112
- ' do
117
+ EO_CONFIG
118
+ ) do
113
119
  sort_proc = proc { |expected_failure_info| expected_failure_info[:reason] }
114
120
  expect(test_config.expected_failures.sort_by(&sort_proc)).to eq [
115
121
  {
@@ -127,13 +133,15 @@ describe HybridPlatformsConductor::Config do
127
133
  end
128
134
 
129
135
  it 'returns the expected failures correctly' do
130
- with_platforms '
131
- expect_tests_to_fail :my_test_1, \'Failure reason 1\'
132
- expect_tests_to_fail %i[my_test_2 my_test_3], \'Failure reason 23\'
133
- for_nodes(%w[node1 node2 node3]) do
134
- expect_tests_to_fail :my_test_4, \'Failure reason 4\'
135
- end
136
- ' do
136
+ with_platforms(
137
+ <<~EO_CONFIG
138
+ expect_tests_to_fail :my_test_1, 'Failure reason 1'
139
+ expect_tests_to_fail %i[my_test_2 my_test_3], 'Failure reason 23'
140
+ for_nodes(%w[node1 node2 node3]) do
141
+ expect_tests_to_fail :my_test_4, 'Failure reason 4'
142
+ end
143
+ EO_CONFIG
144
+ ) do
137
145
  sort_proc = proc { |expected_failure_info| expected_failure_info[:reason] }
138
146
  expect(test_config.expected_failures.sort_by(&sort_proc)).to eq [
139
147
  {
@@ -156,12 +164,14 @@ describe HybridPlatformsConductor::Config do
156
164
  end
157
165
 
158
166
  it 'returns the deployment schedules correctly' do
159
- with_platforms '
160
- deployment_schedule(IceCube::Schedule.new(Time.parse(\'2020-05-01 11:22:33 UTC\')))
161
- for_nodes(%w[node1 node2 node3]) do
162
- deployment_schedule(IceCube::Schedule.new(Time.parse(\'2020-05-02 22:33:44 UTC\')))
163
- end
164
- ' do
167
+ with_platforms(
168
+ <<~EO_CONFIG
169
+ deployment_schedule(IceCube::Schedule.new(Time.parse('2020-05-01 11:22:33 UTC')))
170
+ for_nodes(%w[node1 node2 node3]) do
171
+ deployment_schedule(IceCube::Schedule.new(Time.parse('2020-05-02 22:33:44 UTC')))
172
+ end
173
+ EO_CONFIG
174
+ ) do
165
175
  sort_proc = proc { |deployment_schedule_info| deployment_schedule_info[:schedule].to_ical }
166
176
  expect(test_config.deployment_schedules.sort_by(&sort_proc)).to eq [
167
177
  {