moose-inventory 1.0.9 → 2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +15 -1
  3. data/.github/workflows/release.yml +60 -0
  4. data/.gitignore +2 -1
  5. data/.gitleaks.toml +9 -0
  6. data/.rubocop.yml +49 -0
  7. data/BACKLOG.md +752 -24
  8. data/Gemfile +2 -0
  9. data/Gemfile.lock +36 -1
  10. data/README.md +340 -44
  11. data/Rakefile +2 -0
  12. data/bin/moose-inventory +2 -1
  13. data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
  14. data/docs/compatibility/cli-output-compatibility.md +76 -0
  15. data/docs/governance/approval-register.md +37 -0
  16. data/docs/maintenance/database-backup-restore-guidance.md +162 -0
  17. data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
  18. data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
  19. data/docs/product/product-brief.md +161 -0
  20. data/docs/product/requirements-baseline.md +477 -0
  21. data/docs/qa/qa-documentation-and-release-gates.md +283 -0
  22. data/docs/release/package-provenance-hardening.md +126 -0
  23. data/docs/release/publishing.md +54 -50
  24. data/docs/release/release-environment-protection.md +70 -0
  25. data/docs/release/release-readiness.md +37 -4
  26. data/docs/security/accepted-risk-register.md +84 -0
  27. data/docs/security/security-privacy-process.md +287 -0
  28. data/docs/security-audit-2026-05-26-rerun.md +75 -0
  29. data/docs/security-audit-2026-05-26.md +63 -0
  30. data/docs/ux/cli-workflow-notes.md +287 -0
  31. data/examples/ansible/ansible.cfg +3 -0
  32. data/examples/ansible/inventory/moose_inventory.yml +5 -0
  33. data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
  34. data/examples/ci/README.md +16 -0
  35. data/examples/ci/github-actions/inventory-review.yml +38 -0
  36. data/examples/ci/inventory/example-snapshot.yml +19 -0
  37. data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
  38. data/lib/moose_inventory/cli/application.rb +133 -5
  39. data/lib/moose_inventory/cli/association_rendering.rb +74 -0
  40. data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
  41. data/lib/moose_inventory/cli/audit.rb +62 -0
  42. data/lib/moose_inventory/cli/audit_recording.rb +40 -0
  43. data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
  44. data/lib/moose_inventory/cli/console.rb +135 -0
  45. data/lib/moose_inventory/cli/db.rb +64 -0
  46. data/lib/moose_inventory/cli/factory.rb +28 -0
  47. data/lib/moose_inventory/cli/formatter.rb +8 -12
  48. data/lib/moose_inventory/cli/group.rb +7 -1
  49. data/lib/moose_inventory/cli/group_add.rb +91 -73
  50. data/lib/moose_inventory/cli/group_addchild.rb +41 -66
  51. data/lib/moose_inventory/cli/group_addhost.rb +33 -71
  52. data/lib/moose_inventory/cli/group_addvar.rb +27 -47
  53. data/lib/moose_inventory/cli/group_get.rb +8 -42
  54. data/lib/moose_inventory/cli/group_list.rb +7 -40
  55. data/lib/moose_inventory/cli/group_listvars.rb +9 -55
  56. data/lib/moose_inventory/cli/group_rm.rb +105 -73
  57. data/lib/moose_inventory/cli/group_rmchild.rb +47 -57
  58. data/lib/moose_inventory/cli/group_rmhost.rb +34 -61
  59. data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
  60. data/lib/moose_inventory/cli/group_tags.rb +33 -0
  61. data/lib/moose_inventory/cli/helpers.rb +143 -0
  62. data/lib/moose_inventory/cli/host.rb +8 -2
  63. data/lib/moose_inventory/cli/host_add.rb +91 -66
  64. data/lib/moose_inventory/cli/host_addgroup.rb +39 -66
  65. data/lib/moose_inventory/cli/host_addvar.rb +28 -52
  66. data/lib/moose_inventory/cli/host_get.rb +9 -37
  67. data/lib/moose_inventory/cli/host_list.rb +24 -21
  68. data/lib/moose_inventory/cli/host_listvars.rb +9 -62
  69. data/lib/moose_inventory/cli/host_rm.rb +60 -42
  70. data/lib/moose_inventory/cli/host_rmgroup.rb +39 -55
  71. data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
  72. data/lib/moose_inventory/cli/host_tags.rb +33 -0
  73. data/lib/moose_inventory/cli/listvars_support.rb +55 -0
  74. data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
  75. data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
  76. data/lib/moose_inventory/cli/tag_support.rb +97 -0
  77. data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
  78. data/lib/moose_inventory/config/config.rb +185 -108
  79. data/lib/moose_inventory/db/db.rb +188 -193
  80. data/lib/moose_inventory/db/exceptions.rb +6 -3
  81. data/lib/moose_inventory/db/models.rb +16 -0
  82. data/lib/moose_inventory/db/schema_migrations.rb +248 -0
  83. data/lib/moose_inventory/inventory_context.rb +116 -0
  84. data/lib/moose_inventory/operations/add_associations.rb +131 -0
  85. data/lib/moose_inventory/operations/add_groups.rb +123 -0
  86. data/lib/moose_inventory/operations/add_hosts.rb +123 -0
  87. data/lib/moose_inventory/operations/add_variables.rb +77 -0
  88. data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
  89. data/lib/moose_inventory/operations/group_child_relations.rb +125 -0
  90. data/lib/moose_inventory/operations/group_cleanup.rb +70 -0
  91. data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
  92. data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
  93. data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
  94. data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
  95. data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
  96. data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
  97. data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
  98. data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
  99. data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
  100. data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
  101. data/lib/moose_inventory/operations/query_inventory.rb +47 -0
  102. data/lib/moose_inventory/operations/remove_associations.rb +113 -0
  103. data/lib/moose_inventory/operations/remove_groups.rb +79 -0
  104. data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
  105. data/lib/moose_inventory/operations/remove_variables.rb +67 -0
  106. data/lib/moose_inventory/runtime_options.rb +31 -0
  107. data/lib/moose_inventory/version.rb +3 -1
  108. data/lib/moose_inventory.rb +10 -7
  109. data/moose-inventory.gemspec +22 -35
  110. data/scripts/check.sh +3 -0
  111. data/scripts/ci/check_generated_artifacts.sh +41 -0
  112. data/scripts/ci/check_permissions.sh +5 -0
  113. data/scripts/ci/check_rubocop.sh +33 -0
  114. data/scripts/ci/check_secrets.sh +26 -0
  115. data/scripts/ci/check_security.sh +18 -0
  116. data/scripts/ci/install_security_tools.sh +47 -0
  117. data/scripts/files.rb +5 -4
  118. data/scripts/install_dependencies.sh +2 -0
  119. data/spec/examples/ci_examples_spec.rb +37 -0
  120. data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
  121. data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
  122. data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
  123. data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
  124. data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
  125. data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
  126. data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
  127. data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
  128. data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
  129. data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
  130. data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
  131. data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
  132. data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
  133. data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
  134. data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
  135. data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
  136. data/spec/lib/moose_inventory/cli/group_rm_spec.rb +165 -85
  137. data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +100 -30
  138. data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
  139. data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
  140. data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
  141. data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
  142. data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
  143. data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
  144. data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
  145. data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
  146. data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
  147. data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
  148. data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
  149. data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
  150. data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
  151. data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
  152. data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
  153. data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
  154. data/spec/lib/moose_inventory/db/db_spec.rb +551 -29
  155. data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
  156. data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
  157. data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
  158. data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
  159. data/spec/lib/moose_inventory/operations/add_associations_spec.rb +111 -0
  160. data/spec/lib/moose_inventory/operations/add_groups_spec.rb +80 -0
  161. data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +82 -0
  162. data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
  163. data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +122 -0
  164. data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
  165. data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
  166. data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
  167. data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
  168. data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
  169. data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +113 -0
  170. data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +78 -0
  171. data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
  172. data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
  173. data/spec/shared/shared_config_setup.rb +4 -3
  174. data/spec/spec_helper.rb +50 -40
  175. data/spec/support/cli_harness.rb +33 -0
  176. metadata +163 -35
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
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
+
14
+ module Moose
15
+ module Inventory
16
+ module Cli
17
+ ##
18
+ # Shared helpers for Thor command classes.
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
+
29
+ AUTOMATIC_GROUP = 'ungrouped'
30
+
31
+ private
32
+
33
+ def db
34
+ Moose::Inventory::DB
35
+ end
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
+
53
+ def fmt
54
+ Moose::Inventory::Cli::Formatter
55
+ end
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
+
69
+ def normalize_names(values)
70
+ values.uniq.map(&:downcase)
71
+ end
72
+
73
+ def csv_option_names(value)
74
+ (value || '').downcase.split(',').uniq
75
+ end
76
+
77
+ def abort_if_missing_args(args, minimum, label)
78
+ return unless args.length < minimum
79
+
80
+ abort("ERROR: Wrong number of arguments, #{args.length} for #{label}.")
81
+ end
82
+
83
+ def abort_if_automatic_group(names, message = nil)
84
+ return unless names.include?(AUTOMATIC_GROUP)
85
+
86
+ abort(message || "ERROR: Cannot manually manipulate the automatic group '#{AUTOMATIC_GROUP}'.")
87
+ end
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
+
95
+ def association_exists?(dataset, name)
96
+ !dataset.nil? && !dataset[name: name].nil?
97
+ end
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
+
112
+ def automatic_group
113
+ inventory_context.automatic_group
114
+ end
115
+
116
+ def remove_automatic_group_from_host(host, indent:, message:)
117
+ ungrouped = host.groups_dataset[name: AUTOMATIC_GROUP]
118
+ return if ungrouped.nil?
119
+
120
+ fmt.puts indent, message
121
+ host.remove_group(ungrouped)
122
+ fmt.puts indent + 2, '- OK'
123
+ end
124
+
125
+ def add_automatic_group_to_host_if_last_group(host, indent:, message:)
126
+ add_automatic_group_to_host_if_group_count(host, 1, indent: indent, message: message)
127
+ end
128
+
129
+ def add_automatic_group_to_host_if_no_groups(host, indent:, message:)
130
+ add_automatic_group_to_host_if_group_count(host, 0, indent: indent, message: message)
131
+ end
132
+
133
+ def add_automatic_group_to_host_if_group_count(host, group_count, indent:, message:)
134
+ return unless host.groups_dataset.count == group_count
135
+
136
+ fmt.puts indent, message
137
+ host.add_group(automatic_group)
138
+ fmt.puts indent + 2, '- OK'
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -1,8 +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 '../db/exceptions.rb'
6
+ require_relative 'formatter'
7
+ require_relative 'helpers'
8
+ require_relative '../db/exceptions'
6
9
 
7
10
  module Moose
8
11
  module Inventory
@@ -10,6 +13,8 @@ module Moose
10
13
  ##
11
14
  # Class implementing the "host" methods of the CLI
12
15
  class Host < Thor
16
+ include Moose::Inventory::Cli::Helpers
17
+
13
18
  require_relative 'host_add'
14
19
  require_relative 'host_get'
15
20
  require_relative 'host_list'
@@ -19,6 +24,7 @@ module Moose
19
24
  require_relative 'host_addvar'
20
25
  require_relative 'host_listvars'
21
26
  require_relative 'host_rmvar'
27
+ require_relative 'host_tags'
22
28
  end
23
29
  end
24
30
  end
@@ -1,9 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
  require 'indentation'
4
6
 
5
- require_relative './formatter.rb'
6
- require_relative '../db/exceptions.rb'
7
+ require_relative 'formatter'
8
+ require_relative '../db/exceptions'
9
+ require_relative '../inventory_context'
10
+ require_relative '../operations/add_hosts'
7
11
 
8
12
  module Moose
9
13
  module Inventory
@@ -11,81 +15,102 @@ module Moose
11
15
  ##
12
16
  # Class implementing the "host" methods of the CLI
13
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
+
14
31
  #==========================
15
32
  desc 'add HOSTNAME_1 [HOSTNAME_2 ...]',
16
33
  'Add a hosts HOSTNAME_n to the inventory'
17
34
  option :groups
18
- # rubocop:disable Metrics/LineLength
19
- def add(*argv) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
20
- # rubocop:enable Metrics/LineLength
21
- if argv.empty?
22
- abort('ERROR: Wrong number of arguments, '\
23
- "#{argv.length} for 1 or more.")
24
- end
25
-
26
- # Convenience
27
- db = Moose::Inventory::DB
28
- fmt = Moose::Inventory::Cli::Formatter
35
+ option :dry_run, type: :boolean
36
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
37
+ def add(*argv)
38
+ abort_if_missing_args(argv, 1, '1 or more')
39
+ validate_machine_plan_request!
29
40
 
30
41
  # Arguments
31
- names = argv.uniq.map(&:downcase)
42
+ names = normalize_names(argv)
32
43
 
33
44
  # split(/\W+/) splits on hyphens too, which is not what we want
34
45
  # groups = options[:groups].downcase.split(/\W+/).uniq
35
- options[:groups].nil? && options[:groups] = ''
36
- groups = options[:groups].downcase.split(',').uniq
46
+ groups = csv_option_names(options[:groups])
37
47
 
38
48
  # Sanity
39
- if groups.include?('ungrouped')
40
- abort('ERROR: Cannot manually manipulate '\
41
- "the automatic group 'ungrouped'.")
42
- end
43
-
44
- # Process
45
- db.transaction do # Transaction start
46
- fmt.reset_indent
47
-
48
- names.each do |name|
49
- puts "Add host '#{name}':"
50
- fmt.puts 2, "- Creating host '#{name}'..."
51
- host = db.models[:host].find(name: name)
52
- groups_ds = nil
53
- if host.nil?
54
- host = db.models[:host].create(name: name)
55
- else
56
- fmt.warn "The host '#{name}' already exists, skipping creation.\n"
57
- groups_ds = host.groups_dataset
58
- end
59
- fmt.puts 4, '- OK'
60
-
61
- groups.each do |g|
62
- next if g.nil? || g.empty?
63
- fmt.puts 2, "- Adding association {host:#{name} <-> group:#{g}}..."
64
- group = db.models[:group].find(name: g)
65
- if group.nil?
66
- fmt.warn "The group '#{g}' doesn't exist, but will be created.\n"
67
- group = db.models[:group].create(name: g)
68
- end
69
- if !groups_ds.nil? && !groups_ds[name: g].nil?
70
- fmt.warn "Association {host:#{name} <-> group:#{g}} already exists, skipping creation.\n"
71
- else
72
- host.add_group(group)
73
- end
74
- fmt.puts 4, '- OK'
75
- end
76
-
77
- # Handle the automatic 'ungrouped' group
78
- groups_ds = host.groups_dataset
79
- if !groups_ds.nil? && groups_ds.count == 0
80
- fmt.puts 2, "- Adding automatic association {host:#{name} <-> group:ungrouped}..."
81
- ungrouped = db.models[:group].find_or_create(name: 'ungrouped')
82
- host.add_group(ungrouped)
83
- fmt.puts 4, '- OK'
84
- end
85
- fmt.puts 2, '- All OK'
86
- end
87
- end # Transaction end
88
- puts 'Succeeded'
49
+ abort_if_automatic_group(groups)
50
+
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])
57
+ render_add_hosts_events(result.events)
58
+ print_warning_summary(result, success_message: 'Succeeded', warning_message: 'Succeeded')
59
+ end
60
+
61
+ private
62
+
63
+ def render_add_hosts_events(events)
64
+ fmt.reset_indent
65
+ events.each { |event| render_add_hosts_event(event) }
66
+ end
67
+
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.'
89
114
  end
90
115
  end
91
116
  end
@@ -1,8 +1,12 @@
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 'formatter'
7
+ require_relative '../db/exceptions'
8
+ require_relative '../inventory_context'
9
+ require_relative '../operations/add_associations'
6
10
 
7
11
  module Moose
8
12
  module Inventory
@@ -12,75 +16,44 @@ module Moose
12
16
  class Host
13
17
  desc 'addgroup HOSTNAME GROUPNAME [GROUPNAME ...]',
14
18
  'Associate the host with a group'
15
- # rubocop:disable Metrics/LineLength
16
- def addgroup(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
17
- # rubocop:enable Metrics/LineLength
18
- # Sanity
19
- if args.length < 2
20
- abort('ERROR: Wrong number of arguments, '\
21
- "#{args.length} for 2 or more.")
19
+ option :dry_run, type: :boolean
20
+ option :plan_format, type: :string, desc: 'Emit dry-run plan events as yaml|json|pjson'
21
+ def addgroup(*args)
22
+ abort_if_missing_args(args, 2, '2 or more')
23
+ validate_machine_plan_request!
24
+
25
+ name = args[0].downcase
26
+ groups = normalize_names(args.slice(1, args.length - 1))
27
+
28
+ abort_if_automatic_group(groups)
29
+
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')
22
38
  end
39
+ end
23
40
 
24
- # Convenience
25
- db = Moose::Inventory::DB
26
- fmt = Moose::Inventory::Cli::Formatter
27
-
28
- # Arguments
29
- name = args[0].downcase
30
- groups = args.slice(1, args.length - 1).uniq.map(&:downcase)
41
+ private
31
42
 
32
- # Sanity
33
- if groups.include?('ungrouped')
34
- abort 'ERROR: Cannot manually manipulate the automatic '\
35
- 'group \'ungrouped\'.'
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
36
51
  end
52
+ end
37
53
 
38
- # Transaction
39
- db.transaction do # Transaction start
40
- puts "Associate host '#{name}' with groups '#{groups.join(',')}':"
41
- # Get the target host
42
- fmt.puts 2, "- Retrieve host '#{name}'..."
43
- host = db.models[:host].find(name: name)
44
- if host.nil?
45
- fail db.exceptions[:moose], "The host '#{name}' "\
46
- 'was not found in the database.'
47
- end
48
- fmt.puts 4, '- OK'
49
-
50
- # Associate host with the groups
51
- groups_ds = host.groups_dataset
52
- groups.each do |g|
53
- fmt.puts 2, "- Add association {host:#{name} <-> group:#{g}}..."
54
-
55
- # Check against existing associations
56
- if !groups_ds[name: g].nil?
57
- fmt.warn "Association {host:#{name} <-> group:#{g}} already exists, skipping."
58
- fmt.puts 4, '- Already exists, skipping.'
59
- else
60
- # Add new association
61
- group = db.models[:group].find(name: g)
62
- if group.nil?
63
- fmt.warn "Group '#{g}' does not exist and will be created."
64
- fmt.puts 4, '- Group does not exist, creating now...'
65
- group = db.models[:group].create(name: g)
66
- fmt.puts 6, '- OK'
67
- end
68
- host.add_group(group)
69
- end
70
- fmt.puts 4, '- OK'
71
- end
72
-
73
- # Handle 'ungrouped' group automation
74
- unless groups_ds[name: 'ungrouped'].nil?
75
- fmt.puts 2, '- Remove automatic association '\
76
- "{host:#{name} <-> group:ungrouped}..."
77
- ungrouped = db.models[:group].find(name: 'ungrouped')
78
- host.remove_group(ungrouped) unless ungrouped.nil?
79
- fmt.puts 4, '- OK'
80
- end
81
- fmt.puts 2, '- All OK'
82
- end # Transaction end
83
- puts 'Succeeded'
54
+ def render_host_addgroup_events(events)
55
+ emitter = host_group_association_addition_emitter(perspective: :host)
56
+ events.each { |event| emitter.call(event) }
84
57
  end
85
58
  end
86
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