chore: replace github.com/mitchellh/copystructure

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
pull/31342/head
Terry Howe 3 months ago
parent 61e3d95a94
commit bee9c1a108
No known key found for this signature in database

@ -25,7 +25,6 @@ require (
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/copystructure v1.2.0
github.com/moby/term v0.5.2
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
@ -112,6 +111,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/miekg/dns v1.1.57 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/spdystream v0.5.0 // indirect

@ -20,9 +20,8 @@ import (
"log/slog"
"strings"
"github.com/mitchellh/copystructure"
chart "helm.sh/helm/v4/internal/chart/v3"
"helm.sh/helm/v4/internal/copystructure"
"helm.sh/helm/v4/pkg/chart/common"
"helm.sh/helm/v4/pkg/chart/common/util"
)

@ -0,0 +1,120 @@
/*
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 copystructure
import (
"fmt"
"reflect"
)
// Copy performs a deep copy of the given interface{}.
// This implementation handles the specific use cases needed by Helm.
func Copy(src interface{}) (interface{}, error) {
if src == nil {
return make(map[string]interface{}), nil
}
return copyValue(reflect.ValueOf(src))
}
// copyValue handles copying using reflection for non-map types
func copyValue(original reflect.Value) (interface{}, error) {
switch original.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128, reflect.String, reflect.Array:
return original.Interface(), nil
case reflect.Interface:
if original.IsNil() {
return original.Interface(), nil
}
return copyValue(original.Elem())
case reflect.Map:
if original.IsNil() {
return original.Interface(), nil
}
copied := reflect.MakeMap(original.Type())
var err error
var child interface{}
iter := original.MapRange()
for iter.Next() {
key := iter.Key()
value := iter.Value()
if value.Kind() == reflect.Interface && value.IsNil() {
copied.SetMapIndex(key, value)
continue
}
child, err = copyValue(value)
if err != nil {
return nil, err
}
copied.SetMapIndex(key, reflect.ValueOf(child))
}
return copied.Interface(), nil
case reflect.Pointer:
if original.IsNil() {
return original.Interface(), nil
}
copied, err := copyValue(original.Elem())
if err != nil {
return nil, err
}
ptr := reflect.New(original.Type().Elem())
ptr.Elem().Set(reflect.ValueOf(copied))
return ptr.Interface(), nil
case reflect.Slice:
if original.IsNil() {
return original.Interface(), nil
}
copied := reflect.MakeSlice(original.Type(), original.Len(), original.Cap())
for i := 0; i < original.Len(); i++ {
val, err := copyValue(original.Index(i))
if err != nil {
return nil, err
}
copied.Index(i).Set(reflect.ValueOf(val))
}
return copied.Interface(), nil
case reflect.Struct:
copied := reflect.New(original.Type()).Elem()
for i := 0; i < original.NumField(); i++ {
elem, err := copyValue(original.Field(i))
if err != nil {
return nil, err
}
copied.Field(i).Set(reflect.ValueOf(elem))
}
return copied.Interface(), nil
case reflect.Func, reflect.Chan, reflect.UnsafePointer:
if original.IsNil() {
return original.Interface(), nil
}
return original.Interface(), nil
default:
return original.Interface(), fmt.Errorf("unsupported type %v", original)
}
}

@ -0,0 +1,374 @@
/*
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 copystructure
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCopy_Nil(t *testing.T) {
result, err := Copy(nil)
require.NoError(t, err)
assert.Equal(t, map[string]interface{}{}, result)
}
func TestCopy_PrimitiveTypes(t *testing.T) {
tests := []struct {
name string
input interface{}
}{
{"bool", true},
{"int", 42},
{"int8", int8(8)},
{"int16", int16(16)},
{"int32", int32(32)},
{"int64", int64(64)},
{"uint", uint(42)},
{"uint8", uint8(8)},
{"uint16", uint16(16)},
{"uint32", uint32(32)},
{"uint64", uint64(64)},
{"float32", float32(3.14)},
{"float64", 3.14159},
{"complex64", complex64(1 + 2i)},
{"complex128", 1 + 2i},
{"string", "hello world"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Copy(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.input, result)
})
}
}
func TestCopy_Array(t *testing.T) {
input := [3]int{1, 2, 3}
result, err := Copy(input)
require.NoError(t, err)
assert.Equal(t, input, result)
}
func TestCopy_Slice(t *testing.T) {
t.Run("slice of ints", func(t *testing.T) {
input := []int{1, 2, 3, 4, 5}
result, err := Copy(input)
require.NoError(t, err)
resultSlice, ok := result.([]int)
require.True(t, ok)
assert.Equal(t, input, resultSlice)
// Verify it's a deep copy by modifying original
input[0] = 999
assert.Equal(t, 1, resultSlice[0])
})
t.Run("slice of strings", func(t *testing.T) {
input := []string{"a", "b", "c"}
result, err := Copy(input)
require.NoError(t, err)
assert.Equal(t, input, result)
})
t.Run("nil slice", func(t *testing.T) {
var input []int
result, err := Copy(input)
require.NoError(t, err)
assert.Nil(t, result)
})
t.Run("slice of maps", func(t *testing.T) {
input := []map[string]interface{}{
{"key1": "value1"},
{"key2": "value2"},
}
result, err := Copy(input)
require.NoError(t, err)
resultSlice, ok := result.([]map[string]interface{})
require.True(t, ok)
assert.Equal(t, input, resultSlice)
// Verify deep copy
input[0]["key1"] = "modified"
assert.Equal(t, "value1", resultSlice[0]["key1"])
})
}
func TestCopy_Map(t *testing.T) {
t.Run("map[string]interface{}", func(t *testing.T) {
input := map[string]interface{}{
"string": "value",
"int": 42,
"bool": true,
"nested": map[string]interface{}{
"inner": "value",
},
}
result, err := Copy(input)
require.NoError(t, err)
resultMap, ok := result.(map[string]interface{})
require.True(t, ok)
assert.Equal(t, input, resultMap)
// Verify deep copy
input["string"] = "modified"
assert.Equal(t, "value", resultMap["string"])
nestedInput := input["nested"].(map[string]interface{})
nestedResult := resultMap["nested"].(map[string]interface{})
nestedInput["inner"] = "modified"
assert.Equal(t, "value", nestedResult["inner"])
})
t.Run("map[string]string", func(t *testing.T) {
input := map[string]string{
"key1": "value1",
"key2": "value2",
}
result, err := Copy(input)
require.NoError(t, err)
assert.Equal(t, input, result)
})
t.Run("nil map", func(t *testing.T) {
var input map[string]interface{}
result, err := Copy(input)
require.NoError(t, err)
assert.Nil(t, result)
})
t.Run("map with nil values", func(t *testing.T) {
input := map[string]interface{}{
"key1": "value1",
"key2": nil,
}
result, err := Copy(input)
require.NoError(t, err)
resultMap, ok := result.(map[string]interface{})
require.True(t, ok)
assert.Equal(t, input, resultMap)
assert.Nil(t, resultMap["key2"])
})
}
func TestCopy_Struct(t *testing.T) {
type TestStruct struct {
Name string
Age int
Active bool
Scores []int
Metadata map[string]interface{}
}
input := TestStruct{
Name: "John",
Age: 30,
Active: true,
Scores: []int{95, 87, 92},
Metadata: map[string]interface{}{
"level": "advanced",
"tags": []string{"go", "programming"},
},
}
result, err := Copy(input)
require.NoError(t, err)
resultStruct, ok := result.(TestStruct)
require.True(t, ok)
assert.Equal(t, input, resultStruct)
// Verify deep copy
input.Name = "Modified"
input.Scores[0] = 999
assert.Equal(t, "John", resultStruct.Name)
assert.Equal(t, 95, resultStruct.Scores[0])
}
func TestCopy_Pointer(t *testing.T) {
t.Run("pointer to int", func(t *testing.T) {
value := 42
input := &value
result, err := Copy(input)
require.NoError(t, err)
resultPtr, ok := result.(*int)
require.True(t, ok)
assert.Equal(t, *input, *resultPtr)
// Verify they point to different memory locations
assert.NotSame(t, input, resultPtr)
// Verify deep copy
*input = 999
assert.Equal(t, 42, *resultPtr)
})
t.Run("pointer to struct", func(t *testing.T) {
type Person struct {
Name string
Age int
}
input := &Person{Name: "Alice", Age: 25}
result, err := Copy(input)
require.NoError(t, err)
resultPtr, ok := result.(*Person)
require.True(t, ok)
assert.Equal(t, *input, *resultPtr)
assert.NotSame(t, input, resultPtr)
})
t.Run("nil pointer", func(t *testing.T) {
var input *int
result, err := Copy(input)
require.NoError(t, err)
assert.Nil(t, result)
})
}
func TestCopy_Interface(t *testing.T) {
t.Run("interface{} with value", func(t *testing.T) {
var input interface{} = "hello"
result, err := Copy(input)
require.NoError(t, err)
assert.Equal(t, input, result)
})
t.Run("nil interface{}", func(t *testing.T) {
var input interface{}
result, err := Copy(input)
require.NoError(t, err)
// Copy(nil) returns an empty map according to the implementation
assert.Equal(t, map[string]interface{}{}, result)
})
t.Run("interface{} with complex value", func(t *testing.T) {
var input interface{} = map[string]interface{}{
"key": "value",
"nested": map[string]interface{}{
"inner": 42,
},
}
result, err := Copy(input)
require.NoError(t, err)
assert.Equal(t, input, result)
})
}
func TestCopy_ComplexNested(t *testing.T) {
input := map[string]interface{}{
"users": []map[string]interface{}{
{
"name": "Alice",
"age": 30,
"addresses": []map[string]interface{}{
{"type": "home", "city": "NYC"},
{"type": "work", "city": "SF"},
},
},
{
"name": "Bob",
"age": 25,
"addresses": []map[string]interface{}{
{"type": "home", "city": "LA"},
},
},
},
"metadata": map[string]interface{}{
"version": "1.0",
"flags": []bool{true, false, true},
},
}
result, err := Copy(input)
require.NoError(t, err)
resultMap, ok := result.(map[string]interface{})
require.True(t, ok)
assert.Equal(t, input, resultMap)
// Verify deep copy by modifying nested values
users := input["users"].([]map[string]interface{})
addresses := users[0]["addresses"].([]map[string]interface{})
addresses[0]["city"] = "Modified"
resultUsers := resultMap["users"].([]map[string]interface{})
resultAddresses := resultUsers[0]["addresses"].([]map[string]interface{})
assert.Equal(t, "NYC", resultAddresses[0]["city"])
}
func TestCopy_Functions(t *testing.T) {
t.Run("function", func(t *testing.T) {
input := func() string { return "hello" }
result, err := Copy(input)
require.NoError(t, err)
// Functions should be copied as-is (same reference)
resultFunc, ok := result.(func() string)
require.True(t, ok)
assert.Equal(t, input(), resultFunc())
})
t.Run("nil function", func(t *testing.T) {
var input func()
result, err := Copy(input)
require.NoError(t, err)
assert.Nil(t, result)
})
}
func TestCopy_Channels(t *testing.T) {
t.Run("channel", func(t *testing.T) {
input := make(chan int, 1)
input <- 42
result, err := Copy(input)
require.NoError(t, err)
// Channels should be copied as-is (same reference)
resultChan, ok := result.(chan int)
require.True(t, ok)
// Since channels are copied as references, verify we can read from the result channel
value := <-resultChan
assert.Equal(t, 42, value)
})
t.Run("nil channel", func(t *testing.T) {
var input chan int
result, err := Copy(input)
require.NoError(t, err)
assert.Nil(t, result)
})
}

@ -21,8 +21,7 @@ import (
"log"
"maps"
"github.com/mitchellh/copystructure"
"helm.sh/helm/v4/internal/copystructure"
chart "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chart/common"
)

@ -20,8 +20,7 @@ import (
"log/slog"
"strings"
"github.com/mitchellh/copystructure"
"helm.sh/helm/v4/internal/copystructure"
"helm.sh/helm/v4/pkg/chart/common"
"helm.sh/helm/v4/pkg/chart/common/util"
chart "helm.sh/helm/v4/pkg/chart/v2"

Loading…
Cancel
Save