Skip to content

ggshield machine setup should also install the eligible platform plugins #1439

Description

@clement-tourriere

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:

  1. Fetch the catalog. On PluginsNotEnabledError, a non-entitled plugin, or any network/API error: print one informational line and skip — this must not make machine setup fail. (Contrast with the honeytoken step, which returns non-zero on a free plan — out of scope here, but worth a separate look.)
  2. Install what is available, reusing the existing path: PluginAPIClient.download_pluginPluginDownloader.download_and_installenable_installed_plugin + EnterpriseConfig.save(), i.e. what _install_from_gitguardian in ggshield/cmd/plugin/install.py already does. That code needs a small extract into a reusable helper, since the command version calls ctx.exit() on every error path.
  3. Idempotency, as everywhere else in setup: already installed and up to date → report and do nothing; installed but older → update (or leave to plugin update, see open questions).
  4. Signature verification stays STRICT. No --allow-unsigned equivalent on setup.
  5. 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 setup fully provisions a fleet machine, including endpoint scanning.
  • machine doctor stops asking for a manual step that setup could have done.
  • Accounts without entitlement are unaffected — they see one skipped line.

Open questions

  • Which plugins? Install every available: true entry in the catalog (future-proof, no hardcoded name), or only machine_scan (the one machine doctor and machine scan depend 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 setup upgrade an outdated plugin, or is that strictly ggshield 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    status:newThis issue needs to be reviewedtype:featureFeature request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions