moose-inventory 1.0.9 → 2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +15 -1
- data/.github/workflows/release.yml +60 -0
- data/.gitignore +2 -1
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +49 -0
- data/BACKLOG.md +752 -24
- data/Gemfile +2 -0
- data/Gemfile.lock +36 -1
- data/README.md +340 -44
- data/Rakefile +2 -0
- data/bin/moose-inventory +2 -1
- data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
- data/docs/compatibility/cli-output-compatibility.md +76 -0
- data/docs/governance/approval-register.md +37 -0
- data/docs/maintenance/database-backup-restore-guidance.md +162 -0
- data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
- data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
- data/docs/product/product-brief.md +161 -0
- data/docs/product/requirements-baseline.md +477 -0
- data/docs/qa/qa-documentation-and-release-gates.md +283 -0
- data/docs/release/package-provenance-hardening.md +126 -0
- data/docs/release/publishing.md +54 -50
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +37 -4
- data/docs/security/accepted-risk-register.md +84 -0
- data/docs/security/security-privacy-process.md +287 -0
- data/docs/security-audit-2026-05-26-rerun.md +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- data/docs/ux/cli-workflow-notes.md +287 -0
- data/examples/ansible/ansible.cfg +3 -0
- data/examples/ansible/inventory/moose_inventory.yml +5 -0
- data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
- data/examples/ci/README.md +16 -0
- data/examples/ci/github-actions/inventory-review.yml +38 -0
- data/examples/ci/inventory/example-snapshot.yml +19 -0
- data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
- data/lib/moose_inventory/cli/application.rb +133 -5
- data/lib/moose_inventory/cli/association_rendering.rb +74 -0
- data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
- data/lib/moose_inventory/cli/audit.rb +62 -0
- data/lib/moose_inventory/cli/audit_recording.rb +40 -0
- data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
- data/lib/moose_inventory/cli/console.rb +135 -0
- data/lib/moose_inventory/cli/db.rb +64 -0
- data/lib/moose_inventory/cli/factory.rb +28 -0
- data/lib/moose_inventory/cli/formatter.rb +8 -12
- data/lib/moose_inventory/cli/group.rb +7 -1
- data/lib/moose_inventory/cli/group_add.rb +91 -73
- data/lib/moose_inventory/cli/group_addchild.rb +41 -66
- data/lib/moose_inventory/cli/group_addhost.rb +33 -71
- data/lib/moose_inventory/cli/group_addvar.rb +27 -47
- data/lib/moose_inventory/cli/group_get.rb +8 -42
- data/lib/moose_inventory/cli/group_list.rb +7 -40
- data/lib/moose_inventory/cli/group_listvars.rb +9 -55
- data/lib/moose_inventory/cli/group_rm.rb +105 -73
- data/lib/moose_inventory/cli/group_rmchild.rb +47 -57
- data/lib/moose_inventory/cli/group_rmhost.rb +34 -61
- data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
- data/lib/moose_inventory/cli/group_tags.rb +33 -0
- data/lib/moose_inventory/cli/helpers.rb +143 -0
- data/lib/moose_inventory/cli/host.rb +8 -2
- data/lib/moose_inventory/cli/host_add.rb +91 -66
- data/lib/moose_inventory/cli/host_addgroup.rb +39 -66
- data/lib/moose_inventory/cli/host_addvar.rb +28 -52
- data/lib/moose_inventory/cli/host_get.rb +9 -37
- data/lib/moose_inventory/cli/host_list.rb +24 -21
- data/lib/moose_inventory/cli/host_listvars.rb +9 -62
- data/lib/moose_inventory/cli/host_rm.rb +60 -42
- data/lib/moose_inventory/cli/host_rmgroup.rb +39 -55
- data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
- data/lib/moose_inventory/cli/host_tags.rb +33 -0
- data/lib/moose_inventory/cli/listvars_support.rb +55 -0
- data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
- data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
- data/lib/moose_inventory/cli/tag_support.rb +97 -0
- data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
- data/lib/moose_inventory/config/config.rb +185 -108
- data/lib/moose_inventory/db/db.rb +188 -193
- data/lib/moose_inventory/db/exceptions.rb +6 -3
- data/lib/moose_inventory/db/models.rb +16 -0
- data/lib/moose_inventory/db/schema_migrations.rb +248 -0
- data/lib/moose_inventory/inventory_context.rb +116 -0
- data/lib/moose_inventory/operations/add_associations.rb +131 -0
- data/lib/moose_inventory/operations/add_groups.rb +123 -0
- data/lib/moose_inventory/operations/add_hosts.rb +123 -0
- data/lib/moose_inventory/operations/add_variables.rb +77 -0
- data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +125 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +70 -0
- data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
- data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
- data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
- data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
- data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
- data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
- data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
- data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
- data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
- data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
- data/lib/moose_inventory/operations/query_inventory.rb +47 -0
- data/lib/moose_inventory/operations/remove_associations.rb +113 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
- data/lib/moose_inventory/operations/remove_variables.rb +67 -0
- data/lib/moose_inventory/runtime_options.rb +31 -0
- data/lib/moose_inventory/version.rb +3 -1
- data/lib/moose_inventory.rb +10 -7
- data/moose-inventory.gemspec +22 -35
- data/scripts/check.sh +3 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +5 -0
- data/scripts/ci/check_rubocop.sh +33 -0
- data/scripts/ci/check_secrets.sh +26 -0
- data/scripts/ci/check_security.sh +18 -0
- data/scripts/ci/install_security_tools.sh +47 -0
- data/scripts/files.rb +5 -4
- data/scripts/install_dependencies.sh +2 -0
- data/spec/examples/ci_examples_spec.rb +37 -0
- data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
- data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
- data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
- data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
- data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
- data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
- data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
- data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
- data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +165 -85
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +100 -30
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
- data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
- data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
- data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +551 -29
- data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
- data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
- data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
- data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +111 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +80 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +82 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +122 -0
- data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
- data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
- data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +113 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
- data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
- data/spec/shared/shared_config_setup.rb +4 -3
- data/spec/spec_helper.rb +50 -40
- data/spec/support/cli_harness.rb +33 -0
- metadata +163 -35
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'inventory_context'
|
|
6
|
+
require 'operations/inventory_snapshot'
|
|
7
|
+
require 'operations/import_inventory_snapshot'
|
|
8
|
+
|
|
9
|
+
RSpec.describe Moose::Inventory::Operations::ImportInventorySnapshot do
|
|
10
|
+
before(:all) do
|
|
11
|
+
@mockargs = [
|
|
12
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
13
|
+
'--format', 'yaml',
|
|
14
|
+
'--env', 'test'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
18
|
+
@db = Moose::Inventory::DB
|
|
19
|
+
@db.init if @db.db.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
before(:each) do
|
|
23
|
+
@db.reset
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def operation
|
|
27
|
+
described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'imports a validated snapshot transactionally' do
|
|
31
|
+
snapshot = {
|
|
32
|
+
'version' => 1,
|
|
33
|
+
'hosts' => {
|
|
34
|
+
'web01' => { 'groups' => ['web'], 'tags' => [], 'vars' => { 'env' => 'prod' } }
|
|
35
|
+
},
|
|
36
|
+
'groups' => {
|
|
37
|
+
'web' => { 'children' => ['blue'], 'tags' => [], 'vars' => { 'role' => 'frontend' } },
|
|
38
|
+
'blue' => { 'children' => [], 'tags' => [], 'vars' => {} }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
result = operation.call(snapshot: snapshot)
|
|
43
|
+
|
|
44
|
+
expect(result.created_hosts).to eq(1)
|
|
45
|
+
expect(result.created_groups).to eq(2)
|
|
46
|
+
expect(result.updated_variables).to eq(2)
|
|
47
|
+
expect(result.associations).to eq(2)
|
|
48
|
+
host = @db.models[:host].find(name: 'web01')
|
|
49
|
+
group = @db.models[:group].find(name: 'web')
|
|
50
|
+
expect(host.groups_dataset[name: 'web']).not_to be_nil
|
|
51
|
+
expect(host.hostvars_dataset[name: 'env'][:value]).to eq('prod')
|
|
52
|
+
expect(group.children_dataset[name: 'blue']).not_to be_nil
|
|
53
|
+
expect(group.groupvars_dataset[name: 'role'][:value]).to eq('frontend')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'normalizes imported tag casing and deduplicates tags before applying' do
|
|
57
|
+
snapshot = {
|
|
58
|
+
version: 1,
|
|
59
|
+
hosts: {
|
|
60
|
+
web01: { groups: ['web'], tags: ['Prod', 'prod', ' OWNER-Platform ', ''], vars: {} }
|
|
61
|
+
},
|
|
62
|
+
groups: {
|
|
63
|
+
web: { children: [], tags: %w[Frontend frontend], vars: {} }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
result = operation.call(snapshot: snapshot)
|
|
68
|
+
|
|
69
|
+
host = @db.models[:host].find(name: 'web01')
|
|
70
|
+
group = @db.models[:group].find(name: 'web')
|
|
71
|
+
expect(result.associations).to eq(4)
|
|
72
|
+
expect(host.tags_dataset.order(:name).map(:name)).to eq(%w[owner-platform prod])
|
|
73
|
+
expect(group.tags_dataset.order(:name).map(:name)).to eq(%w[frontend])
|
|
74
|
+
expect(@db.models[:tag].order(:name).map(:name)).to eq(%w[frontend owner-platform prod])
|
|
75
|
+
expect(@db.models[:tag].where(name: 'Prod').count).to eq(0)
|
|
76
|
+
|
|
77
|
+
exported = Moose::Inventory::Operations::InventorySnapshot.new(
|
|
78
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
79
|
+
).export
|
|
80
|
+
expect(exported.dig('hosts', 'web01', 'tags')).to eq(%w[owner-platform prod])
|
|
81
|
+
expect(exported.dig('groups', 'web', 'tags')).to eq(%w[frontend])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'rejects unknown group references before writing anything' do
|
|
85
|
+
snapshot = {
|
|
86
|
+
version: 1,
|
|
87
|
+
hosts: { web01: { groups: ['missing'], vars: {} } },
|
|
88
|
+
groups: {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
expect do
|
|
92
|
+
operation.call(snapshot: snapshot)
|
|
93
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /references unknown group 'missing'/)
|
|
94
|
+
|
|
95
|
+
expect(@db.models[:host].count).to eq(0)
|
|
96
|
+
expect(@db.models[:group].count).to eq(0)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'rejects whitespace-only entity names before writing anything' do
|
|
100
|
+
snapshot = {
|
|
101
|
+
version: 1,
|
|
102
|
+
hosts: { ' ' => { groups: [], vars: {} } },
|
|
103
|
+
groups: {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
expect do
|
|
107
|
+
operation.call(snapshot: snapshot)
|
|
108
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /host name cannot be empty/)
|
|
109
|
+
|
|
110
|
+
expect(@db.models[:host].count).to eq(0)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'rejects duplicate normalized keys before applying the snapshot' do
|
|
114
|
+
snapshot = {
|
|
115
|
+
'version' => 1,
|
|
116
|
+
:version => 1,
|
|
117
|
+
hosts: {},
|
|
118
|
+
groups: {}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
expect do
|
|
122
|
+
operation.call(snapshot: snapshot)
|
|
123
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /duplicate normalized key 'version'/)
|
|
124
|
+
|
|
125
|
+
expect(@db.models[:host].count).to eq(0)
|
|
126
|
+
expect(@db.models[:group].count).to eq(0)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'rejects whitespace-only variable names before writing anything' do
|
|
130
|
+
snapshot = {
|
|
131
|
+
version: 1,
|
|
132
|
+
hosts: { web01: { groups: [], vars: { ' ' => 'prod' } } },
|
|
133
|
+
groups: {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
expect do
|
|
137
|
+
operation.call(snapshot: snapshot)
|
|
138
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /variable name cannot be empty/)
|
|
139
|
+
|
|
140
|
+
expect(@db.models[:host].count).to eq(0)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'rejects circular group hierarchies before writing anything' do
|
|
144
|
+
snapshot = {
|
|
145
|
+
version: 1,
|
|
146
|
+
hosts: {},
|
|
147
|
+
groups: {
|
|
148
|
+
parent: { children: ['child'], vars: {} },
|
|
149
|
+
child: { children: ['parent'], vars: {} }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
expect do
|
|
154
|
+
operation.call(snapshot: snapshot)
|
|
155
|
+
end.to raise_error(Moose::Inventory::DB.exceptions[:moose], /contains a cycle/)
|
|
156
|
+
|
|
157
|
+
expect(@db.models[:group].count).to eq(0)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
# rubocop:enable Metrics/BlockLength
|
|
161
|
+
|
|
162
|
+
# rubocop:disable Metrics/BlockLength
|
|
163
|
+
|
|
164
|
+
RSpec.describe Moose::Inventory::Operations::ImportInventorySnapshot, '#preview' do
|
|
165
|
+
before(:all) do
|
|
166
|
+
@mockargs = [
|
|
167
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
168
|
+
'--format', 'yaml',
|
|
169
|
+
'--env', 'test'
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
173
|
+
@db = Moose::Inventory::DB
|
|
174
|
+
@db.init if @db.db.nil?
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
before(:each) do
|
|
178
|
+
@db.reset
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def operation
|
|
182
|
+
described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'returns a non-mutating snapshot import diff' do
|
|
186
|
+
runner = operation
|
|
187
|
+
@db.models[:host].create(name: 'existing')
|
|
188
|
+
group = @db.models[:group].create(name: 'web')
|
|
189
|
+
var = @db.models[:groupvar].create(name: 'role', value: 'old')
|
|
190
|
+
group.add_groupvar(var)
|
|
191
|
+
|
|
192
|
+
snapshot = {
|
|
193
|
+
version: 1,
|
|
194
|
+
hosts: {
|
|
195
|
+
web01: { groups: ['web'], tags: ['Prod'], vars: { env: 'prod' } }
|
|
196
|
+
},
|
|
197
|
+
groups: {
|
|
198
|
+
web: { children: ['blue'], tags: ['Frontend'], vars: { role: 'frontend' } },
|
|
199
|
+
blue: { children: [], tags: [], vars: {} }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
preview = runner.preview(snapshot: snapshot)
|
|
204
|
+
|
|
205
|
+
expect(preview['schema_version']).to eq('snapshot-import-preview-v1')
|
|
206
|
+
expect(preview['changes_applied']).to eq(false)
|
|
207
|
+
expect(preview['summary']).to include(
|
|
208
|
+
'hosts_created' => 1,
|
|
209
|
+
'groups_created' => 1,
|
|
210
|
+
'variables_changed' => 2,
|
|
211
|
+
'associations_added' => 4,
|
|
212
|
+
'ignored_existing_hosts' => 1,
|
|
213
|
+
'destructive_changes' => 0
|
|
214
|
+
)
|
|
215
|
+
expect(preview.dig('creates', 'hosts')).to eq(['web01'])
|
|
216
|
+
expect(preview.dig('creates', 'groups')).to eq(['blue'])
|
|
217
|
+
expect(preview.dig('updates', 'group_vars')).to include(
|
|
218
|
+
'entity' => 'web', 'name' => 'role', 'from' => 'old', 'to' => 'frontend'
|
|
219
|
+
)
|
|
220
|
+
expect(preview.dig('ignored', 'existing_hosts_not_in_snapshot')).to eq(['existing'])
|
|
221
|
+
expect(@db.models[:host].where(name: 'web01').count).to eq(0)
|
|
222
|
+
expect(@db.models[:group].where(name: 'blue').count).to eq(0)
|
|
223
|
+
expect(group.groupvars_dataset[name: 'role'][:value]).to eq('old')
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'inventory_context'
|
|
6
|
+
require 'operations/inventory_doctor'
|
|
7
|
+
|
|
8
|
+
RSpec.describe Moose::Inventory::Operations::InventoryDoctor do
|
|
9
|
+
before(:all) do
|
|
10
|
+
@mockargs = [
|
|
11
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
12
|
+
'--format', 'yaml',
|
|
13
|
+
'--env', 'test'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
17
|
+
@db = Moose::Inventory::DB
|
|
18
|
+
@db.init if @db.db.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
before(:each) do
|
|
22
|
+
@db.reset
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def doctor(config: Moose::Inventory::Config)
|
|
26
|
+
described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db), config: config)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'reports ok when the inventory and config have no findings' do
|
|
30
|
+
web = @db.models[:group].create(name: 'web')
|
|
31
|
+
host = @db.models[:host].create(name: 'web01')
|
|
32
|
+
host.add_group(web)
|
|
33
|
+
|
|
34
|
+
report = doctor.call
|
|
35
|
+
|
|
36
|
+
expect(report[:ok]).to eq(true)
|
|
37
|
+
expect(report[:issues]).to eq([])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'detects inventory health findings' do
|
|
41
|
+
ungrouped = @db.models[:group].create(name: 'ungrouped')
|
|
42
|
+
orphan = @db.models[:group].create(name: 'orphan')
|
|
43
|
+
duplicate = @db.models[:group].create(name: 'or-phan')
|
|
44
|
+
parent = @db.models[:group].create(name: 'parent')
|
|
45
|
+
child = @db.models[:group].create(name: 'child')
|
|
46
|
+
host = @db.models[:host].create(name: 'lonely')
|
|
47
|
+
bad_var = @db.models[:hostvar].create(name: '', value: 'oops')
|
|
48
|
+
host.add_group(ungrouped)
|
|
49
|
+
host.add_hostvar(bad_var)
|
|
50
|
+
parent.add_child(child)
|
|
51
|
+
child.add_child(parent)
|
|
52
|
+
|
|
53
|
+
report = doctor.call
|
|
54
|
+
@db.db[:groups_groups].delete
|
|
55
|
+
|
|
56
|
+
expect(report[:ok]).to eq(false)
|
|
57
|
+
expect(report[:issues].map { |issue| issue[:id] }).to include(
|
|
58
|
+
'host_only_in_ungrouped',
|
|
59
|
+
'orphaned_group',
|
|
60
|
+
'empty_group',
|
|
61
|
+
'duplicateish_group_names',
|
|
62
|
+
'invalid_variable_shape',
|
|
63
|
+
'circular_group_relationship'
|
|
64
|
+
)
|
|
65
|
+
expect(report[:issues].map { |issue| issue[:subject] }).to include(orphan.name)
|
|
66
|
+
expect(report[:issues].map { |issue| issue[:subject] }).to include([duplicate.name, orphan.name].sort)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'detects plaintext password configuration' do
|
|
70
|
+
config = instance_double('Config', db_settings: { adapter: 'mysql', password: 'secret' })
|
|
71
|
+
|
|
72
|
+
report = doctor(config: config).call
|
|
73
|
+
|
|
74
|
+
expect(report[:issues].map { |issue| issue[:id] }).to include('plaintext_password_config')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'inventory_context'
|
|
6
|
+
require 'operations/inventory_snapshot'
|
|
7
|
+
|
|
8
|
+
RSpec.describe Moose::Inventory::Operations::InventorySnapshot do
|
|
9
|
+
before(:all) do
|
|
10
|
+
@mockargs = [
|
|
11
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
12
|
+
'--format', 'yaml',
|
|
13
|
+
'--env', 'test'
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
17
|
+
@db = Moose::Inventory::DB
|
|
18
|
+
@db.init if @db.db.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
before(:each) do
|
|
22
|
+
@db.reset
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'exports hosts, groups, variables, and child relationships in canonical order' do
|
|
26
|
+
host = @db.models[:host].create(name: 'web01')
|
|
27
|
+
group = @db.models[:group].create(name: 'web')
|
|
28
|
+
child = @db.models[:group].create(name: 'blue')
|
|
29
|
+
hostvar = @db.models[:hostvar].create(name: 'env', value: 'prod')
|
|
30
|
+
groupvar = @db.models[:groupvar].create(name: 'role', value: 'frontend')
|
|
31
|
+
host.add_group(group)
|
|
32
|
+
host.add_hostvar(hostvar)
|
|
33
|
+
group.add_child(child)
|
|
34
|
+
group.add_groupvar(groupvar)
|
|
35
|
+
|
|
36
|
+
snapshot = described_class.new(context: Moose::Inventory::InventoryContext.new(db: @db)).export
|
|
37
|
+
|
|
38
|
+
expect(snapshot).to eq(
|
|
39
|
+
'version' => 1,
|
|
40
|
+
'hosts' => {
|
|
41
|
+
'web01' => { 'groups' => ['web'], 'tags' => [], 'vars' => { 'env' => 'prod' } }
|
|
42
|
+
},
|
|
43
|
+
'groups' => {
|
|
44
|
+
'blue' => { 'children' => [], 'tags' => [], 'vars' => {} },
|
|
45
|
+
'web' => { 'children' => ['blue'], 'tags' => [], 'vars' => { 'role' => 'frontend' } }
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'spec_helper'
|
|
5
|
+
require 'operations/operation_event_support'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::OperationEventSupport do
|
|
8
|
+
subject(:operation) { operation_class.new }
|
|
9
|
+
|
|
10
|
+
let(:operation_class) do
|
|
11
|
+
Class.new do
|
|
12
|
+
include Moose::Inventory::Operations::OperationEventSupport
|
|
13
|
+
|
|
14
|
+
def event(type, payload = {})
|
|
15
|
+
build_event(type, payload)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def append_event(events, type, payload = {})
|
|
19
|
+
emit(events, type, payload)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def result(events:, warning_count: 0)
|
|
23
|
+
operation_result(events: events, warning_count: warning_count)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '#build_event' do
|
|
29
|
+
it 'builds structured events with an empty default payload' do
|
|
30
|
+
event = operation.event(:started)
|
|
31
|
+
|
|
32
|
+
expect(event).to be_a(described_class::Event)
|
|
33
|
+
expect(event.type).to eq(:started)
|
|
34
|
+
expect(event.payload).to eq({})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'preserves the provided event payload' do
|
|
38
|
+
event = operation.event(:created, name: 'testhost')
|
|
39
|
+
|
|
40
|
+
expect(event.type).to eq(:created)
|
|
41
|
+
expect(event.payload).to eq(name: 'testhost')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe '#emit' do
|
|
46
|
+
it 'appends a constructed event to the provided collection' do
|
|
47
|
+
events = []
|
|
48
|
+
|
|
49
|
+
result = operation.append_event(events, :warning, name: 'missinggroup')
|
|
50
|
+
event = events.fetch(0)
|
|
51
|
+
|
|
52
|
+
expect(result).to eq(events)
|
|
53
|
+
expect(event).to be_a(described_class::Event)
|
|
54
|
+
expect(event.type).to eq(:warning)
|
|
55
|
+
expect(event.payload).to eq(name: 'missinggroup')
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe '#operation_result' do
|
|
60
|
+
it 'defaults warning_count to zero' do
|
|
61
|
+
events = [operation.event(:ok)]
|
|
62
|
+
|
|
63
|
+
result = operation.result(events: events)
|
|
64
|
+
|
|
65
|
+
expect(result).to be_a(described_class::Result)
|
|
66
|
+
expect(result.events).to eq(events)
|
|
67
|
+
expect(result.warning_count).to eq(0)
|
|
68
|
+
expect(result.warning_count.zero?).to eq(true)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'preserves an explicit warning_count' do
|
|
72
|
+
result = operation.result(events: [], warning_count: 2)
|
|
73
|
+
|
|
74
|
+
expect(result.warning_count).to eq(2)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/query_inventory'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::QueryInventory do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def operation
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'gets host data without rendering output' do
|
|
31
|
+
host = @db.models[:host].create(name: 'test1')
|
|
32
|
+
host.add_group(@db.models[:group].find_or_create(name: 'ungrouped'))
|
|
33
|
+
var = @db.models[:hostvar].create(name: 'foo', value: 'bar')
|
|
34
|
+
host.add_hostvar(var)
|
|
35
|
+
|
|
36
|
+
actual = runner do
|
|
37
|
+
@result = operation.get_hosts(names: ['test1'])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
expected(actual, STDOUT: '', STDERR: '')
|
|
41
|
+
expect(@result).to eq(
|
|
42
|
+
test1: {
|
|
43
|
+
groups: ['ungrouped'],
|
|
44
|
+
hostvars: { foo: 'bar' }
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'filters listed hosts by group, tag, and variable' do
|
|
50
|
+
host = @db.models[:host].create(name: 'web01')
|
|
51
|
+
other = @db.models[:host].create(name: 'db01')
|
|
52
|
+
group = @db.models[:group].find_or_create(name: 'web')
|
|
53
|
+
tag = @db.models[:tag].find_or_create(name: 'prod')
|
|
54
|
+
host.add_group(group)
|
|
55
|
+
host.add_tag(tag)
|
|
56
|
+
other.add_tag(tag)
|
|
57
|
+
hostvar = @db.models[:hostvar].create(name: 'os', value: 'fedora')
|
|
58
|
+
other_var = @db.models[:hostvar].create(name: 'os', value: 'debian')
|
|
59
|
+
host.add_hostvar(hostvar)
|
|
60
|
+
other.add_hostvar(other_var)
|
|
61
|
+
|
|
62
|
+
expect(operation.list_hosts(filters: { groups: ['web'], tags: ['prod'], variables: { 'os' => 'fedora' } })).to eq(
|
|
63
|
+
web01: {
|
|
64
|
+
groups: ['web'],
|
|
65
|
+
tags: ['prod'],
|
|
66
|
+
hostvars: { os: 'fedora' }
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'uses DB-backed host filters without loading all hosts first' do
|
|
72
|
+
host = @db.models[:host].create(name: 'web01')
|
|
73
|
+
other = @db.models[:host].create(name: 'db01')
|
|
74
|
+
group = @db.models[:group].find_or_create(name: 'web')
|
|
75
|
+
tag = @db.models[:tag].find_or_create(name: 'prod')
|
|
76
|
+
host.add_group(group)
|
|
77
|
+
host.add_tag(tag)
|
|
78
|
+
other.add_tag(tag)
|
|
79
|
+
host.add_hostvar(@db.models[:hostvar].create(name: 'os', value: 'fedora'))
|
|
80
|
+
other.add_hostvar(@db.models[:hostvar].create(name: 'os', value: 'debian'))
|
|
81
|
+
context = Moose::Inventory::InventoryContext.new(db: @db)
|
|
82
|
+
expect(context).not_to receive(:all_hosts)
|
|
83
|
+
|
|
84
|
+
result = described_class.new(context: context).list_hosts(
|
|
85
|
+
filters: { groups: ['web'], tags: ['prod'], variables: { 'os' => 'fedora' } }
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
expect(result.keys).to eq([:web01])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'returns no listed hosts when a DB-backed filter references a missing group or tag' do
|
|
92
|
+
@db.models[:host].create(name: 'web01')
|
|
93
|
+
|
|
94
|
+
expect(operation.list_hosts(filters: { groups: ['missing'] })).to eq({})
|
|
95
|
+
expect(operation.list_hosts(filters: { tags: ['missing'] })).to eq({})
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'applies multiple group filters as AND predicates' do
|
|
99
|
+
host = @db.models[:host].create(name: 'web01')
|
|
100
|
+
partial = @db.models[:host].create(name: 'web02')
|
|
101
|
+
web = @db.models[:group].find_or_create(name: 'web')
|
|
102
|
+
prod = @db.models[:group].find_or_create(name: 'prod')
|
|
103
|
+
host.add_group(web)
|
|
104
|
+
host.add_group(prod)
|
|
105
|
+
partial.add_group(web)
|
|
106
|
+
|
|
107
|
+
expect(operation.list_hosts(filters: { groups: %w[web prod] }).keys).to eq([:web01])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'gets group data while omitting empty relationship collections' do
|
|
111
|
+
@db.models[:group].create(name: 'group1')
|
|
112
|
+
|
|
113
|
+
expect(operation.get_groups(names: ['group1'])).to eq(
|
|
114
|
+
group1: {}
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'lists groups in ansible mode with hosts arrays and vars key' do
|
|
119
|
+
group = @db.models[:group].create(name: 'group1')
|
|
120
|
+
var = @db.models[:groupvar].create(name: 'foo', value: 'bar')
|
|
121
|
+
group.add_groupvar(var)
|
|
122
|
+
|
|
123
|
+
expect(operation.list_groups(ansible: true)).to eq(
|
|
124
|
+
group1: {
|
|
125
|
+
hosts: [],
|
|
126
|
+
vars: { foo: 'bar' }
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'builds ansible hostvars metadata for host listvars queries' do
|
|
132
|
+
host = @db.models[:host].create(name: 'test1')
|
|
133
|
+
host.add_group(@db.models[:group].find_or_create(name: 'ungrouped'))
|
|
134
|
+
var = @db.models[:hostvar].create(name: 'foo', value: 'bar')
|
|
135
|
+
host.add_hostvar(var)
|
|
136
|
+
|
|
137
|
+
expect(operation.list_host_vars(names: ['test1'], ansible: true)).to eq(
|
|
138
|
+
foo: 'bar',
|
|
139
|
+
_meta: {
|
|
140
|
+
hostvars: {
|
|
141
|
+
test1: { foo: 'bar' }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'inventory_context'
|
|
5
|
+
require 'operations/remove_associations'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Moose::Inventory::Operations::RemoveAssociations do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@mockargs = [
|
|
10
|
+
'--config', File.join(spec_root, 'config/config.yml'),
|
|
11
|
+
'--format', 'yaml',
|
|
12
|
+
'--env', 'test'
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
Moose::Inventory::Config.init(@mockargs)
|
|
16
|
+
@db = Moose::Inventory::DB
|
|
17
|
+
@db.init if @db.db.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
@db.reset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def operation
|
|
25
|
+
described_class.new(
|
|
26
|
+
context: Moose::Inventory::InventoryContext.new(db: @db)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'removes groups from a host and reports missing associations and ungrouped reattachment' do
|
|
31
|
+
host = @db.models[:host].create(name: 'host1')
|
|
32
|
+
group = @db.models[:group].create(name: 'group1')
|
|
33
|
+
other = @db.models[:group].create(name: 'group2')
|
|
34
|
+
host.add_group(group)
|
|
35
|
+
host.add_group(other)
|
|
36
|
+
|
|
37
|
+
result = operation.host_from_groups(
|
|
38
|
+
host: host,
|
|
39
|
+
host_name: 'host1',
|
|
40
|
+
group_names: %w[group1 missing group2]
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
expect(result.warning_count).to eq(1)
|
|
44
|
+
expect(result.events.map(&:type)).to include(
|
|
45
|
+
:host_group_association_missing,
|
|
46
|
+
:adding_automatic_group
|
|
47
|
+
)
|
|
48
|
+
expect(host.groups_dataset[name: 'group1']).to be_nil
|
|
49
|
+
expect(host.groups_dataset[name: 'group2']).to be_nil
|
|
50
|
+
expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'dry-runs removing groups from a host without removing associations or adding ungrouped' do
|
|
54
|
+
host = @db.models[:host].create(name: 'host1')
|
|
55
|
+
group = @db.models[:group].create(name: 'group1')
|
|
56
|
+
host.add_group(group)
|
|
57
|
+
|
|
58
|
+
result = operation.host_from_groups(
|
|
59
|
+
host: host,
|
|
60
|
+
host_name: 'host1',
|
|
61
|
+
group_names: ['group1'],
|
|
62
|
+
dry_run: true
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
expect(result.warning_count).to eq(0)
|
|
66
|
+
expect(result.events.map(&:type)).to include(:adding_automatic_group, :dry_run_summary)
|
|
67
|
+
expect(host.groups_dataset[name: 'group1']).not_to be_nil
|
|
68
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'dry-runs removing hosts from a group without removing associations or adding ungrouped' do
|
|
72
|
+
group = @db.models[:group].create(name: 'group1')
|
|
73
|
+
host = @db.models[:host].create(name: 'host1')
|
|
74
|
+
group.add_host(host)
|
|
75
|
+
|
|
76
|
+
result = operation.group_from_hosts(
|
|
77
|
+
group: group,
|
|
78
|
+
group_name: 'group1',
|
|
79
|
+
host_names: ['host1'],
|
|
80
|
+
dry_run: true
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
expect(result.warning_count).to eq(0)
|
|
84
|
+
expect(result.events.map(&:type)).to include(:adding_automatic_group, :dry_run_summary)
|
|
85
|
+
expect(group.hosts_dataset[name: 'host1']).not_to be_nil
|
|
86
|
+
expect(host.groups_dataset[name: 'ungrouped']).to be_nil
|
|
87
|
+
end
|
|
88
|
+
it 'removes hosts from a group and reports missing associations and ungrouped reattachment' do
|
|
89
|
+
group = @db.models[:group].create(name: 'group1')
|
|
90
|
+
host1 = @db.models[:host].create(name: 'host1')
|
|
91
|
+
host2 = @db.models[:host].create(name: 'host2')
|
|
92
|
+
extra = @db.models[:group].create(name: 'extra')
|
|
93
|
+
host2.add_group(extra)
|
|
94
|
+
group.add_host(host1)
|
|
95
|
+
group.add_host(host2)
|
|
96
|
+
|
|
97
|
+
result = operation.group_from_hosts(
|
|
98
|
+
group: group,
|
|
99
|
+
group_name: 'group1',
|
|
100
|
+
host_names: %w[host1 missing host2]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
expect(result.warning_count).to eq(1)
|
|
104
|
+
expect(result.events.map(&:type)).to include(
|
|
105
|
+
:group_host_association_missing,
|
|
106
|
+
:adding_automatic_group
|
|
107
|
+
)
|
|
108
|
+
expect(group.hosts_dataset[name: 'host1']).to be_nil
|
|
109
|
+
expect(group.hosts_dataset[name: 'host2']).to be_nil
|
|
110
|
+
expect(host1.groups_dataset[name: 'ungrouped']).not_to be_nil
|
|
111
|
+
expect(host2.groups_dataset[name: 'ungrouped']).to be_nil
|
|
112
|
+
end
|
|
113
|
+
end
|