moose-inventory 1.0.9 → 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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +15 -1
  3. data/.github/workflows/release.yml +60 -0
  4. data/.gitignore +2 -1
  5. data/.gitleaks.toml +9 -0
  6. data/.rubocop.yml +49 -0
  7. data/BACKLOG.md +752 -24
  8. data/Gemfile +2 -0
  9. data/Gemfile.lock +36 -1
  10. data/README.md +340 -44
  11. data/Rakefile +2 -0
  12. data/bin/moose-inventory +2 -1
  13. data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
  14. data/docs/compatibility/cli-output-compatibility.md +76 -0
  15. data/docs/governance/approval-register.md +37 -0
  16. data/docs/maintenance/database-backup-restore-guidance.md +162 -0
  17. data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
  18. data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
  19. data/docs/product/product-brief.md +161 -0
  20. data/docs/product/requirements-baseline.md +477 -0
  21. data/docs/qa/qa-documentation-and-release-gates.md +283 -0
  22. data/docs/release/package-provenance-hardening.md +126 -0
  23. data/docs/release/publishing.md +54 -50
  24. data/docs/release/release-environment-protection.md +70 -0
  25. data/docs/release/release-readiness.md +37 -4
  26. data/docs/security/accepted-risk-register.md +84 -0
  27. data/docs/security/security-privacy-process.md +287 -0
  28. data/docs/security-audit-2026-05-26-rerun.md +75 -0
  29. data/docs/security-audit-2026-05-26.md +63 -0
  30. data/docs/ux/cli-workflow-notes.md +287 -0
  31. data/examples/ansible/ansible.cfg +3 -0
  32. data/examples/ansible/inventory/moose_inventory.yml +5 -0
  33. data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
  34. data/examples/ci/README.md +16 -0
  35. data/examples/ci/github-actions/inventory-review.yml +38 -0
  36. data/examples/ci/inventory/example-snapshot.yml +19 -0
  37. data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
  38. data/lib/moose_inventory/cli/application.rb +133 -5
  39. data/lib/moose_inventory/cli/association_rendering.rb +74 -0
  40. data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
  41. data/lib/moose_inventory/cli/audit.rb +62 -0
  42. data/lib/moose_inventory/cli/audit_recording.rb +40 -0
  43. data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
  44. data/lib/moose_inventory/cli/console.rb +135 -0
  45. data/lib/moose_inventory/cli/db.rb +64 -0
  46. data/lib/moose_inventory/cli/factory.rb +28 -0
  47. data/lib/moose_inventory/cli/formatter.rb +8 -12
  48. data/lib/moose_inventory/cli/group.rb +7 -1
  49. data/lib/moose_inventory/cli/group_add.rb +91 -73
  50. data/lib/moose_inventory/cli/group_addchild.rb +41 -66
  51. data/lib/moose_inventory/cli/group_addhost.rb +33 -71
  52. data/lib/moose_inventory/cli/group_addvar.rb +27 -47
  53. data/lib/moose_inventory/cli/group_get.rb +8 -42
  54. data/lib/moose_inventory/cli/group_list.rb +7 -40
  55. data/lib/moose_inventory/cli/group_listvars.rb +9 -55
  56. data/lib/moose_inventory/cli/group_rm.rb +105 -73
  57. data/lib/moose_inventory/cli/group_rmchild.rb +47 -57
  58. data/lib/moose_inventory/cli/group_rmhost.rb +34 -61
  59. data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
  60. data/lib/moose_inventory/cli/group_tags.rb +33 -0
  61. data/lib/moose_inventory/cli/helpers.rb +143 -0
  62. data/lib/moose_inventory/cli/host.rb +8 -2
  63. data/lib/moose_inventory/cli/host_add.rb +91 -66
  64. data/lib/moose_inventory/cli/host_addgroup.rb +39 -66
  65. data/lib/moose_inventory/cli/host_addvar.rb +28 -52
  66. data/lib/moose_inventory/cli/host_get.rb +9 -37
  67. data/lib/moose_inventory/cli/host_list.rb +24 -21
  68. data/lib/moose_inventory/cli/host_listvars.rb +9 -62
  69. data/lib/moose_inventory/cli/host_rm.rb +60 -42
  70. data/lib/moose_inventory/cli/host_rmgroup.rb +39 -55
  71. data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
  72. data/lib/moose_inventory/cli/host_tags.rb +33 -0
  73. data/lib/moose_inventory/cli/listvars_support.rb +55 -0
  74. data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
  75. data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
  76. data/lib/moose_inventory/cli/tag_support.rb +97 -0
  77. data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
  78. data/lib/moose_inventory/config/config.rb +185 -108
  79. data/lib/moose_inventory/db/db.rb +188 -193
  80. data/lib/moose_inventory/db/exceptions.rb +6 -3
  81. data/lib/moose_inventory/db/models.rb +16 -0
  82. data/lib/moose_inventory/db/schema_migrations.rb +248 -0
  83. data/lib/moose_inventory/inventory_context.rb +116 -0
  84. data/lib/moose_inventory/operations/add_associations.rb +131 -0
  85. data/lib/moose_inventory/operations/add_groups.rb +123 -0
  86. data/lib/moose_inventory/operations/add_hosts.rb +123 -0
  87. data/lib/moose_inventory/operations/add_variables.rb +77 -0
  88. data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
  89. data/lib/moose_inventory/operations/group_child_relations.rb +125 -0
  90. data/lib/moose_inventory/operations/group_cleanup.rb +70 -0
  91. data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
  92. data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
  93. data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
  94. data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
  95. data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
  96. data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
  97. data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
  98. data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
  99. data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
  100. data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
  101. data/lib/moose_inventory/operations/query_inventory.rb +47 -0
  102. data/lib/moose_inventory/operations/remove_associations.rb +113 -0
  103. data/lib/moose_inventory/operations/remove_groups.rb +79 -0
  104. data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
  105. data/lib/moose_inventory/operations/remove_variables.rb +67 -0
  106. data/lib/moose_inventory/runtime_options.rb +31 -0
  107. data/lib/moose_inventory/version.rb +3 -1
  108. data/lib/moose_inventory.rb +10 -7
  109. data/moose-inventory.gemspec +22 -35
  110. data/scripts/check.sh +3 -0
  111. data/scripts/ci/check_generated_artifacts.sh +41 -0
  112. data/scripts/ci/check_permissions.sh +5 -0
  113. data/scripts/ci/check_rubocop.sh +33 -0
  114. data/scripts/ci/check_secrets.sh +26 -0
  115. data/scripts/ci/check_security.sh +18 -0
  116. data/scripts/ci/install_security_tools.sh +47 -0
  117. data/scripts/files.rb +5 -4
  118. data/scripts/install_dependencies.sh +2 -0
  119. data/spec/examples/ci_examples_spec.rb +37 -0
  120. data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
  121. data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
  122. data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
  123. data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
  124. data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
  125. data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
  126. data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
  127. data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
  128. data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
  129. data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
  130. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
  131. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
  132. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
  133. data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
  134. data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
  135. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
  136. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +165 -85
  137. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +100 -30
  138. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
  139. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
  140. data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
  141. data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
  142. data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
  143. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
  144. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
  145. data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
  146. data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
  147. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
  148. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
  149. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
  150. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
  151. data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
  152. data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
  153. data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
  154. data/spec/lib/moose_inventory/db/db_spec.rb +551 -29
  155. data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
  156. data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
  157. data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
  158. data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
  159. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +111 -0
  160. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +80 -0
  161. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +82 -0
  162. data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
  163. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +122 -0
  164. data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
  165. data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
  166. data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
  167. data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
  168. data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
  169. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +113 -0
  170. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +78 -0
  171. data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
  172. data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
  173. data/spec/shared/shared_config_setup.rb +4 -3
  174. data/spec/spec_helper.rb +50 -40
  175. data/spec/support/cli_harness.rb +33 -0
  176. metadata +163 -35
@@ -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
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative 'formatter'
5
+ require_relative '../inventory_context'
6
+ require_relative '../operations/remove_groups'
3
7
 
4
8
  module Moose
5
9
  module Inventory
@@ -8,82 +12,110 @@ module Moose
8
12
  # Implementation of "group rm" methods of the CLI
9
13
  class Group
10
14
  #==========================
15
+ option :recursive,
16
+ type: :boolean,
17
+ default: false,
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'
11
22
  desc 'rm NAME',
12
23
  'Remove a group NAME from the inventory'
13
- def rm(*argv) # rubocop:disable Metrics/AbcSize
14
- #
15
- # Sanity
16
- if argv.empty?
17
- abort('ERROR: Wrong number of arguments, '\
18
- "#{argv.length} for 1 or more.")
19
- end
20
-
21
- # Convenience
22
- db = Moose::Inventory::DB
23
- fmt = Moose::Inventory::Cli::Formatter
24
-
25
- # Arguments
26
- names = argv.uniq.map(&:downcase)
27
-
28
- # sanity
29
- if names.include?('ungrouped')
30
- abort("Cannot manually manipulate the automatic group 'ungrouped'\n")
24
+ def rm(*argv)
25
+ abort_if_missing_args(argv, 1, '1 or more')
26
+ validate_machine_plan_request!
27
+
28
+ names = normalize_names(argv)
29
+
30
+ abort_if_automatic_group(
31
+ names,
32
+ "Cannot manually manipulate the automatic group 'ungrouped'\n"
33
+ )
34
+ confirm_destructive_action!("group rm #{names.join(',')}")
35
+
36
+ result = remove_groups(names)
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?
40
+ end
41
+
42
+ private
43
+
44
+ def remove_groups(names)
45
+ operation = build_operation(Moose::Inventory::Operations::RemoveGroups)
46
+
47
+ db.transaction do
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)
50
+ return result
51
+ end
52
+ end
53
+
54
+ def render_group_rm_events(events)
55
+ events.each { |event| render_group_rm_event(event) }
56
+ end
57
+
58
+ def render_group_rm_event(event)
59
+ payload = event.payload
60
+
61
+ render_group_rm_warning(payload) if event.type == :group_missing
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
64
+
65
+ render_group_rm_status(event.type, payload)
66
+ end
67
+
68
+ def group_rm_progress?(type)
69
+ %i[group_started retrieving_group removing_parent_association removing_child_association].include?(type)
70
+ end
71
+
72
+ def render_group_rm_warning(payload)
73
+ fmt.warn "Group '#{payload[:name]}' does not exist, skipping.\n"
74
+ end
75
+
76
+ def render_group_rm_progress(type, payload)
77
+ case type
78
+ when :group_started
79
+ puts "Remove group '#{payload[:name]}':"
80
+ when :retrieving_group
81
+ fmt.puts 2, "- Retrieve group '#{payload[:name]}'..."
82
+ when :removing_parent_association, :removing_child_association
83
+ fmt.puts 2, "- Remove association {group:#{payload[:group]} <-> group:#{payload[:related_group]}}..."
31
84
  end
85
+ end
86
+
87
+ def render_group_rm_status(type, payload)
88
+ return render_group_rm_secondary_status(type, payload) if group_rm_secondary_status?(type)
89
+
90
+ case type
91
+ when :group_missing
92
+ fmt.puts 4, '- No such group, skipping.'
93
+ when :destroying_group
94
+ fmt.puts payload[:indent], "- Destroy group '#{payload[:name]}'..."
95
+ when :group_complete
96
+ fmt.puts 2, '- All OK'
97
+ end
98
+ end
99
+
100
+ def group_rm_secondary_status?(type)
101
+ %i[
102
+ recursively_delete_orphaned_group
103
+ removing_recursive_child_association
104
+ adding_automatic_group_to_host
105
+ ok
106
+ ].include?(type)
107
+ end
32
108
 
33
- # Transaction
34
- warn_count = 0
35
- db.transaction do # Transaction start
36
- names.each do |name|
37
- puts "Remove group '#{name}':"
38
- fmt.puts 2, "- Retrieve group '#{name}'..."
39
- group = db.models[:group].find(name: name)
40
- if group.nil?
41
- warn_count += 1
42
- fmt.warn "Group '#{name}' does not exist, skipping.\n"
43
- fmt.puts 4, '- No such group, skipping.'
44
- end
45
- fmt.puts 4, '- OK'
46
- unless group.nil?
47
- # Dissociate from any parent groups
48
- pgroups_ds = group.parents_dataset
49
- pgroups_ds.each do |parent|
50
- fmt.puts 2, "- Remove association {group:#{name} <-> group:#{parent.name}}..."
51
- parent.remove_child(group)
52
- fmt.puts 4, '- OK'
53
- end
54
-
55
- # Dissociate from any child groups
56
- groups_ds = group.children_dataset
57
- groups_ds.each do |child|
58
- fmt.puts 2, "- Remove association {group:#{name} <-> group:#{child.name}}..."
59
- group.remove_child(child)
60
- # TODO: Should we propagate the delete to orphaned children?
61
- fmt.puts 4, '- OK'
62
- end
63
-
64
- # Handle automatic group for any associated hosts
65
- hosts_ds = group.hosts_dataset
66
- hosts_ds.each do |host|
67
- host_groups_ds = host.groups_dataset
68
- next unless host_groups_ds.count == 1 # We're the only group
69
- fmt.puts 2, "- Adding automatic association {group:ungrouped <-> host:#{host[:name]}}..."
70
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
71
- host.add_group(ungrouped)
72
- fmt.puts 4, '- OK'
73
- end
74
- # Remove the group
75
- fmt.puts 2, "- Destroy group '#{name}'..."
76
- group.remove_all_hosts
77
- group.destroy
78
- fmt.puts 4, '- OK'
79
- end
80
- fmt.puts 2, '- All OK'
81
- end
82
- end # Transaction end
83
- if warn_count == 0
84
- puts 'Succeeded.'
85
- else
86
- puts 'Succeeded, with warnings.'
109
+ def render_group_rm_secondary_status(type, payload)
110
+ case type
111
+ when :recursively_delete_orphaned_group
112
+ fmt.puts 2, "- Recursively delete orphaned group '#{payload[:name]}'..."
113
+ when :removing_recursive_child_association
114
+ fmt.puts 4, "- Remove association {group:#{payload[:parent]} <-> group:#{payload[:child]}}..."
115
+ when :adding_automatic_group_to_host
116
+ fmt.puts payload[:indent], "- Adding automatic association {group:ungrouped <-> host:#{payload[:host]}}..."
117
+ when :ok
118
+ fmt.puts payload[:indent], '- OK'
87
119
  end
88
120
  end
89
121
  end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
 
3
- require_relative './formatter.rb'
5
+ require_relative 'formatter'
6
+ require_relative '../inventory_context'
7
+ require_relative '../operations/group_child_relations'
4
8
 
5
9
  module Moose
6
10
  module Inventory
@@ -9,72 +13,58 @@ module Moose
9
13
  # Implemention of the "group rmchild" methods of the CLI
10
14
  class Group
11
15
  #==========================
16
+ option :delete_orphans,
17
+ type: :boolean,
18
+ default: false,
19
+ desc: 'Delete child groups that become orphaned'
12
20
  desc 'rmchild PARENTGROUP CHILDGROUP_1 [CHILDGROUP_2 ... ]',
13
21
  'Dissociate one or more child-groups CHILDGROUP_n from PARENTGROUP'
14
- def rmchild(*_argv)
15
- # Sanity check
16
- if args.length < 2
17
- abort("ERROR: Wrong number of arguments, #{args.length} "\
18
- 'for 2 or more.')
19
- end
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'
25
+ def rmchild(*argv)
26
+ abort_if_missing_args(argv, 2, '2 or more')
27
+ validate_machine_plan_request!
20
28
 
21
- # Arguments
22
- pname = args[0].downcase
23
- cnames = args.slice(1, args.length - 1).uniq.map(&:downcase)
24
-
25
- # Sanity
26
- if pname == 'ungrouped' || cnames.include?('ungrouped')
27
- abort("ERROR: Cannot manually manipulate the automatic group 'ungrouped'.")
28
- end
29
+ pname = argv[0].downcase
30
+ cnames = normalize_names(argv.slice(1, argv.length - 1))
29
31
 
30
- # Convenience
31
- db = Moose::Inventory::DB
32
- fmt = Moose::Inventory::Cli::Formatter
32
+ abort_if_automatic_group([pname] + cnames)
33
+ confirm_destructive_action!("group rmchild #{pname} #{cnames.join(',')}")
33
34
 
34
- # Transaction
35
- warn_count = 0
36
- begin
37
- db.transaction do # Transaction start
38
- puts "Dissociate parent group '#{pname}' from child group(s) '#{cnames.join(',')}':"
39
- # Get the target group
40
- fmt.puts 2, "- retrieve group '#{pname}'..."
41
- pgroup = db.models[:group].find(name: pname)
42
- if pgroup.nil?
43
- abort("ERROR: The group '#{pname}' does not exist.")
44
- end
45
- fmt.puts 4, '- OK'
35
+ result = remove_children_from_group(pname, cnames)
36
+ return if machine_plan_output_rendered?(result, command: 'group rmchild')
46
37
 
47
- # Dissociate parent group from the child groups
48
- groups_ds = pgroup.children_dataset
49
- cnames.each do |cname|
50
- fmt.puts 2, "- remove association {group:#{pname} <-> group:#{cname}}..."
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)
41
+ end
51
42
 
52
- # Check against existing associations
53
- if groups_ds[name: cname].nil?
54
- warn_count += 1
55
- fmt.warn "Association {group:#{pname} <-> group:#{cname}}"\
56
- " does not exist, skipping.\n"
57
- fmt.puts 4, "- doesn't exist, skipping."
58
- fmt.puts 4, '- OK'
59
- next
60
- end
43
+ private
61
44
 
62
- # remove association
63
- cgroup = db.models[:group].find(name: cname)
64
- pgroup.remove_child(cgroup)
65
- fmt.puts 4, '- OK'
66
- end
67
- fmt.puts 2, '- all OK'
68
- end # Transaction end
69
- rescue db.exceptions[:moose] => e
70
- abort("ERROR: #{e}")
71
- end
72
- if warn_count == 0
73
- puts 'Succeeded.'
74
- else
75
- puts 'Succeeded, with warnings.'
45
+ def remove_children_from_group(parent_name, child_names)
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
76
61
  end
77
62
  end
63
+
64
+ def render_rmchild_events(events)
65
+ emitter = rmchild_emitter
66
+ events.each { |event| emitter.call(event) }
67
+ end
78
68
  end
79
69
  end
80
70
  end
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative 'formatter'
5
+ require_relative '../inventory_context'
6
+ require_relative '../operations/remove_associations'
3
7
 
4
8
  module Moose
5
9
  module Inventory
@@ -10,75 +14,44 @@ module Moose
10
14
  #==========================
11
15
  desc 'rmhost GROUPNAME HOSTNAME_1 [HOSTNAME_2 ...]',
12
16
  'Dissociate the hosts HOSTNAME_n from the group NAME'
13
- # rubocop:disable Metrics/LineLength
14
- def rmhost(*args) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity
15
- # rubocop:enable Metrics/LineLength
16
- # Sanity
17
- if args.length < 2
18
- abort("ERROR: Wrong number of arguments, #{args.length} for 2 or more.")
19
- end
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'
20
+ def rmhost(*args)
21
+ abort_if_missing_args(args, 2, '2 or more')
22
+ validate_machine_plan_request!
20
23
 
21
- # Arguments
22
24
  name = args[0].downcase
23
- hosts = args.slice(1, args.length - 1).uniq.map(&:downcase)
24
-
25
- # Sanity
26
- if name == 'ungrouped'
27
- abort("ERROR: Cannot manually manipulate the automatic group 'ungrouped'.")
28
- end
29
-
30
- # Convenience
31
- db = Moose::Inventory::DB
32
- fmt = Moose::Inventory::Cli::Formatter
25
+ hosts = normalize_names(args.slice(1, args.length - 1))
33
26
 
34
- # Transaction
35
- warn_count = 0
36
- begin
37
- db.transaction do # Transaction start
38
- # Get the target group
39
- puts "Dissociate group '#{name}' from host(s) '#{hosts.join(',')}':"
40
- fmt.puts 2, "- retrieve group '#{name}'..."
41
- group = db.models[:group].find(name: name)
42
- abort("ERROR: The group '#{name}' does not exist.") if group.nil?
43
- fmt.puts 4, '- OK'
27
+ abort_if_automatic_group([name])
28
+ confirm_destructive_action!("group rmhost #{name} #{hosts.join(',')}")
44
29
 
45
- # dissociate group from the hosts
46
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
47
- hosts_ds = group.hosts_dataset
48
- hosts.each do |h|
49
- fmt.puts 2, "- remove association {group:#{name} <-> host:#{h}}..."
30
+ result = remove_hosts_from_group(name, hosts)
31
+ return if machine_plan_output_rendered?(result, command: 'group rmhost')
50
32
 
51
- # Check against existing associations
52
- if hosts_ds[name: h].nil?
53
- warn_count += 1
54
- fmt.warn "Association {group:#{name} <-> host:#{h}} doesn't"\
55
- " exist, skipping.\n"
56
- fmt.puts 4, '- doesn\'t exist, skipping.'
57
- fmt.puts 4, '- OK'
58
- next
59
- 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)
36
+ end
60
37
 
61
- host = db.models[:host].find(name: h)
62
- group.remove_host(host) unless host.nil?
63
- fmt.puts 4, '- OK'
38
+ private
64
39
 
65
- # Add the host to the ungrouped group if not in any other group
66
- next unless host.groups_dataset.count == 0
67
- fmt.puts 2, "- add automatic association {group:ungrouped <-> host:#{h}}..."
68
- host.add_group(ungrouped)
69
- fmt.puts 4, '- OK'
70
- end
71
- fmt.puts 2, '- all OK'
72
- end # Transaction end
73
- rescue db.exceptions[:moose] => e
74
- abort("ERROR: #{e.message}")
75
- end
76
- if warn_count == 0
77
- puts 'Succeeded.'
78
- else
79
- puts 'Succeeded, with warnings.'
40
+ def remove_hosts_from_group(name, hosts)
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
80
48
  end
81
49
  end
50
+
51
+ def render_group_rmhost_events(events)
52
+ emitter = host_group_association_removal_emitter(perspective: :group)
53
+ events.each { |event| emitter.call(event) }
54
+ end
82
55
  end
83
56
  end
84
57
  end
@@ -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/remove_variables'
3
6
 
4
7
  module Moose
5
8
  module Inventory
@@ -10,54 +13,40 @@ module Moose
10
13
  #==========================
11
14
  desc 'rmvar NAME VARNAME',
12
15
  'Remove a variable VARNAME from the group NAME'
16
+ option :dry_run, type: :boolean
17
+ option :yes, type: :boolean, desc: 'Confirm destructive removal without prompting'
18
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
13
19
  def rmvar(*args)
14
- if args.length < 2
15
- abort('ERROR: Wrong number of arguments, ' \
16
- "#{args.length} for 2 or more.")
17
- end
18
-
19
- # Convenience
20
- db = Moose::Inventory::DB
21
- fmt = Moose::Inventory::Cli::Formatter
20
+ abort_if_missing_args(args, 2, '2 or more')
21
+ validate_machine_plan_request!
22
22
 
23
- # Arguments
24
23
  name = args[0].downcase
25
24
  vars = args.slice(1, args.length - 1).uniq
25
+ confirm_destructive_action!("group rmvar #{name} #{vars.join(',')}")
26
+ operation = build_operation(Moose::Inventory::Operations::RemoveVariables,
27
+ entity_type: :group,
28
+ emitter: machine_plan_emitter(group_rmvar_emitter(name, vars)))
26
29
 
27
- # Transaction
28
- db.transaction do # Transaction start
29
- puts "Remove variable(s) '#{vars.join(',')}' from group '#{name}':"
30
+ result = db.transaction do
31
+ operation.call(name: name, vars: vars, dry_run: options[:dry_run])
32
+ end
33
+
34
+ return if machine_plan_output_rendered?(result, command: 'group rmvar')
30
35
 
31
- fmt.puts 2, "- retrieve group '#{name}'..."
32
- group = db.models[:group].find(name: name)
33
- if group.nil?
34
- fail db.exceptions[:moose],
35
- "The group '#{name}' does not exist."
36
- end
37
- fmt.puts 4, '- OK'
36
+ record_audit({ command: 'group rmvar', action: 'remove_variable', entity_type: 'group',
37
+ entity_names: name }, result: result, dry_run: options[:dry_run])
38
+ print_success_summary
39
+ end
38
40
 
39
- groupvars_ds = group.groupvars_dataset
40
- vars.each do |v|
41
- fmt.puts 2, "- remove variable '#{v}'..."
42
- vararray = v.split('=')
43
- if v.start_with?('=') || v.scan('=').count > 1
44
- fail db.exceptions[:moose],
45
- "Incorrect format in {#{v}}. " \
46
- 'Expected \'key\' or \'key=value\'.'
47
- end
41
+ private
48
42
 
49
- # Check against existing associations
50
- groupvar = groupvars_ds[name: vararray[0]]
51
- unless groupvar.nil?
52
- # remove the association
53
- group.remove_groupvar(groupvar)
54
- groupvar.destroy
55
- end
56
- fmt.puts 4, '- OK'
57
- end
58
- fmt.puts 2, '- all OK'
59
- end # Transaction end
60
- puts 'Succeeded.'
43
+ def group_rmvar_emitter(name, vars)
44
+ variable_operation_emitter(
45
+ action: :remove,
46
+ entity_label: 'group',
47
+ entity_name: name,
48
+ variables_label: vars.join(',')
49
+ )
61
50
  end
62
51
  end
63
52
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moose
4
+ module Inventory
5
+ module Cli
6
+ class Group
7
+ desc 'addtag GROUP TAG_1 [TAG_2 ...]', 'Add metadata tags to a group'
8
+ def addtag(*args)
9
+ abort_if_missing_args(args, 2, '2 or more')
10
+
11
+ add_tags('group', args[0].downcase, args.slice(1, args.length - 1))
12
+ end
13
+
14
+ desc 'rmtag GROUP TAG_1 [TAG_2 ...]', 'Remove metadata tags from a group'
15
+ option :yes, type: :boolean, desc: 'Confirm destructive tag removal without prompting'
16
+ def rmtag(*args)
17
+ abort_if_missing_args(args, 2, '2 or more')
18
+ confirm_destructive_action!("group rmtag #{args[0].downcase} #{args.slice(1, args.length - 1).join(',')}")
19
+
20
+ remove_tags('group', args[0].downcase, args.slice(1, args.length - 1))
21
+ end
22
+
23
+ desc 'listtags GROUP', 'List metadata tags for a group'
24
+ option :format, type: :string, desc: 'Emit tags as yaml|json|pjson'
25
+ def listtags(*args)
26
+ abort_if_missing_args(args, 1, '1')
27
+
28
+ list_tags('group', args[0].downcase)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end