moose-inventory 2.0 → 2.1.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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +2 -0
  3. data/.gitignore +6 -1
  4. data/.rubocop.yml +21 -0
  5. data/BACKLOG.md +638 -9
  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 +78 -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/security-audit-2026-05-29-snapshot-import-fuzz.md +58 -0
  28. data/docs/ux/cli-workflow-notes.md +287 -0
  29. data/examples/ansible/ansible.cfg +3 -0
  30. data/examples/ansible/inventory/moose_inventory.yml +5 -0
  31. data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
  32. data/examples/ci/README.md +16 -0
  33. data/examples/ci/github-actions/inventory-review.yml +38 -0
  34. data/examples/ci/inventory/example-snapshot.yml +19 -0
  35. data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
  36. data/lib/moose_inventory/cli/application.rb +135 -5
  37. data/lib/moose_inventory/cli/association_rendering.rb +74 -0
  38. data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
  39. data/lib/moose_inventory/cli/audit.rb +62 -0
  40. data/lib/moose_inventory/cli/audit_recording.rb +40 -0
  41. data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
  42. data/lib/moose_inventory/cli/console.rb +135 -0
  43. data/lib/moose_inventory/cli/db.rb +64 -0
  44. data/lib/moose_inventory/cli/factory.rb +28 -0
  45. data/lib/moose_inventory/cli/formatter.rb +8 -12
  46. data/lib/moose_inventory/cli/group.rb +5 -2
  47. data/lib/moose_inventory/cli/group_add.rb +11 -9
  48. data/lib/moose_inventory/cli/group_addchild.rb +23 -65
  49. data/lib/moose_inventory/cli/group_addhost.rb +16 -67
  50. data/lib/moose_inventory/cli/group_addvar.rb +27 -47
  51. data/lib/moose_inventory/cli/group_get.rb +8 -42
  52. data/lib/moose_inventory/cli/group_list.rb +7 -40
  53. data/lib/moose_inventory/cli/group_listvars.rb +9 -55
  54. data/lib/moose_inventory/cli/group_rm.rb +12 -10
  55. data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
  56. data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
  57. data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
  58. data/lib/moose_inventory/cli/group_tags.rb +33 -0
  59. data/lib/moose_inventory/cli/helpers.rb +68 -1
  60. data/lib/moose_inventory/cli/host.rb +6 -3
  61. data/lib/moose_inventory/cli/host_add.rb +69 -29
  62. data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
  63. data/lib/moose_inventory/cli/host_addvar.rb +28 -52
  64. data/lib/moose_inventory/cli/host_get.rb +9 -37
  65. data/lib/moose_inventory/cli/host_list.rb +24 -21
  66. data/lib/moose_inventory/cli/host_listvars.rb +9 -62
  67. data/lib/moose_inventory/cli/host_rm.rb +60 -42
  68. data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
  69. data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
  70. data/lib/moose_inventory/cli/host_tags.rb +33 -0
  71. data/lib/moose_inventory/cli/listvars_support.rb +55 -0
  72. data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
  73. data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
  74. data/lib/moose_inventory/cli/tag_support.rb +97 -0
  75. data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
  76. data/lib/moose_inventory/config/config.rb +185 -108
  77. data/lib/moose_inventory/db/db.rb +170 -195
  78. data/lib/moose_inventory/db/exceptions.rb +6 -3
  79. data/lib/moose_inventory/db/models.rb +16 -0
  80. data/lib/moose_inventory/db/schema_migrations.rb +248 -0
  81. data/lib/moose_inventory/inventory_context.rb +68 -2
  82. data/lib/moose_inventory/operations/add_associations.rb +20 -16
  83. data/lib/moose_inventory/operations/add_groups.rb +21 -13
  84. data/lib/moose_inventory/operations/add_hosts.rb +30 -17
  85. data/lib/moose_inventory/operations/add_variables.rb +77 -0
  86. data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
  87. data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
  88. data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
  89. data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
  90. data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
  91. data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
  92. data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
  93. data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
  94. data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +174 -0
  95. data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
  96. data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
  97. data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
  98. data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
  99. data/lib/moose_inventory/operations/query_inventory.rb +47 -0
  100. data/lib/moose_inventory/operations/remove_associations.rb +30 -18
  101. data/lib/moose_inventory/operations/remove_groups.rb +12 -12
  102. data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
  103. data/lib/moose_inventory/operations/remove_variables.rb +67 -0
  104. data/lib/moose_inventory/runtime_options.rb +31 -0
  105. data/lib/moose_inventory/version.rb +3 -1
  106. data/lib/moose_inventory.rb +10 -7
  107. data/moose-inventory.gemspec +19 -35
  108. data/scripts/check.sh +1 -0
  109. data/scripts/ci/check_generated_artifacts.sh +41 -0
  110. data/scripts/ci/check_permissions.sh +2 -0
  111. data/scripts/ci/check_rubocop.sh +30 -25
  112. data/scripts/ci/check_security.sh +4 -1
  113. data/scripts/files.rb +5 -4
  114. data/spec/examples/ci_examples_spec.rb +37 -0
  115. data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
  116. data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
  117. data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +132 -0
  118. data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
  119. data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
  120. data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
  121. data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
  122. data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
  123. data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
  124. data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
  125. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
  126. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
  127. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
  128. data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
  129. data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
  130. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
  131. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
  132. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
  133. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
  134. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
  135. data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
  136. data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
  137. data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
  138. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
  139. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
  140. data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
  141. data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
  142. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
  143. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
  144. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
  145. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
  146. data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
  147. data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
  148. data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
  149. data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
  150. data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
  151. data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
  152. data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
  153. data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
  154. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
  155. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
  156. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
  157. data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
  158. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
  159. data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +239 -0
  160. data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
  161. data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
  162. data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
  163. data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
  164. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
  165. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
  166. data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
  167. data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
  168. data/spec/shared/shared_config_setup.rb +4 -3
  169. data/spec/spec_helper.rb +50 -40
  170. data/spec/support/cli_harness.rb +33 -0
  171. metadata +81 -41
@@ -14,8 +14,11 @@ module Moose
14
14
  #==========================
15
15
  desc 'add NAME', 'Add a group NAME to the inventory'
16
16
  option :hosts
17
+ option :dry_run, type: :boolean
18
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
17
19
  def add(*argv)
18
20
  abort_if_missing_args(argv, 1, '1 or more')
21
+ validate_machine_plan_request!
19
22
 
20
23
  names = normalize_names(argv)
21
24
  hosts = csv_option_names(options[:hosts])
@@ -25,16 +28,14 @@ module Moose
25
28
  "ERROR: Cannot manually manipulate the automatic group 'ungrouped'\n"
26
29
  )
27
30
 
28
- result = Moose::Inventory::Operations::AddGroups
29
- .new(context: Moose::Inventory::InventoryContext.new(db: db))
30
- .call(names: names, hosts: hosts)
31
- render_add_groups_events(result.events)
31
+ result = build_operation(Moose::Inventory::Operations::AddGroups)
32
+ .call(names: names, hosts: hosts, dry_run: options[:dry_run])
33
+ return if machine_plan_output_rendered?(result, command: 'group add')
32
34
 
33
- if result.warning_count.zero?
34
- puts 'Succeeded'
35
- else
36
- puts 'Succeeded, with warnings.'
37
- end
35
+ record_audit({ command: 'group add', action: 'add', entity_type: 'group',
36
+ entity_names: names }, result: result, dry_run: options[:dry_run])
37
+ render_add_groups_events(result.events)
38
+ print_warning_summary(result, success_message: 'Succeeded')
38
39
  end
39
40
 
40
41
  private
@@ -48,6 +49,7 @@ module Moose
48
49
 
49
50
  return render_add_groups_event_puts(event.type, payload) if puts_event?(event.type)
50
51
  return render_add_groups_event_warn(event.type, payload) if warn_event?(event.type)
52
+ return puts 'Dry run complete. No changes applied.' if event.type == :dry_run_summary
51
53
 
52
54
  render_add_groups_event_fmt(event.type, payload)
53
55
  end
@@ -14,8 +14,11 @@ module Moose
14
14
  #==========================
15
15
  desc 'addchild PARENTGROUP CHILDGROUP_1 [CHILDGROUP_2 ... ]',
16
16
  'Associate one or more child-groups CHILDGROUP_n with PARENTGROUP'
17
+ option :dry_run, type: :boolean
18
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
17
19
  def addchild(*argv)
18
20
  abort_if_missing_args(argv, 2, '2 or more')
21
+ validate_machine_plan_request!
19
22
 
20
23
  pname = argv[0].downcase
21
24
  cnames = normalize_names(argv.slice(1, argv.length - 1))
@@ -23,81 +26,36 @@ module Moose
23
26
  abort_if_automatic_group([pname] + cnames)
24
27
 
25
28
  result = add_children_to_group(pname, cnames)
29
+ return if machine_plan_output_rendered?(result, command: 'group addchild')
26
30
 
27
- if result.warning_count.zero?
28
- puts 'Succeeded.'
29
- else
30
- puts 'Succeeded, with warnings.'
31
- end
31
+ record_audit({ command: 'group addchild', action: 'associate_child', entity_type: 'group',
32
+ entity_names: pname }, result: result, dry_run: options[:dry_run])
33
+ print_warning_summary(result)
32
34
  end
33
35
 
34
36
  private
35
37
 
36
38
  def add_children_to_group(parent_name, child_names)
37
- context = Moose::Inventory::InventoryContext.new(db: db)
38
- operation = Moose::Inventory::Operations::GroupChildRelations.new(context: context)
39
-
40
- begin
41
- db.transaction do
42
- puts "Associate parent group '#{parent_name}' with child group(s) '#{child_names.join(',')}':"
43
- parent_group = fetch_existing_group_for_child_relation(context, parent_name)
44
- result = operation.add_children(
45
- parent_group: parent_group,
46
- parent_name: parent_name,
47
- child_names: child_names
48
- )
49
- render_addchild_events(result.events)
50
- fmt.puts 2, '- all OK'
51
- return result
52
- end
53
- rescue db.exceptions[:moose] => e
54
- abort("ERROR: #{e}")
39
+ operation = build_operation(Moose::Inventory::Operations::GroupChildRelations)
40
+ run_group_relation_transaction(
41
+ heading: "Associate parent group '#{parent_name}' with child group(s) '#{child_names.join(',')}':",
42
+ on_error: method(:exception_to_s)
43
+ ) do
44
+ parent_group = fetch_existing_group_or_abort(parent_name)
45
+ result = operation.add_children(
46
+ parent_group: parent_group,
47
+ parent_name: parent_name,
48
+ child_names: child_names,
49
+ dry_run: options[:dry_run]
50
+ )
51
+ render_addchild_events(result.events) unless machine_plan_output_requested?
52
+ result
55
53
  end
56
54
  end
57
55
 
58
- def fetch_existing_group_for_child_relation(context, name)
59
- fmt.puts 2, "- retrieve group '#{name}'..."
60
- group = context.find_group(name)
61
- abort("ERROR: The group '#{name}' does not exist.") if group.nil?
62
-
63
- fmt.puts 4, '- OK'
64
- group
65
- end
66
-
67
56
  def render_addchild_events(events)
68
- events.each { |event| render_addchild_event(event) }
69
- end
70
-
71
- def render_addchild_event(event)
72
- payload = event.payload
73
-
74
- return render_addchild_warning(event.type, payload) if addchild_warning?(event.type)
75
- return render_addchild_existing(payload) if event.type == :already_exists_skipping
76
-
77
- case event.type
78
- when :adding_child_association
79
- fmt.puts 2, "- add association {group:#{payload[:parent]} <-> group:#{payload[:child]}}..."
80
- when :child_group_creating_now
81
- fmt.puts 4, '- child group does not exist, creating now...'
82
- when :ok
83
- fmt.puts payload[:indent], '- OK'
84
- end
85
- end
86
-
87
- def addchild_warning?(type)
88
- %i[child_association_exists child_group_missing].include?(type)
89
- end
90
-
91
- def render_addchild_warning(type, payload)
92
- if type == :child_association_exists
93
- fmt.warn "Association {group:#{payload[:parent]} <-> group:#{payload[:child]}}} already exists, skipping.\n"
94
- else
95
- fmt.warn "Group '#{payload[:name]}' does not exist and will be created.\n"
96
- end
97
- end
98
-
99
- def render_addchild_existing(payload)
100
- fmt.puts payload[:indent], '- already exists, skipping.'
57
+ emitter = addchild_emitter
58
+ events.each { |event| emitter.call(event) }
101
59
  end
102
60
  end
103
61
  end
@@ -14,8 +14,11 @@ module Moose
14
14
  #==========================
15
15
  desc 'addhost NAME HOSTNAME',
16
16
  'Associate a host HOSTNAME with the group NAME'
17
+ option :dry_run, type: :boolean
18
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
17
19
  def addhost(*args)
18
20
  abort_if_missing_args(args, 2, '2 or more')
21
+ validate_machine_plan_request!
19
22
 
20
23
  name = args[0].downcase
21
24
  hosts = normalize_names(args.slice(1, args.length - 1))
@@ -23,83 +26,29 @@ module Moose
23
26
  abort_if_automatic_group([name])
24
27
 
25
28
  result = add_hosts_to_group(name, hosts)
29
+ return if machine_plan_output_rendered?(result, command: 'group addhost')
26
30
 
27
- if result.warning_count.zero?
28
- puts 'Succeeded.'
29
- else
30
- puts 'Succeeded, with warnings.'
31
- end
31
+ record_audit({ command: 'group addhost', action: 'associate', entity_type: 'group',
32
+ entity_names: name }, result: result, dry_run: options[:dry_run])
33
+ print_warning_summary(result)
32
34
  end
33
35
 
34
36
  private
35
37
 
36
38
  def add_hosts_to_group(name, hosts)
37
- context = Moose::Inventory::InventoryContext.new(db: db)
38
- operation = Moose::Inventory::Operations::AddAssociations.new(context: context)
39
-
40
- begin
41
- db.transaction do
42
- puts "Associate group '#{name}' with host(s) '#{hosts.join(',')}':"
43
- group = fetch_existing_group_for_addhost(context, name)
44
- result = operation.group_to_hosts(group: group, group_name: name, host_names: hosts)
45
- render_group_addhost_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}")
39
+ operation = build_operation(Moose::Inventory::Operations::AddAssociations)
40
+ run_group_relation_transaction(heading: "Associate group '#{name}' with host(s) '#{hosts.join(',')}':") do
41
+ group = fetch_existing_group_or_abort(name)
42
+ result = operation.group_to_hosts(group: group, group_name: name, host_names: hosts,
43
+ dry_run: options[:dry_run])
44
+ render_group_addhost_events(result.events) unless machine_plan_output_requested?
45
+ result
51
46
  end
52
47
  end
53
48
 
54
- def fetch_existing_group_for_addhost(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
49
  def render_group_addhost_events(events)
64
- events.each { |event| render_group_addhost_event(event) }
65
- end
66
-
67
- def render_group_addhost_event(event)
68
- payload = event.payload
69
-
70
- return render_group_addhost_warning(event.type, payload) if group_addhost_warning?(event.type)
71
- return render_group_addhost_status(payload) if event.type == :already_exists_skipping
72
-
73
- render_group_addhost_output(event.type, payload)
74
- end
75
-
76
- def group_addhost_warning?(type)
77
- %i[group_host_association_exists host_missing_created].include?(type)
78
- end
79
-
80
- def render_group_addhost_warning(type, payload)
81
- if type == :group_host_association_exists
82
- fmt.warn "Association {group:#{payload[:group]} <-> host:#{payload[:host]}} already exists, skipping.\n"
83
- else
84
- fmt.warn "Host '#{payload[:name]}' does not exist and will be created.\n"
85
- end
86
- end
87
-
88
- def render_group_addhost_status(payload)
89
- fmt.puts payload[:indent], '- already exists, skipping.'
90
- end
91
-
92
- def render_group_addhost_output(type, payload)
93
- case type
94
- when :adding_group_host_association
95
- fmt.puts 2, "- add association {group:#{payload[:group]} <-> host:#{payload[:host]}}..."
96
- when :host_creating_now
97
- fmt.puts 4, '- host does not exist, creating now...'
98
- when :removing_automatic_group
99
- fmt.puts 2, "- remove automatic association {group:ungrouped <-> host:#{payload[:host]}}..."
100
- when :ok
101
- fmt.puts payload[:indent], '- OK'
102
- end
50
+ emitter = host_group_association_addition_emitter(perspective: :group)
51
+ events.each { |event| emitter.call(event) }
103
52
  end
104
53
  end
105
54
  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/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