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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +15 -1
- data/.github/workflows/release.yml +60 -0
- data/.gitignore +2 -1
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +49 -0
- data/BACKLOG.md +752 -24
- data/Gemfile +2 -0
- data/Gemfile.lock +36 -1
- data/README.md +340 -44
- data/Rakefile +2 -0
- data/bin/moose-inventory +2 -1
- data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
- data/docs/compatibility/cli-output-compatibility.md +76 -0
- data/docs/governance/approval-register.md +37 -0
- data/docs/maintenance/database-backup-restore-guidance.md +162 -0
- data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
- data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
- data/docs/product/product-brief.md +161 -0
- data/docs/product/requirements-baseline.md +477 -0
- data/docs/qa/qa-documentation-and-release-gates.md +283 -0
- data/docs/release/package-provenance-hardening.md +126 -0
- data/docs/release/publishing.md +54 -50
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +37 -4
- data/docs/security/accepted-risk-register.md +84 -0
- data/docs/security/security-privacy-process.md +287 -0
- data/docs/security-audit-2026-05-26-rerun.md +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- data/docs/ux/cli-workflow-notes.md +287 -0
- data/examples/ansible/ansible.cfg +3 -0
- data/examples/ansible/inventory/moose_inventory.yml +5 -0
- data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
- data/examples/ci/README.md +16 -0
- data/examples/ci/github-actions/inventory-review.yml +38 -0
- data/examples/ci/inventory/example-snapshot.yml +19 -0
- data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
- data/lib/moose_inventory/cli/application.rb +133 -5
- data/lib/moose_inventory/cli/association_rendering.rb +74 -0
- data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
- data/lib/moose_inventory/cli/audit.rb +62 -0
- data/lib/moose_inventory/cli/audit_recording.rb +40 -0
- data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
- data/lib/moose_inventory/cli/console.rb +135 -0
- data/lib/moose_inventory/cli/db.rb +64 -0
- data/lib/moose_inventory/cli/factory.rb +28 -0
- data/lib/moose_inventory/cli/formatter.rb +8 -12
- data/lib/moose_inventory/cli/group.rb +7 -1
- data/lib/moose_inventory/cli/group_add.rb +91 -73
- data/lib/moose_inventory/cli/group_addchild.rb +41 -66
- data/lib/moose_inventory/cli/group_addhost.rb +33 -71
- data/lib/moose_inventory/cli/group_addvar.rb +27 -47
- data/lib/moose_inventory/cli/group_get.rb +8 -42
- data/lib/moose_inventory/cli/group_list.rb +7 -40
- data/lib/moose_inventory/cli/group_listvars.rb +9 -55
- data/lib/moose_inventory/cli/group_rm.rb +105 -73
- data/lib/moose_inventory/cli/group_rmchild.rb +47 -57
- data/lib/moose_inventory/cli/group_rmhost.rb +34 -61
- data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
- data/lib/moose_inventory/cli/group_tags.rb +33 -0
- data/lib/moose_inventory/cli/helpers.rb +143 -0
- data/lib/moose_inventory/cli/host.rb +8 -2
- data/lib/moose_inventory/cli/host_add.rb +91 -66
- data/lib/moose_inventory/cli/host_addgroup.rb +39 -66
- data/lib/moose_inventory/cli/host_addvar.rb +28 -52
- data/lib/moose_inventory/cli/host_get.rb +9 -37
- data/lib/moose_inventory/cli/host_list.rb +24 -21
- data/lib/moose_inventory/cli/host_listvars.rb +9 -62
- data/lib/moose_inventory/cli/host_rm.rb +60 -42
- data/lib/moose_inventory/cli/host_rmgroup.rb +39 -55
- data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
- data/lib/moose_inventory/cli/host_tags.rb +33 -0
- data/lib/moose_inventory/cli/listvars_support.rb +55 -0
- data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
- data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
- data/lib/moose_inventory/cli/tag_support.rb +97 -0
- data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
- data/lib/moose_inventory/config/config.rb +185 -108
- data/lib/moose_inventory/db/db.rb +188 -193
- data/lib/moose_inventory/db/exceptions.rb +6 -3
- data/lib/moose_inventory/db/models.rb +16 -0
- data/lib/moose_inventory/db/schema_migrations.rb +248 -0
- data/lib/moose_inventory/inventory_context.rb +116 -0
- data/lib/moose_inventory/operations/add_associations.rb +131 -0
- data/lib/moose_inventory/operations/add_groups.rb +123 -0
- data/lib/moose_inventory/operations/add_hosts.rb +123 -0
- data/lib/moose_inventory/operations/add_variables.rb +77 -0
- data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +125 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +70 -0
- data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
- data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
- data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
- data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
- data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
- data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
- data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
- data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
- data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
- data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
- data/lib/moose_inventory/operations/query_inventory.rb +47 -0
- data/lib/moose_inventory/operations/remove_associations.rb +113 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
- data/lib/moose_inventory/operations/remove_variables.rb +67 -0
- data/lib/moose_inventory/runtime_options.rb +31 -0
- data/lib/moose_inventory/version.rb +3 -1
- data/lib/moose_inventory.rb +10 -7
- data/moose-inventory.gemspec +22 -35
- data/scripts/check.sh +3 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +5 -0
- data/scripts/ci/check_rubocop.sh +33 -0
- data/scripts/ci/check_secrets.sh +26 -0
- data/scripts/ci/check_security.sh +18 -0
- data/scripts/ci/install_security_tools.sh +47 -0
- data/scripts/files.rb +5 -4
- data/scripts/install_dependencies.sh +2 -0
- data/spec/examples/ci_examples_spec.rb +37 -0
- data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
- data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
- data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
- data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
- data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
- data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
- data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
- data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
- data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +165 -85
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +100 -30
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
- data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
- data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
- data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +551 -29
- data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
- data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
- data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
- data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +111 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +80 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +82 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +122 -0
- data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
- data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
- data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +113 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
- data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
- data/spec/shared/shared_config_setup.rb +4 -3
- data/spec/spec_helper.rb +50 -40
- data/spec/support/cli_harness.rb +33 -0
- metadata +163 -35
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Moose Inventory CLI UX and Workflow Notes
|
|
2
|
+
|
|
3
|
+
## Approval status
|
|
4
|
+
|
|
5
|
+
Status: **Approved CLI UX/workflow baseline**
|
|
6
|
+
|
|
7
|
+
Approval reference: `GOV-UX-001` in `docs/governance/approval-register.md` approves this document as the CLI UX/workflow baseline for Moose Inventory.
|
|
8
|
+
|
|
9
|
+
Approved references:
|
|
10
|
+
|
|
11
|
+
- `GOV-TAILOR-001`: Moose Inventory is approved as Class 4 with target profile Software Library / Package.
|
|
12
|
+
- `GOV-PRODUCT-001`: `docs/product/product-brief.md` is approved as the product-framing baseline.
|
|
13
|
+
- `GOV-REQ-001`: `docs/product/requirements-baseline.md` is approved as the requirements and acceptance criteria baseline.
|
|
14
|
+
|
|
15
|
+
Scope limit: this approval covers command-line workflows and interaction conventions only. It is not architecture approval, security/privacy design approval, release approval, accepted-risk approval, public/compliance-claim approval, RubyGems publishing approval, or implementation approval for the UX follow-up backlog items.
|
|
16
|
+
|
|
17
|
+
## UX posture
|
|
18
|
+
|
|
19
|
+
Moose Inventory is a command-line tool and RubyGem, so its UX baseline is not wireframes or visual mockups. The appropriate UX artifact is a workflow and interaction convention baseline for CLI users, automation users, reviewers, and maintainers.
|
|
20
|
+
|
|
21
|
+
Primary UX priorities:
|
|
22
|
+
|
|
23
|
+
1. Make inventory-changing actions explicit, reviewable, and hard to mistake for read-only actions.
|
|
24
|
+
2. Preserve existing command names, output shape, and wording where users or tests may depend on them.
|
|
25
|
+
3. Keep machine-readable output parseable and stable enough for automation.
|
|
26
|
+
4. Give clear, actionable error messages before any write occurs when inputs/options are invalid.
|
|
27
|
+
5. Keep safety controls visible: transactions, dry-run, plan output, validation, doctor checks, and audit history.
|
|
28
|
+
|
|
29
|
+
## User personas and interaction modes
|
|
30
|
+
|
|
31
|
+
### CLI operator
|
|
32
|
+
|
|
33
|
+
A human operator uses `moose-inventory` directly to inspect and change inventory state.
|
|
34
|
+
|
|
35
|
+
UX needs:
|
|
36
|
+
|
|
37
|
+
- discoverable help
|
|
38
|
+
- predictable command families
|
|
39
|
+
- readable progress output
|
|
40
|
+
- clear success/failure states
|
|
41
|
+
- safe review before mutation
|
|
42
|
+
|
|
43
|
+
### Automation/review consumer
|
|
44
|
+
|
|
45
|
+
A script, CI job, or reviewer consumes machine-readable output from list/get/doctor/export/dry-run plan commands.
|
|
46
|
+
|
|
47
|
+
UX needs:
|
|
48
|
+
|
|
49
|
+
- parseable YAML/JSON/pjson
|
|
50
|
+
- non-zero status for failed checks/findings where documented
|
|
51
|
+
- stable keys and event structure
|
|
52
|
+
- no secret leakage in examples/output
|
|
53
|
+
|
|
54
|
+
### Maintainer/release reviewer
|
|
55
|
+
|
|
56
|
+
A maintainer checks behavior, documentation, release evidence, and compatibility.
|
|
57
|
+
|
|
58
|
+
UX needs:
|
|
59
|
+
|
|
60
|
+
- regression tests for output contracts
|
|
61
|
+
- README examples matching supported behavior
|
|
62
|
+
- clear approval boundaries for breaking changes
|
|
63
|
+
- process evidence distinguishing checks from approval
|
|
64
|
+
|
|
65
|
+
## Core workflows
|
|
66
|
+
|
|
67
|
+
### 1. Discover commands
|
|
68
|
+
|
|
69
|
+
Workflow:
|
|
70
|
+
|
|
71
|
+
1. User runs top-level or nested help.
|
|
72
|
+
2. CLI displays available commands/options.
|
|
73
|
+
3. User chooses a command family such as `host`, `group`, `db`, `audit`, `doctor`, `import`, or `export`.
|
|
74
|
+
|
|
75
|
+
UX expectations:
|
|
76
|
+
|
|
77
|
+
- Help must be available at top-level and command-family levels.
|
|
78
|
+
- Help examples should be accurate enough to copy/adapt.
|
|
79
|
+
- New command families should follow existing README/help style.
|
|
80
|
+
|
|
81
|
+
### 2. Select configuration and environment
|
|
82
|
+
|
|
83
|
+
Workflow:
|
|
84
|
+
|
|
85
|
+
1. User relies on config discovery or passes `--config <FILE>`.
|
|
86
|
+
2. User relies on `general.defaultenv` or passes `--env <SECTION>`.
|
|
87
|
+
3. CLI initializes the configured database context.
|
|
88
|
+
|
|
89
|
+
UX expectations:
|
|
90
|
+
|
|
91
|
+
- Missing config, missing environment, and invalid DB config errors should fail before mutation.
|
|
92
|
+
- Error messages should name the missing/invalid element when practical.
|
|
93
|
+
- Docs should steer users toward `password_env` rather than plaintext `password`.
|
|
94
|
+
|
|
95
|
+
### 3. Inspect inventory
|
|
96
|
+
|
|
97
|
+
Workflow:
|
|
98
|
+
|
|
99
|
+
1. User runs list/get commands for hosts/groups/variables/tags.
|
|
100
|
+
2. User optionally passes `--format yaml|json|pjson`.
|
|
101
|
+
3. CLI returns current state without mutation.
|
|
102
|
+
|
|
103
|
+
UX expectations:
|
|
104
|
+
|
|
105
|
+
- Read-only commands should not create, repair, or migrate data implicitly unless explicitly documented.
|
|
106
|
+
- Machine-readable formats should remain parseable and consistent.
|
|
107
|
+
- Empty results should be explicit rather than misleading.
|
|
108
|
+
|
|
109
|
+
### 4. Mutate inventory safely
|
|
110
|
+
|
|
111
|
+
Workflow:
|
|
112
|
+
|
|
113
|
+
1. User chooses a mutating command: add/remove hosts/groups, variables, tags, associations, or child relationships.
|
|
114
|
+
2. User optionally previews with `--dry-run` or `--dry-run --plan-format`.
|
|
115
|
+
3. CLI validates inputs/options before writes.
|
|
116
|
+
4. Destructive removal commands require `--yes` before writing unless the user selected `--dry-run`.
|
|
117
|
+
5. CLI applies changes transactionally when not dry-run.
|
|
118
|
+
5. CLI reports progress, warnings, success/failure, and audit evidence where applicable.
|
|
119
|
+
|
|
120
|
+
UX expectations:
|
|
121
|
+
|
|
122
|
+
- Mutating commands must be visually distinguishable from read-only workflows through command naming, progress output, and documentation.
|
|
123
|
+
- Invalid option combinations, such as `--plan-format` without `--dry-run`, fail before mutation.
|
|
124
|
+
- Removal commands should fail closed without `--yes`, while still allowing dry-run preview without confirmation.
|
|
125
|
+
- Dry-run output must not imply changes were applied.
|
|
126
|
+
- Real mutation output should preserve existing output contracts unless a breaking change is approved.
|
|
127
|
+
|
|
128
|
+
### 5. Review planned changes
|
|
129
|
+
|
|
130
|
+
Workflow:
|
|
131
|
+
|
|
132
|
+
1. User runs a mutating command with `--dry-run`.
|
|
133
|
+
2. CLI renders planned progress and ends with `Dry run complete. No changes applied.`
|
|
134
|
+
3. User optionally requests `--plan-format yaml|json|pjson` for automation review.
|
|
135
|
+
|
|
136
|
+
UX expectations:
|
|
137
|
+
|
|
138
|
+
- Dry-run should use the same conceptual progress path as a real mutation.
|
|
139
|
+
- Dry-run should not write inventory, audit records, schema state, or automatic cleanup associations.
|
|
140
|
+
- Plan output should include ordered event types and payloads suitable for review tools.
|
|
141
|
+
- Human-readable dry-run output should remain easy to compare with real command output.
|
|
142
|
+
|
|
143
|
+
### 6. Validate health
|
|
144
|
+
|
|
145
|
+
Workflow:
|
|
146
|
+
|
|
147
|
+
1. User runs `moose-inventory doctor`.
|
|
148
|
+
2. CLI performs read-only checks.
|
|
149
|
+
3. CLI reports no issues or lists findings with severity/check identifiers.
|
|
150
|
+
4. CLI exits non-zero when findings are present.
|
|
151
|
+
|
|
152
|
+
UX expectations:
|
|
153
|
+
|
|
154
|
+
- Findings should be actionable and identify affected subject when practical.
|
|
155
|
+
- Machine-readable doctor output should be suitable for CI gates.
|
|
156
|
+
- Doctor should not silently fix inventory; repairs should remain explicit user actions.
|
|
157
|
+
|
|
158
|
+
### 7. Import/export snapshots
|
|
159
|
+
|
|
160
|
+
Workflow:
|
|
161
|
+
|
|
162
|
+
1. User exports inventory for review, backup, migration, or automation.
|
|
163
|
+
2. User previews a YAML/JSON snapshot with `import FILE --preview` or `--preview --preview-format yaml|json|pjson` when reviewing a proposed change.
|
|
164
|
+
3. User imports a YAML/JSON snapshot.
|
|
165
|
+
4. CLI validates before writing and applies additive/update-oriented changes only.
|
|
166
|
+
|
|
167
|
+
UX expectations:
|
|
168
|
+
|
|
169
|
+
- Export should be read-only.
|
|
170
|
+
- Import preview should be read-only and distinct from command-level dry-run planning.
|
|
171
|
+
- Preview output should show creates, variable updates, association additions, unchanged items, ignored existing records, and destructive-change count.
|
|
172
|
+
- Import validation failures should avoid partial writes.
|
|
173
|
+
- Additive import semantics must be documented clearly.
|
|
174
|
+
- Destructive sync/restore semantics must not be introduced without separate requirements, UX, recovery, and approval records.
|
|
175
|
+
|
|
176
|
+
### 8. Review audit history
|
|
177
|
+
|
|
178
|
+
Workflow:
|
|
179
|
+
|
|
180
|
+
1. User runs `audit list` with optional format/limit.
|
|
181
|
+
2. CLI returns append-only evidence for successful mutating commands.
|
|
182
|
+
|
|
183
|
+
UX expectations:
|
|
184
|
+
|
|
185
|
+
- Audit output should help explain what changed, when, by whom/tooling, and against what target.
|
|
186
|
+
- Audit output is accountability/debug evidence, not rollback by itself.
|
|
187
|
+
- Dry-runs should not appear as mutation audit records.
|
|
188
|
+
|
|
189
|
+
## Destructive and high-risk operations
|
|
190
|
+
|
|
191
|
+
High-risk CLI areas include:
|
|
192
|
+
|
|
193
|
+
- host/group removal
|
|
194
|
+
- recursive group deletion
|
|
195
|
+
- child-group cleanup with orphan deletion
|
|
196
|
+
- variable removal
|
|
197
|
+
- snapshot import into populated databases
|
|
198
|
+
- schema migration
|
|
199
|
+
- backup/restore-adjacent workflows
|
|
200
|
+
- future destructive snapshot sync/restore features, if ever approved
|
|
201
|
+
|
|
202
|
+
UX requirements for high-risk operations:
|
|
203
|
+
|
|
204
|
+
1. The command name/options must make destructive intent visible.
|
|
205
|
+
2. Documentation must describe mutation scope and notable cleanup behavior.
|
|
206
|
+
3. `--dry-run` should be available for documented mutating workflows where supported.
|
|
207
|
+
4. Invalid inputs/options must fail before writes.
|
|
208
|
+
5. Real writes should be transactional where practical.
|
|
209
|
+
6. Future destructive restore/sync behavior requires a separate UX design and approval record.
|
|
210
|
+
|
|
211
|
+
## Error states and messaging
|
|
212
|
+
|
|
213
|
+
Expected error behavior:
|
|
214
|
+
|
|
215
|
+
- fail early before mutation when arguments/options/config are invalid
|
|
216
|
+
- name the invalid option/value where practical
|
|
217
|
+
- distinguish usage errors from database/runtime failures
|
|
218
|
+
- preserve existing exact error strings where tests or downstream users rely on them
|
|
219
|
+
- avoid exposing secrets in errors
|
|
220
|
+
|
|
221
|
+
Known UX improvement area:
|
|
222
|
+
|
|
223
|
+
- Read-only console parsing now uses shell-style quoting and command-specific validation for the current browsing commands. Future console changes should preserve the read-only boundary unless mutation flows add confirmation, dry-run, and audit behavior.
|
|
224
|
+
|
|
225
|
+
## Accessibility and readability expectations
|
|
226
|
+
|
|
227
|
+
For a CLI, accessibility/readability means:
|
|
228
|
+
|
|
229
|
+
- plain text that works in ordinary terminals
|
|
230
|
+
- no required color-only signal
|
|
231
|
+
- stable indentation for progress output
|
|
232
|
+
- concise severity/check IDs for doctor findings
|
|
233
|
+
- machine-readable alternatives for automation and assistive tooling
|
|
234
|
+
- examples that can be copied without hidden state or secrets
|
|
235
|
+
- readable failure messages rather than stack traces for ordinary user errors
|
|
236
|
+
|
|
237
|
+
## Machine-readable output conventions
|
|
238
|
+
|
|
239
|
+
Machine-readable output is automation-facing UX and is governed by `CLI-OUTPUT-v1` in `docs/compatibility/cli-output-compatibility.md`.
|
|
240
|
+
|
|
241
|
+
Expectations:
|
|
242
|
+
|
|
243
|
+
- JSON/YAML/pjson structures should remain parseable.
|
|
244
|
+
- Event/check keys should not be renamed casually.
|
|
245
|
+
- New fields should prefer additive compatibility.
|
|
246
|
+
- Breaking output changes require approval and migration/release notes.
|
|
247
|
+
- Pretty JSON is for humans reviewing structured data; normal JSON/YAML are for scripts and CI.
|
|
248
|
+
|
|
249
|
+
## Compatibility conventions
|
|
250
|
+
|
|
251
|
+
Human-readable output is also a versioned compatibility surface under `CLI-OUTPUT-v1` when tests, docs, or scripts rely on exact wording.
|
|
252
|
+
|
|
253
|
+
Expectations:
|
|
254
|
+
|
|
255
|
+
- Preserve existing wording/newline behavior during refactors unless an intentional change is approved.
|
|
256
|
+
- Tests should cover known legacy output contracts, especially around warnings and cleanup behavior.
|
|
257
|
+
- README examples should not promise output that the CLI no longer emits.
|
|
258
|
+
- Breaking human-readable output changes need backlog/approval evidence and release notes just like machine-readable breaking changes.
|
|
259
|
+
|
|
260
|
+
## UX acceptance checklist
|
|
261
|
+
|
|
262
|
+
A new or changed CLI workflow is UX-ready when:
|
|
263
|
+
|
|
264
|
+
- The command path and option names are consistent with existing command families.
|
|
265
|
+
- Read vs write behavior is obvious from command naming and docs.
|
|
266
|
+
- Invalid inputs/options fail before mutation.
|
|
267
|
+
- Mutating behavior is transactional where practical.
|
|
268
|
+
- Dry-run/plan behavior exists where required by the requirements baseline.
|
|
269
|
+
- Human-readable output is clear and compatible unless an approved breaking change exists.
|
|
270
|
+
- Machine-readable output is parseable and compatibility-reviewed.
|
|
271
|
+
- README/help examples are updated where user-facing behavior changed.
|
|
272
|
+
- Tests cover success, failure, and safety boundaries.
|
|
273
|
+
- Any unresolved UX risk is recorded as a backlog item or accepted risk.
|
|
274
|
+
|
|
275
|
+
## UX decisions recorded from review
|
|
276
|
+
|
|
277
|
+
These decisions were provided by Russ during review on 2026-05-28. They are captured here as product/UX direction for future implementation, but this draft UX baseline still requires explicit approval through `GOV-UX-001` before it becomes approved UX evidence.
|
|
278
|
+
|
|
279
|
+
1. Destructive commands should eventually require explicit confirmation unless `--yes` or an equivalent non-interactive acknowledgement is provided.
|
|
280
|
+
2. Human-readable output compatibility is formally versioned through `CLI-OUTPUT-v1`, alongside machine-readable output compatibility.
|
|
281
|
+
3. Read-only console support for quoted names and richer validation through `Shellwords.split` should be prioritized before adding more CLI features.
|
|
282
|
+
4. Audit history should remain evidence only; future rollback/change-set UX should not be introduced through audit history.
|
|
283
|
+
5. Snapshot import should eventually offer a formal preview/diff mode distinct from command-level dry-run planning, but this is future work and does not block the current UX baseline.
|
|
284
|
+
|
|
285
|
+
## Open UX questions
|
|
286
|
+
|
|
287
|
+
No open UX questions remain in this draft. Future questions should be added here only when they are not already represented as a decision, backlog item, or accepted scope limit.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright: (c) Russell Davies
|
|
3
|
+
# MIT License
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
from ansible.errors import AnsibleParserError
|
|
11
|
+
from ansible.plugins.inventory import BaseInventoryPlugin
|
|
12
|
+
|
|
13
|
+
DOCUMENTATION = r'''
|
|
14
|
+
name: moose_inventory
|
|
15
|
+
plugin_type: inventory
|
|
16
|
+
short_description: Moose Inventory plugin
|
|
17
|
+
description:
|
|
18
|
+
- Loads inventory from the C(moose-inventory) command line tool.
|
|
19
|
+
- Keeps Moose Inventory configuration and environment selection in YAML instead of a shell shim.
|
|
20
|
+
options:
|
|
21
|
+
plugin:
|
|
22
|
+
description: Token that ensures this is a source file for this plugin.
|
|
23
|
+
required: true
|
|
24
|
+
choices: ['moose_inventory']
|
|
25
|
+
executable:
|
|
26
|
+
description: Moose Inventory executable path.
|
|
27
|
+
required: false
|
|
28
|
+
default: moose-inventory
|
|
29
|
+
config:
|
|
30
|
+
description: Moose Inventory config file passed to C(--config).
|
|
31
|
+
required: false
|
|
32
|
+
env:
|
|
33
|
+
description: Moose Inventory environment section passed to C(--env).
|
|
34
|
+
required: false
|
|
35
|
+
'''
|
|
36
|
+
|
|
37
|
+
EXAMPLES = r'''
|
|
38
|
+
plugin: moose_inventory
|
|
39
|
+
executable: moose-inventory
|
|
40
|
+
config: ./example.conf
|
|
41
|
+
env: dev
|
|
42
|
+
'''
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class InventoryModule(BaseInventoryPlugin):
|
|
46
|
+
NAME = 'moose_inventory'
|
|
47
|
+
|
|
48
|
+
def verify_file(self, path):
|
|
49
|
+
return super().verify_file(path) and path.endswith(('moose_inventory.yml', 'moose_inventory.yaml'))
|
|
50
|
+
|
|
51
|
+
def parse(self, inventory, loader, path, cache=True):
|
|
52
|
+
super().parse(inventory, loader, path, cache=cache)
|
|
53
|
+
config = self._read_config_data(path)
|
|
54
|
+
executable = config.get('executable', 'moose-inventory')
|
|
55
|
+
moose_config = config.get('config')
|
|
56
|
+
env = config.get('env')
|
|
57
|
+
|
|
58
|
+
groups = self._run_moose(executable, moose_config, env, ['--ansible', 'group', 'list'])
|
|
59
|
+
hosts = self._run_moose(executable, moose_config, env, ['host', 'list'])
|
|
60
|
+
|
|
61
|
+
self._apply_groups(groups)
|
|
62
|
+
self._apply_hosts(hosts)
|
|
63
|
+
|
|
64
|
+
def _run_moose(self, executable, config, env, args):
|
|
65
|
+
command = [executable]
|
|
66
|
+
if config:
|
|
67
|
+
command.extend(['--config', config])
|
|
68
|
+
if env:
|
|
69
|
+
command.extend(['--env', env])
|
|
70
|
+
command.extend(args)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
completed = subprocess.run(command, check=True, capture_output=True, text=True)
|
|
74
|
+
except (OSError, subprocess.CalledProcessError) as error:
|
|
75
|
+
raise AnsibleParserError('moose-inventory command failed: %s' % error) from error
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
return json.loads(completed.stdout or '{}')
|
|
79
|
+
except json.JSONDecodeError as error:
|
|
80
|
+
raise AnsibleParserError('moose-inventory returned invalid JSON: %s' % error) from error
|
|
81
|
+
|
|
82
|
+
def _apply_groups(self, groups):
|
|
83
|
+
for group_name, payload in groups.items():
|
|
84
|
+
self.inventory.add_group(group_name)
|
|
85
|
+
for host_name in payload.get('hosts', []):
|
|
86
|
+
self.inventory.add_host(host_name, group=group_name)
|
|
87
|
+
for child_name in payload.get('children', []):
|
|
88
|
+
self.inventory.add_group(child_name)
|
|
89
|
+
self.inventory.add_child(group_name, child_name)
|
|
90
|
+
for key, value in payload.get('vars', {}).items():
|
|
91
|
+
self.inventory.set_variable(group_name, key, value)
|
|
92
|
+
|
|
93
|
+
def _apply_hosts(self, hosts):
|
|
94
|
+
for host_name, payload in hosts.items():
|
|
95
|
+
self.inventory.add_host(host_name)
|
|
96
|
+
for group_name in payload.get('groups', []):
|
|
97
|
+
self.inventory.add_group(group_name)
|
|
98
|
+
self.inventory.add_host(host_name, group=group_name)
|
|
99
|
+
for key, value in payload.get('hostvars', {}).items():
|
|
100
|
+
self.inventory.set_variable(host_name, key, value)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Moose Inventory CI/CD Examples
|
|
2
|
+
|
|
3
|
+
These examples show how to validate an inventory snapshot in CI without using production database credentials.
|
|
4
|
+
|
|
5
|
+
- `inventory/example-snapshot.yml` is a small review snapshot fixture.
|
|
6
|
+
- `scripts/validate-inventory-snapshot.sh` imports a snapshot into a temporary SQLite database, runs `doctor`, exports a canonical snapshot, lists hosts, and produces an Ansible-compatible inventory artifact.
|
|
7
|
+
- `github-actions/inventory-review.yml` is a copy/paste GitHub Actions workflow example. It is intentionally stored under `examples/` rather than `.github/workflows/` so projects can adapt it before enabling it.
|
|
8
|
+
|
|
9
|
+
The example writes artifacts to `tmp/inventory-ci-artifacts` by default:
|
|
10
|
+
|
|
11
|
+
- `doctor.txt`
|
|
12
|
+
- `inventory.yml`
|
|
13
|
+
- `hosts.json`
|
|
14
|
+
- `ansible-inventory.json`
|
|
15
|
+
|
|
16
|
+
Use this pattern for pull-request review gates before applying inventory changes to a shared or production Moose Inventory database.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Inventory review example
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
pull_request:
|
|
6
|
+
paths:
|
|
7
|
+
- 'inventory/**'
|
|
8
|
+
- 'examples/ci/inventory/**'
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
validate-inventory:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Check out repository
|
|
18
|
+
uses: actions/checkout@v5
|
|
19
|
+
|
|
20
|
+
- name: Set up Ruby
|
|
21
|
+
uses: ruby/setup-ruby@v1
|
|
22
|
+
with:
|
|
23
|
+
ruby-version: '3.4'
|
|
24
|
+
bundler-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Validate proposed inventory snapshot
|
|
27
|
+
env:
|
|
28
|
+
MOOSE_INVENTORY_CMD: bundle exec ruby -Ilib bin/moose-inventory
|
|
29
|
+
run: |
|
|
30
|
+
examples/ci/scripts/validate-inventory-snapshot.sh \
|
|
31
|
+
examples/ci/inventory/example-snapshot.yml \
|
|
32
|
+
tmp/inventory-ci-artifacts
|
|
33
|
+
|
|
34
|
+
- name: Upload inventory review artifacts
|
|
35
|
+
uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: inventory-review
|
|
38
|
+
path: tmp/inventory-ci-artifacts
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
snapshot="${1:-examples/ci/inventory/example-snapshot.yml}"
|
|
5
|
+
artifact_dir="${2:-tmp/inventory-ci-artifacts}"
|
|
6
|
+
moose_cmd="${MOOSE_INVENTORY_CMD:-moose-inventory}"
|
|
7
|
+
work_dir="$(mktemp -d)"
|
|
8
|
+
trap 'rm -rf "$work_dir"' EXIT
|
|
9
|
+
|
|
10
|
+
mkdir -p "$artifact_dir"
|
|
11
|
+
config_file="$work_dir/moose-inventory-ci.yml"
|
|
12
|
+
db_file="$work_dir/inventory.db"
|
|
13
|
+
|
|
14
|
+
cat > "$config_file" <<YAML
|
|
15
|
+
---
|
|
16
|
+
general:
|
|
17
|
+
defaultenv: ci
|
|
18
|
+
ci:
|
|
19
|
+
db:
|
|
20
|
+
adapter: sqlite3
|
|
21
|
+
file: "$db_file"
|
|
22
|
+
YAML
|
|
23
|
+
|
|
24
|
+
$moose_cmd --config "$config_file" --env ci import "$snapshot"
|
|
25
|
+
$moose_cmd --config "$config_file" --env ci doctor > "$artifact_dir/doctor.txt"
|
|
26
|
+
$moose_cmd --config "$config_file" --env ci --format yaml export "$artifact_dir/inventory.yml"
|
|
27
|
+
$moose_cmd --config "$config_file" --env ci --format pjson host list > "$artifact_dir/hosts.json"
|
|
28
|
+
$moose_cmd --config "$config_file" --env ci --ansible group list > "$artifact_dir/ansible-inventory.json"
|
|
29
|
+
|
|
30
|
+
echo "Inventory CI artifacts written to $artifact_dir"
|
|
@@ -1,20 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
1
4
|
require 'thor'
|
|
2
|
-
require_relative '../version
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require_relative '
|
|
5
|
+
require_relative '../version'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
|
|
8
|
+
require_relative '../config/config'
|
|
9
|
+
require_relative '../operations/import_inventory_snapshot'
|
|
10
|
+
require_relative '../operations/inventory_doctor'
|
|
11
|
+
require_relative '../operations/inventory_snapshot'
|
|
12
|
+
require_relative 'formatter'
|
|
13
|
+
require_relative 'helpers'
|
|
14
|
+
require_relative 'audit'
|
|
15
|
+
require_relative 'console'
|
|
16
|
+
require_relative 'db'
|
|
17
|
+
require_relative 'group'
|
|
18
|
+
require_relative 'host'
|
|
6
19
|
|
|
7
20
|
module Moose
|
|
8
21
|
module Inventory
|
|
9
22
|
module Cli
|
|
10
23
|
##
|
|
11
|
-
#
|
|
24
|
+
# Top-level Thor application for moose-inventory.
|
|
25
|
+
# rubocop:disable Metrics/ClassLength
|
|
12
26
|
class Application < Thor
|
|
27
|
+
include Moose::Inventory::Cli::Helpers
|
|
28
|
+
|
|
13
29
|
desc 'version', 'Get the code version'
|
|
14
30
|
def version
|
|
15
31
|
puts "Version #{Moose::Inventory::VERSION}"
|
|
16
32
|
end
|
|
17
33
|
|
|
34
|
+
desc 'doctor', 'Run inventory health checks'
|
|
35
|
+
option :format, type: :string, desc: 'Emit doctor report as yaml|json|pjson'
|
|
36
|
+
def doctor
|
|
37
|
+
report = build_operation(Moose::Inventory::Operations::InventoryDoctor).call
|
|
38
|
+
render_doctor_report(report)
|
|
39
|
+
exit(1) unless report[:ok]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
desc 'export [FILE]', 'Export a canonical inventory snapshot'
|
|
43
|
+
def export(file = nil)
|
|
44
|
+
snapshot = build_operation(Moose::Inventory::Operations::InventorySnapshot).export
|
|
45
|
+
output = serialize_snapshot(snapshot)
|
|
46
|
+
|
|
47
|
+
if file.nil?
|
|
48
|
+
puts output
|
|
49
|
+
else
|
|
50
|
+
File.write(file, output)
|
|
51
|
+
puts "Exported inventory snapshot to #{file}."
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc 'import FILE', 'Import and validate an inventory snapshot'
|
|
56
|
+
option :preview, type: :boolean, desc: 'Preview snapshot import changes without writing'
|
|
57
|
+
option :preview_format, type: :string, desc: 'Emit preview as yaml|json|pjson'
|
|
58
|
+
def import(file)
|
|
59
|
+
snapshot = YAML.safe_load_file(file, aliases: false)
|
|
60
|
+
operation = build_operation(Moose::Inventory::Operations::ImportInventorySnapshot)
|
|
61
|
+
return render_import_preview(operation.preview(snapshot: snapshot)) if options[:preview]
|
|
62
|
+
|
|
63
|
+
abort('ERROR: --preview-format requires --preview.') if options[:preview_format]
|
|
64
|
+
|
|
65
|
+
result = operation.call(snapshot: snapshot)
|
|
66
|
+
record_audit({ command: 'import', action: 'import', entity_type: 'inventory',
|
|
67
|
+
entity_names: file }, result: result)
|
|
68
|
+
puts "Imported inventory snapshot from #{file}."
|
|
69
|
+
puts "Created hosts: #{result.created_hosts}"
|
|
70
|
+
puts "Created groups: #{result.created_groups}"
|
|
71
|
+
puts "Variables changed: #{result.updated_variables}"
|
|
72
|
+
puts "Associations added: #{result.associations}"
|
|
73
|
+
rescue Psych::SyntaxError => e
|
|
74
|
+
abort("ERROR: Could not parse inventory snapshot '#{file}': #{e.message}")
|
|
75
|
+
rescue db.exceptions[:moose] => e
|
|
76
|
+
abort("ERROR: #{e.message}")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
map 'db' => :database
|
|
80
|
+
desc 'audit ACTION', 'Inspect append-only inventory change history'
|
|
81
|
+
subcommand 'audit', Moose::Inventory::Cli::Audit
|
|
82
|
+
|
|
83
|
+
desc 'database ACTION', 'Inspect and manage database lifecycle state'
|
|
84
|
+
subcommand 'database', Moose::Inventory::Cli::Db
|
|
85
|
+
|
|
86
|
+
desc 'console', 'Open a small read-only inventory browsing console'
|
|
87
|
+
def console
|
|
88
|
+
Moose::Inventory::Cli::Console.new(context: inventory_context).run
|
|
89
|
+
end
|
|
90
|
+
|
|
18
91
|
desc 'group ACTION',
|
|
19
92
|
'Manipulate groups in the inventory. ' \
|
|
20
93
|
'ACTION can be add, rm, get, list, addhost, rmhost, addchild, rmchild, addvar, rmvar'
|
|
@@ -24,7 +97,62 @@ module Moose
|
|
|
24
97
|
'Manipulate hosts in the inventory. ' \
|
|
25
98
|
'ACTION can be add, rm, get, list, addgroup, rmgroup, addvar, rmvar'
|
|
26
99
|
subcommand 'host', Moose::Inventory::Cli::Host
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def render_doctor_report(report)
|
|
104
|
+
if options[:format]
|
|
105
|
+
puts serialize_data(report, options[:format].downcase)
|
|
106
|
+
else
|
|
107
|
+
render_human_doctor_report(report)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def render_human_doctor_report(report)
|
|
112
|
+
if report[:ok]
|
|
113
|
+
puts 'Inventory doctor found no issues.'
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
puts "Inventory doctor found #{report[:issue_count]} issue(s):"
|
|
118
|
+
report[:issues].each do |entry|
|
|
119
|
+
puts "- [#{entry[:severity]}] #{entry[:id]}: #{entry[:message]}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def render_import_preview(preview)
|
|
124
|
+
if options[:preview_format]
|
|
125
|
+
puts serialize_data(preview, options[:preview_format].downcase)
|
|
126
|
+
else
|
|
127
|
+
render_human_import_preview(preview)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def render_human_import_preview(preview)
|
|
132
|
+
puts 'Snapshot import preview. No changes applied.'
|
|
133
|
+
preview.fetch('summary').each do |key, value|
|
|
134
|
+
puts "#{key.tr('_', ' ').capitalize}: #{value}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def serialize_snapshot(snapshot)
|
|
139
|
+
serialize_data(snapshot, output_format)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def serialize_data(data, format)
|
|
143
|
+
case format
|
|
144
|
+
when 'yaml', 'y'
|
|
145
|
+
data.to_yaml
|
|
146
|
+
when 'prettyjson', 'pjson', 'p'
|
|
147
|
+
JSON.pretty_generate(data)
|
|
148
|
+
when 'json', 'j'
|
|
149
|
+
data.to_json
|
|
150
|
+
else
|
|
151
|
+
abort("Output format '#{format}' is not yet supported.")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
27
154
|
end
|
|
155
|
+
# rubocop:enable Metrics/ClassLength
|
|
28
156
|
end
|
|
29
157
|
end
|
|
30
158
|
end
|