minioenv

package
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package minioenv provides MinIO environment management for testing. It offers flexible connection management with support for containers, and integration with the testenv framework.

The package provides:

  • Connector type for managing MinIO connections
  • Environment type for high-level MinIO environment management
  • ReuseStrategyCreateNewBucket for per-Connect bucket isolation
  • WithBuckets for ensuring shared buckets exist
  • Integration with Docker containers via testcontainers

Example Usage:

// Simple connector
connector := minioenv.NewConnector(
    minioenv.WithEndpoint("localhost:9000"),
    minioenv.WithAccessKey("minioadmin"),
    minioenv.WithSecretKey("minioadmin"),
)

// Connect to MinIO
client, err := env.Connect(ctx)
if err != nil {
    return err
}
defer client.Close()

// Use client for object operations...

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BeforeConnectHook

type BeforeConnectHook func(ctx context.Context, cfg *ConnectionConfig) error

BeforeConnectHook is a function called before establishing a MinIO connection. It allows modification of the connection parameters before the *minio.Client is created. This is useful for scenarios like:

  • Starting a container before the first connection
  • Modifying connection parameters based on runtime conditions
  • Implementing custom connection initialization logic

Parameters:

  • ctx: Context for cancellation and timeout
  • cfg: Connection parameters that can be modified

Returns:

  • error: Non-nil to prevent the connection from being established

Example:

hook := func(ctx context.Context, cfg *minioenv.ConnectionConfig) error {
    // Start container on first connection
    return container.Launch(ctx, cfg)
}

type Client

type Client struct {
	// *minio.Client is embedded (not named) so that all minio.Client
	// methods are promoted onto minioenv.Client. This lets callers
	// write client.BucketExists(...), client.PutObject(...), etc.
	// without an intermediate accessor.
	*minio.Client
	// contains filtered or unexported fields
}

Client is a wrapper around *minio.Client that owns bucket-cleanup lifecycle and the reuse Exit. It mirrors postgresenv.Pool but adapts the wrapped type to MinIO's *minio.Client.

Client instances are created by Environment.Connect and should be closed using the Close method when no longer needed. The Close method handles both bucket cleanup (if any was created by a ReuseStrategy) and connector reuse refcount decrement.

Thread Safety: Client is safe for concurrent use. The underlying *minio.Client is concurrency-safe per the minio-go docs.

Example:

cnt := containers.NewContainer()
connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithReuseStrategy(&minioenv.ReuseStrategyCreateNewBucket{}),
)

client, err := env.Connect(ctx)
if err != nil {
    return err
}
defer client.Close()

// Use client for object operations...
err = client.PutObject(ctx, client.Bucket(), "key", reader)

func Use

func Use(ctx context.Context, env *Environment, opts ...Option) (*Client, error)

Use connects to a MinIO environment and returns the client. This is a simple convenience function that calls the environment's Connect method, applies any provided options, and wraps any error with additional context.

This function is suitable for production code where you need to manage the client lifecycle manually. For testing, consider using UseForTesting which provides automatic cleanup.

Cleanup contract: Use does NOT register any cleanup. The caller is REQUIRED to call client.Close() exactly once when done. Forgetting to call Close() will leak a MinIO container. See the package-level "Resource Management" documentation for details. client.Close() is idempotent (subsequent calls return the first call's result and do not re-run the cleanup).

Parameters:

  • ctx: Context for cancellation and timeout
  • env: MinIO environment to connect to
  • opts: Optional configuration functions to apply after connection

Returns:

  • *Client: MinIO client ready to use
  • error: Non-nil if connection or any option fails

Example:

// Connect to environment
client, err := Use(ctx, env)
if err != nil {
    return fmt.Errorf("connect: %w", err)
}
defer client.Close()

// Use client...
err = client.PutObject(ctx, "my-bucket", "key", reader)

Example with options:

// Connect with seed object
client, err := Use(ctx, env,
    func(ctx context.Context, c *minioenv.Client) error {
        _, err := c.PutObject(ctx, "config", "seed.json", strings.NewReader("{}"))
        return err
    },
)

func UseForTesting

func UseForTesting(t *testing.T, env *Environment, opts ...Option) *Client

UseForTesting connects to a MinIO environment for use in tests. This function is designed specifically for testing scenarios and provides automatic client cleanup via t.Cleanup.

Key features:

  • Calls t.Helper() to improve error reporting
  • Automatically closes the client when the test completes (via t.Cleanup). The client is closed even if the test panics.
  • Fails the test immediately if connection fails (t.Fatal)
  • Reports cleanup errors to the test log (t.Error)
  • Applies any provided options after connection

Cleanup contract: this is the recommended helper for tests. The t.Cleanup registration ensures the client is closed exactly once per test, no matter what. If a test also wraps the client in its own t.Cleanup that calls client.Close(), the second Close is a no-op (idempotent). See the package-level "Resource Management" documentation for details.

Parameters:

  • t: Testing context (must not be nil)
  • env: MinIO environment to connect to
  • opts: Optional configuration functions to apply after connection

Returns:

  • *Client: MinIO client ready to use (automatically closed after test)

The function will call t.Fatal if connection fails, which immediately stops the test execution.

Example:

func TestWithMinio(t *testing.T) {
    t.Parallel()

    // Create environment
    cnt := containers.NewContainer()
    connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
    env := minioenv.New(
        minioenv.WithConnector(connector),
        minioenv.WithReuseStrategy(&minioenv.ReuseStrategyCreateNewBucket{}),
    )

    // Get client — automatically closed after test
    client := minioenv.UseForTesting(t, env)

    // Use client in test
    err := client.PutObject(ctx, client.Bucket(), "key", reader)
    require.NoError(t, err)
}

Note: The client is closed using t.Cleanup(), which runs after the test completes. If you need to close the client earlier, use Use instead and manage the lifecycle manually.

func (*Client) Bucket

func (c *Client) Bucket() string

Bucket returns the name of the isolated bucket this client created via ReuseStrategyCreateNewBucket.

Returns:

  • string: The bucket name. Empty if no reuse strategy was used, or if only WithBuckets was used (in which case buckets are referenced by their declared names directly).

Example:

client, _ := env.Connect(ctx)
defer client.Close()

// Put object into the isolated bucket
err := client.PutObject(ctx, client.Bucket(), "key", reader)

func (*Client) Close

func (c *Client) Close() error

Close closes the MinIO client and releases all associated resources. This method:

  1. Runs the bucket cleanup function (if set) to remove any bucket created by the reuse strategy
  2. Exits the connector reuse tracking

Order of operations: cleanup runs first, then Exit (via defer). This means that if cleanup fails, Exit still happens, so the reuse refcount is properly decremented even on cleanup failure.

Close is safe to call multiple times and from concurrent goroutines. The bucket cleanup is wrapped in a sync.Once, so subsequent calls return the error from the first call (or nil if the first cleanup succeeded) without re-running the cleanup. This protects against double-cleanup of the same bucket, which would otherwise fail with "bucket not found" on the second call.

Close should be called when the client is no longer needed. After Close is called, the client should not be used for any object operations.

Returns:

  • error: Non-nil if the first bucket cleanup failed. Cleanup errors are wrapped via fmt.Errorf("cleanup buckets: %w", err). Subsequent calls return the same error (or nil if the first cleanup succeeded) without re-running the cleanup. The caller can decide whether to ignore the error.

Example:

client, err := env.Connect(ctx)
if err != nil {
    return err
}
defer client.Close()

type CloseHook

type CloseHook func() error

CloseHook is a function that is called when the connector is closed. It allows cleanup of resources associated with the connector. This is useful for scenarios like:

  • Stopping containers when the connector is no longer needed
  • Cleaning up external resources
  • Releasing allocated resources

Returns:

  • error: Non-nil if cleanup fails

Example:

hook := func() error {
    // Stop container when connector closes
    return container.Close()
}

type ConnectionConfig

type ConnectionConfig struct {
	// Endpoint is the address of the MinIO server in host:port form.
	// Mutated by BeforeConnectHook to point at a real server.
	Endpoint string

	// AccessKey is the access key for authenticating to the MinIO server.
	AccessKey string

	// SecretKey is the secret key for authenticating to the MinIO server.
	SecretKey string

	// UseSSL controls whether connections to the MinIO server use TLS.
	// Defaults to false (suitable for local/test environments).
	UseSSL bool

	// Region is the MinIO region passed to the client. Empty means
	// "unspecified" (MinIO will use its server-default region, typically
	// "us-east-1").
	Region string
}

ConnectionConfig holds the connection parameters used to build a *minio.Client. It is passed to BeforeConnectHook so the hook can modify the endpoint (e.g. when launching a container, the hook writes the container's host:port back into Endpoint) and read the credentials to pass to the underlying service.

type Connector

type Connector struct {
	// contains filtered or unexported fields
}

Connector manages connections to a MinIO instance. It provides a flexible connection management system with support for:

  • Lazy *minio.Client creation via BeforeConnectHook
  • Hook-based lifecycle management
  • Reuse-based reference counting across multiple environments

Connector is designed to work with various MinIO deployment scenarios:

  • Local MinIO instances
  • Docker containers
  • Cloud storage (S3-compatible)

Thread Safety: Connector is safe for concurrent use. The embedded reuse instance serializes Enter/Exit operations across all goroutines.

Example:

// Create connector with container lifecycle
cnt := containers.NewContainer()
connector := NewConnector(cnt.ConnectorOptions()...)

// Use connector...
client, err := connector.Client(ctx)
if err != nil {
    return err
}

// Clean up when done
defer connector.Close()

func NewConnector

func NewConnector(opts ...ConnectorOption) *Connector

NewConnector creates a new MinIO connector with the specified options. If no options are provided, the connector uses defaults suitable for connecting to a local MinIO instance with the testcontainers default credentials.

Default values:

  • Endpoint: "0.0.0.0:9000"
  • AccessKey: "minioadmin"
  • SecretKey: "minioadmin"
  • UseSSL: false
  • Region: "" (MinIO server default)

Options are applied in order, allowing later options to override earlier ones.

Example:

// Create connector with custom settings
connector := NewConnector(
    WithEndpoint("localhost:9000"),
    WithAccessKey("minioadmin"),
    WithSecretKey("minioadmin"),
)

// Create connector with container lifecycle
cnt := containers.NewContainer()
connector := NewConnector(cnt.ConnectorOptions()...)

// Create connector with hooks
connector := NewConnector(
    WithBeforeConnectHook(func(ctx context.Context, cfg *ConnectionConfig) error {
        fmt.Println("Connecting to MinIO...")
        return nil
    }),
    WithCloseHook(func() error {
        fmt.Println("Connector closed")
        return nil
    }),
)

func (*Connector) Client

func (c *Connector) Client(ctx context.Context) (*minio.Client, error)

Client builds and returns a configured *minio.Client.

Connection Process:

  1. Builds a ConnectionConfig from the connector's settings
  2. Calls beforeConnectHook (if set), which may modify the config (e.g. to set Endpoint to a running container's host:port)
  3. Creates a *minio.Client using the (possibly modified) config

The returned client is concurrency-safe per the minio-go docs and may be used by multiple goroutines.

Parameters:

  • ctx: Context for cancellation and timeout

Returns:

  • *minio.Client: Client ready to use
  • error: Non-nil if connection fails

Example:

client, err := connector.Client(ctx)
if err != nil {
    return err
}
defer client.Close()

// Use client for object operations...
err = client.MakeBucket(ctx, "my-bucket", minio.MakeBucketOptions{})

func (*Connector) Close

func (c *Connector) Close() error

Close closes the connector by invoking the closeHook. This method is called by the reuse instance's cleanup after all users have exited and the waitUntilCleanup grace period has elapsed.

After Close is called, the connector should not be used to create new clients. The BeforeConnectHook may re-launch resources on next Enter (e.g. lazy container restart on first Connect of a new lifecycle).

Close is safe to call multiple times and from concurrent goroutines. The closeHook itself is expected to be idempotent (see containers.Container.Close — it no-ops on a nil container). The first error returned by the closeHook is cached in c.closeErr and returned by every subsequent Close call, so callers can observe the result of the cleanup even if they call Close many times.

Note: this method does NOT use sync.Once. The reuse subsystem drives Close at the end of each lifecycle (after the last user exits and WaitUntilCleanup elapses), and the closeHook must run each time the lifecycle ends so that newly-launched resources (e.g., a MinIO container re-launched by BeforeConnectHook) are properly torn down. The closeHook's own idempotency prevents double-cleanup effects when the same Close is called by both the reuse and a manual caller.

Returns:

  • error: Non-nil if the closeHook returned a non-nil error (the first such error is cached and returned by every subsequent Close call). The error is wrapped via fmt.Errorf("closeHook: %w", err).

Example:

cnt := containers.NewContainer()
connector := NewConnector(cnt.ConnectorOptions()...)

// Use connector...
client, _ := connector.Client(ctx)
defer client.Close()

// Close is called automatically by the reuse cleanup
// (or you can call it manually to force immediate cleanup)

func (*Connector) Enter

func (c *Connector) Enter(ctx context.Context) (reuse.Entered[*Connector], error)

Enter enters the connector's reuse instance, incrementing the user count. This is used by Environment to track connector usage across multiple environments.

When the connector is shared, Enter blocks if the reuse weight limit is reached. When the connector is reused after cleanup, the CreateFunc returns the same connector, and the BeforeConnectHook will re-launch the container on next connection.

type ConnectorOption

type ConnectorOption func(*Connector)

ConnectorOption is a function that configures a Connector. Options can be passed to NewConnector to customize the connector's behavior.

func WithAccessKey

func WithAccessKey(accessKey string) ConnectorOption

WithAccessKey sets the MinIO access key for authentication. The connector passes this to both the *minio.Client and to any BeforeConnectHook that needs to configure an external service (e.g. a container launcher that creates a MinIO container with matching credentials).

Default: "minioadmin"

Example:

connector := NewConnector(
    WithAccessKey("admin"),
    WithSecretKey("secret123"),
)

func WithBeforeConnectHook

func WithBeforeConnectHook(hook BeforeConnectHook) ConnectorOption

WithBeforeConnectHook sets a callback function that is invoked before establishing a MinIO connection. This allows for custom initialization logic or resource setup before the client is created.

The hook is called every time Client() is invoked, allowing for dynamic behavior based on runtime conditions (e.g. lazy container launch on first connection).

Example:

// Start container before first connection
cnt := containers.NewContainer()
connector := NewConnector(
    WithBeforeConnectHook(cnt.Launch),
    WithCloseHook(cnt.Close),
)

func WithCloseHook

func WithCloseHook(hook CloseHook) ConnectorOption

WithCloseHook sets a callback function that is invoked when the connector is closed. This allows for cleanup of resources associated with the connector.

The hook is called when Close() is invoked by the reuse instance's cleanup, after all users across all environments have exited.

Example:

// Stop container when connector closes
cnt := containers.NewContainer()
connector := NewConnector(
    WithBeforeConnectHook(cnt.Launch),
    WithCloseHook(cnt.Close),
)

func WithEndpoint

func WithEndpoint(endpoint string) ConnectorOption

WithEndpoint sets the MinIO server endpoint in host:port form.

Default: "0.0.0.0:9000"

Example:

connector := NewConnector(
    WithEndpoint("minio.example.com:9000"),
)

func WithRegion

func WithRegion(region string) ConnectorOption

WithRegion sets the MinIO region passed to the client. An empty string means "unspecified" (MinIO uses its server default, typically "us-east-1").

Default: "" (unspecified)

Example:

connector := NewConnector(
    WithRegion("us-west-1"),
)

func WithReuseOptions

func WithReuseOptions(opts ...reuse.Option[*Connector]) ConnectorOption

WithReuseOptions configures the reuse instance that manages the connector's lifecycle. These options control how the connector is shared and cleaned up across multiple environments.

Use this to set:

  • Weight: maximum concurrent users of this connector
  • WaitUntilCleanup: grace period before cleanup after last user exits
  • CleanupTimeout: maximum time allowed for cleanup operations
  • CleanupFunc: custom cleanup logic
  • ManualCleanup: disable automatic cleanup

When multiple environments share the same connector, the reuse instance ensures correct reference counting — cleanup only fires when ALL users across ALL environments have exited.

Example:

cnt := containers.NewContainer()
connector := minioenv.NewConnector(
    append(
        cnt.ConnectorOptions(),
        minioenv.WithReuseOptions(
            reuse.WithWeight[*minioenv.Connector](10),
            reuse.WithWaitUntilCleanup[*minioenv.Connector](time.Second),
        ),
    )...,
)

func WithSecretKey

func WithSecretKey(secretKey string) ConnectorOption

WithSecretKey sets the MinIO secret key for authentication.

Default: "minioadmin"

Example:

connector := NewConnector(
    WithAccessKey("admin"),
    WithSecretKey("secret123"),
)

func WithUseSSL

func WithUseSSL(useSSL bool) ConnectorOption

WithUseSSL controls whether connections to the MinIO server use TLS.

Default: false

Example:

// Connect to a MinIO server with TLS
connector := NewConnector(
    WithUseSSL(true),
)

type Environment

type Environment struct {
	// contains filtered or unexported fields
}

Environment manages MinIO client lifecycles with flexible reuse strategies. It provides a high-level interface for creating MinIO clients with automatic resource management and bucket isolation.

Environment supports different reuse strategies to balance isolation and performance:

  • No strategy (default): Raw client, no bucket management
  • CreateNewBucket: Full isolation with a new bucket per connection

Environment integrates with the reuse package to efficiently manage connectors and resources across multiple connections and tests.

Thread Safety: Environment is safe for concurrent use. Multiple goroutines can call Connect concurrently, and each will receive an independent Client.

Example:

cnt := containers.NewContainer()
connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
env := minioenv.New(
    minioenv.WithConnector(connector),
)

client, err := env.Connect(ctx)
if err != nil {
    return err
}
defer client.Close()

func New

func New(opts ...EnvironmentOption) *Environment

New creates a new MinIO environment with the specified options. The environment manages client lifecycles and bucket isolation according to the configured strategy.

Default Configuration:

  • Reuse Strategy: None (raw client, no bucket management)
  • Connector: Default connector with standard MinIO settings
  • WithBuckets: none

Options are applied in order, allowing later options to override earlier ones.

Example:

// Simple environment
env := minioenv.New()

// Environment with container
cnt := containers.NewContainer()
connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
env := minioenv.New(minioenv.WithConnector(connector))

// Environment with all options
env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithReuseStrategy(&ReuseStrategyCreateNewBucket{}),
    minioenv.WithBuckets("config"),
)

func (*Environment) Connect

func (e *Environment) Connect(ctx context.Context) (*Client, error)

Connect creates a new MinIO client according to the configured reuse strategy.

Connection Process:

  1. Acquires a connector from the reuse pool
  2. Ensures all WithBuckets buckets exist (idempotent)
  3. Applies the configured reuse strategy: - No strategy: Returns the raw client - CreateNewBucket: Creates a new bucket, returns client with cleanup
  4. Returns a Client ready for object operations

The returned Client must be closed when no longer needed. Closing the client automatically cleans up any bucket created by the reuse strategy and decrements the connector reuse refcount.

Parameters:

  • ctx: Context for cancellation and timeout

Returns:

  • *Client: MinIO client ready for object operations
  • error: Non-nil if connection fails

Example:

client, err := env.Connect(ctx)
if err != nil {
    return err
}
defer client.Close()

// Use client for object operations...
err = client.PutObject(ctx, client.Bucket(), "key", reader)

type EnvironmentOption

type EnvironmentOption func(*Environment)

EnvironmentOption is a function that configures an Environment. Options can be passed to New to customize the environment's behavior.

func WithBuckets

func WithBuckets(names ...string) EnvironmentOption

WithBuckets is an EnvironmentOption that ensures the listed buckets exist before Connect returns. The check is idempotent: if a bucket already exists, it is left alone. If creation fails because the bucket appeared between the existence check and the create call (race with another concurrent test), the error is treated as success.

Buckets declared via WithBuckets are NOT removed when the Client is closed — they live for the entire env lifecycle and persist across tests/runs. Use this for shared, well-known buckets that multiple tests reference by name.

For per-Connect isolated buckets, use ReuseStrategyCreateNewBucket instead. The two can be combined: WithBuckets for shared fixtures, the reuse strategy for an isolated working bucket.

Example:

env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithBuckets("config", "uploads"),
)

client, _ := env.Connect(ctx)
defer client.Close()

// "config" and "uploads" are guaranteed to exist
_ = client.PutObject(ctx, "config", "app.yaml", reader)

func WithConnector

func WithConnector(connector *Connector) EnvironmentOption

WithConnector sets the connector for the environment. The connector manages its own reuse lifecycle internally, so multiple environments can safely share the same connector — cleanup only fires when ALL users across ALL environments have exited.

Example:

cnt := containers.NewContainer()
connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithReuseStrategy(&ReuseStrategyCreateNewBucket{}),
)

func WithReuseStrategy

func WithReuseStrategy(strategy ReuseStrategy) EnvironmentOption

WithReuseStrategy sets the strategy for reusing buckets. Different strategies provide different levels of isolation.

If not set, the environment returns a raw client without any bucket management (the user creates and removes buckets themselves).

Example:

// Create new bucket per connection
env := minioenv.New(
    minioenv.WithReuseStrategy(&ReuseStrategyCreateNewBucket{}),
)

type Option

type Option func(ctx context.Context, client *Client) error

Option is a function that configures a MinIO client after it is established. Options are applied in order and can be used for tasks like:

  • Putting seed objects into a known bucket
  • Configuring bucket policies
  • Setting up bucket notifications

The option receives the context and the established MinIO client, and returns an error if the configuration fails.

Example:

// Custom option that seeds an object
func WithSeedObject(bucket, key string, body io.Reader) minioenv.Option {
    return func(ctx context.Context, c *minioenv.Client) error {
        _, err := c.PutObject(ctx, bucket, key, body, -1, -1)
        return err
    }
}

type ReuseStrategy

type ReuseStrategy interface {
	// contains filtered or unexported methods
}

ReuseStrategy defines the strategy for reusing buckets across multiple connections. Different strategies provide different levels of isolation and cleanup semantics.

Implementations:

  • ReuseStrategyCreateNewBucket: Creates a new bucket per connection
  • nil: Uses the raw client without any bucket management

ReuseStrategy is a sealed interface — only types defined in this package can implement it.

type ReuseStrategyCreateNewBucket

type ReuseStrategyCreateNewBucket struct {
	// NewBucketName is a function that generates a unique bucket name.
	// If nil, a default name generator is used that creates names like
	// "bucket123456789" using crypto/rand.
	//
	// The function should return a unique name each time it's called.
	// Names are automatically converted to lowercase.
	NewBucketName func() string
}

ReuseStrategyCreateNewBucket creates a new bucket for each connection. This provides complete isolation between connections but the bucket is removed when the client is closed, keeping the MinIO server clean.

Each time Connect is called, a new bucket is created with a unique name. When the Client is closed, the bucket is automatically removed via RemoveBucket (using context.WithoutCancel so cleanup succeeds even if the caller's context is canceled).

Use this strategy when:

  • You need complete isolation between tests
  • Tests modify bucket-level settings
  • Tests create/drop objects in the bucket

Example:

cnt := containers.NewContainer()
connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithReuseStrategy(&ReuseStrategyCreateNewBucket{}),
)

Directories

Path Synopsis
Package containers provides Docker container management for MinIO test environments.
Package containers provides Docker container management for MinIO test environments.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL