Plugins¶
A plugin binary can implement any of four independent contracts:
| Contract | Subcommands | Used by | <kind> names |
|---|---|---|---|
| Component | component pull/push/remote |
build, distribute |
a purl type |
| SBOM generation | sbom generate |
sbom generate |
a deployment medium |
| Security scanning | security scan/supported-components |
security scan, scan on pull |
a scanner |
| Signing | signature sign/attest/verify/verify-attestation/supported-types |
--sign, --verify |
a signing scheme |
Every call bomify makes to a plugin goes through plugin.Invoke: run
the binary, parse stdout as the contract's JSON result, and fold stderr
into the error on failure. plugin.Find, which locates the binary
first, refuses one that doesn't speak the contract version bomify needs
(see
Plugin installation). On the plugin side, pkg/plugin's
ComponentCommand, SecurityCommand, and SignatureCommand build each
contract's subcommands (flags, validation, logging, output) around a
small Go interface, so a first-party plugin's cmd package is only an
adapter over its internal logic. NewRootCommand adds the contract
subcommand every plugin answers with the version of each contract it
speaks (see
contract versions).
Component plugins¶
For each SBOM component, bomify build and bomify distribute:
- Detect the kind from the purl type (
plugin.Detect):pkg:oci/...isoci. A missing or unparseable purl fails. - Find
bomify-plugin-<kind>inplugins/. The exception ispkg:bomify-plugin/...: a plugin binary itself, whichbuildcopies directly (see Plugin installation) anddistributeskips. - Delegate:
component pull --purl --outputorcomponent push --purl --input --remote. The plugin prints one JSON{outputPath, message, hash}, wherehashis the SHA-256 of what it pulled.distributefirst callscomponent remoteto learn the component's origin, then picks the destination:--remote <kind>=<endpoint>, else the bestconf/distribution.jsonrule matching that origin (internal/distribution).
stdout is reserved for the result and stderr for one fatal message.
Plugins log to --log, logs/<purlHash>.log, which bomify creates
fresh before each call, streams to its own stdout under --verbose
(prefixed [<verb> <purl>]), and deletes afterwards. --log-color
passes on whether bomify's stdout is a terminal.
--check on build/distribute passes --check=true instead of
--output/--input, asking the plugin to cheaply confirm the transfer
would succeed. plugin.CheckPull/CheckPush are stateless: no pid
file, manifest, or layer directory, and build --check records no build.
Source: docs/diagrams/plugin-dispatch.mmd
SBOM generation¶
A plugin's sbom generate takes its options as a --config file, which
it validates itself. Its SBOM is the part of a CycloneDX BOM bomify reads
(metadata, components, dependencies), following rules strict enough to
merge several plugins' SBOMs into one: every component identified by its
purl, nothing nested, no timestamp. pkg/plugin's SBOMCommand
implements both sides for a Go plugin, and its ValidateGenerated
checks the rules; cyclonedx-go itself checks the CycloneDX objects, as
bomify decodes them rejecting unknown fields.
bomify sbom compose is its caller
(internal/sbom's
Composition). It finds every source's plugin before running any,
reads each SBOM strictly (sbom.DecodeStrict), then merges them:
components by purl
(sources disagreeing on one's type, name, version, or hashes fail),
dependencies unioned, and the composition as the root, depending on
each source's root. Everything is sorted and the spec version pinned,
so an unchanged composition yields the same SBOM, and bomify build
the same package.
Concurrent, idempotent pulls¶
plugin.Pull is safe to call concurrently, even across processes, for
components sharing a purl hash:
manifests/<purlHash>.pidholds the PID of the process pulling it. A second caller waits until the file is gone or its process is dead.- A stale pid file (process gone) is discarded and the pull redone.
- If a manifest exists and nothing is pulling, the result is reused. A reused or fresh result is always checked against the SBOM's declared hash, so a same-purl component with a different hash never slips through.