moose-inventory 2.0 → 2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +2 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +21 -0
  5. data/BACKLOG.md +630 -8
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +1 -1
  8. data/README.md +315 -39
  9. data/Rakefile +2 -0
  10. data/bin/moose-inventory +2 -1
  11. data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
  12. data/docs/compatibility/cli-output-compatibility.md +76 -0
  13. data/docs/governance/approval-register.md +37 -0
  14. data/docs/maintenance/database-backup-restore-guidance.md +162 -0
  15. data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
  16. data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
  17. data/docs/product/product-brief.md +161 -0
  18. data/docs/product/requirements-baseline.md +477 -0
  19. data/docs/qa/qa-documentation-and-release-gates.md +283 -0
  20. data/docs/release/package-provenance-hardening.md +126 -0
  21. data/docs/release/publishing.md +11 -3
  22. data/docs/release/release-environment-protection.md +70 -0
  23. data/docs/release/release-readiness.md +23 -4
  24. data/docs/security/accepted-risk-register.md +84 -0
  25. data/docs/security/security-privacy-process.md +287 -0
  26. data/docs/security-audit-2026-05-26-rerun.md +2 -2
  27. data/docs/ux/cli-workflow-notes.md +287 -0
  28. data/examples/ansible/ansible.cfg +3 -0
  29. data/examples/ansible/inventory/moose_inventory.yml +5 -0
  30. data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
  31. data/examples/ci/README.md +16 -0
  32. data/examples/ci/github-actions/inventory-review.yml +38 -0
  33. data/examples/ci/inventory/example-snapshot.yml +19 -0
  34. data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
  35. data/lib/moose_inventory/cli/application.rb +133 -5
  36. data/lib/moose_inventory/cli/association_rendering.rb +74 -0
  37. data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
  38. data/lib/moose_inventory/cli/audit.rb +62 -0
  39. data/lib/moose_inventory/cli/audit_recording.rb +40 -0
  40. data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
  41. data/lib/moose_inventory/cli/console.rb +135 -0
  42. data/lib/moose_inventory/cli/db.rb +64 -0
  43. data/lib/moose_inventory/cli/factory.rb +28 -0
  44. data/lib/moose_inventory/cli/formatter.rb +8 -12
  45. data/lib/moose_inventory/cli/group.rb +5 -2
  46. data/lib/moose_inventory/cli/group_add.rb +11 -9
  47. data/lib/moose_inventory/cli/group_addchild.rb +23 -65
  48. data/lib/moose_inventory/cli/group_addhost.rb +16 -67
  49. data/lib/moose_inventory/cli/group_addvar.rb +27 -47
  50. data/lib/moose_inventory/cli/group_get.rb +8 -42
  51. data/lib/moose_inventory/cli/group_list.rb +7 -40
  52. data/lib/moose_inventory/cli/group_listvars.rb +9 -55
  53. data/lib/moose_inventory/cli/group_rm.rb +12 -10
  54. data/lib/moose_inventory/cli/group_rmchild.rb +26 -82
  55. data/lib/moose_inventory/cli/group_rmhost.rb +18 -53
  56. data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
  57. data/lib/moose_inventory/cli/group_tags.rb +33 -0
  58. data/lib/moose_inventory/cli/helpers.rb +68 -1
  59. data/lib/moose_inventory/cli/host.rb +6 -3
  60. data/lib/moose_inventory/cli/host_add.rb +69 -29
  61. data/lib/moose_inventory/cli/host_addgroup.rb +22 -58
  62. data/lib/moose_inventory/cli/host_addvar.rb +28 -52
  63. data/lib/moose_inventory/cli/host_get.rb +9 -37
  64. data/lib/moose_inventory/cli/host_list.rb +24 -21
  65. data/lib/moose_inventory/cli/host_listvars.rb +9 -62
  66. data/lib/moose_inventory/cli/host_rm.rb +60 -42
  67. data/lib/moose_inventory/cli/host_rmgroup.rb +25 -44
  68. data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
  69. data/lib/moose_inventory/cli/host_tags.rb +33 -0
  70. data/lib/moose_inventory/cli/listvars_support.rb +55 -0
  71. data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
  72. data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
  73. data/lib/moose_inventory/cli/tag_support.rb +97 -0
  74. data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
  75. data/lib/moose_inventory/config/config.rb +185 -108
  76. data/lib/moose_inventory/db/db.rb +170 -195
  77. data/lib/moose_inventory/db/exceptions.rb +6 -3
  78. data/lib/moose_inventory/db/models.rb +16 -0
  79. data/lib/moose_inventory/db/schema_migrations.rb +248 -0
  80. data/lib/moose_inventory/inventory_context.rb +68 -2
  81. data/lib/moose_inventory/operations/add_associations.rb +20 -16
  82. data/lib/moose_inventory/operations/add_groups.rb +21 -13
  83. data/lib/moose_inventory/operations/add_hosts.rb +30 -17
  84. data/lib/moose_inventory/operations/add_variables.rb +77 -0
  85. data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
  86. data/lib/moose_inventory/operations/group_child_relations.rb +23 -16
  87. data/lib/moose_inventory/operations/group_cleanup.rb +23 -8
  88. data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
  89. data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
  90. data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
  91. data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
  92. data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
  93. data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
  94. data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
  95. data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
  96. data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
  97. data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
  98. data/lib/moose_inventory/operations/query_inventory.rb +47 -0
  99. data/lib/moose_inventory/operations/remove_associations.rb +30 -18
  100. data/lib/moose_inventory/operations/remove_groups.rb +12 -12
  101. data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
  102. data/lib/moose_inventory/operations/remove_variables.rb +67 -0
  103. data/lib/moose_inventory/runtime_options.rb +31 -0
  104. data/lib/moose_inventory/version.rb +3 -1
  105. data/lib/moose_inventory.rb +10 -7
  106. data/moose-inventory.gemspec +19 -35
  107. data/scripts/check.sh +1 -0
  108. data/scripts/ci/check_generated_artifacts.sh +41 -0
  109. data/scripts/ci/check_permissions.sh +2 -0
  110. data/scripts/ci/check_rubocop.sh +30 -25
  111. data/scripts/files.rb +5 -4
  112. data/spec/examples/ci_examples_spec.rb +37 -0
  113. data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
  114. data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
  115. data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
  116. data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
  117. data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
  118. data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
  119. data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
  120. data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
  121. data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
  122. data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
  123. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
  124. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
  125. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
  126. data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
  127. data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
  128. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
  129. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +136 -96
  130. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +66 -41
  131. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
  132. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
  133. data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
  134. data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
  135. data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
  136. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
  137. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
  138. data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
  139. data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
  140. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
  141. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
  142. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
  143. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
  144. data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
  145. data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
  146. data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
  147. data/spec/lib/moose_inventory/db/db_spec.rb +396 -36
  148. data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
  149. data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
  150. data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
  151. data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
  152. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +34 -0
  153. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +15 -0
  154. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +13 -0
  155. data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
  156. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +46 -0
  157. data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
  158. data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
  159. data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
  160. data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
  161. data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
  162. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +35 -0
  163. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +21 -0
  164. data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
  165. data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
  166. data/spec/shared/shared_config_setup.rb +4 -3
  167. data/spec/spec_helper.rb +50 -40
  168. data/spec/support/cli_harness.rb +33 -0
  169. metadata +80 -41
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './formatter.rb'
4
+ require_relative '../inventory_context'
5
+ require_relative '../operations/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
@@ -1,11 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../inventory_context'
4
+ require_relative 'audit_recording'
5
+ require_relative 'association_rendering'
6
+ require_relative 'child_relation_rendering'
7
+ require_relative 'factory'
8
+ require_relative 'listvars_support'
9
+ require_relative 'plan_rendering'
10
+ require_relative 'relation_transaction_support'
11
+ require_relative 'tag_support'
12
+ require_relative 'variable_rendering'
13
+
3
14
  module Moose
4
15
  module Inventory
5
16
  module Cli
6
17
  ##
7
18
  # Shared helpers for Thor command classes.
8
19
  module Helpers
20
+ include Moose::Inventory::Cli::AssociationRendering
21
+ include Moose::Inventory::Cli::AuditRecording
22
+ include Moose::Inventory::Cli::ChildRelationRendering
23
+ include Moose::Inventory::Cli::ListvarsSupport
24
+ include Moose::Inventory::Cli::PlanRendering
25
+ include Moose::Inventory::Cli::RelationTransactionSupport
26
+ include Moose::Inventory::Cli::TagSupport
27
+ include Moose::Inventory::Cli::VariableRendering
28
+
9
29
  AUTOMATIC_GROUP = 'ungrouped'
10
30
 
11
31
  private
@@ -14,10 +34,38 @@ module Moose
14
34
  Moose::Inventory::DB
15
35
  end
16
36
 
37
+ def inventory_context
38
+ @inventory_context ||= Moose::Inventory::InventoryContext.new(db: db)
39
+ end
40
+
41
+ def cli_factory
42
+ @cli_factory ||= Moose::Inventory::Cli::Factory.new(context: inventory_context)
43
+ end
44
+
45
+ def build_operation(operation_class, **)
46
+ cli_factory.operation(operation_class, **)
47
+ end
48
+
49
+ def inventory_query
50
+ cli_factory.query_inventory
51
+ end
52
+
17
53
  def fmt
18
54
  Moose::Inventory::Cli::Formatter
19
55
  end
20
56
 
57
+ def runtime_options
58
+ Moose::Inventory::Config.runtime_options
59
+ end
60
+
61
+ def output_format
62
+ runtime_options.output_format
63
+ end
64
+
65
+ def ansible_mode?
66
+ runtime_options.ansible?
67
+ end
68
+
21
69
  def normalize_names(values)
22
70
  values.uniq.map(&:downcase)
23
71
  end
@@ -38,12 +86,31 @@ module Moose
38
86
  abort(message || "ERROR: Cannot manually manipulate the automatic group '#{AUTOMATIC_GROUP}'.")
39
87
  end
40
88
 
89
+ def confirm_destructive_action!(description)
90
+ return if options[:dry_run] || options[:yes]
91
+
92
+ abort("ERROR: #{description} is destructive. Re-run with --yes to confirm, or use --dry-run to preview.")
93
+ end
94
+
41
95
  def association_exists?(dataset, name)
42
96
  !dataset.nil? && !dataset[name: name].nil?
43
97
  end
44
98
 
99
+ def exception_to_s(error)
100
+ error.to_s
101
+ end
102
+
103
+ def print_warning_summary(result, success_message: 'Succeeded.', warning_message: 'Succeeded, with warnings.')
104
+ warning_count = result.respond_to?(:warning_count) ? result.warning_count : 0
105
+ print_success_summary(warning_count.zero? ? success_message : warning_message)
106
+ end
107
+
108
+ def print_success_summary(message = 'Succeeded.')
109
+ puts message
110
+ end
111
+
45
112
  def automatic_group
46
- db.models[:group].find_or_create(name: AUTOMATIC_GROUP)
113
+ inventory_context.automatic_group
47
114
  end
48
115
 
49
116
  def remove_automatic_group_from_host(host, indent:, message:)
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
 
4
- require_relative './formatter.rb'
5
- require_relative './helpers.rb'
6
- require_relative '../db/exceptions.rb'
6
+ require_relative 'formatter'
7
+ require_relative 'helpers'
8
+ require_relative '../db/exceptions'
7
9
 
8
10
  module Moose
9
11
  module Inventory
@@ -22,6 +24,7 @@ module Moose
22
24
  require_relative 'host_addvar'
23
25
  require_relative 'host_listvars'
24
26
  require_relative 'host_rmvar'
27
+ require_relative 'host_tags'
25
28
  end
26
29
  end
27
30
  end
@@ -15,12 +15,28 @@ module Moose
15
15
  ##
16
16
  # Class implementing the "host" methods of the CLI
17
17
  class Host
18
+ ADD_HOST_EVENT_RENDERERS = {
19
+ host_started: :render_add_host_started,
20
+ creating_host: :render_add_host_creation,
21
+ host_exists: :render_add_host_exists_warning,
22
+ ok: :render_add_host_ok,
23
+ adding_association: :render_add_host_association,
24
+ group_missing_created: :render_add_host_missing_group_warning,
25
+ association_exists: :render_add_host_association_exists_warning,
26
+ adding_automatic_group: :render_add_host_automatic_group,
27
+ host_complete: :render_add_host_complete,
28
+ dry_run_summary: :render_dry_run_summary
29
+ }.freeze
30
+
18
31
  #==========================
19
32
  desc 'add HOSTNAME_1 [HOSTNAME_2 ...]',
20
33
  'Add a hosts HOSTNAME_n to the inventory'
21
34
  option :groups
35
+ option :dry_run, type: :boolean
36
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
22
37
  def add(*argv)
23
38
  abort_if_missing_args(argv, 1, '1 or more')
39
+ validate_machine_plan_request!
24
40
 
25
41
  # Arguments
26
42
  names = normalize_names(argv)
@@ -32,11 +48,14 @@ module Moose
32
48
  # Sanity
33
49
  abort_if_automatic_group(groups)
34
50
 
35
- result = Moose::Inventory::Operations::AddHosts
36
- .new(context: Moose::Inventory::InventoryContext.new(db: db))
37
- .call(names: names, groups: groups)
51
+ result = build_operation(Moose::Inventory::Operations::AddHosts)
52
+ .call(names: names, groups: groups, dry_run: options[:dry_run])
53
+ return if machine_plan_output_rendered?(result, command: 'host add')
54
+
55
+ record_audit({ command: 'host add', action: 'add', entity_type: 'host',
56
+ entity_names: names }, result: result, dry_run: options[:dry_run])
38
57
  render_add_hosts_events(result.events)
39
- puts 'Succeeded'
58
+ print_warning_summary(result, success_message: 'Succeeded', warning_message: 'Succeeded')
40
59
  end
41
60
 
42
61
  private
@@ -46,31 +65,52 @@ module Moose
46
65
  events.each { |event| render_add_hosts_event(event) }
47
66
  end
48
67
 
49
- def render_add_hosts_event(event) # rubocop:disable Metrics/CyclomaticComplexity
50
- payload = event.payload
51
- case event.type
52
- when :host_started
53
- puts "Add host '#{payload[:name]}':"
54
- when :creating_host
55
- fmt.puts 2, "- Creating host '#{payload[:name]}'..."
56
- when :host_exists
57
- fmt.warn "The host '#{payload[:name]}' already exists, skipping creation.\n"
58
- when :ok
59
- fmt.puts payload[:indent], '- OK'
60
- when :adding_association
61
- fmt.puts 2, "- Adding association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
62
- when :group_missing_created
63
- fmt.warn "The group '#{payload[:name]}' doesn't exist, but will be created.\n"
64
- when :association_exists
65
- fmt.warn(
66
- "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} " \
67
- "already exists, skipping creation.\n"
68
- )
69
- when :adding_automatic_group
70
- fmt.puts 2, "- Adding automatic association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
71
- when :host_complete
72
- fmt.puts 2, '- All OK'
73
- end
68
+ def render_add_hosts_event(event)
69
+ renderer = ADD_HOST_EVENT_RENDERERS[event.type]
70
+ send(renderer, event.payload) unless renderer.nil?
71
+ end
72
+
73
+ def render_add_host_started(payload)
74
+ puts "Add host '#{payload[:name]}':"
75
+ end
76
+
77
+ def render_add_host_creation(payload)
78
+ fmt.puts 2, "- Creating host '#{payload[:name]}'..."
79
+ end
80
+
81
+ def render_add_host_exists_warning(payload)
82
+ fmt.warn "The host '#{payload[:name]}' already exists, skipping creation.\n"
83
+ end
84
+
85
+ def render_add_host_ok(payload)
86
+ fmt.puts payload[:indent], '- OK'
87
+ end
88
+
89
+ def render_add_host_association(payload)
90
+ fmt.puts 2, "- Adding association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
91
+ end
92
+
93
+ def render_add_host_missing_group_warning(payload)
94
+ fmt.warn "The group '#{payload[:name]}' doesn't exist, but will be created.\n"
95
+ end
96
+
97
+ def render_add_host_association_exists_warning(payload)
98
+ fmt.warn(
99
+ "Association {host:#{payload[:host]} <-> group:#{payload[:group]}} " \
100
+ "already exists, skipping creation.\n"
101
+ )
102
+ end
103
+
104
+ def render_add_host_automatic_group(payload)
105
+ fmt.puts 2, "- Adding automatic association {host:#{payload[:host]} <-> group:#{payload[:group]}}..."
106
+ end
107
+
108
+ def render_add_host_complete(_payload)
109
+ fmt.puts 2, '- All OK'
110
+ end
111
+
112
+ def render_dry_run_summary(_payload)
113
+ puts 'Dry run complete. No changes applied.'
74
114
  end
75
115
  end
76
116
  end
@@ -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