Skip to content

Commit 07783a4

Browse files
authored
feat(config): default configuration with merge support for downstream overrides (#396)
* feat(config): default configuration with merge support for downstream overrides Creates the required infrastructure for downstream forks to be able to provide default config overrides without modifying the original source code. Downstream forks should be able to create merge/rebase scripts that automatically accepted downstream merge conflicts in config_default_overrides.go since this file will never change upstream. Example usage for downstream forks: To customize defaults, simply populate fields in the returned StaticConfig: ```go func defaultOverrides() *StaticConfig { return &StaticConfig{ ListOutput: "json", // Override default list output format Toolsets: []string{"core"}, // Override default enabled toolsets Port: "9000", // Override default port } } ``` Any fields specified here will override the base defaults defined in config_default.go. Fields not specified will preserve their base default values. Signed-off-by: Marc Nuri <[email protected]> * test: skip downstream toolset (full) tests Skips toolset metadata tests in case there are config overrides. This is useful in downstream forks where the default toolsets might be different to those upstream. Signed-off-by: Marc Nuri <[email protected]> --------- Signed-off-by: Marc Nuri <[email protected]>
1 parent acf465b commit 07783a4

File tree

5 files changed

+107
-8
lines changed

5 files changed

+107
-8
lines changed

pkg/config/config.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const (
1919
type StaticConfig struct {
2020
DeniedResources []GroupVersionKind `toml:"denied_resources"`
2121

22-
LogLevel int `toml:"log_level,omitempty"`
22+
LogLevel int `toml:"log_level,omitzero"`
2323
Port string `toml:"port,omitempty"`
2424
SSEBaseURL string `toml:"sse_base_url,omitempty"`
2525
KubeConfig string `toml:"kubeconfig,omitempty"`
@@ -70,13 +70,6 @@ type StaticConfig struct {
7070
parsedClusterProviderConfigs map[string]ProviderConfig
7171
}
7272

73-
func Default() *StaticConfig {
74-
return &StaticConfig{
75-
ListOutput: "table",
76-
Toolsets: []string{"core", "config", "helm"},
77-
}
78-
}
79-
8073
type GroupVersionKind struct {
8174
Group string `toml:"group"`
8275
Version string `toml:"version"`

pkg/config/config_default.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
6+
"github.com/BurntSushi/toml"
7+
)
8+
9+
func Default() *StaticConfig {
10+
defaultConfig := StaticConfig{
11+
ListOutput: "table",
12+
Toolsets: []string{"core", "config", "helm"},
13+
}
14+
overrides := defaultOverrides()
15+
mergedConfig := mergeConfig(defaultConfig, overrides)
16+
return &mergedConfig
17+
}
18+
19+
// HasDefaultOverrides indicates whether the internal defaultOverrides function
20+
// provides any overrides or an empty StaticConfig.
21+
func HasDefaultOverrides() bool {
22+
overrides := defaultOverrides()
23+
var buf bytes.Buffer
24+
if err := toml.NewEncoder(&buf).Encode(overrides); err != nil {
25+
// If marshaling fails, assume no overrides
26+
return false
27+
}
28+
return len(bytes.TrimSpace(buf.Bytes())) > 0
29+
}
30+
31+
// mergeConfig applies non-zero values from override to base using TOML serialization
32+
// and returns the merged StaticConfig.
33+
// In case of any error during marshalling or unmarshalling, it returns the base config unchanged.
34+
func mergeConfig(base, override StaticConfig) StaticConfig {
35+
var overrideBuffer bytes.Buffer
36+
if err := toml.NewEncoder(&overrideBuffer).Encode(override); err != nil {
37+
// If marshaling fails, return base unchanged
38+
return base
39+
}
40+
41+
_, _ = toml.NewDecoder(&overrideBuffer).Decode(&base)
42+
return base
43+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package config
2+
3+
func defaultOverrides() StaticConfig {
4+
return StaticConfig{
5+
// IMPORTANT: this file is used to override default config values in downstream builds.
6+
// This is intentionally left blank.
7+
}
8+
}

pkg/config/config_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,49 @@ func (s *ConfigSuite) TestReadConfigValidPreservesDefaultsForMissingFields() {
174174
})
175175
}
176176

177+
func (s *ConfigSuite) TestMergeConfig() {
178+
base := StaticConfig{
179+
ListOutput: "table",
180+
Toolsets: []string{"core", "config", "helm"},
181+
Port: "8080",
182+
}
183+
s.Run("merges override values on top of base", func() {
184+
override := StaticConfig{
185+
ListOutput: "json",
186+
Port: "9090",
187+
}
188+
189+
result := mergeConfig(base, override)
190+
191+
s.Equal("json", result.ListOutput, "ListOutput should be overridden")
192+
s.Equal("9090", result.Port, "Port should be overridden")
193+
})
194+
195+
s.Run("preserves base values when override is empty", func() {
196+
override := StaticConfig{}
197+
198+
result := mergeConfig(base, override)
199+
200+
s.Equal("table", result.ListOutput, "ListOutput should be preserved from base")
201+
s.Equal([]string{"core", "config", "helm"}, result.Toolsets, "Toolsets should be preserved from base")
202+
s.Equal("8080", result.Port, "Port should be preserved from base")
203+
})
204+
205+
s.Run("handles partial overrides", func() {
206+
override := StaticConfig{
207+
Toolsets: []string{"custom"},
208+
ReadOnly: true,
209+
}
210+
211+
result := mergeConfig(base, override)
212+
213+
s.Equal("table", result.ListOutput, "ListOutput should be preserved from base")
214+
s.Equal([]string{"custom"}, result.Toolsets, "Toolsets should be overridden")
215+
s.Equal("8080", result.Port, "Port should be preserved from base since override doesn't specify it")
216+
s.True(result.ReadOnly, "ReadOnly should be overridden to true")
217+
})
218+
}
219+
177220
func TestConfig(t *testing.T) {
178221
suite.Run(t, new(ConfigSuite))
179222
}

pkg/mcp/toolsets_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ func (s *ToolsetsSuite) TestNoToolsets() {
6565
}
6666

6767
func (s *ToolsetsSuite) TestDefaultToolsetsTools() {
68+
if configuration.HasDefaultOverrides() {
69+
s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)")
70+
}
6871
s.Run("Default configuration toolsets", func() {
6972
s.InitMcpClient()
7073
tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{})
@@ -82,6 +85,9 @@ func (s *ToolsetsSuite) TestDefaultToolsetsTools() {
8285
}
8386

8487
func (s *ToolsetsSuite) TestDefaultToolsetsToolsInOpenShift() {
88+
if configuration.HasDefaultOverrides() {
89+
s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)")
90+
}
8591
s.Run("Default configuration toolsets in OpenShift", func() {
8692
s.Handle(&test.InOpenShiftHandler{})
8793
s.InitMcpClient()
@@ -100,6 +106,9 @@ func (s *ToolsetsSuite) TestDefaultToolsetsToolsInOpenShift() {
100106
}
101107

102108
func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiCluster() {
109+
if configuration.HasDefaultOverrides() {
110+
s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)")
111+
}
103112
s.Run("Default configuration toolsets in multi-cluster (with 11 clusters)", func() {
104113
kubeconfig := s.Kubeconfig()
105114
for i := 0; i < 10; i++ {
@@ -123,6 +132,9 @@ func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiCluster() {
123132
}
124133

125134
func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiClusterEnum() {
135+
if configuration.HasDefaultOverrides() {
136+
s.T().Skip("Skipping test because default configuration overrides are present (this is a downstream fork)")
137+
}
126138
s.Run("Default configuration toolsets in multi-cluster (with 2 clusters)", func() {
127139
kubeconfig := s.Kubeconfig()
128140
// Add additional cluster to force multi-cluster behavior with enum parameter

0 commit comments

Comments
 (0)