s3

package module
v0.0.0-...-0eac00f Latest Latest
Warning

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

Go to latest
Published: Nov 13, 2025 License: MIT Imports: 18 Imported by: 0

README

RoadRunner S3 Storage Plugin

A high-performance Go plugin for RoadRunner that provides S3-compatible object storage operations via RPC interface.

Features

  • Multiple Bucket Support: Configure multiple S3 buckets (AWS S3, MinIO, DigitalOcean Spaces, etc.)
  • Dynamic Configuration: Register buckets at runtime via RPC
  • Full S3 Operations: Upload, download, copy, move, delete, metadata operations
  • Concurrent Operations: Built-in goroutine management and connection pooling
  • Public URL Generation: Generate public and presigned URLs
  • Visibility Control: Manage file ACLs (public/private)
  • Large File Support: Multipart upload for files > 5MB
  • Graceful Shutdown: Proper context cancellation and operation tracking

Installation

go get github.com/roadrunner-server/s3-plugin

Configuration

Configuration Structure

The S3 plugin uses a two-level configuration structure:

  1. Servers: Define S3 server credentials and endpoints (can be reused by multiple buckets)
  2. Buckets: Define individual buckets that reference servers

This separation allows you to:

  • Share credentials across multiple buckets
  • Easily manage different S3 providers (AWS, MinIO, DigitalOcean Spaces, etc.)
  • Keep sensitive credentials in one place
Basic Configuration (.rr.yaml)
s3:
  # Default bucket to use when none specified
  default: uploads

  # Server definitions (credentials and endpoints)
  servers:
    # AWS S3 server
    aws-primary:
      region: us-east-1
      endpoint: ""  # Empty for AWS S3 (uses default endpoint)
      credentials:
        key: ${AWS_ACCESS_KEY_ID}
        secret: ${AWS_SECRET_ACCESS_KEY}
        token: ${AWS_SESSION_TOKEN}  # Optional for temporary credentials

    # MinIO server
    minio-dev:
      region: us-east-1
      endpoint: http://localhost:9000
      credentials:
        key: minioadmin
        secret: minioadmin

  # Bucket definitions (reference servers)
  buckets:
    # Public uploads bucket
    uploads:
      server: aws-primary           # References server from servers section
      bucket: my-uploads-bucket     # Actual S3 bucket name
      prefix: "uploads/"            # Optional path prefix
      visibility: public            # "public" or "private"
      max_concurrent_operations: 100  # Optional, default: 100
      part_size: 5242880           # Optional, default: 5MB (multipart uploads)
      concurrency: 5                # Optional, default: 5 (goroutines)

    # Private documents bucket (same AWS account)
    documents:
      server: aws-primary           # Reuses same credentials
      bucket: company-documents
      prefix: "docs/"
      visibility: private
      max_concurrent_operations: 50

    # Development bucket on MinIO
    dev-storage:
      server: minio-dev
      bucket: dev-bucket
      visibility: public
Multi-Provider Configuration Example
s3:
  default: uploads

  servers:
    # AWS S3 US East
    aws-us:
      region: us-east-1
      endpoint: ""
      credentials:
        key: ${AWS_ACCESS_KEY_ID}
        secret: ${AWS_SECRET_ACCESS_KEY}

    # AWS S3 EU West
    aws-eu:
      region: eu-west-1
      endpoint: ""
      credentials:
        key: ${AWS_EU_ACCESS_KEY_ID}
        secret: ${AWS_EU_SECRET_ACCESS_KEY}

    # DigitalOcean Spaces
    do-spaces:
      region: nyc3
      endpoint: https://nyc3.digitaloceanspaces.com
      credentials:
        key: ${DO_SPACES_KEY}
        secret: ${DO_SPACES_SECRET}

    # Backblaze B2
    backblaze:
      region: us-west-002
      endpoint: https://s3.us-west-002.backblazeb2.com
      credentials:
        key: ${B2_APPLICATION_KEY_ID}
        secret: ${B2_APPLICATION_KEY}

  buckets:
    # User uploads in US
    uploads:
      server: aws-us
      bucket: app-uploads-us
      prefix: "uploads/"
      visibility: public

    # User avatars in EU (GDPR compliance)
    avatars-eu:
      server: aws-eu
      bucket: app-avatars-eu
      prefix: "avatars/"
      visibility: public

    # CDN assets on DigitalOcean
    cdn-assets:
      server: do-spaces
      bucket: cdn-bucket
      prefix: "static/"
      visibility: public
      max_concurrent_operations: 200

    # Long-term backups on Backblaze B2
    backups:
      server: backblaze
      bucket: app-backups
      prefix: "daily/"
      visibility: private
      part_size: 104857600  # 100MB chunks for large files
      concurrency: 10

PHP Usage

Basic Operations
use Spiral\Goridge\RPC\RPC;

$rpc = new RPC(/* connection */);

// Upload a file
$response = $rpc->call('s3.Write', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg',
    'content' => base64_encode(file_get_contents('photo.jpg')),
    'visibility' => 'public'  // Optional
]);
// Returns: ['success' => true, 'pathname' => '...', 'size' => 12345, 'last_modified' => 1234567890]

// Download a file
$response = $rpc->call('s3.Read', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg'
]);
// Returns: ['content' => base64_data, 'size' => 12345, 'mime_type' => 'image/jpeg', 'last_modified' => 1234567890]

// Check if file exists
$response = $rpc->call('s3.Exists', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg'
]);
// Returns: ['exists' => true]

// Delete a file
$response = $rpc->call('s3.Delete', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg'
]);
// Returns: ['success' => true]

// Get file metadata
$response = $rpc->call('s3.GetMetadata', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg'
]);
// Returns: ['size' => 12345, 'mime_type' => 'image/jpeg', 'last_modified' => 1234567890, 'visibility' => 'public']

// Get public URL (permanent)
$response = $rpc->call('s3.GetPublicURL', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg',
    'expires_in' => 0  // 0 for permanent URL
]);
// Returns: ['url' => 'https://...']

// Get presigned URL (expires in 1 hour)
$response = $rpc->call('s3.GetPublicURL', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg',
    'expires_in' => 3600  // seconds
]);
// Returns: ['url' => 'https://...', 'expires_at' => 1234567890]
Advanced Operations
// Copy file within same bucket
$response = $rpc->call('s3.Copy', [
    'source_bucket' => 'uploads',
    'source_pathname' => 'images/photo.jpg',
    'dest_bucket' => 'uploads',
    'dest_pathname' => 'images/photo-copy.jpg',
    'visibility' => 'private'  // Optional
]);

// Move file between buckets
$response = $rpc->call('s3.Move', [
    'source_bucket' => 'uploads',
    'source_pathname' => 'temp/photo.jpg',
    'dest_bucket' => 'private-docs',
    'dest_pathname' => 'archive/photo.jpg'
]);

// Change file visibility
$response = $rpc->call('s3.SetVisibility', [
    'bucket' => 'uploads',
    'pathname' => 'images/photo.jpg',
    'visibility' => 'private'  // or 'public'
]);
Dynamic Bucket Registration

You can register new buckets at runtime via RPC. Note: The bucket must reference an existing server from your configuration.

// Register a new bucket at runtime (references existing server)
$response = $rpc->call('s3.RegisterBucket', [
    'name' => 'dynamic-bucket',      // Unique bucket identifier
    'server' => 'aws-primary',       // Must reference existing server from config
    'bucket' => 'my-new-bucket',     // Actual S3 bucket name
    'prefix' => 'files/',            // Optional path prefix
    'visibility' => 'public'          // "public" or "private"
]);
// Returns: ['success' => true, 'message' => 'Bucket registered successfully']

// List all registered buckets
$response = $rpc->call('s3.ListBuckets', []);
// Returns: ['buckets' => ['uploads', 'documents', 'dynamic-bucket'], 'default' => 'uploads']

Important: Dynamic bucket registration requires that the referenced server already exists in your .rr.yaml configuration. You cannot add new servers at runtime - only new buckets that use existing server credentials.

Architecture

Plugin Structure
s3/
├── plugin.go           # Main plugin with DI and lifecycle management
├── config.go           # Configuration structures and validation
├── bucket_manager.go   # Bucket registration and S3 client management
├── operations.go       # All S3 file operations implementation
├── rpc.go             # RPC interface definitions and handlers
├── errors.go          # Structured error types
└── go.mod             # Go module dependencies
Concurrency Model
  • Per-Bucket Semaphores: Limits concurrent operations per bucket (default: 100)
  • AWS SDK Connection Pooling: Built-in HTTP connection reuse
  • Goroutine Tracking: WaitGroup for graceful shutdown
  • Context Propagation: All operations support cancellation
Performance Characteristics
  • Small Files (< 1MB): Direct upload, 100+ ops/sec per bucket
  • Large Files (> 5MB): Multipart upload with configurable concurrency
  • Memory Usage: Streams large files, minimal memory footprint
  • Concurrent Operations: Supports 50+ simultaneous operations per bucket

Error Handling

All RPC methods return structured errors:

type S3Error struct {
    Code    string `json:"code"`     // Error code (e.g., "BUCKET_NOT_FOUND")
    Message string `json:"message"`  // Human-readable message
    Details string `json:"details"`  // Additional context
}
Error Codes
Code Description
BUCKET_NOT_FOUND Requested bucket doesn't exist
FILE_NOT_FOUND Requested file doesn't exist
INVALID_CONFIG Invalid bucket configuration
S3_OPERATION_FAILED S3 operation failed
PERMISSION_DENIED Insufficient permissions
INVALID_PATHNAME Invalid file path
BUCKET_ALREADY_EXISTS Bucket already registered
INVALID_VISIBILITY Invalid visibility value

Testing

# Run unit tests
go test ./...

# Run with coverage
go test -cover ./...

# Run integration tests (requires S3 credentials)
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
go test -tags=integration ./...

Integration with RoadRunner

Plugin Registration
// In your RoadRunner server
import (
    s3plugin "github.com/roadrunner-server/s3-plugin"
)

func main() {
    container := endure.New(/* config */)
    
    // Register S3 plugin
    container.Register(&s3plugin.Plugin{})
    
    // ... other plugins
    
    container.Init()
    container.Serve()
}

Security Best Practices

  1. Credentials Management

    • Use environment variables for secrets
    • Never commit credentials to version control
    • Rotate credentials regularly
  2. Access Control

    • Use IAM roles when running on AWS infrastructure
    • Apply principle of least privilege
    • Use bucket policies for additional security
  3. Network Security

    • Use HTTPS endpoints for production
    • Consider VPC endpoints for AWS S3
    • Implement proper CORS policies if needed

Troubleshooting

Common Issues

"Bucket not found" error

  • Verify bucket name is correct in configuration
  • Check that bucket is registered (use ListBuckets RPC)
  • Ensure credentials have access to the bucket

"Permission denied" error

  • Verify AWS credentials are correct
  • Check IAM policy allows required S3 operations
  • Ensure bucket policy doesn't block access

Slow upload performance

  • Increase concurrency setting for multipart uploads
  • Adjust part_size (larger parts = fewer API calls)
  • Check max_concurrent_operations limit

Memory usage too high

  • Files are streamed for large uploads
  • Check if multiple large files are processed simultaneously
  • Adjust max_concurrent_operations to limit parallelism

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

MIT License - see LICENSE file for details

Support

Documentation

Index

Constants

View Source
const (
	// PluginName is the name of the S3 plugin
	PluginName = "s3"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Bucket

type Bucket struct {
	// Name is the bucket identifier in the plugin
	Name string

	// Config is the bucket configuration
	Config *BucketConfig

	// ServerConfig is the server configuration this bucket uses
	ServerConfig *ServerConfig

	// Client is the AWS S3 client
	Client *s3.Client
	// contains filtered or unexported fields
}

Bucket represents a single S3 bucket with its client and configuration

func (*Bucket) Acquire

func (b *Bucket) Acquire()

Acquire acquires a semaphore slot for the bucket

func (*Bucket) GetFullPath

func (b *Bucket) GetFullPath(pathname string) string

GetFullPath returns the full S3 key including prefix

func (*Bucket) GetVisibility

func (b *Bucket) GetVisibility() string

GetVisibility returns the ACL for the bucket

func (*Bucket) Release

func (b *Bucket) Release()

Release releases a semaphore slot for the bucket

type BucketConfig

type BucketConfig struct {
	// Server is the reference to a server defined in the servers section
	Server string `mapstructure:"server"`

	// Bucket is the actual S3 bucket name
	Bucket string `mapstructure:"bucket"`

	// Prefix is the path prefix for all operations (optional)
	// Example: "uploads/" - all files will be stored under this prefix
	Prefix string `mapstructure:"prefix"`

	// Visibility defines default ACL: "public" or "private"
	Visibility string `mapstructure:"visibility"`

	// MaxConcurrentOperations limits concurrent operations per bucket (default: 100)
	MaxConcurrentOperations int `mapstructure:"max_concurrent_operations"`

	// PartSize defines multipart upload part size in bytes (default: 5MB)
	PartSize int64 `mapstructure:"part_size"`

	// Concurrency defines number of goroutines for multipart uploads (default: 5)
	Concurrency int `mapstructure:"concurrency"`
}

BucketConfig represents a single bucket configuration

func (*BucketConfig) GetFullPath

func (bc *BucketConfig) GetFullPath(pathname string) string

GetFullPath returns the full path including prefix

func (*BucketConfig) GetServerConfig

func (bc *BucketConfig) GetServerConfig(servers map[string]*ServerConfig) (*ServerConfig, error)

GetServerConfig returns the server configuration for this bucket

func (*BucketConfig) GetVisibility

func (bc *BucketConfig) GetVisibility() string

GetVisibility returns the ACL string for S3 operations

func (*BucketConfig) Validate

func (bc *BucketConfig) Validate(servers map[string]*ServerConfig) error

Validate validates a bucket configuration

type BucketManager

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

BucketManager manages all S3 bucket clients

func NewBucketManager

func NewBucketManager(log *zap.Logger) *BucketManager

NewBucketManager creates a new bucket manager

func (*BucketManager) CloseAll

func (bm *BucketManager) CloseAll() error

CloseAll closes all bucket clients

func (*BucketManager) GetBucket

func (bm *BucketManager) GetBucket(name string) (*Bucket, error)

GetBucket retrieves a bucket by name

func (*BucketManager) GetDefaultBucket

func (bm *BucketManager) GetDefaultBucket() (*Bucket, error)

GetDefaultBucket retrieves the default bucket

func (*BucketManager) GetDefaultBucketName

func (bm *BucketManager) GetDefaultBucketName() string

GetDefaultBucketName returns the default bucket name

func (*BucketManager) ListBuckets

func (bm *BucketManager) ListBuckets() []string

ListBuckets returns all registered bucket names

func (*BucketManager) RegisterBucket

func (bm *BucketManager) RegisterBucket(ctx context.Context, name string, bucketCfg *BucketConfig) error

RegisterBucket registers a new bucket with S3 client initialization

func (*BucketManager) RemoveBucket

func (bm *BucketManager) RemoveBucket(name string) error

RemoveBucket removes a bucket (used for dynamic buckets)

func (*BucketManager) SetDefault

func (bm *BucketManager) SetDefault(name string) error

SetDefault sets the default bucket

func (*BucketManager) SetServers

func (bm *BucketManager) SetServers(servers map[string]*ServerConfig)

SetServers sets the server configurations

type CommonPrefix

type CommonPrefix struct {
	Prefix string `json:"prefix"`
}

CommonPrefix represents a common prefix (directory-like structure)

type Config

type Config struct {
	// Default bucket name to use when none specified
	Default string `mapstructure:"default"`

	// Servers contains S3 server definitions (credentials and endpoints)
	Servers map[string]*ServerConfig `mapstructure:"servers"`

	// Buckets contains bucket definitions that reference servers
	Buckets map[string]*BucketConfig `mapstructure:"buckets"`
}

Config represents the plugin configuration from .rr.yaml

func (*Config) Validate

func (c *Config) Validate() error

Validate validates the configuration

type Configurer

type Configurer interface {
	// UnmarshalKey takes a single key and unmarshal it into a Struct
	UnmarshalKey(name string, out interface{}) error
	// Has checks if config section exists
	Has(name string) bool
}

Configurer interface for configuration loading

type CopyRequest

type CopyRequest struct {
	SourceBucket   string            `json:"source_bucket"`
	SourcePathname string            `json:"source_pathname"`
	DestBucket     string            `json:"dest_bucket"`
	DestPathname   string            `json:"dest_pathname"`
	Config         map[string]string `json:"config,omitempty"`
	Visibility     string            `json:"visibility,omitempty"`
}

CopyRequest represents a file copy request

type CopyResponse

type CopyResponse struct {
	Success      bool   `json:"success"`
	Pathname     string `json:"pathname"`
	Size         int64  `json:"size"`
	LastModified int64  `json:"last_modified"`
}

CopyResponse represents the response from a copy operation

type DeleteRequest

type DeleteRequest struct {
	Bucket   string `json:"bucket"`
	Pathname string `json:"pathname"`
}

DeleteRequest represents a file deletion request

type DeleteResponse

type DeleteResponse struct {
	Success bool `json:"success"`
}

DeleteResponse represents the response from a delete operation

type ErrorCode

type ErrorCode string

ErrorCode represents structured error codes for S3 operations

const (
	// ErrBucketNotFound indicates the requested bucket doesn't exist
	ErrBucketNotFound ErrorCode = "BUCKET_NOT_FOUND"

	// ErrFileNotFound indicates the requested file doesn't exist
	ErrFileNotFound ErrorCode = "FILE_NOT_FOUND"

	// ErrInvalidConfig indicates invalid configuration
	ErrInvalidConfig ErrorCode = "INVALID_CONFIG"

	// ErrS3Operation indicates an S3 operation failed
	ErrS3Operation ErrorCode = "S3_OPERATION_FAILED"

	// ErrPermissionDenied indicates insufficient permissions
	ErrPermissionDenied ErrorCode = "PERMISSION_DENIED"

	// ErrInvalidPathname indicates invalid file path
	ErrInvalidPathname ErrorCode = "INVALID_PATHNAME"

	// ErrBucketAlreadyExists indicates bucket is already registered
	ErrBucketAlreadyExists ErrorCode = "BUCKET_ALREADY_EXISTS"

	// ErrInvalidVisibility indicates invalid visibility value
	ErrInvalidVisibility ErrorCode = "INVALID_VISIBILITY"

	// ErrOperationTimeout indicates operation exceeded timeout
	ErrOperationTimeout ErrorCode = "OPERATION_TIMEOUT"
)

type ExistsRequest

type ExistsRequest struct {
	Bucket   string `json:"bucket"`
	Pathname string `json:"pathname"`
}

ExistsRequest represents a file existence check request

type ExistsResponse

type ExistsResponse struct {
	Exists bool `json:"exists"`
}

ExistsResponse represents the response from an exists check

type GetMetadataRequest

type GetMetadataRequest struct {
	Bucket   string `json:"bucket"`
	Pathname string `json:"pathname"`
}

GetMetadataRequest represents a request to get file metadata

type GetMetadataResponse

type GetMetadataResponse struct {
	Size         int64  `json:"size"`
	MimeType     string `json:"mime_type"`
	LastModified int64  `json:"last_modified"`
	Visibility   string `json:"visibility"`
	ETag         string `json:"etag,omitempty"`
}

GetMetadataResponse represents file metadata

type GetPublicURLRequest

type GetPublicURLRequest struct {
	Bucket    string `json:"bucket"`
	Pathname  string `json:"pathname"`
	ExpiresIn int64  `json:"expires_in,omitempty"` // Seconds, 0 for permanent
}

GetPublicURLRequest represents a request to generate a public URL

type GetPublicURLResponse

type GetPublicURLResponse struct {
	URL       string `json:"url"`
	ExpiresAt int64  `json:"expires_at,omitempty"` // Unix timestamp
}

GetPublicURLResponse represents the response with a public URL

type ListBucketsRequest

type ListBucketsRequest struct{}

ListBucketsRequest represents the request to list all buckets

type ListBucketsResponse

type ListBucketsResponse struct {
	Buckets []string `json:"buckets"`
	Default string   `json:"default"`
}

ListBucketsResponse represents the response with all bucket names

type ListObjectsRequest

type ListObjectsRequest struct {
	Bucket            string `json:"bucket"`
	Prefix            string `json:"prefix,omitempty"`             // Filter by prefix
	Delimiter         string `json:"delimiter,omitempty"`          // Delimiter for grouping (e.g., "/")
	MaxKeys           int32  `json:"max_keys,omitempty"`           // Maximum number of keys to return (default: 1000)
	ContinuationToken string `json:"continuation_token,omitempty"` // Token for pagination
}

ListObjectsRequest represents a request to list objects in a bucket

type ListObjectsResponse

type ListObjectsResponse struct {
	Objects               []ObjectInfo   `json:"objects"`
	CommonPrefixes        []CommonPrefix `json:"common_prefixes,omitempty"`
	IsTruncated           bool           `json:"is_truncated"`
	NextContinuationToken string         `json:"next_continuation_token,omitempty"`
	KeyCount              int32          `json:"key_count"`
}

ListObjectsResponse represents the response from list objects operation

type Logger

type Logger interface {
	NamedLogger(name string) *zap.Logger
}

Logger interface for logging

type MoveRequest

type MoveRequest struct {
	SourceBucket   string            `json:"source_bucket"`
	SourcePathname string            `json:"source_pathname"`
	DestBucket     string            `json:"dest_bucket"`
	DestPathname   string            `json:"dest_pathname"`
	Config         map[string]string `json:"config,omitempty"`
	Visibility     string            `json:"visibility,omitempty"`
}

MoveRequest represents a file move request (copy + delete)

type MoveResponse

type MoveResponse struct {
	Success      bool   `json:"success"`
	Pathname     string `json:"pathname"`
	Size         int64  `json:"size"`
	LastModified int64  `json:"last_modified"`
}

MoveResponse represents the response from a move operation

type ObjectInfo

type ObjectInfo struct {
	Key          string `json:"key"`
	Size         int64  `json:"size"`
	LastModified int64  `json:"last_modified"` // Unix timestamp
	ETag         string `json:"etag"`
	StorageClass string `json:"storage_class,omitempty"`
}

ObjectInfo represents information about a single S3 object

type Operations

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

Operations handles all S3 file operations

func NewOperations

func NewOperations(plugin *Plugin, log *zap.Logger) *Operations

NewOperations creates a new Operations instance

func (*Operations) Copy

func (o *Operations) Copy(ctx context.Context, req *CopyRequest, resp *CopyResponse) error

Copy copies a file within or between buckets

func (*Operations) Delete

func (o *Operations) Delete(ctx context.Context, req *DeleteRequest, resp *DeleteResponse) error

Delete deletes a file from S3

func (*Operations) Exists

func (o *Operations) Exists(ctx context.Context, req *ExistsRequest, resp *ExistsResponse) error

Exists checks if a file exists in S3

func (*Operations) GetMetadata

func (o *Operations) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp *GetMetadataResponse) error

GetMetadata retrieves file metadata

func (*Operations) GetPublicURL

func (o *Operations) GetPublicURL(ctx context.Context, req *GetPublicURLRequest, resp *GetPublicURLResponse) error

GetPublicURL generates a public or presigned URL for a file

func (*Operations) ListObjects

func (o *Operations) ListObjects(ctx context.Context, req *ListObjectsRequest, resp *ListObjectsResponse) error

ListObjects lists objects in a bucket with optional filtering and pagination

func (*Operations) Move

func (o *Operations) Move(ctx context.Context, req *MoveRequest, resp *MoveResponse) error

Move moves a file within or between buckets (copy + delete)

func (*Operations) Read

func (o *Operations) Read(ctx context.Context, req *ReadRequest, resp *ReadResponse) error

Read downloads a file from S3

func (*Operations) SetVisibility

func (o *Operations) SetVisibility(ctx context.Context, req *SetVisibilityRequest, resp *SetVisibilityResponse) error

SetVisibility changes file visibility (ACL)

func (*Operations) Write

func (o *Operations) Write(ctx context.Context, req *WriteRequest, resp *WriteResponse) error

Write uploads a file to S3

type Plugin

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

Plugin represents the main S3 storage plugin structure

func (*Plugin) Collects

func (p *Plugin) Collects() []*dep.In

Collects declares the plugin's dependencies

func (*Plugin) CompleteOperation

func (p *Plugin) CompleteOperation()

CompleteOperation marks an operation as complete

func (*Plugin) GetBucketManager

func (p *Plugin) GetBucketManager() *BucketManager

GetBucketManager returns the bucket manager (for internal use)

func (*Plugin) GetContext

func (p *Plugin) GetContext() context.Context

GetContext returns the plugin context

func (*Plugin) Init

func (p *Plugin) Init(cfg Configurer, log Logger) error

Init initializes the plugin with dependencies

func (*Plugin) MetricsCollector

func (p *Plugin) MetricsCollector() []prometheus.Collector

MetricsCollector implements the StatProvider interface for Prometheus metrics integration This method is called by the metrics plugin during its Serve phase to register all collectors Metrics are also registered directly in Init() to ensure availability even if this method isn't called

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the plugin name

func (*Plugin) RPC

func (p *Plugin) RPC() interface{}

RPC returns the RPC interface exposed to PHP

func (*Plugin) Serve

func (p *Plugin) Serve() chan error

Serve starts the plugin (long-running service)

func (*Plugin) Stop

func (p *Plugin) Stop(ctx context.Context) error

Stop gracefully stops the plugin

func (*Plugin) TrackOperation

func (p *Plugin) TrackOperation()

TrackOperation adds an operation to the wait group

func (*Plugin) Weight

func (p *Plugin) Weight() uint

Weight returns plugin weight for dependency resolution Higher weight = initialized later

type ReadRequest

type ReadRequest struct {
	Bucket   string `json:"bucket"`
	Pathname string `json:"pathname"`
}

ReadRequest represents a file read/download request

type ReadResponse

type ReadResponse struct {
	Content      []byte `json:"content"`
	Size         int64  `json:"size"`
	MimeType     string `json:"mime_type"`
	LastModified int64  `json:"last_modified"`
}

ReadResponse represents the response from a read operation

type RegisterBucketRequest

type RegisterBucketRequest struct {
	Name       string `json:"name"`
	Server     string `json:"server"`
	Bucket     string `json:"bucket"`
	Prefix     string `json:"prefix"`
	Visibility string `json:"visibility"`
}

RegisterBucketRequest represents the request to register a new bucket dynamically

type RegisterBucketResponse

type RegisterBucketResponse struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

RegisterBucketResponse represents the response from bucket registration

type S3Error

type S3Error struct {
	// Code is the error code
	Code ErrorCode `json:"code"`

	// Message is the human-readable error message
	Message string `json:"message"`

	// Details contains additional error context (optional)
	Details string `json:"details,omitempty"`
}

S3Error represents a structured error returned to PHP

func NewBucketNotFoundError

func NewBucketNotFoundError(bucketName string) *S3Error

NewBucketNotFoundError creates a bucket not found error

func NewFileNotFoundError

func NewFileNotFoundError(pathname string) *S3Error

NewFileNotFoundError creates a file not found error

func NewInvalidConfigError

func NewInvalidConfigError(reason string) *S3Error

NewInvalidConfigError creates an invalid config error

func NewInvalidPathnameError

func NewInvalidPathnameError(pathname string, reason string) *S3Error

NewInvalidPathnameError creates an invalid pathname error

func NewPermissionDeniedError

func NewPermissionDeniedError(operation string) *S3Error

NewPermissionDeniedError creates a permission denied error

func NewS3Error

func NewS3Error(code ErrorCode, message string, details string) *S3Error

NewS3Error creates a new S3Error

func NewS3OperationError

func NewS3OperationError(operation string, err error) *S3Error

NewS3OperationError creates an S3 operation error

func (*S3Error) Error

func (e *S3Error) Error() string

Error implements the error interface

type ServerConfig

type ServerConfig struct {
	// Region is the AWS region (e.g., "us-east-1", "fra1" for DigitalOcean)
	Region string `mapstructure:"region"`

	// Endpoint is the S3 endpoint URL (required for S3-compatible services)
	// Example: "https://fra1.digitaloceanspaces.com"
	// Leave empty for AWS S3 (will use default AWS endpoint)
	Endpoint string `mapstructure:"endpoint"`

	// Credentials contains authentication credentials for this server
	Credentials ServerCredentials `mapstructure:"credentials"`
}

ServerConfig represents S3 server configuration (credentials and endpoint)

func (*ServerConfig) Validate

func (sc *ServerConfig) Validate() error

Validate validates a server configuration

type ServerCredentials

type ServerCredentials struct {
	// Key is the Access Key ID
	Key string `mapstructure:"key"`

	// Secret is the Secret Access Key
	Secret string `mapstructure:"secret"`

	// Token is the Session Token (optional, for temporary credentials)
	Token string `mapstructure:"token"`
}

ServerCredentials contains S3 authentication credentials

type SetVisibilityRequest

type SetVisibilityRequest struct {
	Bucket     string `json:"bucket"`
	Pathname   string `json:"pathname"`
	Visibility string `json:"visibility"`
}

SetVisibilityRequest represents a request to change file visibility

type SetVisibilityResponse

type SetVisibilityResponse struct {
	Success bool `json:"success"`
}

SetVisibilityResponse represents the response from visibility change

type WriteRequest

type WriteRequest struct {
	Bucket     string            `json:"bucket"`
	Pathname   string            `json:"pathname"`
	Content    []byte            `json:"content"`
	Config     map[string]string `json:"config,omitempty"`
	Visibility string            `json:"visibility,omitempty"`
}

WriteRequest represents a file write/upload request

type WriteResponse

type WriteResponse struct {
	Success      bool   `json:"success"`
	Pathname     string `json:"pathname"`
	Size         int64  `json:"size"`
	LastModified int64  `json:"last_modified"`
}

WriteResponse represents the response from a write operation

Jump to

Keyboard shortcuts

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