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
-
21
- @console = Moose::Inventory::Cli::Formatter
22
-
23
- @config = Moose::Inventory::Config
24
- @config.init(@mockargs)
25
-
26
- @db = Moose::Inventory::DB
27
- @db.init if @db.db.nil?
28
-
29
- @group = Moose::Inventory::Cli::Group
30
- @host = Moose::Inventory::Cli::Host
31
- @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
+ )
32
15
  end
33
16
 
34
17
  before(:each) do
35
- @db.reset
18
+ reset_cli_harness
36
19
  end
37
20
 
38
21
  #=======================
39
22
  describe 'addhost' do
40
23
  #------------------------
41
24
  it 'Group.addhost() should be responsive' do
42
- result = @group.instance_methods(false).include?(:addhost)
25
+ result = @group.method_defined?(:addhost, false)
43
26
  expect(result).to eq(true)
44
27
  end
45
28
 
46
29
  #------------------------
47
30
  it 'addhost <missing args> ... should abort with an error' do
48
31
  actual = runner do
49
- @app.start(%w(group addhost)) # <- no group given
32
+ @app.start(%w[group addhost]) # <- no group given
50
33
  end
51
34
 
52
35
  # @console.out(actual, 'y')
@@ -63,17 +46,17 @@ RSpec.describe Moose::Inventory::Cli::Group do
63
46
  group_name = 'not-a-group'
64
47
 
65
48
  actual = runner do
66
- @app.start(%W(group addhost #{group_name} #{host_name}))
49
+ @app.start(%W[group addhost #{group_name} #{host_name}])
67
50
  end
68
51
 
69
52
  # @console.out(actual, 'y')
70
53
  # Check output
71
54
  desired = { aborted: true }
72
55
  desired[:STDOUT] =
73
- "Associate group '#{group_name}' with host(s) '#{host_name}':\n"\
74
- " - retrieve group '#{group_name}'...\n"
56
+ "Associate group '#{group_name}' with host(s) '#{host_name}':\n " \
57
+ "- retrieve group '#{group_name}'...\n"
75
58
  desired[:STDERR] =
76
- "ERROR: The group '#{group_name}' does not exist.\n"\
59
+ "ERROR: The group '#{group_name}' does not exist.\n" \
77
60
  "An error occurred during a transaction, any changes have been rolled back.\n"
78
61
  expected(actual, desired)
79
62
  end
@@ -86,26 +69,23 @@ RSpec.describe Moose::Inventory::Cli::Group do
86
69
  host_name = 'test1'
87
70
  group_name = 'testgroup1'
88
71
 
89
- runner { @app.start(%W(host add #{host_name})) }
72
+ runner { @app.start(%W[host add #{host_name}]) }
90
73
  @db.models[:group].create(name: group_name)
91
74
 
92
- actual = runner { @app.start(%W(group addhost #{group_name} #{host_name})) }
75
+ actual = runner { @app.start(%W[group addhost #{group_name} #{host_name}]) }
93
76
 
94
- # rubocop:disable Metrics/LineLength
95
77
  desired = { aborted: false }
96
78
  desired[:STDOUT] =
97
- "Associate group '#{group_name}' with host(s) '#{host_name}':\n"\
98
- " - retrieve group '#{group_name}'...\n"\
99
- " - OK\n"\
100
- " - add association {group:#{group_name} <-> host:#{host_name}}...\n"\
101
- " - OK\n"\
102
- " - remove automatic association {group:ungrouped <-> host:#{host_name}}...\n"\
103
- " - OK\n"\
104
- " - all OK\n"\
79
+ "Associate group '#{group_name}' with host(s) '#{host_name}':\n " \
80
+ "- retrieve group '#{group_name}'...\n " \
81
+ "- OK\n " \
82
+ "- add association {group:#{group_name} <-> host:#{host_name}}...\n " \
83
+ "- OK\n " \
84
+ "- remove automatic association {group:ungrouped <-> host:#{host_name}}...\n " \
85
+ "- OK\n " \
86
+ "- all OK\n" \
105
87
  "Succeeded.\n"
106
88
  expected(actual, desired)
107
- # rubocop:enable Metrics/LineLength
108
-
109
89
  # We should have the correct group associations
110
90
  host = @db.models[:host].find(name: host_name)
111
91
  groups = host.groups_dataset
@@ -114,14 +94,30 @@ RSpec.describe Moose::Inventory::Cli::Group do
114
94
  expect(groups[name: 'ungrouped']).to be_nil # redundant, but for clarity!
115
95
  end
116
96
 
97
+ #------------------------
98
+ it 'group addhost GROUP HOST --dry-run should not create the host or association' do
99
+ host_name = 'dry-host'
100
+ group_name = 'testgroup1'
101
+ runner { @app.start(%W[group add #{group_name}]) }
102
+
103
+ actual = runner { @app.start(%W[group addhost #{group_name} #{host_name} --dry-run]) }
104
+
105
+ expect(actual[:unexpected]).to eq(false)
106
+ expect(actual[:aborted]).to eq(false)
107
+ expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
108
+ expect(@db.models[:host].find(name: host_name)).to be_nil
109
+ group = @db.models[:group].find(name: group_name)
110
+ expect(group.hosts_dataset[name: host_name]).to be_nil
111
+ end
112
+
117
113
  #------------------------
118
114
  it '\'ungrouped\' HOST... should abort with an error' do
119
115
  host_name = 'test1'
120
116
  group_name = 'ungrouped'
121
117
 
122
- runner { @app.start(%W(host add #{host_name})) }
118
+ runner { @app.start(%W[host add #{host_name}]) }
123
119
 
124
- actual = runner { @app.start(%W(group addhost #{group_name} #{host_name})) }
120
+ actual = runner { @app.start(%W[group addhost #{group_name} #{host_name}]) }
125
121
 
126
122
  desired = { aborted: true }
127
123
  desired[:STDERR] =
@@ -134,23 +130,23 @@ RSpec.describe Moose::Inventory::Cli::Group do
134
130
  host_name = 'test1'
135
131
  group_name = 'testgroup1'
136
132
 
137
- runner { @app.start(%W(group add #{group_name})) }
133
+ runner { @app.start(%W[group add #{group_name}]) }
138
134
 
139
135
  # DON'T CREATE THE HOST! That's the point of the test. ;o)
140
136
 
141
- actual = runner { @app.start(%W(group addhost #{group_name} #{host_name})) }
137
+ actual = runner { @app.start(%W[group addhost #{group_name} #{host_name}]) }
142
138
 
143
139
  # Check output
144
140
  desired = {}
145
141
  desired[:STDOUT] =
146
- "Associate group '#{group_name}' with host(s) '#{host_name}':\n"\
147
- " - retrieve group '#{group_name}'...\n"\
148
- " - OK\n"\
149
- " - add association {group:#{group_name} <-> host:#{host_name}}...\n"\
150
- " - host does not exist, creating now...\n"\
151
- " - OK\n"\
152
- " - OK\n"\
153
- " - all OK\n"\
142
+ "Associate group '#{group_name}' with host(s) '#{host_name}':\n " \
143
+ "- retrieve group '#{group_name}'...\n " \
144
+ "- OK\n " \
145
+ "- add association {group:#{group_name} <-> host:#{host_name}}...\n " \
146
+ "- host does not exist, creating now...\n " \
147
+ "- OK\n " \
148
+ "- OK\n " \
149
+ "- all OK\n" \
154
150
  "Succeeded, with warnings.\n"
155
151
  desired[:STDERR] =
156
152
  "WARNING: Host '#{host_name}' does not exist and will be created.\n"
@@ -164,32 +160,32 @@ RSpec.describe Moose::Inventory::Cli::Group do
164
160
  end
165
161
 
166
162
  #------------------------
167
- it 'GROUP HOST... should skip associations that already '\
168
- ' exist, but raise a warning.' do
163
+ it 'GROUP HOST... should skip associations that already ' \
164
+ 'exist, but raise a warning.' do
169
165
  host_name = 'test1'
170
166
  group_name = 'testgroup1'
171
167
 
172
- runner { @app.start(%W(group add #{group_name})) }
173
- runner { @app.start(%W(host add #{host_name})) }
168
+ runner { @app.start(%W[group add #{group_name}]) }
169
+ runner { @app.start(%W[host add #{host_name}]) }
174
170
 
175
171
  # Run once to make the initial association
176
- runner { @app.start(%W(group addhost #{group_name} #{host_name})) }
172
+ runner { @app.start(%W[group addhost #{group_name} #{host_name}]) }
177
173
 
178
174
  # Run again, to prove expected result
179
- actual = runner { @app.start(%W(group addhost #{group_name} #{host_name})) }
175
+ actual = runner { @app.start(%W[group addhost #{group_name} #{host_name}]) }
180
176
 
181
177
  # @console.out(actual,'y')
182
178
 
183
179
  # Check output
184
180
  desired = {}
185
181
  desired[:STDOUT] =
186
- "Associate group '#{group_name}' with host(s) '#{host_name}':\n"\
187
- " - retrieve group \'#{group_name}\'...\n"\
188
- " - OK\n"\
189
- " - add association {group:#{group_name} <-> host:#{host_name}}...\n"\
190
- " - already exists, skipping.\n"\
191
- " - OK\n"\
192
- " - all OK\n"\
182
+ "Associate group '#{group_name}' with host(s) '#{host_name}':\n " \
183
+ "- retrieve group '#{group_name}'...\n " \
184
+ "- OK\n " \
185
+ "- add association {group:#{group_name} <-> host:#{host_name}}...\n " \
186
+ "- already exists, skipping.\n " \
187
+ "- OK\n " \
188
+ "- all OK\n" \
193
189
  "Succeeded, with warnings.\n"
194
190
  desired[:STDERR] =
195
191
  "WARNING: Association {group:#{group_name} <-> host:#{host_name}} already exists, skipping.\n"
@@ -203,38 +199,38 @@ RSpec.describe Moose::Inventory::Cli::Group do
203
199
  end
204
200
 
205
201
  #------------------------
206
- it 'GROUP HOST1 HOST2 ... should associate the group with '\
207
- ' multiple hosts at once' do
202
+ it 'GROUP HOST1 HOST2 ... should associate the group with ' \
203
+ 'multiple hosts at once' do
208
204
  group_name = 'test1'
209
- host_names = %w(host1 host2 host3)
205
+ host_names = %w[host1 host2 host3]
210
206
 
211
- runner { @app.start(%W(group add #{group_name})) }
207
+ runner { @app.start(%W[group add #{group_name}]) }
212
208
  host_names.each do |host|
213
- runner { @app.start(%W(host add #{host})) }
209
+ runner { @app.start(%W[host add #{host}]) }
214
210
  end
215
211
 
216
- actual = runner { @app.start(%W(group addhost #{group_name}) + host_names) }
212
+ actual = runner { @app.start(%W[group addhost #{group_name}] + host_names) }
217
213
 
218
214
  # @console.out(actual, 'y')
219
215
 
220
216
  # Check output
221
217
  desired = { aborted: false, STDERR: '' }
222
218
  desired[:STDOUT] =
223
- "Associate group '#{group_name}' with host(s) '#{host_names.join(',')}':\n"\
224
- " - retrieve group '#{group_name}'...\n"\
225
- " - OK\n"
219
+ "Associate group '#{group_name}' with host(s) '#{host_names.join(',')}':\n " \
220
+ "- retrieve group '#{group_name}'...\n " \
221
+ "- OK\n"
226
222
  host_names.each do |host|
227
223
  desired[:STDOUT] = desired[:STDOUT] +
228
- " - add association {group:#{group_name} <-> host:#{host}}...\n"\
229
- " - OK\n"\
230
- " - remove automatic association {group:ungrouped <-> host:#{host}}...\n"\
231
- " - OK\n"\
224
+ " - add association {group:#{group_name} <-> host:#{host}}...\n " \
225
+ "- OK\n " \
226
+ "- remove automatic association {group:ungrouped <-> host:#{host}}...\n " \
227
+ "- OK\n" \
232
228
 
233
229
  # desired[:STDERR] = desired[:STDERR] +
234
230
  # "WARNING: Host '#{host}' does not exist and will be created.\n"
235
231
  end
236
232
  desired[:STDOUT] = desired[:STDOUT] +
237
- " - all OK\n"\
233
+ " - all OK\n" \
238
234
  "Succeeded.\n"
239
235
  expected(actual, desired)
240
236
 
@@ -246,3 +242,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
246
242
  end
247
243
  end
248
244
  end
245
+ # 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::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
-
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
- @group = Moose::Inventory::Cli::Group
29
- @app = Moose::Inventory::Cli::Application
10
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Group, command_ivar: :@group)
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 = @group.instance_methods(false).include?(:addvar)
21
+ result = @group.method_defined?(:addvar, false)
41
22
  expect(result).to eq(true)
42
23
  end
43
24
 
44
25
  #-----------------
45
26
  it '<missing args> ... should abort with an error' do
46
27
  actual = runner do
47
- @app.start(%w(group addvar)) # <- no group given
28
+ @app.start(%w[group addvar]) # <- no group given
48
29
  end
49
30
  # @console.out(actual, 'y')
50
31
 
@@ -60,16 +41,16 @@ RSpec.describe Moose::Inventory::Cli::Group do
60
41
  group_var = 'foo=bar'
61
42
 
62
43
  actual = runner do
63
- @app.start(%W(group addvar #{group_name} #{group_var}))
44
+ @app.start(%W[group addvar #{group_name} #{group_var}])
64
45
  end
65
46
 
66
47
  # Check output
67
48
  desired = { aborted: true }
68
49
  desired[:STDOUT] =
69
- "Add variables '#{group_var}' to group '#{group_name}':\n"\
70
- " - retrieve group '#{group_name}'...\n"
50
+ "Add variables '#{group_var}' to group '#{group_name}':\n " \
51
+ "- retrieve group '#{group_name}'...\n"
71
52
  desired[:STDERR] =
72
- "An error occurred during a transaction, any changes have been rolled back.\n"\
53
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
73
54
  "ERROR: The group '#{group_name}' does not exist.\n"
74
55
  expected(actual, desired)
75
56
  end
@@ -81,32 +62,30 @@ RSpec.describe Moose::Inventory::Cli::Group do
81
62
 
82
63
  group_name = 'test_group'
83
64
  @db.models[:group].create(name: group_name)
84
-
85
- var = { name: 'var1', value: 'testval' }
86
- cases = %w(
65
+ cases = %w[
87
66
  testvar
88
67
  testvar=
89
68
  =testval
90
69
  testvar=testval=
91
70
  =testvar=testval
92
71
  testvar=testval=extra
93
- )
72
+ ]
94
73
 
95
74
  cases.each do |args|
96
75
  actual = runner do
97
- @app.start(%W(group addvar #{group_name} #{args}))
76
+ @app.start(%W[group addvar #{group_name} #{args}])
98
77
  end
99
78
  # @console.out(actual,'p')
100
79
 
101
80
  desired = { aborted: true }
102
81
  desired[:STDOUT] =
103
- "Add variables '#{args}' to group '#{group_name}':\n"\
104
- " - retrieve group '#{group_name}'...\n"\
105
- " - OK\n"\
106
- " - add variable '#{args}'...\n"
82
+ "Add variables '#{args}' to group '#{group_name}':\n " \
83
+ "- retrieve group '#{group_name}'...\n " \
84
+ "- OK\n " \
85
+ "- add variable '#{args}'...\n"
107
86
 
108
87
  desired[:STDERR] =
109
- "An error occurred during a transaction, any changes have been rolled back.\n"\
88
+ "An error occurred during a transaction, any changes have been rolled back.\n" \
110
89
  "ERROR: Incorrect format in '{#{args}}'. Expected 'key=value'.\n"
111
90
 
112
91
  expected(actual, desired)
@@ -124,18 +103,18 @@ RSpec.describe Moose::Inventory::Cli::Group do
124
103
  @db.models[:group].create(name: group_name)
125
104
 
126
105
  actual = runner do
127
- @app.start(%W(group addvar #{group_name} #{var[:name]}=#{var[:value]}))
106
+ @app.start(%W[group addvar #{group_name} #{var[:name]}=#{var[:value]}])
128
107
  end
129
108
  # @console.out(actual,'p')
130
109
 
131
110
  desired = { aborted: false }
132
111
  desired[:STDOUT] =
133
- "Add variables '#{var[:name]}=#{var[:value]}' to group '#{group_name}':\n"\
134
- " - retrieve group '#{group_name}'...\n"\
135
- " - OK\n"\
136
- " - add variable '#{var[:name]}=#{var[:value]}'...\n"\
137
- " - OK\n"\
138
- " - all OK\n"\
112
+ "Add variables '#{var[:name]}=#{var[:value]}' to group '#{group_name}':\n " \
113
+ "- retrieve group '#{group_name}'...\n " \
114
+ "- OK\n " \
115
+ "- add variable '#{var[:name]}=#{var[:value]}'...\n " \
116
+ "- OK\n " \
117
+ "- all OK\n" \
139
118
  "Succeeded.\n"
140
119
  expected(actual, desired)
141
120
 
@@ -155,43 +134,60 @@ RSpec.describe Moose::Inventory::Cli::Group do
155
134
  group_name = 'test1'
156
135
  varsarray = [
157
136
  { name: 'var1', value: 'val1' },
158
- { name: 'var2', value: 'val2' },
137
+ { name: 'var2', value: 'val2' }
159
138
  ]
160
139
 
161
- vars = []
162
- varsarray.each do |var|
163
- vars << "#{var[:name]}=#{var[:value]}"
140
+ vars = varsarray.map do |var|
141
+ "#{var[:name]}=#{var[:value]}"
164
142
  end
165
143
 
166
144
  @db.models[:group].create(name: group_name)
167
145
 
168
146
  actual = runner do
169
- @app.start(%W(group addvar #{group_name}) + vars)
147
+ @app.start(%W[group addvar #{group_name}] + vars)
170
148
  end
171
149
 
172
150
  # @console.out(actual,'y')
173
151
 
174
152
  desired = { aborted: false }
175
153
  desired[:STDOUT] =
176
- "Add variables '#{vars.join(',')}' to group '#{group_name}':\n"\
177
- " - retrieve group '#{group_name}'...\n"\
178
- " - OK\n"
154
+ "Add variables '#{vars.join(',')}' to group '#{group_name}':\n " \
155
+ "- retrieve group '#{group_name}'...\n " \
156
+ "- OK\n"
179
157
  vars.each do |var|
180
158
  desired[:STDOUT] = desired[:STDOUT] +
181
- " - add variable '#{var}'...\n"\
182
- " - OK\n"
159
+ " - add variable '#{var}'...\n " \
160
+ "- OK\n"
183
161
  end
184
162
  desired[:STDOUT] = desired[:STDOUT] +
185
- " - all OK\n"\
163
+ " - all OK\n" \
186
164
  "Succeeded.\n"
187
165
  expected(actual, desired)
188
166
 
189
167
  # We should have the correct hostvar associations
190
168
  group = @db.models[:group].find(name: group_name)
191
- groupvars = group.groupvars_dataset
169
+ group.groupvars_dataset
192
170
  expect(vars.count).to eq(vars.length)
193
171
  end
194
172
 
173
+ #------------------------
174
+ it 'GROUP key=value --dry-run should not create or update variables' do
175
+ group_name = 'test1'
176
+ @db.models[:group].create(name: group_name)
177
+ runner { @app.start(%W[group addvar #{group_name} var1=old]) }
178
+
179
+ actual = runner do
180
+ @app.start(%W[group addvar #{group_name} var1=new var2=val2 --dry-run])
181
+ end
182
+
183
+ expect(actual[:unexpected]).to eq(false)
184
+ expect(actual[:aborted]).to eq(false)
185
+ expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
186
+ group = @db.models[:group].find(name: group_name)
187
+ expect(group.groupvars_dataset[name: 'var1'][:value]).to eq('old')
188
+ expect(group.groupvars_dataset[name: 'var2']).to be_nil
189
+ end
190
+
195
191
  #------------------------
196
192
  it 'GROUP key=value ... should update an already existing association' do
197
193
  # 1. Should add the var to the db
@@ -201,23 +197,23 @@ RSpec.describe Moose::Inventory::Cli::Group do
201
197
  var = { name: 'var1', value: 'testval' }
202
198
 
203
199
  @db.models[:group].create(name: group_name)
204
- runner { @app.start(%W(group addvar #{group_name} #{var[:name]}=#{var[:value]})) }
200
+ runner { @app.start(%W[group addvar #{group_name} #{var[:name]}=#{var[:value]}]) }
205
201
 
206
202
  var[:value] = 'newtestval'
207
203
  actual = runner do
208
- @app.start(%W(group addvar #{group_name} #{var[:name]}=#{var[:value]}))
204
+ @app.start(%W[group addvar #{group_name} #{var[:name]}=#{var[:value]}])
209
205
  end
210
206
  # @console.out(actual,'y')
211
207
 
212
208
  desired = { aborted: false }
213
209
  desired[:STDOUT] =
214
- "Add variables '#{var[:name]}=#{var[:value]}' to group '#{group_name}':\n"\
215
- " - retrieve group '#{group_name}'...\n"\
216
- " - OK\n"\
217
- " - add variable '#{var[:name]}=#{var[:value]}'...\n"\
218
- " - already exists, applying as an update...\n"\
219
- " - OK\n"\
220
- " - all OK\n"\
210
+ "Add variables '#{var[:name]}=#{var[:value]}' to group '#{group_name}':\n " \
211
+ "- retrieve group '#{group_name}'...\n " \
212
+ "- OK\n " \
213
+ "- add variable '#{var[:name]}=#{var[:value]}'...\n " \
214
+ "- already exists, applying as an update...\n " \
215
+ "- OK\n " \
216
+ "- all OK\n" \
221
217
  "Succeeded.\n"
222
218
  expected(actual, desired)
223
219
 
@@ -233,3 +229,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
233
229
  end
234
230
  end
235
231
  end
232
+ # rubocop:enable Metrics/BlockLength
@@ -1,49 +1,30 @@
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
-
21
- @config = Moose::Inventory::Config
22
- @config.init(@mockargs)
23
- @console = Moose::Inventory::Cli::Formatter
24
-
25
- @db = Moose::Inventory::DB
26
- @db.init if @db.db.nil?
27
-
28
- @group = Moose::Inventory::Cli::Group
29
- @app = Moose::Inventory::Cli::Application
10
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Group, command_ivar: :@group)
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 'get' do
38
19
  #---------------------
39
20
  it 'should be responsive' do
40
- result = @group.instance_methods(false).include?(:get)
21
+ result = @group.method_defined?(:get, false)
41
22
  expect(result).to eq(true)
42
23
  end
43
24
 
44
25
  #---------------------
45
26
  it '<missing args> ... should abort with an error' do
46
- actual = runner { @app.start(%w(group get)) }
27
+ actual = runner { @app.start(%w[group get]) }
47
28
 
48
29
  # @console.out(actual,'y')
49
30
 
@@ -56,7 +37,7 @@ RSpec.describe Moose::Inventory::Cli::Group do
56
37
  #---------------------
57
38
  it "GROUP ... should return an empty set when GROUP doesn't exist" do
58
39
  group_name = 'does-not-exist'
59
- actual = runner { @app.start(%W(group get #{group_name})) }
40
+ actual = runner { @app.start(%W[group get #{group_name}]) }
60
41
 
61
42
  # @console.out(actual, 'y')
62
43
 
@@ -69,13 +50,15 @@ RSpec.describe Moose::Inventory::Cli::Group do
69
50
  #---------------------
70
51
  it 'GROUP ... should get a group from the db' do
71
52
  name = 'test_group'
72
- runner { @app.start(%W(group add #{name})) }
53
+ runner { @app.start(%W[group add #{name}]) }
73
54
 
74
- actual = runner { @app.start(%W(group get #{name})) }
55
+ actual = runner { @app.start(%W[group get #{name}]) }
75
56
 
76
57
  mock = {}
77
58
  mock[name.to_sym] = {}
78
- # mock[name.to_sym][:hosts] = [] # TODO: Should this be present or not?
59
+ # Contract: `group get` omits empty relationship collections in the default
60
+ # human/data output. `group list --ansible` keeps `hosts: []` because
61
+ # Ansible inventory consumers expect the key to exist.
79
62
 
80
63
  desired = { aborted: false, STDOUT: '', STDERR: '' }
81
64
  desired[:STDOUT] = mock.to_yaml
@@ -87,10 +70,10 @@ RSpec.describe Moose::Inventory::Cli::Group do
87
70
  it 'GROUP ... should display groupvars, if any are set' do
88
71
  name = 'test_group'
89
72
  var = 'foo=bar'
90
- tmp = runner { @app.start(%W(group add #{name})) }
91
- tmp = runner { @app.start(%W(group addvar #{name} #{var})) }
73
+ runner { @app.start(%W[group add #{name}]) }
74
+ runner { @app.start(%W[group addvar #{name} #{var}]) }
92
75
 
93
- actual = runner { @app.start(%W(group get #{name})) }
76
+ actual = runner { @app.start(%W[group get #{name}]) }
94
77
  # @console.out(actual, 'y')
95
78
 
96
79
  mock = {}
@@ -104,3 +87,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
104
87
  end
105
88
  end
106
89
  end
90
+ # rubocop:enable Metrics/BlockLength