Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(helm): properly compare existing and candidate releases #4937

Merged
merged 1 commit into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/fragments/helm-randalphanum.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
entries:
- description: >
For Helm-based operators, fixed a bug where deployed and candidate release comparison was always false when an RNG was used to derive some manifest value, resulting in the chart release constantly upgrading
kind: bugfix
32 changes: 16 additions & 16 deletions internal/helm/release/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"helm.sh/helm/v3/pkg/storage/driver"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -125,14 +126,7 @@ func (m *manager) Sync(ctx context.Context) error {
m.deployedRelease = deployedRelease
m.isInstalled = true

// Get the next candidate release to determine if an upgrade is necessary.
candidateRelease, err := m.getCandidateRelease(m.namespace, m.releaseName, m.chart, m.values)
if err != nil {
return fmt.Errorf("failed to get candidate release: %w", err)
}
if deployedRelease.Manifest != candidateRelease.Manifest {
m.isUpgradeRequired = true
}
m.isUpgradeRequired = m.isUpgrade(deployedRelease)

return nil
}
Expand All @@ -141,6 +135,20 @@ func notFoundErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "not found")
}

func (m manager) isUpgrade(deployedRelease *rpb.Release) bool {
if deployedRelease == nil {
return false
}

// Judging whether to skip updates
skip := m.namespace == deployedRelease.Namespace
skip = skip && m.releaseName == deployedRelease.Name
skip = skip && apiequality.Semantic.DeepEqual(m.chart, deployedRelease.Chart)
skip = skip && apiequality.Semantic.DeepEqual(m.values, deployedRelease.Config)

return !skip
}

func (m manager) getDeployedRelease() (*rpb.Release, error) {
deployedRelease, err := m.storageBackend.Deployed(m.releaseName)
if err != nil {
Expand All @@ -152,14 +160,6 @@ func (m manager) getDeployedRelease() (*rpb.Release, error) {
return deployedRelease, nil
}

func (m manager) getCandidateRelease(namespace, name string, chart *cpb.Chart,
values map[string]interface{}) (*rpb.Release, error) {
upgrade := action.NewUpgrade(m.actionConfig)
upgrade.Namespace = namespace
upgrade.DryRun = true
return upgrade.Run(name, chart, values)
}

// InstallRelease performs a Helm release install.
func (m manager) InstallRelease(ctx context.Context, opts ...InstallOption) (*rpb.Release, error) {
install := action.NewInstall(m.actionConfig)
Expand Down
82 changes: 76 additions & 6 deletions internal/helm/release/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ package release
import (
"testing"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/resource"

"github.com/stretchr/testify/assert"
cpb "helm.sh/helm/v3/pkg/chart"
lpb "helm.sh/helm/v3/pkg/chart/loader"
rpb "helm.sh/helm/v3/pkg/release"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/resource"
)

func newTestUnstructured(containers []interface{}) *unstructured.Unstructured {
Expand Down Expand Up @@ -213,3 +214,72 @@ func TestManagerGenerateStrategicMergePatch(t *testing.T) {
assert.Equal(t, test.patch, string(diff))
}
}

func TestManagerisUpgrade(t *testing.T) {
tests := []struct {
name string
releaseName string
releaseNs string
values map[string]interface{}
chart *cpb.Chart
deployedRelease *rpb.Release
want bool
}{
{
name: "ok",
releaseName: "deployed",
releaseNs: "deployed-ns",
values: map[string]interface{}{"key": "value"},
chart: newTestChart(t, "./testdata/simple"),
deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": "value"}, "deployed", "deployed-ns"),
want: false,
},
{
name: "different chart",
releaseName: "deployed",
releaseNs: "deployed-ns",
values: map[string]interface{}{"key": "value"},
chart: newTestChart(t, "./testdata/simple"),
deployedRelease: newTestRelease(newTestChart(t, "./testdata/simpledf"), map[string]interface{}{"key": "value"}, "deployed", "deployed-ns"),
want: true,
},
{
name: "different values",
releaseName: "deployed",
releaseNs: "deployed-ns",
values: map[string]interface{}{"key": "1"},
chart: newTestChart(t, "./testdata/simple"),
deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": ""}, "deployed", "deployed-ns"),
want: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
m := manager{
releaseName: test.releaseName,
namespace: test.releaseNs,
values: test.values,
chart: test.chart,
}
isUpgrade := m.isUpgrade(test.deployedRelease)
assert.Equal(t, test.want, isUpgrade)
})
}
}

func newTestChart(t *testing.T, path string) *cpb.Chart {
chart, err := lpb.Load(path)
assert.Nil(t, err)
return chart
}

func newTestRelease(chart *cpb.Chart, values map[string]interface{}, name, namespace string) *rpb.Release {
release := rpb.Mock(&rpb.MockReleaseOptions{
Name: name,
Namespace: namespace,
Chart: chart,
Version: 1,
})
release.Config = values
return release
}
21 changes: 21 additions & 0 deletions internal/helm/release/testdata/simple/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
8 changes: 8 additions & 0 deletions internal/helm/release/testdata/simple/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
description: a simple charts
name: simple
keywords:
- helm
- operator-sdk
version: 0.0.1+master
appVersion: "master"
1 change: 1 addition & 0 deletions internal/helm/release/testdata/simple/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A simple charts
7 changes: 7 additions & 0 deletions internal/helm/release/testdata/simple/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "simple.fullname" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
8 changes: 8 additions & 0 deletions internal/helm/release/testdata/simple/templates/secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ template "simple.fullname" . }}
namespace: {{ .Release.Namespace }}
type: Opaque
data:
password: {{ randAlphaNum 10 | b64enc | quote }}
1 change: 1 addition & 0 deletions internal/helm/release/testdata/simple/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# simple values
21 changes: 21 additions & 0 deletions internal/helm/release/testdata/simpledf/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
8 changes: 8 additions & 0 deletions internal/helm/release/testdata/simpledf/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
description: a simple charts
name: simple
keywords:
- helm
- operator-sdk
version: 0.0.1+master
appVersion: "master"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A simple charts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "simple.fullname" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ template "simple.fullname" . }}
namespace: {{ .Release.Namespace }}
type: Opaque
data:
password: {{ randAlphaNum 10 | b64enc | quote }}
2 changes: 2 additions & 0 deletions internal/helm/release/testdata/simpledf/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# simple values
name: simpledf