Skip to content
Merged
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
28 changes: 13 additions & 15 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ module azappconfig/provider
go 1.26.0

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2 v2.2.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.0
github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2 v2.2.1-beta.1
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.5.0
github.com/golang/mock v1.6.0
github.com/onsi/gomega v1.39.1
golang.org/x/crypto v0.53.0
golang.org/x/sync v0.21.0
golang.org/x/crypto v0.55.0
golang.org/x/sync v0.22.0
k8s.io/apimachinery v0.36.2
k8s.io/client-go v0.36.2
sigs.k8s.io/controller-runtime v0.24.1
Expand All @@ -18,7 +18,7 @@ require (
require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
Expand All @@ -29,7 +29,6 @@ require (
github.com/go-openapi/swag/cmdutils v0.27.0 // indirect
github.com/go-openapi/swag/conv v0.27.0 // indirect
github.com/go-openapi/swag/fileutils v0.27.0 // indirect
github.com/go-openapi/swag/jsonname v0.27.0 // indirect
github.com/go-openapi/swag/jsonutils v0.27.0 // indirect
github.com/go-openapi/swag/loading v0.27.0 // indirect
github.com/go-openapi/swag/mangling v0.27.0 // indirect
Expand All @@ -39,7 +38,6 @@ require (
github.com/go-openapi/swag/yamlutils v0.27.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
Expand All @@ -49,8 +47,8 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/tools v0.45.0 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/tools v0.48.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect
Expand All @@ -76,14 +74,14 @@ require (
github.com/prometheus/common v0.69.0 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.12.0
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.38.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.41.0 // indirect
golang.org/x/time v0.15.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
Expand All @@ -97,5 +95,5 @@ require (
k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.7.0
software.sslmate.com/src/go-pkcs12 v0.7.3
)
113 changes: 31 additions & 82 deletions go.sum

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions internal/controller/appconfigurationprovider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type ReconciliationState struct {
SentinelETags map[acpv1.Sentinel]*azcore.ETag
KeyValueETags map[acpv1.ComparableSelector][]*azcore.ETag
FeatureFlagETags map[acpv1.ComparableSelector][]*azcore.ETag
EnhancedFeatureFlagETags map[acpv1.ComparableSelector][]*azcore.ETag
ExistingK8sSecrets map[string]*loader.TargetK8sSecretMetadata
NextKeyValueRefreshReconcileTime metav1.Time
NextSecretReferenceRefreshReconcileTime metav1.Time
Expand Down Expand Up @@ -149,6 +150,7 @@ func (reconciler *AzureAppConfigurationProviderReconciler) Reconcile(ctx context
SentinelETags: make(map[acpv1.Sentinel]*azcore.ETag),
KeyValueETags: make(map[acpv1.ComparableSelector][]*azcore.ETag),
FeatureFlagETags: make(map[acpv1.ComparableSelector][]*azcore.ETag),
EnhancedFeatureFlagETags: make(map[acpv1.ComparableSelector][]*azcore.ETag),
ExistingK8sSecrets: make(map[string]*loader.TargetK8sSecretMetadata),
ClientManager: nil,
}
Expand Down
61 changes: 37 additions & 24 deletions internal/controller/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,20 @@ type AppConfigurationProviderProcessor struct {
}

type RefreshOptions struct {
keyValueRefreshEnabled bool
secretReferenceRefreshEnabled bool
secretReferenceRefreshNeeded bool
featureFlagRefreshEnabled bool
featureFlagRefreshNeeded bool
ConfigMapSettingPopulated bool
SecretSettingPopulated bool
sentinelChanged bool
keyValuePageETagsChanged bool
updatedSentinelETags map[acpv1.Sentinel]*azcore.ETag
updatedKeyValueETags map[acpv1.ComparableSelector][]*azcore.ETag
updatedFeatureFlagETags map[acpv1.ComparableSelector][]*azcore.ETag
keyValueRefreshEnabled bool
secretReferenceRefreshEnabled bool
secretReferenceRefreshNeeded bool
featureFlagRefreshEnabled bool
featureFlagRefreshNeeded bool
enhancedFeatureFlagRefreshNeeded bool
ConfigMapSettingPopulated bool
SecretSettingPopulated bool
sentinelChanged bool
keyValuePageETagsChanged bool
updatedSentinelETags map[acpv1.Sentinel]*azcore.ETag
updatedKeyValueETags map[acpv1.ComparableSelector][]*azcore.ETag
updatedFeatureFlagETags map[acpv1.ComparableSelector][]*azcore.ETag
updatedEnhancedFeatureFlagETags map[acpv1.ComparableSelector][]*azcore.ETag
}

func (processor *AppConfigurationProviderProcessor) PopulateSettings(existingConfigMap *corev1.ConfigMap, existingSecrets map[string]corev1.Secret) error {
Expand Down Expand Up @@ -75,6 +77,7 @@ func (processor *AppConfigurationProviderProcessor) processFullReconciliation()
processor.RefreshOptions.ConfigMapSettingPopulated = true
processor.RefreshOptions.updatedKeyValueETags = updatedSettings.KeyValueETags
processor.RefreshOptions.updatedFeatureFlagETags = updatedSettings.FeatureFlagETags
processor.RefreshOptions.updatedEnhancedFeatureFlagETags = updatedSettings.EnhancedFeatureFlagETags
processor.RefreshOptions.updatedSentinelETags = updatedSettings.SentinelETags
if processor.Provider.Spec.Secret != nil {
processor.RefreshOptions.SecretSettingPopulated = true
Expand Down Expand Up @@ -112,7 +115,11 @@ func (processor *AppConfigurationProviderProcessor) processFeatureFlagRefresh(ex
return err
}

if !processor.RefreshOptions.featureFlagRefreshNeeded {
if processor.RefreshOptions.enhancedFeatureFlagRefreshNeeded, err = (processor.Retriever).CheckIfEnhancedFeatureFlagsChanged(processor.Context, reconcileState.EnhancedFeatureFlagETags); err != nil {
return err
}

Comment thread
linglingye001 marked this conversation as resolved.
if !(processor.RefreshOptions.featureFlagRefreshNeeded || processor.RefreshOptions.enhancedFeatureFlagRefreshNeeded) {
reconcileState.NextFeatureFlagRefreshReconcileTime = nextFeatureFlagRefreshReconcileTime
return nil
}
Expand All @@ -123,6 +130,7 @@ func (processor *AppConfigurationProviderProcessor) processFeatureFlagRefresh(ex
}

processor.RefreshOptions.updatedFeatureFlagETags = featureFlagRefreshedSettings.FeatureFlagETags
processor.RefreshOptions.updatedEnhancedFeatureFlagETags = featureFlagRefreshedSettings.EnhancedFeatureFlagETags
processor.Settings = featureFlagRefreshedSettings
processor.RefreshOptions.ConfigMapSettingPopulated = true
// Update next refresh time only if settings updated successfully
Expand Down Expand Up @@ -323,6 +331,10 @@ func (processor *AppConfigurationProviderProcessor) Finish() (ctrl.Result, error
processor.ReconciliationState.FeatureFlagETags = processor.RefreshOptions.updatedFeatureFlagETags
}

if processor.RefreshOptions.updatedEnhancedFeatureFlagETags != nil {
processor.ReconciliationState.EnhancedFeatureFlagETags = processor.RefreshOptions.updatedEnhancedFeatureFlagETags
}

if processor.ShouldReconcile {
processor.ReconciliationState.SentinelETags = processor.RefreshOptions.updatedSentinelETags
}
Expand All @@ -348,7 +360,7 @@ func (processor *AppConfigurationProviderProcessor) Finish() (ctrl.Result, error
processor.Provider.Status.RefreshStatus.LastKeyVaultReferenceRefreshTime = processor.CurrentTime
}
// Update provider last feature flag refresh time
if processor.RefreshOptions.featureFlagRefreshNeeded {
if processor.RefreshOptions.featureFlagRefreshNeeded || processor.RefreshOptions.enhancedFeatureFlagRefreshNeeded {
processor.Provider.Status.RefreshStatus.LastFeatureFlagRefreshTime = processor.CurrentTime
}
// At least one dynamic feature is enabled, requeueAfterInterval need be recalculated
Expand All @@ -361,16 +373,17 @@ func (processor *AppConfigurationProviderProcessor) Finish() (ctrl.Result, error

func NewRefreshOptions() *RefreshOptions {
return &RefreshOptions{
keyValueRefreshEnabled: false,
secretReferenceRefreshEnabled: false,
secretReferenceRefreshNeeded: false,
featureFlagRefreshEnabled: false,
featureFlagRefreshNeeded: false,
ConfigMapSettingPopulated: false,
SecretSettingPopulated: false,
sentinelChanged: false,
keyValuePageETagsChanged: false,
updatedSentinelETags: make(map[acpv1.Sentinel]*azcore.ETag),
keyValueRefreshEnabled: false,
secretReferenceRefreshEnabled: false,
secretReferenceRefreshNeeded: false,
featureFlagRefreshEnabled: false,
featureFlagRefreshNeeded: false,
enhancedFeatureFlagRefreshNeeded: false,
ConfigMapSettingPopulated: false,
SecretSettingPopulated: false,
sentinelChanged: false,
keyValuePageETagsChanged: false,
updatedSentinelETags: make(map[acpv1.Sentinel]*azcore.ETag),
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/controller/processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ var _ = Describe("AppConfiguationProvider processor", func() {
BeforeEach(func() {
mockCtrl = gomock.NewController(GinkgoT())
mockConfigurationSettings = mocks.NewMockConfigurationSettingsRetriever(mockCtrl)
// The enhanced feature flag endpoint is checked whenever the feature flag page ETags
// are unchanged; default to reporting no change so existing scenarios are unaffected.
mockConfigurationSettings.EXPECT().CheckIfEnhancedFeatureFlagsChanged(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes()
})

AfterEach(func() {
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ var _ = BeforeSuite(func() {

mockCtrl = gomock.NewController(GinkgoT())
mockConfigurationSettings = mocks.NewMockConfigurationSettingsRetriever(mockCtrl)
// The dedicated feature flag endpoint is checked whenever the classic feature flag page ETags
Comment thread
linglingye001 marked this conversation as resolved.
// are unchanged; default to reporting no change so existing scenarios are unaffected.
mockConfigurationSettings.EXPECT().CheckIfEnhancedFeatureFlagsChanged(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes()

err = (&AzureAppConfigurationProviderReconciler{
Client: k8sManager.GetClient(),
Expand Down
92 changes: 92 additions & 0 deletions internal/loader/app_configuration_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package loader

import (
"context"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
azappconfig "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2"
)

// AppConfigurationClient abstracts the Azure App Configuration operations used by the provider.
type AppConfigurationClient interface {
// Key-value configuration operations.
NewListSettingsPager(selector azappconfig.SettingSelector, options *azappconfig.ListSettingsOptions) *runtime.Pager[azappconfig.ListSettingsPageResponse]
GetSetting(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error)
GetSnapshot(ctx context.Context, snapshotName string, options *azappconfig.GetSnapshotOptions) (azappconfig.GetSnapshotResponse, error)
NewListSettingsForSnapshotPager(snapshotName string, options *azappconfig.ListSettingsForSnapshotOptions) *runtime.Pager[azappconfig.ListSettingsForSnapshotResponse]

// Feature flag operations served by the enhanced feature flag endpoint.
NewListFeatureFlagsPager(selector azappconfig.FeatureFlagSelector, options *azappconfig.ListFeatureFlagsOptions) *runtime.Pager[azappconfig.ListFeatureFlagsPageResponse]
}

type appConfigurationClient struct {
configurationClient *azappconfig.Client
featureFlagClient *azappconfig.FeatureFlagClient
}

func NewAppConfigurationClient(endpoint string, credential azcore.TokenCredential, options *azappconfig.ClientOptions) (AppConfigurationClient, error) {
configurationClient, err := azappconfig.NewClient(endpoint, credential, options)
if err != nil {
return nil, err
}

featureFlagClient, err := azappconfig.NewFeatureFlagClient(endpoint, credential, featureFlagClientOptions(options))
if err != nil {
return nil, err
}

return &appConfigurationClient{
configurationClient: configurationClient,
featureFlagClient: featureFlagClient,
}, nil
}

func NewAppConfigurationClientFromConnectionString(connectionString string, options *azappconfig.ClientOptions) (AppConfigurationClient, error) {
configurationClient, err := azappconfig.NewClientFromConnectionString(connectionString, options)
if err != nil {
return nil, err
}

featureFlagClient, err := azappconfig.NewFeatureFlagClientFromConnectionString(connectionString, featureFlagClientOptions(options))
if err != nil {
return nil, err
}

return &appConfigurationClient{
configurationClient: configurationClient,
featureFlagClient: featureFlagClient,
}, nil
}

// featureFlagClientOptions mirrors the configuration client options onto feature flag client options
func featureFlagClientOptions(options *azappconfig.ClientOptions) *azappconfig.FeatureFlagClientOptions {
if options == nil {
return nil
}

return &azappconfig.FeatureFlagClientOptions{ClientOptions: options.ClientOptions}
}

func (c *appConfigurationClient) NewListSettingsPager(selector azappconfig.SettingSelector, options *azappconfig.ListSettingsOptions) *runtime.Pager[azappconfig.ListSettingsPageResponse] {
return c.configurationClient.NewListSettingsPager(selector, options)
}

func (c *appConfigurationClient) GetSetting(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) {
return c.configurationClient.GetSetting(ctx, key, options)
}

func (c *appConfigurationClient) GetSnapshot(ctx context.Context, snapshotName string, options *azappconfig.GetSnapshotOptions) (azappconfig.GetSnapshotResponse, error) {
return c.configurationClient.GetSnapshot(ctx, snapshotName, options)
}

func (c *appConfigurationClient) NewListSettingsForSnapshotPager(snapshotName string, options *azappconfig.ListSettingsForSnapshotOptions) *runtime.Pager[azappconfig.ListSettingsForSnapshotResponse] {
return c.configurationClient.NewListSettingsForSnapshotPager(snapshotName, options)
}

func (c *appConfigurationClient) NewListFeatureFlagsPager(selector azappconfig.FeatureFlagSelector, options *azappconfig.ListFeatureFlagsOptions) *runtime.Pager[azappconfig.ListFeatureFlagsPageResponse] {
return c.featureFlagClient.NewListFeatureFlagsPager(selector, options)
}
Loading
Loading