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,48 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
1
4
  require 'spec_helper'
2
5
 
3
6
  RSpec.describe Moose::Inventory::Cli::Group do
4
7
  before(:all) do
5
- # Set up the configuration object
6
- @mockarg_parts = {
7
- config: File.join(spec_root, 'config/config.yml'),
8
- format: 'yaml',
9
- env: 'test',
10
- }
11
-
12
- @mockargs = []
13
- @mockarg_parts.each do |key, val|
14
- @mockargs << "--#{key}"
15
- @mockargs << val
16
- end
17
-
18
- @config = Moose::Inventory::Config
19
- @config.init(@mockargs)
20
-
21
- @db = Moose::Inventory::DB
22
- @db.init if @db.db.nil?
23
-
24
- @console = Moose::Inventory::Cli::Formatter
25
- @group = Moose::Inventory::Cli::Group
26
- @cli = Moose::Inventory::Cli
27
- @app = Moose::Inventory::Cli::Application
8
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Group, command_ivar: :@group, include_cli: true)
28
9
  end
29
10
 
30
11
  before(:each) do
31
- @db.reset
12
+ reset_cli_harness
32
13
  end
33
14
 
34
15
  #====================
35
16
  describe 'list' do
36
17
  #---------------------
37
18
  it 'should be responsive' do
38
- result = @group.instance_methods(false).include?(:list)
19
+ result = @group.method_defined?(:list, false)
39
20
  expect(result).to eq(true)
40
21
  end
41
22
 
42
23
  #---------------------
43
24
  it 'should return an empty set when no results' do
44
25
  # no items in the db
45
- actual = runner { @app.start(%w(group list)) }
26
+ actual = runner { @app.start(%w[group list]) }
46
27
 
47
28
  desired = { aborted: false, STDOUT: '', STDERR: '' }
48
29
  desired[:STDOUT] = {}.to_yaml
@@ -53,19 +34,18 @@ RSpec.describe Moose::Inventory::Cli::Group do
53
34
  #---------------------
54
35
  it 'should get a list of group from the db' do
55
36
  var = 'foo=bar'
56
- host_name = 'test_host'
57
37
 
58
38
  mock = {}
59
- groups = %w(group1 group2 group3)
39
+ groups = %w[group1 group2 group3]
60
40
  groups.each do |name|
61
- runner { @app.start(%W(group add #{name})) }
62
- runner { @app.start(%W(group addvar #{name} #{var})) }
41
+ runner { @app.start(%W[group add #{name}]) }
42
+ runner { @app.start(%W[group addvar #{name} #{var}]) }
63
43
  mock[name.to_sym] = {}
64
44
  mock[name.to_sym][:groupvars] = { foo: 'bar' }
65
45
  end
66
46
 
67
47
  # items should now be in the db
68
- actual = runner { @app.start(%w(group list)) }
48
+ actual = runner { @app.start(%w[group list]) }
69
49
 
70
50
  desired = { aborted: false, STDOUT: '', STDERR: '' }
71
51
  desired[:STDOUT] = mock.to_yaml
@@ -75,12 +55,10 @@ RSpec.describe Moose::Inventory::Cli::Group do
75
55
 
76
56
  #---------------------
77
57
  it 'should be an alias of --list (i.e. Ansible parameter)' do
78
- host_name = 'test_host'
79
-
80
58
  mock = {}
81
- groups = %w(group1 group2 group3)
59
+ groups = %w[group1 group2 group3]
82
60
  groups.each do |name|
83
- runner { @app.start(%W(group add #{name})) }
61
+ runner { @app.start(%W[group add #{name}]) }
84
62
  mock[name.to_sym] = { hosts: [] }
85
63
  end
86
64
 
@@ -92,10 +70,10 @@ RSpec.describe Moose::Inventory::Cli::Group do
92
70
  # @console.out(actual, 'y')
93
71
 
94
72
  desired = { aborted: false, STDOUT: '', STDERR: '' }
95
- desired[:STDOUT] = mock.to_json + "\n"
73
+ desired[:STDOUT] = "#{mock.to_json}\n"
96
74
 
97
75
  expected(actual, desired)
98
76
  end
99
- end
100
77
  end
101
-
78
+ end
79
+ # rubocop:enable Metrics/BlockLength
@@ -1,50 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
1
4
  require 'spec_helper'
2
5
 
3
6
  RSpec.describe Moose::Inventory::Cli::Group do
4
7
  before(:all) do
5
- # Set up the configuration object
6
- @mockarg_parts = {
7
- config: File.join(spec_root, 'config/config.yml'),
8
- format: 'yaml',
9
- env: 'test',
10
- }
11
-
12
- @mockargs = []
13
- @mockarg_parts.each do |key, val|
14
- @mockargs << "--#{key}"
15
- @mockargs << val
16
- end
17
-
18
- @config = Moose::Inventory::Config
19
- @config.init(@mockargs)
20
-
21
- @db = Moose::Inventory::DB
22
- @db.init if @db.db.nil?
23
-
24
- @console = Moose::Inventory::Cli::Formatter
25
- @group = Moose::Inventory::Cli::Group
26
- @cli = Moose::Inventory::Cli
27
- @app = Moose::Inventory::Cli::Application
8
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Group, command_ivar: :@group, include_cli: true)
28
9
  end
29
10
 
30
11
  before(:each) do
31
- # We make some @cli calls, which changes config,
32
- # so we must reset config on each pass
33
- @config.init(@mockargs)
34
- @db.reset
12
+ reset_cli_harness(reset_config: true)
35
13
  end
36
14
 
37
15
  #==================
38
16
  describe 'listvar' do
39
17
  #-----------------
40
18
  it 'should be responsive' do
41
- result = @group.instance_methods(false).include?(:listvars)
19
+ result = @group.method_defined?(:listvars, false)
42
20
  expect(result).to eq(true)
43
21
  end
44
22
 
45
23
  #-----------------
46
24
  it '<missing args> ... should abort with an error' do
47
- actual = runner { @app.start(%w(group listvars)) }
25
+ actual = runner { @app.start(%w[group listvars]) }
48
26
 
49
27
  # Check output
50
28
  desired = { aborted: true }
@@ -55,7 +33,7 @@ RSpec.describe Moose::Inventory::Cli::Group do
55
33
  #-----------------
56
34
  it '--ansible <missing args> ... should abort with an error' do
57
35
  args = @mockargs.clone
58
- args.concat(%w(--ansible group listvars)).flatten
36
+ args.push('--ansible', 'group', 'listvars').flatten
59
37
 
60
38
  actual = runner { @cli.start(args) }
61
39
 
@@ -68,13 +46,13 @@ RSpec.describe Moose::Inventory::Cli::Group do
68
46
  #------------------------
69
47
  it 'GROUP ... should return a list of group variables grouped by group' do
70
48
  group_name = 'test_group'
71
- group_vars = %w(foo=bar cow=chicken)
49
+ group_vars = %w[foo=bar cow=chicken]
72
50
 
73
- tmp = runner { @app.start(%W(group add #{group_name})) }
74
- tmp = runner { @app.start(%W(group addvar #{group_name} #{group_vars[0]} #{group_vars[1]})) }
51
+ runner { @app.start(%W[group add #{group_name}]) }
52
+ runner { @app.start(%W[group addvar #{group_name} #{group_vars[0]} #{group_vars[1]}]) }
75
53
 
76
54
  actual = runner do
77
- @app.start(%W(group listvars #{group_name}))
55
+ @app.start(%W[group listvars #{group_name}])
78
56
  end
79
57
 
80
58
  # @console.out(actual, 'y')
@@ -93,15 +71,15 @@ RSpec.describe Moose::Inventory::Cli::Group do
93
71
  end
94
72
 
95
73
  #------------------------
96
- it '--ansible GROUP ... should return a list of group variables, in a style akin to Ansible\'s \'--host HOSTNAME\'' do
74
+ it 'returns group variables in an Ansible-style hostvars payload' do
97
75
  group_name = 'test_group'
98
- group_vars = %w(foo=bar cow=chicken)
76
+ group_vars = %w[foo=bar cow=chicken]
99
77
 
100
- tmp = runner { @app.start(%W(group add #{group_name})) }
101
- tmp = runner { @app.start(%W(group addvar #{group_name} #{group_vars[0]} #{group_vars[1]})) }
78
+ runner { @app.start(%W[group add #{group_name}]) }
79
+ runner { @app.start(%W[group addvar #{group_name} #{group_vars[0]} #{group_vars[1]}]) }
102
80
 
103
81
  actual = runner do
104
- @cli.start(%W(--config #{@mockarg_parts[:config]} --ansible group listvars #{group_name}))
82
+ @cli.start(%W[--config #{@mockarg_parts[:config]} --ansible group listvars #{group_name}])
105
83
  end
106
84
 
107
85
  # @console.out(actual, 'y')
@@ -114,8 +92,23 @@ RSpec.describe Moose::Inventory::Cli::Group do
114
92
  end
115
93
 
116
94
  desired = {}
117
- desired[:STDOUT] = mock.to_json + "\n"
95
+ desired[:STDOUT] = "#{mock.to_json}\n"
96
+ expected(actual, desired)
97
+ end
98
+
99
+ #------------------------
100
+ it 'warns when an Ansible-mode group does not exist' do
101
+ group_name = 'missing_group'
102
+
103
+ actual = runner do
104
+ @cli.start(%W[--config #{@mockarg_parts[:config]} --ansible group listvars #{group_name}])
105
+ end
106
+
107
+ desired = {}
108
+ desired[:STDOUT] = "{}\n"
109
+ desired[:STDERR] = "WARNING: The Group #{group_name} does not exist."
118
110
  expected(actual, desired)
119
111
  end
120
112
  end
121
113
  end
114
+ # rubocop:enable Metrics/BlockLength
@@ -1,51 +1,34 @@
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
- @console = Moose::Inventory::Cli::Formatter
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 'rm' do
40
23
  #---------------
41
24
  it 'Group.rm() should be responsive' do
42
- result = @group.instance_methods(false).include?(:rm)
25
+ result = @group.method_defined?(:rm, false)
43
26
  expect(result).to eq(true)
44
27
  end
45
28
 
46
29
  #---------------
47
30
  it '<missing argument> ... should abort with an error' do
48
- actual = runner { @app.start(%w(host rm)) }
31
+ actual = runner { @app.start(%w[host rm]) }
49
32
 
50
33
  # Check output
51
34
  desired = { aborted: true, STDERR: '', STDOUT: '' }
@@ -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 rm ungrouped)) }
41
+ actual = runner { @app.start(%w[group rm ungrouped]) }
59
42
 
60
43
  # Check output
61
44
  desired = { aborted: true }
@@ -73,16 +56,16 @@ RSpec.describe Moose::Inventory::Cli::Group do
73
56
 
74
57
  # no items in the db
75
58
  group_name = 'fake'
76
- actual = runner { @app.start(%W(group rm #{group_name})) }
59
+ actual = runner { @app.start(%W[group rm #{group_name} --yes]) }
77
60
 
78
61
  # @console.out(actual,'y')
79
62
  desired = {}
80
63
  desired[:STDOUT] =
81
- "Remove group '#{group_name}':\n"\
82
- " - Retrieve group '#{group_name}'...\n"\
83
- " - No such group, skipping.\n"\
84
- " - OK\n"\
85
- " - All OK\n"\
64
+ "Remove group '#{group_name}':\n " \
65
+ "- Retrieve group '#{group_name}'...\n " \
66
+ "- No such group, skipping.\n " \
67
+ "- OK\n " \
68
+ "- All OK\n" \
86
69
  "Succeeded, with warnings.\n"
87
70
  desired[:STDERR] =
88
71
  "WARNING: Group '#{group_name}' does not exist, skipping.\n"
@@ -95,17 +78,17 @@ RSpec.describe Moose::Inventory::Cli::Group do
95
78
  group_name = 'test1'
96
79
  @db.models[:group].create(name: group_name)
97
80
 
98
- actual = runner { @app.start(%W(group rm #{group_name})) }
81
+ actual = runner { @app.start(%W[group rm #{group_name} --yes]) }
99
82
 
100
83
  # Check output
101
84
  desired = {}
102
85
  desired[:STDOUT] =
103
- "Remove group '#{group_name}':\n"\
104
- " - Retrieve group '#{group_name}'...\n"\
105
- " - OK\n"\
106
- " - Destroy group '#{group_name}'...\n"\
107
- " - OK\n"\
108
- " - All OK\n"\
86
+ "Remove group '#{group_name}':\n " \
87
+ "- Retrieve group '#{group_name}'...\n " \
88
+ "- OK\n " \
89
+ "- Destroy group '#{group_name}'...\n " \
90
+ "- OK\n " \
91
+ "- All OK\n" \
109
92
  "Succeeded.\n"
110
93
  expected(actual, desired)
111
94
 
@@ -114,12 +97,68 @@ RSpec.describe Moose::Inventory::Cli::Group do
114
97
  expect(group).to be_nil
115
98
  end
116
99
 
100
+ #---------------
101
+ it 'GROUP without --yes or --dry-run should abort before deleting' do
102
+ group_name = 'group1'
103
+ @db.models[:group].create(name: group_name)
104
+
105
+ actual = runner { @app.start(%W[group rm #{group_name}]) }
106
+
107
+ desired = {
108
+ aborted: true,
109
+ STDOUT: '',
110
+ STDERR: "ERROR: group rm #{group_name} is destructive. Re-run with --yes to confirm, " \
111
+ "or use --dry-run to preview.\n"
112
+ }
113
+ expected(actual, desired)
114
+ expect(@db.models[:group].find(name: group_name)).not_to be_nil
115
+ end
116
+
117
+ #---------------
118
+ it 'GROUP --dry-run should show planned removal without deleting the group' do
119
+ group_name = 'test1'
120
+ @db.models[:group].create(name: group_name)
121
+
122
+ actual = runner { @app.start(%W[group rm #{group_name} --dry-run]) }
123
+
124
+ desired = {}
125
+ desired[:STDOUT] =
126
+ "Remove group '#{group_name}':\n " \
127
+ "- Retrieve group '#{group_name}'...\n " \
128
+ "- OK\n " \
129
+ "- Destroy group '#{group_name}'...\n " \
130
+ "- OK\n " \
131
+ "- All OK\n" \
132
+ "Dry run complete. No changes applied.\n" \
133
+ "Succeeded.\n"
134
+
135
+ expected(actual, desired)
136
+ expect(@db.models[:group].find(name: group_name)).not_to be_nil
137
+ end
138
+
139
+ #---------------
140
+ it 'GROUP --recursive --dry-run should show recursive cleanup without deleting groups' do
141
+ runner { @app.start(%w[group add parent]) }
142
+ runner { @app.start(%w[group add child --hosts child-host]) }
143
+ runner { @app.start(%w[group addchild parent child]) }
144
+
145
+ actual = runner { @app.start(%w[group rm --recursive parent --dry-run]) }
146
+
147
+ expect(actual[:unexpected]).to eq(false)
148
+ expect(actual[:aborted]).to eq(false)
149
+ expect(actual[:STDOUT]).to include("- Recursively delete orphaned group 'child'...\n")
150
+ expect(actual[:STDOUT]).to include('Dry run complete. No changes applied.')
151
+ expect(@db.models[:group].find(name: 'parent')).not_to be_nil
152
+ expect(@db.models[:group].find(name: 'child')).not_to be_nil
153
+ host = @db.models[:host].find(name: 'child-host')
154
+ expect(host.groups_dataset[name: 'ungrouped']).to be_nil
155
+ end
117
156
  #---------------
118
157
  it "GROUP ... should handle the automatic 'ungrouped' group for associated hosts" do
119
158
  host_name = 'test-host1'
120
159
  group_name = 'test-group1'
121
160
 
122
- tmp = runner { @app.start(%W(group add #{group_name} --hosts #{host_name})) }
161
+ tmp = runner { @app.start(%W[group add #{group_name} --hosts #{host_name}]) }
123
162
  expect(tmp[:unexpected]).to eq(false)
124
163
  expect(tmp[:aborted]).to eq(false)
125
164
  host = @db.models[:host].find(name: host_name)
@@ -128,21 +167,21 @@ RSpec.describe Moose::Inventory::Cli::Group do
128
167
  expect(groups_ds[name: 'ungrouped']).to be_nil # Shouldn't be ungrouped
129
168
 
130
169
  # Now do the rm
131
- actual = runner { @app.start(%W(group rm #{group_name})) }
170
+ actual = runner { @app.start(%W[group rm #{group_name} --yes]) }
132
171
 
133
172
  # @console.out(actual)
134
173
 
135
174
  # Check output
136
175
  desired = {}
137
176
  desired[:STDOUT] =
138
- "Remove group '#{group_name}':\n"\
139
- " - Retrieve group '#{group_name}'...\n"\
140
- " - OK\n"\
141
- " - Adding automatic association {group:ungrouped <-> host:#{host_name}}...\n"\
142
- " - OK\n"\
143
- " - Destroy group '#{group_name}'...\n"\
144
- " - OK\n"\
145
- " - All OK\n"\
177
+ "Remove group '#{group_name}':\n " \
178
+ "- Retrieve group '#{group_name}'...\n " \
179
+ "- OK\n " \
180
+ "- Adding automatic association {group:ungrouped <-> host:#{host_name}}...\n " \
181
+ "- OK\n " \
182
+ "- Destroy group '#{group_name}'...\n " \
183
+ "- OK\n " \
184
+ "- All OK\n" \
146
185
  "Succeeded.\n"
147
186
  expected(actual, desired)
148
187
 
@@ -159,26 +198,26 @@ RSpec.describe Moose::Inventory::Cli::Group do
159
198
 
160
199
  #---------------
161
200
  it 'GROUP1 GROUP2 ... should remove multiple groups' do
162
- names = %w(group1 group2 group3)
201
+ names = %w[group1 group2 group3]
163
202
  names.each do |name|
164
203
  @db.models[:group].create(name: name)
165
204
  end
166
205
 
167
- actual = runner { @app.start(%w(group rm) + names) }
206
+ actual = runner { @app.start(%w[group rm --yes] + names) }
168
207
 
169
208
  # Check output
170
209
  desired = { STDOUT: '' }
171
210
  names.each do |name|
172
211
  # Check output
173
212
  desired[:STDOUT] = desired[:STDOUT] +
174
- "Remove group '#{name}':\n"\
175
- " - Retrieve group '#{name}'...\n"\
176
- " - OK\n"\
177
- " - Destroy group '#{name}'...\n"\
178
- " - OK\n"\
179
- " - All OK\n"
213
+ "Remove group '#{name}':\n " \
214
+ "- Retrieve group '#{name}'...\n " \
215
+ "- OK\n " \
216
+ "- Destroy group '#{name}'...\n " \
217
+ "- OK\n " \
218
+ "- All OK\n"
180
219
  end
181
- desired[:STDOUT] = desired[:STDOUT] + "Succeeded.\n"
220
+ desired[:STDOUT] = "#{desired[:STDOUT]}Succeeded.\n"
182
221
  expected(actual, desired)
183
222
 
184
223
  # Check db
@@ -189,24 +228,24 @@ RSpec.describe Moose::Inventory::Cli::Group do
189
228
  #---------------
190
229
  it 'GROUP ... should remove GROUP, where GROUP has an associated parent.' do
191
230
  @db.models[:group].create(name: 'parent')
192
- runner { @app.start(%w(group addchild parent child)) }
231
+ runner { @app.start(%w[group addchild parent child]) }
193
232
 
194
- actual = runner { @app.start(%w(group rm child)) }
233
+ actual = runner { @app.start(%w[group rm child --yes]) }
195
234
 
196
235
  # Check output
197
236
  desired = { STDOUT: '' }
198
237
  # Check output
199
238
  desired[:STDOUT] = desired[:STDOUT] +
200
- "Remove group 'child':\n"\
201
- " - Retrieve group 'child'...\n"\
202
- " - OK\n"\
203
- " - Remove association {group:child <-> group:parent}...\n"\
204
- " - OK\n"\
205
- " - Destroy group 'child'...\n"\
206
- " - OK\n"\
207
- " - All OK\n"
208
-
209
- desired[:STDOUT] = desired[:STDOUT] + "Succeeded.\n"
239
+ "Remove group 'child':\n " \
240
+ "- Retrieve group 'child'...\n " \
241
+ "- OK\n " \
242
+ "- Remove association {group:child <-> group:parent}...\n " \
243
+ "- OK\n " \
244
+ "- Destroy group 'child'...\n " \
245
+ "- OK\n " \
246
+ "- All OK\n"
247
+
248
+ desired[:STDOUT] = "#{desired[:STDOUT]}Succeeded.\n"
210
249
  expected(actual, desired)
211
250
 
212
251
  # Check db
@@ -217,24 +256,24 @@ RSpec.describe Moose::Inventory::Cli::Group do
217
256
  #---------------
218
257
  it 'GROUP ... should remove GROUP, where GROUP has an associated child.' do
219
258
  @db.models[:group].create(name: 'parent')
220
- runner { @app.start(%w(group addchild parent child)) }
259
+ runner { @app.start(%w[group addchild parent child]) }
221
260
 
222
- actual = runner { @app.start(%w(group rm parent)) }
261
+ actual = runner { @app.start(%w[group rm parent --yes]) }
223
262
 
224
263
  # Check output
225
264
  desired = { STDOUT: '' }
226
265
  # Check output
227
266
  desired[:STDOUT] = desired[:STDOUT] +
228
- "Remove group 'parent':\n"\
229
- " - Retrieve group 'parent'...\n"\
230
- " - OK\n"\
231
- " - Remove association {group:parent <-> group:child}...\n"\
232
- " - OK\n"\
233
- " - Destroy group 'parent'...\n"\
234
- " - OK\n"\
235
- " - All OK\n"
236
-
237
- desired[:STDOUT] = desired[:STDOUT] + "Succeeded.\n"
267
+ "Remove group 'parent':\n " \
268
+ "- Retrieve group 'parent'...\n " \
269
+ "- OK\n " \
270
+ "- Remove association {group:parent <-> group:child}...\n " \
271
+ "- OK\n " \
272
+ "- Destroy group 'parent'...\n " \
273
+ "- OK\n " \
274
+ "- All OK\n"
275
+
276
+ desired[:STDOUT] = "#{desired[:STDOUT]}Succeeded.\n"
238
277
  expected(actual, desired)
239
278
 
240
279
  # Check db
@@ -244,20 +283,20 @@ RSpec.describe Moose::Inventory::Cli::Group do
244
283
 
245
284
  #---------------
246
285
  it 'GROUP --recursive ... should remove orphaned child groups recursively' do
247
- runner { @app.start(%w(group add parent)) }
248
- runner { @app.start(%w(group add child --hosts child-host)) }
249
- runner { @app.start(%w(group add grandchild)) }
250
- runner { @app.start(%w(group addchild parent child)) }
251
- runner { @app.start(%w(group addchild child grandchild)) }
286
+ runner { @app.start(%w[group add parent]) }
287
+ runner { @app.start(%w[group add child --hosts child-host]) }
288
+ runner { @app.start(%w[group add grandchild]) }
289
+ runner { @app.start(%w[group addchild parent child]) }
290
+ runner { @app.start(%w[group addchild child grandchild]) }
252
291
 
253
- actual = runner { @app.start(%w(group rm --recursive parent)) }
292
+ actual = runner { @app.start(%w[group rm --recursive parent --yes]) }
254
293
 
255
294
  expect(actual[:unexpected]).to eq(false)
256
295
  expect(actual[:aborted]).to eq(false)
257
296
  expect(actual[:STDOUT]).to include("- Recursively delete orphaned group 'child'...\n")
258
297
  expect(actual[:STDOUT]).to include("- Recursively delete orphaned group 'grandchild'...\n")
259
298
 
260
- %w(parent child grandchild).each do |name|
299
+ %w[parent child grandchild].each do |name|
261
300
  expect(@db.models[:group].find(name: name)).to be_nil
262
301
  end
263
302
 
@@ -267,11 +306,11 @@ RSpec.describe Moose::Inventory::Cli::Group do
267
306
 
268
307
  #---------------
269
308
  it 'GROUP --recursive ... should not remove child groups with another parent' do
270
- runner { @app.start(%w(group add parent other-parent)) }
271
- runner { @app.start(%w(group addchild parent child)) }
272
- runner { @app.start(%w(group addchild other-parent child)) }
309
+ runner { @app.start(%w[group add parent other-parent]) }
310
+ runner { @app.start(%w[group addchild parent child]) }
311
+ runner { @app.start(%w[group addchild other-parent child]) }
273
312
 
274
- actual = runner { @app.start(%w(group rm --recursive parent)) }
313
+ actual = runner { @app.start(%w[group rm --recursive parent --yes]) }
275
314
 
276
315
  expect(actual[:unexpected]).to eq(false)
277
316
  expect(actual[:aborted]).to eq(false)
@@ -283,3 +322,4 @@ RSpec.describe Moose::Inventory::Cli::Group do
283
322
  end
284
323
  end
285
324
  end
325
+ # rubocop:enable Metrics/BlockLength