From aafba3c690557f053ba69679aac40bcb4a1bf6c6 Mon Sep 17 00:00:00 2001 From: MengjiaLiang Date: Fri, 14 Mar 2025 18:41:53 -0700 Subject: [PATCH 1/4] Feat: support tracking binaryData in CM and stringData in Secret Signed-off-by: MengjiaLiang --- pkg/canary/config_tracker.go | 38 +++++++++++++++++--- pkg/canary/config_tracker_test.go | 16 +++++++++ pkg/canary/deployment_fixture_test.go | 51 +++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/pkg/canary/config_tracker.go b/pkg/canary/config_tracker.go index c821312e6..9cd927ba3 100644 --- a/pkg/canary/config_tracker.go +++ b/pkg/canary/config_tracker.go @@ -21,6 +21,7 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "maps" "strings" "go.uber.org/zap" @@ -88,7 +89,7 @@ func (ct *ConfigTracker) getRefFromConfigMap(name string, namespace string) (*Co return &ConfigRef{ Name: config.Name, Type: ConfigRefMap, - Checksum: checksum(config.Data), + Checksum: checksum(ct.getFullDataFromConfigMap(*config)), }, nil } @@ -116,10 +117,35 @@ func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*Confi return &ConfigRef{ Name: secret.Name, Type: ConfigRefSecret, - Checksum: checksum(secret.Data), + Checksum: checksum(ct.getFullDataFromSecret(*secret)), }, nil } +// getFullDataFromConfigMap fetches both data and binaryData in the configmap +func (ct *ConfigTracker) getFullDataFromConfigMap(config corev1.ConfigMap) map[string]string { + fullData := make(map[string]string) + maps.Copy(fullData, config.Data) + + for k, v := range config.BinaryData { + fullData[k] = string(v) + + } + + return fullData +} + +// getFullDataFromSecret fetches both data and stringData in the secret +func (ct *ConfigTracker) getFullDataFromSecret(secret corev1.Secret) map[string]string { + fullData := make(map[string]string) + maps.Copy(fullData, secret.StringData) + + for k, v := range secret.Data { + fullData[k] = string(v) + } + + return fullData +} + // GetTargetConfigs scans the target deployment for Kubernetes ConfigMaps and Secrets // and returns a list of config references func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]ConfigRef, error) { @@ -332,7 +358,8 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str Labels: labels, OwnerReferences: ownerReferences, }, - Data: config.Data, + Data: config.Data, + BinaryData: config.BinaryData, } // update or insert primary ConfigMap @@ -385,8 +412,9 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str Labels: labels, OwnerReferences: ownerReferences, }, - Type: secret.Type, - Data: secret.Data, + Type: secret.Type, + Data: secret.Data, + StringData: secret.StringData, } // update or insert primary Secret diff --git a/pkg/canary/config_tracker_test.go b/pkg/canary/config_tracker_test.go index edb88d1e5..57d46f041 100644 --- a/pkg/canary/config_tracker_test.go +++ b/pkg/canary/config_tracker_test.go @@ -60,26 +60,31 @@ func TestConfigTracker_ConfigMaps(t *testing.T) { configPrimaryInit, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-init-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryInit.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryInit.BinaryData["color_binary"]) } configPrimaryInitEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-init-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryInitEnv.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryInitEnv.BinaryData["color_binary"]) } configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimary.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimary.BinaryData["color_binary"]) } configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryEnv.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryEnv.BinaryData["color_binary"]) } configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-vol-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryVol.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryVol.BinaryData["color_binary"]) } configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name @@ -136,26 +141,31 @@ func TestConfigTracker_ConfigMaps(t *testing.T) { configPrimaryInit, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-init-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryInit.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryInit.BinaryData["color_binary"]) } configPrimaryInitEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-init-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryInitEnv.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryInitEnv.BinaryData["color_binary"]) } configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimary.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimary.BinaryData["color_binary"]) } configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryEnv.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryEnv.BinaryData["color_binary"]) } configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-vol-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryVol.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryVol.BinaryData["color_binary"]) } configProjectedName := daePrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name @@ -164,6 +174,7 @@ func TestConfigTracker_ConfigMaps(t *testing.T) { configPrimaryProjected, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-vol-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMapProjected.Data["color"], configPrimaryProjected.Data["color"]) + assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryProjected.BinaryData["color_binary"]) } _, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-tracker-enabled", metav1.GetOptions{}) @@ -213,26 +224,31 @@ func TestConfigTracker_Secrets(t *testing.T) { secretPrimaryInit, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-init-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryInit.Data["apiKey"])) + assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryInit.StringData["apiKey_string"])) } secretPrimaryInitEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-init-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryInitEnv.Data["apiKey"])) + assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryInitEnv.StringData["apiKey_string"])) } secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimary.Data["apiKey"])) + assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimary.StringData["apiKey_string"])) } secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryEnv.Data["apiKey"])) + assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryEnv.StringData["apiKey_string"])) } secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-vol-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryVol.Data["apiKey"])) + assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryVol.StringData["apiKey_string"])) } secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name diff --git a/pkg/canary/deployment_fixture_test.go b/pkg/canary/deployment_fixture_test.go index 42688b9ed..b5b6714d4 100644 --- a/pkg/canary/deployment_fixture_test.go +++ b/pkg/canary/deployment_fixture_test.go @@ -143,6 +143,9 @@ func newDeploymentControllerTestConfigMap() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -157,6 +160,9 @@ func newDeploymentControllerTestConfigMapV2() *corev1.ConfigMap { "color": "blue", "output": "console", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("Ymx1ZAo="), + }, } } @@ -170,6 +176,9 @@ func newDeploymentControllerTestConfigMapInit() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -183,6 +192,9 @@ func newDeploymentControllerTestConfigMapInitEnv() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -196,6 +208,9 @@ func newDeploymentControllerTestConfigProjected() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -209,6 +224,9 @@ func newDeploymentControllerTestConfigMapEnv() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -227,6 +245,9 @@ func newDeploymentControllerTestConfigMapTrackerEnabled() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -245,6 +266,9 @@ func newDeploymentControllerTestConfigMapTrackerDisabled() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -258,6 +282,9 @@ func newDeploymentControllerTestConfigMapVol() *corev1.ConfigMap { Data: map[string]string{ "color": "red", }, + BinaryData: map[string][]byte{ + "color_binary": []byte("cmVkCg=="), + }, } } @@ -272,6 +299,9 @@ func newDeploymentControllerTestSecret() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } @@ -286,6 +316,9 @@ func newDeploymentControllerTestSecretProjected() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } @@ -300,6 +333,9 @@ func newDeploymentControllerTestSecretEnv() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } @@ -314,6 +350,9 @@ func newDeploymentControllerTestSecretVol() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } @@ -333,6 +372,9 @@ func newDeploymentControllerTestSecretTrackerEnabled() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } @@ -352,6 +394,9 @@ func newDeploymentControllerTestSecretTrackerDisabled() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } @@ -366,6 +411,9 @@ func newDeploymentControllerTestSecretInit() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } @@ -380,6 +428,9 @@ func newDeploymentControllerTestSecretInitEnv() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, + StringData: map[string]string{ + "apiKey_string": "test", + }, } } From beef790dbee1223795ca062cd0eeaca00bbcd76a Mon Sep 17 00:00:00 2001 From: Arman Babaei <292arma@gmail.com> Date: Mon, 8 Jun 2026 15:29:42 +0330 Subject: [PATCH 2/4] add feature flag for configmap binary data tracking Signed-off-by: Arman Babaei <292arma@gmail.com> --- cmd/flagger/main.go | 73 ++++++++++++++------------- pkg/canary/config_tracker.go | 51 ++++++++----------- pkg/canary/config_tracker_test.go | 10 ---- pkg/canary/deployment_fixture_test.go | 24 --------- 4 files changed, 59 insertions(+), 99 deletions(-) diff --git a/cmd/flagger/main.go b/cmd/flagger/main.go index 2031c506f..1239104e1 100644 --- a/cmd/flagger/main.go +++ b/cmd/flagger/main.go @@ -56,38 +56,39 @@ import ( ) var ( - masterURL string - kubeconfig string - kubeconfigQPS int - kubeconfigBurst int - metricsServer string - controlLoopInterval time.Duration - logLevel string - port string - msteamsURL string - msteamsProxyURL string - includeLabelPrefix string - slackURL string - slackToken string - slackProxyURL string - slackUser string - slackChannel string - eventWebhook string - threadiness int - zapReplaceGlobals bool - zapEncoding string - namespace string - meshProvider string - selectorLabels string - ingressAnnotationsPrefix string - ingressClass string - enableLeaderElection bool - leaderElectionNamespace string - enableConfigTracking bool - ver bool - kubeconfigServiceMesh string - clusterName string - noCrossNamespaceRefs bool + masterURL string + kubeconfig string + kubeconfigQPS int + kubeconfigBurst int + metricsServer string + controlLoopInterval time.Duration + logLevel string + port string + msteamsURL string + msteamsProxyURL string + includeLabelPrefix string + slackURL string + slackToken string + slackProxyURL string + slackUser string + slackChannel string + eventWebhook string + threadiness int + zapReplaceGlobals bool + zapEncoding string + namespace string + meshProvider string + selectorLabels string + ingressAnnotationsPrefix string + ingressClass string + enableLeaderElection bool + leaderElectionNamespace string + enableConfigTracking bool + enableConfigBinaryDataTracking bool + ver bool + kubeconfigServiceMesh string + clusterName string + noCrossNamespaceRefs bool ) func init() { @@ -119,6 +120,7 @@ func init() { flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election.") flag.StringVar(&leaderElectionNamespace, "leader-election-namespace", "kube-system", "Namespace used to create the leader election config map.") flag.BoolVar(&enableConfigTracking, "enable-config-tracking", true, "Enable secrets and configmaps tracking.") + flag.BoolVar(&enableConfigBinaryDataTracking, "enable-config-binary-data-tracking", false, "Enable tracking of binary data in configmaps.") flag.BoolVar(&ver, "version", false, "Print version") flag.StringVar(&kubeconfigServiceMesh, "kubeconfig-service-mesh", "", "Path to a kubeconfig for the service mesh control plane cluster.") flag.StringVar(&clusterName, "cluster-name", "", "Cluster name to be included in alert msgs.") @@ -233,9 +235,10 @@ func main() { var configTracker canary.Tracker if enableConfigTracking { configTracker = &canary.ConfigTracker{ - Logger: logger, - KubeClient: kubeClient, - FlaggerClient: flaggerClient, + Logger: logger, + KubeClient: kubeClient, + FlaggerClient: flaggerClient, + TrackBinaryData: enableConfigBinaryDataTracking, } } else { configTracker = &canary.NopTracker{} diff --git a/pkg/canary/config_tracker.go b/pkg/canary/config_tracker.go index 9cd927ba3..dbe832aef 100644 --- a/pkg/canary/config_tracker.go +++ b/pkg/canary/config_tracker.go @@ -37,9 +37,10 @@ import ( // ConfigTracker is managing the operations for Kubernetes ConfigMaps and Secrets type ConfigTracker struct { - KubeClient kubernetes.Interface - FlaggerClient clientset.Interface - Logger *zap.SugaredLogger + KubeClient kubernetes.Interface + FlaggerClient clientset.Interface + Logger *zap.SugaredLogger + TrackBinaryData bool } type ConfigRefType string @@ -89,7 +90,7 @@ func (ct *ConfigTracker) getRefFromConfigMap(name string, namespace string) (*Co return &ConfigRef{ Name: config.Name, Type: ConfigRefMap, - Checksum: checksum(ct.getFullDataFromConfigMap(*config)), + Checksum: checksum(ct.getDataFromConfigMap(*config)), }, nil } @@ -117,33 +118,22 @@ func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*Confi return &ConfigRef{ Name: secret.Name, Type: ConfigRefSecret, - Checksum: checksum(ct.getFullDataFromSecret(*secret)), + Checksum: checksum(secret.Data), }, nil } -// getFullDataFromConfigMap fetches both data and binaryData in the configmap -func (ct *ConfigTracker) getFullDataFromConfigMap(config corev1.ConfigMap) map[string]string { - fullData := make(map[string]string) - maps.Copy(fullData, config.Data) +// getDataFromConfigMap fetches both data and binaryData with regards to TrackBinaryData in the configmap +func (ct *ConfigTracker) getDataFromConfigMap(config corev1.ConfigMap) map[string]string { + data := make(map[string]string) + maps.Copy(data, config.Data) - for k, v := range config.BinaryData { - fullData[k] = string(v) - - } - - return fullData -} - -// getFullDataFromSecret fetches both data and stringData in the secret -func (ct *ConfigTracker) getFullDataFromSecret(secret corev1.Secret) map[string]string { - fullData := make(map[string]string) - maps.Copy(fullData, secret.StringData) - - for k, v := range secret.Data { - fullData[k] = string(v) + if ct.TrackBinaryData { + for k, v := range config.BinaryData { + data[k] = string(v) + } } - return fullData + return data } // GetTargetConfigs scans the target deployment for Kubernetes ConfigMaps and Secrets @@ -358,8 +348,10 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str Labels: labels, OwnerReferences: ownerReferences, }, - Data: config.Data, - BinaryData: config.BinaryData, + Data: config.Data, + } + if ct.TrackBinaryData { + primaryConfigMap.BinaryData = config.BinaryData } // update or insert primary ConfigMap @@ -412,9 +404,8 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str Labels: labels, OwnerReferences: ownerReferences, }, - Type: secret.Type, - Data: secret.Data, - StringData: secret.StringData, + Type: secret.Type, + Data: secret.Data, } // update or insert primary Secret diff --git a/pkg/canary/config_tracker_test.go b/pkg/canary/config_tracker_test.go index 57d46f041..b7de62faa 100644 --- a/pkg/canary/config_tracker_test.go +++ b/pkg/canary/config_tracker_test.go @@ -60,31 +60,26 @@ func TestConfigTracker_ConfigMaps(t *testing.T) { configPrimaryInit, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-init-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryInit.Data["color"]) - assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryInit.BinaryData["color_binary"]) } configPrimaryInitEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-init-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryInitEnv.Data["color"]) - assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryInitEnv.BinaryData["color_binary"]) } configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimary.Data["color"]) - assert.Equal(t, configMap.BinaryData["color_binary"], configPrimary.BinaryData["color_binary"]) } configPrimaryEnv, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryEnv.Data["color"]) - assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryEnv.BinaryData["color_binary"]) } configPrimaryVol, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-vol-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, configMap.Data["color"], configPrimaryVol.Data["color"]) - assert.Equal(t, configMap.BinaryData["color_binary"], configPrimaryVol.BinaryData["color_binary"]) } configProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[0].ConfigMap.Name @@ -224,31 +219,26 @@ func TestConfigTracker_Secrets(t *testing.T) { secretPrimaryInit, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-init-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryInit.Data["apiKey"])) - assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryInit.StringData["apiKey_string"])) } secretPrimaryInitEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-init-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryInitEnv.Data["apiKey"])) - assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryInitEnv.StringData["apiKey_string"])) } secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimary.Data["apiKey"])) - assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimary.StringData["apiKey_string"])) } secretPrimaryEnv, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-all-env-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryEnv.Data["apiKey"])) - assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryEnv.StringData["apiKey_string"])) } secretPrimaryVol, err := mocks.kubeClient.CoreV1().Secrets("default").Get(context.TODO(), "podinfo-secret-vol-primary", metav1.GetOptions{}) if assert.NoError(t, err) { assert.Equal(t, string(secret.Data["apiKey"]), string(secretPrimaryVol.Data["apiKey"])) - assert.Equal(t, string(secret.StringData["apiKey_string"]), string(secretPrimaryVol.StringData["apiKey_string"])) } secretProjectedName := depPrimary.Spec.Template.Spec.Volumes[2].VolumeSource.Projected.Sources[1].Secret.Name diff --git a/pkg/canary/deployment_fixture_test.go b/pkg/canary/deployment_fixture_test.go index b5b6714d4..f32cfdb97 100644 --- a/pkg/canary/deployment_fixture_test.go +++ b/pkg/canary/deployment_fixture_test.go @@ -299,9 +299,6 @@ func newDeploymentControllerTestSecret() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } @@ -316,9 +313,6 @@ func newDeploymentControllerTestSecretProjected() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } @@ -333,9 +327,6 @@ func newDeploymentControllerTestSecretEnv() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } @@ -350,9 +341,6 @@ func newDeploymentControllerTestSecretVol() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } @@ -372,9 +360,6 @@ func newDeploymentControllerTestSecretTrackerEnabled() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } @@ -394,9 +379,6 @@ func newDeploymentControllerTestSecretTrackerDisabled() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } @@ -411,9 +393,6 @@ func newDeploymentControllerTestSecretInit() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } @@ -428,9 +407,6 @@ func newDeploymentControllerTestSecretInitEnv() *corev1.Secret { Data: map[string][]byte{ "apiKey": []byte("test"), }, - StringData: map[string]string{ - "apiKey_string": "test", - }, } } From f3fa6a1ca425fe9a49605b96f3352a650b226af8 Mon Sep 17 00:00:00 2001 From: Arman Babaei <292arma@gmail.com> Date: Mon, 8 Jun 2026 15:29:49 +0330 Subject: [PATCH 3/4] fix hyperlink Signed-off-by: Arman Babaei <292arma@gmail.com> --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 8f6f8df73..ad7c02672 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,5 +1,5 @@ # Flagger Governance The Flagger project is governed by the [Flux governance document](https://github.com/fluxcd/community/blob/main/GOVERNANCE.md), -involvement is defined in the [Flux community roles document](chttps://github.com/fluxcd/community/blob/main/community-roles.md), +involvement is defined in the [Flux community roles document](https://github.com/fluxcd/community/blob/main/community-roles.md), and processes can be found in the [Flux process document](https://github.com/fluxcd/community/blob/main/PROCESS.md). From 29c33c0eb6e23a0c540fcebf7ac00b74f4e4fa0a Mon Sep 17 00:00:00 2001 From: Arman Babaei <292arma@gmail.com> Date: Mon, 8 Jun 2026 15:46:01 +0330 Subject: [PATCH 4/4] add unittests Signed-off-by: Arman Babaei <292arma@gmail.com> --- pkg/canary/config_tracker_test.go | 106 ++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/pkg/canary/config_tracker_test.go b/pkg/canary/config_tracker_test.go index b7de62faa..bae9d44fd 100644 --- a/pkg/canary/config_tracker_test.go +++ b/pkg/canary/config_tracker_test.go @@ -429,3 +429,109 @@ func TestConfigTracker_ConfigOwnerMultiDeployment(t *testing.T) { assert.Len(t, secretPrimary.OwnerReferences, 2) }) } + +func TestConfigTracker_TrackBinaryDataEnabled(t *testing.T) { + t.Run("checksum computation includes binary data", func(t *testing.T) { + dc := deploymentConfigs{name: "podinfo", label: "name", labelValue: "podinfo"} + + mocks := newDeploymentFixture(dc) + ct := mocks.controller.configTracker.(*ConfigTracker) + ct.TrackBinaryData = true + + config := newDeploymentControllerTestConfigMap() + mocks.kubeClient.CoreV1().ConfigMaps("default").Create(context.TODO(), config, metav1.CreateOptions{}) + + ref, err := ct.getRefFromConfigMap("podinfo-config-env", "default") + require.NoError(t, err) + require.NotNil(t, ref) + + // Verify checksum includes binary data by checking it's not empty + assert.NotEmpty(t, ref.Checksum) + }) + + t.Run("primary configmaps include binary data", func(t *testing.T) { + dc := deploymentConfigs{name: "podinfo", label: "name", labelValue: "podinfo"} + mocks := newDeploymentFixture(dc) + ct := mocks.controller.configTracker.(*ConfigTracker) + ct.TrackBinaryData = true + + configMap := newDeploymentControllerTestConfigMap() + + mocks.initializeCanary(t) + + // Verify all primary ConfigMaps include binary data + configMapsToCheck := []string{ + "podinfo-config-init-env-primary", + "podinfo-config-all-env-primary", + "podinfo-config-env-primary", + "podinfo-config-vol-primary", + } + + for _, cmName := range configMapsToCheck { + cm, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), cmName, metav1.GetOptions{}) + if assert.NoError(t, err) { + assert.Equal(t, configMap.BinaryData["color_binary"], cm.BinaryData["color_binary"], + "ConfigMap %s should have binary data", cmName) + } + } + }) + + t.Run("daemonset primary configmaps include binary data", func(t *testing.T) { + dc := daemonsetConfigs{name: "podinfo", label: "name", labelValue: "podinfo"} + mocks := newDaemonSetFixture(dc) + ct := mocks.controller.configTracker.(*ConfigTracker) + ct.TrackBinaryData = true + + configMap := newDaemonSetControllerTestConfigMap() + + _, err := mocks.controller.Initialize(mocks.canary) + require.NoError(t, err) + + // Verify all primary ConfigMaps include binary data + configMapsToCheck := []string{ + "podinfo-config-init-env-primary", + "podinfo-config-all-env-primary", + "podinfo-config-env-primary", + "podinfo-config-vol-primary", + } + + for _, cmName := range configMapsToCheck { + cm, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), cmName, metav1.GetOptions{}) + if assert.NoError(t, err) { + assert.Equal(t, configMap.BinaryData["color_binary"], cm.BinaryData["color_binary"], + "ConfigMap %s should have binary data", cmName) + } + } + }) + + t.Run("config changes detected with binary data modifications", func(t *testing.T) { + dc := deploymentConfigs{name: "podinfo", label: "name", labelValue: "podinfo"} + + // Create fixture and enable binary data tracking + mocks := newDeploymentFixture(dc) + ct := mocks.controller.configTracker.(*ConfigTracker) + ct.TrackBinaryData = true + + // Get the initial config and compute its checksum with binary data + config, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get(context.TODO(), "podinfo-config-env", metav1.GetOptions{}) + require.NoError(t, err) + + ref1, err := ct.getRefFromConfigMap("podinfo-config-env", "default") + require.NoError(t, err) + require.NotNil(t, ref1) + checksum1 := ref1.Checksum + + // Update binary data in the ConfigMap + config.BinaryData["color_binary"] = []byte("Ymx1ZQo=") + mocks.kubeClient.CoreV1().ConfigMaps("default").Update(context.TODO(), config, metav1.UpdateOptions{}) + + // Get the updated config and compute its checksum with binary data + ref2, err := ct.getRefFromConfigMap("podinfo-config-env", "default") + require.NoError(t, err) + require.NotNil(t, ref2) + checksum2 := ref2.Checksum + + // Checksums should differ when binary data is modified + assert.NotEqual(t, checksum1, checksum2, "checksum should change when binary data is modified") + }) +}