Skip to content

Commit a4d86d9

Browse files
Merge pull request #59 from Azure/release/v1.4.0
Merge release/v1.4.0 to main 12/04
2 parents bafb4b1 + e2eb2ce commit a4d86d9

14 files changed

Lines changed: 435 additions & 52 deletions

azureappconfiguration/azureappconfiguration.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/tracing"
3434
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/tree"
3535
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
36-
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig"
36+
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2"
3737
decoder "github.com/go-viper/mapstructure/v2"
3838
"golang.org/x/sync/errgroup"
3939
)
@@ -55,8 +55,8 @@ type AzureAppConfiguration struct {
5555
// Settings used for refresh scenarios
5656
sentinelETags map[WatchedSetting]*azcore.ETag
5757
watchAll bool
58-
kvETags map[Selector][]*azcore.ETag
59-
ffETags map[Selector][]*azcore.ETag
58+
kvETags map[comparableSelector][]*azcore.ETag
59+
ffETags map[comparableSelector][]*azcore.ETag
6060
keyVaultRefs map[string]string // unversioned Key Vault references
6161
kvRefreshTimer refresh.Condition
6262
secretRefreshTimer refresh.Condition
@@ -121,7 +121,7 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
121121
azappcfg.kvRefreshTimer = refresh.NewTimer(options.RefreshOptions.Interval)
122122
azappcfg.watchedSettings = normalizedWatchedSettings(options.RefreshOptions.WatchedSettings)
123123
azappcfg.sentinelETags = make(map[WatchedSetting]*azcore.ETag)
124-
azappcfg.kvETags = make(map[Selector][]*azcore.ETag)
124+
azappcfg.kvETags = make(map[comparableSelector][]*azcore.ETag)
125125
if len(options.RefreshOptions.WatchedSettings) == 0 {
126126
azappcfg.watchAll = true
127127
}
@@ -137,7 +137,7 @@ func Load(ctx context.Context, authentication AuthenticationOptions, options *Op
137137
azappcfg.ffSelectors = getFeatureFlagSelectors(deduplicateSelectors(options.FeatureFlagOptions.Selectors))
138138
if options.FeatureFlagOptions.RefreshOptions.Enabled {
139139
azappcfg.ffRefreshTimer = refresh.NewTimer(options.FeatureFlagOptions.RefreshOptions.Interval)
140-
azappcfg.ffETags = make(map[Selector][]*azcore.ETag)
140+
azappcfg.ffETags = make(map[comparableSelector][]*azcore.ETag)
141141
}
142142
}
143143

@@ -759,7 +759,7 @@ func deduplicateSelectors(selectors []Selector) []Selector {
759759
}
760760

761761
// Create a map to track unique selectors
762-
seen := make(map[Selector]struct{})
762+
seen := make(map[comparableSelector]struct{})
763763
var result []Selector
764764

765765
// Process the selectors in reverse order to maintain the behavior
@@ -771,8 +771,9 @@ func deduplicateSelectors(selectors []Selector) []Selector {
771771
}
772772

773773
// Check if we've seen this selector before
774-
if _, exists := seen[selectors[i]]; !exists {
775-
seen[selectors[i]] = struct{}{}
774+
key := selectors[i].comparableKey()
775+
if _, exists := seen[key]; !exists {
776+
seen[key] = struct{}{}
776777
result = append(result, selectors[i])
777778
}
778779
}

azureappconfiguration/azureappconfiguration_test.go

Lines changed: 203 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/fm"
1717
"github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration/internal/tracing"
1818
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
19-
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig"
19+
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2"
2020
"github.com/stretchr/testify/assert"
2121
"github.com/stretchr/testify/mock"
2222
)
@@ -128,7 +128,7 @@ func TestLoadFeatureFlags_Success(t *testing.T) {
128128
{Key: toPtr(".appconfig.featureflag/Beta"), Value: &value1, ContentType: toPtr(featureFlagContentType)},
129129
{Key: toPtr(".appconfig.featureflag/Alpha"), Value: &value2, ContentType: toPtr(featureFlagContentType)},
130130
},
131-
pageETags: map[Selector][]*azcore.ETag{},
131+
pageETags: map[comparableSelector][]*azcore.ETag{},
132132
}
133133

134134
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
@@ -1545,7 +1545,7 @@ func TestLoadFeatureFlags_TracingUpdated(t *testing.T) {
15451545
ContentType: toPtr(featureFlagContentType),
15461546
},
15471547
},
1548-
pageETags: map[Selector][]*azcore.ETag{},
1548+
pageETags: map[comparableSelector][]*azcore.ETag{},
15491549
}
15501550

15511551
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
@@ -1601,3 +1601,203 @@ func TestLoadFeatureFlags_TracingUpdated(t *testing.T) {
16011601
// Verify max variants is included
16021602
assert.Contains(t, correlationCtx, tracing.FFMaxVariantsKey+"=3")
16031603
}
1604+
1605+
func TestLoadKeyValues_WithTagFilter(t *testing.T) {
1606+
ctx := context.Background()
1607+
mockClient := new(mockSettingsClient)
1608+
1609+
// Create mock settings with different tags
1610+
value1 := "value1"
1611+
value3 := "value3"
1612+
value4 := "value4"
1613+
1614+
mockResponse := &settingsResponse{
1615+
settings: []azappconfig.Setting{
1616+
{
1617+
Key: toPtr("app:key1"),
1618+
Value: &value1,
1619+
Tags: map[string]*string{
1620+
"env": toPtr("production"),
1621+
"team": toPtr("backend"),
1622+
},
1623+
},
1624+
{
1625+
Key: toPtr("app:key3"),
1626+
Value: &value3,
1627+
Tags: map[string]*string{
1628+
"env": toPtr("production"),
1629+
"team": toPtr("frontend"),
1630+
},
1631+
},
1632+
{
1633+
Key: toPtr("app:key4"),
1634+
Value: &value4,
1635+
Tags: map[string]*string{
1636+
"env": toPtr("production"),
1637+
"team": toPtr("backend"),
1638+
"feature": toPtr("new"),
1639+
},
1640+
},
1641+
},
1642+
pageETags: map[comparableSelector][]*azcore.ETag{},
1643+
}
1644+
1645+
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
1646+
1647+
// Test with single tag filter
1648+
azappcfg := &AzureAppConfiguration{
1649+
clientManager: &configurationClientManager{
1650+
staticClient: &configurationClientWrapper{client: &azappconfig.Client{}},
1651+
},
1652+
kvSelectors: []Selector{
1653+
{
1654+
KeyFilter: "*",
1655+
TagFilters: []string{"env=production"},
1656+
},
1657+
},
1658+
keyValues: make(map[string]any),
1659+
}
1660+
1661+
err := azappcfg.loadKeyValues(ctx, mockClient)
1662+
assert.NoError(t, err)
1663+
1664+
// Should load keys with env=production tag (key1, key3, key4)
1665+
assert.Equal(t, &value1, azappcfg.keyValues["app:key1"])
1666+
assert.Equal(t, &value3, azappcfg.keyValues["app:key3"])
1667+
assert.Equal(t, &value4, azappcfg.keyValues["app:key4"])
1668+
assert.NotContains(t, azappcfg.keyValues, "app:key2") // staging env, should be filtered out
1669+
}
1670+
1671+
func TestLoadKeyValues_WithMultipleTagFilters(t *testing.T) {
1672+
ctx := context.Background()
1673+
mockClient := new(mockSettingsClient)
1674+
1675+
value1 := "value1"
1676+
value4 := "value4"
1677+
1678+
mockResponse := &settingsResponse{
1679+
settings: []azappconfig.Setting{
1680+
{
1681+
Key: toPtr("app:key1"),
1682+
Value: &value1,
1683+
Tags: map[string]*string{
1684+
"env": toPtr("production"),
1685+
"team": toPtr("backend"),
1686+
},
1687+
},
1688+
{
1689+
Key: toPtr("app:key4"),
1690+
Value: &value4,
1691+
Tags: map[string]*string{
1692+
"env": toPtr("production"),
1693+
"team": toPtr("backend"),
1694+
"feature": toPtr("new"),
1695+
},
1696+
},
1697+
},
1698+
pageETags: map[comparableSelector][]*azcore.ETag{},
1699+
}
1700+
1701+
mockClient.On("getSettings", ctx).Return(mockResponse, nil)
1702+
1703+
// Test with multiple tag filters (must match ALL)
1704+
azappcfg := &AzureAppConfiguration{
1705+
clientManager: &configurationClientManager{
1706+
staticClient: &configurationClientWrapper{client: &azappconfig.Client{}},
1707+
},
1708+
kvSelectors: []Selector{
1709+
{
1710+
KeyFilter: "*",
1711+
TagFilters: []string{"env=production", "team=backend"},
1712+
},
1713+
},
1714+
keyValues: make(map[string]any),
1715+
}
1716+
1717+
err := azappcfg.loadKeyValues(ctx, mockClient)
1718+
assert.NoError(t, err)
1719+
1720+
// Should load only keys that match BOTH env=production AND team=backend (key1, key4)
1721+
assert.Equal(t, &value1, azappcfg.keyValues["app:key1"])
1722+
assert.Equal(t, &value4, azappcfg.keyValues["app:key4"])
1723+
}
1724+
1725+
func TestSelectorComparableKey_WithTagFilter(t *testing.T) {
1726+
// Test that selectors with same TagFilter (but different order) produce the same comparable key
1727+
selector1 := Selector{
1728+
KeyFilter: "app*",
1729+
LabelFilter: "prod",
1730+
TagFilters: []string{"env=production", "team=backend"},
1731+
}
1732+
1733+
selector2 := Selector{
1734+
KeyFilter: "app*",
1735+
LabelFilter: "prod",
1736+
TagFilters: []string{"team=backend", "env=production"}, // Different order
1737+
}
1738+
1739+
key1 := selector1.comparableKey()
1740+
key2 := selector2.comparableKey()
1741+
1742+
// Should produce the same comparable key due to sorting
1743+
assert.Equal(t, key1, key2)
1744+
assert.Equal(t, `["env=production","team=backend"]`, key1.TagFilters)
1745+
assert.Equal(t, `["env=production","team=backend"]`, key2.TagFilters)
1746+
}
1747+
1748+
func TestSelectorComparableKey_WithSpecialCharacters(t *testing.T) {
1749+
// Test that selectors handle special characters in tag values correctly
1750+
selector := Selector{
1751+
KeyFilter: "app*",
1752+
LabelFilter: "prod",
1753+
TagFilters: []string{
1754+
`env=prod,staging`, // Comma in value
1755+
`description="test,with,quotes"`, // Quotes and commas
1756+
`path=c:\windows\system32`, // Backslashes
1757+
`json={"key":"value"}`, // JSON in value
1758+
},
1759+
}
1760+
1761+
key := selector.comparableKey()
1762+
1763+
// Verify JSON encoding handles all special characters properly
1764+
expected := `["description=\"test,with,quotes\"","env=prod,staging","json={\"key\":\"value\"}","path=c:\\windows\\system32"]`
1765+
assert.Equal(t, expected, key.TagFilters)
1766+
}
1767+
1768+
func TestSelectorComparableKey_WithEmptyAndNilTagFilter(t *testing.T) {
1769+
// Test empty TagFilter
1770+
selector1 := Selector{
1771+
KeyFilter: "app*",
1772+
LabelFilter: "prod",
1773+
TagFilters: []string{},
1774+
}
1775+
1776+
key1 := selector1.comparableKey()
1777+
assert.Equal(t, "", key1.TagFilters)
1778+
1779+
// Test nil TagFilter (should be handled the same as empty)
1780+
selector2 := Selector{
1781+
KeyFilter: "app*",
1782+
LabelFilter: "prod",
1783+
TagFilters: nil,
1784+
}
1785+
1786+
key2 := selector2.comparableKey()
1787+
assert.Equal(t, "", key2.TagFilters)
1788+
}
1789+
1790+
func TestSelectorComparableKey_Deterministic(t *testing.T) {
1791+
// Test that the same selector always produces the same key
1792+
selector := Selector{
1793+
KeyFilter: "app*",
1794+
LabelFilter: "prod",
1795+
TagFilters: []string{"z=last", "a=first", "m=middle"},
1796+
}
1797+
1798+
key1 := selector.comparableKey()
1799+
key2 := selector.comparableKey()
1800+
1801+
assert.Equal(t, key1, key2)
1802+
assert.Equal(t, `["a=first","m=middle","z=last"]`, key1.TagFilters) // Should be sorted
1803+
}

azureappconfiguration/client_manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717

1818
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1919
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
20-
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig"
20+
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2"
2121
)
2222

2323
// configurationClientManager handles creation and management of app configuration clients

azureappconfiguration/failover_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
17-
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig"
17+
"github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2"
1818
"github.com/stretchr/testify/assert"
1919
"github.com/stretchr/testify/mock"
2020
)

azureappconfiguration/go.mod

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration
22

33
go 1.24.0
44

5-
require github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0
5+
require github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2 v2.0.0
66

77
require (
88
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
@@ -13,12 +13,12 @@ require (
1313
)
1414

1515
require (
16-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2
16+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0
1717
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
1818
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0
1919
github.com/go-viper/mapstructure/v2 v2.4.0
20-
github.com/stretchr/testify v1.10.0
21-
golang.org/x/net v0.42.0 // indirect
22-
golang.org/x/sync v0.16.0
23-
golang.org/x/text v0.27.0 // indirect
20+
github.com/stretchr/testify v1.11.1
21+
golang.org/x/net v0.43.0 // indirect
22+
golang.org/x/sync v0.18.0
23+
golang.org/x/text v0.31.0 // indirect
2424
)

azureappconfiguration/go.sum

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
2-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
1+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
2+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
33
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
44
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
5-
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0 h1:uU4FujKFQAz31AbWOO3INV9qfIanHeIUSsGhRlcJJmg=
6-
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0/go.mod h1:qr3M3Oy6V98VR0c5tCHKUpaeJTRQh6KYzJewRtFWqfc=
5+
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2 v2.0.0 h1:K7LqZL3VW+DElZhW+5tY/cp2RRFrB3W45WUG/9fhhls=
6+
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2 v2.0.0/go.mod h1:4IPby+BYf0rPMnMur/mNtowysFd4NoEW5U1vhrkhARA=
77
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
88
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
99
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU=
@@ -34,18 +34,18 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
3434
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
3535
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
3636
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
37-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
38-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
39-
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
40-
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
41-
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
42-
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
43-
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
44-
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
45-
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
46-
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
47-
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
48-
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
37+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
38+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
39+
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
40+
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
41+
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
42+
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
43+
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
44+
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
45+
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
46+
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
47+
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
48+
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
4949
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5050
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
5151
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

azureappconfiguration/internal/tracing/tracing.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ const (
4545
LoadBalancingEnabledTag = "LB"
4646

4747
// Feature flag usage tracing
48-
FMGoVerEnv = "MS_FEATURE_MANAGEMENT_GO_VERSION"
49-
FMGoVerKey = "FMGoVer"
48+
FMGoVerEnv = "MS_FEATURE_MANAGEMENT_GO_VERSION"
49+
FMGoVerKey = "FMGoVer"
5050
FeatureFilterTypeKey = "Filter"
5151
CustomFilterKey = "CSTM"
5252
TimeWindowFilterKey = "TIME"

0 commit comments

Comments
 (0)