Skip to content

Commit

Permalink
feat: Azure Blob Storage Exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
Abeeujah committed Nov 7, 2024
1 parent cba1570 commit d99dbf3
Show file tree
Hide file tree
Showing 14 changed files with 472 additions and 30 deletions.
57 changes: 29 additions & 28 deletions README.md

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion cmd/relayproxy/config/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type ExporterConf struct {
Topic string `mapstructure:"topic" koanf:"topic"`
StreamArn string `mapstructure:"streamArn" koanf:"streamarn"`
StreamName string `mapstructure:"streamName" koanf:"streamname"`
AccountName string `mapstructure:"accountName" koanf:"accountname"`
AccountKey string `mapstructure:"accountKey" koanf:"accountkey"`
Container string `mapstructure:"container" koanf:"container"`
}

func (c *ExporterConf) IsValid() error {
Expand Down Expand Up @@ -65,6 +68,10 @@ func (c *ExporterConf) IsValid() error {
return fmt.Errorf("invalid exporter: \"projectID\" and \"topic\" are required for kind \"%s\"", c.Kind)
}

if c.Kind == AzureExporter && c.Container == "" {
return fmt.Errorf("invalid exporter: no \"container\" property found for kind \"%s\"", c.Kind)
}

return nil
}

Expand All @@ -80,13 +87,14 @@ const (
SQSExporter ExporterKind = "sqs"
KafkaExporter ExporterKind = "kafka"
PubSubExporter ExporterKind = "pubsub"
AzureExporter ExporterKind = "azureBlobStorage"
)

// IsValid is checking if the value is part of the enum
func (r ExporterKind) IsValid() error {
switch r {
case FileExporter, WebhookExporter, LogExporter, S3Exporter, GoogleStorageExporter, SQSExporter, KafkaExporter,
PubSubExporter, KinesisExporter:
PubSubExporter, KinesisExporter, AzureExporter:
return nil
}
return fmt.Errorf("invalid exporter: kind \"%s\" is not supported", r)
Expand Down
9 changes: 9 additions & 0 deletions cmd/relayproxy/config/exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestExporterConf_IsValid(t *testing.T) {
ProjectID string
Topic string
StreamName string
Container string
}
tests := []struct {
name string
Expand Down Expand Up @@ -77,6 +78,14 @@ func TestExporterConf_IsValid(t *testing.T) {
wantErr: true,
errValue: "invalid exporter: no \"bucket\" property found for kind \"googleStorage\"",
},
{
name: "kind azureBlobStorage without container",
fields: fields{
Kind: "azureBlobStorage",
},
wantErr: true,
errValue: "invalid exporter: no \"container\" property found for kind \"azureBlobStorage\"",
},
{
name: "kind webhook without bucket",
fields: fields{
Expand Down
10 changes: 10 additions & 0 deletions cmd/relayproxy/service/gofeatureflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
ffclient "github.com/thomaspoignant/go-feature-flag"
"github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/config"
"github.com/thomaspoignant/go-feature-flag/exporter"
"github.com/thomaspoignant/go-feature-flag/exporter/azureexporter"
"github.com/thomaspoignant/go-feature-flag/exporter/fileexporter"
"github.com/thomaspoignant/go-feature-flag/exporter/gcstorageexporter"
"github.com/thomaspoignant/go-feature-flag/exporter/kafkaexporter"
Expand Down Expand Up @@ -303,6 +304,15 @@ func createExporter(c *config.ExporterConf) (exporter.CommonExporter, error) {
ProjectID: c.ProjectID,
Topic: c.Topic,
}, nil
case config.AzureExporter:
return &azureexporter.Exporter{
Container: c.Container,
Format: format,
Path: c.Path,
Filename: filename,
CsvTemplate: csvTemplate,
ParquetCompressionCodec: parquetCompressionCodec,
}, nil
default:
return nil, fmt.Errorf("invalid exporter: kind \"%s\" is not supported", c.Kind)
}
Expand Down
24 changes: 24 additions & 0 deletions cmd/relayproxy/service/gofeatureflag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
ffclient "github.com/thomaspoignant/go-feature-flag"
"github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/config"
"github.com/thomaspoignant/go-feature-flag/exporter"
"github.com/thomaspoignant/go-feature-flag/exporter/azureexporter"
"github.com/thomaspoignant/go-feature-flag/exporter/fileexporter"
"github.com/thomaspoignant/go-feature-flag/exporter/gcstorageexporter"
"github.com/thomaspoignant/go-feature-flag/exporter/kafkaexporter"
Expand Down Expand Up @@ -393,6 +394,29 @@ func Test_initExporter(t *testing.T) {
wantType: &kinesisexporter.Exporter{},
skipCompleteValidation: true,
},
{
name: "Azure Blob Storage Exporter",
wantErr: assert.NoError,
conf: &config.ExporterConf{
Kind: "azureBlobStorage",
Container: "my-container",
Path: "/my-path/",
MaxEventInMemory: 1990,
},
want: ffclient.DataExporter{
FlushInterval: config.DefaultExporter.FlushInterval,
MaxEventInMemory: 1990,
Exporter: &azureexporter.Exporter{
Container: "my-container",
Format: config.DefaultExporter.Format,
Path: "/my-path/",
Filename: config.DefaultExporter.FileName,
CsvTemplate: config.DefaultExporter.CsvFormat,
ParquetCompressionCodec: config.DefaultExporter.ParquetCompressionCodec,
},
},
wantType: &azureexporter.Exporter{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
109 changes: 109 additions & 0 deletions exporter/azureexporter/exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package azureexporter

import (
"context"
"fmt"
"log/slog"
"os"

"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/thomaspoignant/go-feature-flag/exporter"
"github.com/thomaspoignant/go-feature-flag/exporter/fileexporter"
"github.com/thomaspoignant/go-feature-flag/utils/fflog"
)

type Exporter struct {
// Container is the name of your Azure Blob Storage Container similar to Buckets in S3.
Container string

// Storage Account Name and Key
AccountName string
AccountKey string

Format string
Path string
Filename string
CsvTemplate string
ParquetCompressionCodec string
}

func (f *Exporter) initializeAzureClient() (*azblob.Client, error) {
url := fmt.Sprintf("https://%s.blob.core.windows.net/", f.AccountName)

if f.AccountKey == "" {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, err
}

Check warning on line 38 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L37-L38

Added lines #L37 - L38 were not covered by tests
return azblob.NewClient(url, cred, nil)
}
cred, err := azblob.NewSharedKeyCredential(f.AccountName, f.AccountKey)
if err != nil {
return nil, err
}
return azblob.NewClientWithSharedKeyCredential(url, cred, nil)

Check warning on line 45 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L41-L45

Added lines #L41 - L45 were not covered by tests
}

func (f *Exporter) Export(ctx context.Context, logger *fflog.FFLogger, featureEvents []exporter.FeatureEvent) error {
client, err := f.initializeAzureClient()
if err != nil {
return err
}

Check warning on line 52 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L51-L52

Added lines #L51 - L52 were not covered by tests

if f.Container == "" {
return fmt.Errorf("you should specify a container. %v is invalid", f.Container)
}

outputDir, err := os.MkdirTemp("", "go_feature_flag_AzureBlobStorage_export")
if err != nil {
return err
}

Check warning on line 61 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L60-L61

Added lines #L60 - L61 were not covered by tests
defer func() { _ = os.Remove(outputDir) }()

fileExporter := fileexporter.Exporter{
Format: f.Format,
OutputDir: outputDir,
Filename: f.Filename,
CsvTemplate: f.CsvTemplate,
ParquetCompressionCodec: f.ParquetCompressionCodec,
}
err = fileExporter.Export(ctx, logger, featureEvents)
if err != nil {
return err
}

files, err := os.ReadDir(outputDir)
if err != nil {
return err
}

Check warning on line 79 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L78-L79

Added lines #L78 - L79 were not covered by tests

for _, file := range files {
fileName := file.Name()
of, err := os.Open(outputDir + fileName)
if err != nil {
logger.Error("[Azure Exporter] impossible to open file", slog.String("path", outputDir+"/"+fileName))
continue
}
defer func() { _ = of.Close() }()

Check warning on line 88 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L88

Added line #L88 was not covered by tests

// prepend the path
source := fileName
if f.Path != "" {
source = f.Path + "/" + fileName
}

Check warning on line 94 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L91-L94

Added lines #L91 - L94 were not covered by tests

_, err = client.UploadFile(context.Background(), f.Container, source, of, nil)
if err != nil {
logger.Error("[Azure Exporter] failed to upload file", slog.String("path", outputDir+"/"+fileName))
return err
}

Check warning on line 100 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L96-L100

Added lines #L96 - L100 were not covered by tests

logger.Info("[Azure Exporter] file uploaded.", slog.String("location", f.Container+"/"+fileName))

Check warning on line 102 in exporter/azureexporter/exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter/azureexporter/exporter.go#L102

Added line #L102 was not covered by tests
}
return nil
}

func (f *Exporter) IsBulk() bool {
return true
}
Loading

0 comments on commit d99dbf3

Please sign in to comment.