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 canerrors.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.goare the canonical error types. New errors get a string table generated bystringer(apierrorcode_string.goetc.). - HTTP errors come from
errorCodes. Translate internal errors to HTTP viaerrorCodes.ToAPIErr(...)incmd/api-errors.goand write them withwriteErrorResponse.
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.goafter 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.gofiles for every type that has a//go:generate msgpdirective.golang.org/x/tools/cmd/stringer— generates the*_string.goenum 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>.gofor production code (bucket-replication.go,iam-store.go).<area>-<concept>_test.gofor unit tests.<area>-<concept>_gen.goand_gen_test.gofor generated MessagePack code.<area>-<concept>_string.goforstringeroutput (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:
- Health (
cmd/healthcheck-router.go). - Metrics (
cmd/metrics-router.go). - Admin (
cmd/admin-router.go). - KMS (
cmd/kms-router.go). - STS handlers (mounted directly in
cmd/sts-handlers.go). - 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.gofor 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.Sleepin tests; use the test clock or polling helpers intest-utils_test.go.
Things to avoid
- Global mutable HTTP clients. Use
xhttp.NewHTTPClientfrominternal/http/. fmt.Printlnfor logging. Uselogger.Info,logger.LogIf,logger.AuditLog.- Importing
internal/-private packages from outside this module. That's why they live there. - Editing
*_gen.goor*_string.goby hand. Usego generate.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.