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,51 +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
- @console = Moose::Inventory::Cli::Formatter
24
-
25
- @db = Moose::Inventory::DB
26
- @db.init if @db.db.nil?
27
-
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 'get' do
38
19
  #---------------------
39
20
  it 'Host.get() should be responsive' do
40
- result = @host.instance_methods(false).include?(:get)
21
+ result = @host.method_defined?(:get, false)
41
22
  expect(result).to eq(true)
42
23
  end
43
24
 
44
25
  #---------------------
45
26
  it 'host get <missing args> ... should abort with an error' do
46
27
  # no items in the db
47
- name = 'not-in-db'
48
- actual = runner { @app.start(%w(host get)) }
28
+ actual = runner { @app.start(%w[host get]) }
49
29
 
50
30
  desired = { aborted: true }
51
31
  desired[:STDERR] = "ERROR: Wrong number of arguments, 0 for 1 or more\n"
@@ -57,7 +37,7 @@ RSpec.describe Moose::Inventory::Cli::Host do
57
37
  it 'host get HOST ... should return an empty set when HOST doesn\'t exist' do
58
38
  # no items in the db
59
39
  name = 'not-in-db'
60
- actual = runner { @app.start(%W(host get #{name})) }
40
+ actual = runner { @app.start(%W[host get #{name}]) }
61
41
 
62
42
  desired = {}
63
43
  desired[:STDOUT] = {}.to_yaml
@@ -68,9 +48,9 @@ RSpec.describe Moose::Inventory::Cli::Host do
68
48
  #---------------------
69
49
  it 'host get HOST ... should get a host from the db' do
70
50
  name = 'test-host-add'
71
- runner { @app.start(%W(host add #{name})) }
51
+ runner { @app.start(%W[host add #{name}]) }
72
52
 
73
- actual = runner { @app.start(%W(host get #{name})) }
53
+ actual = runner { @app.start(%W[host get #{name}]) }
74
54
 
75
55
  mock = {}
76
56
  mock[name.to_sym] = {}
@@ -86,10 +66,10 @@ RSpec.describe Moose::Inventory::Cli::Host do
86
66
  it 'host get HOST ... should display hostvars, if any are set' do
87
67
  name = 'test-host-add'
88
68
  var = 'foo=bar'
89
- runner { @app.start(%W(host add #{name})) }
90
- runner { @app.start(%W(host addvar #{name} #{var})) }
69
+ runner { @app.start(%W[host add #{name}]) }
70
+ runner { @app.start(%W[host addvar #{name} #{var}]) }
91
71
 
92
- actual = runner { @app.start(%W(host get #{name})) }
72
+ actual = runner { @app.start(%W[host get #{name}]) }
93
73
 
94
74
  mock = {}
95
75
  mock[name.to_sym] = {}
@@ -103,3 +83,4 @@ RSpec.describe Moose::Inventory::Cli::Host do
103
83
  end
104
84
  end
105
85
  end
86
+ # rubocop:enable Metrics/BlockLength
@@ -1,51 +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 'list' do
38
19
  #---------------------
39
20
  it 'should be responsive' do
40
- result = @host.instance_methods(false).include?(:list)
21
+ result = @host.method_defined?(:list, false)
41
22
  expect(result).to eq(true)
42
23
  end
43
24
 
44
25
  #---------------------
45
26
  it 'should return an empty set when no results' do
46
27
  # no items in the db
47
- name = 'not-in-db'
48
- actual = runner { @app.start(%w(host list)) }
28
+ actual = runner { @app.start(%w[host list]) }
49
29
 
50
30
  desired = { aborted: false, STDOUT: '', STDERR: '' }
51
31
  desired[:STDOUT] = {}.to_yaml
@@ -55,25 +35,53 @@ RSpec.describe Moose::Inventory::Cli::Host do
55
35
 
56
36
  #---------------------
57
37
  it 'should get a list of hosts from the db' do
58
- var = 'foo=bar'
59
-
60
38
  mock = {}
61
- hosts = %w(host1 host2 host3)
39
+ hosts = %w[host1 host2 host3]
62
40
  hosts.each do |name|
63
- runner { @app.start(%W(host add #{name})) }
64
- runner { @app.start(%W(host addvar #{name} foo=bar)) }
41
+ runner { @app.start(%W[host add #{name}]) }
42
+ runner { @app.start(%W[host addvar #{name} foo=bar]) }
65
43
  mock[name.to_sym] = {}
66
44
  mock[name.to_sym][:groups] = ['ungrouped']
67
45
  mock[name.to_sym][:hostvars] = { foo: 'bar' }
68
46
  end
69
47
 
70
48
  # items should now be in the db
71
- actual = runner { @app.start(%w(host list)) }
49
+ actual = runner { @app.start(%w[host list]) }
72
50
 
73
51
  desired = { aborted: false, STDOUT: '', STDERR: '' }
74
52
  desired[:STDOUT] = mock.to_yaml
75
53
 
76
54
  expected(actual, desired)
77
55
  end
56
+
57
+ it 'filters hosts by group, tag, and variable' do
58
+ runner { @app.start(%w[group add web]) }
59
+ runner { @app.start(%w[host add web01 --groups web]) }
60
+ runner { @app.start(%w[host add db01]) }
61
+ runner { @app.start(%w[host addtag web01 prod]) }
62
+ runner { @app.start(%w[host addvar web01 os=fedora]) }
63
+ runner { @app.start(%w[host addtag db01 prod]) }
64
+ runner { @app.start(%w[host addvar db01 os=debian]) }
65
+
66
+ actual = runner { @app.start(%w[host list --group web --tag prod --var os=fedora]) }
67
+
68
+ desired = { aborted: false, STDERR: '' }
69
+ desired[:STDOUT] = {
70
+ web01: {
71
+ groups: ['web'],
72
+ tags: ['prod'],
73
+ hostvars: { os: 'fedora' }
74
+ }
75
+ }.to_yaml
76
+ expected(actual, desired)
77
+ end
78
+
79
+ it 'aborts on invalid variable filters' do
80
+ actual = runner { @app.start(%w[host list --var broken]) }
81
+
82
+ expect(actual[:aborted]).to eq(true)
83
+ expect(actual[:STDERR]).to include("ERROR: Invalid variable filter 'broken'. Expected key=value.")
84
+ end
78
85
  end
79
86
  end
87
+ # rubocop:enable Metrics/BlockLength
@@ -1,53 +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::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
- @cli = Moose::Inventory::Cli
30
- @app = Moose::Inventory::Cli::Application
10
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Host, command_ivar: :@host, include_cli: true)
31
11
  end
32
12
 
33
13
  before(:each) do
34
- # We make some @cli calls, which changes config,
35
- # so we must reset config on each pass
36
- @config.init(@mockargs)
37
- @db.reset
14
+ reset_cli_harness(reset_config: true)
38
15
  end
39
16
 
40
17
  #==================
41
18
  describe 'listvar' do
42
19
  #-----------------
43
20
  it 'should be responsive' do
44
- result = @host.instance_methods(false).include?(:listvars)
21
+ result = @host.method_defined?(:listvars, false)
45
22
  expect(result).to eq(true)
46
23
  end
47
24
 
48
25
  #-----------------
49
26
  it '<missing args> ... should abort with an error' do
50
- actual = runner { @app.start(%w(host listvars)) }
27
+ actual = runner { @app.start(%w[host listvars]) }
51
28
 
52
29
  # Check output
53
30
  desired = { aborted: true }
@@ -58,7 +35,7 @@ RSpec.describe Moose::Inventory::Cli::Host do
58
35
  #-----------------
59
36
  it '--ansible <missing args> ... should abort with an error' do
60
37
  args = @mockargs.clone
61
- args.concat(%w(--ansible host listvars)).flatten
38
+ args.push('--ansible', 'host', 'listvars').flatten
62
39
 
63
40
  actual = runner { @cli.start(args) }
64
41
 
@@ -71,13 +48,13 @@ RSpec.describe Moose::Inventory::Cli::Host do
71
48
  #------------------------
72
49
  it 'HOST ... should return a list of host variables grouped by host' do
73
50
  host_name = 'test_host'
74
- host_vars = %w(foo=bar cow=chicken)
51
+ host_vars = %w[foo=bar cow=chicken]
75
52
 
76
- tmp = runner { @app.start(%W(host add #{host_name})) }
77
- tmp = runner { @app.start(%W(host addvar #{host_name} #{host_vars[0]} #{host_vars[1]})) }
53
+ runner { @app.start(%W[host add #{host_name}]) }
54
+ runner { @app.start(%W[host addvar #{host_name} #{host_vars[0]} #{host_vars[1]}]) }
78
55
 
79
56
  actual = runner do
80
- @app.start(%W(host listvars #{host_name}))
57
+ @app.start(%W[host listvars #{host_name}])
81
58
  end
82
59
 
83
60
  # @console.out(actual, 'y')
@@ -98,65 +75,80 @@ RSpec.describe Moose::Inventory::Cli::Host do
98
75
  #------------------------
99
76
  it '--ansible HOST ... should return a list of host variables per Ansible specs' do
100
77
  host_name = 'test_host'
101
- host_vars = %w(foo=bar cow=chicken)
78
+ host_vars = %w[foo=bar cow=chicken]
102
79
 
103
- tmp = runner { @app.start(%W(host add #{host_name})) }
104
- tmp = runner { @app.start(%W(host addvar #{host_name} #{host_vars[0]} #{host_vars[1]})) }
80
+ runner { @app.start(%W[host add #{host_name}]) }
81
+ runner { @app.start(%W[host addvar #{host_name} #{host_vars[0]} #{host_vars[1]}]) }
105
82
 
106
83
  actual = runner do
107
- @cli.start(%W(--config #{@mockarg_parts[:config]} --ansible host listvars #{host_name}))
84
+ @cli.start(%W[--config #{@mockarg_parts[:config]} --ansible host listvars #{host_name}])
108
85
  end
109
86
 
110
87
  # @console.out(actual, 'y')
111
88
 
112
89
  # Check output
113
90
  meta = {}
114
- meta['hostvars'.to_sym] = {}
115
- meta['hostvars'.to_sym][host_name.to_sym] = {}
91
+ meta[:hostvars] = {}
92
+ meta[:hostvars][host_name.to_sym] = {}
116
93
 
117
94
  mock = {}
118
95
  host_vars.each do |hv|
119
96
  hv_array = hv.split('=')
120
97
  mock[hv_array[0].to_sym] = hv_array[1]
121
- meta['hostvars'.to_sym][host_name.to_sym][hv_array[0].to_sym] = hv_array[1]
98
+ meta[:hostvars][host_name.to_sym][hv_array[0].to_sym] = hv_array[1]
99
+ end
100
+ mock[:_meta] = meta
101
+
102
+ desired = {}
103
+ desired[:STDOUT] = "#{mock.to_json}\n"
104
+ expected(actual, desired)
105
+ end
106
+
107
+ #------------------------
108
+ it '--ansible HOST ... should warn when the host does not exist' do
109
+ host_name = 'missing_host'
110
+
111
+ actual = runner do
112
+ @cli.start(%W[--config #{@mockarg_parts[:config]} --ansible host listvars #{host_name}])
122
113
  end
123
- mock['_meta'.to_sym] = meta
124
114
 
125
115
  desired = {}
126
- desired[:STDOUT] = mock.to_json + "\n"
116
+ desired[:STDOUT] = "{\"_meta\":{\"hostvars\":{}}}\n"
117
+ desired[:STDERR] = "WARNING: The host #{host_name} does not exist.\n"
127
118
  expected(actual, desired)
128
119
  end
129
120
 
130
121
  #------------------------
131
122
  it '--ansible HOST ... should be an alias for Ansible\'s --host HOST' do
132
123
  host_name = 'test_host'
133
- host_vars = %w(foo=bar cow=chicken)
124
+ host_vars = %w[foo=bar cow=chicken]
134
125
 
135
- tmp = runner { @app.start(%W(host add #{host_name})) }
136
- tmp = runner { @app.start(%W(host addvar #{host_name} #{host_vars[0]} #{host_vars[1]})) }
126
+ runner { @app.start(%W[host add #{host_name}]) }
127
+ runner { @app.start(%W[host addvar #{host_name} #{host_vars[0]} #{host_vars[1]}]) }
137
128
 
138
129
  actual = runner do
139
- @cli.start(%W(--config #{@mockarg_parts[:config]} --host #{host_name}))
130
+ @cli.start(%W[--config #{@mockarg_parts[:config]} --host #{host_name}])
140
131
  end
141
132
 
142
133
  # @console.out(actual, 'y')
143
134
 
144
135
  # Check output
145
136
  meta = {}
146
- meta['hostvars'.to_sym] = {}
147
- meta['hostvars'.to_sym][host_name.to_sym] = {}
137
+ meta[:hostvars] = {}
138
+ meta[:hostvars][host_name.to_sym] = {}
148
139
 
149
140
  mock = {}
150
141
  host_vars.each do |hv|
151
142
  hv_array = hv.split('=')
152
143
  mock[hv_array[0].to_sym] = hv_array[1]
153
- meta['hostvars'.to_sym][host_name.to_sym][hv_array[0].to_sym] = hv_array[1]
144
+ meta[:hostvars][host_name.to_sym][hv_array[0].to_sym] = hv_array[1]
154
145
  end
155
- mock['_meta'.to_sym] = meta
146
+ mock[:_meta] = meta
156
147
 
157
148
  desired = {}
158
- desired[:STDOUT] = mock.to_json + "\n"
149
+ desired[:STDOUT] = "#{mock.to_json}\n"
159
150
  expected(actual, desired)
160
151
  end
161
152
  end
162
153
  end
154
+ # rubocop:enable Metrics/BlockLength
@@ -1,48 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
- # TODO: the usual respond_to? method doesn't seem to work on Thor objects.
4
5
  # Why not? For now, we'll check against instance_methods.
5
6
 
6
7
  RSpec.describe Moose::Inventory::Cli::Host do
7
8
  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
9
+ setup_cli_harness(command_class: Moose::Inventory::Cli::Host, command_ivar: :@host)
29
10
  end
30
11
 
31
12
  before(:each) do
32
- @db.reset
13
+ reset_cli_harness
33
14
  end
34
15
 
35
16
  #======================
36
17
  describe 'rm' do
37
18
  #---------------
38
19
  it 'Host.rm() should be responsive' do
39
- result = @host.instance_methods(false).include?(:rm)
20
+ result = @host.method_defined?(:rm, false)
40
21
  expect(result).to eq(true)
41
22
  end
42
23
 
43
24
  #---------------
44
25
  it '<missing argument> ... should abort with an error' do
45
- actual = runner { @app.start(%w(host rm)) }
26
+ actual = runner { @app.start(%w[host rm]) }
46
27
 
47
28
  # Check output
48
29
  desired = { aborted: true, STDERR: '', STDOUT: '' }
@@ -59,15 +40,15 @@ RSpec.describe Moose::Inventory::Cli::Host do
59
40
 
60
41
  # no items in the db
61
42
  name = 'fake'
62
- actual = runner { @app.start(%W(host rm #{name})) }
43
+ actual = runner { @app.start(%W[host rm #{name} --yes]) }
63
44
 
64
45
  desired = {}
65
46
  desired[:STDOUT] =
66
- "Remove host '#{name}':\n"\
67
- " - Retrieve host '#{name}'...\n"\
68
- " - No such host, skipping.\n"\
69
- " - OK\n"\
70
- " - All OK\n"\
47
+ "Remove host '#{name}':\n " \
48
+ "- Retrieve host '#{name}'...\n " \
49
+ "- No such host, skipping.\n " \
50
+ "- OK\n " \
51
+ "- All OK\n" \
71
52
  "Succeeded, with warnings.\n"
72
53
  desired[:STDERR] =
73
54
  "WARNING: Host '#{name}' does not exist, skipping.\n"
@@ -75,22 +56,39 @@ RSpec.describe Moose::Inventory::Cli::Host do
75
56
  expected(actual, desired)
76
57
  end
77
58
 
59
+ #---------------
60
+ it 'HOST without --yes or --dry-run should abort before deleting' do
61
+ name = 'test1'
62
+ @db.models[:host].create(name: name)
63
+
64
+ actual = runner { @app.start(%W[host rm #{name}]) }
65
+
66
+ desired = {
67
+ aborted: true,
68
+ STDOUT: '',
69
+ STDERR: "ERROR: host rm #{name} is destructive. Re-run with --yes to confirm, " \
70
+ "or use --dry-run to preview.\n"
71
+ }
72
+ expected(actual, desired)
73
+ expect(@db.models[:host].find(name: name)).not_to be_nil
74
+ end
75
+
78
76
  #---------------
79
77
  it 'HOST ... should remove a host' do
80
78
  name = 'test1'
81
79
  @db.models[:host].create(name: name)
82
80
 
83
- actual = runner { @app.start(%W(host rm #{name})) }
81
+ actual = runner { @app.start(%W[host rm #{name} --yes]) }
84
82
 
85
83
  # Check output
86
84
  desired = {}
87
85
  desired[:STDOUT] =
88
- "Remove host '#{name}':\n"\
89
- " - Retrieve host '#{name}'...\n"\
90
- " - OK\n"\
91
- " - Destroy host '#{name}'...\n"\
92
- " - OK\n"\
93
- " - All OK\n"\
86
+ "Remove host '#{name}':\n " \
87
+ "- Retrieve host '#{name}'...\n " \
88
+ "- OK\n " \
89
+ "- Destroy host '#{name}'...\n " \
90
+ "- OK\n " \
91
+ "- All OK\n" \
94
92
  "Succeeded.\n"
95
93
 
96
94
  expected(actual, desired)
@@ -100,28 +98,48 @@ RSpec.describe Moose::Inventory::Cli::Host do
100
98
  expect(host).to be_nil
101
99
  end
102
100
 
101
+ #---------------
102
+ it 'HOST --dry-run should show planned removal without deleting the host' do
103
+ name = 'test1'
104
+ @db.models[:host].create(name: name)
105
+
106
+ actual = runner { @app.start(%W[host rm #{name} --dry-run]) }
107
+
108
+ desired = {}
109
+ desired[:STDOUT] =
110
+ "Remove host '#{name}':\n " \
111
+ "- Retrieve host '#{name}'...\n " \
112
+ "- OK\n " \
113
+ "- Destroy host '#{name}'...\n " \
114
+ "- OK\n " \
115
+ "- All OK\n" \
116
+ "Dry run complete. No changes applied.\n" \
117
+ "Succeeded.\n"
118
+
119
+ expected(actual, desired)
120
+ expect(@db.models[:host].find(name: name)).not_to be_nil
121
+ end
103
122
  #---------------
104
123
  it 'HOST1 HOST2 ... should remove multiple hosts' do
105
- names = %w(host1 host2 host3)
124
+ names = %w[host1 host2 host3]
106
125
  names.each do |name|
107
126
  @db.models[:host].create(name: name)
108
127
  end
109
128
 
110
- actual = runner { @app.start(%w(host rm) + names) }
129
+ actual = runner { @app.start(%w[host rm --yes] + names) }
111
130
 
112
131
  # Check output
113
132
  desired = { aborted: false, STDERR: '', STDOUT: '' }
114
133
  names.each do |name|
115
134
  desired[:STDOUT] = desired[:STDOUT] +
116
- "Remove host '#{name}':\n"\
117
- " - Retrieve host '#{name}'...\n"\
118
- " - OK\n"\
119
- " - Destroy host '#{name}'...\n"\
120
- " - OK\n"\
121
- " - All OK\n"
135
+ "Remove host '#{name}':\n " \
136
+ "- Retrieve host '#{name}'...\n " \
137
+ "- OK\n " \
138
+ "- Destroy host '#{name}'...\n " \
139
+ "- OK\n " \
140
+ "- All OK\n"
122
141
  end
123
- desired[:STDOUT] = desired[:STDOUT] +
124
- "Succeeded.\n"
142
+ desired[:STDOUT] = "#{desired[:STDOUT]}Succeeded.\n"
125
143
  expected(actual, desired)
126
144
 
127
145
  # Check db