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,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moose
4
+ module Inventory
5
+ module Operations
6
+ class QueryInventory
7
+ # Host-focused read queries.
8
+ class HostQueries < BaseQuery
9
+ def get_hosts(names:)
10
+ names.each_with_object({}) do |name, results|
11
+ host = context.find_host(name)
12
+ next if host.nil?
13
+
14
+ results[host.name.to_sym] = host_data(host)
15
+ end
16
+ end
17
+
18
+ def list_hosts(filters: {})
19
+ dataset = filtered_hosts_dataset(filters)
20
+ return {} if dataset.nil?
21
+
22
+ dataset.order(:id).all.to_h do |host|
23
+ [host.name.to_sym, host_data(host)]
24
+ end
25
+ end
26
+
27
+ def list_host_vars(names:, ansible:)
28
+ return ansible_host_vars(names.first) if ansible
29
+
30
+ names.each_with_object({}) do |name, results|
31
+ host = context.find_host(name)
32
+ next if host.nil?
33
+
34
+ results[name.to_sym] = variables_hash(host.hostvars_dataset)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def host_data(host)
41
+ {}.tap do |data|
42
+ groups = host.groups_dataset.map(:name)
43
+ data[:groups] = groups unless groups.empty?
44
+
45
+ tags = host.tags_dataset.map(:name).sort
46
+ data[:tags] = tags unless tags.empty?
47
+
48
+ hostvars = variables_hash(host.hostvars_dataset)
49
+ data[:hostvars] = hostvars unless hostvars.empty?
50
+ end
51
+ end
52
+
53
+ def filtered_hosts_dataset(filters)
54
+ dataset = context.hosts_dataset
55
+ dataset = filter_hosts_by_groups(dataset, filters.fetch(:groups, []))
56
+ return nil if dataset.nil?
57
+
58
+ dataset = filter_hosts_by_tags(dataset, filters.fetch(:tags, []))
59
+ return nil if dataset.nil?
60
+
61
+ filter_hosts_by_variables(dataset, filters.fetch(:variables, {}))
62
+ end
63
+
64
+ def filter_hosts_by_groups(dataset, groups)
65
+ groups.reduce(dataset) do |current_dataset, group_name|
66
+ group = context.find_group(group_name)
67
+ return nil if group.nil?
68
+
69
+ current_dataset.where(id: context.db_dataset(:groups_hosts).where(group_id: group.id).select(:host_id))
70
+ end
71
+ end
72
+
73
+ def filter_hosts_by_tags(dataset, tags)
74
+ tags.reduce(dataset) do |current_dataset, tag_name|
75
+ tag = context.find_tag(tag_name)
76
+ return nil if tag.nil?
77
+
78
+ current_dataset.where(id: context.db_dataset(:hosts_tags).where(tag_id: tag.id).select(:host_id))
79
+ end
80
+ end
81
+
82
+ def filter_hosts_by_variables(dataset, variables)
83
+ variables.reduce(dataset) do |current_dataset, (name, value)|
84
+ current_dataset.where(
85
+ id: context.db_dataset(:hostvars).where(name: name, value: value).select(:host_id)
86
+ )
87
+ end
88
+ end
89
+
90
+ def ansible_host_vars(name)
91
+ results = {}
92
+ host = context.find_host(name)
93
+ results.merge!(variables_hash(host.hostvars_dataset)) unless host.nil?
94
+
95
+ results[:_meta] = {
96
+ hostvars: context.all_hosts.to_h do |entry|
97
+ [entry.name.to_sym, variables_hash(entry.hostvars_dataset)]
98
+ end
99
+ }
100
+ results
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'query_inventory/base_query'
4
+ require_relative 'query_inventory/host_queries'
5
+ require_relative 'query_inventory/group_queries'
6
+
7
+ module Moose
8
+ module Inventory
9
+ module Operations
10
+ # Read-only inventory query seam for host/group CLI commands.
11
+ class QueryInventory
12
+ def initialize(context:)
13
+ @host_queries = HostQueries.new(context: context)
14
+ @group_queries = GroupQueries.new(context: context)
15
+ end
16
+
17
+ def get_hosts(names:)
18
+ host_queries.get_hosts(names: names)
19
+ end
20
+
21
+ def list_hosts(filters: {})
22
+ host_queries.list_hosts(filters: filters)
23
+ end
24
+
25
+ def list_host_vars(names:, ansible:)
26
+ host_queries.list_host_vars(names: names, ansible: ansible)
27
+ end
28
+
29
+ def get_groups(names:)
30
+ group_queries.get_groups(names: names)
31
+ end
32
+
33
+ def list_groups(ansible:)
34
+ group_queries.list_groups(ansible: ansible)
35
+ end
36
+
37
+ def list_group_vars(names:, ansible:)
38
+ group_queries.list_group_vars(names: names, ansible: ansible)
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :group_queries, :host_queries
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'operation_event_support'
4
+
5
+ module Moose
6
+ module Inventory
7
+ module Operations
8
+ # Removes host/group associations for existing primary entities.
9
+ class RemoveAssociations
10
+ AUTOMATIC_GROUP = 'ungrouped'
11
+ include OperationEventSupport
12
+
13
+ def initialize(context:)
14
+ @context = context
15
+ end
16
+
17
+ def host_from_groups(host:, host_name:, group_names:, dry_run: false)
18
+ events = []
19
+ warning_count = 0
20
+ @dry_run = dry_run
21
+
22
+ group_names.each do |group_name|
23
+ next if group_name.nil? || group_name.empty?
24
+
25
+ warning_count += remove_group_from_host(host, host_name, group_name, events)
26
+ end
27
+
28
+ add_automatic_group_if_needed(host, host_name, events,
29
+ planned_empty: planned_host_groups_empty?(host, group_names))
30
+ emit(events, :dry_run_summary) if dry_run
31
+
32
+ operation_result(events: events, warning_count: warning_count)
33
+ end
34
+
35
+ def group_from_hosts(group:, group_name:, host_names:, dry_run: false)
36
+ events = []
37
+ warning_count = 0
38
+ @dry_run = dry_run
39
+ hosts_dataset = group.hosts_dataset
40
+
41
+ host_names.each do |host_name|
42
+ next if host_name.nil? || host_name.empty?
43
+
44
+ warning_count += remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
45
+ end
46
+
47
+ emit(events, :dry_run_summary) if dry_run
48
+
49
+ operation_result(events: events, warning_count: warning_count)
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :context, :dry_run
55
+
56
+ def remove_group_from_host(host, host_name, group_name, events)
57
+ groups_dataset = host.groups_dataset
58
+ emit(events, :removing_host_group_association, host: host_name, group: group_name)
59
+
60
+ unless association_exists?(groups_dataset, group_name)
61
+ emit(events, :host_group_association_missing, host: host_name, group: group_name)
62
+ emit(events, :missing_skipping, indent: 4)
63
+ emit(events, :ok, indent: 4)
64
+ return 1
65
+ end
66
+
67
+ group = context.find_group(group_name)
68
+ host.remove_group(group) unless group.nil? || dry_run
69
+ emit(events, :ok, indent: 4)
70
+ 0
71
+ end
72
+
73
+ def remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
74
+ emit(events, :removing_group_host_association, group: group_name, host: host_name)
75
+
76
+ unless association_exists?(hosts_dataset, host_name)
77
+ emit(events, :group_host_association_missing, group: group_name, host: host_name)
78
+ emit(events, :missing_skipping, indent: 4)
79
+ emit(events, :ok, indent: 4)
80
+ return 1
81
+ end
82
+
83
+ host = context.find_host(host_name)
84
+ group.remove_host(host) unless host.nil? || dry_run
85
+ emit(events, :ok, indent: 4)
86
+ add_automatic_group_if_needed(host, host_name, events,
87
+ planned_empty: dry_run && !host.nil? && host.groups_dataset.one?)
88
+ 0
89
+ end
90
+
91
+ def add_automatic_group_if_needed(host, host_name, events, planned_empty: false)
92
+ return if host.nil?
93
+ return unless planned_empty || host.groups_dataset.none?
94
+
95
+ emit(events, :adding_automatic_group, host: host_name)
96
+ host.add_group(context.automatic_group) unless dry_run
97
+ emit(events, :ok, indent: 4)
98
+ end
99
+
100
+ def planned_host_groups_empty?(host, group_names)
101
+ return false unless dry_run
102
+
103
+ remaining = host.groups_dataset.map(&:name) - group_names
104
+ remaining.empty?
105
+ end
106
+
107
+ def association_exists?(dataset, name)
108
+ !dataset.nil? && !dataset[name: name].nil?
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'operation_event_support'
4
+
5
+ require_relative 'group_cleanup'
6
+
7
+ module Moose
8
+ module Inventory
9
+ module Operations
10
+ # Removes top-level groups and their direct associations.
11
+ class RemoveGroups
12
+ include OperationEventSupport
13
+
14
+ def initialize(context:)
15
+ @context = context
16
+ @cleanup = Moose::Inventory::Operations::GroupCleanup.new(
17
+ context: context,
18
+ emitter: method(:emit)
19
+ )
20
+ end
21
+
22
+ def call(names:, recursive: false, dry_run: false)
23
+ events = []
24
+ warning_count = 0
25
+ @dry_run = dry_run
26
+ cleanup.dry_run = dry_run
27
+
28
+ names.each do |name|
29
+ warning_count += remove_group(name, events, recursive: recursive)
30
+ end
31
+ emit(events, :dry_run_summary) if dry_run
32
+
33
+ operation_result(events: events, warning_count: warning_count)
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :cleanup, :context, :dry_run
39
+
40
+ def remove_group(name, events, recursive:)
41
+ emit(events, :group_started, name: name)
42
+ emit(events, :retrieving_group, name: name)
43
+ group = context.find_group(name)
44
+
45
+ if group.nil?
46
+ emit(events, :group_missing, name: name)
47
+ emit(events, :ok, indent: 4)
48
+ emit(events, :group_complete)
49
+ return 1
50
+ end
51
+
52
+ emit(events, :ok, indent: 4)
53
+ remove_parent_associations(group, name, events)
54
+ remove_child_associations(group, name, events, recursive: recursive)
55
+ cleanup.destroy_group(group, events, indent: 2)
56
+ emit(events, :group_complete)
57
+ 0
58
+ end
59
+
60
+ def remove_parent_associations(group, name, events)
61
+ group.parents_dataset.each do |parent|
62
+ emit(events, :removing_parent_association, group: name, related_group: parent.name)
63
+ parent.remove_child(group) unless dry_run
64
+ emit(events, :ok, indent: 4)
65
+ end
66
+ end
67
+
68
+ def remove_child_associations(group, name, events, recursive:)
69
+ group.children_dataset.each do |child|
70
+ emit(events, :removing_child_association, group: name, related_group: child.name)
71
+ group.remove_child(child) unless dry_run
72
+ emit(events, :ok, indent: 4)
73
+ cleanup.delete_orphaned_group(child, events, ignored_parent: group) if recursive
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'operation_event_support'
4
+
5
+ module Moose
6
+ module Inventory
7
+ module Operations
8
+ # Removes hosts and returns structured progress/warning events.
9
+ class RemoveHosts
10
+ include OperationEventSupport
11
+
12
+ def initialize(context:)
13
+ @context = context
14
+ end
15
+
16
+ def call(names:, dry_run: false)
17
+ events = []
18
+ warning_count = 0
19
+ @dry_run = dry_run
20
+
21
+ if dry_run
22
+ names.each do |name|
23
+ warning_count += remove_host(name, events)
24
+ end
25
+ emit(events, :dry_run_summary)
26
+ return operation_result(events: events, warning_count: warning_count)
27
+ end
28
+
29
+ context.transaction do
30
+ names.each do |name|
31
+ warning_count += remove_host(name, events)
32
+ end
33
+ end
34
+
35
+ operation_result(events: events, warning_count: warning_count)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :context, :dry_run
41
+
42
+ def remove_host(name, events)
43
+ emit(events, :host_started, name: name)
44
+ emit(events, :retrieving_host, name: name)
45
+ host = context.find_host(name)
46
+
47
+ if host.nil?
48
+ emit(events, :host_missing, name: name)
49
+ emit(events, :missing_skipping, indent: 4)
50
+ emit(events, :ok, indent: 4)
51
+ emit(events, :host_complete)
52
+ return 1
53
+ end
54
+
55
+ emit(events, :ok, indent: 4)
56
+ emit(events, :destroying_host, name: name)
57
+ unless dry_run
58
+ host.remove_all_groups
59
+ host.destroy
60
+ end
61
+ emit(events, :ok, indent: 4)
62
+ emit(events, :host_complete)
63
+ 0
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entity_variable_operation_support'
4
+
5
+ module Moose
6
+ module Inventory
7
+ module Operations
8
+ # Removes host/group variables by key.
9
+ class RemoveVariables
10
+ include EntityVariableOperationSupport
11
+
12
+ def call(name:, vars:, dry_run: false)
13
+ @events = []
14
+ @dry_run = dry_run
15
+
16
+ emit(:entity_started, name: name)
17
+ emit(:retrieving_entity, name: name)
18
+ entity = find_entity(name)
19
+ raise_missing_entity(name) if entity.nil?
20
+
21
+ emit(:ok, indent: 4)
22
+
23
+ dataset = entity.public_send("#{entity_type}vars_dataset")
24
+ vars.each do |variable|
25
+ remove_variable(entity, dataset, variable)
26
+ end
27
+
28
+ emit(:entity_complete)
29
+ emit(:dry_run_summary) if dry_run
30
+ operation_result(events: events)
31
+ ensure
32
+ @events = nil
33
+ @dry_run = nil
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :dry_run
39
+
40
+ def remove_variable(entity, dataset, variable)
41
+ emit(:removing_variable, variable: variable)
42
+ key = parse_variable_name(variable)
43
+
44
+ existing = dataset[name: key]
45
+ unless existing.nil? || dry_run
46
+ entity.public_send("remove_#{entity_type}var", existing)
47
+ existing.destroy
48
+ end
49
+
50
+ emit(:ok, indent: 4)
51
+ end
52
+
53
+ def parse_variable_name(variable)
54
+ invalid = variable.start_with?('=') || variable.count('=') > 1
55
+ raise_invalid_variable(variable) if invalid
56
+
57
+ variable.split('=').first
58
+ end
59
+
60
+ def raise_invalid_variable(variable)
61
+ raise context.moose_exception_class,
62
+ "Incorrect format in {#{variable}}. Expected 'key' or 'key=value'."
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moose
4
+ module Inventory
5
+ # Small value object for resolved runtime CLI options.
6
+ class RuntimeOptions
7
+ attr_reader :argv, :config, :env, :format
8
+
9
+ def initialize(argv:, config:, env:, format:, flags:)
10
+ @argv = argv
11
+ @config = config
12
+ @env = env
13
+ @format = format
14
+ @ansible = flags[:ansible] == true
15
+ @trace = flags[:trace] == true
16
+ end
17
+
18
+ def ansible?
19
+ @ansible
20
+ end
21
+
22
+ def trace?
23
+ @trace
24
+ end
25
+
26
+ def output_format
27
+ format.to_s.downcase
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moose
2
4
  ##
3
5
  # The Moose-Tools dynamic inventory management library
4
6
  module Inventory
5
- VERSION = '1.0.9'.freeze
7
+ VERSION = '2.1'
6
8
  end
7
9
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
- require_relative './moose_inventory/config/config.rb'
3
- require_relative './moose_inventory/db/db.rb'
4
- require_relative './moose_inventory/cli/application.rb'
4
+ require_relative 'moose_inventory/config/config'
5
+ require_relative 'moose_inventory/db/db'
6
+ require_relative 'moose_inventory/cli/application'
5
7
 
6
8
  module Moose
7
9
  module Inventory
@@ -12,13 +14,14 @@ module Moose
12
14
  extend self
13
15
  # rubocop:enable Style/ModuleFunction
14
16
 
15
- def start(args)
17
+ def start(args, config: Moose::Inventory::Config, db: Moose::Inventory::DB,
18
+ application: Moose::Inventory::Cli::Application)
16
19
  # initialization stuff.
17
- Moose::Inventory::Config.init(args)
18
- Moose::Inventory::DB.init
20
+ config.init(args)
21
+ db.init
19
22
 
20
23
  # Start the main application
21
- Moose::Inventory::Cli::Application.start(Moose::Inventory::Config._argv)
24
+ application.start(config.application_args)
22
25
  end
23
26
  end
24
27
  end
@@ -1,60 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # -*- mode: ruby -*-
2
4
  # vi: set ft=ruby :
3
5
 
4
- lib = File.expand_path('../lib', __FILE__)
6
+ lib = File.expand_path('lib', __dir__)
5
7
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
8
  require 'moose_inventory/version'
7
9
 
10
+ # rubocop:disable Metrics/BlockLength
8
11
  Gem::Specification.new do |spec|
9
12
  spec.name = 'moose-inventory'
10
13
  spec.version = Moose::Inventory::VERSION
11
14
  spec.authors = ['Russell Davies']
12
15
  spec.email = ['russell@blakemere.ca']
13
16
  spec.summary = 'Moose-tools inventory manager'
14
- # rubocop:disable Metrics/LineLength
15
- spec.description = 'The Moosecastle CLI tool for Ansible-compatable dynamic inventory management.'
16
- # rubocop:enable Metrics/LineLength
17
+ spec.description = 'The Moosecastle CLI tool for Ansible-compatible dynamic inventory management.'
17
18
  spec.homepage = 'https://github.com/RusDavies/moose-inventory'
18
19
  spec.license = 'MIT'
19
20
  spec.required_ruby_version = '>= 3.2'
21
+ spec.metadata['rubygems_mfa_required'] = 'true'
20
22
 
21
23
  spec.files = `git ls-files -z`.split("\x0")
22
24
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
25
  spec.require_paths = ['lib']
25
26
 
26
- # spec.add_runtime_dependency 'indentation', '~> 0.1'
27
- # spec.add_runtime_dependency 'json', '~>1.8'
28
- # #spec.add_runtime_dependency 'mysql', '~>2.9' # This causes lots of problems. Need to migrate to the newer mysql2.
29
- # #spec.add_runtime_dependency 'mysql2', '~>0.3'
30
- # spec.add_runtime_dependency 'mysql2'
31
- # spec.add_runtime_dependency 'pg', '~>0.17'
32
- # spec.add_runtime_dependency 'sequel', '~>4.22'
33
- # spec.add_runtime_dependency 'sqlite3', '~>1.3'
34
- # spec.add_runtime_dependency 'thor', '~>0.19'
35
- # # spec.add_runtime_dependency 'yaml', '~>1.0'
36
-
37
- # spec.add_development_dependency 'bundler', '~> 1.7'
38
- # spec.add_development_dependency 'coveralls', '~> 0.8'
39
- # spec.add_development_dependency 'guard', '~> 2.12'
40
- # spec.add_development_dependency 'guard-rspec', '~> 4.5'
41
- # spec.add_development_dependency 'guard-rubocop', '~> 1.2'
42
- # spec.add_development_dependency 'rake', '~> 10.1'
43
- # spec.add_development_dependency 'rspec', '~>3.2'
44
- # spec.add_development_dependency 'rubocop', '>= 0.19'
45
- # spec.add_development_dependency 'simplecov', '~> 0.10'
46
-
47
- spec.add_runtime_dependency 'indentation', '~> 0'
48
- spec.add_runtime_dependency 'json', '>= 2.7', '< 3'
49
- spec.add_runtime_dependency 'mysql2', '>= 0.5.7', '< 0.6'
50
- spec.add_runtime_dependency 'pg', '>= 1.5', '< 2'
51
- spec.add_runtime_dependency 'sequel', '>= 5.80', '< 6'
52
- spec.add_runtime_dependency 'sqlite3', '>= 1.7', '< 3'
53
- spec.add_runtime_dependency 'thor', '>= 1.3', '< 2'
54
-
27
+ spec.add_dependency 'indentation', '~> 0'
28
+ spec.add_dependency 'json', '>= 2.7', '< 3'
29
+ spec.add_dependency 'mysql2', '>= 0.5.7', '< 0.6'
30
+ spec.add_dependency 'pg', '>= 1.5', '< 2'
31
+ spec.add_dependency 'sequel', '>= 5.80', '< 6'
32
+ spec.add_dependency 'sqlite3', '>= 1.7', '< 3'
33
+ spec.add_dependency 'thor', '>= 1.3', '< 2'
34
+
35
+ # rubocop:disable Gemspec/DevelopmentDependencies
36
+ # Development dependencies intentionally remain here because this project uses
37
+ # `gemspec` as its Gemfile dependency source.
55
38
  spec.add_development_dependency 'bundler', '>= 2.2.33', '< 3'
39
+ spec.add_development_dependency 'bundler-audit', '>= 0.9', '< 1'
40
+ spec.add_development_dependency 'parallel', '>= 1.10', '< 2.0'
56
41
  spec.add_development_dependency 'rake', '>= 13.0', '< 14'
57
42
  spec.add_development_dependency 'rspec', '~> 3'
43
+ spec.add_development_dependency 'rubocop', '>= 1.72', '< 2'
58
44
  spec.add_development_dependency 'simplecov', '~> 0'
59
-
45
+ # rubocop:enable Gemspec/DevelopmentDependencies
60
46
  end
47
+ # rubocop:enable Metrics/BlockLength
data/scripts/check.sh CHANGED
@@ -2,7 +2,10 @@
2
2
  set -euo pipefail
3
3
 
4
4
  bundle exec rspec --format progress
5
+ scripts/ci/check_rubocop.sh
5
6
  git diff --check
6
7
  scripts/ci/check_permissions.sh
8
+ scripts/ci/check_generated_artifacts.sh
7
9
  scripts/ci/check_security.sh
10
+ scripts/ci/check_secrets.sh
8
11
  scripts/ci/package_sanity.sh