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
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- moose-inventory (2.0)
4
+ moose-inventory (2.1)
5
5
  indentation (~> 0)
6
6
  json (>= 2.7, < 3)
7
7
  mysql2 (>= 0.5.7, < 0.6)
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # moose-inventory
2
2
 
3
- The [moose-inventory](https://github.com/RusDavies/moose-inventory) software is a tool for managing dynamic inventories, intended for use with [Ansible](http://www.ansible.com/home).
3
+ The [moose-inventory](https://github.com/RusDavies/moose-inventory) software is a tool for managing dynamic inventories, intended for use with [Ansible](http://www.ansible.com/home).
4
4
 
5
5
  Note 1: For many, the really interesting part of this tool will be it's ability to write to the inventory database from within Ansible, as described at the end of this document. If that's what tickles your fancy, then I encourage you to get a sense of the capability by [jumping to that section first](https://github.com/RusDavies/moose-inventory#writing-to-the-dynamic-inventory-from-ansible). ;o)
6
6
 
7
7
 
8
- Note 2: This software is intended for use on UNIX/Linux systems. It will likely not work on Windows, due to some hard-wired search paths - I may fix that in the future but, for now, sorry.
8
+ Note 2: This software is intended for use on UNIX/Linux systems. It will likely not work on Windows, due to some hard-wired search paths - I may fix that in the future but, for now, sorry.
9
9
 
10
10
  ## Installation
11
11
 
@@ -25,11 +25,11 @@ gem 'moose-inventory'
25
25
 
26
26
 
27
27
  ## Configuration
28
- The [moose-inventory](https://github.com/RusDavies/moose-inventory) tool makes use of a simple YAML configuration file.
28
+ The [moose-inventory](https://github.com/RusDavies/moose-inventory) tool makes use of a simple YAML configuration file.
29
29
 
30
30
 
31
- ###File Location
32
-
31
+ ### File Location
32
+
33
33
  The following locations, in descending order of precedence, are searched for a configuration file:
34
34
 
35
35
  1. location passed via the `--config` option
@@ -38,14 +38,14 @@ The following locations, in descending order of precedence, are searched for a
38
38
  5. ~/local/etc/moose-tools/inventory/config
39
39
  6. /etc/moose-tools/inventory/config
40
40
 
41
- ###Format
42
- The file consists of a mandatory *general* section, and at least one *environment* section. For example:
41
+ ### Format
42
+ The file consists of a mandatory *general* section, and at least one *environment* section. For example:
43
43
  ```yaml
44
44
  ---
45
45
  general:
46
46
  defaultenv: moose_dev
47
47
 
48
- moose_dev:
48
+ moose_dev:
49
49
  db:
50
50
  adapter: "sqlite3"
51
51
  file: "~/.moose/db/dev.db"
@@ -68,13 +68,13 @@ another_example_section:
68
68
 
69
69
  ```
70
70
 
71
- ###The *general* section
72
- The general section is mandatory, and contains a single parameter **defaultenv**, which points to the name of the default environment section.
71
+ ### The *general* section
72
+ The general section is mandatory, and contains a single parameter **defaultenv**, which points to the name of the default environment section.
73
73
 
74
- ###Environment sections
74
+ ### Environment sections
75
75
  You may add as many environment sections as you desire. The intention is to enable the user to easily manage multiple environments, such as development, staging, production, etc., via a single configuration file. The name of each environment section must be unique, but can otherwise be any valid YAML tag.
76
76
 
77
- At present, each environment section contains only a **db** subsection, describing database connection parameters. Additional subsections may be added in the future, as functionality increases.
77
+ At present, each environment section contains only a **db** subsection, describing database connection parameters. Additional subsections may be added in the future, as functionality increases.
78
78
 
79
79
  Each **db** section must include an **adapter** parameter. Currently supported adapter types are *sqlite3*, *mysql*, and *postgresql*. The test suite exercises SQLite with a local database file and includes adapter dispatch/error-path smoke coverage for MySQL and PostgreSQL without requiring live database servers.
80
80
 
@@ -98,19 +98,19 @@ The tool itself provides a convenient help feature. For example, try each of th
98
98
  $ moose-inventory help group
99
99
  $ moose-inventory group help add
100
100
 
101
- ###Global switches
101
+ ### Global switches
102
102
 
103
103
  #### Option `--config <FILE>`
104
104
  The `--config` flag sets the configuration file to be used. If specified, then the file must exist. This takes precedence over all other config files in other locations. If not provided, then the default is to search the locations previously mentioned.
105
105
 
106
- For example,
106
+ For example,
107
107
 
108
108
  $ moose-inventory --config ./mystuff.conf host list
109
109
 
110
110
  #### Option `--env <SECTION>`
111
- The *--env* flag sets the section in the configuration file to be used as the environment configuration. If set, then the section must exist. If not set, then what ever default is provided by the **defaultenv** parameter will be used.
111
+ The *--env* flag sets the section in the configuration file to be used as the environment configuration. If set, then the section must exist. If not set, then what ever default is provided by the **defaultenv** parameter will be used.
112
112
 
113
- For example,
113
+ For example,
114
114
 
115
115
  $ moose-inventory --env my_section host list
116
116
 
@@ -125,13 +125,216 @@ For example,
125
125
  :groups:
126
126
  - ungrouped
127
127
 
128
- ###Transactional Behaviour
129
- The *moose-inventory* tool performs database operations in a transactional manner. That is to say, either all operations of a command succeed, or they are all rolled back.
128
+ ### Transactional Behaviour
129
+ The *moose-inventory* tool performs database operations in a transactional manner. That is to say, either all operations of a command succeed, or they are all rolled back.
130
+
131
+ ### Dry-run and plan output
132
+ Mutating commands support a `--dry-run` option. This renders the same kind of progress output as the real command, but does not write anything to the database. This is useful when checking inventory surgery before applying it, particularly for operations that affect automatic `ungrouped` associations or child-group cleanup.
133
+
134
+ Examples:
135
+
136
+ $ moose-inventory host add web01 --groups web --dry-run
137
+ Add host 'web01':
138
+ - Creating host 'web01'...
139
+ - OK
140
+ - Adding association {host:web01 <-> group:web}...
141
+ - OK
142
+ - All OK
143
+ Dry run complete. No changes applied.
144
+ Succeeded
145
+
146
+ $ moose-inventory group rm --recursive old_parent_group --dry-run
147
+ $ moose-inventory host addvar web01 owner=russ env=prod --dry-run
148
+ $ moose-inventory group addhost web web01 web02 --dry-run
149
+ $ moose-inventory group rmchild --delete-orphans parent_group child_group --dry-run
150
+
151
+ The following mutating command families support `--dry-run`:
152
+
153
+ 1. `host add` and `host rm`
154
+ 2. `group add` and `group rm`
155
+ 3. `host addvar`, `host rmvar`, `group addvar`, and `group rmvar`
156
+ 4. `host addgroup`, `host rmgroup`, `group addhost`, and `group rmhost`
157
+ 5. `group addchild` and `group rmchild`
158
+
159
+ For automation and review workflows, dry-run events can also be emitted as YAML, JSON, or pretty JSON with `--plan-format`. This option requires `--dry-run`; without it, the command aborts before making changes.
160
+
161
+ Destructive removal commands require an explicit acknowledgement before they write. Use `--dry-run` to preview, or add `--yes` when you intentionally want to apply a removal non-interactively. This applies to host/group deletion, variable removal, association removal, child-group dissociation, and metadata tag removal commands.
162
+
163
+ $ moose-inventory host rm old-web01 --dry-run
164
+ $ moose-inventory host rm old-web01 --yes
165
+ $ moose-inventory group rm --recursive old_parent_group --yes
166
+
167
+ $ moose-inventory host add web01 --groups web --dry-run --plan-format pjson
168
+ {
169
+ "command": "host add",
170
+ "dry_run": true,
171
+ "changes_applied": false,
172
+ "events": [
173
+ {
174
+ "type": "host_started",
175
+ "payload": {
176
+ "name": "web01"
177
+ }
178
+ }
179
+ ]
180
+ }
181
+
182
+ The actual `events` array includes the full ordered plan for the command. Each event has a `type` and a `payload`, so scripts can inspect planned host, group, variable, association, automatic `ungrouped`, and child-group cleanup actions without scraping human-readable output.
183
+
184
+ CLI output compatibility is governed by `CLI-OUTPUT-v1` in `docs/compatibility/cli-output-compatibility.md`. Machine-readable JSON/YAML/pjson structures are the supported automation interface. Documented human-readable output is also compatibility-protected when tests, README examples, or release notes rely on it, but scripts should prefer machine-readable formats.
185
+
186
+ ### Import and export snapshots
187
+ The full inventory can be exported as a portable snapshot. The snapshot contains a version number, hosts, host variables, host-to-group memberships, host/group tags, groups, group variables, and child-group relationships. It is intended for review, backup, migration, and automation workflows.
188
+
189
+ $ moose-inventory --format yaml export inventory.yml
190
+ Exported inventory snapshot to inventory.yml.
191
+
192
+ $ moose-inventory --format pjson export
193
+ {
194
+ "version": 1,
195
+ "hosts": {
196
+ "web01": {
197
+ "groups": [
198
+ "web"
199
+ ],
200
+ "tags": [
201
+ "prod"
202
+ ],
203
+ "vars": {
204
+ "env": "prod"
205
+ }
206
+ }
207
+ },
208
+ "groups": {
209
+ "web": {
210
+ "children": [],
211
+ "tags": [
212
+ "frontend"
213
+ ],
214
+ "vars": {
215
+ "role": "frontend"
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ Snapshots can be imported from YAML or JSON. Import validates the file before writing anything. It rejects malformed snapshots, unknown host/group references, unsupported fields, invalid variable shapes, and circular child-group hierarchies.
222
+
223
+ Use `--preview` to validate a snapshot and review its additive import diff without writing to the database. For automation/review gates, add `--preview-format yaml|json|pjson`; the preview uses `snapshot-import-preview-v1`, reports creates, variable updates, association additions, unchanged items, existing records that are absent from the snapshot and therefore ignored, and confirms that destructive changes are not part of normal import.
224
+
225
+ $ moose-inventory import inventory.yml
226
+ Imported inventory snapshot from inventory.yml.
227
+ Created hosts: 1
228
+ Created groups: 1
229
+ Variables changed: 2
230
+ Associations added: 1
231
+
232
+ $ moose-inventory import inventory.yml --preview --preview-format pjson
233
+
234
+ Import is additive and update-oriented: it creates missing hosts and groups, adds missing associations and tags, and creates or updates variables found in the snapshot. It does not delete existing inventory records that are absent from the file. Use a fresh database when you want the imported snapshot to be the whole world, because databases are notoriously bad at guessing intent.
235
+
236
+ ### Inventory doctor
237
+ The `doctor` command runs read-only inventory health checks and exits with a non-zero status if it finds issues. This makes it suitable for CI checks, release gates, and pre-change reviews.
238
+
239
+ $ moose-inventory doctor
240
+ Inventory doctor found no issues.
241
+
242
+ When findings are present, the human-readable output lists each issue with a severity and check id:
243
+
244
+ $ moose-inventory doctor
245
+ Inventory doctor found 2 issue(s):
246
+ - [warning] host_only_in_ungrouped: Host 'web01' is only in automatic group 'ungrouped'.
247
+ - [warning] orphaned_group: Group 'old_web' has no parents and no hosts.
248
+
249
+ For automation, use `--format yaml`, `--format json`, or `--format pjson` on the doctor command itself:
250
+
251
+ $ moose-inventory doctor --format pjson
252
+ {
253
+ "ok": false,
254
+ "issue_count": 1,
255
+ "issues": [
256
+ {
257
+ "id": "host_only_in_ungrouped",
258
+ "severity": "warning",
259
+ "message": "Host 'web01' is only in automatic group 'ungrouped'.",
260
+ "subject": "web01"
261
+ }
262
+ ]
263
+ }
264
+
265
+ Current doctor checks include missing database configuration, plaintext database passwords, hosts only in `ungrouped`, orphaned groups, empty groups, duplicate-ish names, invalid variable records, and circular child-group relationships.
266
+
267
+ ### Metadata tags
268
+ Hosts and groups can carry metadata tags that are separate from Ansible variables. Use tags for operational labels such as environment, owner, lifecycle, location, role, or criticality when you want metadata without exposing it as inventory variables.
269
+
270
+ $ moose-inventory host addtag web01 prod critical owner-platform
271
+ Added host tag(s) to 'web01': prod, critical, owner-platform.
272
+
273
+ $ moose-inventory host listtags web01
274
+ Host 'web01' tags: critical, owner-platform, prod
275
+
276
+ $ moose-inventory host rmtag web01 critical
277
+ Removed host tag(s) from 'web01': critical.
278
+
279
+ Groups support the same tag commands:
280
+
281
+ $ moose-inventory group addtag web frontend public-edge
282
+ $ moose-inventory group listtags web --format json
283
+
284
+ Tag names are case-insensitive operational metadata: CLI tag commands and snapshot imports normalize them to lowercase, strip surrounding whitespace, deduplicate repeated values, and store them in portable join tables. Tag add/remove operations are audited when they change state.
130
285
 
131
- ###Walk-through example
132
- This walk-through goes through the process of creating three hosts and three groups, assigning variables to some of each, and then associating hosts with groups. Once done, each association, variable, group, and host are removed.
286
+ ### Audit log / change history
287
+ Moose Inventory records append-only audit events for successful mutating CLI commands. Dry-run commands are intentionally excluded, because planned changes are already available through `--plan-format` and did not actually mutate inventory state.
133
288
 
134
- We start by creating three hosts, in this case named *host1*, *host2*, and *host3*. Note, we can add as many hosts as we desire via this single command. Also, although we have used short names here, we could equally have used fully qualified names.
289
+ Audit events record when the change happened, the local actor from `USER`, the command/action, the entity type/name, and structured operation details. The audit log is deliberately small: it is for debugging and accountability, not yet a full rollback system.
290
+
291
+ List recent events in a human-readable form:
292
+
293
+ $ moose-inventory audit list
294
+ 12 2026-05-28T17:01:02Z host add host=app01 action=add
295
+
296
+ Machine-readable output is available for scripts and support bundles:
297
+
298
+ $ moose-inventory audit list --format yaml
299
+ $ moose-inventory audit list --format json
300
+ $ moose-inventory audit list --format pjson
301
+
302
+ The default limit is 20 events; use `--limit` to inspect more history:
303
+
304
+ $ moose-inventory audit list --limit 100
305
+
306
+ ### Database lifecycle commands
307
+ Moose Inventory records a small schema metadata table and exposes database lifecycle commands under `db`. These commands are intentionally conservative: they inspect, create missing schema metadata, and back up SQLite databases, but they do not silently rewrite production databases into a modern art installation.
308
+
309
+ $ moose-inventory db status
310
+ Adapter: sqlite3
311
+ Schema version: 4
312
+ Expected schema version: 4
313
+ SQLite file: /home/russ/.moose/db/dev.db
314
+ Tables:
315
+ - hosts: present
316
+ - hostvars: present
317
+ - groups: present
318
+
319
+ $ moose-inventory db doctor
320
+ Database doctor found no issues.
321
+
322
+ $ moose-inventory db migrate
323
+ Database schema is at version 4.
324
+
325
+ `db migrate` runs explicit ordered schema migrations up to the current schema version. The current migration chain is `1 -> 2 -> 3 -> 4`: version 1 creates the core inventory tables and schema metadata, version 2 adds audit history, version 3 adds tag metadata, and version 4 adds DB-level uniqueness and lookup indexes for variables, host/group relationships, group-child relationships, and tag joins. Moose Inventory refuses to open or migrate a database whose recorded schema version is newer than the tool supports; upgrade the tool instead of letting old code write to a future schema. `db doctor` reports missing known tables in a dirty or partially migrated database.
326
+
327
+ SQLite users can create a direct database-file backup:
328
+
329
+ $ moose-inventory db backup ./backup/moose-inventory.sqlite3
330
+ Backed up database to /absolute/path/backup/moose-inventory.sqlite3.
331
+
332
+ `db backup` is currently supported for SQLite only. For MySQL/MariaDB and PostgreSQL, use native database tools such as `mysqldump`, `mariadb-dump`, `pg_dump`, managed-service snapshots, or equivalent backup systems, because those engines already have adult supervision built in. Moose Inventory does not run server-backed restore commands, manage database users/grants, or implement destructive snapshot sync/restore behavior. See `docs/maintenance/database-backup-restore-guidance.md` for adapter-specific backup and restore boundaries.
333
+
334
+ ### Walk-through example
335
+ This walk-through goes through the process of creating three hosts and three groups, assigning variables to some of each, and then associating hosts with groups. Once done, each association, variable, group, and host are removed.
336
+
337
+ We start by creating three hosts, in this case named *host1*, *host2*, and *host3*. Note, we can add as many hosts as we desire via this single command. Also, although we have used short names here, we could equally have used fully qualified names.
135
338
 
136
339
  $ moose-inventory add host host1 host2 host3
137
340
  Add host 'host1':
@@ -154,7 +357,7 @@ We start by creating three hosts, in this case named *host1*, *host2*, and *hos
154
357
  - all OK
155
358
  Succeeded.
156
359
 
157
- Notice that each host is initially associated with an automatic group, *ungrouped*.
360
+ Notice that each host is initially associated with an automatic group, *ungrouped*.
158
361
 
159
362
  Now we can list our hosts, to see that they are stored as expected. In this example, we will request the output be formatted as YAML. If we didn't specify a format, then it would default to regular JSON.
160
363
 
@@ -177,7 +380,7 @@ Now we can list our hosts, to see that they are stored as expected. In this exa
177
380
  }
178
381
  }
179
382
 
180
- The *host list* command simply lists all hosts, in the order that they were entered into the database. We can also get a specific host, or hosts, by name. In this example, we'll get only *host3* and *host1*, outputting the result in YAML.
383
+ The *host list* command simply lists all hosts, in the order that they were entered into the database. We can also get a specific host, or hosts, by name. In this example, we'll get only *host3* and *host1*, outputting the result in YAML.
181
384
 
182
385
  $ moose-inventory host get host3 host1 --format yaml
183
386
  ---
@@ -200,7 +403,7 @@ Now we'll add some host variables. Again, we can add as many variables to a hos
200
403
  - OK
201
404
  - all OK
202
405
  Succeeded.
203
-
406
+
204
407
  $ moose-inventory host addvar host2 owner=caroline id=54321
205
408
  Add variables 'owner=caroline,id=54321' to host 'host2':
206
409
  - retrieve host 'host2'...
@@ -234,7 +437,13 @@ Let's list our hosts again, to see what that looks like.
234
437
 
235
438
  As you can see, the hosts with variables each have a new section, hostvars, in which those variables are listed. Try also with *--format pjson*.
236
439
 
237
- We can do the same with groups. In the following example, the output has been omitted for compactness. Nevertheless, you will see that the form of the commands is as for hosts. Of note, when listing the groups, you will see that the *ungrouped* group is shown. This is an automatic group which cannot be manipulated manually.
440
+ Host listing can also be filtered by group, metadata tag, and host variable. Multiple comma-separated values are treated as an AND filter: the host must match all requested groups, all requested tags, and all requested variable key/value pairs.
441
+
442
+ $ moose-inventory host list --group web --tag prod --var os=fedora --format yaml
443
+
444
+ Variable filters use `key=value` syntax. Metadata tags appear under a `tags` section when present; hosts without tags keep the older compact output. Group-side listing filters are still part of the remaining query/filter backlog, because one haunted query surface per slice is plenty.
445
+
446
+ We can do the same with groups. In the following example, the output has been omitted for compactness. Nevertheless, you will see that the form of the commands is as for hosts. Of note, when listing the groups, you will see that the *ungrouped* group is shown. This is an automatic group which cannot be manipulated manually.
238
447
 
239
448
  $ moose-inventory group add group1 group2 group3
240
449
  $ moose-inventory group list --format yaml
@@ -244,7 +453,7 @@ We can do the same with groups. In the following example, the output has been o
244
453
 
245
454
  At this point, we have three hosts and three groups, some of each with variables. Let's now associate hosts with groups. We can either associate one or more hosts with a group,
246
455
 
247
- $ moose-inventory group addhost group1 host1 host2
456
+ $ moose-inventory group addhost group1 host1 host2
248
457
  Associate group 'group1' with host(s) 'host1,host2':
249
458
  - retrieve group 'group1'...
250
459
  - OK
@@ -261,7 +470,7 @@ At this point, we have three hosts and three groups, some of each with variables
261
470
 
262
471
  or one or more groups with a host,
263
472
 
264
- $ moose-inventory host addgroup host3 group2 group3
473
+ $ moose-inventory host addgroup host3 group2 group3
265
474
  Associate host 'host3' with groups 'group2,group3':
266
475
  - Retrieve host 'host3'...
267
476
  - OK
@@ -312,6 +521,31 @@ We can also list hosts, to get the host-centric view.
312
521
  - group2
313
522
  - group3
314
523
 
524
+ ### Read-only console
525
+ For human browsing, Moose Inventory includes a small read-only console. It is intentionally conservative: the first console slice lets operators inspect inventory state, tags, and recent audit events, but does not mutate records.
526
+
527
+ $ moose-inventory console
528
+ Moose Inventory console (read-only). Type help or quit.
529
+
530
+ Useful console commands include:
531
+
532
+ help
533
+ hosts
534
+ groups
535
+ host web01
536
+ group web
537
+ tags host web01
538
+ tags group web
539
+ audit 10
540
+ quit
541
+
542
+ Console parsing uses shell-style quoting for read-only lookups, so names containing spaces can be inspected without turning the prompt into confetti:
543
+
544
+ host "web 01"
545
+ tags group 'production web'
546
+
547
+ The console reports command-specific usage for extra arguments, invalid tag targets, invalid audit limits, and malformed quotes. Use the normal CLI commands for edits. Future interactive mutation can be added with confirmation, dry-run, and audit semantics instead of improvising a tiny foot-gun in a prompt loop.
548
+
315
549
  Removing variables, groups, and hosts is just as easy. In the following examples, the output is again omitted for compactness; the reader is encouraged to work along to experience the tool. Note, that although we show how to remove the variables, it is not strictly necessary to do so in this example, since deleting hosts and groups would delete all associated variables anyway.
316
550
 
317
551
  By default, deleting a group preserves its child groups as root groups. Use `group rm --recursive` when child groups that become orphaned should also be deleted. Similarly, `group rmchild --delete-orphans` removes a parent-child association and deletes the child subtree only when it becomes orphaned by that removal. Hosts whose last group is deleted are automatically moved to `ungrouped`.
@@ -324,12 +558,56 @@ By default, deleting a group preserves its child groups as root groups. Use `gro
324
558
  $ moose-inventory host rmvar host1 owner id
325
559
  $ moose-inventory host rm host1 host2 host3
326
560
 
561
+ ### CI/CD integration examples
562
+ The `examples/ci/` directory contains a pull-request review pattern for inventory changes that does not require production database credentials. It imports a proposed snapshot into a temporary SQLite database, runs `doctor`, exports a canonical snapshot, lists hosts, and writes an Ansible-compatible inventory artifact.
563
+
564
+ Run the example locally with:
565
+
566
+ $ MOOSE_INVENTORY_CMD="bundle exec ruby -Ilib bin/moose-inventory" \
567
+ examples/ci/scripts/validate-inventory-snapshot.sh \
568
+ examples/ci/inventory/example-snapshot.yml \
569
+ tmp/inventory-ci-artifacts
570
+
571
+ The script writes:
572
+
573
+ tmp/inventory-ci-artifacts/doctor.txt
574
+ tmp/inventory-ci-artifacts/inventory.yml
575
+ tmp/inventory-ci-artifacts/hosts.json
576
+ tmp/inventory-ci-artifacts/ansible-inventory.json
577
+
578
+ `examples/ci/github-actions/inventory-review.yml` shows the same pattern as a GitHub Actions workflow. It is stored under `examples/` rather than `.github/workflows/` so teams can adapt paths, snapshot locations, artifact names, and deployment rules before enabling it. Use this as a review gate before applying inventory changes to a shared or production Moose Inventory database; CI should validate proposals, not casually scribble on prod like a bored intern.
579
+
327
580
  ### Using moose-inventory with Ansible
328
581
 
329
582
 
330
583
  The *moose-inventory* tool is compliant with the Ansible specifications for [dynamic inventory sources](http://docs.ansible.com/developing_inventory.html).
331
584
 
332
- However, to make use of *moose-inventory's* multiple environment and configuration file options, a shim script should be used as the target for the [external inventory script](http://docs.ansible.com/intro_dynamic_inventory.html). A trivial example may look something like the following.
585
+ The preferred modern integration is the example inventory plugin shipped in `examples/ansible/inventory_plugins/moose_inventory.py`. Copy or vendor that plugin into your Ansible project, then point `ansible.cfg` at the plugin directory and inventory source file:
586
+
587
+ ```ini
588
+ [defaults]
589
+ inventory = inventory/moose_inventory.yml
590
+ inventory_plugins = inventory_plugins
591
+ ```
592
+
593
+ The inventory source file is plain YAML:
594
+
595
+ ```yaml
596
+ ---
597
+ plugin: moose_inventory
598
+ executable: moose-inventory
599
+ config: ./example.conf
600
+ env: dev
601
+ ```
602
+
603
+ With those files in place, Ansible can use Moose Inventory directly:
604
+
605
+ $ ansible-inventory -i inventory/moose_inventory.yml --list
606
+ $ ansible -i inventory/moose_inventory.yml -u ubuntu us-east-1d -m ping
607
+
608
+ The plugin calls `moose-inventory` for group and host data, preserving Moose Inventory's own configuration file and environment selection instead of hiding them in a shell wrapper. The shipped `examples/ansible/` directory contains a complete minimal `ansible.cfg`, inventory source, and plugin file.
609
+
610
+ A legacy external-inventory shim still works, and remains useful on older Ansible installs or when you want the simplest possible integration. To make use of *moose-inventory's* multiple environment and configuration file options with the shim approach, use a script as the target for the [external inventory script](http://docs.ansible.com/intro_dynamic_inventory.html). A trivial example may look something like the following.
333
611
 
334
612
  ```shell
335
613
  #!/bin/bash
@@ -346,16 +624,16 @@ exit $?
346
624
 
347
625
  $ ./shim.sh host add example
348
626
  $ ./shim.sh host addvar example "my var"="hello world"
349
-
350
627
 
351
- When Ansible calls the external inventory script, it passes certain parameters, which *moose-inventory* automatically recognises and responds to. The Ansible parameters, and their equivalent *moose-inventory* parameters are shown below.
628
+
629
+ When Ansible calls the external inventory script, it passes certain parameters, which *moose-inventory* automatically recognises and responds to. The Ansible parameters, and their equivalent *moose-inventory* parameters are shown below.
352
630
 
353
631
  Ansible | moose-inventory
354
632
  ---------------- |-------------
355
- `--list` | `--ansible group list`
633
+ `--list` | `--ansible group list`
356
634
  `--host HOSTNAME` | `--ansible host listvars HOSTNAME`
357
635
 
358
- Note, the above conversions are performed automatically within *moose-inventory*.
636
+ Note, the above conversions are performed automatically within *moose-inventory*.
359
637
 
360
638
  With *moose-inventory* installed and configured, and a shim script (e.g. *shim.sh*) in place, then integration with Ansible can be acheived via Ansible's `-i <file>` option.
361
639
 
@@ -364,18 +642,18 @@ With *moose-inventory* installed and configured, and a shim script (e.g. *shim.s
364
642
  Alternatively, if using an [Ansible configuration file](http://docs.ansible.com/intro_configuration.html), then one may set the [inventory](http://docs.ansible.com/intro_configuration.html#inventory) option,
365
643
 
366
644
  inventory = ./shim.sh
367
-
368
- Yet another option is to copy the shim script to */etc/ansible/hosts* and `chmod +x` it. However, since this would essentially fix the config file and environment used, doing so would defeat the flexibility intended for *moose-inventory*.
645
+
646
+ Yet another option is to copy the shim script to */etc/ansible/hosts* and `chmod +x` it. However, since this would essentially fix the config file and environment used, doing so would defeat the flexibility intended for *moose-inventory*.
369
647
 
370
648
  #### Writing to the dynamic inventory from Ansible
371
649
  A useful aspect of dynamic inventories is the possibility of writing data to the inventory. To persist data from Ansible to the inventory, simply call the shim script via a local_action command, for example:
372
650
 
373
651
  ```shell
374
652
  - set_fact: mydata="Hello world"
375
- - local_action: command shim.sh host addvar {{ inventory_hostname }} mydata="{{ mydata }}"
653
+ - local_action: command shim.sh host addvar {{ inventory_hostname }} mydata="{{ mydata }}"
376
654
  ```
377
655
 
378
-
656
+
379
657
  ## Development checks
380
658
 
381
659
  Run the local verification gate before committing changes:
@@ -384,7 +662,7 @@ Run the local verification gate before committing changes:
384
662
  ./scripts/check.sh
385
663
  ```
386
664
 
387
- The check script runs the RSpec suite, enforces the SimpleCov coverage minimum, checks file permissions, queries OSV for locked RubyGems advisories, runs `bundler-audit`, runs `gitleaks` when available, and builds/smoke-tests the packaged gem.
665
+ The check script runs the RSpec suite, enforces the SimpleCov coverage minimum, checks file permissions, verifies generated/local artifact paths remain ignored and untracked, queries OSV for locked RubyGems advisories, runs `bundler-audit`, runs `gitleaks` when available, and builds/smoke-tests the packaged gem.
388
666
 
389
667
  Optional Go-based security tools used by CI can be installed locally with:
390
668
 
@@ -402,8 +680,6 @@ That installs `gitleaks` and `osv-scanner` into `tmp/security-tools/bin` unless
402
680
  5. Create a new Pull Request
403
681
 
404
682
 
405
-
406
-
407
683
 
408
684
 
409
685
 
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
data/bin/moose-inventory CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  begin
4
5
  require 'moose_inventory'
5
- rescue
6
+ rescue LoadError
6
7
  require 'rubygems'
7
8
  require 'moose_inventory'
8
9
  end