Skip to content
Open
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
62 changes: 52 additions & 10 deletions azureappconfiguration/azureappconfiguration.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"sync/atomic"
"time"

"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/afd"
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/jsonc"
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/refresh"
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/tracing"
Expand Down Expand Up @@ -55,8 +56,8 @@ type AzureAppConfiguration struct {
// Settings used for refresh scenarios
sentinelETags map[WatchedSetting]*azcore.ETag
watchAll bool
kvETags map[comparableSelector][]*azcore.ETag
ffETags map[comparableSelector][]*azcore.ETag
kvWatchers map[comparableSelector][]settingWatcher
ffWatchers map[comparableSelector][]settingWatcher
keyVaultRefs map[string]string // unversioned Key Vault references
kvRefreshTimer refresh.Condition
secretRefreshTimer refresh.Condition
Expand Down Expand Up @@ -117,11 +118,17 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
credential: options.KeyVaultOptions.Credential,
}

if authentication.Credential != nil {
if _, ok := authentication.Credential.(*afd.EmptyTokenCredential); ok {
azappcfg.tracingOptions.AfdUsed = true
}
}

if options.RefreshOptions.Enabled {
azappcfg.kvRefreshTimer = refresh.NewTimer(options.RefreshOptions.Interval)
azappcfg.watchedSettings = normalizedWatchedSettings(options.RefreshOptions.WatchedSettings)
azappcfg.sentinelETags = make(map[WatchedSetting]*azcore.ETag)
azappcfg.kvETags = make(map[comparableSelector][]*azcore.ETag)
azappcfg.kvWatchers = make(map[comparableSelector][]settingWatcher)
if len(options.RefreshOptions.WatchedSettings) == 0 {
azappcfg.watchAll = true
}
Expand All @@ -137,7 +144,7 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
azappcfg.ffSelectors = getFeatureFlagSelectors(deduplicateSelectors(options.FeatureFlagOptions.Selectors))
if options.FeatureFlagOptions.RefreshOptions.Enabled {
azappcfg.ffRefreshTimer = refresh.NewTimer(options.FeatureFlagOptions.RefreshOptions.Interval)
azappcfg.ffETags = make(map[comparableSelector][]*azcore.ETag)
azappcfg.ffWatchers = make(map[comparableSelector][]settingWatcher)
}
}

Expand All @@ -150,6 +157,41 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
return azappcfg, nil
}

func (azappcfg *AzureAppConfiguration) LoadFromAzureFrontDoor(ctx context.Context, endpoint string, options *Options) (*AzureAppConfiguration, error) {
if options == nil {
options = &Options{}
}

if options.ReplicaDiscoveryEnabled != nil && *options.ReplicaDiscoveryEnabled {
return nil, errors.New("replica discovery is not supported when loading from Azure Front Door. For guidance on how to take advantage of geo-replication when Azure Front Door is used, visit https://aka.ms/appconfig/geo-replication-with-afd")
}

if options.LoadBalancingEnabled {
return nil, errors.New("load balancing is not supported when loading from Azure Front Door. For guidance on how to take advantage of geo-replication when Azure Front Door is used, visit https://aka.ms/appconfig/geo-replication-with-afd")
}

if options.RefreshOptions.Enabled &&
len(options.RefreshOptions.WatchedSettings) > 0 {
return nil, errors.New("specifying watched settings is not supported when loading from Azure Front Door. If refresh is enabled, all loaded configuration settings will be watched automatically")
}

disableReplicaDiscovery := false
options.ReplicaDiscoveryEnabled = &disableReplicaDiscovery

if options.ClientOptions == nil {
options.ClientOptions = &azappconfig.ClientOptions{}
}

options.ClientOptions.PerRetryPolicies = append(options.ClientOptions.PerRetryPolicies, afd.NewAnonymousRequestPipelinePolicy(), afd.NewRemoveSyncTokenPipelinePolicy())

authOptions := AuthenticationOptions{
Endpoint: endpoint,
Credential: afd.NewEmptyTokenCredential(),
}

return Load(ctx, authOptions, options)
}

// Unmarshal parses the configuration and stores the result in the value pointed to v. It builds a hierarchical configuration structure based on key separators.
// It supports converting values to appropriate target types.
//
Expand Down Expand Up @@ -432,7 +474,7 @@ func (azappcfg *AzureAppConfiguration) loadKeyValues(ctx context.Context, settin
maps.Copy(kvSettings, secrets)
azappcfg.keyValues = kvSettings
azappcfg.keyVaultRefs = getUnversionedKeyVaultRefs(keyVaultRefs)
azappcfg.kvETags = settingsResponse.pageETags
azappcfg.kvWatchers = settingsResponse.pageWatchers

return nil
}
Expand Down Expand Up @@ -509,7 +551,7 @@ func (azappcfg *AzureAppConfiguration) loadFeatureFlags(ctx context.Context, set
},
}

azappcfg.ffETags = settingsResponse.pageETags
azappcfg.ffWatchers = settingsResponse.pageWatchers
azappcfg.featureFlags = ffSettings

return nil
Expand Down Expand Up @@ -857,10 +899,10 @@ func normalizedWatchedSettings(s []WatchedSetting) []WatchedSetting {
func (azappcfg *AzureAppConfiguration) newKeyValueRefreshClient(client *azappconfig.Client) refreshClient {
var monitor eTagsClient
if azappcfg.watchAll {
monitor = &pageETagsClient{
monitor = &pageWatcherClient{
client: client,
tracingOptions: azappcfg.tracingOptions,
pageETags: azappcfg.kvETags,
pageWatchers: azappcfg.kvWatchers,
}
} else {
monitor = &watchedSettingClient{
Expand Down Expand Up @@ -892,10 +934,10 @@ func (azappcfg *AzureAppConfiguration) newFeatureFlagRefreshClient(client *azapp
client: client,
tracingOptions: azappcfg.tracingOptions,
},
monitor: &pageETagsClient{
monitor: &pageWatcherClient{
client: client,
tracingOptions: azappcfg.tracingOptions,
pageETags: azappcfg.ffETags,
pageWatchers: azappcfg.ffWatchers,
},
}
}
Expand Down
9 changes: 4 additions & 5 deletions azureappconfiguration/azureappconfiguration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (

"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/fm"
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/tracing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -128,7 +127,7 @@ func TestLoadFeatureFlags_Success(t *testing.T) {
{Key: toPtr(".appconfig.featureflag/Beta"), Value: &value1, ContentType: toPtr(featureFlagContentType)},
{Key: toPtr(".appconfig.featureflag/Alpha"), Value: &value2, ContentType: toPtr(featureFlagContentType)},
},
pageETags: map[comparableSelector][]*azcore.ETag{},
pageWatchers: map[comparableSelector][]settingWatcher{},
}

mockClient.On("getSettings", ctx).Return(mockResponse, nil)
Expand Down Expand Up @@ -1545,7 +1544,7 @@ func TestLoadFeatureFlags_TracingUpdated(t *testing.T) {
ContentType: toPtr(featureFlagContentType),
},
},
pageETags: map[comparableSelector][]*azcore.ETag{},
pageWatchers: map[comparableSelector][]settingWatcher{},
}

mockClient.On("getSettings", ctx).Return(mockResponse, nil)
Expand Down Expand Up @@ -1639,7 +1638,7 @@ func TestLoadKeyValues_WithTagFilter(t *testing.T) {
},
},
},
pageETags: map[comparableSelector][]*azcore.ETag{},
pageWatchers: map[comparableSelector][]settingWatcher{},
}

mockClient.On("getSettings", ctx).Return(mockResponse, nil)
Expand Down Expand Up @@ -1695,7 +1694,7 @@ func TestLoadKeyValues_WithMultipleTagFilters(t *testing.T) {
},
},
},
pageETags: map[comparableSelector][]*azcore.ETag{},
pageWatchers: map[comparableSelector][]settingWatcher{},
}

mockClient.On("getSettings", ctx).Return(mockResponse, nil)
Expand Down
26 changes: 26 additions & 0 deletions azureappconfiguration/internal/afd/empty_token_cred.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package afd

import (
"context"
"math"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
)

type EmptyTokenCredential struct{}

func (e *EmptyTokenCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
return azcore.AccessToken{
Token: "",
ExpiresOn: time.Unix(math.MaxInt64, 0),
}, nil
}

func NewEmptyTokenCredential() azcore.TokenCredential {
return &EmptyTokenCredential{}
}
38 changes: 38 additions & 0 deletions azureappconfiguration/internal/afd/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package afd

import (
"net/http"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
)

type AnonymousRequestPipelinePolicy struct{}

type RemoveSyncTokenPipelinePolicy struct{}

func NewAnonymousRequestPipelinePolicy() *AnonymousRequestPipelinePolicy {
return &AnonymousRequestPipelinePolicy{}
}

func NewRemoveSyncTokenPipelinePolicy() *RemoveSyncTokenPipelinePolicy {
return &RemoveSyncTokenPipelinePolicy{}
}

func (p *AnonymousRequestPipelinePolicy) Do(req *policy.Request) (*http.Response, error) {
if req.Raw().Header.Get("Authorization") != "" {
req.Raw().Header.Del("Authorization")
}

return req.Next()
}

func (p *RemoveSyncTokenPipelinePolicy) Do(req *policy.Request) (*http.Response, error) {
if req.Raw().Header.Get("Sync-Token") != "" {
req.Raw().Header.Del("Sync-Token")
}

return req.Next()
}
6 changes: 6 additions & 0 deletions azureappconfiguration/internal/tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
FailoverRequestTag = "Failover"
ReplicaCountKey = "ReplicaCount"
LoadBalancingEnabledTag = "LB"
AFDUsedTag = "AFD"

// Feature flag usage tracing
FMGoVerEnv = "MS_FEATURE_MANAGEMENT_GO_VERSION"
Expand Down Expand Up @@ -77,6 +78,7 @@ type Options struct {
IsFailoverRequest bool
ReplicaCount int
IsLoadBalancingEnabled bool
AfdUsed bool
FeatureFlagTracing *FeatureFlagTracing
FMVersion string
}
Expand Down Expand Up @@ -143,6 +145,10 @@ func CreateCorrelationContextHeader(ctx context.Context, options Options) http.H
features = append(features, LoadBalancingEnabledTag)
}

if options.AfdUsed {
features = append(features, AFDUsedTag)
}

if len(features) > 0 {
featureStr := FeaturesKey + "=" + strings.Join(features, DelimiterPlus)
output = append(output, featureStr)
Expand Down
2 changes: 1 addition & 1 deletion azureappconfiguration/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ type Selector struct {
// comparableKey returns a comparable representation of the Selector that can be used as a map key.
// This method creates a deterministic string representation by sorting the TagFilter slice.
func (s Selector) comparableKey() comparableSelector {
cs := comparableSelector{
cs := comparableSelector{
KeyFilter: s.KeyFilter,
LabelFilter: s.LabelFilter,
SnapshotName: s.SnapshotName,
Expand Down
Loading
Loading