sigstore/cosign
Patterns and conventions
The cosign codebase has settled into a handful of repeatable patterns. Following them keeps PRs small and reviewable.
Cobra commands always pair with an *Options struct
Every CLI command in cmd/cosign/cli/ follows the same shape:
func Sign() *cobra.Command {
o := &options.SignOptions{}
cmd := &cobra.Command{
Use: "sign",
Short: "...",
Long: "...",
Example: "...",
Args: cobra.MinimumNArgs(1),
PersistentPreRun: options.BindViper,
RunE: func(cmd *cobra.Command, args []string) error { /* ... */ },
}
o.AddFlags(cmd)
return cmd
}The options.SignOptions (or whatever) lives in cmd/cosign/cli/options/sign.go and exposes AddFlags(cmd *cobra.Command). Options structs do not contain runtime logic — they just declare flags and hand the populated struct off to the RunE.
When adding a new command:
- Add the
*Optionsstruct incmd/cosign/cli/options/. - Add the
cobra.Commandfactory incmd/cosign/cli/. - Register it in
cmd/cosign/cli/commands.go::New(). - Run
make docgen.
KeyOpts is the bridge from CLI to library
cmd/cosign/cli/options/key.go defines the KeyOpts struct which carries every key-source/Fulcio/Rekor/TSA setting. Library code accepts a KeyOpts and never re-reads CLI flags. This is how pkg/cosign/ and cmd/cosign/cli/sign/ stay decoupled.
Logical separation: CLI vs library
cmd/cosign/cli/<verb>.go— Cobra wiring, flag parsing, calling the right helper.cmd/cosign/cli/<verb>/(subdirectory) — orchestration that's still CLI-shaped (usesKeyOpts, talks to stderr throughinternal/ui).pkg/cosign/and friends — the core, side-effect-free-where-possible primitives.
When you find yourself writing logic in a cmd/cosign/cli/*.go file that doesn't touch flags or stderr, move it down a layer.
Stderr UI through internal/ui
Anything intended for humans (warnings, prompts, spinners, info) goes through internal/ui:
ui.Infof(ctx, "...")— informational messages on stderr.ui.Warnf(ctx, "...")— warnings, also on stderr.ui.NewSpinner(ctx, "...")— TTY-aware spinner.ui.PromptYesNo(ctx, "...")— interactive yes/no.
Primary command output goes to stdout as the format documented in CLI.md. Mixing the two is a review nit.
OCI abstractions
Read and write OCI artifacts through pkg/oci/:
pkg/oci/SignedEntity— anything you can attach signatures to (an image, an index, etc.).pkg/oci/Signatureandpkg/oci/Signatures— a signature is also av1.Layerso it can be pushed via go-containerregistry.pkg/oci/static/— build aSignaturefrom raw bytes (handy in tests).pkg/oci/remote/— fetch and push them against a registry (the largest sub-package).pkg/oci/mutate/— append a signature/attestation to an existingSignedEntity.
Don't reach for go-containerregistry/pkg/v1/remote directly; let pkg/oci/remote/ handle the deterministic-tag conventions.
Error handling
- Wrap errors with
%w:fmt.Errorf("doing X: %w", err).errors.As/errors.Isconsumers exist (e.g.cmd/cosign/main.gouseserrors.Asto extract acosignError.CosignErrorfor exit-code mapping). - For exit codes, return a
*cosignError.CosignError(seecmd/cosign/errors/). The main loop maps these toos.Exit(code). - Use
pkg/cosign/errors.gotypes for verification failures the user might want to catch programmatically (e.g.ErrNoSignaturesFound,ErrImageTagNotFound).
Environment variables are first-class
Don't add os.Getenv("FOO") directly. Add it to pkg/cosign/env/env.go:
- Define a
Variableconst. - Register a
VariableOptsentry (description, expected value,Sensitive,External). - Read it via
env.Getenv(env.VariableFoo).
This keeps cosign env accurate (cmd/cosign/cli/env.go walks the registry).
Build tags for optional backends
pivkey— PIV smart card support, requires CGO. Stub file iscmd/cosign/cli/piv_tool_disabled.gowith//go:build !pivkey.pkcs11key— same pattern, stub atcmd/cosign/cli/pkcs11_tool_disabled.go.cgo— generic CGO gate.
When you add a feature that needs CGO or a vendor library, hide it behind a build tag and add a stub for the no-CGO build so default builds keep compiling.
Don't introduce breaking API changes
Per the project's stated policy in README.md: "PRs which significantly modify or break the API will not be accepted." If a change requires breaking a public symbol in pkg/, the recommended path is to do it in sigstore-go instead.
License headers
Every Go source file starts with the Apache-2.0 boilerplate (// Copyright 20XX The Sigstore Authors. …). The whitespace.yaml workflow plus golangci-lint's goheader linter enforce it.
DCO sign-off
Every commit needs Signed-off-by: (i.e. git commit -s). The DCO bot blocks merge otherwise.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.