azureblob.go 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. // Copyright (C) 2025 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package azureblob
  7. import (
  8. "context"
  9. "io"
  10. "time"
  11. stblob "github.com/syncthing/syncthing/internal/blob"
  12. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
  13. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
  14. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
  15. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
  16. )
  17. var _ stblob.Store = (*BlobStore)(nil)
  18. type BlobStore struct {
  19. client *azblob.Client
  20. container string
  21. }
  22. func NewBlobStore(accountName, accountKey, containerName string) (*BlobStore, error) {
  23. credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
  24. if err != nil {
  25. return nil, err
  26. }
  27. url := "https://" + accountName + ".blob.core.windows.net/"
  28. sc, err := azblob.NewClientWithSharedKeyCredential(url, credential, &azblob.ClientOptions{})
  29. if err != nil {
  30. return nil, err
  31. }
  32. // This errors when the container already exists, which we ignore.
  33. _, _ = sc.CreateContainer(context.Background(), containerName, &container.CreateOptions{})
  34. return &BlobStore{
  35. client: sc,
  36. container: containerName,
  37. }, nil
  38. }
  39. func (a *BlobStore) Upload(ctx context.Context, key string, data io.Reader) error {
  40. _, err := a.client.UploadStream(ctx, a.container, key, data, &blockblob.UploadStreamOptions{})
  41. return err
  42. }
  43. func (a *BlobStore) Download(ctx context.Context, key string, w stblob.Writer) error {
  44. resp, err := a.client.DownloadStream(ctx, a.container, key, &blob.DownloadStreamOptions{})
  45. if err != nil {
  46. return err
  47. }
  48. defer resp.Body.Close()
  49. _, err = io.Copy(w, resp.Body)
  50. return err
  51. }
  52. func (a *BlobStore) LatestKey(ctx context.Context) (string, error) {
  53. opts := &azblob.ListBlobsFlatOptions{}
  54. pager := a.client.NewListBlobsFlatPager(a.container, opts)
  55. var latest string
  56. var lastModified time.Time
  57. for pager.More() {
  58. page, err := pager.NextPage(ctx)
  59. if err != nil {
  60. return "", err
  61. }
  62. for _, blob := range page.Segment.BlobItems {
  63. if latest == "" || blob.Properties.LastModified.After(lastModified) {
  64. latest = *blob.Name
  65. lastModified = *blob.Properties.LastModified
  66. }
  67. }
  68. }
  69. return latest, nil
  70. }