package minio import ( "context" "net/http" "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) // ConnectionManager manages MinIO connections and operations type ConnectionManager struct { client *minio.Client config Config logger Logger } // NewConnectionManager creates a new MinIO connection manager func NewConnectionManager(config Config, logger Logger) (*ConnectionManager, error) { if err := config.Validate(); err != nil { return nil, err } return &ConnectionManager{ config: config, logger: logger, }, nil } // Connect establishes connection to MinIO func (cm *ConnectionManager) Connect() error { // Create MinIO client minioClient, err := minio.New(cm.config.Endpoint, &minio.Options{ Creds: credentials.NewStaticV4(cm.config.AccessKeyID, cm.config.SecretAccessKey, ""), Secure: cm.config.UseSSL, Region: cm.config.Region, Transport: &http.Transport{ ResponseHeaderTimeout: cm.config.RequestTimeout, TLSHandshakeTimeout: 10 * time.Second, DisableKeepAlives: false, }, }) if err != nil { cm.logger.Error("Failed to create MinIO client", map[string]interface{}{ "endpoint": cm.config.Endpoint, "error": err.Error(), }) return NewErrorWithCause(ErrCodeConnectionFailed, "failed to create MinIO client", err) } cm.client = minioClient // Test connection by checking if we can reach the server ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Try bucket exists check with shorter timeout exists, err := cm.client.BucketExists(ctx, cm.config.DefaultBucket) if err != nil { cm.logger.Error("Failed to connect to MinIO", map[string]interface{}{ "endpoint": cm.config.Endpoint, "bucket": cm.config.DefaultBucket, "error": err.Error(), }) return NewErrorWithCause(ErrCodeConnectionFailed, "failed to connect to MinIO", err) } cm.logger.Info("Successfully connected to MinIO", map[string]interface{}{ "endpoint": cm.config.Endpoint, "region": cm.config.Region, "bucket": cm.config.DefaultBucket, "exists": exists, }) return nil } // Disconnect closes the MinIO connection func (cm *ConnectionManager) Disconnect(ctx context.Context) error { if cm.client == nil { return nil } // MinIO client doesn't have a disconnect method, but we can set it to nil cm.client = nil cm.logger.Info("Disconnected from MinIO", nil) return nil } // Ping tests the MinIO connection func (cm *ConnectionManager) Ping(ctx context.Context) error { if cm.client == nil { return ErrNotConnected } _, err := cm.client.ListBuckets(ctx) if err != nil { cm.logger.Error("MinIO ping failed", map[string]interface{}{ "error": err.Error(), }) return NewErrorWithCause(ErrCodeConnectionFailed, "ping failed", err) } return nil } // IsConnected returns true if connected to MinIO func (cm *ConnectionManager) IsConnected() bool { return cm.client != nil } // GetClient returns the underlying MinIO client func (cm *ConnectionManager) GetClient() interface{} { return cm.client } // CreateBucket creates a new bucket if it doesn't exist func (cm *ConnectionManager) CreateBucket(ctx context.Context, bucketName string) error { if cm.client == nil { return ErrNotConnected } if bucketName == "" { return ErrInvalidBucketName } exists, err := cm.client.BucketExists(ctx, bucketName) if err != nil { cm.logger.Error("Failed to check bucket existence", map[string]interface{}{ "bucket": bucketName, "error": err.Error(), }) return NewErrorWithCause(ErrCodeBucketNotFound, "failed to check bucket existence", err) } if exists { cm.logger.Debug("Bucket already exists", map[string]interface{}{ "bucket": bucketName, }) return nil } err = cm.client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{ Region: cm.config.Region, }) if err != nil { cm.logger.Error("Failed to create bucket", map[string]interface{}{ "bucket": bucketName, "error": err.Error(), }) return NewErrorWithCause("BUCKET_CREATE_FAILED", "failed to create bucket", err) } cm.logger.Info("Bucket created successfully", map[string]interface{}{ "bucket": bucketName, }) return nil } // DeleteBucket deletes a bucket func (cm *ConnectionManager) DeleteBucket(ctx context.Context, bucketName string) error { if cm.client == nil { return ErrNotConnected } if bucketName == "" { return ErrInvalidBucketName } err := cm.client.RemoveBucket(ctx, bucketName) if err != nil { cm.logger.Error("Failed to delete bucket", map[string]interface{}{ "bucket": bucketName, "error": err.Error(), }) return NewErrorWithCause("BUCKET_DELETE_FAILED", "failed to delete bucket", err) } cm.logger.Info("Bucket deleted successfully", map[string]interface{}{ "bucket": bucketName, }) return nil } // BucketExists checks if a bucket exists func (cm *ConnectionManager) BucketExists(ctx context.Context, bucketName string) (bool, error) { if cm.client == nil { return false, ErrNotConnected } if bucketName == "" { return false, ErrInvalidBucketName } exists, err := cm.client.BucketExists(ctx, bucketName) if err != nil { cm.logger.Error("Failed to check bucket existence", map[string]interface{}{ "bucket": bucketName, "error": err.Error(), }) return false, NewErrorWithCause(ErrCodeBucketNotFound, "failed to check bucket existence", err) } return exists, nil } // ListBuckets lists all buckets func (cm *ConnectionManager) ListBuckets(ctx context.Context) ([]string, error) { if cm.client == nil { return nil, ErrNotConnected } buckets, err := cm.client.ListBuckets(ctx) if err != nil { cm.logger.Error("Failed to list buckets", map[string]interface{}{ "error": err.Error(), }) return nil, NewErrorWithCause("LIST_BUCKETS_FAILED", "failed to list buckets", err) } bucketNames := make([]string, len(buckets)) for i, bucket := range buckets { bucketNames[i] = bucket.Name } cm.logger.Debug("Buckets listed successfully", map[string]interface{}{ "count": len(bucketNames), }) return bucketNames, nil } // GetBucketInfo gets bucket information func (cm *ConnectionManager) GetBucketInfo(ctx context.Context, bucketName string) (map[string]interface{}, error) { if cm.client == nil { return nil, ErrNotConnected } if bucketName == "" { return nil, ErrInvalidBucketName } // For now, return basic bucket info // In a real implementation, you might want to get more detailed info info := map[string]interface{}{ "name": bucketName, "region": cm.config.Region, } cm.logger.Debug("Bucket info retrieved", map[string]interface{}{ "bucket": bucketName, }) return info, nil }