Skip to content

Commit 45c9623

Browse files
committed
Fix bug checksum chart values will not consider about order of key-value
Signed-off-by: longquan0104 <longquan0104@gmail.com>
1 parent d1cc2fe commit 45c9623

File tree

4 files changed

+218
-9
lines changed

4 files changed

+218
-9
lines changed

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ api-docs: gen-crd-api-reference-docs
8585

8686
# Run go mod tidy
8787
tidy:
88-
cd api; rm -f go.sum; go mod tidy -compat=1.20
89-
rm -f go.sum; go mod tidy -compat=1.20
88+
cd api; rm -f go.sum; go mod tidy -compat=1.19
89+
rm -f go.sum; go mod tidy -compat=1.19
9090

9191
# Run go fmt against code
9292
fmt:

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/opencontainers/go-digest v1.0.0
2121
github.com/opencontainers/go-digest/blake3 v0.0.0-20220411205349-bde1400a84be
2222
github.com/spf13/pflag v1.0.5
23+
gopkg.in/yaml.v2 v2.4.0
2324
helm.sh/helm/v3 v3.11.3
2425
k8s.io/api v0.26.3
2526
k8s.io/apiextensions-apiserver v0.26.3
@@ -159,7 +160,6 @@ require (
159160
google.golang.org/grpc v1.53.0 // indirect
160161
google.golang.org/protobuf v1.28.1 // indirect
161162
gopkg.in/inf.v0 v0.9.1 // indirect
162-
gopkg.in/yaml.v2 v2.4.0 // indirect
163163
gopkg.in/yaml.v3 v3.0.1 // indirect
164164
k8s.io/apiserver v0.26.3 // indirect
165165
k8s.io/component-base v0.26.3 // indirect

internal/util/util.go

+83-6
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,96 @@ package util
1919
import (
2020
"crypto/sha1"
2121
"fmt"
22+
"sort"
2223

23-
"helm.sh/helm/v3/pkg/chartutil"
24+
goyaml "gopkg.in/yaml.v2"
2425
"helm.sh/helm/v3/pkg/release"
26+
"sigs.k8s.io/yaml"
2527
)
2628

2729
// ValuesChecksum calculates and returns the SHA1 checksum for the
2830
// given chartutil.Values.
29-
func ValuesChecksum(values chartutil.Values) string {
30-
var s string
31-
if len(values) != 0 {
32-
s, _ = values.YAML()
31+
func ValuesChecksum(values map[string]interface{}) string {
32+
newValues := copyValues(values)
33+
var (
34+
s []byte
35+
err error
36+
)
37+
38+
if len(newValues) != 0 {
39+
// cleanUpInterfaceMap
40+
for i, value := range newValues {
41+
newValues[i] = cleanUpMapValue(value)
42+
}
43+
msValues := yaml.JSONObjectToYAMLObject(newValues)
44+
// Sort
45+
SortMapSlice(msValues)
46+
// Marshal
47+
s, err = goyaml.Marshal(msValues)
48+
if err != nil {
49+
panic(err)
50+
}
51+
}
52+
// Gethash
53+
return fmt.Sprintf("%x", sha1.Sum(s))
54+
}
55+
56+
func SortMapSlice(ms goyaml.MapSlice) {
57+
sort.Slice(ms, func(i, j int) bool {
58+
return fmt.Sprint(ms[i].Key) < fmt.Sprint(ms[j].Key)
59+
})
60+
for _, item := range ms {
61+
if nestedMS, ok := item.Value.(goyaml.MapSlice); ok {
62+
SortMapSlice(nestedMS)
63+
} else if _, ok := item.Value.([]interface{}); ok {
64+
for _, vItem := range item.Value.([]interface{}) {
65+
if itemMS, ok := vItem.(goyaml.MapSlice); ok {
66+
SortMapSlice(itemMS)
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
func cleanUpMapValue(v interface{}) interface{} {
74+
switch v := v.(type) {
75+
case []interface{}:
76+
return cleanUpInterfaceArray(v)
77+
case map[interface{}]interface{}:
78+
return cleanUpInterfaceMap(v)
79+
case string:
80+
return v
81+
default:
82+
return fmt.Sprintf("%v", v)
83+
}
84+
}
85+
86+
func cleanUpInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
87+
result := make(map[string]interface{})
88+
for k, v := range in {
89+
result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
90+
}
91+
return result
92+
}
93+
94+
func cleanUpInterfaceArray(in []interface{}) []interface{} {
95+
result := make([]interface{}, len(in))
96+
for i, v := range in {
97+
result[i] = cleanUpMapValue(v)
98+
}
99+
return result
100+
}
101+
102+
func copyValues(in map[string]interface{}) map[string]interface{} {
103+
// Marshal
104+
coppiedValues, err := goyaml.Marshal(in)
105+
if err != nil {
106+
panic(err)
33107
}
34-
return fmt.Sprintf("%x", sha1.Sum([]byte(s)))
108+
// Unmarshal
109+
newValues := make(map[string]interface{})
110+
goyaml.Unmarshal(coppiedValues, newValues)
111+
return newValues
35112
}
36113

37114
// ReleaseRevision returns the revision of the given release.Release.

internal/util/util_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package util
1818

1919
import (
20+
"reflect"
2021
"testing"
2122

23+
goyaml "gopkg.in/yaml.v2"
2224
"helm.sh/helm/v3/pkg/chartutil"
2325
"helm.sh/helm/v3/pkg/release"
2426
)
@@ -64,3 +66,133 @@ func TestReleaseRevision(t *testing.T) {
6466
t.Fatalf("ReleaseRevision() = %v, want %v", rev, 1)
6567
}
6668
}
69+
70+
func TestSortMapSlice(t *testing.T) {
71+
tests := []struct {
72+
name string
73+
input goyaml.MapSlice
74+
expected goyaml.MapSlice
75+
}{
76+
{
77+
name: "Simple case",
78+
input: goyaml.MapSlice{
79+
{Key: "b", Value: 2},
80+
{Key: "a", Value: 1},
81+
},
82+
expected: goyaml.MapSlice{
83+
{Key: "a", Value: 1},
84+
{Key: "b", Value: 2},
85+
},
86+
},
87+
{
88+
name: "Nested MapSlice",
89+
input: goyaml.MapSlice{
90+
{Key: "b", Value: 2},
91+
{Key: "a", Value: 1},
92+
{Key: "c", Value: goyaml.MapSlice{
93+
{Key: "d", Value: 4},
94+
{Key: "e", Value: 5},
95+
}},
96+
},
97+
expected: goyaml.MapSlice{
98+
{Key: "a", Value: 1},
99+
{Key: "b", Value: 2},
100+
{Key: "c", Value: goyaml.MapSlice{
101+
{Key: "d", Value: 4},
102+
{Key: "e", Value: 5},
103+
}},
104+
},
105+
},
106+
{
107+
name: "Empty MapSlice",
108+
input: goyaml.MapSlice{},
109+
expected: goyaml.MapSlice{},
110+
},
111+
{
112+
name: "Single element",
113+
input: goyaml.MapSlice{
114+
{Key: "a", Value: 1},
115+
},
116+
expected: goyaml.MapSlice{
117+
{Key: "a", Value: 1},
118+
},
119+
},
120+
{
121+
name: "Already sorted",
122+
input: goyaml.MapSlice{
123+
{Key: "a", Value: 1},
124+
{Key: "b", Value: 2},
125+
{Key: "c", Value: 3},
126+
},
127+
expected: goyaml.MapSlice{
128+
{Key: "a", Value: 1},
129+
{Key: "b", Value: 2},
130+
{Key: "c", Value: 3},
131+
},
132+
},
133+
134+
{
135+
name: "Complex Case",
136+
input: goyaml.MapSlice{
137+
{Key: "b", Value: 2},
138+
{Key: "a", Value: map[interface{}]interface{}{
139+
"d": []interface{}{4, 5},
140+
"c": 3,
141+
}},
142+
{Key: "c", Value: goyaml.MapSlice{
143+
{Key: "f", Value: 6},
144+
{Key: "e", Value: goyaml.MapSlice{
145+
{Key: "h", Value: 8},
146+
{Key: "g", Value: 7},
147+
}},
148+
}},
149+
},
150+
expected: goyaml.MapSlice{
151+
{Key: "a", Value: map[interface{}]interface{}{
152+
"c": 3,
153+
"d": []interface{}{4, 5},
154+
}},
155+
{Key: "b", Value: 2},
156+
{Key: "c", Value: goyaml.MapSlice{
157+
{Key: "e", Value: goyaml.MapSlice{
158+
{Key: "g", Value: 7},
159+
{Key: "h", Value: 8},
160+
}},
161+
{Key: "f", Value: 6},
162+
}},
163+
},
164+
},
165+
{
166+
name: "Map slice in slice",
167+
input: goyaml.MapSlice{
168+
{Key: "b", Value: 2},
169+
{Key: "a", Value: []interface{}{
170+
map[interface{}]interface{}{
171+
"d": 4,
172+
"c": 3,
173+
},
174+
1,
175+
}},
176+
},
177+
expected: goyaml.MapSlice{
178+
{Key: "a", Value: []interface{}{
179+
map[interface{}]interface{}{
180+
"c": 3,
181+
"d": 4,
182+
},
183+
1,
184+
}},
185+
{Key: "b", Value: 2},
186+
},
187+
},
188+
}
189+
190+
for _, test := range tests {
191+
t.Run(test.name, func(t *testing.T) {
192+
SortMapSlice(test.input)
193+
if !reflect.DeepEqual(test.input, test.expected) {
194+
t.Errorf("Expected %v, got %v", test.expected, test.input)
195+
}
196+
})
197+
}
198+
}

0 commit comments

Comments
 (0)