Add Client interface with PutObject, GetObject, RemoveObject, PresignedGetObject, and HandleError; Component now embeds Client with Native() as escape hatch for operations not covered by the interface. Add xerrors dependency: HandleError maps minio-go error codes to portable typed codes (NoSuchKey → ErrNotFound, AccessDenied → ErrPermissionDenied, BucketAlreadyExists → ErrAlreadyExists, etc.). OnStop sets c.mc = nil for lifecycle consistency. doc.go updated with launcher wiring, upload, presigned URL, GetObject, and HandleError usage examples. API committed as stable. What's included: - Config with Endpoint, AccessKey, SecretKey, UseSSL, Bucket, Region (env-driven, embeddable) - Transport field in Config for test injection (env:"-", nil uses minio-go default) - Client interface: PutObject, GetObject, RemoveObject, PresignedGetObject, HandleError - Component interface: launcher.Component + health.Checkable + Client + Native() - New(logger, cfg) constructor for lifecycle registration via lc.Append - Automatic bucket creation on OnStart if bucket does not exist - Health check via BucketExists at LevelCritical priority - HandleError: maps minio-go ErrorResponse codes to xerrors typed errors - 21 unit tests using mock HTTP transport; no live server required
4.5 KiB
minio
MinIO (S3-compatible) client with launcher lifecycle and health check integration.
Purpose
Manages the lifecycle of a minio-go SDK client: constructs it from config, verifies
bucket existence at startup (creating the bucket if absent), exposes the native SDK client
to consumers, and provides a health check via BucketExists. Does not wrap SDK operations —
callers use the full minio-go API directly via Native().
Tier & Dependencies
Tier 3 (infrastructure) — depends on:
code.nochebuena.dev/go/health(Tier 2)code.nochebuena.dev/go/launcher(Tier 2)code.nochebuena.dev/go/logz(Tier 1)github.com/minio/minio-go/v7(native MinIO client)
Does not depend on xerrors — errors from the minio-go client are wrapped with fmt.Errorf
and returned as-is.
Key Design Decisions
-
Native client exposure:
Native() *miniogo.Clientreturns the raw minio-go SDK client. No wrapper, no operation subset. Callers usePutObject,RemoveObject,PresignedGetObject, and all other SDK methods directly. -
Single bucket per component:
Config.Bucketis used only for health checks and the startup init hook. Callers needing multiple buckets instantiate multipleComponentvalues with different configs. Object key namespacing (e.g.products/{id}/{uuid}.ext) handles logical separation within a single bucket — no multi-bucket API is needed. -
Bucket auto-creation: If
Config.Bucketdoes not exist whenOnStartruns, the component creates it automatically and logs the event. Failure to create the bucket is a hard startup error — the application will not start. -
Region defaults to
us-east-1: Self-hosted MinIO uses this region by default. Setting a non-empty region inOptions.Regionbypasses per-request region detection in minio-go, which avoids an extraGET /<bucket>?locationHTTP call on every bucket operation. -
Stateless client —
OnStopis a no-op: minio-go holds no persistent connections; there is nothing to close on shutdown. -
Health priority is Critical:
Priority()returnshealth.LevelCritical. Without MinIO, object uploads and retrievals fail completely — there is no graceful degradation path. -
Transportfield for testing:Config.Transport http.RoundTripper(taggedenv:"-") lets tests inject a mock transport without a live server. Setting it tonilin production uses the minio-go default transport.
Patterns
Lifecycle registration:
mc := minio.New(logger, cfg)
lc.Append(mc)
r.Get("/health", health.NewHandler(logger, mc))
Uploading an object:
_, err := provider.Native().PutObject(ctx, cfg.Bucket, key, reader, size,
miniogo.PutObjectOptions{ContentType: "image/jpeg"})
Generating a presigned URL:
url, err := provider.Native().PresignedGetObject(ctx, cfg.Bucket, key, time.Hour, nil)
Injecting into a repository:
type imageRepo struct {
provider minio.Provider
bucket string
}
func (r *imageRepo) Upload(ctx context.Context, key string, rd io.Reader, size int64) error {
_, err := r.provider.Native().PutObject(ctx, r.bucket, key,
rd, size, miniogo.PutObjectOptions{})
return err
}
What to Avoid
- Do not wrap minio-go operations in this module. Keep
PutObject,RemoveObject, etc. in the caller (repository layer). This module is a lifecycle building block, not a domain repository. - Do not define interfaces that subset minio-go methods here. If a consumer needs a minimal testable interface, define it in the consumer package.
- Do not call
Native()beforeOnInithas run — it returnsnil. - Do not add a
Regionauto-detection request in tests — useConfig.Region = "us-east-1"to bypass theGET /<bucket>?locationlookup.
Testing Notes
- Unit tests (
minio_test.go) use an injectedmockTransportviaConfig.Transport. No live MinIO server is required. Config.Region = "us-east-1"must be set in test configs — it prevents minio-go from making an extra region detection request that the mock transport would not handle.TestComponent_OnStart_BucketErrorandTestComponent_HealthCheck_Unreachabletake ~4 seconds each due to minio-go's built-in retry backoff on errors. This is expected and correct behavior.compliance_test.go(packageminio_test) assertsNew(...)satisfiesComponentat compile time.- Integration tests requiring a live MinIO instance belong in a higher-tier test suite (e.g. the consuming application's integration test suite).