[Feature]: `system property ls`: design the plugin-properties contract before extending output (follow-up to #607)
- Dominant language
- Swift
- Stars
- 49.9k
- Forks
- 1.8k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 22
Description
This is a scoping follow-up to #607, which asks `container system property ls` to surface plugin settings alongside core configuration. After investigating the codebase and running a throwaway plugin against the live system, the request can't be cleanly resolved by only changing `PropertyList` — there's no producer side. This issue proposes the contract and a staged plan.
### Current state (as of `3512373`)
1. **`property ls` only knows the seven hardcoded sections** (`build`, `container`, `dns`, `kernel`, `network`, `registry`, `vminit`). `Application.PropertyList.run()` (`Sources/ContainerCommands/System/Property/PropertyList.swift:44`) loads `ContainerSystemConfig` and renders it. `ContainerSystemConfig.init(from:)` (`Sources/ContainerPersistence/ContainerSystemConfig.swift:63-72`) only `decodeIfPresent`s those seven keys.
2. **`[plugin.]` sections in `config.toml` are silently dropped** at decode time. No warning, no error — they parse fine, then go nowhere. Verified end-to-end: writing `[plugin.container-hello]` with three keys produces zero diagnostic and zero output in `property ls`.
3. **Infrastructure for plugin-scoped config exists but has no production conformers.** `ConfigurationLoader.loadForPlugin` (`Sources/ContainerPersistence/ConfigurationLoader.swift:123`) scopes a TOML snapshot to `plugin.` and decodes it. `LoadablePluginConfiguration` is only conformed to by test fixtures (`Tests/ContainerPersistenceTests/ConfigurationLoaderTests.swift:36,53,64`).
4. **No in-tree plugin declares any user-tunable property.** The three plugins (`container-core-images`, `container-network-vmnet`, `container-runtime-linux`) ship `config.toml` files containing only manifest metadata (`abstract`, `author`, `version`, `servicesConfig`). All current per-plugin knobs are CLI flags or hardcoded (e.g. `RuntimeLinuxHelper+Start.swift:66-72` has a literal `FIXME: should be configurable elsewhere`).
So `property ls` has no producer to read from. The issue is implicitly asking the project to design that producer.
### Why a raw `[plugin.*]` passthrough isn't enough
A minimal fix that emits whatever the user wrote under `[plugin.*]` would:
- show nothing for plugins the user hasn't touched (defeats discoverability),
- echo back typos and stale keys without validation (silently misleading),
- not lay groundwork for `property get/set/unset`.
### Proposal
Two-PR plan:
**PR 1 — define the contract.**
- Extend the plugin manifest (`Sources/ContainerPlugin/PluginConfig.swift`) with an optional `[properties]` table: each entry declares `type`, `default`, and `description`. Loaded at discovery time by `DefaultPluginFactory.create` (`Sources/ContainerPlugin/PluginFactory.swift:56-78`). Optional — zero impact on existing plugins.
- Extend `PropertyList.run()` to enumerate plugins via `PluginLoader.findPlugins()`, read each plugin's declared schema, layer in the user's actual values via `ConfigurationLoader.loadForPlugin` for plugins that conform to `LoadablePluginConfiguration` (or via a generic snapshot read otherwise), and merge into the TOML/JSON output.
- Emit a warning for any `[plugin.]` section in `config.toml` whose `` doesn't match a discovered plugin, instead of dropping silently.
**PR 2 — migrate one real plugin to prove the path.**
- Pick `container-network-vmnet`. Promote its `--variant` CLI flag (`NetworkVmnetHelper+Start.swift:62-68`) into a declared property under `[plugin.container-network-vmnet]`. Have the API server read it via `loadForPlugin` when starting the helper. Now `property ls` has a non-empty plugin section to display in the real install.
### Out of scope (follow-ups)
- `system property get/set/unset` for plugin-scoped keys (needs decisions about validation and write paths).
- The CLI-plugin invocation naming quirk surfaced while testing this — users have to type `container container-foo` because `DefaultCommand` looks up plugins by literal directory name (`DefaultCommand.swift:84`, `PluginLoader.findPlugin` `PluginLoader.swift:179-200`). Unrelated to property output, worth its own issue.
### References
- Original request: #607
### Code of Conduct
- [x] I agree to follow this project's Code of Conduct
Contributor guide
Assessment
This issue has not been assessed yet.