Skip to content

Commit 144a935

Browse files
authored
Add a second ConfigMap for individual key overrides (#929)
* Add a second configmap, for specifying overrides as individual keys
1 parent 3e0cd71 commit 144a935

File tree

7 files changed

+102
-16
lines changed

7 files changed

+102
-16
lines changed

chart/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
apiVersion: v2
66
name: chaos-controller
77
description: Datadog Chaos Controller chart
8-
version: 3.1.1
8+
version: 3.2.0
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Unless explicitly stated otherwise all files in this repository are licensed
2+
# under the Apache License Version 2.0.
3+
# This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
# Copyright 2024 Datadog, Inc.
5+
{{- if .Values.controller.configMapOverrides }}
6+
apiVersion: v1
7+
kind: ConfigMap
8+
metadata:
9+
name: chaos-controller-overrides
10+
namespace: "{{ .Values.chaosNamespace }}"
11+
data:
12+
controller.deleteOnly: "false"
13+
# Include any key from the chaos-controller config map's config.yaml
14+
# Keys must be flattened and values must be strings, e.g., `controller:\n\tdeleteOnly: true` becomes `controller.deleteOnly: "true"`
15+
# This configMap is strictly optional, and primarily used for dynamically altering config from external systems
16+
{{- end }}

chart/templates/deployment.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ spec:
6767
- /usr/local/bin/manager
6868
args:
6969
- --config=/etc/chaos-controller/config.yaml
70+
{{- if .Values.controller.configMapOverrides }}
71+
- --config-overrides=chaos-controller-overrides
72+
{{- end }}
7073
env:
7174
- name: POD_NAMESPACE
7275
valueFrom:

chart/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ global:
1616
pullSecrets: false # name of the secret object to use when pulling images
1717

1818
controller:
19+
configMapOverrides: false # deploy a second configmap, which overrides values from the first
1920
version: ""
2021
image:
2122
repo: chaos-controller

config/config.go

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@
55
package config
66

77
import (
8+
"context"
89
"fmt"
910
"os"
1011
"time"
1112

1213
cloudtypes "github.com/DataDog/chaos-controller/cloudservice/types"
1314
"github.com/DataDog/chaos-controller/eventnotifier"
14-
"go.uber.org/zap"
15-
15+
"github.com/cenkalti/backoff"
1616
"github.com/fsnotify/fsnotify"
1717
"github.com/spf13/pflag"
1818
"github.com/spf13/viper"
19+
"go.uber.org/zap"
20+
corev1 "k8s.io/api/core/v1"
1921
"k8s.io/apimachinery/pkg/api/resource"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
2024
)
2125

2226
type config struct {
@@ -109,20 +113,23 @@ type handlerConfig struct {
109113
const DefaultDisruptionDeletionTimeout = time.Minute * 15
110114
const DefaultFinalizerDeletionDelay = time.Second * 20
111115

112-
func New(logger *zap.SugaredLogger, osArgs []string) (config, error) {
116+
func New(client corev1client.ConfigMapInterface, logger *zap.SugaredLogger, osArgs []string) (config, error) {
113117
var (
114-
configPath string
115-
cfg config
118+
configPath string
119+
configMapOverrides string
120+
cfg config
116121
)
117122

118123
preConfigFS := pflag.NewFlagSet("pre-config", pflag.ContinueOnError)
119124
mainFS := pflag.NewFlagSet("main-config", pflag.ContinueOnError)
120125

121126
preConfigFS.ParseErrorsWhitelist.UnknownFlags = true
122127
preConfigFS.StringVar(&configPath, "config", "", "Configuration file path")
128+
preConfigFS.StringVar(&configMapOverrides, "config-overrides", "", "Name of ConfigMap to provide config overrides")
123129
// we redefine configuration flag into main flag to avoid removing it manually from provided args
124130
// we also define it to avoid activating "UnknownFlags" for main flags so we'll return an error in case a flag is unknown
125131
mainFS.StringVar(&configPath, "config", "", "Configuration file path")
132+
mainFS.StringVar(&configMapOverrides, "config-overrides", "", "Name of ConfigMap to provide config overrides")
126133

127134
mainFS.StringVar(&cfg.Controller.MetricsBindAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
128135

@@ -544,8 +551,7 @@ func New(logger *zap.SugaredLogger, osArgs []string) (config, error) {
544551
return cfg, err
545552
}
546553

547-
err := preConfigFS.Parse(osArgs)
548-
if err != nil {
554+
if err := preConfigFS.Parse(osArgs); err != nil {
549555
return cfg, fmt.Errorf("unable to retrieve configuration parse from provided flag: %w", err)
550556
}
551557

@@ -559,6 +565,53 @@ func New(logger *zap.SugaredLogger, osArgs []string) (config, error) {
559565
return cfg, fmt.Errorf("error loading configuration file: %w", err)
560566
}
561567

568+
if configMapOverrides != "" {
569+
var configMap *corev1.ConfigMap
570+
571+
if backOffErr := backoff.Retry(func() error {
572+
var err error
573+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
574+
defer cancel()
575+
configMap, err = client.Get(ctx, configMapOverrides, metav1.GetOptions{})
576+
if err != nil {
577+
logger.Debugw(fmt.Sprintf("failed to get %s configMap", configMapOverrides), "error", err)
578+
}
579+
return err
580+
},
581+
backoff.NewConstantBackOff(time.Second*5)); backOffErr != nil {
582+
return cfg, fmt.Errorf("unable to retry fetching %s: %w", configMapOverrides, backOffErr)
583+
}
584+
585+
interfacedMap := make(map[string]interface{}, len(configMap.Data))
586+
for k, v := range configMap.Data {
587+
interfacedMap[k] = v
588+
}
589+
590+
if err := viper.MergeConfigMap(interfacedMap); err != nil {
591+
return cfg, fmt.Errorf("unable to merge config map: %w", err)
592+
}
593+
594+
go func(resourceVersion string) {
595+
ticker := time.NewTicker(time.Second * 30)
596+
597+
for {
598+
<-ticker.C
599+
600+
configMap, err := client.Get(context.Background(), configMapOverrides, metav1.GetOptions{})
601+
602+
if err != nil {
603+
logger.Errorw(fmt.Sprintf("error getting %s configMap", configMapOverrides), "error", err)
604+
continue
605+
}
606+
607+
if configMap.ResourceVersion != resourceVersion {
608+
logger.Info("override configmap has changed, restarting")
609+
os.Exit(0)
610+
}
611+
}
612+
}(configMap.ResourceVersion)
613+
}
614+
562615
if err := viper.Unmarshal(&cfg); err != nil {
563616
return cfg, fmt.Errorf("error unmarshaling configuration: %w", err)
564617
}

config/config_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,29 @@ var _ = Describe("Config", func() {
2525

2626
Context("invalid config", func() {
2727
It("fails with a missing path", func() {
28-
_, err := config.New(logger, []string{"--config"})
28+
_, err := config.New(nil, logger, []string{"--config"})
2929
Expect(err).Should(MatchError("unable to retrieve configuration parse from provided flag: flag needs an argument: --config"))
3030
})
3131

3232
It("fails with an invalid path", func() {
33-
_, err := config.New(logger, []string{"--config", "invalid-path/invalid-file.yaml"})
33+
_, err := config.New(nil, logger, []string{"--config", "invalid-path/invalid-file.yaml"})
3434
Expect(err).Should(MatchError("error loading configuration file: open invalid-path/invalid-file.yaml: no such file or directory"))
3535
})
3636

3737
It("fails with an invalid config file", func() {
38-
_, err := config.New(logger, []string{"--config", "testdata/invalid.yaml"})
38+
_, err := config.New(nil, logger, []string{"--config", "testdata/invalid.yaml"})
3939
Expect(err).Should(MatchError(ContainSubstring("error loading configuration file: While parsing config: yaml: unmarshal errors:")))
4040
})
4141

4242
It("fails with a defaultDuration greater than the maxDuration", func() {
43-
_, err := config.New(logger, []string{"--config", "testdata/default-duration-too-big.yaml"})
43+
_, err := config.New(nil, logger, []string{"--config", "testdata/default-duration-too-big.yaml"})
4444
Expect(err).Should(MatchError(fmt.Sprintf("defaultDuration of %s, must be less than or equal to the maxDuration %s", "3m0s", "2m0s")))
4545
})
4646
})
4747

4848
Context("without configuration", func() {
4949
It("succeed with default values", func() {
50-
v, err := config.New(logger, []string{})
50+
v, err := config.New(nil, logger, []string{})
5151
Expect(err).ToNot(HaveOccurred())
5252

5353
By("overriding controller values")
@@ -115,7 +115,7 @@ var _ = Describe("Config", func() {
115115

116116
Context("with configuration file", func() {
117117
It("succeed with overriden values", func() {
118-
v, err := config.New(logger, []string{"--config", "testdata/local.yaml"})
118+
v, err := config.New(nil, logger, []string{"--config", "testdata/local.yaml"})
119119
Expect(err).ToNot(HaveOccurred())
120120

121121
By("overriding controller values")
@@ -184,7 +184,7 @@ var _ = Describe("Config", func() {
184184

185185
Context("with configuration file and flag", func() {
186186
It("succeed with values from flags", func() {
187-
v, err := config.New(logger, []string{"--config", "testdata/local.yaml", "--notifiers-common-clustername", "provided-by-command-flag-cluster-name"})
187+
v, err := config.New(nil, logger, []string{"--config", "testdata/local.yaml", "--notifiers-common-clustername", "provided-by-command-flag-cluster-name"})
188188
Expect(err).ToNot(HaveOccurred())
189189

190190
Expect(v.Controller.Notifiers.Common.ClusterName).To(Equal("provided-by-command-flag-cluster-name"))

main.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"go.uber.org/zap"
1515
"k8s.io/apimachinery/pkg/api/resource"
16+
"k8s.io/client-go/rest"
1617
"k8s.io/klog/v2"
1718
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
1819

@@ -86,7 +87,19 @@ func main() {
8687
logger.Fatal("missing required CONTROLLER_NODE_NAME environment variable")
8788
}
8889

89-
cfg, err := config.New(logger, os.Args[1:])
90+
restConfig, err := rest.InClusterConfig()
91+
if err != nil {
92+
logger.Fatalw("error creating in-cluster rest config", "error", err)
93+
}
94+
95+
clientset, err := kubernetes.NewForConfig(restConfig)
96+
if err != nil {
97+
logger.Fatalw("error creating kubernetes clientset", "error", err)
98+
}
99+
100+
configMapClient := clientset.CoreV1().ConfigMaps(os.Getenv("POD_NAMESPACE"))
101+
102+
cfg, err := config.New(configMapClient, logger, os.Args[1:])
90103
if err != nil {
91104
logger.Fatalw("unable to create a valid configuration", "error", err)
92105
}

0 commit comments

Comments
 (0)