Skip to content

Commit

Permalink
Implement pruning for Azure blob storage
Browse files Browse the repository at this point in the history
  • Loading branch information
m90 committed Dec 24, 2022
1 parent a253fdf commit fdce7ee
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 6 deletions.
2 changes: 1 addition & 1 deletion cmd/backup/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type Config struct {
AzureStorageAccountName string `split_words:"true"`
AzureStoragePrimaryAccountKey string `split_words:"true"`
AzureStorageContainerName string `split_words:"true"`
AzureStorageEndpoint string `split_words:"true" default:"https://%s.blob.core.windows.net/"`
AzureStorageEndpoint string `split_words:"true" default:"https://{{ .AccountName }}.blob.core.windows.net/"`
}

func (c *Config) resolveSecret(envVar string, secretPath string) (string, error) {
Expand Down
75 changes: 71 additions & 4 deletions internal/storage/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
package azure

import (
"bytes"
"context"
"fmt"
"os"
"path"
"sync"
"text/template"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
"github.com/offen/docker-volume-backup/internal/storage"
"github.com/offen/docker-volume-backup/internal/utilities"
)

type azureBlobStorage struct {
Expand All @@ -34,13 +39,26 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error
if err != nil {
return nil, fmt.Errorf("NewStorageBackend: error creating shared Azure credential: %w", err)
}
client, err := azblob.NewClientWithSharedKeyCredential(fmt.Sprintf(opts.Endpoint, opts.AccountName), cred, nil)

endpointTemplate, err := template.New("endpoint").Parse(opts.Endpoint)
if err != nil {
return nil, fmt.Errorf("NewStorageBackend: error parsing endpoint template: %w", err)
}

var ep bytes.Buffer
if err := endpointTemplate.Execute(&ep, opts); err != nil {
return nil, fmt.Errorf("NewStorageBackend: error executing endpoint template: %w", err)
}
client, err := azblob.NewClientWithSharedKeyCredential(ep.String(), cred, nil)
if err != nil {
return nil, fmt.Errorf("NewStorageBackend: error creating Azure client: %w", err)
}
storage := azureBlobStorage{
client: client,
containerName: opts.ContainerName,
StorageBackend: &storage.StorageBackend{
Log: logFunc,
},
}
return &storage, nil
}
Expand All @@ -57,7 +75,8 @@ func (b *azureBlobStorage) Copy(file string) error {
return fmt.Errorf("(*azureBlobStorage).Copy: error opening file %s: %w", file, err)
}

_, err = b.client.UploadStream(context.TODO(),
_, err = b.client.UploadStream(
context.Background(),
b.containerName,
path.Base(file),
fileReader,
Expand All @@ -69,7 +88,55 @@ func (b *azureBlobStorage) Copy(file string) error {
return nil
}

// Prune rotates away backups according to the configuration and provided deadline for the S3/Minio storage backend.
// Prune rotates away backups according to the configuration and provided
// deadline for the Azure Blob storage backend.
func (b *azureBlobStorage) Prune(deadline time.Time, pruningPrefix string) (*storage.PruneStats, error) {
return &storage.PruneStats{}, nil
pager := b.client.NewListBlobsFlatPager(b.containerName, &container.ListBlobsFlatOptions{
Prefix: &pruningPrefix,
})
var matches []string
var totalCount uint
for pager.More() {
resp, err := pager.NextPage(context.Background())
if err != nil {
return nil, fmt.Errorf("(*azureBlobStorage).Prune: error paging over blobs: %w", err)
}
for _, v := range resp.Segment.BlobItems {
totalCount++
if v.Properties.LastModified.Before(deadline) {
matches = append(matches, *v.Name)
}
}
}

stats := storage.PruneStats{
Total: totalCount,
Pruned: uint(len(matches)),
}

if err := b.DoPrune(b.Name(), len(matches), int(totalCount), "Azure Blob Storage backup(s)", func() error {
wg := sync.WaitGroup{}
wg.Add(len(matches))
var errors []error

for _, match := range matches {
name := match
go func() {
_, err := b.client.DeleteBlob(context.Background(), b.containerName, name, nil)
if err != nil {
errors = append(errors, err)
}
wg.Done()
}()
}
wg.Wait()
if len(errors) != 0 {
return utilities.Join(errors...)
}
return nil
}); err != nil {
return &stats, err
}

return &stats, nil
}
2 changes: 1 addition & 1 deletion test/azure/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ services:
AZURE_STORAGE_ACCOUNT_NAME: devstoreaccount1
AZURE_STORAGE_PRIMARY_ACCOUNT_KEY: Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
AZURE_STORAGE_CONTAINER_NAME: test-container
AZURE_STORAGE_ENDPOINT: http://storage:10000/%s/
AZURE_STORAGE_ENDPOINT: http://storage:10000/{{ .AccountName }}/
BACKUP_FILENAME: test.tar.gz
BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ?
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7}
Expand Down

0 comments on commit fdce7ee

Please sign in to comment.