func (a *AzureBackend) Close() error {
	return nil
}
Close will release any resources used by the Azure backend.
package backends
import (
	"bytes"
	"context"
	"crypto/md5"  
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"io"
	"net/url"
	"os"
	"strings"
	"sync"
	"github.com/Azure/azure-storage-blob-go/azblob"
	"github.com/pkg/errors"
	"golang.org/x/sync/errgroup"
	"github.com/someone1/zfsbackup-go/files"
	"github.com/someone1/zfsbackup-go/log"
)
const (
	AzureBackendPrefix = "azure"
	blobAPIURL         = "blob.core.windows.net"
)
var (
	errContainerMismatch = errors.New("container name in SAS URI is different than destination container provided")
)
type AzureBackend struct {
	conf          *BackendConfig
	mutex         sync.Mutex
	accountName   string
	accountKey    string
	containersas  string
	azureURL      string
	prefix        string
	containerName string
	containerSvc  azblob.ContainerURL
}
func (a *AzureBackend) Init(ctx context.Context, conf *BackendConfig, opts ...Option) error {
	a.conf = conf
	cleanPrefix := strings.TrimPrefix(a.conf.TargetURI, AzureBackendPrefix+"://")
	if cleanPrefix == a.conf.TargetURI {
		return ErrInvalidURI
	}
	a.accountName = os.Getenv("AZURE_ACCOUNT_NAME")
	a.accountKey = os.Getenv("AZURE_ACCOUNT_KEY")
	a.containersas = os.Getenv("AZURE_SAS_URI")
	a.azureURL = os.Getenv("AZURE_CUSTOM_ENDPOINT")
	if a.azureURL == "" {
		a.azureURL = fmt.Sprintf("https://%s.%s", a.accountName, blobAPIURL)
	}
	uriParts := strings.Split(cleanPrefix, "/")
	a.containerName = uriParts[0]
	if len(uriParts) > 1 {
		a.prefix = strings.Join(uriParts[1:], "/")
	}
	for _, opt := range opts {
		opt.Apply(a)
	}
	pipelineOpts := azblob.PipelineOptions{
		RequestLog: azblob.RequestLogOptions{
			LogWarningIfTryOverThreshold: -1,
		},
	}
	if a.containersas != "" {
		parsedsas, err := url.Parse(a.containersas)
		if err != nil {
			return errors.Wrap(err, "failed to parse SAS URI")
		}
		pipeline := azblob.NewPipeline(azblob.NewAnonymousCredential(), pipelineOpts)
		sasParts := azblob.NewBlobURLParts(*parsedsas)
		if sasParts.ContainerName != a.containerName {
			return errContainerMismatch
		}
		a.containerSvc = azblob.NewContainerURL(*parsedsas, pipeline)
	} else {
		credential, err := azblob.NewSharedKeyCredential(a.accountName, a.accountKey)
		if err != nil {
			return errors.Wrap(err, "failed to initilze Azure credential")
		}
		destURL, err := url.Parse(a.azureURL)
		if err != nil {
			return errors.Wrap(err, "failed to construct Azure API URL")
		}
		pipeline := azblob.NewPipeline(credential, pipelineOpts)
		svcURL := azblob.NewServiceURL(*destURL, pipeline)
		a.containerSvc = svcURL.NewContainerURL(a.containerName)
	}
	_, err := a.containerSvc.ListBlobsFlatSegment(ctx, azblob.Marker{}, azblob.ListBlobsSegmentOptions{MaxResults: 0})
	return err
}
func (a *AzureBackend) Upload(ctx context.Context, vol *files.VolumeInfo) error {
	a.mutex.Lock()
	defer a.mutex.Unlock()
	name := a.prefix + vol.ObjectName
	blobURL := a.containerSvc.NewBlockBlobURL(name)
	blockIDBinaryToBase64 := func(blockID []byte) string { return base64.StdEncoding.EncodeToString(blockID) }
	blockIDIntToBase64 := func(blockID int) string {
		binaryBlockID := (&[4]byte{})[:]  
		binary.LittleEndian.PutUint32(binaryBlockID, uint32(blockID))
		return blockIDBinaryToBase64(binaryBlockID)
	}
	var (
		blockIDs  []string
		errg      errgroup.Group
		blockid   int
		readBytes uint64
	)
	for {
		blockID := blockIDIntToBase64(blockid)
		blockIDs = append(blockIDs, blockID)
		blockid++
		blockSize := uint64(a.conf.UploadChunkSize)
		if !vol.IsUsingPipe() && blockSize > vol.Size-readBytes {
			blockSize = vol.Size - readBytes
		}
		buf := make([]byte, blockSize)
		n, rerr := io.ReadFull(vol, buf)
		if rerr != nil && rerr != io.ErrUnexpectedEOF {
			return rerr
		}
		readBytes += uint64(n)
		if n > 0 {
			md5sum := md5.Sum(buf[:n])
			select {
			case <-ctx.Done():
				return ctx.Err()
			case a.conf.MaxParallelUploadBuffer <- true:
				errg.Go(func() error {
					defer func() { <-a.conf.MaxParallelUploadBuffer }()
					_, err := blobURL.StageBlock(ctx, blockID, bytes.NewReader(buf[:n]), azblob.LeaseAccessConditions{}, md5sum[:])
					return err
				})
			}
		}
		if !vol.IsUsingPipe() && readBytes == vol.Size || rerr == io.ErrUnexpectedEOF {
			break
		}
	}
	err := errg.Wait()
	if err != nil {
		log.AppLogger.Debugf("azure backend: Error while uploading volume %s - %v", vol.ObjectName, err)
		return err
	}
	md5Raw, merr := hex.DecodeString(vol.MD5Sum)
	if merr != nil {
		return merr
	}
	_, err = blobURL.CommitBlockList(
		ctx, blockIDs, azblob.BlobHTTPHeaders{ContentMD5: md5Raw}, azblob.Metadata{}, azblob.BlobAccessConditions{},
	)
	if err != nil {
		log.AppLogger.Debugf("azure backend: Error while finalizing volume %s - %v", vol.ObjectName, err)
	}
	return err
}
func (a *AzureBackend) Delete(ctx context.Context, name string) error {
	blobURL := a.containerSvc.NewBlobURL(a.prefix + name)
	_, err := blobURL.Delete(ctx, azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
	return err
}
func (a *AzureBackend) PreDownload(ctx context.Context, keys []string) error {
	return nil
}
func (a *AzureBackend) Download(ctx context.Context, name string) (io.ReadCloser, error) {
	blobURL := a.containerSvc.NewBlobURL(a.prefix + name)
	resp, err := blobURL.Download(ctx, 0, 0, azblob.BlobAccessConditions{}, false)
	if err != nil {
		return nil, err
	}
	return resp.Body(azblob.RetryReaderOptions{}), nil
}