4.5 KiB
name, description
| name | description |
|---|---|
| plugin-contract-guard | Invariants and change procedure for the target-plugin / extension system — plugin manifests, admin plugin/extension catalog and instance APIs, secret redaction, external-plugin install policy. Use when editing crates/targets (manifest, plugin, control_plane, catalog, runtime), crates/extension-schema, or rustfs/src/admin plugin_contract.rs / plugins_*.rs / extensions.rs / target_descriptor.rs. |
Plugin & Extension Contract Guard
The "plugin system" spans four surfaces that must stay consistent:
| Surface | Location |
|---|---|
| Manifests & registry | crates/targets/src/{manifest,plugin}.rs |
| Install/enable planning (control plane) | crates/targets/src/control_plane.rs |
| Extension schemas | crates/extension-schema/src/lib.rs, crates/targets/src/catalog/extension.rs |
| Admin API contract | rustfs/src/admin/plugin_contract.rs, handlers/{plugins_catalog,plugins_instances,extensions,target_descriptor}.rs |
Hard invariants (verify before merging)
-
Secrets have one source of truth. Secret config keys are declared only in the plugin manifest (
TargetPluginManifest.secret_fields,crates/targets/src/manifest.rs) and flow to admin viaAdminTargetSpec.secret_fields. Never add a hand-maintained per-service secret table in a handler; if redaction misses a field, fix the manifest. -
Redaction must round-trip. Instance GET responses replace secret values with
***redacted***(REDACTED_SECRET_VALUEinplugins_instances.rs). Instance PUT restores the stored secret when it receives that placeholder back (restore_redacted_secret_values). Any new read or write path for target config must keep both halves: redact on the way out, restore the placeholder on the way in. The placeholder literal must never be persisted. -
Fixtures never reach production responses.
example_external_webhook_plugin()(crates/targets/src/catalog/mod.rs) is a test/demo fixture for control-plane planning tests. Production catalog/extension handlers must not include it; regression tests (plugin_catalog_never_exposes_example_or_external_fixtures,extension_catalog_never_exposes_example_or_external_fixtures) enforce it. -
External plugin flow is planning-only and deny-by-default.
plan_external_target_plugin_actionreturns decisions, it executes nothing.TargetPluginExternalFlowGate::default()is fully closed andTargetPluginInstallPolicy::default().allowed_download_hostsis empty — keep it that way; tests opt in via explicit policies. Install validation requires https, an allowlisted host, a full 64-hex-char sha256 digest, signature and provenance URIs, and an artifact matching the hosttarget_triple. -
Custom target types must not collide. Unknown target types get an interned unique
custom:<type>plugin id (custom_plugin_idinmanifest.rs). Custom plugins with secrets must register viaTargetPluginDescriptor::with_manifestand declaresecret_fields;::newderives a manifest with no secrets.
Changing the admin JSON contract
- Shapes are locked twice in
plugin_contract.rstests: insta snapshots (rustfs/src/admin/snapshots/) plus literaljson!assertions. Update both deliberately; a shape change is a console-facing API change. - Field naming is
snake_case, except discovery blocks (runtimeCapabilities,clusterSnapshot,extensionsCatalog) which are camelCase by cross-endpoint convention (same shape insystem.rs,console.rs,pools.rs). Do not "fix" that inconsistency locally. - Contract types deliberately duplicate
rustfs_targetstypes (anti-corruption layer). Add aFromimpl; do not serialize internal types directly.
Handler conventions
- Every new admin plugin/extension route needs authorization at the top of
calland aninclude_str!guard test asserting it (repo-wide pattern — seeplugin_instance_handlers_require_admin_authorization_contract). - Reads use
GetBucketTargetAction(instances) orServerInfoAdminAction(catalogs); writes useSetBucketTargetAction. - Refresh persisted module switches once per request
(
refresh_persisted_module_switches), then evaluate the syncmodule_disabled_block_reasonper domain — do not re-read the store per domain or per instance.
Generic bounds
Event-payload generics use the PluginEvent blanket trait
(crates/targets/src/plugin.rs). Do not respell
Send + Sync + 'static + Clone + Serialize + DeserializeOwned.