mirror of https://github.com/helm/helm
The memory driver handed out pointers into its own cache, so callers could change stored releases without an Update. The persistent drivers encode on write and decode on read, so they never expose stored data. Copy releases when storing them and when returning them from Get, List and Query, so the memory driver offers the same isolation. List copies before calling the filter, matching the persistent drivers, which decode each release before filtering. Closes helm/helm#11304 Signed-off-by: Weiyi Li <1093656961@qq.com>pull/32680/head
parent
d31cd6992f
commit
f6cc4a124e
@ -0,0 +1,223 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
rspb "helm.sh/helm/v4/internal/release/v2"
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
)
|
||||
|
||||
// copyRelease returns a deep copy of rls.
|
||||
//
|
||||
// The persistent drivers serialize releases on write and deserialize them on
|
||||
// read, so callers can never reach the stored data. The memory driver keeps
|
||||
// releases as Go values, and therefore has to copy them explicitly to offer the
|
||||
// same isolation.
|
||||
//
|
||||
// Values held in the release that Helm does not model, such as custom types
|
||||
// stored in Config, are copied as interface values rather than recursively.
|
||||
func copyRelease(rls *rspb.Release) *rspb.Release {
|
||||
if rls == nil {
|
||||
return nil
|
||||
}
|
||||
return &rspb.Release{
|
||||
Name: rls.Name,
|
||||
Info: copyInfo(rls.Info),
|
||||
Chart: copyChart(rls.Chart),
|
||||
Config: copyValues(rls.Config),
|
||||
Manifest: rls.Manifest,
|
||||
Hooks: copyHooks(rls.Hooks),
|
||||
Version: rls.Version,
|
||||
Namespace: rls.Namespace,
|
||||
Labels: maps.Clone(rls.Labels),
|
||||
ApplyMethod: rls.ApplyMethod,
|
||||
}
|
||||
}
|
||||
|
||||
func copyInfo(info *rspb.Info) *rspb.Info {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
out := *info
|
||||
if info.Resources != nil {
|
||||
out.Resources = make(map[string][]runtime.Object, len(info.Resources))
|
||||
for name, objs := range info.Resources {
|
||||
copied := make([]runtime.Object, len(objs))
|
||||
for i, obj := range objs {
|
||||
if obj != nil {
|
||||
copied[i] = obj.DeepCopyObject()
|
||||
}
|
||||
}
|
||||
out.Resources[name] = copied
|
||||
}
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
func copyHooks(hooks []*rspb.Hook) []*rspb.Hook {
|
||||
if hooks == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*rspb.Hook, len(hooks))
|
||||
for i, hook := range hooks {
|
||||
if hook == nil {
|
||||
continue
|
||||
}
|
||||
copied := *hook
|
||||
copied.Events = slices.Clone(hook.Events)
|
||||
copied.DeletePolicies = slices.Clone(hook.DeletePolicies)
|
||||
copied.OutputLogPolicies = slices.Clone(hook.OutputLogPolicies)
|
||||
out[i] = &copied
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyChart(ch *chart.Chart) *chart.Chart {
|
||||
if ch == nil {
|
||||
return nil
|
||||
}
|
||||
out := &chart.Chart{
|
||||
Raw: copyFiles(ch.Raw),
|
||||
Metadata: copyMetadata(ch.Metadata),
|
||||
Lock: copyLock(ch.Lock),
|
||||
Templates: copyFiles(ch.Templates),
|
||||
Values: copyValues(ch.Values),
|
||||
Schema: bytes.Clone(ch.Schema),
|
||||
SchemaModTime: ch.SchemaModTime,
|
||||
Files: copyFiles(ch.Files),
|
||||
ModTime: ch.ModTime,
|
||||
}
|
||||
// AddDependency sets the parent of each dependency, which rebuilds the
|
||||
// chart tree without reaching into the unexported fields.
|
||||
for _, dep := range ch.Dependencies() {
|
||||
out.AddDependency(copyChart(dep))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyMetadata(md *chart.Metadata) *chart.Metadata {
|
||||
if md == nil {
|
||||
return nil
|
||||
}
|
||||
out := *md
|
||||
out.Sources = slices.Clone(md.Sources)
|
||||
out.Keywords = slices.Clone(md.Keywords)
|
||||
out.Annotations = maps.Clone(md.Annotations)
|
||||
if md.Maintainers != nil {
|
||||
out.Maintainers = make([]*chart.Maintainer, len(md.Maintainers))
|
||||
for i, m := range md.Maintainers {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
copied := *m
|
||||
out.Maintainers[i] = &copied
|
||||
}
|
||||
}
|
||||
out.Dependencies = copyDependencies(md.Dependencies)
|
||||
return &out
|
||||
}
|
||||
|
||||
func copyLock(lock *chart.Lock) *chart.Lock {
|
||||
if lock == nil {
|
||||
return nil
|
||||
}
|
||||
out := *lock
|
||||
out.Dependencies = copyDependencies(lock.Dependencies)
|
||||
return &out
|
||||
}
|
||||
|
||||
func copyDependencies(deps []*chart.Dependency) []*chart.Dependency {
|
||||
if deps == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*chart.Dependency, len(deps))
|
||||
for i, dep := range deps {
|
||||
if dep == nil {
|
||||
continue
|
||||
}
|
||||
copied := *dep
|
||||
copied.Tags = slices.Clone(dep.Tags)
|
||||
copied.ImportValues = copyValueSlice(dep.ImportValues)
|
||||
out[i] = &copied
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyFiles(files []*common.File) []*common.File {
|
||||
if files == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*common.File, len(files))
|
||||
for i, file := range files {
|
||||
if file == nil {
|
||||
continue
|
||||
}
|
||||
copied := *file
|
||||
copied.Data = bytes.Clone(file.Data)
|
||||
out[i] = &copied
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// copyValues deep copies the maps and slices that make up decoded chart values
|
||||
// and release config. Other values are copied as interface values, which is
|
||||
// enough for the scalars these maps normally hold.
|
||||
func copyValues(values map[string]any) map[string]any {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]any, len(values))
|
||||
for key, value := range values {
|
||||
out[key] = copyValue(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyValue(value any) any {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
return copyValues(typed)
|
||||
case map[any]any:
|
||||
out := make(map[any]any, len(typed))
|
||||
for key, nested := range typed {
|
||||
out[key] = copyValue(nested)
|
||||
}
|
||||
return out
|
||||
case []any:
|
||||
return copyValueSlice(typed)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func copyValueSlice(values []any) []any {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]any, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = copyValue(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
chart "helm.sh/helm/v4/internal/chart/v3"
|
||||
rspb "helm.sh/helm/v4/internal/release/v2"
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
rcommon "helm.sh/helm/v4/pkg/release/common"
|
||||
)
|
||||
|
||||
// chartFixture builds a chart with a dependency, raw files and nested values so
|
||||
// the copy can be checked against the parts a JSON round-trip would drop.
|
||||
func chartFixture() *chart.Chart {
|
||||
ch := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "parent",
|
||||
Annotations: map[string]string{"example.com/team": "helm"},
|
||||
Maintainers: []*chart.Maintainer{{Name: "maintainer"}},
|
||||
},
|
||||
Raw: []*common.File{{Name: "Chart.yaml", Data: []byte("name: parent")}},
|
||||
Templates: []*common.File{{Name: "templates/cm.yaml", Data: []byte("kind: ConfigMap")}},
|
||||
Values: map[string]any{"nested": map[string]any{"replicas": 1}},
|
||||
Schema: []byte("schema-bytes"),
|
||||
}
|
||||
ch.AddDependency(&chart.Chart{Metadata: &chart.Metadata{Name: "child"}})
|
||||
return ch
|
||||
}
|
||||
|
||||
func releaseFixture() *rspb.Release {
|
||||
rls := releaseStub("copy-me", 1, "default", rcommon.StatusDeployed)
|
||||
rls.Chart = chartFixture()
|
||||
rls.Config = map[string]any{"nested": map[string]any{"enabled": true}}
|
||||
rls.Hooks = []*rspb.Hook{{
|
||||
Name: "pre-install",
|
||||
Events: []rspb.HookEvent{rspb.HookPreInstall},
|
||||
}}
|
||||
return rls
|
||||
}
|
||||
|
||||
func TestCopyReleaseIsIndependent(t *testing.T) {
|
||||
rls := releaseFixture()
|
||||
copied := copyRelease(rls)
|
||||
|
||||
require.NotSame(t, rls, copied)
|
||||
assert.Equal(t, rls, copied, "a copy should hold the same data as the original")
|
||||
|
||||
copied.Info.Status = rcommon.StatusFailed
|
||||
copied.Labels["key1"] = "changed"
|
||||
copied.Config["nested"].(map[string]any)["enabled"] = false
|
||||
copied.Chart.Metadata.Name = "renamed"
|
||||
copied.Chart.Metadata.Annotations["example.com/team"] = "changed"
|
||||
copied.Chart.Metadata.Maintainers[0].Name = "changed"
|
||||
copied.Chart.Values["nested"].(map[string]any)["replicas"] = 2
|
||||
copied.Chart.Raw[0].Data[0] = 'X'
|
||||
copied.Chart.Schema[0] = 'X'
|
||||
copied.Chart.Dependencies()[0].Metadata.Name = "changed"
|
||||
copied.Hooks[0].Events[0] = rspb.HookPostInstall
|
||||
|
||||
assert.Equal(t, rcommon.StatusDeployed, rls.Info.Status)
|
||||
assert.Equal(t, "val1", rls.Labels["key1"])
|
||||
assert.Equal(t, true, rls.Config["nested"].(map[string]any)["enabled"])
|
||||
assert.Equal(t, "parent", rls.Chart.Metadata.Name)
|
||||
assert.Equal(t, "helm", rls.Chart.Metadata.Annotations["example.com/team"])
|
||||
assert.Equal(t, "maintainer", rls.Chart.Metadata.Maintainers[0].Name)
|
||||
assert.Equal(t, 1, rls.Chart.Values["nested"].(map[string]any)["replicas"])
|
||||
assert.Equal(t, []byte("name: parent"), rls.Chart.Raw[0].Data)
|
||||
assert.Equal(t, []byte("schema-bytes"), rls.Chart.Schema)
|
||||
assert.Equal(t, "child", rls.Chart.Dependencies()[0].Metadata.Name)
|
||||
assert.Equal(t, rspb.HookPreInstall, rls.Hooks[0].Events[0])
|
||||
}
|
||||
|
||||
// The chart tree is rebuilt through AddDependency, so the copied dependency has
|
||||
// to point at the copied parent rather than the original one.
|
||||
func TestCopyReleaseRebuildsChartTree(t *testing.T) {
|
||||
copied := copyRelease(releaseFixture())
|
||||
|
||||
require.Len(t, copied.Chart.Dependencies(), 1)
|
||||
dep := copied.Chart.Dependencies()[0]
|
||||
assert.Same(t, copied.Chart, dep.Parent())
|
||||
assert.True(t, copied.Chart.IsRoot())
|
||||
assert.Equal(t, "parent.child", dep.ChartPath())
|
||||
}
|
||||
|
||||
func TestCopyReleaseKeepsNilFields(t *testing.T) {
|
||||
copied := copyRelease(&rspb.Release{Name: "sparse"})
|
||||
|
||||
assert.Equal(t, "sparse", copied.Name)
|
||||
assert.Nil(t, copied.Info)
|
||||
assert.Nil(t, copied.Chart)
|
||||
assert.Nil(t, copied.Config)
|
||||
assert.Nil(t, copied.Hooks)
|
||||
assert.Nil(t, copied.Labels)
|
||||
assert.Nil(t, copyRelease(nil))
|
||||
}
|
||||
@ -0,0 +1,223 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
chart "helm.sh/helm/v4/pkg/chart/v2"
|
||||
rspb "helm.sh/helm/v4/pkg/release/v1"
|
||||
)
|
||||
|
||||
// copyRelease returns a deep copy of rls.
|
||||
//
|
||||
// The persistent drivers serialize releases on write and deserialize them on
|
||||
// read, so callers can never reach the stored data. The memory driver keeps
|
||||
// releases as Go values, and therefore has to copy them explicitly to offer the
|
||||
// same isolation.
|
||||
//
|
||||
// Values held in the release that Helm does not model, such as custom types
|
||||
// stored in Config, are copied as interface values rather than recursively.
|
||||
func copyRelease(rls *rspb.Release) *rspb.Release {
|
||||
if rls == nil {
|
||||
return nil
|
||||
}
|
||||
return &rspb.Release{
|
||||
Name: rls.Name,
|
||||
Info: copyInfo(rls.Info),
|
||||
Chart: copyChart(rls.Chart),
|
||||
Config: copyValues(rls.Config),
|
||||
Manifest: rls.Manifest,
|
||||
Hooks: copyHooks(rls.Hooks),
|
||||
Version: rls.Version,
|
||||
Namespace: rls.Namespace,
|
||||
Labels: maps.Clone(rls.Labels),
|
||||
ApplyMethod: rls.ApplyMethod,
|
||||
}
|
||||
}
|
||||
|
||||
func copyInfo(info *rspb.Info) *rspb.Info {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
out := *info
|
||||
if info.Resources != nil {
|
||||
out.Resources = make(map[string][]runtime.Object, len(info.Resources))
|
||||
for name, objs := range info.Resources {
|
||||
copied := make([]runtime.Object, len(objs))
|
||||
for i, obj := range objs {
|
||||
if obj != nil {
|
||||
copied[i] = obj.DeepCopyObject()
|
||||
}
|
||||
}
|
||||
out.Resources[name] = copied
|
||||
}
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
func copyHooks(hooks []*rspb.Hook) []*rspb.Hook {
|
||||
if hooks == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*rspb.Hook, len(hooks))
|
||||
for i, hook := range hooks {
|
||||
if hook == nil {
|
||||
continue
|
||||
}
|
||||
copied := *hook
|
||||
copied.Events = slices.Clone(hook.Events)
|
||||
copied.DeletePolicies = slices.Clone(hook.DeletePolicies)
|
||||
copied.OutputLogPolicies = slices.Clone(hook.OutputLogPolicies)
|
||||
out[i] = &copied
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyChart(ch *chart.Chart) *chart.Chart {
|
||||
if ch == nil {
|
||||
return nil
|
||||
}
|
||||
out := &chart.Chart{
|
||||
Raw: copyFiles(ch.Raw),
|
||||
Metadata: copyMetadata(ch.Metadata),
|
||||
Lock: copyLock(ch.Lock),
|
||||
Templates: copyFiles(ch.Templates),
|
||||
Values: copyValues(ch.Values),
|
||||
Schema: bytes.Clone(ch.Schema),
|
||||
SchemaModTime: ch.SchemaModTime,
|
||||
Files: copyFiles(ch.Files),
|
||||
ModTime: ch.ModTime,
|
||||
}
|
||||
// AddDependency sets the parent of each dependency, which rebuilds the
|
||||
// chart tree without reaching into the unexported fields.
|
||||
for _, dep := range ch.Dependencies() {
|
||||
out.AddDependency(copyChart(dep))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyMetadata(md *chart.Metadata) *chart.Metadata {
|
||||
if md == nil {
|
||||
return nil
|
||||
}
|
||||
out := *md
|
||||
out.Sources = slices.Clone(md.Sources)
|
||||
out.Keywords = slices.Clone(md.Keywords)
|
||||
out.Annotations = maps.Clone(md.Annotations)
|
||||
if md.Maintainers != nil {
|
||||
out.Maintainers = make([]*chart.Maintainer, len(md.Maintainers))
|
||||
for i, m := range md.Maintainers {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
copied := *m
|
||||
out.Maintainers[i] = &copied
|
||||
}
|
||||
}
|
||||
out.Dependencies = copyDependencies(md.Dependencies)
|
||||
return &out
|
||||
}
|
||||
|
||||
func copyLock(lock *chart.Lock) *chart.Lock {
|
||||
if lock == nil {
|
||||
return nil
|
||||
}
|
||||
out := *lock
|
||||
out.Dependencies = copyDependencies(lock.Dependencies)
|
||||
return &out
|
||||
}
|
||||
|
||||
func copyDependencies(deps []*chart.Dependency) []*chart.Dependency {
|
||||
if deps == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*chart.Dependency, len(deps))
|
||||
for i, dep := range deps {
|
||||
if dep == nil {
|
||||
continue
|
||||
}
|
||||
copied := *dep
|
||||
copied.Tags = slices.Clone(dep.Tags)
|
||||
copied.ImportValues = copyValueSlice(dep.ImportValues)
|
||||
out[i] = &copied
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyFiles(files []*common.File) []*common.File {
|
||||
if files == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*common.File, len(files))
|
||||
for i, file := range files {
|
||||
if file == nil {
|
||||
continue
|
||||
}
|
||||
copied := *file
|
||||
copied.Data = bytes.Clone(file.Data)
|
||||
out[i] = &copied
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// copyValues deep copies the maps and slices that make up decoded chart values
|
||||
// and release config. Other values are copied as interface values, which is
|
||||
// enough for the scalars these maps normally hold.
|
||||
func copyValues(values map[string]any) map[string]any {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]any, len(values))
|
||||
for key, value := range values {
|
||||
out[key] = copyValue(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyValue(value any) any {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
return copyValues(typed)
|
||||
case map[any]any:
|
||||
out := make(map[any]any, len(typed))
|
||||
for key, nested := range typed {
|
||||
out[key] = copyValue(nested)
|
||||
}
|
||||
return out
|
||||
case []any:
|
||||
return copyValueSlice(typed)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func copyValueSlice(values []any) []any {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]any, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = copyValue(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"helm.sh/helm/v4/pkg/chart/common"
|
||||
chart "helm.sh/helm/v4/pkg/chart/v2"
|
||||
rcommon "helm.sh/helm/v4/pkg/release/common"
|
||||
rspb "helm.sh/helm/v4/pkg/release/v1"
|
||||
)
|
||||
|
||||
// chartFixture builds a chart with a dependency, raw files and nested values so
|
||||
// the copy can be checked against the parts a JSON round-trip would drop.
|
||||
func chartFixture() *chart.Chart {
|
||||
ch := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "parent",
|
||||
Annotations: map[string]string{"example.com/team": "helm"},
|
||||
Maintainers: []*chart.Maintainer{{Name: "maintainer"}},
|
||||
},
|
||||
Raw: []*common.File{{Name: "Chart.yaml", Data: []byte("name: parent")}},
|
||||
Templates: []*common.File{{Name: "templates/cm.yaml", Data: []byte("kind: ConfigMap")}},
|
||||
Values: map[string]any{"nested": map[string]any{"replicas": 1}},
|
||||
Schema: []byte("schema-bytes"),
|
||||
}
|
||||
ch.AddDependency(&chart.Chart{Metadata: &chart.Metadata{Name: "child"}})
|
||||
return ch
|
||||
}
|
||||
|
||||
func releaseFixture() *rspb.Release {
|
||||
rls := releaseStub("copy-me", 1, "default", rcommon.StatusDeployed)
|
||||
rls.Chart = chartFixture()
|
||||
rls.Config = map[string]any{"nested": map[string]any{"enabled": true}}
|
||||
rls.Hooks = []*rspb.Hook{{
|
||||
Name: "pre-install",
|
||||
Events: []rspb.HookEvent{rspb.HookPreInstall},
|
||||
}}
|
||||
return rls
|
||||
}
|
||||
|
||||
func TestCopyReleaseIsIndependent(t *testing.T) {
|
||||
rls := releaseFixture()
|
||||
copied := copyRelease(rls)
|
||||
|
||||
require.NotSame(t, rls, copied)
|
||||
assert.Equal(t, rls, copied, "a copy should hold the same data as the original")
|
||||
|
||||
copied.Info.Status = rcommon.StatusFailed
|
||||
copied.Labels["key1"] = "changed"
|
||||
copied.Config["nested"].(map[string]any)["enabled"] = false
|
||||
copied.Chart.Metadata.Name = "renamed"
|
||||
copied.Chart.Metadata.Annotations["example.com/team"] = "changed"
|
||||
copied.Chart.Metadata.Maintainers[0].Name = "changed"
|
||||
copied.Chart.Values["nested"].(map[string]any)["replicas"] = 2
|
||||
copied.Chart.Raw[0].Data[0] = 'X'
|
||||
copied.Chart.Schema[0] = 'X'
|
||||
copied.Chart.Dependencies()[0].Metadata.Name = "changed"
|
||||
copied.Hooks[0].Events[0] = rspb.HookPostInstall
|
||||
|
||||
assert.Equal(t, rcommon.StatusDeployed, rls.Info.Status)
|
||||
assert.Equal(t, "val1", rls.Labels["key1"])
|
||||
assert.Equal(t, true, rls.Config["nested"].(map[string]any)["enabled"])
|
||||
assert.Equal(t, "parent", rls.Chart.Metadata.Name)
|
||||
assert.Equal(t, "helm", rls.Chart.Metadata.Annotations["example.com/team"])
|
||||
assert.Equal(t, "maintainer", rls.Chart.Metadata.Maintainers[0].Name)
|
||||
assert.Equal(t, 1, rls.Chart.Values["nested"].(map[string]any)["replicas"])
|
||||
assert.Equal(t, []byte("name: parent"), rls.Chart.Raw[0].Data)
|
||||
assert.Equal(t, []byte("schema-bytes"), rls.Chart.Schema)
|
||||
assert.Equal(t, "child", rls.Chart.Dependencies()[0].Metadata.Name)
|
||||
assert.Equal(t, rspb.HookPreInstall, rls.Hooks[0].Events[0])
|
||||
}
|
||||
|
||||
// The chart tree is rebuilt through AddDependency, so the copied dependency has
|
||||
// to point at the copied parent rather than the original one.
|
||||
func TestCopyReleaseRebuildsChartTree(t *testing.T) {
|
||||
copied := copyRelease(releaseFixture())
|
||||
|
||||
require.Len(t, copied.Chart.Dependencies(), 1)
|
||||
dep := copied.Chart.Dependencies()[0]
|
||||
assert.Same(t, copied.Chart, dep.Parent())
|
||||
assert.True(t, copied.Chart.IsRoot())
|
||||
assert.Equal(t, "parent.child", dep.ChartPath())
|
||||
}
|
||||
|
||||
func TestCopyReleaseKeepsNilFields(t *testing.T) {
|
||||
copied := copyRelease(&rspb.Release{Name: "sparse"})
|
||||
|
||||
assert.Equal(t, "sparse", copied.Name)
|
||||
assert.Nil(t, copied.Info)
|
||||
assert.Nil(t, copied.Chart)
|
||||
assert.Nil(t, copied.Config)
|
||||
assert.Nil(t, copied.Hooks)
|
||||
assert.Nil(t, copied.Labels)
|
||||
assert.Nil(t, copyRelease(nil))
|
||||
}
|
||||
Loading…
Reference in new issue