Repository navigation
ggshield machine setup should also install the eligible platform plugins #1439
Description
ggshield machine setup should also install the eligible platform plugins
Context
ggshield machine setup configures three protections in one idempotent run: AI hooks, global/system git hooks, and a honeytoken (ggshield/cmd/machine/setup.py). It does not touch plugins.
That leaves the machine half-set-up for accounts that are entitled to them: ggshield machine doctor already checks the machine_scan plugin (native scanner loads, endpoints:send scope) and, when it is missing, tells the user to go and run ggshield plugin install machine_scan by hand. ggshield machine scan likewise only appears once that plugin is installed (it merges into the group via PluginAwareLazyGroup(plugin_scope="machine")). So the one command that is supposed to set the machine up is the one command that does not install the thing doctor then asks for.
We don't have to guess the plan
The concern that this is "Business-only, so setup can't know" is already solved server-side. GET /v1/endpoints/plugins (PluginAPIClient.get_available_plugins) returns per-plugin entitlement:
available: true+latest_version→ installable for this account;available: false+reason→ not entitled, with a human-readable reason;404→ plugin system not enabled on the workspace (PluginsNotEnabledError).
So machine setup can simply ask, and install what comes back as available. No plan hardcoding, no new endpoint.
Proposal
Add a fourth step to machine setup, after the existing three:
- Fetch the catalog. On
PluginsNotEnabledError, a non-entitled plugin, or any network/API error: print one informational line and skip — this must not makemachine setupfail. (Contrast with the honeytoken step, which returns non-zero on a free plan — out of scope here, but worth a separate look.) - Install what is available, reusing the existing path:
PluginAPIClient.download_plugin→PluginDownloader.download_and_install→enable_installed_plugin+EnterpriseConfig.save(), i.e. what_install_from_gitguardianinggshield/cmd/plugin/install.pyalready does. That code needs a small extract into a reusable helper, since the command version callsctx.exit()on every error path. - Idempotency, as everywhere else in
setup: already installed and up to date → report and do nothing; installed but older → update (or leave toplugin update, see open questions). - Signature verification stays STRICT. No
--allow-unsignedequivalent onsetup. - New opt-out flag
--no-plugins, consistent with--no-ai-hooks/--no-git-hooks/--no-honeytokens.
Root / MDM already works: PluginDownloader lands the wheel in get_system_plugins_dir() when is_root(), which is exactly the fleet case machine setup --system targets.
Why it's worth doing
- A single root-run
machine setupfully provisions a fleet machine, including endpoint scanning. machine doctorstops asking for a manual step thatsetupcould have done.- Accounts without entitlement are unaffected — they see one skipped line.
Open questions
- Which plugins? Install every
available: trueentry in the catalog (future-proof, no hardcoded name), or onlymachine_scan(the onemachine doctorandmachine scandepend on)? Catalog-driven is the same amount of code; the risk is silently installing something new the day it ships in the catalog. - Update-on-rerun? Should a re-run of
setupupgrade an outdated plugin, or is that strictlyggshield plugin update's job? - Prompt in interactive runs? Silent install is right for MDM; interactive users might expect to be asked before a wheel is downloaded.
Activity