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,21 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'operation_event_support'
4
+
3
5
  module Moose
4
6
  module Inventory
5
7
  module Operations
6
8
  # Removes host/group associations for existing primary entities.
7
9
  class RemoveAssociations
8
10
  AUTOMATIC_GROUP = 'ungrouped'
9
- Event = Struct.new(:type, :payload, keyword_init: true)
10
- Result = Struct.new(:events, :warning_count, keyword_init: true)
11
+ include OperationEventSupport
11
12
 
12
13
  def initialize(context:)
13
14
  @context = context
14
15
  end
15
16
 
16
- def host_from_groups(host:, host_name:, group_names:)
17
+ def host_from_groups(host:, host_name:, group_names:, dry_run: false)
17
18
  events = []
18
19
  warning_count = 0
20
+ @dry_run = dry_run
19
21
 
20
22
  group_names.each do |group_name|
21
23
  next if group_name.nil? || group_name.empty?
@@ -23,14 +25,17 @@ module Moose
23
25
  warning_count += remove_group_from_host(host, host_name, group_name, events)
24
26
  end
25
27
 
26
- add_automatic_group_if_needed(host, host_name, events)
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
27
31
 
28
- Result.new(events: events, warning_count: warning_count)
32
+ operation_result(events: events, warning_count: warning_count)
29
33
  end
30
34
 
31
- def group_from_hosts(group:, group_name:, host_names:)
35
+ def group_from_hosts(group:, group_name:, host_names:, dry_run: false)
32
36
  events = []
33
37
  warning_count = 0
38
+ @dry_run = dry_run
34
39
  hosts_dataset = group.hosts_dataset
35
40
 
36
41
  host_names.each do |host_name|
@@ -39,12 +44,14 @@ module Moose
39
44
  warning_count += remove_host_from_group(group, group_name, host_name, hosts_dataset, events)
40
45
  end
41
46
 
42
- Result.new(events: events, warning_count: warning_count)
47
+ emit(events, :dry_run_summary) if dry_run
48
+
49
+ operation_result(events: events, warning_count: warning_count)
43
50
  end
44
51
 
45
52
  private
46
53
 
47
- attr_reader :context
54
+ attr_reader :context, :dry_run
48
55
 
49
56
  def remove_group_from_host(host, host_name, group_name, events)
50
57
  groups_dataset = host.groups_dataset
@@ -58,7 +65,7 @@ module Moose
58
65
  end
59
66
 
60
67
  group = context.find_group(group_name)
61
- host.remove_group(group) unless group.nil?
68
+ host.remove_group(group) unless group.nil? || dry_run
62
69
  emit(events, :ok, indent: 4)
63
70
  0
64
71
  end
@@ -74,26 +81,31 @@ module Moose
74
81
  end
75
82
 
76
83
  host = context.find_host(host_name)
77
- group.remove_host(host) unless host.nil?
84
+ group.remove_host(host) unless host.nil? || dry_run
78
85
  emit(events, :ok, indent: 4)
79
- add_automatic_group_if_needed(host, host_name, events)
86
+ add_automatic_group_if_needed(host, host_name, events,
87
+ planned_empty: dry_run && !host.nil? && host.groups_dataset.one?)
80
88
  0
81
89
  end
82
90
 
83
- def add_automatic_group_if_needed(host, host_name, events)
84
- return unless host.groups_dataset.none?
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?
85
94
 
86
95
  emit(events, :adding_automatic_group, host: host_name)
87
- host.add_group(context.automatic_group)
96
+ host.add_group(context.automatic_group) unless dry_run
88
97
  emit(events, :ok, indent: 4)
89
98
  end
90
99
 
91
- def association_exists?(dataset, name)
92
- !dataset.nil? && !dataset[name: name].nil?
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?
93
105
  end
94
106
 
95
- def emit(events, type, payload = {})
96
- events << Event.new(type: type, payload: payload)
107
+ def association_exists?(dataset, name)
108
+ !dataset.nil? && !dataset[name: name].nil?
97
109
  end
98
110
  end
99
111
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'operation_event_support'
4
+
3
5
  require_relative 'group_cleanup'
4
6
 
5
7
  module Moose
@@ -7,8 +9,7 @@ module Moose
7
9
  module Operations
8
10
  # Removes top-level groups and their direct associations.
9
11
  class RemoveGroups
10
- Event = Struct.new(:type, :payload, keyword_init: true)
11
- Result = Struct.new(:events, :warning_count, keyword_init: true)
12
+ include OperationEventSupport
12
13
 
13
14
  def initialize(context:)
14
15
  @context = context
@@ -18,20 +19,23 @@ module Moose
18
19
  )
19
20
  end
20
21
 
21
- def call(names:, recursive: false)
22
+ def call(names:, recursive: false, dry_run: false)
22
23
  events = []
23
24
  warning_count = 0
25
+ @dry_run = dry_run
26
+ cleanup.dry_run = dry_run
24
27
 
25
28
  names.each do |name|
26
29
  warning_count += remove_group(name, events, recursive: recursive)
27
30
  end
31
+ emit(events, :dry_run_summary) if dry_run
28
32
 
29
- Result.new(events: events, warning_count: warning_count)
33
+ operation_result(events: events, warning_count: warning_count)
30
34
  end
31
35
 
32
36
  private
33
37
 
34
- attr_reader :cleanup, :context
38
+ attr_reader :cleanup, :context, :dry_run
35
39
 
36
40
  def remove_group(name, events, recursive:)
37
41
  emit(events, :group_started, name: name)
@@ -56,7 +60,7 @@ module Moose
56
60
  def remove_parent_associations(group, name, events)
57
61
  group.parents_dataset.each do |parent|
58
62
  emit(events, :removing_parent_association, group: name, related_group: parent.name)
59
- parent.remove_child(group)
63
+ parent.remove_child(group) unless dry_run
60
64
  emit(events, :ok, indent: 4)
61
65
  end
62
66
  end
@@ -64,15 +68,11 @@ module Moose
64
68
  def remove_child_associations(group, name, events, recursive:)
65
69
  group.children_dataset.each do |child|
66
70
  emit(events, :removing_child_association, group: name, related_group: child.name)
67
- group.remove_child(child)
71
+ group.remove_child(child) unless dry_run
68
72
  emit(events, :ok, indent: 4)
69
- cleanup.delete_orphaned_group(child, events) if recursive
73
+ cleanup.delete_orphaned_group(child, events, ignored_parent: group) if recursive
70
74
  end
71
75
  end
72
-
73
- def emit(events, type, payload = {})
74
- events << Event.new(type: type, payload: payload)
75
- end
76
76
  end
77
77
  end
78
78
  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 = '2.0'.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,57 +1,40 @@
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'
56
39
  spec.add_development_dependency 'bundler-audit', '>= 0.9', '< 1'
57
40
  spec.add_development_dependency 'parallel', '>= 1.10', '< 2.0'
@@ -59,5 +42,6 @@ Gem::Specification.new do |spec|
59
42
  spec.add_development_dependency 'rspec', '~> 3'
60
43
  spec.add_development_dependency 'rubocop', '>= 1.72', '< 2'
61
44
  spec.add_development_dependency 'simplecov', '~> 0'
62
-
45
+ # rubocop:enable Gemspec/DevelopmentDependencies
63
46
  end
47
+ # rubocop:enable Metrics/BlockLength
data/scripts/check.sh CHANGED
@@ -5,6 +5,7 @@ bundle exec rspec --format progress
5
5
  scripts/ci/check_rubocop.sh
6
6
  git diff --check
7
7
  scripts/ci/check_permissions.sh
8
+ scripts/ci/check_generated_artifacts.sh
8
9
  scripts/ci/check_security.sh
9
10
  scripts/ci/check_secrets.sh
10
11
  scripts/ci/package_sanity.sh
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Generated/local artifacts are useful during development but must not become
5
+ # source inputs, package contents, or review noise. Keep this list aligned with
6
+ # .gitignore and scanner excludes.
7
+ generated_paths=(
8
+ ".openclaw-security-audit"
9
+ "coverage"
10
+ "pkg"
11
+ "spec/reports"
12
+ "tmp"
13
+ )
14
+
15
+ tracked=()
16
+ for path in "${generated_paths[@]}"; do
17
+ while IFS= read -r file; do
18
+ tracked+=("$file")
19
+ done < <(git ls-files "$path" "$path/**")
20
+ done
21
+
22
+ if (( ${#tracked[@]} > 0 )); then
23
+ echo "Generated artifact paths are tracked and must be removed from source commits:" >&2
24
+ printf ' %s\n' "${tracked[@]}" >&2
25
+ exit 1
26
+ fi
27
+
28
+ ignored_failures=()
29
+ for path in "${generated_paths[@]}"; do
30
+ if ! git check-ignore -q "$path/placeholder"; then
31
+ ignored_failures+=("$path/")
32
+ fi
33
+ done
34
+
35
+ if (( ${#ignored_failures[@]} > 0 )); then
36
+ echo "Generated artifact paths are not ignored:" >&2
37
+ printf ' %s\n' "${ignored_failures[@]}" >&2
38
+ exit 1
39
+ fi
40
+
41
+ echo "Generated artifact guard passed."
@@ -3,10 +3,12 @@ set -euo pipefail
3
3
 
4
4
  allowed_executables=(
5
5
  "bin/moose-inventory"
6
+ "examples/ci/scripts/validate-inventory-snapshot.sh"
6
7
  "scripts/check.sh"
7
8
  "scripts/ci/check_permissions.sh"
8
9
  "scripts/ci/check_rubocop.sh"
9
10
  "scripts/ci/check_secrets.sh"
11
+ "scripts/ci/check_generated_artifacts.sh"
10
12
  "scripts/ci/check_security.sh"
11
13
  "scripts/ci/install_security_tools.sh"
12
14
  "scripts/ci/package_sanity.sh"
@@ -1,28 +1,33 @@
1
1
  #!/bin/bash
2
2
  set -euo pipefail
3
3
 
4
- bundle exec rubocop \
5
- lib/moose_inventory/inventory_context.rb \
6
- lib/moose_inventory/operations/add_hosts.rb \
7
- lib/moose_inventory/operations/add_groups.rb \
8
- lib/moose_inventory/operations/add_associations.rb \
9
- lib/moose_inventory/operations/remove_associations.rb \
10
- lib/moose_inventory/operations/group_cleanup.rb \
11
- lib/moose_inventory/operations/group_child_relations.rb \
12
- lib/moose_inventory/operations/remove_groups.rb \
13
- lib/moose_inventory/cli/helpers.rb \
14
- lib/moose_inventory/cli/host_add.rb \
15
- lib/moose_inventory/cli/group_add.rb \
16
- lib/moose_inventory/cli/host_addgroup.rb \
17
- lib/moose_inventory/cli/group_addhost.rb \
18
- lib/moose_inventory/cli/host_rmgroup.rb \
19
- lib/moose_inventory/cli/group_rmhost.rb \
20
- lib/moose_inventory/cli/group_addchild.rb \
21
- lib/moose_inventory/cli/group_rmchild.rb \
22
- lib/moose_inventory/cli/group_rm.rb \
23
- spec/lib/moose_inventory/operations/add_hosts_spec.rb \
24
- spec/lib/moose_inventory/operations/add_groups_spec.rb \
25
- spec/lib/moose_inventory/operations/add_associations_spec.rb \
26
- spec/lib/moose_inventory/operations/remove_associations_spec.rb \
27
- spec/lib/moose_inventory/operations/group_child_relations_spec.rb \
28
- spec/lib/moose_inventory/operations/remove_groups_spec.rb
4
+ repo_root="$(git rev-parse --show-toplevel)"
5
+ cd "$repo_root"
6
+
7
+ rubocop_files=(
8
+ Gemfile
9
+ Rakefile
10
+ )
11
+
12
+ while IFS= read -r -d '' file; do
13
+ rubocop_files+=("$file")
14
+ done < <(find bin -maxdepth 1 -type f -print0 | sort -z)
15
+
16
+ while IFS= read -r -d '' file; do
17
+ rubocop_files+=("$file")
18
+ done < <(
19
+ find \
20
+ lib \
21
+ scripts \
22
+ spec \
23
+ -path 'spec/reports' -prune -o \
24
+ -path 'spec/reports/*' -prune -o \
25
+ -type f \( -name '*.rb' -o -name '*.gemspec' \) \
26
+ -print0 | sort -z
27
+ )
28
+
29
+ while IFS= read -r -d '' file; do
30
+ rubocop_files+=("$file")
31
+ done < <(find . -maxdepth 1 -type f -name '*.gemspec' -print0 | sort -z)
32
+
33
+ bundle exec rubocop "${rubocop_files[@]}"
data/scripts/files.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'yaml'
4
5
 
@@ -8,8 +9,8 @@ test_files = files.grep(%r{^(test|spec|features)/})
8
9
  require_paths = ['lib']
9
10
 
10
11
  out = {}
11
- out['Executables'.to_sym] = executables
12
- out['Test_Files'.to_sym] = test_files
13
- out['Require_Paths'.to_sym] = require_paths
12
+ out[:Executables] = executables
13
+ out[:Test_Files] = test_files
14
+ out[:Require_Paths] = require_paths
14
15
 
15
- puts out.to_yaml.to_s
16
+ puts out.to_yaml
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'spec_helper'
5
+ require 'tmpdir'
6
+ require 'yaml'
7
+
8
+ RSpec.describe 'CI/CD examples' do
9
+ it 'ships parseable inventory and GitHub Actions examples' do
10
+ expect(YAML.safe_load_file('examples/ci/inventory/example-snapshot.yml')).to include('version' => 1)
11
+ workflow = YAML.safe_load_file('examples/ci/github-actions/inventory-review.yml')
12
+
13
+ expect(workflow).to include('name' => 'Inventory review example')
14
+ end
15
+
16
+ it 'keeps the snapshot validation script syntax-valid' do
17
+ expect(system('bash', '-n', 'examples/ci/scripts/validate-inventory-snapshot.sh')).to eq(true)
18
+ end
19
+
20
+ it 'validates a snapshot and writes review artifacts without production credentials' do
21
+ Dir.mktmpdir do |dir|
22
+ command = 'bundle exec ruby -Ilib bin/moose-inventory'
23
+ result = system(
24
+ { 'MOOSE_INVENTORY_CMD' => command },
25
+ 'examples/ci/scripts/validate-inventory-snapshot.sh',
26
+ 'examples/ci/inventory/example-snapshot.yml',
27
+ dir
28
+ )
29
+
30
+ expect(result).to eq(true)
31
+ expect(File).to exist(File.join(dir, 'doctor.txt'))
32
+ expect(YAML.safe_load_file(File.join(dir, 'inventory.yml'))).to include('hosts')
33
+ expect(JSON.parse(File.read(File.join(dir, 'hosts.json')))).to include('web01')
34
+ expect(JSON.parse(File.read(File.join(dir, 'ansible-inventory.json')))).to include('web')
35
+ end
36
+ end
37
+ end