Add active directory management for snapshot operations
Introduced `GetActiveDir` and `CleanActiveDir` methods in the blob store to manage a dedicated directory for active snapshot operations. This ensures a clean working state before starting new operations and prevents conflicts. Updated related logic in snapshot creation and restoration to utilize the active directory.
This commit is contained in:
parent
9d04f43104
commit
b05058b5cd
310
README.md
Normal file
310
README.md
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
# Agate
|
||||||
|
|
||||||
|
Agate is a Go library for creating, managing, and sharing snapshots of directories. It provides functionality for creating incremental snapshots, storing them efficiently, and sharing them over a network using gRPC.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get gitea.unprism.ru/KRBL/Agate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Create snapshots of directories
|
||||||
|
- Incremental snapshots (only store changes)
|
||||||
|
- Restore snapshots
|
||||||
|
- List and manage snapshots
|
||||||
|
- Share snapshots over a network using gRPC
|
||||||
|
- Connect to remote snapshot repositories
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Creating a Snapshot Repository
|
||||||
|
|
||||||
|
To create a snapshot repository, you need to initialize the Agate library with the appropriate options:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/KRBL/Agate"
|
||||||
|
"gitea.unprism.ru/KRBL/Agate/stores"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create directories for your repository
|
||||||
|
workDir := "/path/to/your/repository"
|
||||||
|
if err := os.MkdirAll(workDir, 0755); err != nil {
|
||||||
|
log.Fatalf("Failed to create work directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the default stores
|
||||||
|
metadataStore, blobStore, err := stores.InitDefaultStores(workDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize stores: %v", err)
|
||||||
|
}
|
||||||
|
defer metadataStore.Close()
|
||||||
|
|
||||||
|
// Initialize Agate
|
||||||
|
agateOptions := agate.AgateOptions{
|
||||||
|
WorkDir: workDir,
|
||||||
|
MetadataStore: metadataStore,
|
||||||
|
BlobStore: blobStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
ag, err := agate.New(agateOptions)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize Agate: %v", err)
|
||||||
|
}
|
||||||
|
defer ag.Close()
|
||||||
|
|
||||||
|
// Create a snapshot
|
||||||
|
ctx := context.Background()
|
||||||
|
snapshotID, err := ag.SaveSnapshot(ctx, "My First Snapshot", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create snapshot: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Created snapshot with ID: %s\n", snapshotID)
|
||||||
|
|
||||||
|
// List snapshots
|
||||||
|
snapshots, err := ag.ListSnapshots(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to list snapshots: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Found %d snapshots:\n", len(snapshots))
|
||||||
|
for _, s := range snapshots {
|
||||||
|
fmt.Printf(" - %s: %s (created at %s)\n", s.ID, s.Name, s.CreationTime.Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hosting a Snapshot Repository
|
||||||
|
|
||||||
|
To host a snapshot repository and make it available over the network, you can use the `StartServer` method:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/KRBL/Agate"
|
||||||
|
"gitea.unprism.ru/KRBL/Agate/stores"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create directories for your repository
|
||||||
|
workDir := "/path/to/your/repository"
|
||||||
|
if err := os.MkdirAll(workDir, 0755); err != nil {
|
||||||
|
log.Fatalf("Failed to create work directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the default stores
|
||||||
|
metadataStore, blobStore, err := stores.InitDefaultStores(workDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize stores: %v", err)
|
||||||
|
}
|
||||||
|
defer metadataStore.Close()
|
||||||
|
|
||||||
|
// Initialize Agate
|
||||||
|
agateOptions := agate.AgateOptions{
|
||||||
|
WorkDir: workDir,
|
||||||
|
MetadataStore: metadataStore,
|
||||||
|
BlobStore: blobStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
ag, err := agate.New(agateOptions)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize Agate: %v", err)
|
||||||
|
}
|
||||||
|
defer ag.Close()
|
||||||
|
|
||||||
|
// Start the gRPC server
|
||||||
|
ctx := context.Background()
|
||||||
|
address := "0.0.0.0:50051" // Listen on all interfaces, port 50051
|
||||||
|
if err := ag.StartServer(ctx, address); err != nil {
|
||||||
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Server started on %s", address)
|
||||||
|
|
||||||
|
// Wait for termination signal
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-sigCh
|
||||||
|
|
||||||
|
log.Println("Shutting down...")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connecting to a Hosted Snapshot Repository
|
||||||
|
|
||||||
|
To connect to a hosted snapshot repository and retrieve snapshots:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gitea.unprism.ru/KRBL/Agate"
|
||||||
|
"gitea.unprism.ru/KRBL/Agate/stores"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create directories for your local repository
|
||||||
|
workDir := "/path/to/your/local/repository"
|
||||||
|
if err := os.MkdirAll(workDir, 0755); err != nil {
|
||||||
|
log.Fatalf("Failed to create work directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the default stores
|
||||||
|
metadataStore, blobStore, err := stores.InitDefaultStores(workDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize stores: %v", err)
|
||||||
|
}
|
||||||
|
defer metadataStore.Close()
|
||||||
|
|
||||||
|
// Initialize Agate
|
||||||
|
agateOptions := agate.AgateOptions{
|
||||||
|
WorkDir: workDir,
|
||||||
|
MetadataStore: metadataStore,
|
||||||
|
BlobStore: blobStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
ag, err := agate.New(agateOptions)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize Agate: %v", err)
|
||||||
|
}
|
||||||
|
defer ag.Close()
|
||||||
|
|
||||||
|
// Connect to a remote server
|
||||||
|
ctx := context.Background()
|
||||||
|
remoteAddress := "remote-server:50051"
|
||||||
|
|
||||||
|
// List snapshots from the remote server
|
||||||
|
snapshots, err := ag.GetRemoteSnapshotList(ctx, remoteAddress)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to list remote snapshots: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d remote snapshots:\n", len(snapshots))
|
||||||
|
for _, s := range snapshots {
|
||||||
|
fmt.Printf(" - %s: %s (created at %s)\n", s.ID, s.Name, s.CreationTime.Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download a specific snapshot
|
||||||
|
if len(snapshots) > 0 {
|
||||||
|
snapshotID := snapshots[0].ID
|
||||||
|
fmt.Printf("Downloading snapshot %s...\n", snapshotID)
|
||||||
|
|
||||||
|
// Download the snapshot (pass empty string as localParentID if this is the first download)
|
||||||
|
if err := ag.GetRemoteSnapshot(ctx, remoteAddress, snapshotID, ""); err != nil {
|
||||||
|
log.Fatalf("Failed to download snapshot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Successfully downloaded snapshot %s\n", snapshotID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Creating Incremental Snapshots
|
||||||
|
|
||||||
|
You can create incremental snapshots by specifying a parent snapshot ID:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a first snapshot
|
||||||
|
snapshotID1, err := ag.SaveSnapshot(ctx, "First Snapshot", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create first snapshot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make some changes to your files...
|
||||||
|
|
||||||
|
// Create a second snapshot with the first one as parent
|
||||||
|
snapshotID2, err := ag.SaveSnapshot(ctx, "Second Snapshot", snapshotID1)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create second snapshot: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restoring a Snapshot
|
||||||
|
|
||||||
|
To restore a snapshot:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := ag.RestoreSnapshot(ctx, snapshotID); err != nil {
|
||||||
|
log.Fatalf("Failed to restore snapshot: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Getting Snapshot Details
|
||||||
|
|
||||||
|
To get detailed information about a snapshot:
|
||||||
|
|
||||||
|
```go
|
||||||
|
snapshot, err := ag.GetSnapshotDetails(ctx, snapshotID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to get snapshot details: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Snapshot: %s\n", snapshot.Name)
|
||||||
|
fmt.Printf("Created: %s\n", snapshot.CreationTime.Format("2006-01-02 15:04:05"))
|
||||||
|
fmt.Printf("Files: %d\n", len(snapshot.Files))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deleting a Snapshot
|
||||||
|
|
||||||
|
To delete a snapshot:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := ag.DeleteSnapshot(ctx, snapshotID); err != nil {
|
||||||
|
log.Fatalf("Failed to delete snapshot: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Agate
|
||||||
|
|
||||||
|
The main entry point for the library.
|
||||||
|
|
||||||
|
- `New(options AgateOptions) (*Agate, error)` - Create a new Agate instance
|
||||||
|
- `SaveSnapshot(ctx context.Context, name string, parentID string) (string, error)` - Create a new snapshot
|
||||||
|
- `RestoreSnapshot(ctx context.Context, snapshotID string) error` - Restore a snapshot
|
||||||
|
- `ListSnapshots(ctx context.Context) ([]store.SnapshotInfo, error)` - List all snapshots
|
||||||
|
- `GetSnapshotDetails(ctx context.Context, snapshotID string) (*store.Snapshot, error)` - Get details of a snapshot
|
||||||
|
- `DeleteSnapshot(ctx context.Context, snapshotID string) error` - Delete a snapshot
|
||||||
|
- `StartServer(ctx context.Context, address string) error` - Start a gRPC server to share snapshots
|
||||||
|
- `ConnectRemote(address string) (*grpc.SnapshotClient, error)` - Connect to a remote server
|
||||||
|
- `GetRemoteSnapshotList(ctx context.Context, address string) ([]store.SnapshotInfo, error)` - List snapshots from a remote server
|
||||||
|
- `GetRemoteSnapshot(ctx context.Context, address string, snapshotID string, localParentID string) error` - Download a snapshot from a remote server
|
||||||
|
|
||||||
|
### AgateOptions
|
||||||
|
|
||||||
|
Configuration options for the Agate library.
|
||||||
|
|
||||||
|
- `WorkDir string` - Directory where snapshots will be stored and managed
|
||||||
|
- `OpenFunc func(dir string) error` - Called after a snapshot is restored
|
||||||
|
- `CloseFunc func() error` - Called before a snapshot is created or restored
|
||||||
|
- `MetadataStore store.MetadataStore` - Implementation of the metadata store
|
||||||
|
- `BlobStore store.BlobStore` - Implementation of the blob store
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Add your license information here]
|
27
manager.go
27
manager.go
@ -59,12 +59,20 @@ func (data *SnapshotManagerData) CreateSnapshot(ctx context.Context, sourceDir s
|
|||||||
// Generate a unique ID for the snapshot
|
// Generate a unique ID for the snapshot
|
||||||
snapshotID := uuid.New().String()
|
snapshotID := uuid.New().String()
|
||||||
|
|
||||||
// Create a temporary file for the archive
|
// Clean the active directory to avoid conflicts
|
||||||
tempFile, err := os.CreateTemp("", "agate-snapshot-*.zip")
|
if err := data.blobStore.CleanActiveDir(ctx); err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to clean active directory: %w", err)
|
||||||
return nil, fmt.Errorf("failed to create temporary file: %w", err)
|
}
|
||||||
|
|
||||||
|
// Get the active directory for operations
|
||||||
|
activeDir := data.blobStore.GetActiveDir()
|
||||||
|
|
||||||
|
// Create a temporary file for the archive in the active directory
|
||||||
|
tempFilePath := filepath.Join(activeDir, "temp-"+snapshotID+".zip")
|
||||||
|
tempFile, err := os.Create(tempFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create temporary file in active directory: %w", err)
|
||||||
}
|
}
|
||||||
tempFilePath := tempFile.Name()
|
|
||||||
tempFile.Close() // Close it as CreateArchive will reopen it
|
tempFile.Close() // Close it as CreateArchive will reopen it
|
||||||
defer os.Remove(tempFilePath) // Clean up temp file after we're done
|
defer os.Remove(tempFilePath) // Clean up temp file after we're done
|
||||||
|
|
||||||
@ -253,8 +261,15 @@ func (data *SnapshotManagerData) ExtractSnapshot(ctx context.Context, snapshotID
|
|||||||
if snapshotID == "" {
|
if snapshotID == "" {
|
||||||
return errors.New("snapshot ID cannot be empty")
|
return errors.New("snapshot ID cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no specific path is provided, use the active directory
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return errors.New("target path cannot be empty")
|
// Clean the active directory to avoid conflicts
|
||||||
|
if err := data.blobStore.CleanActiveDir(ctx); err != nil {
|
||||||
|
return fmt.Errorf("failed to clean active directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(data.blobStore.GetActiveDir(), snapshotID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// First check if the snapshot exists
|
// First check if the snapshot exists
|
||||||
|
@ -15,15 +15,26 @@ const blobExtension = ".zip"
|
|||||||
// fileSystemStore реализует интерфейс store.BlobStore с использованием локальной файловой системы.
|
// fileSystemStore реализует интерфейс store.BlobStore с использованием локальной файловой системы.
|
||||||
type fileSystemStore struct {
|
type fileSystemStore struct {
|
||||||
baseDir string // Директория для хранения блобов (архивов)
|
baseDir string // Директория для хранения блобов (архивов)
|
||||||
|
activeDir string // Директория для активных операций (создание и восстановление)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSystemStore создает новое хранилище блобов в указанной директории.
|
// NewFileSystemStore создает новое хранилище блобов в указанной директории.
|
||||||
func NewFileSystemStore(baseDir string) (store.BlobStore, error) {
|
func NewFileSystemStore(baseDir string) (store.BlobStore, error) {
|
||||||
// Убедимся, что директория существует
|
// Убедимся, что базовая директория существует
|
||||||
if err := os.MkdirAll(baseDir, 0755); err != nil {
|
if err := os.MkdirAll(baseDir, 0755); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create base directory %s for filesystem blob store: %w", baseDir, err)
|
return nil, fmt.Errorf("failed to create base directory %s for filesystem blob store: %w", baseDir, err)
|
||||||
}
|
}
|
||||||
return &fileSystemStore{baseDir: baseDir}, nil
|
|
||||||
|
// Создаем директорию для активных операций внутри базовой директории
|
||||||
|
activeDir := filepath.Join(baseDir, "active")
|
||||||
|
if err := os.MkdirAll(activeDir, 0755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create active directory %s for filesystem blob store: %w", activeDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fileSystemStore{
|
||||||
|
baseDir: baseDir,
|
||||||
|
activeDir: activeDir,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBlobPath формирует полный путь к файлу блоба.
|
// getBlobPath формирует полный путь к файлу блоба.
|
||||||
@ -106,3 +117,27 @@ func (fs *fileSystemStore) GetBlobPath(ctx context.Context, snapshotID string) (
|
|||||||
// Файл существует, возвращаем путь
|
// Файл существует, возвращаем путь
|
||||||
return blobPath, nil
|
return blobPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetActiveDir возвращает путь к директории для активных операций.
|
||||||
|
func (fs *fileSystemStore) GetActiveDir() string {
|
||||||
|
return fs.activeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanActiveDir очищает директорию для активных операций.
|
||||||
|
// Это полезно перед началом новых операций, чтобы избежать конфликтов.
|
||||||
|
func (fs *fileSystemStore) CleanActiveDir(ctx context.Context) error {
|
||||||
|
// Удаляем все файлы в активной директории, но сохраняем саму директорию
|
||||||
|
entries, err := os.ReadDir(fs.activeDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read active directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
path := filepath.Join(fs.activeDir, entry.Name())
|
||||||
|
if err := os.RemoveAll(path); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove %s from active directory: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -71,4 +71,11 @@ type BlobStore interface {
|
|||||||
// Это может быть полезно для функций пакета archive, которые работают с путями.
|
// Это может быть полезно для функций пакета archive, которые работают с путями.
|
||||||
// Возвращает agate.ErrNotFound, если блоб не найден.
|
// Возвращает agate.ErrNotFound, если блоб не найден.
|
||||||
GetBlobPath(ctx context.Context, snapshotID string) (string, error)
|
GetBlobPath(ctx context.Context, snapshotID string) (string, error)
|
||||||
|
|
||||||
|
// GetActiveDir возвращает путь к директории для активных операций (создание и восстановление).
|
||||||
|
GetActiveDir() string
|
||||||
|
|
||||||
|
// CleanActiveDir очищает директорию для активных операций.
|
||||||
|
// Это полезно перед началом новых операций, чтобы избежать конфликтов.
|
||||||
|
CleanActiveDir(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user