-
-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Azure Blob Storage Retriever (#2672)
- Loading branch information
Showing
11 changed files
with
539 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# Azure Blob Storage Feature Flag Retriever | ||
|
||
This retriever is used to retrieve data from a Container on Azure Blob Storage. | ||
|
||
## Installation | ||
|
||
```bash | ||
go get github.com/thomaspoignant/go-feature-flag/retriever/azblobstorageretriever | ||
``` | ||
|
||
## Usage | ||
|
||
### Configuration | ||
|
||
Create a `Retriever` struct with the following fields: | ||
|
||
- `Container`: Name of the Azure Blob Storage container | ||
- `AccountName`: Azure Storage Account Name | ||
- `AccountKey`: (Optional) Storage Account Key | ||
- `ServiceURL`: (Optional) Custom service URL | ||
- `Object`: Name of the feature flag file in the container | ||
|
||
### Authentication Methods | ||
|
||
#### 1. Shared Key Authentication | ||
|
||
```go | ||
retriever := &azblobretriever.Retriever{ | ||
Container: "your-container", | ||
AccountName: "your-account-name", | ||
AccountKey: "your-account-key", | ||
Object: "feature-flags.json", | ||
} | ||
``` | ||
|
||
#### 2. Microsoft Entra ID (Recommended) | ||
|
||
```go | ||
retriever := &azblobretriever.Retriever{ | ||
Container: "your-container", | ||
AccountName: "your-account-name", | ||
Object: "feature-flags.json", | ||
} | ||
``` | ||
|
||
### Retrieving Feature Flags | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"github.com/thomaspoignant/go-feature-flag/retriever/azblobstorageretriever" | ||
) | ||
|
||
func main() { | ||
retriever := &azblobretriever.Retriever{ | ||
Container: "feature-flags", | ||
AccountName: "mystorageaccount", | ||
AccountKey: "your-account-key", | ||
Object: "flags.json", | ||
} | ||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||
defer cancel() | ||
|
||
err := retriever.Init(ctx, nil) | ||
defer func() { _ = r.Shutdown(ctx) }() | ||
if err != nil { | ||
log.Fatalf("Failed to initialize retriever:", err) | ||
} | ||
|
||
data, err := retriever.Retrieve(ctx) | ||
if err != nil { | ||
log.Fatalf("Failed to retrieve feature flags: %v", err) | ||
} | ||
|
||
fmt.Println("Retrieved feature flags:") | ||
fmt.Println(string(data)) | ||
} | ||
``` | ||
|
||
## Key Features | ||
|
||
- Supports both shared key and default Azure credential authentication | ||
- Automatic retry mechanism for blob downloads | ||
- Flexible configuration with optional custom service URL | ||
|
||
## Error Handling | ||
|
||
The `Retrieve` method returns an error if: | ||
- `AccountName` is empty | ||
- `Container` or `Object` is not specified | ||
- There's an issue initializing the Azure client. | ||
- There's a problem downloading or reading the file from Azure Blob Storage. | ||
|
||
## Best Practices | ||
|
||
- **Security** Never hard-code your `AccountKey` in your source code. Use environment variables or a secure secret management system. | ||
- **Error Handling**: Always check for errors returned by the `Retrieve` method. | ||
- **Context**: Use a context with timeout for better control over the retrieval process. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package azblobretriever | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
|
||
"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/retriever" | ||
"github.com/thomaspoignant/go-feature-flag/utils/fflog" | ||
) | ||
|
||
type Retriever struct { | ||
// Container is the name of your Azure Blob Storage Container. | ||
Container string | ||
|
||
// Storage Account Name and Key | ||
AccountName string | ||
AccountKey string | ||
|
||
// ServiceURL is the URL of the storage account e.g. https://<account>.blob.core.windows.net/ | ||
// It can be overridden by the user to use a custom URL. | ||
// Default: https://<account>.blob.core.windows.net/ | ||
ServiceURL string | ||
|
||
// Object is the name of your file in your container. | ||
Object string | ||
|
||
// client is a pointer to an Azure Blob Storage client. | ||
// It provides access to Azure Blob Storage services for operations like | ||
// creating, reading, updating, and deleting blobs. | ||
client *azblob.Client | ||
status retriever.Status | ||
} | ||
|
||
func (r *Retriever) Init(_ context.Context, _ *fflog.FFLogger) error { | ||
if r.AccountName == "" { | ||
return fmt.Errorf("unable to connect to Azure Blob Storage, \"AccountName\" cannot be empty") | ||
} | ||
|
||
url := r.ServiceURL | ||
if url == "" { | ||
url = fmt.Sprintf("https://%s.blob.core.windows.net/", r.AccountName) | ||
} | ||
|
||
var client *azblob.Client | ||
var err error | ||
|
||
if r.AccountKey == "" { | ||
var cred *azidentity.DefaultAzureCredential | ||
cred, err = azidentity.NewDefaultAzureCredential(nil) | ||
if err != nil { | ||
r.status = retriever.RetrieverError | ||
return err | ||
} | ||
client, err = azblob.NewClient(url, cred, nil) | ||
} else { | ||
var cred *azblob.SharedKeyCredential | ||
cred, err = azblob.NewSharedKeyCredential(r.AccountName, r.AccountKey) | ||
if err != nil { | ||
r.status = retriever.RetrieverError | ||
return err | ||
} | ||
client, err = azblob.NewClientWithSharedKeyCredential(url, cred, nil) | ||
} | ||
|
||
if err != nil { | ||
r.status = retriever.RetrieverError | ||
return err | ||
} | ||
|
||
r.client = client | ||
r.status = retriever.RetrieverReady | ||
return nil | ||
} | ||
|
||
func (r *Retriever) Shutdown(_ context.Context) error { | ||
r.client = nil | ||
r.status = retriever.RetrieverNotReady | ||
return nil | ||
} | ||
|
||
func (r *Retriever) Status() retriever.Status { | ||
return r.status | ||
} | ||
|
||
func (r *Retriever) Retrieve(ctx context.Context) ([]byte, error) { | ||
if r.client == nil { | ||
r.status = retriever.RetrieverError | ||
return nil, fmt.Errorf("client is not initialized") | ||
} | ||
|
||
if r.Object == "" || r.Container == "" { | ||
return nil, fmt.Errorf("missing mandatory information object=%s, repositorySlug=%s", r.Object, r.Container) | ||
} | ||
|
||
fileStream, err := r.client.DownloadStream(ctx, r.Container, r.Object, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
retryReader := fileStream.NewRetryReader(ctx, nil) | ||
defer func() { _ = retryReader.Close() }() | ||
|
||
body, err := io.ReadAll(retryReader) | ||
if err != nil { | ||
return nil, | ||
fmt.Errorf("unable to read from Azure Blob Storage Object %s in Container %s, error: %s", r.Object, r.Container, err) | ||
} | ||
|
||
return body, nil | ||
} |
Oops, something went wrong.