Skip to content
Open
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
2 changes: 1 addition & 1 deletion GOVERNANCE.md
Original file line number Diff line number Diff line change
@@ -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).
73 changes: 38 additions & 35 deletions cmd/flagger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -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{}
Expand Down
27 changes: 23 additions & 4 deletions pkg/canary/config_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"maps"
"strings"

"go.uber.org/zap"
Expand All @@ -36,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
Expand Down Expand Up @@ -88,7 +90,7 @@ func (ct *ConfigTracker) getRefFromConfigMap(name string, namespace string) (*Co
return &ConfigRef{
Name: config.Name,
Type: ConfigRefMap,
Checksum: checksum(config.Data),
Checksum: checksum(ct.getDataFromConfigMap(*config)),
}, nil
}

Expand Down Expand Up @@ -120,6 +122,20 @@ func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*Confi
}, nil
}

// 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)

if ct.TrackBinaryData {
for k, v := range config.BinaryData {
data[k] = string(v)
}
}

return data
}

// 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) {
Expand Down Expand Up @@ -334,6 +350,9 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str
},
Data: config.Data,
}
if ct.TrackBinaryData {
primaryConfigMap.BinaryData = config.BinaryData
}

// update or insert primary ConfigMap
_, err = ct.KubeClient.CoreV1().ConfigMaps(cd.Namespace).Update(context.TODO(), primaryConfigMap, metav1.UpdateOptions{})
Expand Down
112 changes: 112 additions & 0 deletions pkg/canary/config_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,26 +136,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
Expand All @@ -164,6 +169,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{})
Expand Down Expand Up @@ -423,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")
})
}
Loading