Skip to content

Commit 7b7bd5e

Browse files
Merge pull request #1413 from bshephar/OSPRH-15097
Convert floats to ints after unmarshalling
2 parents b0944fc + 0db46a6 commit 7b7bd5e

4 files changed

Lines changed: 221 additions & 28 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
297297
ENVTEST ?= $(LOCALBIN)/setup-envtest
298298
CRD_MARKDOWN ?= $(LOCALBIN)/crd-to-markdown
299299
GINKGO ?= $(LOCALBIN)/ginkgo
300-
GINKGO_TESTS ?= ./tests/... ./apis/client/... ./apis/core/... ./apis/dataplane/...
300+
GINKGO_TESTS ?= ./tests/... ./apis/client/... ./apis/core/... ./apis/dataplane/... ./pkg/dataplane/...
301301

302302
KUTTL ?= $(LOCALBIN)/kubectl-kuttl
303303

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ require (
4040
github.com/openstack-k8s-operators/test-operator/api v0.6.1-0.20250415132754-51bb16559bed
4141
github.com/pkg/errors v0.9.1
4242
github.com/rabbitmq/cluster-operator/v2 v2.11.0
43+
github.com/stretchr/testify v1.10.0
4344
go.uber.org/zap v1.27.0
4445
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
4546
gopkg.in/yaml.v3 v3.0.1
@@ -84,6 +85,7 @@ require (
8485
github.com/modern-go/reflect2 v1.0.2 // indirect
8586
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
8687
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250423055245-3cb2ae8df6f0 // indirect
88+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
8789
github.com/prometheus/client_golang v1.19.0 // indirect
8890
github.com/prometheus/client_model v0.6.0 // indirect
8991
github.com/prometheus/common v0.53.0 // indirect

pkg/dataplane/inventory.go

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,51 +39,76 @@ import (
3939
// getAnsibleVarsFrom gets ansible vars from ConfigMap/Secret
4040
func getAnsibleVarsFrom(ctx context.Context, helper *helper.Helper, namespace string, ansible *dataplanev1.AnsibleOpts) (map[string]interface{}, error) {
4141
result := make(map[string]interface{})
42-
var value interface{}
4342

4443
for _, dataSource := range ansible.AnsibleVarsFrom {
4544
configMap, secret, err := util.GetDataSourceCmSecret(ctx, helper, namespace, dataSource)
4645
if err != nil {
4746
return result, err
4847
}
4948

50-
// AnsibleVars will override AnsibleVarsFrom variables.
51-
// Process AnsibleVarsFrom first then allow AnsibleVars to replace existing values.
5249
if configMap != nil {
53-
for k, v := range configMap.Data {
54-
if len(dataSource.Prefix) > 0 {
55-
k = dataSource.Prefix + k
56-
}
57-
58-
// Attempt to unmarshal the value into json. If that fails,
59-
// assume it's a plain string.
60-
err := json.Unmarshal([]byte(v), &value)
61-
if err != nil {
62-
value = v
63-
}
64-
result[k] = value
50+
err := processConfigMapData(configMap.Data, dataSource.Prefix, result)
51+
if err != nil {
52+
return result, err
6553
}
6654
}
6755

6856
if secret != nil {
69-
for k, v := range secret.Data {
70-
if len(dataSource.Prefix) > 0 {
71-
k = dataSource.Prefix + k
72-
}
73-
// Attempt to unmarshal the value into json. If that fails,
74-
// assume it's a plain string.
75-
err := json.Unmarshal(v, &value)
76-
if err != nil {
77-
value = string(v)
78-
}
79-
result[k] = value
57+
err := processSecretData(secret.Data, dataSource.Prefix, result)
58+
if err != nil {
59+
return result, err
8060
}
8161
}
82-
8362
}
8463
return result, nil
8564
}
8665

66+
// processConfigMapData processes the key-value pairs from ConfigMap and adds them to the result map.
67+
func processConfigMapData(data map[string]string, prefix string, result map[string]any) error {
68+
69+
var value any
70+
71+
for k, v := range data {
72+
if len(prefix) > 0 {
73+
k = prefix + k
74+
}
75+
76+
decoder := json.NewDecoder(strings.NewReader(v))
77+
decoder.UseNumber()
78+
79+
err := decoder.Decode(&value)
80+
if err != nil {
81+
value = v
82+
}
83+
result[k] = value
84+
}
85+
86+
return nil
87+
}
88+
89+
// processSecretData processes the key-value pairs from Secret and adds them to the result map.
90+
func processSecretData(data map[string][]byte, prefix string, result map[string]any) error {
91+
92+
var value any
93+
94+
for k, v := range data {
95+
if len(prefix) > 0 {
96+
k = prefix + k
97+
}
98+
99+
decoder := json.NewDecoder(strings.NewReader(string(v[:])))
100+
decoder.UseNumber()
101+
102+
err := decoder.Decode(&value)
103+
if err != nil {
104+
value = string(v)
105+
}
106+
result[k] = value
107+
}
108+
109+
return nil
110+
}
111+
87112
// GenerateNodeSetInventory yields a parsed Inventory for role
88113
func GenerateNodeSetInventory(ctx context.Context, helper *helper.Helper,
89114
instance *dataplanev1.OpenStackDataPlaneNodeSet,

pkg/dataplane/inventory_test.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package deployment
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestProcessConfigMapData_NumbersAsInts(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
data map[string]string
14+
prefix string
15+
expected map[string]interface{}
16+
expectedType any
17+
}{
18+
{
19+
name: "string value remains string",
20+
data: map[string]string{
21+
"foo": "bar",
22+
},
23+
prefix: "",
24+
expected: map[string]interface{}{
25+
"foo": "bar",
26+
},
27+
expectedType: "",
28+
},
29+
{
30+
name: "integer number decoded correctly",
31+
data: map[string]string{
32+
"intVal": "123",
33+
},
34+
prefix: "",
35+
expected: map[string]interface{}{
36+
"intVal": json.Number("123"),
37+
},
38+
expectedType: json.Number("1"),
39+
},
40+
{
41+
name: "float number decoded correctly",
42+
data: map[string]string{
43+
"floatVal": "123.45",
44+
},
45+
prefix: "",
46+
expected: map[string]interface{}{
47+
"floatVal": json.Number("123.45"),
48+
},
49+
expectedType: json.Number("1"),
50+
},
51+
{
52+
name: "with prefix",
53+
data: map[string]string{
54+
"somekey": "42",
55+
},
56+
prefix: "myprefix-",
57+
expected: map[string]interface{}{
58+
"myprefix-somekey": json.Number("42"),
59+
},
60+
expectedType: json.Number("1"),
61+
},
62+
}
63+
64+
for _, tt := range tests {
65+
t.Run(tt.name, func(t *testing.T) {
66+
result := make(map[string]any)
67+
err := processConfigMapData(tt.data, tt.prefix, result)
68+
assert.NoError(t, err)
69+
70+
for k, v := range tt.expected {
71+
actual, ok := result[k]
72+
assert.True(t, ok, "key %s should exist", k)
73+
74+
assert.IsType(t, tt.expectedType, actual)
75+
76+
expectedVal, ok1 := v.(json.Number)
77+
actualVal, ok2 := actual.(json.Number)
78+
79+
if ok1 && ok2 {
80+
assert.Equal(t, expectedVal, actualVal)
81+
} else {
82+
assert.Equal(t, v, actual)
83+
}
84+
}
85+
})
86+
}
87+
}
88+
89+
func TestProcessSecretData_NumbersAsInts(t *testing.T) {
90+
tests := []struct {
91+
name string
92+
data map[string][]byte
93+
prefix string
94+
expected map[string]interface{}
95+
expectedType any
96+
}{
97+
{
98+
name: "string value remains string",
99+
data: map[string][]byte{
100+
"foo": []byte("bar"),
101+
},
102+
prefix: "",
103+
expected: map[string]interface{}{
104+
"foo": "bar",
105+
},
106+
expectedType: "",
107+
},
108+
{
109+
name: "integer number decoded correctly",
110+
data: map[string][]byte{
111+
"intVal": []byte("123"),
112+
},
113+
prefix: "",
114+
expected: map[string]interface{}{
115+
"intVal": json.Number("123"),
116+
},
117+
expectedType: json.Number("1"),
118+
},
119+
{
120+
name: "float number decoded correctly",
121+
data: map[string][]byte{
122+
"floatVal": []byte("123.45"),
123+
},
124+
prefix: "",
125+
expected: map[string]interface{}{
126+
"floatVal": json.Number("123.45"),
127+
},
128+
expectedType: json.Number("1"),
129+
},
130+
{
131+
name: "with prefix",
132+
data: map[string][]byte{
133+
"somekey": []byte("42"),
134+
},
135+
prefix: "myprefix-",
136+
expected: map[string]interface{}{
137+
"myprefix-somekey": json.Number("42"),
138+
},
139+
expectedType: json.Number("1"),
140+
},
141+
}
142+
143+
for _, tt := range tests {
144+
t.Run(tt.name, func(t *testing.T) {
145+
result := make(map[string]any)
146+
err := processSecretData(tt.data, tt.prefix, result)
147+
assert.NoError(t, err)
148+
149+
for k, v := range tt.expected {
150+
actual, ok := result[k]
151+
assert.True(t, ok, "key %s should exist", k)
152+
153+
assert.IsType(t, tt.expectedType, actual)
154+
155+
expectedVal, ok1 := v.(json.Number)
156+
actualVal, ok2 := actual.(json.Number)
157+
158+
if ok1 && ok2 {
159+
assert.Equal(t, expectedVal, actualVal)
160+
} else {
161+
assert.Equal(t, v, actual)
162+
}
163+
}
164+
})
165+
}
166+
}

0 commit comments

Comments
 (0)