Skip to content

Commit e8e6009

Browse files
committed
fix: issue-5041 (#5042)
Signed-off-by: cndoit18 <[email protected]>
1 parent 2781cf8 commit e8e6009

File tree

3 files changed

+80
-14
lines changed

3 files changed

+80
-14
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
entries:
2+
- description: >
3+
For Helm-based operators, fixed release equality comparison such that number values are compared and not their types to avoid unnecessary reconciliations.
4+
kind: bugfix

internal/helm/release/manager.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"errors"
2222
"fmt"
23+
"reflect"
2324
"strings"
2425

2526
jsonpatch "gomodules.xyz/jsonpatch/v3"
@@ -126,27 +127,64 @@ func (m *manager) Sync(ctx context.Context) error {
126127
m.deployedRelease = deployedRelease
127128
m.isInstalled = true
128129

129-
m.isUpgradeRequired = m.isUpgrade(deployedRelease)
130-
130+
m.isUpgradeRequired, err = m.isUpgrade(deployedRelease)
131+
if err != nil {
132+
return fmt.Errorf("failed to get upgrade status: %w", err)
133+
}
131134
return nil
132135
}
133136

134137
func notFoundErr(err error) bool {
135138
return err != nil && strings.Contains(err.Error(), "not found")
136139
}
137140

138-
func (m manager) isUpgrade(deployedRelease *rpb.Release) bool {
141+
func (m manager) getCandidateRelease(namespace, name string, chart *cpb.Chart,
142+
values map[string]interface{}) (*rpb.Release, error) {
143+
upgrade := action.NewUpgrade(m.actionConfig)
144+
upgrade.Namespace = namespace
145+
upgrade.DryRun = true
146+
return upgrade.Run(name, chart, values)
147+
}
148+
149+
// This is caused by the different logic of loading from local and loading from secret
150+
// For example, the Raw field, which has the tag `json:"-"`, causes the Unmarshal to be lost when it into Release
151+
// We need to make them follow the JSON tag
152+
// see: https://github.com/helm/helm/blob/cf0c6fed519d48101cd69ce01a355125215ee46f/pkg/storage/driver/util.go#L81
153+
func equalJSONStruct(a, b interface{}) (bool, error) {
154+
if reflect.ValueOf(a).IsNil() || reflect.ValueOf(b).IsNil() {
155+
return apiequality.Semantic.DeepEqual(a, b), nil
156+
}
157+
158+
aBuf, bBuf := &bytes.Buffer{}, &bytes.Buffer{}
159+
err := json.NewEncoder(aBuf).Encode(a)
160+
if err != nil {
161+
return false, err
162+
}
163+
err = json.NewEncoder(bBuf).Encode(b)
164+
return aBuf.String() == bBuf.String(), err
165+
}
166+
167+
func (m manager) isUpgrade(deployedRelease *rpb.Release) (bool, error) {
139168
if deployedRelease == nil {
140-
return false
169+
return false, nil
170+
}
171+
172+
candidateRelease, err := m.getCandidateRelease(m.namespace, m.releaseName, m.chart, m.values)
173+
if err != nil {
174+
return false, err
141175
}
142176

143177
// Judging whether to skip updates
144-
skip := m.namespace == deployedRelease.Namespace
145-
skip = skip && m.releaseName == deployedRelease.Name
146-
skip = skip && apiequality.Semantic.DeepEqual(m.chart, deployedRelease.Chart)
147-
skip = skip && apiequality.Semantic.DeepEqual(m.values, deployedRelease.Config)
178+
skip := candidateRelease.Namespace == deployedRelease.Namespace
179+
skip = skip && candidateRelease.Name == deployedRelease.Name
148180

149-
return !skip
181+
ok, err := equalJSONStruct(m.chart, deployedRelease.Chart)
182+
if err != nil {
183+
return false, err
184+
}
185+
skip = skip && ok
186+
ok, err = equalJSONStruct(m.values, deployedRelease.Config)
187+
return !(skip && ok), err
150188
}
151189

152190
func (m manager) getDeployedRelease() (*rpb.Release, error) {

internal/helm/release/manager_test.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@
1515
package release
1616

1717
import (
18+
"io/ioutil"
1819
"testing"
1920

2021
"github.com/stretchr/testify/assert"
22+
"helm.sh/helm/v3/pkg/action"
2123
cpb "helm.sh/helm/v3/pkg/chart"
2224
lpb "helm.sh/helm/v3/pkg/chart/loader"
25+
"helm.sh/helm/v3/pkg/chartutil"
26+
kubefake "helm.sh/helm/v3/pkg/kube/fake"
2327
rpb "helm.sh/helm/v3/pkg/release"
28+
"helm.sh/helm/v3/pkg/storage"
29+
"helm.sh/helm/v3/pkg/storage/driver"
2430
appsv1 "k8s.io/api/apps/v1"
2531
v1 "k8s.io/api/core/v1"
2632
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -247,11 +253,20 @@ func TestManagerisUpgrade(t *testing.T) {
247253
name: "different values",
248254
releaseName: "deployed",
249255
releaseNs: "deployed-ns",
250-
values: map[string]interface{}{"key": "1"},
256+
values: map[string]interface{}{"key": "1", "int": int32(1)},
251257
chart: newTestChart(t, "./testdata/simple"),
252-
deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": ""}, "deployed", "deployed-ns"),
258+
deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": "", "int": int64(1)}, "deployed", "deployed-ns"),
253259
want: true,
254260
},
261+
{
262+
name: "nil values",
263+
releaseName: "deployed",
264+
releaseNs: "deployed-ns",
265+
values: nil,
266+
chart: newTestChart(t, "./testdata/simple"),
267+
deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{}, "deployed", "deployed-ns"),
268+
want: false,
269+
},
255270
}
256271
for _, test := range tests {
257272
t.Run(test.name, func(t *testing.T) {
@@ -260,9 +275,17 @@ func TestManagerisUpgrade(t *testing.T) {
260275
namespace: test.releaseNs,
261276
values: test.values,
262277
chart: test.chart,
278+
actionConfig: &action.Configuration{
279+
Releases: storage.Init(driver.NewMemory()),
280+
KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}},
281+
Capabilities: chartutil.DefaultCapabilities,
282+
Log: t.Logf,
283+
},
263284
}
264-
isUpgrade := m.isUpgrade(test.deployedRelease)
285+
assert.Equal(t, nil, m.actionConfig.Releases.Create(test.deployedRelease))
286+
isUpgrade, err := m.isUpgrade(test.deployedRelease)
265287
assert.Equal(t, test.want, isUpgrade)
288+
assert.Equal(t, nil, err)
266289
})
267290
}
268291
}
@@ -273,13 +296,14 @@ func newTestChart(t *testing.T, path string) *cpb.Chart {
273296
return chart
274297
}
275298

276-
func newTestRelease(chart *cpb.Chart, values map[string]interface{}, name, namespace string) *rpb.Release {
299+
func newTestRelease(chart *cpb.Chart, values map[string]interface{}, name, namespace string) *rpb.Release { // nolint: unparam
277300
release := rpb.Mock(&rpb.MockReleaseOptions{
278301
Name: name,
279302
Namespace: namespace,
280-
Chart: chart,
281303
Version: 1,
304+
Chart: chart,
282305
})
306+
283307
release.Config = values
284308
return release
285309
}

0 commit comments

Comments
 (0)