Files
minio/CLAUDE.md
Rene Nochebuena 3d12d18200 feat(minio): initial stable release v1.0.0 — Client interface, xerrors, GetObject, doc examples
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
2026-05-19 21:42:30 -06:00

108 lines
4.5 KiB
Markdown

# 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 /<bucket>?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 /<bucket>?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).