Open-Source Wikis

/

Cosign

/

How to contribute

/

Patterns and conventions

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:

  1. Add the *Options struct in cmd/cosign/cli/options/.
  2. Add the cobra.Command factory in cmd/cosign/cli/.
  3. Register it in cmd/cosign/cli/commands.go::New().
  4. 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 (uses KeyOpts, talks to stderr through internal/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/Signature and pkg/oci/Signatures — a signature is also a v1.Layer so it can be pushed via go-containerregistry.
  • pkg/oci/static/ — build a Signature from 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 existing SignedEntity.

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.Is consumers exist (e.g. cmd/cosign/main.go uses errors.As to extract a cosignError.CosignError for exit-code mapping).
  • For exit codes, return a *cosignError.CosignError (see cmd/cosign/errors/). The main loop maps these to os.Exit(code).
  • Use pkg/cosign/errors.go types 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:

  1. Define a Variable const.
  2. Register a VariableOpts entry (description, expected value, Sensitive, External).
  3. 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 is cmd/cosign/cli/piv_tool_disabled.go with //go:build !pivkey.
  • pkcs11key — same pattern, stub at cmd/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.

Patterns and conventions – Cosign wiki | Factory