Skip to content

Commit 4f609ff

Browse files
committed
Load from Azure Front Door support
1 parent 79ab02c commit 4f609ff

6 files changed

Lines changed: 180 additions & 34 deletions

File tree

azureappconfiguration/azureappconfiguration.go

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"sync/atomic"
2929
"time"
3030

31+
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/afd"
3132
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/jsonc"
3233
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/refresh"
3334
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/tracing"
@@ -51,12 +52,13 @@ type AzureAppConfiguration struct {
5152
trimPrefixes []string
5253
watchedSettings []WatchedSetting
5354
loadBalancingEnabled bool
55+
afdUsed bool
5456

5557
// Settings used for refresh scenarios
5658
sentinelETags map[WatchedSetting]*azcore.ETag
5759
watchAll bool
58-
kvETags map[comparableSelector][]*azcore.ETag
59-
ffETags map[comparableSelector][]*azcore.ETag
60+
kvWatchers map[comparableSelector][]settingWatcher
61+
ffWatchers map[comparableSelector][]settingWatcher
6062
keyVaultRefs map[string]string // unversioned Key Vault references
6163
kvRefreshTimer refresh.Condition
6264
secretRefreshTimer refresh.Condition
@@ -117,11 +119,18 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
117119
credential: options.KeyVaultOptions.Credential,
118120
}
119121

122+
if authentication.Credential != nil {
123+
if _, ok := authentication.Credential.(*afd.EmptyTokenCredential); ok {
124+
azappcfg.afdUsed = true
125+
azappcfg.tracingOptions.AfdUsed = true
126+
}
127+
}
128+
120129
if options.RefreshOptions.Enabled {
121130
azappcfg.kvRefreshTimer = refresh.NewTimer(options.RefreshOptions.Interval)
122131
azappcfg.watchedSettings = normalizedWatchedSettings(options.RefreshOptions.WatchedSettings)
123132
azappcfg.sentinelETags = make(map[WatchedSetting]*azcore.ETag)
124-
azappcfg.kvETags = make(map[comparableSelector][]*azcore.ETag)
133+
azappcfg.kvWatchers = make(map[comparableSelector][]settingWatcher)
125134
if len(options.RefreshOptions.WatchedSettings) == 0 {
126135
azappcfg.watchAll = true
127136
}
@@ -137,7 +146,7 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
137146
azappcfg.ffSelectors = getFeatureFlagSelectors(deduplicateSelectors(options.FeatureFlagOptions.Selectors))
138147
if options.FeatureFlagOptions.RefreshOptions.Enabled {
139148
azappcfg.ffRefreshTimer = refresh.NewTimer(options.FeatureFlagOptions.RefreshOptions.Interval)
140-
azappcfg.ffETags = make(map[comparableSelector][]*azcore.ETag)
149+
azappcfg.ffWatchers = make(map[comparableSelector][]settingWatcher)
141150
}
142151
}
143152

@@ -150,6 +159,41 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
150159
return azappcfg, nil
151160
}
152161

162+
func (azappcfg *AzureAppConfiguration) LoadFromAzureFrontDoor(ctx context.Context, endpoint string, options *Options) (*AzureAppConfiguration, error) {
163+
if options == nil {
164+
options = &Options{}
165+
}
166+
167+
if options.ReplicaDiscoveryEnabled != nil && *options.ReplicaDiscoveryEnabled {
168+
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")
169+
}
170+
171+
if options.LoadBalancingEnabled {
172+
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")
173+
}
174+
175+
if options.RefreshOptions.Enabled &&
176+
len(options.RefreshOptions.WatchedSettings) > 0 {
177+
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")
178+
}
179+
180+
disableReplicaDiscovery := false
181+
options.ReplicaDiscoveryEnabled = &disableReplicaDiscovery
182+
183+
if options.ClientOptions == nil {
184+
options.ClientOptions = &azappconfig.ClientOptions{}
185+
}
186+
187+
options.ClientOptions.PerRetryPolicies = append(options.ClientOptions.PerRetryPolicies, afd.NewAnonymousRequestPipelinePolicy(), afd.NewRemoveSyncTokenPipelinePolicy())
188+
189+
authOptions := AuthenticationOptions{
190+
Endpoint: endpoint,
191+
Credential: afd.NewEmptyTokenCredential(),
192+
}
193+
194+
return Load(ctx, authOptions, options)
195+
}
196+
153197
// Unmarshal parses the configuration and stores the result in the value pointed to v. It builds a hierarchical configuration structure based on key separators.
154198
// It supports converting values to appropriate target types.
155199
//
@@ -451,7 +495,7 @@ func (azappcfg *AzureAppConfiguration) loadKeyValues(ctx context.Context, settin
451495
maps.Copy(kvSettings, secrets)
452496
azappcfg.keyValues = kvSettings
453497
azappcfg.keyVaultRefs = getUnversionedKeyVaultRefs(keyVaultRefs)
454-
azappcfg.kvETags = settingsResponse.pageETags
498+
azappcfg.kvWatchers = settingsResponse.pageWatchers
455499

456500
return nil
457501
}
@@ -600,7 +644,7 @@ func (azappcfg *AzureAppConfiguration) loadFeatureFlags(ctx context.Context, set
600644
},
601645
}
602646

603-
azappcfg.ffETags = settingsResponse.pageETags
647+
azappcfg.ffWatchers = settingsResponse.pageWatchers
604648
azappcfg.featureFlags = ffSettings
605649

606650
return nil
@@ -948,10 +992,10 @@ func normalizedWatchedSettings(s []WatchedSetting) []WatchedSetting {
948992
func (azappcfg *AzureAppConfiguration) newKeyValueRefreshClient(client *azappconfig.Client) refreshClient {
949993
var monitor eTagsClient
950994
if azappcfg.watchAll {
951-
monitor = &pageETagsClient{
995+
monitor = &pageWatcherClient{
952996
client: client,
953997
tracingOptions: azappcfg.tracingOptions,
954-
pageETags: azappcfg.kvETags,
998+
pageWatchers: azappcfg.kvWatchers,
955999
}
9561000
} else {
9571001
monitor = &watchedSettingClient{
@@ -983,10 +1027,10 @@ func (azappcfg *AzureAppConfiguration) newFeatureFlagRefreshClient(client *azapp
9831027
client: client,
9841028
tracingOptions: azappcfg.tracingOptions,
9851029
},
986-
monitor: &pageETagsClient{
1030+
monitor: &pageWatcherClient{
9871031
client: client,
9881032
tracingOptions: azappcfg.tracingOptions,
989-
pageETags: azappcfg.ffETags,
1033+
pageWatchers: azappcfg.ffWatchers,
9901034
},
9911035
}
9921036
}

azureappconfiguration/azureappconfiguration_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515

1616
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/fm"
1717
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/tracing"
18-
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1918
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2"
2019
"github.com/stretchr/testify/assert"
2120
"github.com/stretchr/testify/mock"
@@ -128,7 +127,7 @@ func TestLoadFeatureFlags_Success(t *testing.T) {
128127
{Key: toPtr(".appconfig.featureflag/Beta"), Value: &value1, ContentType: toPtr(featureFlagContentType)},
129128
{Key: toPtr(".appconfig.featureflag/Alpha"), Value: &value2, ContentType: toPtr(featureFlagContentType)},
130129
},
131-
pageETags: map[comparableSelector][]*azcore.ETag{},
130+
pageWatchers: map[comparableSelector][]settingWatcher{},
132131
}
133132

134133
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
@@ -1545,7 +1544,7 @@ func TestLoadFeatureFlags_TracingUpdated(t *testing.T) {
15451544
ContentType: toPtr(featureFlagContentType),
15461545
},
15471546
},
1548-
pageETags: map[comparableSelector][]*azcore.ETag{},
1547+
pageWatchers: map[comparableSelector][]settingWatcher{},
15491548
}
15501549

15511550
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
@@ -1639,7 +1638,7 @@ func TestLoadKeyValues_WithTagFilter(t *testing.T) {
16391638
},
16401639
},
16411640
},
1642-
pageETags: map[comparableSelector][]*azcore.ETag{},
1641+
pageWatchers: map[comparableSelector][]settingWatcher{},
16431642
}
16441643

16451644
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
@@ -1695,7 +1694,7 @@ func TestLoadKeyValues_WithMultipleTagFilters(t *testing.T) {
16951694
},
16961695
},
16971696
},
1698-
pageETags: map[comparableSelector][]*azcore.ETag{},
1697+
pageWatchers: map[comparableSelector][]settingWatcher{},
16991698
}
17001699

17011700
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package afd
5+
6+
import (
7+
"context"
8+
"math"
9+
"time"
10+
11+
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
12+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
13+
)
14+
15+
type EmptyTokenCredential struct{}
16+
17+
func (e *EmptyTokenCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
18+
return azcore.AccessToken{
19+
Token: "",
20+
ExpiresOn: time.Unix(math.MaxInt64, 0),
21+
}, nil
22+
}
23+
24+
func NewEmptyTokenCredential() azcore.TokenCredential {
25+
return &EmptyTokenCredential{}
26+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package afd
5+
6+
import (
7+
"net/http"
8+
9+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
10+
)
11+
12+
type AnonymousRequestPipelinePolicy struct{}
13+
14+
type RemoveSyncTokenPipelinePolicy struct{}
15+
16+
func NewAnonymousRequestPipelinePolicy() *AnonymousRequestPipelinePolicy {
17+
return &AnonymousRequestPipelinePolicy{}
18+
}
19+
20+
func NewRemoveSyncTokenPipelinePolicy() *RemoveSyncTokenPipelinePolicy {
21+
return &RemoveSyncTokenPipelinePolicy{}
22+
}
23+
24+
func (p *AnonymousRequestPipelinePolicy) Do(req *policy.Request) (*http.Response, error) {
25+
if req.Raw().Header.Get("Authorization") != "" {
26+
req.Raw().Header.Del("Authorization")
27+
}
28+
29+
return req.Next()
30+
}
31+
32+
func (p *RemoveSyncTokenPipelinePolicy) Do(req *policy.Request) (*http.Response, error) {
33+
if req.Raw().Header.Get("Sync-Token") != "" {
34+
req.Raw().Header.Del("Sync-Token")
35+
}
36+
37+
return req.Next()
38+
}

azureappconfiguration/internal/tracing/tracing.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
ReplicaCountKey = "ReplicaCount"
4545
LoadBalancingEnabledTag = "LB"
4646
SnapshotReferenceTag = "SnapshotRef"
47+
AFDUsedTag = "AFD"
4748

4849
// Feature flag usage tracing
4950
FMGoVerEnv = "MS_FEATURE_MANAGEMENT_GO_VERSION"
@@ -79,6 +80,7 @@ type Options struct {
7980
IsFailoverRequest bool
8081
ReplicaCount int
8182
IsLoadBalancingEnabled bool
83+
AfdUsed bool
8284
FeatureFlagTracing *FeatureFlagTracing
8385
FMVersion string
8486
}
@@ -149,6 +151,10 @@ func CreateCorrelationContextHeader(ctx context.Context, options Options) http.H
149151
features = append(features, SnapshotReferenceTag)
150152
}
151153

154+
if options.AfdUsed {
155+
features = append(features, AFDUsedTag)
156+
}
157+
152158
if len(features) > 0 {
153159
featureStr := FeaturesKey + "=" + strings.Join(features, DelimiterPlus)
154160
output = append(output, featureStr)

0 commit comments

Comments
 (0)