# 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.Client` returns the raw minio-go SDK client. No wrapper, no operation subset. Callers use `PutObject`, `RemoveObject`, `PresignedGetObject`, and all other SDK methods directly. - **Single bucket per component**: `Config.Bucket` is used only for health checks and the startup init hook. Callers needing multiple buckets instantiate multiple `Component` values 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.Bucket` does not exist when `OnStart` runs, 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 in `Options.Region` bypasses per-request region detection in minio-go, which avoids an extra `GET /?location` HTTP call on every bucket operation. - **Stateless client — `OnStop` is a no-op**: minio-go holds no persistent connections; there is nothing to close on shutdown. - **Health priority is Critical**: `Priority()` returns `health.LevelCritical`. Without MinIO, object uploads and retrievals fail completely — there is no graceful degradation path. - **`Transport` field for testing**: `Config.Transport http.RoundTripper` (tagged `env:"-"`) lets tests inject a mock transport without a live server. Setting it to `nil` in production uses the minio-go default transport. ## Patterns **Lifecycle registration:** ```go mc := minio.New(logger, cfg) lc.Append(mc) r.Get("/health", health.NewHandler(logger, mc)) ``` **Uploading an object:** ```go _, err := provider.Native().PutObject(ctx, cfg.Bucket, key, reader, size, miniogo.PutObjectOptions{ContentType: "image/jpeg"}) ``` **Generating a presigned URL:** ```go url, err := provider.Native().PresignedGetObject(ctx, cfg.Bucket, key, time.Hour, nil) ``` **Injecting into a repository:** ```go 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()` before `OnInit` has run — it returns `nil`. - Do not add a `Region` auto-detection request in tests — use `Config.Region = "us-east-1"` to bypass the `GET /?location` lookup. ## Testing Notes - Unit tests (`minio_test.go`) use an injected `mockTransport` via `Config.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_BucketError` and `TestComponent_HealthCheck_Unreachable` take ~4 seconds each due to minio-go's built-in retry backoff on errors. This is expected and correct behavior. - `compliance_test.go` (package `minio_test`) asserts `New(...)` satisfies `Component` at 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).