Skip to content

Commit

Permalink
On demand refresh support (#87)
Browse files Browse the repository at this point in the history
* on demand refresh support

* update

* update
  • Loading branch information
linglingye001 authored Feb 28, 2025
1 parent a39c7be commit 255d2ab
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 11 deletions.
22 changes: 11 additions & 11 deletions internal/controller/appconfigurationprovider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"azappconfig/provider/internal/loader"
"context"
"errors"
"maps"
"strconv"
"time"

Expand Down Expand Up @@ -51,6 +52,7 @@ type AzureAppConfigurationProviderReconciler struct {
type ReconciliationState struct {
Generation int64
ConfigMapResourceVersion *string
Annotations map[string]string
SentinelETags map[acpv1.Sentinel]*azcore.ETag
KeyValueETags map[acpv1.Selector][]*azcore.ETag
FeatureFlagETags map[acpv1.Selector][]*azcore.ETag
Expand Down Expand Up @@ -180,6 +182,7 @@ func (reconciler *AzureAppConfigurationProviderReconciler) Reconcile(ctx context
reconciler.ProvidersReconcileState[req.NamespacedName] = &ReconciliationState{
Generation: -1,
ConfigMapResourceVersion: nil,
Annotations: make(map[string]string),
SentinelETags: make(map[acpv1.Sentinel]*azcore.ETag),
KeyValueETags: make(map[acpv1.Selector][]*azcore.ETag),
FeatureFlagETags: make(map[acpv1.Selector][]*azcore.ETag),
Expand Down Expand Up @@ -389,17 +392,16 @@ func (reconciler *AzureAppConfigurationProviderReconciler) createOrUpdateConfigM
return reconcile.Result{Requeue: true, RequeueAfter: RequeueReconcileAfter}, err
}

if provider.Annotations == nil {
provider.Annotations = make(map[string]string)
}
provider.Annotations[LastReconcileTimeAnnotation] = metav1.Now().UTC().String()
annotations := make(map[string]string)
maps.Copy(annotations, provider.Annotations)
annotations[LastReconcileTimeAnnotation] = metav1.Now().UTC().String()
if len(settings.ConfigMapSettings) == 0 {
klog.V(3).Info("No configMap settings are fetched from Azure AppConfiguration")
}
operationResult, err := ctrl.CreateOrUpdate(ctx, reconciler.Client, configMapObj, func() error {
configMapObj.Data = settings.ConfigMapSettings
configMapObj.Labels = provider.Labels
configMapObj.Annotations = provider.Annotations
configMapObj.Annotations = annotations

return nil
})
Expand Down Expand Up @@ -427,10 +429,6 @@ func (reconciler *AzureAppConfigurationProviderReconciler) createOrUpdateSecrets
klog.V(3).Info("No secret settings are fetched from Azure AppConfiguration")
}

if provider.Annotations == nil {
provider.Annotations = make(map[string]string)
}

namespacedName := types.NamespacedName{
Name: provider.Name,
Namespace: provider.Namespace,
Expand Down Expand Up @@ -459,11 +457,13 @@ func (reconciler *AzureAppConfigurationProviderReconciler) createOrUpdateSecrets
return reconcile.Result{Requeue: true, RequeueAfter: RequeueReconcileAfter}, err
}

provider.Annotations[LastReconcileTimeAnnotation] = metav1.Now().UTC().String()
annotations := make(map[string]string)
maps.Copy(annotations, provider.Annotations)
annotations[LastReconcileTimeAnnotation] = metav1.Now().UTC().String()
operationResult, err := ctrl.CreateOrUpdate(ctx, reconciler.Client, secretObj, func() error {
secretObj.Data = secret.Data
secretObj.Labels = provider.Labels
secretObj.Annotations = provider.Annotations
secretObj.Annotations = annotations

return nil
})
Expand Down
78 changes: 78 additions & 0 deletions internal/controller/appconfigurationprovider_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,84 @@ var _ = Describe("AppConfiguationProvider controller", func() {
_ = k8sClient.Delete(ctx, configProvider)
})

It("Should on-demand refresh configMap", func() {
By("By updating the provider's annotations and trigger reconciliation")
mapResult := make(map[string]string)
mapResult["testKey"] = "testValue"
mapResult["testKey2"] = "testValue2"
mapResult["testKey3"] = "testValue3"

allSettings := &loader.TargetKeyValueSettings{
ConfigMapSettings: mapResult,
}

mockConfigurationSettings.EXPECT().CreateTargetSettings(gomock.Any(), gomock.Any()).Return(allSettings, nil)

ctx := context.Background()
providerName := "on-demand-refresh-appconfigurationprovider-1"
configMapName := "configmap-on-demand-refresh"
configProvider := &acpv1.AzureAppConfigurationProvider{
TypeMeta: metav1.TypeMeta{
APIVersion: "appconfig.kubernetes.config/v1",
Kind: "AzureAppConfigurationProvider",
},
ObjectMeta: metav1.ObjectMeta{
Name: providerName,
Namespace: ProviderNamespace,
Annotations: map[string]string{"foo": "fooValue"},
},
Spec: acpv1.AzureAppConfigurationProviderSpec{
Endpoint: &EndpointName,
Target: acpv1.ConfigurationGenerationParameters{
ConfigMapName: configMapName,
},
},
}
Expect(k8sClient.Create(ctx, configProvider)).Should(Succeed())
configmapLookupKey := types.NamespacedName{Name: configMapName, Namespace: ProviderNamespace}
configmap := &corev1.ConfigMap{}

Eventually(func() bool {
err := k8sClient.Get(ctx, configmapLookupKey, configmap)
return err == nil
}, timeout, interval).Should(BeTrue())

Expect(configmap.Name).Should(Equal(configMapName))
Expect(configmap.Namespace).Should(Equal(ProviderNamespace))
Expect(configmap.Data["testKey"]).Should(Equal("testValue"))
Expect(configmap.Data["testKey2"]).Should(Equal("testValue2"))
Expect(configmap.Data["testKey3"]).Should(Equal("testValue3"))

mapResult2 := make(map[string]string)
mapResult2["testKey"] = "newtestValue"
mapResult2["testKey2"] = "newtestValue2"
mapResult2["testKey3"] = "newtestValue3"

allSettings2 := &loader.TargetKeyValueSettings{
ConfigMapSettings: mapResult2,
}

mockConfigurationSettings.EXPECT().CreateTargetSettings(gomock.Any(), gomock.Any()).Return(allSettings2, nil)

_ = k8sClient.Get(ctx, types.NamespacedName{Name: providerName, Namespace: ProviderNamespace}, configProvider)
configProvider.ObjectMeta.Annotations["foo"] = "fooValue2"

Expect(k8sClient.Update(ctx, configProvider)).Should(Succeed())

time.Sleep(5 * time.Second)

Eventually(func() bool {
err := k8sClient.Get(ctx, configmapLookupKey, configmap)
return err == nil
}, timeout, interval).Should(BeTrue())

Expect(configmap.Data["testKey"]).Should(Equal("newtestValue"))
Expect(configmap.Data["testKey2"]).Should(Equal("newtestValue2"))
Expect(configmap.Data["testKey3"]).Should(Equal("newtestValue3"))

_ = k8sClient.Delete(ctx, configProvider)
})

It("Should refresh configMap", func() {
By("By sentinel value updated in Azure App Configuration")
mapResult := make(map[string]string)
Expand Down
19 changes: 19 additions & 0 deletions internal/controller/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ func (processor *AppConfigurationProviderProcessor) shouldReconcile(
return true
}

if annotationChanged(processor.ReconciliationState.Annotations, processor.Provider.Annotations) {
return true
}

if processor.ReconciliationState.ConfigMapResourceVersion == nil ||
*processor.ReconciliationState.ConfigMapResourceVersion != existingConfigMap.ResourceVersion {
// If the ConfigMap is removed or updated, we need to reconcile anyway
Expand All @@ -303,6 +307,7 @@ func (processor *AppConfigurationProviderProcessor) shouldReconcile(

func (processor *AppConfigurationProviderProcessor) Finish() (ctrl.Result, error) {
processor.ReconciliationState.Generation = processor.Provider.Generation
processor.ReconciliationState.Annotations = processor.Provider.Annotations

if processor.RefreshOptions.SecretSettingPopulated {
processor.ReconciliationState.ExistingK8sSecrets = processor.Settings.K8sSecrets
Expand Down Expand Up @@ -386,3 +391,17 @@ func (processor *AppConfigurationProviderProcessor) calculateRequeueAfterInterva

return requeueAfterInterval
}

func annotationChanged(oldAnnotations, newAnnotations map[string]string) bool {
if len(oldAnnotations) != len(newAnnotations) {
return true
}

for key, value := range newAnnotations {
if oldValue, ok := oldAnnotations[key]; !ok || value != oldValue {
return true
}
}

return false
}

0 comments on commit 255d2ab

Please sign in to comment.