moose-inventory 2.0 → 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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +2 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +21 -0
  5. data/BACKLOG.md +630 -8
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +1 -1
  8. data/README.md +315 -39
  9. data/Rakefile +2 -0
  10. data/bin/moose-inventory +2 -1
  11. data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
  12. data/docs/compatibility/cli-output-compatibility.md +76 -0
  13. data/docs/governance/approval-register.md +37 -0
  14. data/docs/maintenance/database-backup-restore-guidance.md +162 -0
  15. data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
  16. data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
  17. data/docs/product/product-brief.md +161 -0
  18. data/docs/product/requirements-baseline.md +477 -0
  19. data/docs/qa/qa-documentation-and-release-gates.md +283 -0
  20. data/docs/release/package-provenance-hardening.md +126 -0
  21. data/docs/release/publishing.md +11 -3
  22. data/docs/release/release-environment-protection.md +70 -0
  23. data/docs/release/release-readiness.md +23 -4
  24. data/docs/security/accepted-risk-register.md +84 -0
  25. data/docs/security/security-privacy-process.md +287 -0
  26. data/docs/security-audit-2026-05-26-rerun.md +2 -2
  27. data/docs/ux/cli-workflow-notes.md +287 -0
  28. data/examples/ansible/ansible.cfg +3 -0
  29. data/examples/ansible/inventory/moose_inventory.yml +5 -0
  30. data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
  31. data/examples/ci/README.md +16 -0
  32. data/examples/ci/github-actions/inventory-review.yml +38 -0
  33. data/examples/ci/inventory/example-snapshot.yml +19 -0
  34. data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
  35. data/lib/moose_inventory/cli/application.rb +133 -5
  36. data/lib/moose_inventory/cli/association_rendering.rb +74 -0
  37. data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
  38. data/lib/moose_inventory/cli/audit.rb +62 -0
  39. data/lib/moose_inventory/cli/audit_recording.rb +40 -0
  40. data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
  41. data/lib/moose_inventory/cli/console.rb +135 -0
  42. data/lib/moose_inventory/cli/db.rb +64 -0
  43. data/lib/moose_inventory/cli/factory.rb +28 -0
  44. data/lib/moose_inventory/cli/formatter.rb +8 -12
  45. data/lib/moose_inventory/cli/group.rb +5 -2
  46. data/lib/moose_inventory/cli/group_add.rb +11 -9
  47. data/lib/moose_inventory/cli/group_addchild.rb +23 -65
  48. data/lib/moose_inventory/cli/group_addhost.rb +16 -67
  49. data/lib/moose_inventory/cli/group_addvar.rb +27 -47
  50. data/lib/moose_inventory/cli/group_get.rb +8 -42
  51. data/lib/moose_inventory/cli/group_list.rb +7 -40
  52. data/lib/moose_inventory/cli/group_listvars.rb +9 -55
  53. data/lib/moose_inventory/cli/group_rm.rb +12 -10
  54. data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
  55. data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
  56. data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
  57. data/lib/moose_inventory/cli/group_tags.rb +33 -0
  58. data/lib/moose_inventory/cli/helpers.rb +68 -1
  59. data/lib/moose_inventory/cli/host.rb +6 -3
  60. data/lib/moose_inventory/cli/host_add.rb +69 -29
  61. data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
  62. data/lib/moose_inventory/cli/host_addvar.rb +28 -52
  63. data/lib/moose_inventory/cli/host_get.rb +9 -37
  64. data/lib/moose_inventory/cli/host_list.rb +24 -21
  65. data/lib/moose_inventory/cli/host_listvars.rb +9 -62
  66. data/lib/moose_inventory/cli/host_rm.rb +60 -42
  67. data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
  68. data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
  69. data/lib/moose_inventory/cli/host_tags.rb +33 -0
  70. data/lib/moose_inventory/cli/listvars_support.rb +55 -0
  71. data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
  72. data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
  73. data/lib/moose_inventory/cli/tag_support.rb +97 -0
  74. data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
  75. data/lib/moose_inventory/config/config.rb +185 -108
  76. data/lib/moose_inventory/db/db.rb +170 -195
  77. data/lib/moose_inventory/db/exceptions.rb +6 -3
  78. data/lib/moose_inventory/db/models.rb +16 -0
  79. data/lib/moose_inventory/db/schema_migrations.rb +248 -0
  80. data/lib/moose_inventory/inventory_context.rb +68 -2
  81. data/lib/moose_inventory/operations/add_associations.rb +20 -16
  82. data/lib/moose_inventory/operations/add_groups.rb +21 -13
  83. data/lib/moose_inventory/operations/add_hosts.rb +30 -17
  84. data/lib/moose_inventory/operations/add_variables.rb +77 -0
  85. data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
  86. data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
  87. data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
  88. data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
  89. data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
  90. data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
  91. data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
  92. data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
  93. data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
  94. data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
  95. data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
  96. data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
  97. data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
  98. data/lib/moose_inventory/operations/query_inventory.rb +47 -0
  99. data/lib/moose_inventory/operations/remove_associations.rb +30 -18
  100. data/lib/moose_inventory/operations/remove_groups.rb +12 -12
  101. data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
  102. data/lib/moose_inventory/operations/remove_variables.rb +67 -0
  103. data/lib/moose_inventory/runtime_options.rb +31 -0
  104. data/lib/moose_inventory/version.rb +3 -1
  105. data/lib/moose_inventory.rb +10 -7
  106. data/moose-inventory.gemspec +19 -35
  107. data/scripts/check.sh +1 -0
  108. data/scripts/ci/check_generated_artifacts.sh +41 -0
  109. data/scripts/ci/check_permissions.sh +2 -0
  110. data/scripts/ci/check_rubocop.sh +30 -25
  111. data/scripts/files.rb +5 -4
  112. data/spec/examples/ci_examples_spec.rb +37 -0
  113. data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
  114. data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
  115. data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
  116. data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
  117. data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
  118. data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
  119. data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
  120. data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
  121. data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
  122. data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
  123. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
  124. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
  125. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
  126. data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
  127. data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
  128. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
  129. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
  130. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
  131. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
  132. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
  133. data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
  134. data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
  135. data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
  136. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
  137. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
  138. data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
  139. data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
  140. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
  141. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
  142. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
  143. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
  144. data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
  145. data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
  146. data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
  147. data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
  148. data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
  149. data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
  150. data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
  151. data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
  152. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
  153. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
  154. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
  155. data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
  156. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
  157. data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
  158. data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
  159. data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
  160. data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
  161. data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
  162. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
  163. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
  164. data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
  165. data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
  166. data/spec/shared/shared_config_setup.rb +4 -3
  167. data/spec/spec_helper.rb +50 -40
  168. data/spec/support/cli_harness.rb +33 -0
  169. metadata +80 -41
@@ -1,52 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
1
4
  require 'spec_helper'
2
5
 
3
- # TODO: the usual respond_to? method doesn't seem to work on Thor objects.
4
6
  # Why not? For now, we'll check against instance_methods.
5
7
 
6
8
  RSpec.describe Moose::Inventory::Cli::Group do
7
9
  before(:all) do
8
- # Set up the configuration object
9
- @mockarg_parts = {
10
- config: File.join(spec_root, 'config/config.yml'),
11
- format: 'yaml',
12
- env: 'test',
13
- }
14
-
15
- @mockargs = []
16
- @mockarg_parts.each do |key, val|
17
- @mockargs << "--#{key}"
18
- @mockargs << val
19
- end
20
- @mockargs << '--trace' # extra info for debugging
21
-
22
- @console = Moose::Inventory::Cli::Formatter
23
-
24
- @config = Moose::Inventory::Config
25
- @config.init(@mockargs)
26
-
27
- @db = Moose::Inventory::DB
28
- @db.init if @db.db.nil?
29
-
30
- @host = Moose::Inventory::Cli::Host
31
- @group = Moose::Inventory::Cli::Group
32
- @app = Moose::Inventory::Cli::Application
10
+ setup_cli_harness(
11
+ command_class: Moose::Inventory::Cli::Group,
12
+ command_ivar: :@group,
13
+ extra_commands: { :@host => Moose::Inventory::Cli::Host },
14
+ extra_args: ['--trace']
15
+ )
33
16
  end
34
17
 
35
18
  before(:each) do
36
- @db.reset
19
+ reset_cli_harness
37
20
  end
38
21
 
39
22
  # ============================
40
23
  describe 'add' do
41
24
  # --------------------
42
25
  it 'Group.add() method should be responsive' do
43
- result = @group.instance_methods(false).include?(:add)
26
+ result = @group.method_defined?(:add, false)
44
27
  expect(result).to eq(true)
45
28
  end
46
29
 
47
30
  # --------------------
48
31
  it '<no arguments> ... should bail with an error' do
49
- actual = runner { @app.start(%w(group add)) }
32
+ actual = runner { @app.start(%w[group add]) }
50
33
 
51
34
  desired = { aborted: true }
52
35
  desired[:STDERR] = "ERROR: Wrong number of arguments, 0 for 1 or more.\n"
@@ -55,7 +38,7 @@ RSpec.describe Moose::Inventory::Cli::Group do
55
38
 
56
39
  # --------------------
57
40
  it 'ungrouped ... should abort with an error' do
58
- actual = runner { @app.start(%w(group add ungrouped)) }
41
+ actual = runner { @app.start(%w[group add ungrouped]) }
59
42
 
60
43
  # Check output
61
44
  desired = { aborted: true }
@@ -67,17 +50,17 @@ RSpec.describe Moose::Inventory::Cli::Group do
67
50
  # --------------------
68
51
  it 'GROUP ... should add a group to the db' do
69
52
  name = 'test'
70
- actual = runner { @app.start(%W(group add #{name})) }
53
+ actual = runner { @app.start(%W[group add #{name}]) }
71
54
 
72
55
  # @console.out(actual)
73
56
 
74
57
  # Check output
75
58
  desired = {}
76
59
  desired[:STDOUT] =
77
- "Add group '#{name}':\n"\
78
- " - create group...\n"\
79
- " - OK\n"\
80
- " - all OK\n"\
60
+ "Add group '#{name}':\n " \
61
+ "- create group...\n " \
62
+ "- OK\n " \
63
+ "- all OK\n" \
81
64
  "Succeeded\n"
82
65
 
83
66
  expected(actual, desired)
@@ -87,23 +70,68 @@ RSpec.describe Moose::Inventory::Cli::Group do
87
70
  expect(group[:name]).to eq(name)
88
71
  end
89
72
 
73
+ # --------------------
74
+ it 'GROUP --dry-run should show planned group creation without writing to the db' do
75
+ name = 'dry-run-group'
76
+
77
+ actual = runner { @app.start(%W[group add #{name} --dry-run]) }
78
+
79
+ desired = {}
80
+ desired[:STDOUT] =
81
+ "Add group '#{name}':\n " \
82
+ "- create group...\n " \
83
+ "- OK\n " \
84
+ "- all OK\n" \
85
+ "Dry run complete. No changes applied.\n" \
86
+ "Succeeded\n"
87
+
88
+ expected(actual, desired)
89
+ expect(@db.models[:group].find(name: name)).to be_nil
90
+ end
91
+
92
+ # --------------------
93
+ it 'GROUP --hosts HOST --dry-run should not create the group or missing host' do
94
+ group_name = 'dry-run-group'
95
+ host_name = 'dry-run-host'
96
+
97
+ actual = runner { @app.start(%W[group add #{group_name} --hosts #{host_name} --dry-run]) }
98
+
99
+ desired = {}
100
+ desired[:STDOUT] =
101
+ "Add group '#{group_name}':\n " \
102
+ "- create group...\n " \
103
+ "- OK\n " \
104
+ "- add association {group:#{group_name} <-> host:#{host_name}}...\n " \
105
+ "- host doesn't exist, creating now...\n " \
106
+ "- OK\n " \
107
+ "- OK\n " \
108
+ "- all OK\n" \
109
+ "Dry run complete. No changes applied.\n" \
110
+ "Succeeded, with warnings.\n"
111
+ desired[:STDERR] =
112
+ "WARNING: Host '#{host_name}' doesn't exist, but will be created.\n"
113
+
114
+ expected(actual, desired)
115
+ expect(@db.models[:group].find(name: group_name)).to be_nil
116
+ expect(@db.models[:host].find(name: host_name)).to be_nil
117
+ end
90
118
  # --------------------
91
119
  it 'GROUP ... should skip GROUP creation if it already exists' do
92
120
  name = 'test-group'
93
121
  @db.models[:group].create(name: name)
94
122
 
95
- actual = runner { @app.start(%W(group add #{name})) }
123
+ actual = runner { @app.start(%W[group add #{name}]) }
96
124
 
97
125
  # @console.out(actual)
98
126
 
99
127
  # Check output
100
128
  desired = {}
101
129
  desired[:STDOUT] =
102
- "Add group '#{name}':\n"\
103
- " - create group...\n"\
104
- " - already exists, skipping.\n"\
105
- " - OK\n"\
106
- " - all OK\n"\
130
+ "Add group '#{name}':\n " \
131
+ "- create group...\n " \
132
+ "- already exists, skipping.\n " \
133
+ "- OK\n " \
134
+ "- all OK\n" \
107
135
  "Succeeded, with warnings.\n"
108
136
  desired[:STDERR] =
109
137
  "WARNING: Group '#{name}' already exists, skipping creation.\n"
@@ -115,9 +143,9 @@ RSpec.describe Moose::Inventory::Cli::Group do
115
143
 
116
144
  # --------------------
117
145
  it 'GROUP1 GROUP2 GROUP3 ... should add multiple groups' do
118
- names = %w(test1 test2 test3)
146
+ names = %w[test1 test2 test3]
119
147
 
120
- actual = runner { @app.start(%w(group add) + names) }
148
+ actual = runner { @app.start(%w[group add] + names) }
121
149
 
122
150
  # @console.out(actual)
123
151
 
@@ -125,12 +153,12 @@ RSpec.describe Moose::Inventory::Cli::Group do
125
153
  desired = { STDOUT: '' }
126
154
  names.each do |name|
127
155
  desired[:STDOUT] = desired[:STDOUT] +
128
- "Add group '#{name}':\n"\
129
- " - create group...\n"\
130
- " - OK\n"\
131
- " - all OK\n"\
156
+ "Add group '#{name}':\n " \
157
+ "- create group...\n " \
158
+ "- OK\n " \
159
+ "- all OK\n" \
132
160
  end
133
- desired[:STDOUT] = desired[:STDOUT] + "Succeeded\n"
161
+ desired[:STDOUT] = "#{desired[:STDOUT]}Succeeded\n"
134
162
 
135
163
  expected(actual, desired)
136
164
 
@@ -142,15 +170,14 @@ RSpec.describe Moose::Inventory::Cli::Group do
142
170
  end
143
171
 
144
172
  # --------------------
145
- it 'GROUP1 --hosts HOST1 ... should add the '\
146
- 'group and associate it with existing hosts' do
147
-
173
+ it 'GROUP1 --hosts HOST1 ... should add the ' \
174
+ 'group and associate it with existing hosts' do
148
175
  host_name = 'test-host'
149
176
  @db.models[:host].create(name: host_name)
150
177
 
151
178
  group_name = 'test-group'
152
179
  actual = runner do
153
- @app.start(%W(group add #{group_name} --hosts #{host_name}))
180
+ @app.start(%W[group add #{group_name} --hosts #{host_name}])
154
181
  end
155
182
 
156
183
  # @console.out(actual)
@@ -158,12 +185,12 @@ RSpec.describe Moose::Inventory::Cli::Group do
158
185
  # Check output
159
186
  desired = {}
160
187
  desired[:STDOUT] =
161
- "Add group '#{group_name}':\n"\
162
- " - create group...\n"\
163
- " - OK\n"\
164
- " - add association {group:#{group_name} <-> host:#{host_name}}...\n"\
165
- " - OK\n"\
166
- " - all OK\n"\
188
+ "Add group '#{group_name}':\n " \
189
+ "- create group...\n " \
190
+ "- OK\n " \
191
+ "- add association {group:#{group_name} <-> host:#{host_name}}...\n " \
192
+ "- OK\n " \
193
+ "- all OK\n" \
167
194
  "Succeeded\n"
168
195
 
169
196
  expected(actual, desired)
@@ -184,7 +211,7 @@ RSpec.describe Moose::Inventory::Cli::Group do
184
211
 
185
212
  group_name = 'test-group'
186
213
  actual = runner do
187
- @app.start(%W(group add #{group_name} --hosts #{host_name}))
214
+ @app.start(%W[group add #{group_name} --hosts #{host_name}])
188
215
  end
189
216
 
190
217
  # @console.out(actual)
@@ -192,14 +219,14 @@ RSpec.describe Moose::Inventory::Cli::Group do
192
219
  # Check output
193
220
  desired = {}
194
221
  desired[:STDOUT] =
195
- "Add group '#{group_name}':\n"\
196
- " - create group...\n"\
197
- " - OK\n"\
198
- " - add association {group:#{group_name} <-> host:#{host_name}}...\n"\
199
- " - host doesn't exist, creating now...\n"\
200
- " - OK\n"\
201
- " - OK\n"\
202
- " - all OK\n"\
222
+ "Add group '#{group_name}':\n " \
223
+ "- create group...\n " \
224
+ "- OK\n " \
225
+ "- add association {group:#{group_name} <-> host:#{host_name}}...\n " \
226
+ "- host doesn't exist, creating now...\n " \
227
+ "- OK\n " \
228
+ "- OK\n " \
229
+ "- all OK\n" \
203
230
  "Succeeded, with warnings.\n"
204
231
  desired[:STDERR] =
205
232
  "WARNING: Host '#{host_name}' doesn't exist, but will be created.\n"
@@ -220,11 +247,11 @@ RSpec.describe Moose::Inventory::Cli::Group do
220
247
  group_name = 'test-group'
221
248
 
222
249
  # Create group and association
223
- runner { @app.start(%W(group add #{group_name} --hosts #{host_name})) }
250
+ runner { @app.start(%W[group add #{group_name} --hosts #{host_name}]) }
224
251
 
225
252
  # Do it again, to prove that we skip
226
253
  actual = runner do
227
- @app.start(%W(group add #{group_name} --hosts #{host_name}))
254
+ @app.start(%W[group add #{group_name} --hosts #{host_name}])
228
255
  end
229
256
 
230
257
  # @console.out(actual, 'y')
@@ -232,17 +259,17 @@ RSpec.describe Moose::Inventory::Cli::Group do
232
259
  # Check output
233
260
  desired = {}
234
261
  desired[:STDOUT] =
235
- "Add group '#{group_name}':\n"\
236
- " - create group...\n"\
237
- " - already exists, skipping.\n"\
238
- " - OK\n"\
239
- " - add association {group:#{group_name} <-> host:#{host_name}}...\n"\
240
- " - already exists, skipping.\n"\
241
- " - OK\n"\
242
- " - all OK\n"\
262
+ "Add group '#{group_name}':\n " \
263
+ "- create group...\n " \
264
+ "- already exists, skipping.\n " \
265
+ "- OK\n " \
266
+ "- add association {group:#{group_name} <-> host:#{host_name}}...\n " \
267
+ "- already exists, skipping.\n " \
268
+ "- OK\n " \
269
+ "- all OK\n" \
243
270
  "Succeeded, with warnings.\n"
244
271
  desired[:STDERR] =
245
- "WARNING: Group '#{group_name}' already exists, skipping creation.\n"\
272
+ "WARNING: Group '#{group_name}' already exists, skipping creation.\n" \
246
273
  "WARNING: Association {group:#{group_name} <-> host:#{host_name}} already exists, skipping creation.\n"
247
274
 
248
275
  expected(actual, desired)
@@ -256,26 +283,25 @@ RSpec.describe Moose::Inventory::Cli::Group do
256
283
  end
257
284
 
258
285
  # --------------------
259
- it 'GROUP --hosts HOST1,HOST2 ... should add the group and '\
260
- 'associate it with multiple hosts' do
261
-
286
+ it 'GROUP --hosts HOST1,HOST2 ... should add the group and ' \
287
+ 'associate it with multiple hosts' do
262
288
  # The group should be added
263
289
  # Each host should be associated with the group
264
290
  # Each host should be removed from the automatic 'ungrouped' group
265
291
 
266
292
  group_name = 'test-group'
267
- host_names = %w(host1 host2 host3)
293
+ host_names = %w[host1 host2 host3]
268
294
 
269
295
  # Add just the first host. This ensure that we cover paths for both
270
296
  # and existing host (with an 'ungrouped' association) and for none
271
297
  # existing groups.
272
- tmp = runner { @app.start(%W(host add #{host_names[0]})) }
298
+ runner { @app.start(%W[host add #{host_names[0]}]) }
273
299
 
274
300
  # @console.out(tmp, 'y')
275
301
 
276
302
  # Now run the actual group addition
277
303
  actual = runner do
278
- @app.start(%W(group add #{group_name} --hosts #{host_names.join(',')}))
304
+ @app.start(%W[group add #{group_name} --hosts #{host_names.join(',')}])
279
305
  end
280
306
 
281
307
  # @console.out(actual,'y')
@@ -283,25 +309,25 @@ RSpec.describe Moose::Inventory::Cli::Group do
283
309
  # Check output
284
310
  desired = { aborted: false, STDERR: '', STDOUT: '' }
285
311
  desired[:STDOUT] =
286
- "Add group '#{group_name}':\n"\
287
- " - create group...\n"\
288
- " - OK\n"\
289
- " - add association {group:#{group_name} <-> host:#{host_names[0]}}...\n"\
290
- " - OK\n"\
291
- " - remove automatic association {group:ungrouped <-> host:#{host_names[0]}}...\n"\
292
- " - OK\n"
312
+ "Add group '#{group_name}':\n " \
313
+ "- create group...\n " \
314
+ "- OK\n " \
315
+ "- add association {group:#{group_name} <-> host:#{host_names[0]}}...\n " \
316
+ "- OK\n " \
317
+ "- remove automatic association {group:ungrouped <-> host:#{host_names[0]}}...\n " \
318
+ "- OK\n"
293
319
 
294
320
  host_names.slice(1, host_names.length - 1).each do |host_name|
295
321
  desired[:STDOUT] = desired[:STDOUT] +
296
- " - add association {group:#{group_name} <-> host:#{host_name}}...\n"\
297
- " - host doesn't exist, creating now...\n"\
298
- " - OK\n"\
299
- " - OK\n"
322
+ " - add association {group:#{group_name} <-> host:#{host_name}}...\n " \
323
+ "- host doesn't exist, creating now...\n " \
324
+ "- OK\n " \
325
+ "- OK\n"
300
326
  desired[:STDERR] = desired[:STDERR] +
301
327
  "WARNING: Host '#{host_name}' doesn't exist, but will be created.\n"
302
328
  end
303
329
  desired[:STDOUT] = desired[:STDOUT] +
304
- " - all OK\n"\
330
+ " - all OK\n" \
305
331
  "Succeeded, with warnings.\n"
306
332
 
307
333
  expected(actual, desired)
@@ -318,13 +344,13 @@ RSpec.describe Moose::Inventory::Cli::Group do
318
344
  end
319
345
 
320
346
  # --------------------
321
- it 'HOST --groups GROUP1,ungrouped ... should bail '\
322
- 'with an error' do
347
+ it 'HOST --groups GROUP1,ungrouped ... should bail ' \
348
+ 'with an error' do
323
349
  name = 'testhost'
324
- group_names = %w(group1 ungrouped)
350
+ group_names = %w[group1 ungrouped]
325
351
 
326
352
  actual = runner do
327
- @app.start(%W(host add #{name} --groups #{group_names.join(',')}))
353
+ @app.start(%W[host add #{name} --groups #{group_names.join(',')}])
328
354
  end
329
355
 
330
356
  # Check output
@@ -336,15 +362,14 @@ RSpec.describe Moose::Inventory::Cli::Group do
336
362
  end
337
363
 
338
364
  # --------------------
339
- it 'HOST1 HOST2 --groups GROUP1,GROUP2 ... should add multiple '\
340
- 'groups, associating each with multiple hosts' do
341
- #
342
- host_names = %w(host1 host2 host3)
365
+ it 'HOST1 HOST2 --groups GROUP1,GROUP2 ... should add multiple ' \
366
+ 'groups, associating each with multiple hosts' do
367
+ host_names = %w[host1 host2 host3]
343
368
  # Note, relies on auto-generation of hosts
344
369
 
345
- group_names = %w(group1 group2 group3)
370
+ group_names = %w[group1 group2 group3]
346
371
  actual = runner do
347
- @app.start(%w(group add) + group_names + %W(--hosts #{host_names.join(',')}))
372
+ @app.start(%w[group add] + group_names + %W[--hosts #{host_names.join(',')}])
348
373
  end
349
374
 
350
375
  # @console.out(actual,'y')
@@ -354,27 +379,25 @@ RSpec.describe Moose::Inventory::Cli::Group do
354
379
  first_pass = true
355
380
  group_names.each do |group|
356
381
  desired[:STDOUT] = desired[:STDOUT] +
357
- "Add group '#{group}':\n"\
358
- " - create group...\n"\
359
- " - OK\n"
382
+ "Add group '#{group}':\n " \
383
+ "- create group...\n " \
384
+ "- OK\n"
360
385
 
361
386
  host_names.each do |host|
362
387
  desired[:STDOUT] = desired[:STDOUT] +
363
388
  " - add association {group:#{group} <-> host:#{host}}...\n"
364
389
  if first_pass
365
390
  desired[:STDOUT] = desired[:STDOUT] +
366
- " - host doesn't exist, creating now...\n"\
367
- " - OK\n"
391
+ " - host doesn't exist, creating now...\n " \
392
+ "- OK\n"
368
393
  end
369
- desired[:STDOUT] = desired[:STDOUT] +
370
- " - OK\n"
394
+ desired[:STDOUT] = "#{desired[:STDOUT]} - OK\n"
371
395
  end
372
- desired[:STDOUT] = desired[:STDOUT] +
373
- " - all OK\n"
396
+ desired[:STDOUT] = "#{desired[:STDOUT]} - all OK\n"
374
397
  first_pass = false
375
398
  end
376
399
 
377
- desired[:STDOUT] = desired[:STDOUT] + "Succeeded, with warnings.\n"
400
+ desired[:STDOUT] = "#{desired[:STDOUT]}Succeeded, with warnings.\n"
378
401
  host_names.each do |host|
379
402
  desired[:STDERR] = desired[:STDERR] +
380
403
  "WARNING: Host '#{host}' doesn't exist, but will be created.\n"
@@ -395,3 +418,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
395
418
  end
396
419
  end
397
420
  end
421
+ # rubocop:enable Metrics/BlockLength
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
1
4
  require 'spec_helper'
2
5
 
3
- # TODO: the usual respond_to? method doesn't seem to work on Thor objects.
4
6
  # Why not? For now, we'll check against instance_methods.
5
7
 
6
8
  RSpec.describe Moose::Inventory::Cli::Group do
7
9
  before(:all) do
8
10
  # Set up the configuration object
9
11
  @mockarg_parts = {
10
- config: File.join(spec_root, 'config/config.yml'),
11
- format: 'yaml',
12
- env: 'test',
12
+ config: File.join(spec_root, 'config/config.yml'),
13
+ format: 'yaml',
14
+ env: 'test'
13
15
  }
14
16
 
15
17
  @mockargs = []
@@ -39,14 +41,14 @@ RSpec.describe Moose::Inventory::Cli::Group do
39
41
  describe 'addchild' do
40
42
  #------------------------
41
43
  it 'Group.addchild() should be responsive' do
42
- result = @group.instance_methods(false).include?(:addchild)
44
+ result = @group.method_defined?(:addchild, false)
43
45
  expect(result).to eq(true)
44
46
  end
45
47
 
46
48
  #------------------------
47
49
  it '<missing args> ... should abort with an error' do
48
50
  actual = runner do
49
- @app.start(%w(group addchild))
51
+ @app.start(%w[group addchild])
50
52
  end
51
53
 
52
54
  # @console.out(actual, 'y')
@@ -63,7 +65,7 @@ RSpec.describe Moose::Inventory::Cli::Group do
63
65
  child_name = 'fake'
64
66
 
65
67
  actual = runner do
66
- @app.start(%W(group addchild #{parent_name} #{child_name}))
68
+ @app.start(%W[group addchild #{parent_name} #{child_name}])
67
69
  end
68
70
 
69
71
  # @console.out(actual, 'y')
@@ -79,7 +81,7 @@ RSpec.describe Moose::Inventory::Cli::Group do
79
81
  child_name = 'ungrouped'
80
82
 
81
83
  actual = runner do
82
- @app.start(%W(group addchild #{parent_name} #{child_name}))
84
+ @app.start(%W[group addchild #{parent_name} #{child_name}])
83
85
  end
84
86
 
85
87
  # @console.out(actual, 'y')
@@ -92,25 +94,28 @@ RSpec.describe Moose::Inventory::Cli::Group do
92
94
 
93
95
  #------------------------
94
96
  it 'GROUP CHILDGROUP ... should abort if GROUP does not exist' do
95
- # TODO: Why don't we just create GROUP? Likewise for all similar functions?
96
-
97
+ # Contract: addchild attaches children to an existing parent. Missing child
98
+ # groups may be created, but the parent must already exist so typos do not
99
+ # silently create a new tree root.
97
100
  pname = 'parent_group'
98
101
  cname = 'child group'
99
102
 
100
103
  actual = runner do
101
- @app.start(%W(group addchild #{pname} #{cname}))
104
+ @app.start(%W[group addchild #{pname} #{cname}])
102
105
  end
103
106
 
104
107
  # @console.out(actual, 'y')
105
108
  # Check output
106
109
  desired = { aborted: true }
107
110
  desired[:STDOUT] =
108
- "Associate parent group '#{pname}' with child group(s) '#{cname}':\n"\
109
- " - retrieve group '#{pname}'...\n"
111
+ "Associate parent group '#{pname}' with child group(s) '#{cname}':\n " \
112
+ "- retrieve group '#{pname}'...\n"
110
113
  desired[:STDERR] =
111
- "ERROR: The group '#{pname}' does not exist.\n"\
114
+ "ERROR: The group '#{pname}' does not exist.\n" \
112
115
  "An error occurred during a transaction, any changes have been rolled back.\n"
113
116
  expected(actual, desired)
117
+ expect(@db.models[:group].find(name: pname)).to be_nil
118
+ expect(@db.models[:group].find(name: cname)).to be_nil
114
119
  end
115
120
 
116
121
  #------------------------
@@ -118,20 +123,20 @@ RSpec.describe Moose::Inventory::Cli::Group do
118
123
  pname = 'parent_group'
119
124
  cname = 'child_group'
120
125
 
121
- runner { @app.start(%W(group add #{pname} #{cname})) }
126
+ runner { @app.start(%W[group add #{pname} #{cname}]) }
122
127
 
123
- actual = runner { @app.start(%W(group addchild #{pname} #{cname})) }
128
+ actual = runner { @app.start(%W[group addchild #{pname} #{cname}]) }
124
129
 
125
130
  # @console.out(actual, 'y')
126
131
 
127
132
  desired = { aborted: false }
128
133
  desired[:STDOUT] =
129
- "Associate parent group '#{pname}' with child group(s) '#{cname}':\n"\
130
- " - retrieve group '#{pname}'...\n"\
131
- " - OK\n"\
132
- " - add association {group:#{pname} <-> group:#{cname}}...\n"\
133
- " - OK\n"\
134
- " - all OK\n"\
134
+ "Associate parent group '#{pname}' with child group(s) '#{cname}':\n " \
135
+ "- retrieve group '#{pname}'...\n " \
136
+ "- OK\n " \
137
+ "- add association {group:#{pname} <-> group:#{cname}}...\n " \
138
+ "- OK\n " \
139
+ "- all OK\n" \
135
140
  "Succeeded.\n"
136
141
  expected(actual, desired)
137
142
 
@@ -143,28 +148,76 @@ RSpec.describe Moose::Inventory::Cli::Group do
143
148
  end
144
149
 
145
150
  #------------------------
146
- it 'GROUP CHILDGROUP... should associate GROUP with a CHILDGROUP '\
147
- 'creating it if necessary' do
148
- #
151
+ it 'GROUP CHILDGROUP --dry-run should not create the child or association' do
152
+ pname = 'parent_group'
153
+ cname = 'dry_child'
154
+
155
+ runner { @app.start(%W[group add #{pname}]) }
156
+
157
+ actual = runner { @app.start(%W[group addchild #{pname} #{cname} --dry-run]) }
158
+
159
+ expect(actual[:unexpected]).to eq(false)
160
+ expect(actual[:aborted]).to eq(false)
161
+ expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
162
+ expect(@db.models[:group].find(name: cname)).to be_nil
163
+ pgroup = @db.models[:group].find(name: pname)
164
+ expect(pgroup.children_dataset[name: cname]).to be_nil
165
+ end
166
+
167
+ #------------------------
168
+
169
+ it 'GROUP CHILDGROUP... should warn and skip when the association already exists' do
170
+ pname = 'parent_group'
171
+ cname = 'child_group'
172
+
173
+ runner { @app.start(%W[group add #{pname} #{cname}]) }
174
+ runner { @app.start(%W[group addchild #{pname} #{cname}]) }
175
+
176
+ actual = runner { @app.start(%W[group addchild #{pname} #{cname}]) }
177
+
178
+ desired = { aborted: false }
179
+ desired[:STDOUT] = <<~OUTPUT
180
+ Associate parent group '#{pname}' with child group(s) '#{cname}':
181
+ - retrieve group '#{pname}'...
182
+ - OK
183
+ - add association {group:#{pname} <-> group:#{cname}}...
184
+ - already exists, skipping.
185
+ - OK
186
+ - all OK
187
+ Succeeded, with warnings.
188
+ OUTPUT
189
+ desired[:STDERR] = <<~ERROR
190
+ WARNING: Association {group:#{pname} <-> group:#{cname}} already exists, skipping.
191
+ ERROR
192
+ expected(actual, desired)
193
+
194
+ pgroup = @db.models[:group].find(name: pname)
195
+ cgroups = pgroup.children_dataset
196
+ expect(cgroups.count).to eq(1)
197
+ expect(cgroups[name: cname]).not_to be_nil
198
+ end
199
+
200
+ it 'GROUP CHILDGROUP... should associate GROUP with a CHILDGROUP ' \
201
+ 'creating it if necessary' do
149
202
  pname = 'parent_group'
150
203
  cname = 'child_group'
151
204
 
152
- runner { @app.start(%W(group add #{pname})) } # <- don't pre-create the child
205
+ runner { @app.start(%W[group add #{pname}]) } # <- don't pre-create the child
153
206
 
154
- actual = runner { @app.start(%W(group addchild #{pname} #{cname})) }
207
+ actual = runner { @app.start(%W[group addchild #{pname} #{cname}]) }
155
208
 
156
209
  # @console.out(actual, 'y')
157
210
 
158
211
  desired = { aborted: false }
159
212
  desired[:STDOUT] =
160
- "Associate parent group '#{pname}' with child group(s) '#{cname}':\n"\
161
- " - retrieve group '#{pname}'...\n"\
162
- " - OK\n"\
163
- " - add association {group:#{pname} <-> group:#{cname}}...\n"\
164
- " - child group does not exist, creating now...\n"\
165
- " - OK\n"\
166
- " - OK\n"\
167
- " - all OK\n"\
213
+ "Associate parent group '#{pname}' with child group(s) '#{cname}':\n " \
214
+ "- retrieve group '#{pname}'...\n " \
215
+ "- OK\n " \
216
+ "- add association {group:#{pname} <-> group:#{cname}}...\n " \
217
+ "- child group does not exist, creating now...\n " \
218
+ "- OK\n " \
219
+ "- OK\n " \
220
+ "- all OK\n" \
168
221
  "Succeeded, with warnings.\n"
169
222
  desired[:STDERR] = "WARNING: Group '#{cname}' does not exist and will be created.\n"
170
223
  expected(actual, desired)
@@ -177,3 +230,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
177
230
  end
178
231
  end
179
232
  end
233
+ # rubocop:enable Metrics/BlockLength