github.com/Jeffail/benthos/v3@v3.65.0/lib/output/writer/azure_blob_storage.go (about) 1 //go:build !wasm 2 // +build !wasm 3 4 package writer 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "net/url" 12 "strings" 13 "time" 14 15 "github.com/Azure/azure-sdk-for-go/storage" 16 "github.com/Azure/go-autorest/autorest/azure" 17 "github.com/Jeffail/benthos/v3/internal/bloblang/field" 18 "github.com/Jeffail/benthos/v3/internal/interop" 19 "github.com/Jeffail/benthos/v3/lib/log" 20 "github.com/Jeffail/benthos/v3/lib/metrics" 21 "github.com/Jeffail/benthos/v3/lib/types" 22 ) 23 24 //------------------------------------------------------------------------------ 25 26 // AzureBlobStorage is a benthos writer. Type implementation that writes messages to an 27 // Azure Blob Storage storage account. 28 type AzureBlobStorage struct { 29 conf AzureBlobStorageConfig 30 container *field.Expression 31 path *field.Expression 32 blobType *field.Expression 33 accessLevel *field.Expression 34 client storage.BlobStorageClient 35 log log.Modular 36 stats metrics.Type 37 } 38 39 // NewAzureBlobStorage creates a new AzureBlobStorage writer.Type. 40 // Deprecated 41 func NewAzureBlobStorage( 42 conf AzureBlobStorageConfig, 43 log log.Modular, 44 stats metrics.Type, 45 ) (*AzureBlobStorage, error) { 46 return NewAzureBlobStorageV2(types.NoopMgr(), conf, log, stats) 47 } 48 49 // NewAzureBlobStorageV2 creates a new AzureBlobStorage writer.Type. 50 func NewAzureBlobStorageV2( 51 mgr types.Manager, 52 conf AzureBlobStorageConfig, 53 log log.Modular, 54 stats metrics.Type, 55 ) (*AzureBlobStorage, error) { 56 if conf.StorageAccount == "" && conf.StorageConnectionString == "" { 57 return nil, errors.New("invalid azure storage account credentials") 58 } 59 var client storage.Client 60 var err error 61 if len(conf.StorageConnectionString) > 0 { 62 if strings.Contains(conf.StorageConnectionString, "UseDevelopmentStorage=true;") { 63 client, err = storage.NewEmulatorClient() 64 } else { 65 client, err = storage.NewClientFromConnectionString(conf.StorageConnectionString) 66 } 67 } else if len(conf.StorageAccessKey) > 0 { 68 client, err = storage.NewBasicClient(conf.StorageAccount, conf.StorageAccessKey) 69 } else { 70 // The SAS token in the Azure UI is provided as an URL query string with 71 // the '?' prepended to it which confuses url.ParseQuery 72 token, err := url.ParseQuery(strings.TrimPrefix(conf.StorageSASToken, "?")) 73 if err != nil { 74 return nil, fmt.Errorf("invalid azure storage SAS token: %v", err) 75 } 76 client = storage.NewAccountSASClient(conf.StorageAccount, token, azure.PublicCloud) 77 } 78 if err != nil { 79 return nil, fmt.Errorf("invalid azure storage account credentials: %v", err) 80 } 81 a := &AzureBlobStorage{ 82 conf: conf, 83 log: log, 84 stats: stats, 85 client: client.GetBlobService(), 86 } 87 if a.container, err = interop.NewBloblangField(mgr, conf.Container); err != nil { 88 return nil, fmt.Errorf("failed to parse container expression: %v", err) 89 } 90 if a.path, err = interop.NewBloblangField(mgr, conf.Path); err != nil { 91 return nil, fmt.Errorf("failed to parse path expression: %v", err) 92 } 93 if a.blobType, err = interop.NewBloblangField(mgr, conf.BlobType); err != nil { 94 return nil, fmt.Errorf("failed to parse blob type expression: %v", err) 95 } 96 if a.accessLevel, err = interop.NewBloblangField(mgr, conf.PublicAccessLevel); err != nil { 97 return nil, fmt.Errorf("failed to parse public access level expression: %v", err) 98 } 99 return a, nil 100 } 101 102 // ConnectWithContext attempts to establish a connection to the target Blob Storage Account. 103 func (a *AzureBlobStorage) ConnectWithContext(ctx context.Context) error { 104 return a.Connect() 105 } 106 107 // Connect attempts to establish a connection to the target Blob Storage Account. 108 func (a *AzureBlobStorage) Connect() error { 109 return nil 110 } 111 112 // Write attempts to write message contents to a target Azure Blob Storage container as files. 113 func (a *AzureBlobStorage) Write(msg types.Message) error { 114 return a.WriteWithContext(context.Background(), msg) 115 } 116 117 func (a *AzureBlobStorage) uploadBlob(b *storage.Blob, blobType string, message []byte) error { 118 if blobType == "APPEND" { 119 exists, err := b.Exists() 120 if err != nil { 121 return err 122 } 123 if !exists { 124 if err := b.PutAppendBlob(nil); err != nil { 125 return err 126 } 127 } 128 return b.AppendBlock(message, nil) 129 } 130 return b.CreateBlockBlobFromReader(bytes.NewReader(message), nil) 131 } 132 133 func (a *AzureBlobStorage) createContainer(c *storage.Container, accessLevel string) error { 134 opts := storage.CreateContainerOptions{} 135 switch accessLevel { 136 case "BLOB": 137 opts.Access = storage.ContainerAccessTypeBlob 138 case "CONTAINER": 139 opts.Access = storage.ContainerAccessTypeContainer 140 } 141 return c.Create(&opts) 142 } 143 144 // WriteWithContext attempts to write message contents to a target storage account as files. 145 func (a *AzureBlobStorage) WriteWithContext(_ context.Context, msg types.Message) error { 146 return IterateBatchedSend(msg, func(i int, p types.Part) error { 147 c := a.client.GetContainerReference(a.container.String(i, msg)) 148 b := c.GetBlobReference(a.path.String(i, msg)) 149 if err := a.uploadBlob(b, a.blobType.String(i, msg), p.Get()); err != nil { 150 if containerNotFound(err) { 151 if cerr := a.createContainer(c, a.accessLevel.String(i, msg)); cerr != nil { 152 a.log.Debugf("error creating container: %v.", cerr) 153 return cerr 154 } 155 err = a.uploadBlob(b, a.blobType.String(i, msg), p.Get()) 156 if err != nil { 157 a.log.Debugf("error retrying to upload blob: %v.", err) 158 } 159 } 160 return err 161 } 162 return nil 163 }) 164 } 165 166 func containerNotFound(err error) bool { 167 if serr, ok := err.(storage.AzureStorageServiceError); ok { 168 return serr.Code == "ContainerNotFound" 169 } 170 return false 171 } 172 173 // CloseAsync begins cleaning up resources used by this reader asynchronously. 174 func (a *AzureBlobStorage) CloseAsync() { 175 } 176 177 // WaitForClose will block until either the reader is closed or a specified 178 // timeout occurs. 179 func (a *AzureBlobStorage) WaitForClose(time.Duration) error { 180 return nil 181 } 182 183 //------------------------------------------------------------------------------