server client restore
This commit is contained in:
197
internal/server/storage.go
Normal file
197
internal/server/storage.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/mistifyio/go-zfs"
|
||||
)
|
||||
|
||||
// StorageBackend defines the interface for different storage types
|
||||
type StorageBackend interface {
|
||||
Upload(ctx context.Context, key string, data io.Reader, size int64) error
|
||||
Download(ctx context.Context, key string) (io.ReadCloser, error)
|
||||
Delete(ctx context.Context, key string) error
|
||||
List(ctx context.Context, prefix string) ([]string, error)
|
||||
GetSize(ctx context.Context, key string) (int64, error)
|
||||
}
|
||||
|
||||
// S3Backend implements StorageBackend for S3-compatible storage
|
||||
type S3Backend struct {
|
||||
client *minio.Client
|
||||
bucketName string
|
||||
}
|
||||
|
||||
// NewS3Backend creates a new S3 storage backend
|
||||
func NewS3Backend(endpoint, accessKey, secretKey, bucketName string, useSSL bool) (*S3Backend, error) {
|
||||
client, err := minio.New(endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||
Secure: useSSL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create S3 client: %v", err)
|
||||
}
|
||||
|
||||
// Ensure bucket exists
|
||||
ctx := context.Background()
|
||||
exists, err := client.BucketExists(ctx, bucketName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check bucket: %v", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
err = client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create bucket: %v", err)
|
||||
}
|
||||
log.Printf("Created S3 bucket: %s", bucketName)
|
||||
}
|
||||
|
||||
return &S3Backend{
|
||||
client: client,
|
||||
bucketName: bucketName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Upload uploads data to S3
|
||||
func (s *S3Backend) Upload(ctx context.Context, key string, data io.Reader, size int64) error {
|
||||
_, err := s.client.PutObject(ctx, s.bucketName, key, data, size,
|
||||
minio.PutObjectOptions{
|
||||
ContentType: "application/octet-stream",
|
||||
PartSize: 10 * 1024 * 1024, // 10MB parts
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Download retrieves data from S3
|
||||
func (s *S3Backend) Download(ctx context.Context, key string) (io.ReadCloser, error) {
|
||||
obj, err := s.client.GetObject(ctx, s.bucketName, key, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Delete removes an object from S3
|
||||
func (s *S3Backend) Delete(ctx context.Context, key string) error {
|
||||
return s.client.RemoveObject(ctx, s.bucketName, key, minio.RemoveObjectOptions{})
|
||||
}
|
||||
|
||||
// List returns all objects with the given prefix
|
||||
func (s *S3Backend) List(ctx context.Context, prefix string) ([]string, error) {
|
||||
var keys []string
|
||||
|
||||
objectCh := s.client.ListObjects(ctx, s.bucketName, minio.ListObjectsOptions{
|
||||
Prefix: prefix,
|
||||
Recursive: true,
|
||||
})
|
||||
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
return nil, object.Err
|
||||
}
|
||||
keys = append(keys, object.Key)
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// GetSize returns the size of an object in S3
|
||||
func (s *S3Backend) GetSize(ctx context.Context, key string) (int64, error) {
|
||||
info, err := s.client.StatObject(ctx, s.bucketName, key, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return info.Size, nil
|
||||
}
|
||||
|
||||
// LocalBackend implements StorageBackend for local ZFS storage
|
||||
type LocalBackend struct {
|
||||
baseDataset string
|
||||
}
|
||||
|
||||
// NewLocalBackend creates a new local ZFS storage backend
|
||||
func NewLocalBackend(baseDataset string) *LocalBackend {
|
||||
return &LocalBackend{baseDataset: baseDataset}
|
||||
}
|
||||
|
||||
// Upload is not supported for local backend
|
||||
func (l *LocalBackend) Upload(ctx context.Context, key string, data io.Reader, size int64) error {
|
||||
return fmt.Errorf("local backend upload not supported via storage interface, use zfs receive endpoint")
|
||||
}
|
||||
|
||||
// Download creates a zfs send stream
|
||||
func (l *LocalBackend) Download(ctx context.Context, key string) (io.ReadCloser, error) {
|
||||
cmd := exec.CommandContext(ctx, "zfs", "send", key)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cmdReadCloser{stdout: stdout, cmd: cmd}, nil
|
||||
}
|
||||
|
||||
// Delete destroys a ZFS dataset
|
||||
func (l *LocalBackend) Delete(ctx context.Context, key string) error {
|
||||
ds, err := zfs.GetDataset(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ds.Destroy(zfs.DestroyDefault)
|
||||
}
|
||||
|
||||
// List returns all snapshots with the given prefix
|
||||
func (l *LocalBackend) List(ctx context.Context, prefix string) ([]string, error) {
|
||||
snapshots, err := zfs.Snapshots(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var names []string
|
||||
for _, snap := range snapshots {
|
||||
names = append(names, snap.Name)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// GetSize returns the used size of a ZFS dataset
|
||||
func (l *LocalBackend) GetSize(ctx context.Context, key string) (int64, error) {
|
||||
ds, err := zfs.GetDataset(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(ds.Used), nil
|
||||
}
|
||||
|
||||
// cmdReadCloser wraps stdout pipe to properly wait for command completion
|
||||
type cmdReadCloser struct {
|
||||
stdout io.ReadCloser
|
||||
cmd *exec.Cmd
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (c *cmdReadCloser) Read(p []byte) (int, error) {
|
||||
return c.stdout.Read(p)
|
||||
}
|
||||
|
||||
func (c *cmdReadCloser) Close() error {
|
||||
if c.closed {
|
||||
return nil
|
||||
}
|
||||
c.closed = true
|
||||
err := c.stdout.Close()
|
||||
waitErr := c.cmd.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return waitErr
|
||||
}
|
||||
Reference in New Issue
Block a user