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
@@ -16,80 +16,44 @@ module Moose
16
16
  class Host
17
17
  desc 'addgroup HOSTNAME GROUPNAME [GROUPNAME ...]',
18
18
  'Associate the host with a group'
19
+ option :dry_run, type: :boolean
20
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
19
21
  def addgroup(*args)
20
22
  abort_if_missing_args(args, 2, '2 or more')
23
+ validate_machine_plan_request!
21
24
 
22
25
  name = args[0].downcase
23
26
  groups = normalize_names(args.slice(1, args.length - 1))
24
27
 
25
28
  abort_if_automatic_group(groups)
26
29
 
27
- context = Moose::Inventory::InventoryContext.new(db: db)
28
- operation = Moose::Inventory::Operations::AddAssociations.new(context: context)
29
-
30
- db.transaction do
31
- puts "Associate host '#{name}' with groups '#{groups.join(',')}':"
32
- host = fetch_existing_host_for_addgroup(context, name)
33
- render_host_addgroup_events(
34
- operation.host_to_groups(host: host, host_name: name, group_names: groups).events
35
- )
36
- fmt.puts 2, '- All OK'
30
+ result = add_groups_to_host(name, groups)
31
+ unless machine_plan_output_rendered?(
32
+ result, command: 'host addgroup'
33
+ )
34
+ record_audit({ command: 'host addgroup', action: 'associate', entity_type: 'host',
35
+ entity_names: name }, result: result, dry_run: options[:dry_run])
36
+ print_warning_summary(result, success_message: 'Succeeded',
37
+ warning_message: 'Succeeded')
37
38
  end
38
-
39
- puts 'Succeeded'
40
39
  end
41
40
 
42
41
  private
43
42
 
44
- def fetch_existing_host_for_addgroup(context, name)
45
- fmt.puts 2, "- Retrieve host '#{name}'..."
46
- host = context.find_host(name)
47
- raise db.exceptions[:moose], "The host '#{name}' was not found in the database." if host.nil?
48
-
49
- fmt.puts 4, '- OK'
50
- host
51
- end
52
-
53
- def render_host_addgroup_events(events)
54
- events.each { |event| render_host_addgroup_event(event) }
55
- end
56
-
57
- def render_host_addgroup_event(event)
58
- payload = event.payload
59
-
60
- return render_host_addgroup_warning(event.type, payload) if host_addgroup_warning?(event.type)
61
- return render_host_addgroup_status(payload) if event.type == :already_exists_skipping
62
-
63
- render_host_addgroup_output(event.type, payload)
64
- end
65
-
66
- def host_addgroup_warning?(type)
67
- %i[host_group_association_exists group_missing_created].include?(type)
68
- end
69
-
70
- def render_host_addgroup_warning(type, payload)
71
- if type == :host_group_association_exists
72
- fmt.warn "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} already exists, skipping."
73
- else
74
- fmt.warn "Group '#{payload[:name]}' does not exist and will be created."
43
+ def add_groups_to_host(name, groups)
44
+ operation = build_operation(Moose::Inventory::Operations::AddAssociations)
45
+ run_host_relation_transaction(heading: "Associate host '#{name}' with groups '#{groups.join(',')}':") do
46
+ host = fetch_existing_host_or_raise(name)
47
+ result = operation.host_to_groups(host: host, host_name: name, group_names: groups,
48
+ dry_run: options[:dry_run])
49
+ render_host_addgroup_events(result.events) unless machine_plan_output_requested?
50
+ result
75
51
  end
76
52
  end
77
53
 
78
- def render_host_addgroup_status(payload)
79
- fmt.puts payload[:indent], '- Already exists, skipping.'
80
- end
81
-
82
- def render_host_addgroup_output(type, payload)
83
- case type
84
- when :adding_host_group_association
85
- fmt.puts 2, "- Add association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
86
- when :group_creating_now
87
- fmt.puts 4, '- Group does not exist, creating now...'
88
- when :removing_automatic_group
89
- fmt.puts 2, "- Remove automatic association {host:#{payload[:host]} <-> group:ungrouped}..."
90
- when :ok
91
- fmt.puts payload[:indent], '- OK'
92
- end
54
+ def render_host_addgroup_events(events)
55
+ emitter = host_group_association_addition_emitter(perspective: :host)
56
+ events.each { |event| emitter.call(event) }
93
57
  end
94
58
  end
95
59
  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/add_variables'
6
8
 
7
9
  module Moose
8
10
  module Inventory
@@ -12,64 +14,38 @@ module Moose
12
14
  class Host
13
15
  #==========================
14
16
  desc 'addvar', 'Add a variable to the host'
15
- # rubocop:disable Metrics/LineLength
16
- def addvar(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
17
- # rubocop:enable Metrics/LineLength
18
- if args.length < 2
19
- abort('ERROR: Wrong number of arguments, '\
20
- "#{args.length} for 2 or more.")
21
- end
22
-
23
- # Convenience
24
- db = Moose::Inventory::DB
25
- fmt = Moose::Inventory::Cli::Formatter
17
+ option :dry_run, type: :boolean
18
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
19
+ def addvar(*args)
20
+ abort_if_missing_args(args, 2, '2 or more')
21
+ validate_machine_plan_request!
26
22
 
27
- # Arguments
28
23
  name = args[0].downcase
29
24
  vars = args.slice(1, args.length - 1).uniq
25
+ operation = build_operation(Moose::Inventory::Operations::AddVariables,
26
+ entity_type: :host,
27
+ emitter: machine_plan_emitter(host_addvar_emitter(name, vars)))
30
28
 
31
- # Transaction
32
- db.transaction do # Transaction start
33
- puts "Add variables '#{vars.join(',')}' to host '#{name}':"
29
+ result = db.transaction do
30
+ operation.call(name: name, vars: vars, dry_run: options[:dry_run])
31
+ end
34
32
 
35
- fmt.puts 2, "- retrieve host '#{name}'..."
36
- host = db.models[:host].find(name: name)
37
- if host.nil?
38
- fail db.exceptions[:moose],
39
- "The host '#{name}' does not exist."
40
- end
41
- fmt.puts 4, '- OK'
33
+ return if machine_plan_output_rendered?(result, command: 'host addvar')
42
34
 
43
- hostvars_ds = host.hostvars_dataset
44
- vars.each do |v|
45
- fmt.puts 2, "- add variable '#{v}'..."
46
- vararray = v.split('=')
47
- if v.start_with?('=') || v.end_with?('=') || vararray.length != 2
48
- fail db.exceptions[:moose],
49
- "Incorrect format in '{#{v}}'. Expected 'key=value'."
50
- end
35
+ record_audit({ command: 'host addvar', action: 'add_variable', entity_type: 'host',
36
+ entity_names: name }, result: result, dry_run: options[:dry_run])
37
+ print_success_summary
38
+ end
51
39
 
52
- # Check against existing associations
53
- hostvar = hostvars_ds[name: vararray[0]]
54
- if !hostvar.nil?
55
- unless hostvar[:value] == vararray[1]
56
- fmt.puts 4, '- already exists, applying as an update...'
57
- update = db.models[:hostvar].find(id: hostvar[:id])
58
- update[:value] = vararray[1]
59
- update.save
60
- end
61
- else
62
- # hostvar doesn't exist, so create and associate
63
- hostvar = db.models[:hostvar].create(name: vararray[0],
64
- value: vararray[1])
65
- host.add_hostvar(hostvar)
66
- end
67
- fmt.puts 4, '- OK'
68
- end
69
- fmt.puts 2, '- all OK'
70
- end # Transaction end
40
+ private
71
41
 
72
- puts 'Succeeded.'
42
+ def host_addvar_emitter(name, vars)
43
+ variable_operation_emitter(
44
+ action: :add,
45
+ entity_label: 'host',
46
+ entity_name: name,
47
+ variables_label: vars.join(',')
48
+ )
73
49
  end
74
50
  end
75
51
  end
@@ -1,54 +1,26 @@
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
9
11
  module Cli
10
- ##
11
12
  # Class implementing the "host get" method of the CLI
12
13
  class Host
13
14
  require_relative 'host_add'
14
15
 
15
- #==========================
16
16
  desc 'get HOST_1 [HOST_2 ...]',
17
17
  'Get hosts HOST_n from the inventory'
18
- def get(*argv) # rubocop:disable Metrics/AbcSize
19
- if argv.empty?
20
- abort('ERROR: Wrong number of arguments, '\
21
- "#{argv.length} for 1 or more")
22
- end
23
-
24
- # Convenience
25
- db = Moose::Inventory::DB
26
- fmt = Moose::Inventory::Cli::Formatter
27
-
28
- # Arguments
29
- names = argv.uniq.map(&:downcase)
30
-
31
- # Process
32
- results = {}
33
- names.each do |name|
34
- host = db.models[:host].find(name: name)
35
-
36
- next if host.nil?
37
- groups = host.groups_dataset.map(:name)
38
-
39
- hostvars = {}
40
- host.hostvars_dataset.each do |hv|
41
- hostvars[hv[:name].to_sym] = hv[:value]
42
- end
43
-
44
- results[host[:name].to_sym] = {}
45
- results[host[:name].to_sym][:groups] = groups unless groups.empty?
46
- unless hostvars.empty?
47
- results[host[:name].to_sym][:hostvars] = hostvars
48
- end
49
- end
18
+ def get(*argv)
19
+ abort("ERROR: Wrong number of arguments, #{argv.length} for 1 or more") if argv.empty?
50
20
 
51
- fmt.dump(results)
21
+ names = normalize_names(argv)
22
+ results = inventory_query.get_hosts(names: names)
23
+ fmt.dump(results, output_format)
52
24
  end
53
25
  end
54
26
  end
@@ -1,38 +1,41 @@
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
9
11
  module Cli
10
- ##
11
12
  # Implementation of the "host list" method of the CLI
12
13
  class Host
13
14
  desc 'list', 'List the contents of the inventory by host'
14
- def list # rubocop:disable Metrics/AbcSize
15
- # Convenience
16
- db = Moose::Inventory::DB
17
- fmt = Moose::Inventory::Cli::Formatter
15
+ option :group, type: :string, desc: 'Only include hosts in all comma-separated groups'
16
+ option :tag, type: :string, desc: 'Only include hosts with all comma-separated tags'
17
+ option :var, type: :string, desc: 'Only include hosts with comma-separated key=value variables'
18
+ def list
19
+ fmt.dump(inventory_query.list_hosts(filters: host_list_filters), output_format)
20
+ end
18
21
 
19
- # Process
20
- results = {}
21
- db.models[:host].all.each do |host|
22
- groups = host.groups_dataset.map(:name)
23
- results[host[:name].to_sym] = {}
24
- results[host[:name].to_sym][:groups] = groups
22
+ private
23
+
24
+ def host_list_filters
25
+ {
26
+ groups: csv_option_names(options[:group]),
27
+ tags: csv_option_names(options[:tag]),
28
+ variables: variable_filter_options(options[:var])
29
+ }
30
+ end
25
31
 
26
- hostvars = {}
27
- host.hostvars_dataset.each do |hv|
28
- hostvars[hv[:name].to_sym] = hv[:value]
29
- end
32
+ def variable_filter_options(value)
33
+ csv_option_names(value).to_h do |entry|
34
+ key, variable_value = entry.split('=', 2)
35
+ abort("ERROR: Invalid variable filter '#{entry}'. Expected key=value.") if variable_value.nil?
30
36
 
31
- unless hostvars.empty?
32
- results[host[:name].to_sym][:hostvars] = hostvars
33
- end
37
+ [key, variable_value]
34
38
  end
35
- fmt.dump(results)
36
39
  end
37
40
  end
38
41
  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,68 +15,13 @@ module Moose
13
15
  #==========================
14
16
  desc 'listvar', 'List all variables associated with the host'
15
17
  def listvars(*argv)
16
- # Convenience
17
- confopts = Moose::Inventory::Config._confopts
18
-
19
- # sanity
20
- if confopts[:ansible] == true
21
- if argv.length != 1
22
- abort('ERROR: Wrong number of arguments for Ansible mode, '\
23
- "#{args.length} for 1.")
24
- end
25
- else
26
- if argv.empty?
27
- abort('ERROR: Wrong number of arguments, '\
28
- "#{args.length} for 1 or more.")
29
- end
30
- end
31
-
32
- # Convenience
33
- db = Moose::Inventory::DB
34
- fmt = Moose::Inventory::Cli::Formatter
35
-
36
- # Arguments
37
- names = argv.uniq.map(&:downcase)
38
-
39
- # process
40
- results = {}
41
-
42
- if confopts[:ansible] == true
43
- # This is the implementation per Ansible specs
44
- name = names.first
45
- host = db.models[:host].find(name: name)
46
- if host.nil?
47
- fmt.warn "The host #{name} does not exist.\n"
48
- else
49
- host.hostvars_dataset.each do |hv|
50
- results[hv[:name].to_sym] = hv[:value]
51
- end
52
- end
18
+ validate_listvars_args(argv)
53
19
 
54
- # Add the Ansible 1.3 '_meta' tag
55
- # see http://docs.ansible.com/ansible/developing_inventory.html#tuning-the-external-inventory-script
56
- results['_meta'.to_sym] = {}
57
- results['_meta'.to_sym]['hostvars'.to_sym] = {}
58
- db.models[:host].each do |host|
59
- results['_meta'.to_sym]['hostvars'.to_sym][host.name.to_sym] = {}
60
- host.hostvars_dataset.each do |hv|
61
- results['_meta'.to_sym]['hostvars'.to_sym][host.name.to_sym][hv[:name].to_sym] = hv[:value]
62
- end
63
- end
20
+ names = normalize_names(argv)
21
+ results = inventory_query.list_host_vars(names: names, ansible: ansible_mode?)
22
+ warn_if_missing_ansible_listvars_entity(:host, names.first)
64
23
 
65
- else
66
- # This our more flexible implementation, which is not compatible
67
- # with the Ansible specs
68
- names.each do |name|
69
- host = db.models[:host].find(name: name)
70
- next if host.nil?
71
- results[name.to_sym] = {}
72
- host.hostvars_dataset.each do |hv|
73
- results[name.to_sym][hv[:name].to_sym] = hv[:value]
74
- end
75
- end
76
- end
77
- fmt.dump(results)
24
+ fmt.dump(results, output_format)
78
25
  end
79
26
  end
80
27
  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/remove_hosts'
6
8
 
7
9
  module Moose
8
10
  module Inventory
@@ -13,49 +15,65 @@ module Moose
13
15
  #==========================
14
16
  desc 'rm HOSTNAME_1 [HOSTNAME_2 ...]',
15
17
  'Remove hosts HOSTNAME_n from the inventory'
16
- def rm(*argv) # rubocop:disable Metrics/AbcSize
17
- #
18
- # Sanity
19
- if argv.empty?
20
- abort('ERROR: Wrong number of arguments, '\
21
- "#{argv.length} for 1 or more.")
22
- end
18
+ option :dry_run, type: :boolean
19
+ option :yes, type: :boolean, desc: 'Confirm destructive removal without prompting'
20
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
21
+ def rm(*argv)
22
+ abort_if_missing_args(argv, 1, '1 or more')
23
+ validate_machine_plan_request!
24
+
25
+ names = normalize_names(argv)
26
+ confirm_destructive_action!("host rm #{names.join(',')}")
27
+ result = remove_hosts_operation.call(names: names, dry_run: options[:dry_run])
28
+ return if machine_plan_output_rendered?(result, command: 'host rm')
29
+
30
+ record_audit({ command: 'host rm', action: 'remove', entity_type: 'host',
31
+ entity_names: names }, result: result, dry_run: options[:dry_run])
32
+ render_remove_hosts_events(result.events)
33
+ print_warning_summary(result)
34
+ end
35
+
36
+ private
37
+
38
+ def remove_hosts_operation
39
+ build_operation(Moose::Inventory::Operations::RemoveHosts)
40
+ end
41
+
42
+ def render_remove_hosts_events(events)
43
+ events.each { |event| render_remove_hosts_event(event) }
44
+ end
23
45
 
24
- # Convenience
25
- db = Moose::Inventory::DB
26
- fmt = Moose::Inventory::Cli::Formatter
27
-
28
- # Arguments
29
- names = argv.uniq.map(&:downcase)
30
-
31
- # Transaction
32
- warn_count = 0
33
- db.transaction do # Transaction start
34
- names.each do |name|
35
- puts "Remove host '#{name}':"
36
- fmt.puts 2, "- Retrieve host '#{name}'..."
37
- host = db.models[:host].find(name: name)
38
- if host.nil?
39
- warn_count += 1
40
- fmt.warn "Host '#{name}' does not exist, skipping.\n"
41
- fmt.puts 4, '- No such host, skipping.'
42
- end
43
- fmt.puts 4, '- OK'
44
- unless host.nil?
45
- fmt.puts 2, "- Destroy host '#{name}'..."
46
- host.remove_all_groups
47
- host.destroy
48
- fmt.puts 4, '- OK'
49
- end
50
- fmt.puts 2, '- All OK'
51
- end
52
- end # Transaction end
53
- if warn_count == 0
54
- puts 'Succeeded.'
55
- else
56
- puts 'Succeeded, with warnings.'
46
+ def render_remove_hosts_event(event)
47
+ payload = event.payload
48
+
49
+ return render_host_rm_progress(event.type, payload) if host_rm_progress_event?(event.type)
50
+ return render_host_rm_warning(payload) if event.type == :host_missing
51
+ return fmt.puts(payload[:indent], '- No such host, skipping.') if event.type == :missing_skipping
52
+ return fmt.puts(payload[:indent], '- OK') if event.type == :ok
53
+
54
+ return fmt.puts 2, '- All OK' if event.type == :host_complete
55
+
56
+ puts 'Dry run complete. No changes applied.' if event.type == :dry_run_summary
57
+ end
58
+
59
+ def host_rm_progress_event?(type)
60
+ %i[host_started retrieving_host destroying_host].include?(type)
61
+ end
62
+
63
+ def render_host_rm_progress(type, payload)
64
+ case type
65
+ when :host_started
66
+ puts "Remove host '#{payload[:name]}':"
67
+ when :retrieving_host
68
+ fmt.puts 2, "- Retrieve host '#{payload[:name]}'..."
69
+ when :destroying_host
70
+ fmt.puts 2, "- Destroy host '#{payload[:name]}'..."
57
71
  end
58
72
  end
73
+
74
+ def render_host_rm_warning(payload)
75
+ fmt.warn "Host '#{payload[:name]}' does not exist, skipping.\n"
76
+ end
59
77
  end
60
78
  end
61
79
  end
@@ -17,65 +17,46 @@ module Moose
17
17
  #==========================
18
18
  desc 'rmgroup HOSTNAME GROUPNAME [GROUPNAME ...]',
19
19
  'dissociation the host from a group'
20
+ option :dry_run, type: :boolean
21
+ option :yes, type: :boolean, desc: 'Confirm destructive dissociation without prompting'
22
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
20
23
  def rmgroup(*args)
21
24
  abort_if_missing_args(args, 2, '2 or more')
25
+ validate_machine_plan_request!
22
26
 
23
27
  name = args[0].downcase
24
28
  groups = normalize_names(args.slice(1, args.length - 1))
25
29
 
26
30
  abort_if_automatic_group(groups)
27
-
28
- context = Moose::Inventory::InventoryContext.new(db: db)
29
- operation = Moose::Inventory::Operations::RemoveAssociations.new(context: context)
30
-
31
- db.transaction do
32
- puts "Dissociate host '#{name}' from groups '#{groups.join(',')}':"
33
- host = fetch_existing_host_for_rmgroup(context, name)
34
- render_host_rmgroup_events(
35
- operation.host_from_groups(host: host, host_name: name, group_names: groups).events
36
- )
37
- fmt.puts 2, '- All OK'
31
+ confirm_destructive_action!("host rmgroup #{name} #{groups.join(',')}")
32
+
33
+ result = remove_groups_from_host(name, groups)
34
+ unless machine_plan_output_rendered?(
35
+ result, command: 'host rmgroup'
36
+ )
37
+ record_audit({ command: 'host rmgroup', action: 'dissociate', entity_type: 'host',
38
+ entity_names: name }, result: result, dry_run: options[:dry_run])
39
+ print_warning_summary(result, success_message: 'Succeeded',
40
+ warning_message: 'Succeeded')
38
41
  end
39
- puts 'Succeeded'
40
42
  end
41
43
 
42
44
  private
43
45
 
44
- def fetch_existing_host_for_rmgroup(context, name)
45
- fmt.puts 2, "- Retrieve host '#{name}'..."
46
- host = context.find_host(name)
47
- raise db.exceptions[:moose], "The host '#{name}' was not found in the database." if host.nil?
48
-
49
- fmt.puts 4, '- OK'
50
- host
51
- end
52
-
53
- def render_host_rmgroup_events(events)
54
- events.each { |event| render_host_rmgroup_event(event) }
55
- end
56
-
57
- def render_host_rmgroup_event(event)
58
- payload = event.payload
59
-
60
- return render_host_rmgroup_warning(payload) if event.type == :host_group_association_missing
61
- return render_host_rmgroup_missing(payload) if event.type == :missing_skipping
62
-
63
- case event.type
64
- when :removing_host_group_association
65
- fmt.puts 2, "- Remove association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
66
- when :adding_automatic_group
67
- fmt.puts 2, "- Add automatic association {host:#{payload[:host]} <-> group:ungrouped}..."
68
- when :ok
69
- fmt.puts payload[:indent], '- OK'
46
+ def remove_groups_from_host(name, groups)
47
+ operation = build_operation(Moose::Inventory::Operations::RemoveAssociations)
48
+ run_host_relation_transaction(heading: "Dissociate host '#{name}' from groups '#{groups.join(',')}':") do
49
+ host = fetch_existing_host_or_raise(name)
50
+ result = operation.host_from_groups(host: host, host_name: name, group_names: groups,
51
+ dry_run: options[:dry_run])
52
+ render_host_rmgroup_events(result.events) unless machine_plan_output_requested?
53
+ result
70
54
  end
71
55
  end
72
56
 
73
- def render_host_rmgroup_warning(payload)
74
- fmt.warn "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} doesn't exist, skipping.\n"
75
- end
76
-
77
- def render_host_rmgroup_missing(payload)
78
- fmt.puts payload[:indent], "- Doesn't exist, skipping."
57
+ def render_host_rmgroup_events(events)
58
+ emitter = host_group_association_removal_emitter(perspective: :host)
59
+ events.each { |event| emitter.call(event) }
79
60
  end
80
61
  end
81
62
  end