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
This commit is contained in:
165
minio.go
Normal file
165
minio.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
|
||||
"code.nochebuena.dev/go/health"
|
||||
"code.nochebuena.dev/go/launcher"
|
||||
"code.nochebuena.dev/go/logz"
|
||||
)
|
||||
|
||||
// Client is the MinIO operation interface for the most common bucket operations.
|
||||
// Inject it into repositories; use Native() on Component for SDK operations not covered here.
|
||||
type Client interface {
|
||||
// PutObject uploads an object to the given bucket and key.
|
||||
PutObject(ctx context.Context, bucket, key string, reader io.Reader, size int64, opts miniogo.PutObjectOptions) (miniogo.UploadInfo, error)
|
||||
// RemoveObject deletes an object from the given bucket and key.
|
||||
RemoveObject(ctx context.Context, bucket, key string, opts miniogo.RemoveObjectOptions) error
|
||||
// GetObject downloads an object and returns it as a readable stream.
|
||||
// The caller must close the returned object after reading.
|
||||
GetObject(ctx context.Context, bucket, key string, opts miniogo.GetObjectOptions) (*miniogo.Object, error)
|
||||
// PresignedGetObject returns a pre-signed URL for downloading an object.
|
||||
PresignedGetObject(ctx context.Context, bucket, key string, expires time.Duration, reqParams url.Values) (*url.URL, error)
|
||||
// HandleError maps a minio-go error to a typed xerrors value.
|
||||
HandleError(err error) error
|
||||
}
|
||||
|
||||
// Component bundles launcher lifecycle, health check, and MinIO client operations.
|
||||
type Component interface {
|
||||
launcher.Component
|
||||
health.Checkable
|
||||
Client
|
||||
// Native returns the underlying minio-go SDK client for operations not covered by Client.
|
||||
Native() *miniogo.Client
|
||||
}
|
||||
|
||||
// Config holds MinIO connection settings. The struct is embeddable in application
|
||||
// config structs and populated from environment variables.
|
||||
type Config struct {
|
||||
Endpoint string `env:"MINIO_ENDPOINT,required"`
|
||||
AccessKey string `env:"MINIO_ACCESS_KEY,required"`
|
||||
SecretKey string `env:"MINIO_SECRET_KEY,required"`
|
||||
UseSSL bool `env:"MINIO_USE_SSL" envDefault:"false"`
|
||||
Bucket string `env:"MINIO_BUCKET,required"`
|
||||
// Region defaults to "us-east-1" — the region MinIO uses by default on self-hosted
|
||||
// deployments. Setting a non-empty region bypasses per-request region detection.
|
||||
Region string `env:"MINIO_REGION" envDefault:"us-east-1"`
|
||||
// Transport is used for testing only. Nil uses the minio-go default transport.
|
||||
Transport http.RoundTripper `env:"-"`
|
||||
}
|
||||
|
||||
var _ Component = (*minioComponent)(nil)
|
||||
|
||||
type minioComponent struct {
|
||||
cfg Config
|
||||
logger logz.Logger
|
||||
mc *miniogo.Client
|
||||
}
|
||||
|
||||
// New returns a MinIO Component. Call lc.Append(mc) to manage its lifecycle.
|
||||
func New(logger logz.Logger, cfg Config) Component {
|
||||
return &minioComponent{cfg: cfg, logger: logger}
|
||||
}
|
||||
|
||||
func (c *minioComponent) OnInit() error {
|
||||
opts := &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(c.cfg.AccessKey, c.cfg.SecretKey, ""),
|
||||
Secure: c.cfg.UseSSL,
|
||||
Region: c.cfg.Region,
|
||||
Transport: c.cfg.Transport,
|
||||
}
|
||||
mc, err := miniogo.New(c.cfg.Endpoint, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("minio: create client: %w", err)
|
||||
}
|
||||
c.mc = mc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *minioComponent) OnStart() error {
|
||||
if c.mc == nil {
|
||||
return fmt.Errorf("minio: client not initialized")
|
||||
}
|
||||
exists, err := c.mc.BucketExists(context.Background(), c.cfg.Bucket)
|
||||
if err != nil {
|
||||
return fmt.Errorf("minio: check bucket %q: %w", c.cfg.Bucket, err)
|
||||
}
|
||||
if !exists {
|
||||
if err := c.mc.MakeBucket(context.Background(), c.cfg.Bucket, miniogo.MakeBucketOptions{}); err != nil {
|
||||
return fmt.Errorf("minio: create bucket %q: %w", c.cfg.Bucket, err)
|
||||
}
|
||||
c.logger.Info("minio: bucket created", "bucket", c.cfg.Bucket)
|
||||
}
|
||||
c.logger.Info("minio: connected", "bucket", c.cfg.Bucket)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *minioComponent) OnStop() error {
|
||||
c.mc = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetObject downloads an object as a readable stream. The caller must close the returned object.
|
||||
func (c *minioComponent) GetObject(ctx context.Context, bucket, key string, opts miniogo.GetObjectOptions) (*miniogo.Object, error) {
|
||||
obj, err := c.mc.GetObject(ctx, bucket, key, opts)
|
||||
if err != nil {
|
||||
return nil, HandleError(err)
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// PutObject uploads an object and maps any minio-go error to a typed xerrors value.
|
||||
func (c *minioComponent) PutObject(ctx context.Context, bucket, key string, reader io.Reader, size int64, opts miniogo.PutObjectOptions) (miniogo.UploadInfo, error) {
|
||||
info, err := c.mc.PutObject(ctx, bucket, key, reader, size, opts)
|
||||
if err != nil {
|
||||
return miniogo.UploadInfo{}, HandleError(err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// RemoveObject deletes an object and maps any minio-go error to a typed xerrors value.
|
||||
func (c *minioComponent) RemoveObject(ctx context.Context, bucket, key string, opts miniogo.RemoveObjectOptions) error {
|
||||
return HandleError(c.mc.RemoveObject(ctx, bucket, key, opts))
|
||||
}
|
||||
|
||||
// PresignedGetObject returns a pre-signed download URL and maps any minio-go error to a typed xerrors value.
|
||||
func (c *minioComponent) PresignedGetObject(ctx context.Context, bucket, key string, expires time.Duration, reqParams url.Values) (*url.URL, error) {
|
||||
u, err := c.mc.PresignedGetObject(ctx, bucket, key, expires, reqParams)
|
||||
if err != nil {
|
||||
return nil, HandleError(err)
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// HandleError maps a minio-go error to a typed xerrors value.
|
||||
func (c *minioComponent) HandleError(err error) error { return HandleError(err) }
|
||||
|
||||
// Native returns the underlying minio-go SDK client for operations not covered by Client.
|
||||
func (c *minioComponent) Native() *miniogo.Client { return c.mc }
|
||||
|
||||
// HealthCheck verifies MinIO is reachable by checking that the configured bucket exists.
|
||||
func (c *minioComponent) HealthCheck(ctx context.Context) error {
|
||||
if c.mc == nil {
|
||||
return fmt.Errorf("minio: client not initialized")
|
||||
}
|
||||
_, err := c.mc.BucketExists(ctx, c.cfg.Bucket)
|
||||
if err != nil {
|
||||
return fmt.Errorf("minio: health check: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the component name used by the health check aggregator.
|
||||
func (c *minioComponent) Name() string { return "minio" }
|
||||
|
||||
// Priority returns LevelCritical — MinIO is the primary store for object data;
|
||||
// an outage means uploads and retrievals fail completely.
|
||||
func (c *minioComponent) Priority() health.Level { return health.LevelCritical }
|
||||
Reference in New Issue
Block a user