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,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative '../inventory_context'
5
+ require_relative '../operations/add_variables'
3
6
 
4
7
  module Moose
5
8
  module Inventory
@@ -10,61 +13,38 @@ module Moose
10
13
  #==========================
11
14
  desc 'addvar NAME VARNAME=VALUE',
12
15
  'Add a variable VARNAME with value VALUE to the group NAME'
16
+ option :dry_run, type: :boolean
17
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
13
18
  def addvar(*args)
14
- if args.length < 2
15
- abort('ERROR: Wrong number of arguments, '\
16
- "#{args.length} for 2 or more.")
17
- end
18
- # Convenience
19
- db = Moose::Inventory::DB
20
- fmt = Moose::Inventory::Cli::Formatter
19
+ abort_if_missing_args(args, 2, '2 or more')
20
+ validate_machine_plan_request!
21
21
 
22
- # Arguments
23
22
  name = args[0].downcase
24
23
  vars = args.slice(1, args.length - 1).uniq
24
+ operation = build_operation(Moose::Inventory::Operations::AddVariables,
25
+ entity_type: :group,
26
+ emitter: machine_plan_emitter(group_addvar_emitter(name, vars)))
25
27
 
26
- # Transaction
27
- db.transaction do # Transaction start
28
- puts "Add variables '#{vars.join(',')}' to group '#{name}':"
29
- fmt.puts 2, "- retrieve group '#{name}'..."
30
- group = db.models[:group].find(name: name)
31
- if group.nil?
32
- fail db.exceptions[:moose],
33
- "The group '#{name}' does not exist."
34
- end
35
- fmt.puts 4, '- OK'
28
+ result = db.transaction do
29
+ operation.call(name: name, vars: vars, dry_run: options[:dry_run])
30
+ end
36
31
 
37
- groupvars_ds = group.groupvars_dataset
38
- vars.each do |v|
39
- fmt.puts 2, "- add variable '#{v}'..."
40
- vararray = v.split('=')
32
+ return if machine_plan_output_rendered?(result, command: 'group addvar')
41
33
 
42
- if v.start_with?('=') || v.end_with?('=') || vararray.length != 2
43
- fail db.exceptions[:moose],
44
- "Incorrect format in '{#{v}}'. Expected 'key=value'."
45
- end
34
+ record_audit({ command: 'group addvar', action: 'add_variable', entity_type: 'group',
35
+ entity_names: name }, result: result, dry_run: options[:dry_run])
36
+ print_success_summary
37
+ end
46
38
 
47
- # Check against existing associations
48
- groupvar = groupvars_ds[name: vararray[0]]
49
- if !groupvar.nil?
50
- unless groupvar[:value] == vararray[1]
51
- fmt.puts 4, '- already exists, applying as an update...'
52
- update = db.models[:groupvar].find(id: groupvar[:id])
53
- update[:value] = vararray[1]
54
- update.save
55
- end
56
- else
57
- # groupvar doesn't exist, so create and associate
58
- groupvar = db.models[:groupvar].create(name: vararray[0],
59
- value: vararray[1])
60
- group.add_groupvar(groupvar)
61
- end
62
- fmt.puts 4, '- OK'
63
- end
64
- fmt.puts 2, '- all OK'
65
- end # Transaction end
39
+ private
66
40
 
67
- puts 'Succeeded.'
41
+ def group_addvar_emitter(name, vars)
42
+ variable_operation_emitter(
43
+ action: :add,
44
+ entity_label: 'group',
45
+ entity_name: name,
46
+ variables_label: vars.join(',')
47
+ )
68
48
  end
69
49
  end
70
50
  end
@@ -1,54 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative '../inventory_context'
5
+ require_relative '../operations/query_inventory'
3
6
 
4
7
  module Moose
5
8
  module Inventory
6
9
  module Cli
7
- ##
8
10
  # Implementation of the "group get" method of the CLI
9
11
  class Group
10
12
  desc 'get GROUP_1 [GROUP_2 ...]', 'Get groups GROUP_n from the inventory'
11
- def get(*argv) # rubocop:disable Metrics/AbcSize
12
- if argv.empty?
13
- abort('ERROR: Wrong number of arguments, '\
14
- "#{argv.length} for 1 or more")
15
- end
16
-
17
- # Convenience
18
- db = Moose::Inventory::DB
19
- fmt = Moose::Inventory::Cli::Formatter
20
-
21
- # Arguments
22
- names = argv.uniq.map(&:downcase)
23
-
24
- # Process
25
- results = {}
26
- names.each do |name|
27
- group = db.models[:group].find(name: name)
28
-
29
- next if group.nil?
30
- hosts = group.hosts_dataset.map(:name)
31
-
32
- children = group.children_dataset.map(:name)
33
-
34
- groupvars = {}
35
- group.groupvars_dataset.each do |gv|
36
- groupvars[gv[:name].to_sym] = gv[:value]
37
- end
38
-
39
- results[group[:name].to_sym] = {}
40
- results[group[:name].to_sym][:hosts] = hosts unless hosts.empty?
41
-
42
- unless children.empty?
43
- results[group[:name].to_sym][:children] = children
44
- end
45
-
46
- unless groupvars.empty?
47
- results[group[:name].to_sym][:groupvars] = groupvars
48
- end
49
- end
13
+ def get(*argv)
14
+ abort("ERROR: Wrong number of arguments, #{argv.length} for 1 or more") if argv.empty?
50
15
 
51
- fmt.dump(results)
16
+ names = normalize_names(argv)
17
+ fmt.dump(inventory_query.get_groups(names: names), output_format)
52
18
  end
53
19
  end
54
20
  end
@@ -1,52 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative '../inventory_context'
5
+ require_relative '../operations/query_inventory'
3
6
 
4
7
  module Moose
5
8
  module Inventory
6
9
  module Cli
7
- ##
8
10
  # Implementation of the "group list" method of the CLI
9
11
  class Group
10
- #==========================
11
12
  desc 'list',
12
13
  'List the groups, together with any associated hosts and groupvars'
13
- def list # rubocop:disable Metrics/AbcSize
14
- # Convenience
15
- db = Moose::Inventory::DB
16
- confopts = Moose::Inventory::Config._confopts
17
-
18
- # Process
19
- results = {}
20
- db.models[:group].all.each do |group|
21
- hosts = group.hosts_dataset.map(:name)
22
-
23
- # Hide the automatic ungrouped group, if it's empty
24
- next if group[:name] == 'ungrouped' && hosts.empty?
25
-
26
- children = group.children_dataset.map(:name)
27
-
28
- groupvars = {}
29
- group.groupvars_dataset.each do |gv|
30
- groupvars[gv[:name].to_sym] = gv[:value]
31
- end
32
-
33
- results[group[:name].to_sym] = {}
34
- unless hosts.empty? && (confopts[:ansible] != true)
35
- results[group[:name].to_sym][:hosts] = hosts
36
- end
37
-
38
- unless children.empty?
39
- results[group[:name].to_sym][:children] = children
40
- end
41
-
42
- next if groupvars.empty?
43
- if confopts[:ansible] == true
44
- results[group[:name].to_sym][:vars] = groupvars
45
- else
46
- results[group[:name].to_sym][:groupvars] = groupvars
47
- end
48
- end
49
- Formatter.out(results)
14
+ def list
15
+ results = inventory_query.list_groups(ansible: ansible_mode?)
16
+ fmt.dump(results, output_format)
50
17
  end
51
18
  end
52
19
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
 
4
- require_relative './formatter.rb'
5
- require_relative '../db/exceptions.rb'
6
+ require_relative '../inventory_context'
7
+ require_relative '../operations/query_inventory'
6
8
 
7
9
  module Moose
8
10
  module Inventory
@@ -13,61 +15,13 @@ module Moose
13
15
  #==========================
14
16
  desc 'listvar', 'List all variables associated with the group'
15
17
  def listvars(*argv)
16
- # Convenience
17
- confopts = Moose::Inventory::Config._confopts
18
-
19
- # Note, the Ansible spects don't call for a "--group GROUPNAME" method.
20
- # So, strictly, there is no Ansible compatibility for this method.
21
- # Instead, the Ansible compatibility included herein is for consistency
22
- # with the "hosts listvars" method, which services the Ansible
23
- # "--host HOSTNAME" specs.
24
-
25
- # sanity
26
- if confopts[:ansible] == true
27
- if argv.length != 1
28
- abort('ERROR: Wrong number of arguments for Ansible mode, '\
29
- "#{args.length} for 1.")
30
- end
31
- else
32
- if argv.empty?
33
- abort('ERROR: Wrong number of arguments, '\
34
- "#{args.length} for 1 or more.")
35
- end
36
- end
37
-
38
- # Convenience
39
- db = Moose::Inventory::DB
40
- fmt = Moose::Inventory::Cli::Formatter
41
-
42
- # Arguments
43
- names = argv.uniq.map(&:downcase)
18
+ validate_listvars_args(argv)
44
19
 
45
- # process
46
- results = {}
20
+ names = normalize_names(argv)
21
+ results = inventory_query.list_group_vars(names: names, ansible: ansible_mode?)
22
+ warn_if_missing_ansible_listvars_entity(:group, names.first)
47
23
 
48
- if confopts[:ansible] == true
49
- # This is the implementation per Ansible specs
50
- name = names.first
51
- group = db.models[:group].find(name: name)
52
- if group.nil?
53
- fmt.warn "The Group #{name} does not exist."
54
- else
55
- group.groupvars_dataset.each do |gv|
56
- results[gv[:name].to_sym] = gv[:value]
57
- end
58
- end
59
- else
60
- # This our more flexible implementation
61
- names.each do |name|
62
- group = db.models[:group].find(name: name)
63
- next if group.nil?
64
- results[name.to_sym] = {}
65
- group.groupvars_dataset.each do |gv|
66
- results[name.to_sym][gv[:name].to_sym] = gv[:value]
67
- end
68
- end
69
- end
70
- fmt.dump(results)
24
+ fmt.dump(results, output_format)
71
25
  end
72
26
  end
73
27
  end
@@ -16,10 +16,14 @@ module Moose
16
16
  type: :boolean,
17
17
  default: false,
18
18
  desc: 'Also delete child groups that become orphaned'
19
+ option :dry_run, type: :boolean
20
+ option :yes, type: :boolean, desc: 'Confirm destructive removal without prompting'
21
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
19
22
  desc 'rm NAME',
20
23
  'Remove a group NAME from the inventory'
21
24
  def rm(*argv)
22
25
  abort_if_missing_args(argv, 1, '1 or more')
26
+ validate_machine_plan_request!
23
27
 
24
28
  names = normalize_names(argv)
25
29
 
@@ -27,25 +31,22 @@ module Moose
27
31
  names,
28
32
  "Cannot manually manipulate the automatic group 'ungrouped'\n"
29
33
  )
34
+ confirm_destructive_action!("group rm #{names.join(',')}")
30
35
 
31
36
  result = remove_groups(names)
32
-
33
- if result.warning_count.zero?
34
- puts 'Succeeded.'
35
- else
36
- puts 'Succeeded, with warnings.'
37
- end
37
+ record_audit({ command: 'group rm', action: 'remove', entity_type: 'group',
38
+ entity_names: names }, result: result, dry_run: options[:dry_run])
39
+ print_warning_summary(result) unless machine_plan_output_requested?
38
40
  end
39
41
 
40
42
  private
41
43
 
42
44
  def remove_groups(names)
43
- context = Moose::Inventory::InventoryContext.new(db: db)
44
- operation = Moose::Inventory::Operations::RemoveGroups.new(context: context)
45
+ operation = build_operation(Moose::Inventory::Operations::RemoveGroups)
45
46
 
46
47
  db.transaction do
47
- result = operation.call(names: names, recursive: options[:recursive])
48
- render_group_rm_events(result.events)
48
+ result = operation.call(names: names, recursive: options[:recursive], dry_run: options[:dry_run])
49
+ machine_plan_output_rendered?(result, command: 'group rm') || render_group_rm_events(result.events)
49
50
  return result
50
51
  end
51
52
  end
@@ -59,6 +60,7 @@ module Moose
59
60
 
60
61
  render_group_rm_warning(payload) if event.type == :group_missing
61
62
  return render_group_rm_progress(event.type, payload) if group_rm_progress?(event.type)
63
+ return puts 'Dry run complete. No changes applied.' if event.type == :dry_run_summary
62
64
 
63
65
  render_group_rm_status(event.type, payload)
64
66
  end
@@ -19,107 +19,51 @@ module Moose
19
19
  desc: 'Delete child groups that become orphaned'
20
20
  desc 'rmchild PARENTGROUP CHILDGROUP_1 [CHILDGROUP_2 ... ]',
21
21
  'Dissociate one or more child-groups CHILDGROUP_n from PARENTGROUP'
22
+ option :dry_run, type: :boolean
23
+ option :yes, type: :boolean, desc: 'Confirm destructive dissociation without prompting'
24
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
22
25
  def rmchild(*argv)
23
26
  abort_if_missing_args(argv, 2, '2 or more')
27
+ validate_machine_plan_request!
24
28
 
25
29
  pname = argv[0].downcase
26
30
  cnames = normalize_names(argv.slice(1, argv.length - 1))
27
31
 
28
32
  abort_if_automatic_group([pname] + cnames)
33
+ confirm_destructive_action!("group rmchild #{pname} #{cnames.join(',')}")
29
34
 
30
35
  result = remove_children_from_group(pname, cnames)
36
+ return if machine_plan_output_rendered?(result, command: 'group rmchild')
31
37
 
32
- if result.warning_count.zero?
33
- puts 'Succeeded.'
34
- else
35
- puts 'Succeeded, with warnings.'
36
- end
38
+ record_audit({ command: 'group rmchild', action: 'dissociate_child', entity_type: 'group',
39
+ entity_names: pname }, result: result, dry_run: options[:dry_run])
40
+ print_warning_summary(result)
37
41
  end
38
42
 
39
43
  private
40
44
 
41
45
  def remove_children_from_group(parent_name, child_names)
42
- context = Moose::Inventory::InventoryContext.new(db: db)
43
- operation = Moose::Inventory::Operations::GroupChildRelations.new(context: context)
44
-
45
- begin
46
- db.transaction do
47
- puts "Dissociate parent group '#{parent_name}' from child group(s) '#{child_names.join(',')}':"
48
- parent_group = fetch_existing_group_for_rmchild(context, parent_name)
49
- result = operation.remove_children(
50
- parent_group: parent_group,
51
- parent_name: parent_name,
52
- child_names: child_names,
53
- delete_orphans: options[:delete_orphans]
54
- )
55
- render_rmchild_events(result.events)
56
- fmt.puts 2, '- all OK'
57
- return result
58
- end
59
- rescue db.exceptions[:moose] => e
60
- abort("ERROR: #{e}")
46
+ operation = build_operation(Moose::Inventory::Operations::GroupChildRelations)
47
+ run_group_relation_transaction(
48
+ heading: "Dissociate parent group '#{parent_name}' from child group(s) '#{child_names.join(',')}':",
49
+ on_error: method(:exception_to_s)
50
+ ) do
51
+ parent_group = fetch_existing_group_or_abort(parent_name)
52
+ result = operation.remove_children(
53
+ parent_group: parent_group,
54
+ parent_name: parent_name,
55
+ child_names: child_names,
56
+ delete_orphans: options[:delete_orphans],
57
+ dry_run: options[:dry_run]
58
+ )
59
+ render_rmchild_events(result.events) unless machine_plan_output_requested?
60
+ result
61
61
  end
62
62
  end
63
63
 
64
- def fetch_existing_group_for_rmchild(context, name)
65
- fmt.puts 2, "- retrieve group '#{name}'..."
66
- group = context.find_group(name)
67
- abort("ERROR: The group '#{name}' does not exist.") if group.nil?
68
-
69
- fmt.puts 4, '- OK'
70
- group
71
- end
72
-
73
64
  def render_rmchild_events(events)
74
- events.each { |event| render_rmchild_event(event) }
75
- end
76
-
77
- def render_rmchild_event(event)
78
- payload = event.payload
79
-
80
- return render_rmchild_warning(payload) if event.type == :child_association_missing
81
- return render_rmchild_missing(payload) if event.type == :missing_skipping
82
- return render_rmchild_progress(event.type, payload) if rmchild_progress_event?(event.type)
83
-
84
- render_rmchild_status(event.type, payload)
85
- end
86
-
87
- def rmchild_progress_event?(type)
88
- %i[
89
- removing_child_association
90
- recursively_delete_orphaned_group
91
- removing_recursive_child_association
92
- ].include?(type)
93
- end
94
-
95
- def render_rmchild_progress(type, payload)
96
- case type
97
- when :removing_child_association
98
- fmt.puts 2, "- remove association {group:#{payload[:parent]} <-> group:#{payload[:child]}}..."
99
- when :recursively_delete_orphaned_group
100
- fmt.puts 2, "- Recursively delete orphaned group '#{payload[:name]}'..."
101
- when :removing_recursive_child_association
102
- fmt.puts 4, "- Remove association {group:#{payload[:parent]} <-> group:#{payload[:child]}}..."
103
- end
104
- end
105
-
106
- def render_rmchild_status(type, payload)
107
- case type
108
- when :adding_automatic_group_to_host
109
- fmt.puts payload[:indent], "- Adding automatic association {group:ungrouped <-> host:#{payload[:host]}}..."
110
- when :destroying_group
111
- fmt.puts payload[:indent], "- Destroy group '#{payload[:name]}'..."
112
- when :ok
113
- fmt.puts payload[:indent], '- OK'
114
- end
115
- end
116
-
117
- def render_rmchild_warning(payload)
118
- fmt.warn "Association {group:#{payload[:parent]} <-> group:#{payload[:child]}} does not exist, skipping.\n"
119
- end
120
-
121
- def render_rmchild_missing(payload)
122
- fmt.puts payload[:indent], "- doesn't exist, skipping."
65
+ emitter = rmchild_emitter
66
+ events.each { |event| emitter.call(event) }
123
67
  end
124
68
  end
125
69
  end
@@ -14,78 +14,43 @@ module Moose
14
14
  #==========================
15
15
  desc 'rmhost GROUPNAME HOSTNAME_1 [HOSTNAME_2 ...]',
16
16
  'Dissociate the hosts HOSTNAME_n from the group NAME'
17
+ option :dry_run, type: :boolean
18
+ option :yes, type: :boolean, desc: 'Confirm destructive dissociation without prompting'
19
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
17
20
  def rmhost(*args)
18
21
  abort_if_missing_args(args, 2, '2 or more')
22
+ validate_machine_plan_request!
19
23
 
20
24
  name = args[0].downcase
21
25
  hosts = normalize_names(args.slice(1, args.length - 1))
22
26
 
23
27
  abort_if_automatic_group([name])
28
+ confirm_destructive_action!("group rmhost #{name} #{hosts.join(',')}")
24
29
 
25
30
  result = remove_hosts_from_group(name, hosts)
31
+ return if machine_plan_output_rendered?(result, command: 'group rmhost')
26
32
 
27
- if result.warning_count.zero?
28
- puts 'Succeeded.'
29
- else
30
- puts 'Succeeded, with warnings.'
31
- end
33
+ record_audit({ command: 'group rmhost', action: 'dissociate', entity_type: 'group',
34
+ entity_names: name }, result: result, dry_run: options[:dry_run])
35
+ print_warning_summary(result)
32
36
  end
33
37
 
34
38
  private
35
39
 
36
40
  def remove_hosts_from_group(name, hosts)
37
- context = Moose::Inventory::InventoryContext.new(db: db)
38
- operation = Moose::Inventory::Operations::RemoveAssociations.new(context: context)
39
-
40
- begin
41
- db.transaction do
42
- puts "Dissociate group '#{name}' from host(s) '#{hosts.join(',')}':"
43
- group = fetch_existing_group_for_rmhost(context, name)
44
- result = operation.group_from_hosts(group: group, group_name: name, host_names: hosts)
45
- render_group_rmhost_events(result.events)
46
- fmt.puts 2, '- all OK'
47
- return result
48
- end
49
- rescue db.exceptions[:moose] => e
50
- abort("ERROR: #{e.message}")
41
+ operation = build_operation(Moose::Inventory::Operations::RemoveAssociations)
42
+ run_group_relation_transaction(heading: "Dissociate group '#{name}' from host(s) '#{hosts.join(',')}':") do
43
+ group = fetch_existing_group_or_abort(name)
44
+ result = operation.group_from_hosts(group: group, group_name: name, host_names: hosts,
45
+ dry_run: options[:dry_run])
46
+ render_group_rmhost_events(result.events) unless machine_plan_output_requested?
47
+ result
51
48
  end
52
49
  end
53
50
 
54
- def fetch_existing_group_for_rmhost(context, name)
55
- fmt.puts 2, "- retrieve group '#{name}'..."
56
- group = context.find_group(name)
57
- abort("ERROR: The group '#{name}' does not exist.") if group.nil?
58
-
59
- fmt.puts 4, '- OK'
60
- group
61
- end
62
-
63
51
  def render_group_rmhost_events(events)
64
- events.each { |event| render_group_rmhost_event(event) }
65
- end
66
-
67
- def render_group_rmhost_event(event)
68
- payload = event.payload
69
-
70
- return render_group_rmhost_warning(payload) if event.type == :group_host_association_missing
71
- return render_group_rmhost_missing(payload) if event.type == :missing_skipping
72
-
73
- case event.type
74
- when :removing_group_host_association
75
- fmt.puts 2, "- remove association {group:#{payload[:group]} <-> host:#{payload[:host]}}..."
76
- when :adding_automatic_group
77
- fmt.puts 2, "- add automatic association {group:ungrouped <-> host:#{payload[:host]}}..."
78
- when :ok
79
- fmt.puts payload[:indent], '- OK'
80
- end
81
- end
82
-
83
- def render_group_rmhost_warning(payload)
84
- fmt.warn "Association {group:#{payload[:group]} <-> host:#{payload[:host]}} doesn't exist, skipping.\n"
85
- end
86
-
87
- def render_group_rmhost_missing(payload)
88
- fmt.puts payload[:indent], "- doesn't exist, skipping."
52
+ emitter = host_group_association_removal_emitter(perspective: :group)
53
+ events.each { |event| emitter.call(event) }
89
54
  end
90
55
  end
91
56
  end