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
108 lines
4.5 KiB
Markdown
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).
|