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,49 +1,31 @@
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::Host 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
-
21
- @config = Moose::Inventory::Config
22
- @config.init(@mockargs)
23
-
24
- @db = Moose::Inventory::DB
25
- @db.init if @db.db.nil?
26
-
27
- @host = Moose::Inventory::Cli::Host
28
- @app = Moose::Inventory::Cli::Application
10
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Host, command_ivar: :@host)
29
11
  end
30
12
 
31
13
  before(:each) do
32
- @db.reset
14
+ reset_cli_harness
33
15
  end
34
16
 
35
17
  #=======================
36
18
  describe 'addgroup' do
37
19
  #------------------------
38
20
  it 'Host.addgroup() should be responsive' do
39
- result = @host.instance_methods(false).include?(:addgroup)
21
+ result = @host.method_defined?(:addgroup, false)
40
22
  expect(result).to eq(true)
41
23
  end
42
24
 
43
25
  #------------------------
44
26
  it 'host addgroup <missing args> ... should abort with an error' do
45
27
  actual = runner do
46
- @app.start(%w(host addgroup)) # <- no group given
28
+ @app.start(%w[host addgroup]) # <- no group given
47
29
  end
48
30
 
49
31
  # Check output
@@ -55,16 +37,16 @@ RSpec.describe Moose::Inventory::Cli::Host do
55
37
  #------------------------
56
38
  it 'host addgroup HOST GROUP ... should abort if the host does not exist' do
57
39
  actual = runner do
58
- @app.start(%w(host addgroup not-a-host example))
40
+ @app.start(%w[host addgroup not-a-host example])
59
41
  end
60
42
 
61
43
  # Check output
62
44
  desired = { aborted: true }
63
45
  desired[:STDOUT] =
64
- "Associate host 'not-a-host' with groups 'example':\n"\
65
- " - Retrieve host 'not-a-host'...\n"
46
+ "Associate host 'not-a-host' with groups 'example':\n " \
47
+ "- Retrieve host 'not-a-host'...\n"
66
48
  desired[:STDERR] =
67
- "An error occurred during a transaction, any changes have been rolled back.\n"\
49
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
68
50
  "ERROR: The host 'not-a-host' was not found in the database.\n"
69
51
  expected(actual, desired)
70
52
  end
@@ -77,26 +59,23 @@ RSpec.describe Moose::Inventory::Cli::Host do
77
59
  name = 'test1'
78
60
  group_name = 'testgroup1'
79
61
 
80
- runner { @app.start(%W(host add #{name})) }
62
+ runner { @app.start(%W[host add #{name}]) }
81
63
  @db.models[:group].create(name: group_name)
82
64
 
83
- actual = runner { @app.start(%W(host addgroup #{name} #{group_name})) }
65
+ actual = runner { @app.start(%W[host addgroup #{name} #{group_name}]) }
84
66
 
85
- # rubocop:disable Metrics/LineLength
86
67
  desired = { aborted: false }
87
68
  desired[:STDOUT] =
88
- "Associate host '#{name}' with groups '#{group_name}':\n"\
89
- " - Retrieve host '#{name}'...\n"\
90
- " - OK\n"\
91
- " - Add association {host:#{name} <-> group:#{group_name}}...\n"\
92
- " - OK\n"\
93
- " - Remove automatic association {host:#{name} <-> group:ungrouped}...\n"\
94
- " - OK\n"\
95
- " - All OK\n"\
69
+ "Associate host '#{name}' with groups '#{group_name}':\n " \
70
+ "- Retrieve host '#{name}'...\n " \
71
+ "- OK\n " \
72
+ "- Add association {host:#{name} <-> group:#{group_name}}...\n " \
73
+ "- OK\n " \
74
+ "- Remove automatic association {host:#{name} <-> group:ungrouped}...\n " \
75
+ "- OK\n " \
76
+ "- All OK\n" \
96
77
  "Succeeded\n"
97
78
  expected(actual, desired)
98
- # rubocop:enable Metrics/LineLength
99
-
100
79
  # We should have the correct group associations
101
80
  host = @db.models[:host].find(name: name)
102
81
  groups = host.groups_dataset
@@ -105,14 +84,51 @@ RSpec.describe Moose::Inventory::Cli::Host do
105
84
  expect(groups[name: 'ungrouped']).to be_nil # redundant, but for clarity!
106
85
  end
107
86
 
87
+ #------------------------
88
+ it 'host addgroup HOST GROUP --dry-run should not create the group or change membership' do
89
+ name = 'test1'
90
+ group_name = 'drygroup'
91
+ runner { @app.start(%W[host add #{name}]) }
92
+
93
+ actual = runner { @app.start(%W[host addgroup #{name} #{group_name} --dry-run]) }
94
+
95
+ expect(actual[:unexpected]).to eq(false)
96
+ expect(actual[:aborted]).to eq(false)
97
+ expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
98
+ expect(@db.models[:group].find(name: group_name)).to be_nil
99
+ host = @db.models[:host].find(name: name)
100
+ expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
101
+ end
102
+
103
+ #------------------------
104
+ it 'host addgroup HOST GROUP --dry-run --plan-format json should emit a pure relation plan' do
105
+ name = 'test1'
106
+ group_name = 'drygroup'
107
+ runner { @app.start(%W[host add #{name}]) }
108
+
109
+ actual = runner { @app.start(%W[host addgroup #{name} #{group_name} --dry-run --plan-format json]) }
110
+
111
+ expect(actual[:unexpected]).to eq(false)
112
+ expect(actual[:aborted]).to eq(false)
113
+ expect(actual[:STDOUT]).not_to include('Associate host')
114
+ plan = JSON.parse(actual[:STDOUT])
115
+ expect(plan['command']).to eq('host addgroup')
116
+ expect(plan['events'].map do |event|
117
+ event['type']
118
+ end).to include('adding_host_group_association', 'dry_run_summary')
119
+ expect(@db.models[:group].find(name: group_name)).to be_nil
120
+ host = @db.models[:host].find(name: name)
121
+ expect(host.groups_dataset[name: 'ungrouped']).not_to be_nil
122
+ end
123
+
108
124
  #------------------------
109
125
  it 'HOST \'ungrouped\' ... should abort with an error' do
110
126
  name = 'test1'
111
127
  group_name = 'ungrouped'
112
128
 
113
- runner { @app.start(%W(host add #{name})) }
129
+ runner { @app.start(%W[host add #{name}]) }
114
130
 
115
- actual = runner { @app.start(%W(host addgroup #{name} #{group_name})) }
131
+ actual = runner { @app.start(%W[host addgroup #{name} #{group_name}]) }
116
132
 
117
133
  desired = { aborted: true }
118
134
  desired[:STDERR] =
@@ -125,25 +141,25 @@ RSpec.describe Moose::Inventory::Cli::Host do
125
141
  name = 'test1'
126
142
  group_name = 'testgroup1'
127
143
 
128
- runner { @app.start(%W(host add #{name})) }
144
+ runner { @app.start(%W[host add #{name}]) }
129
145
 
130
146
  # DON'T CREATE THE GROUP! That's the point of the test. ;o)
131
147
 
132
- actual = runner { @app.start(%W(host addgroup #{name} #{group_name})) }
148
+ actual = runner { @app.start(%W[host addgroup #{name} #{group_name}]) }
133
149
 
134
150
  # Check output
135
151
  desired = { aborted: false }
136
152
  desired[:STDOUT] =
137
- "Associate host '#{name}' with groups '#{group_name}':\n"\
138
- " - Retrieve host '#{name}'...\n"\
139
- " - OK\n"\
140
- " - Add association {host:#{name} <-> group:#{group_name}}...\n"\
141
- " - Group does not exist, creating now...\n"\
142
- " - OK\n"\
143
- " - OK\n"\
144
- " - Remove automatic association {host:#{name} <-> group:ungrouped}...\n"\
145
- " - OK\n"\
146
- " - All OK\n"\
153
+ "Associate host '#{name}' with groups '#{group_name}':\n " \
154
+ "- Retrieve host '#{name}'...\n " \
155
+ "- OK\n " \
156
+ "- Add association {host:#{name} <-> group:#{group_name}}...\n " \
157
+ "- Group does not exist, creating now...\n " \
158
+ "- OK\n " \
159
+ "- OK\n " \
160
+ "- Remove automatic association {host:#{name} <-> group:ungrouped}...\n " \
161
+ "- OK\n " \
162
+ "- All OK\n" \
147
163
  "Succeeded\n"
148
164
  desired[:STDERR] =
149
165
  "WARNING: Group '#{group_name}' does not exist and will be created."
@@ -158,33 +174,33 @@ RSpec.describe Moose::Inventory::Cli::Host do
158
174
  end
159
175
 
160
176
  #------------------------
161
- it 'HOST GROUP ... should skip associations that already '\
162
- ' exist, but raise a warning.' do
177
+ it 'HOST GROUP ... should skip associations that already ' \
178
+ 'exist, but raise a warning.' do
163
179
  name = 'test1'
164
180
  group_name = 'testgroup1'
165
181
 
166
- runner { @app.start(%W(host add #{name})) }
182
+ runner { @app.start(%W[host add #{name}]) }
167
183
 
168
184
  # DON'T CREATE THE GROUP! That's the point of the test. ;o)
169
185
 
170
186
  # Run once to make the association
171
- runner { @app.start(%W(host addgroup #{name} #{group_name})) }
187
+ runner { @app.start(%W[host addgroup #{name} #{group_name}]) }
172
188
 
173
189
  # Run again, to prove expected result
174
- actual = runner { @app.start(%W(host addgroup #{name} #{group_name})) }
190
+ actual = runner { @app.start(%W[host addgroup #{name} #{group_name}]) }
175
191
 
176
192
  # Check output
177
193
  # Note: This time, we don't expect to see any messages about
178
194
  # dissociation from 'ungrouped'
179
195
  desired = { aborted: false }
180
196
  desired[:STDOUT] =
181
- "Associate host '#{name}' with groups '#{group_name}':\n"\
182
- " - Retrieve host \'#{name}\'...\n"\
183
- " - OK\n"\
184
- " - Add association {host:#{name} <-> group:#{group_name}}...\n"\
185
- " - Already exists, skipping.\n"\
186
- " - OK\n"\
187
- " - All OK\n"\
197
+ "Associate host '#{name}' with groups '#{group_name}':\n " \
198
+ "- Retrieve host '#{name}'...\n " \
199
+ "- OK\n " \
200
+ "- Add association {host:#{name} <-> group:#{group_name}}...\n " \
201
+ "- Already exists, skipping.\n " \
202
+ "- OK\n " \
203
+ "- All OK\n" \
188
204
  "Succeeded\n"
189
205
  desired[:STDERR] = "WARNING: Association {host:#{name} <-> group:#{group_name}} already exists, skipping."
190
206
  expected(actual, desired)
@@ -198,35 +214,35 @@ RSpec.describe Moose::Inventory::Cli::Host do
198
214
  end
199
215
 
200
216
  #------------------------
201
- it 'host addgroup GROUP1 GROUP1 ... should add the host to'\
202
- ' multiple groups at once' do
217
+ it 'host addgroup GROUP1 GROUP1 ... should add the host to ' \
218
+ 'multiple groups at once' do
203
219
  name = 'test1'
204
- group_names = %w(group1 group2 group3)
220
+ group_names = %w[group1 group2 group3]
205
221
 
206
- runner { @app.start(%W(host add #{name})) }
222
+ runner { @app.start(%W[host add #{name}]) }
207
223
 
208
- actual = runner { @app.start(%W(host addgroup #{name}) + group_names) }
224
+ actual = runner { @app.start(%W[host addgroup #{name}] + group_names) }
209
225
 
210
226
  # Check output
211
227
  desired = { aborted: false, STDERR: '' }
212
228
  desired[:STDOUT] =
213
- "Associate host '#{name}' with groups '#{group_names.join(',')}':\n"\
214
- " - Retrieve host '#{name}'...\n"\
215
- " - OK\n"
229
+ "Associate host '#{name}' with groups '#{group_names.join(',')}':\n " \
230
+ "- Retrieve host '#{name}'...\n " \
231
+ "- OK\n"
216
232
  group_names.each do |group|
217
233
  desired[:STDOUT] = desired[:STDOUT] +
218
- " - Add association {host:#{name} <-> group:#{group}}...\n"\
219
- " - Group does not exist, creating now...\n"\
220
- " - OK\n"\
221
- " - OK\n"
234
+ " - Add association {host:#{name} <-> group:#{group}}...\n " \
235
+ "- Group does not exist, creating now...\n " \
236
+ "- OK\n " \
237
+ "- OK\n"
222
238
 
223
239
  desired[:STDERR] = desired[:STDERR] +
224
240
  "WARNING: Group '#{group}' does not exist and will be created."
225
241
  end
226
242
  desired[:STDOUT] = desired[:STDOUT] +
227
- " - Remove automatic association {host:#{name} <-> group:ungrouped}...\n"\
228
- " - OK\n"\
229
- " - All OK\n"\
243
+ " - Remove automatic association {host:#{name} <-> group:ungrouped}...\n " \
244
+ "- OK\n " \
245
+ "- All OK\n" \
230
246
  "Succeeded\n"
231
247
  expected(actual, desired)
232
248
 
@@ -244,3 +260,4 @@ RSpec.describe Moose::Inventory::Cli::Host do
244
260
  end
245
261
  end
246
262
  end
263
+ # rubocop:enable Metrics/BlockLength
@@ -1,50 +1,31 @@
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::Host 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
-
21
- @config = Moose::Inventory::Config
22
- @config.init(@mockargs)
23
-
24
- @db = Moose::Inventory::DB
25
- @db.init if @db.db.nil?
26
-
27
- @console = Moose::Inventory::Cli::Formatter
28
- @host = Moose::Inventory::Cli::Host
29
- @app = Moose::Inventory::Cli::Application
10
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Host, command_ivar: :@host)
30
11
  end
31
12
 
32
13
  before(:each) do
33
- @db.reset
14
+ reset_cli_harness
34
15
  end
35
16
 
36
17
  #==================
37
18
  describe 'addvar' do
38
19
  #-----------------
39
20
  it 'should be responsive' do
40
- result = @host.instance_methods(false).include?(:addvar)
21
+ result = @host.method_defined?(:addvar, false)
41
22
  expect(result).to eq(true)
42
23
  end
43
24
 
44
25
  #-----------------
45
26
  it 'host addvar <missing args> ... should abort with an error' do
46
27
  actual = runner do
47
- @app.start(%w(host addvar)) # <- no group given
28
+ @app.start(%w[host addvar]) # <- no group given
48
29
  end
49
30
 
50
31
  # Check output
@@ -59,16 +40,16 @@ RSpec.describe Moose::Inventory::Cli::Host do
59
40
  host_var = 'foo=bar'
60
41
 
61
42
  actual = runner do
62
- @app.start(%W(host addvar #{host_name} #{host_var}))
43
+ @app.start(%W[host addvar #{host_name} #{host_var}])
63
44
  end
64
45
 
65
46
  # Check output
66
47
  desired = { aborted: true }
67
48
  desired[:STDOUT] =
68
- "Add variables '#{host_var}' to host '#{host_name}':\n"\
69
- " - retrieve host '#{host_name}'...\n"
49
+ "Add variables '#{host_var}' to host '#{host_name}':\n " \
50
+ "- retrieve host '#{host_name}'...\n"
70
51
  desired[:STDERR] =
71
- "An error occurred during a transaction, any changes have been rolled back.\n"\
52
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
72
53
  "ERROR: The host '#{host_name}' does not exist.\n"
73
54
  expected(actual, desired)
74
55
  end
@@ -80,32 +61,30 @@ RSpec.describe Moose::Inventory::Cli::Host do
80
61
 
81
62
  host_name = 'test1'
82
63
  @db.models[:host].create(name: host_name)
83
-
84
- var = { name: 'var1', value: 'testval' }
85
- cases = %w(
64
+ cases = %w[
86
65
  testvar
87
66
  testvar=
88
67
  =testval
89
68
  testvar=testval=
90
69
  =testvar=testval
91
70
  testvar=testval=extra
92
- )
71
+ ]
93
72
 
94
73
  cases.each do |args|
95
74
  actual = runner do
96
- @app.start(%W(host addvar #{host_name} #{args}))
75
+ @app.start(%W[host addvar #{host_name} #{args}])
97
76
  end
98
77
  # @console.out(actual,'p')
99
78
 
100
79
  desired = { aborted: true }
101
80
  desired[:STDOUT] =
102
- "Add variables '#{args}' to host '#{host_name}':\n"\
103
- " - retrieve host '#{host_name}'...\n"\
104
- " - OK\n"\
105
- " - add variable '#{args}'...\n"
81
+ "Add variables '#{args}' to host '#{host_name}':\n " \
82
+ "- retrieve host '#{host_name}'...\n " \
83
+ "- OK\n " \
84
+ "- add variable '#{args}'...\n"
106
85
 
107
86
  desired[:STDERR] =
108
- "An error occurred during a transaction, any changes have been rolled back.\n"\
87
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
109
88
  "ERROR: Incorrect format in '{#{args}}'. Expected 'key=value'.\n"
110
89
 
111
90
  expected(actual, desired)
@@ -123,18 +102,18 @@ RSpec.describe Moose::Inventory::Cli::Host do
123
102
  @db.models[:host].create(name: host_name)
124
103
 
125
104
  actual = runner do
126
- @app.start(%W(host addvar #{host_name} #{var[:name]}=#{var[:value]}))
105
+ @app.start(%W[host addvar #{host_name} #{var[:name]}=#{var[:value]}])
127
106
  end
128
107
  # @console.out(actual,'p')
129
108
 
130
109
  desired = { aborted: false }
131
110
  desired[:STDOUT] =
132
- "Add variables '#{var[:name]}=#{var[:value]}' to host '#{host_name}':\n"\
133
- " - retrieve host '#{host_name}'...\n"\
134
- " - OK\n"\
135
- " - add variable '#{var[:name]}=#{var[:value]}'...\n"\
136
- " - OK\n"\
137
- " - all OK\n"\
111
+ "Add variables '#{var[:name]}=#{var[:value]}' to host '#{host_name}':\n " \
112
+ "- retrieve host '#{host_name}'...\n " \
113
+ "- OK\n " \
114
+ "- add variable '#{var[:name]}=#{var[:value]}'...\n " \
115
+ "- OK\n " \
116
+ "- all OK\n" \
138
117
  "Succeeded.\n"
139
118
  expected(actual, desired)
140
119
 
@@ -157,18 +136,18 @@ RSpec.describe Moose::Inventory::Cli::Host do
157
136
  @db.models[:host].create(name: host_name)
158
137
 
159
138
  actual = runner do
160
- @app.start(%W(host addvar #{host_name} #{var[:name]}=#{var[:value]}))
139
+ @app.start(%W[host addvar #{host_name} #{var[:name]}=#{var[:value]}])
161
140
  end
162
141
  # @console.out(actual,'p')
163
142
 
164
143
  desired = { aborted: false }
165
144
  desired[:STDOUT] =
166
- "Add variables '#{var[:name]}=#{var[:value]}' to host '#{host_name}':\n"\
167
- " - retrieve host '#{host_name}'...\n"\
168
- " - OK\n"\
169
- " - add variable '#{var[:name]}=#{var[:value]}'...\n"\
170
- " - OK\n"\
171
- " - all OK\n"\
145
+ "Add variables '#{var[:name]}=#{var[:value]}' to host '#{host_name}':\n " \
146
+ "- retrieve host '#{host_name}'...\n " \
147
+ "- OK\n " \
148
+ "- add variable '#{var[:name]}=#{var[:value]}'...\n " \
149
+ "- OK\n " \
150
+ "- all OK\n" \
172
151
  "Succeeded.\n"
173
152
  expected(actual, desired)
174
153
 
@@ -188,33 +167,32 @@ RSpec.describe Moose::Inventory::Cli::Host do
188
167
  host_name = 'test1'
189
168
  varsarray = [
190
169
  { name: 'var1', value: 'val1' },
191
- { name: 'var2', value: 'val2' },
170
+ { name: 'var2', value: 'val2' }
192
171
  ]
193
172
 
194
- vars = []
195
- varsarray.each do |var|
196
- vars << "#{var[:name]}=#{var[:value]}"
173
+ vars = varsarray.map do |var|
174
+ "#{var[:name]}=#{var[:value]}"
197
175
  end
198
176
 
199
177
  @db.models[:host].create(name: host_name)
200
178
 
201
179
  actual = runner do
202
- @app.start(%W(host addvar #{host_name}) + vars)
180
+ @app.start(%W[host addvar #{host_name}] + vars)
203
181
  end
204
182
  # @console.out(actual,'p')
205
183
 
206
184
  desired = { aborted: false }
207
185
  desired[:STDOUT] =
208
- "Add variables '#{vars.join(',')}' to host '#{host_name}':\n"\
209
- " - retrieve host '#{host_name}'...\n"\
210
- " - OK\n"
186
+ "Add variables '#{vars.join(',')}' to host '#{host_name}':\n " \
187
+ "- retrieve host '#{host_name}'...\n " \
188
+ "- OK\n"
211
189
  vars.each do |var|
212
190
  desired[:STDOUT] = desired[:STDOUT] +
213
- " - add variable '#{var}'...\n"\
214
- " - OK\n"
191
+ " - add variable '#{var}'...\n " \
192
+ "- OK\n"
215
193
  end
216
194
  desired[:STDOUT] = desired[:STDOUT] +
217
- " - all OK\n"\
195
+ " - all OK\n" \
218
196
  "Succeeded.\n"
219
197
  expected(actual, desired)
220
198
 
@@ -224,6 +202,45 @@ RSpec.describe Moose::Inventory::Cli::Host do
224
202
  expect(hostvars.count).to eq(vars.length)
225
203
  end
226
204
 
205
+ #------------------------
206
+ it 'host addvar HOST key=value --dry-run should not create or update variables' do
207
+ host_name = 'test1'
208
+ @db.models[:host].create(name: host_name)
209
+ runner { @app.start(%W[host addvar #{host_name} var1=old]) }
210
+
211
+ actual = runner do
212
+ @app.start(%W[host addvar #{host_name} var1=new var2=val2 --dry-run])
213
+ end
214
+
215
+ expect(actual[:unexpected]).to eq(false)
216
+ expect(actual[:aborted]).to eq(false)
217
+ expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
218
+ host = @db.models[:host].find(name: host_name)
219
+ expect(host.hostvars_dataset[name: 'var1'][:value]).to eq('old')
220
+ expect(host.hostvars_dataset[name: 'var2']).to be_nil
221
+ end
222
+
223
+ #------------------------
224
+ it 'host addvar HOST key=value --dry-run --plan-format json should emit a plan without writing' do
225
+ host_name = 'test1'
226
+ @db.models[:host].create(name: host_name)
227
+ runner { @app.start(%W[host addvar #{host_name} var1=old]) }
228
+
229
+ actual = runner do
230
+ @app.start(%W[host addvar #{host_name} var1=new var2=val2 --dry-run --plan-format json])
231
+ end
232
+
233
+ expect(actual[:unexpected]).to eq(false)
234
+ expect(actual[:aborted]).to eq(false)
235
+ expect(actual[:STDOUT]).not_to include('Add variables')
236
+ plan = JSON.parse(actual[:STDOUT])
237
+ expect(plan['command']).to eq('host addvar')
238
+ expect(plan['events'].map { |event| event['type'] }).to include('adding_variable', 'dry_run_summary')
239
+ host = @db.models[:host].find(name: host_name)
240
+ expect(host.hostvars_dataset[name: 'var1'][:value]).to eq('old')
241
+ expect(host.hostvars_dataset[name: 'var2']).to be_nil
242
+ end
243
+
227
244
  #------------------------
228
245
  it 'host addvar HOST key=value ... should update an already existing association' do
229
246
  # 1. Should add the var to the db
@@ -233,23 +250,23 @@ RSpec.describe Moose::Inventory::Cli::Host do
233
250
  var = { name: 'var1', value: 'testval' }
234
251
 
235
252
  @db.models[:host].create(name: host_name)
236
- runner { @app.start(%W(host addvar #{host_name} #{var[:name]}=#{var[:value]})) }
253
+ runner { @app.start(%W[host addvar #{host_name} #{var[:name]}=#{var[:value]}]) }
237
254
 
238
255
  var[:value] = 'newtestval'
239
256
  actual = runner do
240
- @app.start(%W(host addvar #{host_name} #{var[:name]}=#{var[:value]}))
257
+ @app.start(%W[host addvar #{host_name} #{var[:name]}=#{var[:value]}])
241
258
  end
242
259
  # @console.out(actual,'p')
243
260
 
244
261
  desired = { aborted: false }
245
262
  desired[:STDOUT] =
246
- "Add variables '#{var[:name]}=#{var[:value]}' to host '#{host_name}':\n"\
247
- " - retrieve host '#{host_name}'...\n"\
248
- " - OK\n"\
249
- " - add variable '#{var[:name]}=#{var[:value]}'...\n"\
250
- " - already exists, applying as an update...\n"\
251
- " - OK\n"\
252
- " - all OK\n"\
263
+ "Add variables '#{var[:name]}=#{var[:value]}' to host '#{host_name}':\n " \
264
+ "- retrieve host '#{host_name}'...\n " \
265
+ "- OK\n " \
266
+ "- add variable '#{var[:name]}=#{var[:value]}'...\n " \
267
+ "- already exists, applying as an update...\n " \
268
+ "- OK\n " \
269
+ "- all OK\n" \
253
270
  "Succeeded.\n"
254
271
  expected(actual, desired)
255
272
 
@@ -265,3 +282,4 @@ RSpec.describe Moose::Inventory::Cli::Host do
265
282
  end
266
283
  end
267
284
  end
285
+ # rubocop:enable Metrics/BlockLength