hybrid_platforms_conductor 33.2.4 → 33.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/docs/plugins.md +1 -0
- data/docs/plugins/secrets_reader/keepass.md +62 -0
- data/lib/hybrid_platforms_conductor/deployer.rb +2 -28
- data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +173 -0
- data/lib/hybrid_platforms_conductor/plugins.rb +1 -0
- data/lib/hybrid_platforms_conductor/safe_merge.rb +37 -0
- data/lib/hybrid_platforms_conductor/topographer/plugins/graphviz.rb +5 -3
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test.rb +4 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +8 -6
- data/spec/hybrid_platforms_conductor_test/api/config_spec.rb +48 -38
- data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +13 -11
- data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +719 -0
- data/spec/hybrid_platforms_conductor_test/api/nodes_handler/cmdbs_plugins_api_spec.rb +2 -2
- data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +21 -15
- metadata +158 -139
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0707386d30d5e671d5ceac5e1fec9d971cdccd2b7057c6ed3a75cb5d8bb6d85
|
4
|
+
data.tar.gz: e9e5c023662e7aa9283905f4ae54f4b8995d14f61bea1d844ef4fef32cba68dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
@@ -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
|
21
|
-
|
22
|
-
|
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
|
@@ -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'
|
data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb
CHANGED
@@ -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
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
112
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
{
|