Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ import (
"fmt"
"net/url"
"os"
"strings"
"time"

"github.com/Azure/azure-storage-blob-go/azblob"

"github.com/livekit/protocol/logger"
)

type azureBLOBStorage struct {
Expand All @@ -33,12 +36,35 @@ type azureBLOBStorage struct {
}

func NewAzure(conf *AzureConfig) (Storage, error) {
credential, err := azblob.NewSharedKeyCredential(
conf.AccountName,
conf.AccountKey,
)
if err != nil {
return nil, err
if conf.AccountName == "" {
return nil, errors.New("azure: account_name is required")
}
if conf.ContainerName == "" {
return nil, errors.New("azure: container_name is required")
}

sasToken := strings.TrimPrefix(conf.SASToken, "?")

var credential azblob.Credential
switch {
case sasToken != "":
if conf.AccountKey != "" {
logger.Warnw("azure: both account_key and sas_token configured, using sas_token", nil)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually do not log warnings for bad user provided input. Even less so here where there is no actual failure.

}
credential = azblob.NewAnonymousCredential()
case conf.AccountKey != "":
sharedKeyCred, err := azblob.NewSharedKeyCredential(
conf.AccountName,
conf.AccountKey,
)
if err != nil {
return nil, err
}
credential = sharedKeyCred
case conf.TokenCredential != nil:
credential = conf.TokenCredential
default:
return nil, errors.New("azure: one of account_key, sas_token, or TokenCredential is required")
}

pipeline := azblob.NewPipeline(credential, azblob.PipelineOptions{
Expand All @@ -56,6 +82,9 @@ func NewAzure(conf *AzureConfig) (Storage, error) {
}

cUrl := fmt.Sprintf("%s/%s", sUrl, conf.ContainerName)
if sasToken != "" {
cUrl = fmt.Sprintf("%s?%s", cUrl, sasToken)
}
containerUrl, err := url.Parse(cUrl)
if err != nil {
return nil, err
Expand Down Expand Up @@ -182,6 +211,14 @@ func (s *azureBLOBStorage) DownloadFile(filepath, storagePath string) (int64, er
}

func (s *azureBLOBStorage) GeneratePresignedUrl(storagePath string, expiration time.Duration) (string, error) {
if sasToken := strings.TrimPrefix(s.conf.SASToken, "?"); sasToken != "" {
// When a pre-generated SASToken is configured, return the blob URL with the SAS appended.
// The returned URL's capabilities and lifetime are defined by the SAS itself; the
// expiration argument is ignored.
return fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s?%s",
s.conf.AccountName, s.conf.ContainerName, storagePath, sasToken), nil
}

if s.conf.TokenCredential == nil {
return "", errors.New("OAuth required")
}
Expand Down
3 changes: 2 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ func (c *AliOSSConfig) newStorage() (Storage, error) { return NewAliOSS(c) }
type AzureConfig struct {
AccountName string `yaml:"account_name,omitempty"` // (env AZURE_STORAGE_ACCOUNT)
AccountKey string `yaml:"account_key,omitempty"` // (env AZURE_STORAGE_KEY)
SASToken string `yaml:"sas_token,omitempty"` // Account or Service SAS query string (with or without leading '?'); preferred over AccountKey when set
ContainerName string `yaml:"container_name,omitempty"`
TokenCredential azblob.TokenCredential `yaml:"-"` // required for presigned url generation
TokenCredential azblob.TokenCredential `yaml:"-"` // required for presigned url generation when SASToken is not set
}

func (c *AzureConfig) newStorage() (Storage, error) { return NewAzure(c) }
Expand Down
Loading