Open-Source Wikis

/

MinIO

/

How to contribute

/

Patterns and conventions

minio/minio

Patterns and conventions

CONTRIBUTING.md says simply "MinIO is fully conformant with Golang style. Refer: Effective Go." That's true but underspecified. The conventions below are the ones the codebase actually enforces in review.

Lint configuration

golangci-lint is the linter (Makefile:lint, config in .golangci.yml). It enables errcheck, gosimple, gocritic, gosec, revive, and a handful of others. The build tag kqueue is required so lint sees the same files the build sees.

.typos.toml configures the typo checker. Add intentional MinIO-specific terms to the allowlist when needed.

Error handling

  • Wrap with context, don't lose information. Use fmt.Errorf("foo failed: %w", err) so callers can errors.Is / errors.As. Never log-and-discard.
  • Define typed errors at package boundary. cmd/object-api-errors.go, cmd/storage-errors.go, cmd/api-errors.go are the canonical error types. New errors get a string table generated by stringer (apierrorcode_string.go etc.).
  • HTTP errors come from errorCodes. Translate internal errors to HTTP via errorCodes.ToAPIErr(...) in cmd/api-errors.go and write them with writeErrorResponse.

Locking

Every object operation acquires a namespace-lock first:

lk := objectAPI.NewNSLock(bucket, object)
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
    return err
}
defer lk.Unlock(lkctx)

cmd/namespace-lock.go is the public surface. It dispatches to either the in-process cmd/local-locker.go or the cluster-wide internal/dsync/. Don't reach past it.

Globals

Most singletons live in cmd/globals.go (~600 fields). New globals must:

  • Be initialised in cmd/server-main.go after config is loaded.
  • Be guarded by a mutex if they will be read concurrently before initialisation.
  • Be exported only when truly cross-package; almost everything is unexported.

The globalObjLayerMutex is the canonical pattern for "set once, read many" globals — see the newObjectLayerFn / setObjectLayer helpers in cmd/api-router.go.

Generated code

Two generators run via go generate ./...:

  • github.com/tinylib/msgp — produces MessagePack encoders/decoders into *_gen.go files for every type that has a //go:generate msgp directive.
  • golang.org/x/tools/cmd/stringer — generates the *_string.go enum string tables.

Both tools are declared in the tool block of go.mod. Never edit *_gen.go or *_string.go files by hand. Run go generate ./... and commit the result alongside the change.

Naming and file layout in cmd/

cmd/ is intentionally flat — every concept gets one or more top-level files using a hyphenated naming scheme:

  • <area>-<concept>.go for production code (bucket-replication.go, iam-store.go).
  • <area>-<concept>_test.go for unit tests.
  • <area>-<concept>_gen.go and _gen_test.go for generated MessagePack code.
  • <area>-<concept>_string.go for stringer output (e.g. apierrorcode_string.go, format_string.go).

Don't introduce sub-packages under cmd/ — that's the established convention. Pull reusable fragments out into internal/ instead.

HTTP handlers

S3 verbs share a structure:

func (api objectAPIHandlers) MyVerbHandler(w http.ResponseWriter, r *http.Request) {
    ctx := newContext(r, w, "MyVerb")
    defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))

    objectAPI := api.ObjectAPI()
    if objectAPI == nil {
        writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
        return
    }

    bucket, object := pathParams(r)
    // Auth + signature checks via auth-handler.go
    // Lock acquisition
    // Call objectAPI.MyVerb(...)
    // Translate result via writeSuccessResponseXML or similar
}

Use pathParams and the helper functions from cmd/api-utils.go instead of fetching from r.URL directly. Errors are written with writeErrorResponse so headers and XML bodies stay consistent.

Routing

cmd/routers.go mounts the routers in this order:

  1. Health (cmd/healthcheck-router.go).
  2. Metrics (cmd/metrics-router.go).
  3. Admin (cmd/admin-router.go).
  4. KMS (cmd/kms-router.go).
  5. STS handlers (mounted directly in cmd/sts-handlers.go).
  6. S3 API (cmd/api-router.go).

Order matters because the S3 router uses a wildcard path (/{bucket}/{object:.+}) that would otherwise capture admin routes.

Configuration

Every subsystem registers its own config block in internal/config/<area>/:

var DefaultKVS = config.KVS{...}
var Help = config.HelpKVS{...}
func LookupConfig(kvs config.KVS) (Config, error)

Then cmd/config-current.go reads/writes the merged config to disk. Environment variables follow MINIO_<AREA>_<KEY> and are documented in the Help table. The pkg helpers (github.com/minio/pkg/v3/env) drive the lookup.

Tests

  • Use the harness in cmd/test-utils_test.go for in-process tests; do not reach out to network unless absolutely necessary.
  • Use naughtyDisk (cmd/naughty-disk_test.go) when you need to inject failures into a specific drive call.
  • Avoid time.Sleep in tests; use the test clock or polling helpers in test-utils_test.go.

Things to avoid

  • Global mutable HTTP clients. Use xhttp.NewHTTPClient from internal/http/.
  • fmt.Println for logging. Use logger.Info, logger.LogIf, logger.AuditLog.
  • Importing internal/-private packages from outside this module. That's why they live there.
  • Editing *_gen.go or *_string.go by hand. Use go generate.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – MinIO wiki | Factory