moose-inventory 1.0.8 → 2.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.
Files changed (104) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +49 -0
  3. data/.github/workflows/release.yml +58 -0
  4. data/.gitignore +1 -1
  5. data/.gitleaks.toml +9 -0
  6. data/.rubocop.yml +19 -784
  7. data/BACKLOG.md +290 -0
  8. data/Gemfile.lock +95 -0
  9. data/README.md +38 -9
  10. data/Rakefile +1 -1
  11. data/bin/moose-inventory +1 -1
  12. data/docs/release/publishing.md +109 -0
  13. data/docs/release/release-readiness.md +55 -0
  14. data/docs/security-audit-2026-05-21.md +71 -0
  15. data/docs/security-audit-2026-05-26-rerun.md +75 -0
  16. data/docs/security-audit-2026-05-26.md +63 -0
  17. data/lib/moose_inventory/cli/formatter.rb +16 -17
  18. data/lib/moose_inventory/cli/group.rb +4 -1
  19. data/lib/moose_inventory/cli/group_add.rb +89 -75
  20. data/lib/moose_inventory/cli/group_addchild.rb +84 -71
  21. data/lib/moose_inventory/cli/group_addhost.rb +78 -69
  22. data/lib/moose_inventory/cli/group_addvar.rb +37 -37
  23. data/lib/moose_inventory/cli/group_get.rb +23 -26
  24. data/lib/moose_inventory/cli/group_list.rb +12 -15
  25. data/lib/moose_inventory/cli/group_listvars.rb +12 -14
  26. data/lib/moose_inventory/cli/group_rm.rb +104 -76
  27. data/lib/moose_inventory/cli/group_rmchild.rb +99 -54
  28. data/lib/moose_inventory/cli/group_rmhost.rb +64 -60
  29. data/lib/moose_inventory/cli/group_rmvar.rb +5 -5
  30. data/lib/moose_inventory/cli/helpers.rb +76 -0
  31. data/lib/moose_inventory/cli/host.rb +4 -1
  32. data/lib/moose_inventory/cli/host_add.rb +51 -66
  33. data/lib/moose_inventory/cli/host_addgroup.rb +77 -68
  34. data/lib/moose_inventory/cli/host_addvar.rb +6 -6
  35. data/lib/moose_inventory/cli/host_get.rb +15 -18
  36. data/lib/moose_inventory/cli/host_list.rb +3 -3
  37. data/lib/moose_inventory/cli/host_listvars.rb +21 -23
  38. data/lib/moose_inventory/cli/host_rm.rb +9 -9
  39. data/lib/moose_inventory/cli/host_rmgroup.rb +63 -60
  40. data/lib/moose_inventory/cli/host_rmvar.rb +3 -3
  41. data/lib/moose_inventory/config/config.rb +43 -40
  42. data/lib/moose_inventory/db/db.rb +92 -52
  43. data/lib/moose_inventory/db/models.rb +11 -12
  44. data/lib/moose_inventory/inventory_context.rb +50 -0
  45. data/lib/moose_inventory/operations/add_associations.rb +127 -0
  46. data/lib/moose_inventory/operations/add_groups.rb +115 -0
  47. data/lib/moose_inventory/operations/add_hosts.rb +110 -0
  48. data/lib/moose_inventory/operations/group_child_relations.rb +118 -0
  49. data/lib/moose_inventory/operations/group_cleanup.rb +55 -0
  50. data/lib/moose_inventory/operations/remove_associations.rb +101 -0
  51. data/lib/moose_inventory/operations/remove_groups.rb +79 -0
  52. data/lib/moose_inventory/version.rb +1 -1
  53. data/moose-inventory.gemspec +38 -20
  54. data/scripts/check.sh +10 -0
  55. data/scripts/ci/check_permissions.sh +35 -0
  56. data/scripts/ci/check_rubocop.sh +28 -0
  57. data/scripts/ci/check_secrets.sh +26 -0
  58. data/scripts/ci/check_security.sh +68 -0
  59. data/scripts/ci/install_security_tools.sh +47 -0
  60. data/scripts/ci/package_sanity.sh +46 -0
  61. data/scripts/files.rb +1 -4
  62. data/scripts/install_dependencies.sh +19 -0
  63. data/scripts/reports.sh +2 -2
  64. data/spec/lib/moose_inventory/cli/cli_spec.rb +13 -14
  65. data/spec/lib/moose_inventory/cli/group_add_spec.rb +118 -119
  66. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +49 -51
  67. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +80 -83
  68. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +91 -91
  69. data/spec/lib/moose_inventory/cli/group_get_spec.rb +22 -23
  70. data/spec/lib/moose_inventory/cli/group_list_spec.rb +19 -20
  71. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +35 -36
  72. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +115 -78
  73. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +86 -45
  74. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +43 -46
  75. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +131 -131
  76. data/spec/lib/moose_inventory/cli/group_spec.rb +9 -9
  77. data/spec/lib/moose_inventory/cli/host_add_spec.rb +103 -43
  78. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +78 -80
  79. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +122 -122
  80. data/spec/lib/moose_inventory/cli/host_get_spec.rb +16 -16
  81. data/spec/lib/moose_inventory/cli/host_list_spec.rb +8 -8
  82. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +50 -52
  83. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +12 -12
  84. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +48 -51
  85. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +136 -136
  86. data/spec/lib/moose_inventory/config/config_spec.rb +16 -3
  87. data/spec/lib/moose_inventory/db/db_spec.rb +386 -2
  88. data/spec/lib/moose_inventory/db/models_spec.rb +10 -11
  89. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +77 -0
  90. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +65 -0
  91. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +69 -0
  92. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +76 -0
  93. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +78 -0
  94. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +57 -0
  95. data/spec/shared/shared_config_setup.rb +2 -2
  96. data/spec/spec_helper.rb +7 -8
  97. metadata +157 -105
  98. data/.coveralls.yml +0 -0
  99. data/Guardfile +0 -38
  100. data/config/dotfiles/coveralls.yml +0 -0
  101. data/config/dotfiles/gitignore +0 -20
  102. data/config/dotfiles/rubocop.yml +0 -793
  103. data/scripts/guard_quality.sh +0 -3
  104. data/scripts/guard_test.sh +0 -2
@@ -1,6 +1,26 @@
1
1
  require 'spec_helper'
2
+ require 'fileutils'
3
+ require 'tmpdir'
2
4
 
3
5
  RSpec.describe 'Moose::Inventory::DB' do
6
+ def with_db_config(db_config)
7
+ saved_db = @db.instance_variable_get(:@db)
8
+ saved_settings = @config._settings.dup
9
+
10
+ begin
11
+ @db.instance_variable_set(:@db, nil)
12
+ @config._settings.clear
13
+ @config._settings[:config] = { db: db_config }
14
+ yield
15
+ ensure
16
+ current_db = @db.instance_variable_get(:@db)
17
+ current_db.disconnect if current_db.respond_to?(:disconnect)
18
+ @db.instance_variable_set(:@db, saved_db)
19
+ @config._settings.clear
20
+ @config._settings.merge!(saved_settings)
21
+ end
22
+ end
23
+
4
24
  #=============================
5
25
  # Initialization
6
26
  #
@@ -10,7 +30,7 @@ RSpec.describe 'Moose::Inventory::DB' do
10
30
  @mockarg_parts = {
11
31
  config: File.join(spec_root, 'config/config.yml'),
12
32
  format: 'yaml',
13
- env: 'test'
33
+ env: 'test',
14
34
  }
15
35
 
16
36
  @mockargs = []
@@ -22,7 +42,7 @@ RSpec.describe 'Moose::Inventory::DB' do
22
42
  @config = Moose::Inventory::Config
23
43
  @config.init(@mockargs)
24
44
 
25
- @db = Moose::Inventory::DB
45
+ @db = Moose::Inventory::DB
26
46
  end
27
47
 
28
48
  #=============================
@@ -47,6 +67,322 @@ RSpec.describe 'Moose::Inventory::DB' do
47
67
 
48
68
  expect(failed).to eq(false)
49
69
  end
70
+
71
+ it 'raises a Moose DB exception for unsupported adapters' do
72
+ saved_db = @db.instance_variable_get(:@db)
73
+ saved_models = @db.instance_variable_get(:@models)
74
+ saved_exceptions = @db.instance_variable_get(:@exceptions)
75
+ saved_settings = @config._settings.dup
76
+
77
+ begin
78
+ @db.instance_variable_set(:@db, nil)
79
+ @db.instance_variable_set(:@models, nil)
80
+ @db.instance_variable_set(:@exceptions, nil)
81
+ @config._settings.clear
82
+ @config._settings[:config] = { db: { adapter: 'unsupported' } }
83
+
84
+ expect { @db.init }.to raise_error(
85
+ Moose::Inventory::DB::MooseDBException,
86
+ /database adapter unsupported is not yet supported/
87
+ )
88
+ expect(@db.exceptions[:moose]).to eq(Moose::Inventory::DB::MooseDBException)
89
+ ensure
90
+ @db.instance_variable_set(:@db, saved_db)
91
+ @db.instance_variable_set(:@models, saved_models)
92
+ @db.instance_variable_set(:@exceptions, saved_exceptions)
93
+ @config._settings.clear
94
+ @config._settings.merge!(saved_settings)
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '.init_exceptions()' do
100
+ it 'is responsive' do
101
+ expect(@db.respond_to?(:init_exceptions)).to eq(true)
102
+ end
103
+ end
104
+
105
+ describe '.connect()' do
106
+ it 'dispatches the documented sqlite3 adapter to the sqlite initializer' do
107
+ with_db_config(adapter: 'sqlite3') do
108
+ expect(@db).to receive(:init_sqlite3) do
109
+ @db.instance_variable_set(:@db, :sqlite_connection)
110
+ end
111
+ @db.connect
112
+ expect(@db.db).to eq(:sqlite_connection)
113
+ end
114
+ end
115
+
116
+ it 'dispatches the documented mysql adapter to the mysql initializer' do
117
+ with_db_config(adapter: 'mysql') do
118
+ expect(@db).to receive(:init_mysql) do
119
+ @db.instance_variable_set(:@db, :mysql_connection)
120
+ end
121
+ @db.connect
122
+ expect(@db.db).to eq(:mysql_connection)
123
+ end
124
+ end
125
+
126
+ it 'dispatches the documented postgresql adapter to the postgresql initializer' do
127
+ with_db_config(adapter: 'postgresql') do
128
+ expect(@db).to receive(:init_postgresql) do
129
+ @db.instance_variable_set(:@db, :postgresql_connection)
130
+ end
131
+ @db.connect
132
+ expect(@db.db).to eq(:postgresql_connection)
133
+ end
134
+ end
135
+ end
136
+
137
+ describe '.init_sqlite3()' do
138
+ it 'raises a Moose DB exception when the configured database file is missing' do
139
+ with_db_config(adapter: 'sqlite3') do
140
+ expect { @db.init_sqlite3 }.to raise_error(
141
+ Moose::Inventory::DB::MooseDBException,
142
+ /Expected key file missing in sqlite3 configuration/
143
+ )
144
+ end
145
+ end
146
+
147
+ it 'creates nested parent directories for configured database files' do
148
+ saved_db = @db.instance_variable_get(:@db)
149
+ saved_settings = @config._settings.dup
150
+ tmpdir = Dir.mktmpdir('moose-inventory-sqlite')
151
+ nested_dbfile = File.join(tmpdir, 'one', 'two', 'inventory.db')
152
+
153
+ begin
154
+ @db.instance_variable_set(:@db, nil)
155
+ @config._settings.clear
156
+ @config._settings[:config] = {
157
+ db: {
158
+ adapter: 'sqlite3',
159
+ file: nested_dbfile,
160
+ },
161
+ }
162
+
163
+ @db.init_sqlite3
164
+
165
+ expect(File.directory?(File.dirname(nested_dbfile))).to eq(true)
166
+ expect(File.file?(nested_dbfile)).to eq(true)
167
+ ensure
168
+ current_db = @db.instance_variable_get(:@db)
169
+ current_db.disconnect if current_db.respond_to?(:disconnect)
170
+ @db.instance_variable_set(:@db, saved_db)
171
+ @config._settings.clear
172
+ @config._settings.merge!(saved_settings)
173
+ FileUtils.remove_entry(tmpdir) if tmpdir && Dir.exist?(tmpdir)
174
+ end
175
+ end
176
+ end
177
+
178
+ describe '.init_mysql()' do
179
+ it 'raises a Moose DB exception when a required connection key is missing' do
180
+ with_db_config(
181
+ adapter: 'mysql',
182
+ database: 'moose_inventory_test',
183
+ user: 'moose',
184
+ password: 'secret'
185
+ ) do
186
+ expect { @db.init_mysql }.to raise_error(
187
+ Moose::Inventory::DB::MooseDBException,
188
+ /Expected key host missing in mysql configuration/
189
+ )
190
+ end
191
+ end
192
+
193
+ it 'raises a Moose DB exception when password and password_env are missing' do
194
+ with_db_config(
195
+ adapter: 'mysql',
196
+ host: 'localhost',
197
+ database: 'moose_inventory_test',
198
+ user: 'moose'
199
+ ) do
200
+ expect { @db.init_mysql }.to raise_error(
201
+ Moose::Inventory::DB::MooseDBException,
202
+ /Expected key password or password_env missing in mysql configuration/
203
+ )
204
+ end
205
+ end
206
+
207
+ it 'uses a mysql password from the configured environment variable' do
208
+ saved_db = @db.instance_variable_get(:@db)
209
+ saved_settings = @config._settings.dup
210
+ saved_password = ENV['MOOSE_INVENTORY_MYSQL_PASSWORD']
211
+ mysql_config = {
212
+ adapter: 'mysql',
213
+ host: 'localhost',
214
+ database: 'moose_inventory_test',
215
+ user: 'moose',
216
+ password_env: 'MOOSE_INVENTORY_MYSQL_PASSWORD',
217
+ }
218
+
219
+ begin
220
+ ENV['MOOSE_INVENTORY_MYSQL_PASSWORD'] = 'env-secret'
221
+ @db.instance_variable_set(:@db, nil)
222
+ @config._settings.clear
223
+ @config._settings[:config] = { db: mysql_config }
224
+
225
+ expect(Sequel).to receive(:mysql2).with(
226
+ user: 'moose',
227
+ password: 'env-secret',
228
+ host: 'localhost',
229
+ database: 'moose_inventory_test'
230
+ ).and_return(:mysql2_connection)
231
+
232
+ @db.init_mysql
233
+ expect(@db.db).to eq(:mysql2_connection)
234
+ ensure
235
+ if saved_password.nil?
236
+ ENV.delete('MOOSE_INVENTORY_MYSQL_PASSWORD')
237
+ else
238
+ ENV['MOOSE_INVENTORY_MYSQL_PASSWORD'] = saved_password
239
+ end
240
+ @db.instance_variable_set(:@db, saved_db)
241
+ @config._settings.clear
242
+ @config._settings.merge!(saved_settings)
243
+ end
244
+ end
245
+
246
+ it 'uses the mysql2 Sequel adapter with configured connection settings' do
247
+ saved_db = @db.instance_variable_get(:@db)
248
+ saved_settings = @config._settings.dup
249
+ mysql_config = {
250
+ adapter: 'mysql',
251
+ host: 'localhost',
252
+ database: 'moose_inventory_test',
253
+ user: 'moose',
254
+ password: 'secret',
255
+ }
256
+
257
+ begin
258
+ @db.instance_variable_set(:@db, nil)
259
+ @config._settings.clear
260
+ @config._settings[:config] = { db: mysql_config }
261
+
262
+ expect(Sequel).to receive(:mysql2).with(
263
+ user: 'moose',
264
+ password: 'secret',
265
+ host: 'localhost',
266
+ database: 'moose_inventory_test'
267
+ ).and_return(:mysql2_connection)
268
+
269
+ @db.init_mysql
270
+ expect(@db.db).to eq(:mysql2_connection)
271
+ ensure
272
+ @db.instance_variable_set(:@db, saved_db)
273
+ @config._settings.clear
274
+ @config._settings.merge!(saved_settings)
275
+ end
276
+ end
277
+ end
278
+
279
+ describe '.init_postgresql()' do
280
+ it 'raises a Moose DB exception when a required connection key is missing' do
281
+ with_db_config(
282
+ adapter: 'postgresql',
283
+ host: 'localhost',
284
+ database: 'moose_inventory_test',
285
+ password: 'secret'
286
+ ) do
287
+ expect { @db.init_postgresql }.to raise_error(
288
+ Moose::Inventory::DB::MooseDBException,
289
+ /Expected key user missing in postgresql configuration/
290
+ )
291
+ end
292
+ end
293
+
294
+ it 'raises a Moose DB exception when password_env points to an unset variable' do
295
+ saved_password = ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD']
296
+ ENV.delete('MOOSE_INVENTORY_POSTGRES_PASSWORD')
297
+
298
+ begin
299
+ with_db_config(
300
+ adapter: 'postgresql',
301
+ host: 'localhost',
302
+ database: 'moose_inventory_test',
303
+ user: 'moose',
304
+ password_env: 'MOOSE_INVENTORY_POSTGRES_PASSWORD'
305
+ ) do
306
+ expect { @db.init_postgresql }.to raise_error(
307
+ Moose::Inventory::DB::MooseDBException,
308
+ /Environment variable MOOSE_INVENTORY_POSTGRES_PASSWORD is not set for postgresql password/
309
+ )
310
+ end
311
+ ensure
312
+ ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = saved_password unless saved_password.nil?
313
+ end
314
+ end
315
+
316
+ it 'uses a postgresql password from the configured environment variable' do
317
+ saved_db = @db.instance_variable_get(:@db)
318
+ saved_settings = @config._settings.dup
319
+ saved_password = ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD']
320
+ postgresql_config = {
321
+ adapter: 'postgresql',
322
+ host: 'localhost',
323
+ database: 'moose_inventory_test',
324
+ user: 'moose',
325
+ password_env: 'MOOSE_INVENTORY_POSTGRES_PASSWORD',
326
+ }
327
+
328
+ begin
329
+ ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = 'env-secret'
330
+ @db.instance_variable_set(:@db, nil)
331
+ @config._settings.clear
332
+ @config._settings[:config] = { db: postgresql_config }
333
+
334
+ expect(Sequel).to receive(:postgres).with(
335
+ user: 'moose',
336
+ password: 'env-secret',
337
+ host: 'localhost',
338
+ database: 'moose_inventory_test'
339
+ ).and_return(:postgresql_connection)
340
+
341
+ @db.init_postgresql
342
+ expect(@db.db).to eq(:postgresql_connection)
343
+ ensure
344
+ if saved_password.nil?
345
+ ENV.delete('MOOSE_INVENTORY_POSTGRES_PASSWORD')
346
+ else
347
+ ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = saved_password
348
+ end
349
+ @db.instance_variable_set(:@db, saved_db)
350
+ @config._settings.clear
351
+ @config._settings.merge!(saved_settings)
352
+ end
353
+ end
354
+
355
+ it 'uses the postgres Sequel adapter with configured connection settings' do
356
+ saved_db = @db.instance_variable_get(:@db)
357
+ saved_settings = @config._settings.dup
358
+ postgresql_config = {
359
+ adapter: 'postgresql',
360
+ host: 'localhost',
361
+ database: 'moose_inventory_test',
362
+ user: 'moose',
363
+ password: 'secret',
364
+ }
365
+
366
+ begin
367
+ @db.instance_variable_set(:@db, nil)
368
+ @config._settings.clear
369
+ @config._settings[:config] = { db: postgresql_config }
370
+
371
+ expect(Sequel).to receive(:postgres).with(
372
+ user: 'moose',
373
+ password: 'secret',
374
+ host: 'localhost',
375
+ database: 'moose_inventory_test'
376
+ ).and_return(:postgresql_connection)
377
+
378
+ @db.init_postgresql
379
+ expect(@db.db).to eq(:postgresql_connection)
380
+ ensure
381
+ @db.instance_variable_set(:@db, saved_db)
382
+ @config._settings.clear
383
+ @config._settings.merge!(saved_settings)
384
+ end
385
+ end
50
386
  end
51
387
 
52
388
  describe '.db' do
@@ -180,5 +516,53 @@ RSpec.describe 'Moose::Inventory::DB' do
180
516
 
181
517
  expect(count[:final]).to eq(count[:initial])
182
518
  end
519
+
520
+ it 'prints concise Moose DB transaction errors by default' do
521
+ saved_trace = @config._confopts[:trace]
522
+ @config._confopts[:trace] = false
523
+
524
+ begin
525
+ actual = runner do
526
+ @db.transaction do
527
+ fail @db.exceptions[:moose], 'Trace regression target'
528
+ end
529
+ end
530
+ ensure
531
+ @config._confopts[:trace] = saved_trace
532
+ end
533
+
534
+ expect(actual[:unexpected]).to eq(false)
535
+ expect(actual[:aborted]).to eq(true)
536
+ expect(actual[:STDERR]).to eq(
537
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
538
+ "ERROR: Trace regression target\n"
539
+ )
540
+ end
541
+
542
+ it 'prints the Moose DB exception backtrace when trace is enabled' do
543
+ saved_trace = @config._confopts[:trace]
544
+ @config._confopts[:trace] = true
545
+
546
+ begin
547
+ actual = runner do
548
+ @db.transaction do
549
+ fail @db.exceptions[:moose], 'Trace regression target'
550
+ end
551
+ end
552
+ ensure
553
+ @config._confopts[:trace] = saved_trace
554
+ end
555
+
556
+ expect(actual[:unexpected]).to eq(false)
557
+ expect(actual[:aborted]).to eq(true)
558
+ expect(actual[:STDERR]).to include(
559
+ "An error occurred during a transaction, any changes have been rolled back.\n"
560
+ )
561
+ expect(actual[:STDERR]).to include('Moose::Inventory::DB::MooseDBException')
562
+ expect(actual[:STDERR]).to include('Trace regression target')
563
+ expect(actual[:STDERR]).to include('spec/lib/moose_inventory/db/db_spec.rb')
564
+ expect(actual[:STDERR]).to include("ERROR: Trace regression target\n")
565
+ expect(actual[:STDERR]).not_to include('NoMethodError')
566
+ end
183
567
  end
184
568
  end
@@ -10,7 +10,7 @@ RSpec.describe 'models' do
10
10
  @mockarg_parts = {
11
11
  config: File.join(spec_root, 'config/config.yml'),
12
12
  format: 'yaml',
13
- env: 'test'
13
+ env: 'test',
14
14
  }
15
15
 
16
16
  @mockargs = []
@@ -128,45 +128,44 @@ RSpec.describe 'models' do
128
128
  end
129
129
 
130
130
  it 'should have a many-to-many self-referential relationship (i.e. GROUPS of GROUPS)' do
131
-
132
131
  parent1 = @db.models[:group].create(name: 'parent1')
133
132
  parent2 = @db.models[:group].create(name: 'parent2')
134
133
  child1 = @db.models[:group].create(name: 'child1')
135
134
  child2 = @db.models[:group].create(name: 'child2')
136
135
 
137
- parent1.add_child(child1)
136
+ parent1.add_child(child1)
138
137
  parent1.add_child(child2)
139
138
 
140
- parent2.add_child(child1)
139
+ parent2.add_child(child1)
141
140
  parent2.add_child(child2)
142
-
141
+
143
142
  group = @db.models[:group].find(name: 'parent1')
144
143
  children = group.children_dataset
145
144
 
146
145
  expect(children).not_to be_nil
147
146
  expect(children.count).to eq(2)
148
-
147
+
149
148
  group = @db.models[:group].find(name: 'parent2')
150
149
  children = group.children_dataset
151
150
 
152
151
  expect(children).not_to be_nil
153
152
  expect(children.count).to eq(2)
154
153
  end
155
-
154
+
156
155
  it 'should have relationships with Hostvars' do
157
156
  groupname = 'group-test'
158
157
  groupvarname = 'groupvar-test'
159
158
  groupvarval = '1'
160
-
159
+
161
160
  group = @db.models[:group].create(name: groupname)
162
161
  groupvar = @db.models[:groupvar].create(name: groupvarname,
163
162
  value: groupvarval)
164
-
163
+
165
164
  group.add_groupvar(groupvar)
166
-
165
+
167
166
  group = @db.models[:group].find(name: groupname)
168
167
  groupvars = group.groupvars_dataset
169
-
168
+
170
169
  expect(groupvars).not_to be_nil
171
170
  expect(groupvars.count).to eq(1)
172
171
  expect(groupvars.first[:name]).to eq(groupvarname)
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'inventory_context'
5
+ require 'operations/add_associations'
6
+
7
+ RSpec.describe Moose::Inventory::Operations::AddAssociations 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 'adds groups to an existing host and reports creation/duplicate events' do
31
+ host = @db.models[:host].create(name: 'host1')
32
+ ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
33
+ host.add_group(ungrouped)
34
+ existing_group = @db.models[:group].create(name: 'existing')
35
+ host.add_group(existing_group)
36
+
37
+ result = operation.host_to_groups(
38
+ host: host,
39
+ host_name: 'host1',
40
+ group_names: %w[existing created]
41
+ )
42
+
43
+ expect(result.warning_count).to eq(2)
44
+ expect(result.events.map(&:type)).to include(
45
+ :host_group_association_exists,
46
+ :group_missing_created,
47
+ :removing_automatic_group
48
+ )
49
+ expect(host.groups_dataset[name: 'created']).not_to be_nil
50
+ expect(host.groups_dataset[name: 'ungrouped']).to be_nil
51
+ end
52
+
53
+ it 'adds hosts to an existing group and reports creation/duplicate events' do
54
+ group = @db.models[:group].create(name: 'group1')
55
+ duplicate_host = @db.models[:host].create(name: 'host1')
56
+ existing_host = @db.models[:host].create(name: 'host3')
57
+ ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
58
+ duplicate_host.add_group(ungrouped)
59
+ existing_host.add_group(ungrouped)
60
+ group.add_host(duplicate_host)
61
+
62
+ result = operation.group_to_hosts(
63
+ group: group,
64
+ group_name: 'group1',
65
+ host_names: %w[host1 host2 host3]
66
+ )
67
+
68
+ expect(result.warning_count).to eq(2)
69
+ expect(result.events.map(&:type)).to include(
70
+ :group_host_association_exists,
71
+ :host_missing_created,
72
+ :removing_automatic_group
73
+ )
74
+ expect(group.hosts_dataset[name: 'host2']).not_to be_nil
75
+ expect(existing_host.groups_dataset[name: 'ungrouped']).to be_nil
76
+ end
77
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'inventory_context'
5
+ require 'operations/add_groups'
6
+
7
+ RSpec.describe Moose::Inventory::Operations::AddGroups 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 'adds a group and returns structured events without rendering output' do
31
+ actual = runner do
32
+ @result = operation.call(names: ['testgroup'], hosts: [])
33
+ end
34
+
35
+ expected(actual, STDOUT: '', STDERR: '')
36
+ expect(@result.warning_count).to eq(0)
37
+ expect(@result.events.map(&:type)).to eq(%i[group_started creating_group ok group_complete])
38
+
39
+ group = @db.models[:group].find(name: 'testgroup')
40
+ expect(group).not_to be_nil
41
+ end
42
+
43
+ it 'reports existing groups, created hosts, duplicate associations, and ungrouped removal as events' do
44
+ host = @db.models[:host].create(name: 'testhost')
45
+ ungrouped = @db.models[:group].find_or_create(name: 'ungrouped')
46
+ host.add_group(ungrouped)
47
+ group = @db.models[:group].create(name: 'testgroup')
48
+ group.add_host(host)
49
+
50
+ @result = operation.call(
51
+ names: ['testgroup'],
52
+ hosts: %w[testhost newhost]
53
+ )
54
+
55
+ expect(@result.warning_count).to eq(3)
56
+ expect(@result.events.map(&:type)).to include(
57
+ :group_exists,
58
+ :association_exists,
59
+ :host_missing_created,
60
+ :removing_automatic_group
61
+ )
62
+ expect(@db.models[:host].find(name: 'newhost')).not_to be_nil
63
+ expect(host.groups_dataset[name: 'ungrouped']).to be_nil
64
+ end
65
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Moose::Inventory::Operations::AddHosts do
6
+ before(:all) do
7
+ @mockargs = [
8
+ '--config', File.join(spec_root, 'config/config.yml'),
9
+ '--format', 'yaml',
10
+ '--env', 'test'
11
+ ]
12
+
13
+ Moose::Inventory::Config.init(@mockargs)
14
+ @db = Moose::Inventory::DB
15
+ @db.init if @db.db.nil?
16
+ end
17
+
18
+ before(:each) do
19
+ @db.reset
20
+ end
21
+
22
+ def operation
23
+ described_class.new(
24
+ context: Moose::Inventory::InventoryContext.new(db: @db)
25
+ )
26
+ end
27
+
28
+ describe '#call' do
29
+ it 'adds a host and returns structured events without rendering output' do
30
+ actual = runner do
31
+ @result = operation.call(names: ['testhost'], groups: [])
32
+ end
33
+
34
+ expected(actual, STDOUT: '', STDERR: '')
35
+ expect(@result.events.map(&:type)).to eq(
36
+ %i[host_started creating_host ok adding_automatic_group ok host_complete]
37
+ )
38
+ expect(@result.events[0].payload).to eq(name: 'testhost')
39
+ expect(@result.events[3].payload).to eq(host: 'testhost', group: 'ungrouped')
40
+
41
+ host = @db.models[:host].find(name: 'testhost')
42
+ expect(host).not_to be_nil
43
+ expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
44
+ end
45
+
46
+ it 'reports existing hosts, missing groups, and duplicate associations as events' do
47
+ host = @db.models[:host].create(name: 'testhost')
48
+ group = @db.models[:group].create(name: 'existinggroup')
49
+ host.add_group(group)
50
+
51
+ @result = operation.call(
52
+ names: ['testhost'],
53
+ groups: %w[existinggroup missinggroup]
54
+ )
55
+
56
+ expect(@result.events.map(&:type)).to include(
57
+ :host_exists,
58
+ :association_exists,
59
+ :group_missing_created
60
+ )
61
+ expect(@result.events.find { |event| event.type == :host_exists }.payload).to eq(name: 'testhost')
62
+ expect(@result.events.find { |event| event.type == :association_exists }.payload).to eq(
63
+ host: 'testhost',
64
+ group: 'existinggroup'
65
+ )
66
+ expect(@db.models[:group].find(name: 'missinggroup')).not_to be_nil
67
+ end
68
+ end
69
+ end