Skip to content

Commit c3c585e

Browse files
committed
passing tests
Signed-off-by: J. Harte <[email protected]>
1 parent ca1957b commit c3c585e

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
(c) Copyright IBM Corp. 2024
3+
*/
4+
5+
package secrets
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"reflect"
11+
12+
"github.com/go-logr/logr"
13+
"gopkg.in/yaml.v3"
14+
apiV1 "k8s.io/api/core/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
17+
logf "sigs.k8s.io/controller-runtime/pkg/log"
18+
)
19+
20+
const ConfigMapLabel = "instana.io/agent-config=true"
21+
22+
type ConfigMerger struct {
23+
logger logr.Logger
24+
k8sClient v1.CoreV1Interface
25+
}
26+
27+
func NewConfigMergerBuilder(client v1.CoreV1Interface) *ConfigMerger {
28+
return &ConfigMerger{
29+
logger: logf.Log.WithName("instana-agent-config-merger"),
30+
k8sClient: client,
31+
}
32+
}
33+
34+
func (c *ConfigMerger) MergeConfigurationYaml(agentConfiguration string) []byte {
35+
operator_data := make(map[string]interface{})
36+
err := yaml.Unmarshal([]byte([]byte(agentConfiguration)), operator_data)
37+
config := []byte{}
38+
if err != nil {
39+
c.logger.Error(err, "Failed to load agent configuration")
40+
} else {
41+
config_maps := c.fetchConfigMaps()
42+
for _, config_map := range config_maps {
43+
config_map_data := make(map[string]interface{})
44+
err := yaml.Unmarshal([]byte(config_map.Data["configuration_yaml"]), &config_map_data)
45+
if err != nil {
46+
c.logger.Error(err, "Failed to parse agent configuration YAML")
47+
} else {
48+
operator_data = c.mergeConfig(operator_data, config_map_data)
49+
}
50+
}
51+
config, err = yaml.Marshal(operator_data)
52+
}
53+
return config
54+
}
55+
56+
func (c *ConfigMerger) mergeConfig(operator_data, config_map_data map[string]interface{}) map[string]interface{} {
57+
for key, cm_value := range config_map_data {
58+
if op_value, ok := operator_data[key]; ok {
59+
op_value_kind := reflect.TypeOf(op_value).Kind()
60+
if op_value_kind == reflect.Array || op_value_kind == reflect.Slice {
61+
operator_data[key] = append(op_value.([]interface{}), cm_value.([]interface{})...)
62+
} else {
63+
c.mergeConfig(operator_data[key].(map[string]interface{}), cm_value.(map[string]interface{}))
64+
}
65+
} else {
66+
operator_data[key] = cm_value
67+
}
68+
}
69+
return operator_data
70+
}
71+
72+
func (c *ConfigMerger) fetchConfigMaps() []apiV1.ConfigMap {
73+
config_maps := []apiV1.ConfigMap{}
74+
c.logger.Info(fmt.Sprintf("Fetching agent configmaps with label '%s'", ConfigMapLabel))
75+
config_map_list, err := c.k8sClient.ConfigMaps("").List(context.TODO(), metav1.ListOptions{LabelSelector: ConfigMapLabel})
76+
if err != nil {
77+
c.logger.Error(err, fmt.Sprintf("Failed to fetch agent configmaps with label '%s'", ConfigMapLabel))
78+
} else {
79+
config_maps = config_map_list.Items
80+
c.logger.Info(fmt.Sprintf("Found %d configmaps with label '%s'", len(config_maps), ConfigMapLabel))
81+
}
82+
return config_maps
83+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
(c) Copyright IBM Corp. 2024
3+
*/
4+
5+
package secrets
6+
7+
import (
8+
"context"
9+
"errors"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/mock"
14+
apiV1 "k8s.io/api/core/v1"
15+
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
coreV1 "k8s.io/client-go/kubernetes/typed/core/v1"
17+
)
18+
19+
type CoreV1Mock struct {
20+
coreV1.CoreV1Interface
21+
mock.Mock
22+
}
23+
24+
type ConfigMapMock struct {
25+
coreV1.ConfigMapInterface
26+
mock.Mock
27+
}
28+
29+
func (mock *CoreV1Mock) ConfigMaps(namespace string) coreV1.ConfigMapInterface {
30+
args := mock.Called()
31+
return args.Get(0).(*ConfigMapMock)
32+
}
33+
34+
func (mock *ConfigMapMock) List(ctx context.Context, opts metaV1.ListOptions) (*apiV1.ConfigMapList, error) {
35+
args := mock.Called()
36+
return args.Get(0).(*apiV1.ConfigMapList), nil
37+
}
38+
39+
func TestMergeConfigurationYamlWithNoConfigMaps(t *testing.T) {
40+
client := new(CoreV1Mock)
41+
configMaps := new(ConfigMapMock)
42+
client.On("ConfigMaps").Return(configMaps).Once()
43+
configMapsList := new(apiV1.ConfigMapList)
44+
configMapsList.Items = []apiV1.ConfigMap{}
45+
configMaps.On("List").Return(configMapsList).Once()
46+
47+
merger := NewConfigMergerBuilder(client)
48+
config_bytes := merger.MergeConfigurationYaml("c: d\n")
49+
50+
assert.Equal(t, "c: d\n", string(config_bytes))
51+
}
52+
53+
func TestMergeConfigurationYamlWithOtherConfigMapData(t *testing.T) {
54+
client := new(CoreV1Mock)
55+
configMaps := new(ConfigMapMock)
56+
client.On("ConfigMaps").Return(configMaps).Once()
57+
configMapsList := new(apiV1.ConfigMapList)
58+
configMapsList.Items = []apiV1.ConfigMap{{Data: map[string]string{"a": "b"}}}
59+
configMaps.On("List").Return(configMapsList).Once()
60+
61+
merger := NewConfigMergerBuilder(client)
62+
config_bytes := merger.MergeConfigurationYaml("c: d\n")
63+
64+
assert.Equal(t, "c: d\n", string(config_bytes))
65+
}
66+
67+
func TestMergeConfigurationYamlWithEmptyConfigMapData(t *testing.T) {
68+
client := new(CoreV1Mock)
69+
configMaps := new(ConfigMapMock)
70+
client.On("ConfigMaps").Return(configMaps).Once()
71+
configMapsList := new(apiV1.ConfigMapList)
72+
configMapsList.Items = []apiV1.ConfigMap{{Data: map[string]string{"configuration_yaml": ""}}}
73+
configMaps.On("List").Return(configMapsList).Once()
74+
75+
merger := NewConfigMergerBuilder(client)
76+
config_bytes := merger.MergeConfigurationYaml("c: d\n")
77+
78+
assert.Equal(t, "c: d\n", string(config_bytes))
79+
}
80+
81+
func TestMergeConfigurationYamlWithNewTopLevelKey(t *testing.T) {
82+
client := new(CoreV1Mock)
83+
configMaps := new(ConfigMapMock)
84+
client.On("ConfigMaps").Return(configMaps).Once()
85+
configMapsList := new(apiV1.ConfigMapList)
86+
configMapsList.Items = []apiV1.ConfigMap{{Data: map[string]string{"configuration_yaml": "a:\n b:\n - 1\n"}}}
87+
configMaps.On("List").Return(configMapsList).Once()
88+
89+
merger := NewConfigMergerBuilder(client)
90+
config_bytes := merger.MergeConfigurationYaml("c: d\n")
91+
92+
assert.Equal(t, "a:\n b:\n - 1\nc: d\n", string(config_bytes))
93+
}
94+
95+
func TestMergeConfigurationYamlWithNewListItem(t *testing.T) {
96+
client := new(CoreV1Mock)
97+
configMaps := new(ConfigMapMock)
98+
client.On("ConfigMaps").Return(configMaps).Once()
99+
configMapsList := new(apiV1.ConfigMapList)
100+
configMapsList.Items = []apiV1.ConfigMap{{Data: map[string]string{"configuration_yaml": "a:\n b:\n - 2\n"}}}
101+
configMaps.On("List").Return(configMapsList).Once()
102+
103+
merger := NewConfigMergerBuilder(client)
104+
config_bytes := merger.MergeConfigurationYaml("a:\n b:\n - 1\nc: d\n")
105+
106+
assert.Equal(t, "a:\n b:\n - 1\n - 2\nc: d\n", string(config_bytes))
107+
}
108+
109+
func TestMergeConfigurationYamlWithMultipleNewListItems(t *testing.T) {
110+
client := new(CoreV1Mock)
111+
configMaps := new(ConfigMapMock)
112+
client.On("ConfigMaps").Return(configMaps).Once()
113+
configMapsList := new(apiV1.ConfigMapList)
114+
configMapsList.Items = []apiV1.ConfigMap{{Data: map[string]string{"configuration_yaml": "a:\n b:\n - 2\n - 3\n"}}}
115+
configMaps.On("List").Return(configMapsList).Once()
116+
117+
merger := NewConfigMergerBuilder(client)
118+
config_bytes := merger.MergeConfigurationYaml("a:\n b:\n - 1\nc: d\n")
119+
120+
assert.Equal(t, "a:\n b:\n - 1\n - 2\n - 3\nc: d\n", string(config_bytes))
121+
}
122+
123+
func TestMergeConfigurationYamlForFailedRetrieval(t *testing.T) {
124+
client := new(CoreV1Mock)
125+
configMaps := new(ConfigMapMock)
126+
client.On("ConfigMaps").Return(configMaps).Once()
127+
configMaps.On("List").Return(&apiV1.ConfigMapList{}, errors.New("Failed")).Once()
128+
129+
merger := NewConfigMergerBuilder(client)
130+
config_bytes := merger.MergeConfigurationYaml("a:\n b:\n - 1\nc: d\n")
131+
132+
assert.Equal(t, "a:\n b:\n - 1\nc: d\n", string(config_bytes))
133+
}

0 commit comments

Comments
 (0)