Introduce new `yaml` package with `Encode` func
Comparison versus `sigs.k8s.io/yaml#Marshal`: ``` BenchmarkEncode/EncodeWithSort-12 475 2419063 ns/op 2235305 B/op 5398 allocs/op BenchmarkEncode/EncodeWithSort-12 498 2406794 ns/op 2235300 B/op 5398 allocs/op BenchmarkEncode/EncodeWithSort-12 492 2376460 ns/op 2235312 B/op 5398 allocs/op BenchmarkEncode/EncodeWithSort-12 496 2406756 ns/op 2235323 B/op 5398 allocs/op BenchmarkEncode/EncodeWithSort-12 488 2402969 ns/op 2235336 B/op 5398 allocs/op BenchmarkEncode/SigYAMLMarshal-12 202 5791549 ns/op 3124841 B/op 19324 allocs/op BenchmarkEncode/SigYAMLMarshal-12 205 5780248 ns/op 3123193 B/op 19320 allocs/op BenchmarkEncode/SigYAMLMarshal-12 207 5762621 ns/op 3124537 B/op 19324 allocs/op BenchmarkEncode/SigYAMLMarshal-12 214 5748899 ns/op 3121183 B/op 19324 allocs/op BenchmarkEncode/SigYAMLMarshal-12 211 5682105 ns/op 3120592 B/op 19325 allocs/op ``` Signed-off-by: Hidde Beydals <hidde@hhh.computer>
This commit is contained in:
		
							parent
							
								
									bb4e9b7cee
								
							
						
					
					
						commit
						eee91b06fa
					
				| 
						 | 
				
			
			@ -8,6 +8,7 @@ require (
 | 
			
		|||
	k8s.io/apiextensions-apiserver v0.27.4
 | 
			
		||||
	k8s.io/apimachinery v0.27.4
 | 
			
		||||
	sigs.k8s.io/controller-runtime v0.15.1
 | 
			
		||||
	sigs.k8s.io/yaml v1.3.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -103,3 +103,4 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h6
 | 
			
		|||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
 | 
			
		||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
 | 
			
		||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
 | 
			
		||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,6 @@ limitations under the License.
 | 
			
		|||
package v2beta2
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +24,7 @@ import (
 | 
			
		|||
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/types"
 | 
			
		||||
	"sigs.k8s.io/yaml"
 | 
			
		||||
 | 
			
		||||
	"github.com/fluxcd/pkg/apis/kustomize"
 | 
			
		||||
	"github.com/fluxcd/pkg/apis/meta"
 | 
			
		||||
| 
						 | 
				
			
			@ -1118,7 +1118,7 @@ func (in HelmRelease) GetRequeueAfter() time.Duration {
 | 
			
		|||
func (in HelmRelease) GetValues() map[string]interface{} {
 | 
			
		||||
	var values map[string]interface{}
 | 
			
		||||
	if in.Spec.Values != nil {
 | 
			
		||||
		_ = json.Unmarshal(in.Spec.Values.Raw, &values)
 | 
			
		||||
		_ = yaml.Unmarshal(in.Spec.Values.Raw, &values)
 | 
			
		||||
	}
 | 
			
		||||
	return values
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,14 +19,18 @@ package chartutil
 | 
			
		|||
import (
 | 
			
		||||
	"github.com/opencontainers/go-digest"
 | 
			
		||||
	"helm.sh/helm/v3/pkg/chartutil"
 | 
			
		||||
 | 
			
		||||
	intyaml "github.com/fluxcd/helm-controller/internal/yaml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DigestValues calculates the digest of the values using the provided algorithm.
 | 
			
		||||
// The caller is responsible for ensuring that the algorithm is supported.
 | 
			
		||||
func DigestValues(algo digest.Algorithm, values chartutil.Values) digest.Digest {
 | 
			
		||||
	digester := algo.Digester()
 | 
			
		||||
	if err := values.Encode(digester.Hash()); err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	if values = valuesOrNil(values); values != nil {
 | 
			
		||||
		if err := intyaml.Encode(digester.Hash(), values, intyaml.SortMapSlice); err != nil {
 | 
			
		||||
			return ""
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return digester.Digest()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -36,9 +40,22 @@ func VerifyValues(digest digest.Digest, values chartutil.Values) bool {
 | 
			
		|||
	if digest.Validate() != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	verifier := digest.Verifier()
 | 
			
		||||
	if err := values.Encode(verifier); err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	if values = valuesOrNil(values); values != nil {
 | 
			
		||||
		if err := intyaml.Encode(verifier, values, intyaml.SortMapSlice); err != nil {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return verifier.Verified()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// valuesOrNil returns nil if the values are empty, otherwise the values are
 | 
			
		||||
// returned. This is used to ensure that the digest is calculated against nil
 | 
			
		||||
// opposed to an empty object.
 | 
			
		||||
func valuesOrNil(values chartutil.Values) chartutil.Values {
 | 
			
		||||
	if values != nil && len(values) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return values
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,45 +23,222 @@ import (
 | 
			
		|||
	"helm.sh/helm/v3/pkg/chartutil"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const testDigestAlgo = digest.SHA256
 | 
			
		||||
 | 
			
		||||
func TestDigestValues(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		algo   digest.Algorithm
 | 
			
		||||
		values chartutil.Values
 | 
			
		||||
		want   digest.Digest
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:   "empty",
 | 
			
		||||
			algo:   digest.SHA256,
 | 
			
		||||
			values: chartutil.Values{},
 | 
			
		||||
			want:   "sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
 | 
			
		||||
			want:   "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "nil",
 | 
			
		||||
			algo:   digest.SHA256,
 | 
			
		||||
			values: nil,
 | 
			
		||||
			want:   "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "value map",
 | 
			
		||||
			algo: digest.SHA256,
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"foo": "bar",
 | 
			
		||||
				"baz": map[string]string{
 | 
			
		||||
					"cool": "stuff",
 | 
			
		||||
				"replicas": 3,
 | 
			
		||||
				"image": map[string]interface{}{
 | 
			
		||||
					"tag":        "latest",
 | 
			
		||||
					"repository": "nginx",
 | 
			
		||||
				},
 | 
			
		||||
				"ports": []interface{}{
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"protocol": "TCP",
 | 
			
		||||
						"port":     8080,
 | 
			
		||||
					},
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"port":     9090,
 | 
			
		||||
						"protocol": "UDP",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: "sha256:3f3641788a2d4abda3534eaa90c90b54916e4c6e3a5b2e1b24758b7bfa701ecd",
 | 
			
		||||
			want: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "value map in different order",
 | 
			
		||||
			algo: digest.SHA256,
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"baz": map[string]string{
 | 
			
		||||
				"image": map[string]interface{}{
 | 
			
		||||
					"repository": "nginx",
 | 
			
		||||
					"tag":        "latest",
 | 
			
		||||
				},
 | 
			
		||||
				"ports": []interface{}{
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"port":     8080,
 | 
			
		||||
						"protocol": "TCP",
 | 
			
		||||
					},
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"port":     9090,
 | 
			
		||||
						"protocol": "UDP",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"replicas": 3,
 | 
			
		||||
			},
 | 
			
		||||
			want: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Explicit test for something that does not work with sigs.k8s.io/yaml.
 | 
			
		||||
			// See: https://go.dev/play/p/KRyfK9ZobZx
 | 
			
		||||
			name: "values map with numeric keys",
 | 
			
		||||
			algo: digest.SHA256,
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"replicas": 3,
 | 
			
		||||
				"test": map[string]interface{}{
 | 
			
		||||
					"632bd80235a05f4192aefade": "value1",
 | 
			
		||||
					"632bd80ddf416cf32fd50679": "value2",
 | 
			
		||||
					"632bd817c559818a52307da2": "value3",
 | 
			
		||||
					"632bd82398e71231a98004b6": "value4",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: "sha256:8a980fcbeadd6f05818f07e8aec14070c22250ca3d96af1fcd5f93b3e85b4d70",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "values map with numeric keys in different order",
 | 
			
		||||
			algo: digest.SHA256,
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"test": map[string]interface{}{
 | 
			
		||||
					"632bd82398e71231a98004b6": "value4",
 | 
			
		||||
					"632bd817c559818a52307da2": "value3",
 | 
			
		||||
					"632bd80ddf416cf32fd50679": "value2",
 | 
			
		||||
					"632bd80235a05f4192aefade": "value1",
 | 
			
		||||
				},
 | 
			
		||||
				"replicas": 3,
 | 
			
		||||
			},
 | 
			
		||||
			want: "sha256:8a980fcbeadd6f05818f07e8aec14070c22250ca3d96af1fcd5f93b3e85b4d70",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "using different algorithm",
 | 
			
		||||
			algo: digest.SHA512,
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"foo": "bar",
 | 
			
		||||
				"baz": map[string]interface{}{
 | 
			
		||||
					"cool": "stuff",
 | 
			
		||||
				},
 | 
			
		||||
				"foo": "bar",
 | 
			
		||||
			},
 | 
			
		||||
			want: "sha256:3f3641788a2d4abda3534eaa90c90b54916e4c6e3a5b2e1b24758b7bfa701ecd",
 | 
			
		||||
			want: "sha512:b5f9cd4855ca3b08afd602557f373069b1732ce2e6d52341481b0d38f1938452e9d7759ab177c66699962b592f20ceded03eea3cd405d8670578c47842e2c550",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := DigestValues(testDigestAlgo, tt.values); got != tt.want {
 | 
			
		||||
			if got := DigestValues(tt.algo, tt.values); got != tt.want {
 | 
			
		||||
				t.Errorf("DigestValues() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyValues(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		digest digest.Digest
 | 
			
		||||
		values chartutil.Values
 | 
			
		||||
		want   bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:   "empty values",
 | 
			
		||||
			digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
 | 
			
		||||
			values: chartutil.Values{},
 | 
			
		||||
			want:   true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "nil values",
 | 
			
		||||
			digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
 | 
			
		||||
			values: nil,
 | 
			
		||||
			want:   true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "empty digest",
 | 
			
		||||
			digest: "",
 | 
			
		||||
			want:   false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "invalid digest",
 | 
			
		||||
			digest: "sha512:invalid",
 | 
			
		||||
			values: nil,
 | 
			
		||||
			want:   false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "matching values",
 | 
			
		||||
			digest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"image": map[string]interface{}{
 | 
			
		||||
					"repository": "nginx",
 | 
			
		||||
					"tag":        "latest",
 | 
			
		||||
				},
 | 
			
		||||
				"ports": []interface{}{
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"port":     8080,
 | 
			
		||||
						"protocol": "TCP",
 | 
			
		||||
					},
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"port":     9090,
 | 
			
		||||
						"protocol": "UDP",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"replicas": 3,
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "matching values in different order",
 | 
			
		||||
			digest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6",
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"replicas": 3,
 | 
			
		||||
				"image": map[string]interface{}{
 | 
			
		||||
					"tag":        "latest",
 | 
			
		||||
					"repository": "nginx",
 | 
			
		||||
				},
 | 
			
		||||
				"ports": []interface{}{
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"protocol": "TCP",
 | 
			
		||||
						"port":     8080,
 | 
			
		||||
					},
 | 
			
		||||
					map[string]interface{}{
 | 
			
		||||
						"port":     9090,
 | 
			
		||||
						"protocol": "UDP",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "matching values with numeric keys",
 | 
			
		||||
			digest: "sha256:8a980fcbeadd6f05818f07e8aec14070c22250ca3d96af1fcd5f93b3e85b4d70",
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"replicas": 3,
 | 
			
		||||
				"test": map[string]interface{}{
 | 
			
		||||
					"632bd80235a05f4192aefade": "value1",
 | 
			
		||||
					"632bd80ddf416cf32fd50679": "value2",
 | 
			
		||||
					"632bd817c559818a52307da2": "value3",
 | 
			
		||||
					"632bd82398e71231a98004b6": "value4",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:   "mismatching values",
 | 
			
		||||
			digest: "sha256:3f3641788a2d4abda3534eaa90c90b54916e4c6e3a5b2e1b24758b7bfa701ecd",
 | 
			
		||||
			values: chartutil.Values{
 | 
			
		||||
				"foo": "bar",
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := VerifyValues(tt.digest, tt.values); got != tt.want {
 | 
			
		||||
				t.Errorf("VerifyValues() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,14 +17,12 @@ limitations under the License.
 | 
			
		|||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/sha1"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	goyaml "gopkg.in/yaml.v2"
 | 
			
		||||
	intyaml "github.com/fluxcd/helm-controller/internal/yaml"
 | 
			
		||||
	"helm.sh/helm/v3/pkg/chartutil"
 | 
			
		||||
	"helm.sh/helm/v3/pkg/release"
 | 
			
		||||
	"sigs.k8s.io/yaml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ValuesChecksum calculates and returns the SHA1 checksum for the
 | 
			
		||||
| 
						 | 
				
			
			@ -40,76 +38,11 @@ func ValuesChecksum(values chartutil.Values) string {
 | 
			
		|||
// OrderedValuesChecksum sort the chartutil.Values then calculates
 | 
			
		||||
// and returns the SHA1 checksum for the sorted values.
 | 
			
		||||
func OrderedValuesChecksum(values chartutil.Values) string {
 | 
			
		||||
	var s []byte
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	if len(values) != 0 {
 | 
			
		||||
		msValues := yaml.JSONObjectToYAMLObject(copyValues(values))
 | 
			
		||||
		SortMapSlice(msValues)
 | 
			
		||||
		s, _ = goyaml.Marshal(msValues)
 | 
			
		||||
		_ = intyaml.Encode(&buf, values, intyaml.SortMapSlice)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%x", sha1.Sum(s))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SortMapSlice recursively sorts the given goyaml.MapSlice by key.
 | 
			
		||||
// This is used to ensure that the values checksum is the same regardless
 | 
			
		||||
// of the order of the keys in the values file.
 | 
			
		||||
func SortMapSlice(ms goyaml.MapSlice) {
 | 
			
		||||
	sort.Slice(ms, func(i, j int) bool {
 | 
			
		||||
		return fmt.Sprint(ms[i].Key) < fmt.Sprint(ms[j].Key)
 | 
			
		||||
	})
 | 
			
		||||
	for _, item := range ms {
 | 
			
		||||
		if nestedMS, ok := item.Value.(goyaml.MapSlice); ok {
 | 
			
		||||
			SortMapSlice(nestedMS)
 | 
			
		||||
		} else if _, ok := item.Value.([]interface{}); ok {
 | 
			
		||||
			for _, vItem := range item.Value.([]interface{}) {
 | 
			
		||||
				if itemMS, ok := vItem.(goyaml.MapSlice); ok {
 | 
			
		||||
					SortMapSlice(itemMS)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cleanUpMapValue changes all instances of
 | 
			
		||||
// map[interface{}]interface{} to map[string]interface{}.
 | 
			
		||||
// This is for handling the mismatch when unmarshaling
 | 
			
		||||
// reference to the issue: https://github.com/go-yaml/yaml/issues/139
 | 
			
		||||
func cleanUpMapValue(v interface{}) interface{} {
 | 
			
		||||
	switch v := v.(type) {
 | 
			
		||||
	case []interface{}:
 | 
			
		||||
		return cleanUpInterfaceArray(v)
 | 
			
		||||
	case map[interface{}]interface{}:
 | 
			
		||||
		return cleanUpInterfaceMap(v)
 | 
			
		||||
	default:
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cleanUpInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
 | 
			
		||||
	result := make(map[string]interface{})
 | 
			
		||||
	for k, v := range in {
 | 
			
		||||
		result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cleanUpInterfaceArray(in []interface{}) []interface{} {
 | 
			
		||||
	result := make([]interface{}, len(in))
 | 
			
		||||
	for i, v := range in {
 | 
			
		||||
		result[i] = cleanUpMapValue(v)
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyValues(in map[string]interface{}) map[string]interface{} {
 | 
			
		||||
	copiedValues, _ := goyaml.Marshal(in)
 | 
			
		||||
	newValues := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	_ = goyaml.Unmarshal(copiedValues, newValues)
 | 
			
		||||
	for i, value := range newValues {
 | 
			
		||||
		newValues[i] = cleanUpMapValue(value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return newValues
 | 
			
		||||
	return fmt.Sprintf("%x", sha1.Sum(buf.Bytes()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReleaseRevision returns the revision of the given release.Release.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,10 +17,8 @@ limitations under the License.
 | 
			
		|||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	goyaml "gopkg.in/yaml.v2"
 | 
			
		||||
	"helm.sh/helm/v3/pkg/chartutil"
 | 
			
		||||
	"helm.sh/helm/v3/pkg/release"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -98,133 +96,3 @@ func TestReleaseRevision(t *testing.T) {
 | 
			
		|||
		t.Fatalf("ReleaseRevision() = %v, want %v", rev, 1)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSortMapSlice(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		input    goyaml.MapSlice
 | 
			
		||||
		expected goyaml.MapSlice
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Simple case",
 | 
			
		||||
			input: goyaml.MapSlice{
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
			},
 | 
			
		||||
			expected: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Nested MapSlice",
 | 
			
		||||
			input: goyaml.MapSlice{
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
				{Key: "c", Value: goyaml.MapSlice{
 | 
			
		||||
					{Key: "d", Value: 4},
 | 
			
		||||
					{Key: "e", Value: 5},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "c", Value: goyaml.MapSlice{
 | 
			
		||||
					{Key: "d", Value: 4},
 | 
			
		||||
					{Key: "e", Value: 5},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Empty MapSlice",
 | 
			
		||||
			input:    goyaml.MapSlice{},
 | 
			
		||||
			expected: goyaml.MapSlice{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Single element",
 | 
			
		||||
			input: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
			},
 | 
			
		||||
			expected: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Already sorted",
 | 
			
		||||
			input: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "c", Value: 3},
 | 
			
		||||
			},
 | 
			
		||||
			expected: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: 1},
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "c", Value: 3},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			name: "Complex Case",
 | 
			
		||||
			input: goyaml.MapSlice{
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "a", Value: map[interface{}]interface{}{
 | 
			
		||||
					"d": []interface{}{4, 5},
 | 
			
		||||
					"c": 3,
 | 
			
		||||
				}},
 | 
			
		||||
				{Key: "c", Value: goyaml.MapSlice{
 | 
			
		||||
					{Key: "f", Value: 6},
 | 
			
		||||
					{Key: "e", Value: goyaml.MapSlice{
 | 
			
		||||
						{Key: "h", Value: 8},
 | 
			
		||||
						{Key: "g", Value: 7},
 | 
			
		||||
					}},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: map[interface{}]interface{}{
 | 
			
		||||
					"c": 3,
 | 
			
		||||
					"d": []interface{}{4, 5},
 | 
			
		||||
				}},
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "c", Value: goyaml.MapSlice{
 | 
			
		||||
					{Key: "e", Value: goyaml.MapSlice{
 | 
			
		||||
						{Key: "g", Value: 7},
 | 
			
		||||
						{Key: "h", Value: 8},
 | 
			
		||||
					}},
 | 
			
		||||
					{Key: "f", Value: 6},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Map slice in slice",
 | 
			
		||||
			input: goyaml.MapSlice{
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
				{Key: "a", Value: []interface{}{
 | 
			
		||||
					map[interface{}]interface{}{
 | 
			
		||||
						"d": 4,
 | 
			
		||||
						"c": 3,
 | 
			
		||||
					},
 | 
			
		||||
					1,
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: goyaml.MapSlice{
 | 
			
		||||
				{Key: "a", Value: []interface{}{
 | 
			
		||||
					map[interface{}]interface{}{
 | 
			
		||||
						"c": 3,
 | 
			
		||||
						"d": 4,
 | 
			
		||||
					},
 | 
			
		||||
					1,
 | 
			
		||||
				}},
 | 
			
		||||
				{Key: "b", Value: 2},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			SortMapSlice(test.input)
 | 
			
		||||
			if !reflect.DeepEqual(test.input, test.expected) {
 | 
			
		||||
				t.Errorf("Expected %v, got %v", test.expected, test.input)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2023 The Flux 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 yaml
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
 | 
			
		||||
	goyaml "gopkg.in/yaml.v2"
 | 
			
		||||
	"sigs.k8s.io/yaml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PreEncoder allows for pre-processing of the YAML data before encoding.
 | 
			
		||||
type PreEncoder func(goyaml.MapSlice)
 | 
			
		||||
 | 
			
		||||
// Encode encodes the given data to YAML format and writes it to the provided
 | 
			
		||||
// io.Write, without going through a byte representation (unlike
 | 
			
		||||
// sigs.k8s.io/yaml#Unmarshal).
 | 
			
		||||
//
 | 
			
		||||
// It optionally takes one or more PreEncoder functions that allow
 | 
			
		||||
// for pre-processing of the data before encoding, such as sorting the data.
 | 
			
		||||
//
 | 
			
		||||
// It returns an error if the data cannot be encoded.
 | 
			
		||||
func Encode(w io.Writer, data map[string]interface{}, pe ...PreEncoder) error {
 | 
			
		||||
	ms := yaml.JSONObjectToYAMLObject(data)
 | 
			
		||||
	for _, m := range pe {
 | 
			
		||||
		m(ms)
 | 
			
		||||
	}
 | 
			
		||||
	return goyaml.NewEncoder(w).Encode(ms)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,106 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2023 The Flux 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 yaml
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"sigs.k8s.io/yaml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestEncode(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		input       map[string]interface{}
 | 
			
		||||
		preEncoders []PreEncoder
 | 
			
		||||
		want        []byte
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:  "empty map",
 | 
			
		||||
			input: map[string]interface{}{},
 | 
			
		||||
			want: []byte(`{}
 | 
			
		||||
`),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "simple values",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"replicaCount": 3,
 | 
			
		||||
			},
 | 
			
		||||
			want: []byte(`replicaCount: 3
 | 
			
		||||
`),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "with pre-encoder",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"replicaCount": 3,
 | 
			
		||||
				"image": map[string]interface{}{
 | 
			
		||||
					"repository": "nginx",
 | 
			
		||||
					"tag":        "latest",
 | 
			
		||||
				},
 | 
			
		||||
				"port": 8080,
 | 
			
		||||
			},
 | 
			
		||||
			preEncoders: []PreEncoder{SortMapSlice},
 | 
			
		||||
			want: []byte(`image:
 | 
			
		||||
  repository: nginx
 | 
			
		||||
  tag: latest
 | 
			
		||||
port: 8080
 | 
			
		||||
replicaCount: 3
 | 
			
		||||
`),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			var actual bytes.Buffer
 | 
			
		||||
			err := Encode(&actual, tt.input, tt.preEncoders...)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("error encoding: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !bytes.Equal(actual.Bytes(), tt.want) {
 | 
			
		||||
				t.Errorf("Encode() = %v, want: %s", actual.String(), tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkEncode(b *testing.B) {
 | 
			
		||||
	// Test against the values.yaml from the kube-prometheus-stack chart, which
 | 
			
		||||
	// is a fairly large file.
 | 
			
		||||
	v, err := os.ReadFile("testdata/values.yaml")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatalf("error reading testdata: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data map[string]interface{}
 | 
			
		||||
	if err = yaml.Unmarshal(v, &data); err != nil {
 | 
			
		||||
		b.Fatalf("error unmarshalling testdata: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b.Run("EncodeWithSort", func(b *testing.B) {
 | 
			
		||||
		for i := 0; i < b.N; i++ {
 | 
			
		||||
			Encode(bytes.NewBuffer(nil), data, SortMapSlice)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	b.Run("SigYAMLMarshal", func(b *testing.B) {
 | 
			
		||||
		for i := 0; i < b.N; i++ {
 | 
			
		||||
			yaml.Marshal(data)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2023 The Flux 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 yaml
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	goyaml "gopkg.in/yaml.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SortMapSlice recursively sorts the given goyaml.MapSlice by key.
 | 
			
		||||
// It can be used in combination with Encode to sort YAML by key
 | 
			
		||||
// before encoding it.
 | 
			
		||||
func SortMapSlice(ms goyaml.MapSlice) {
 | 
			
		||||
	sort.Slice(ms, func(i, j int) bool {
 | 
			
		||||
		return ms[i].Key.(string) < ms[j].Key.(string)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	for _, item := range ms {
 | 
			
		||||
		if nestedMS, ok := item.Value.(goyaml.MapSlice); ok {
 | 
			
		||||
			SortMapSlice(nestedMS)
 | 
			
		||||
		} else if nestedSlice, ok := item.Value.([]interface{}); ok {
 | 
			
		||||
			for _, vItem := range nestedSlice {
 | 
			
		||||
				if nestedMS, ok := vItem.(goyaml.MapSlice); ok {
 | 
			
		||||
					SortMapSlice(nestedMS)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,183 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2023 The Flux 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 yaml
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	goyaml "gopkg.in/yaml.v2"
 | 
			
		||||
	"sigs.k8s.io/yaml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSortMapSlice(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name  string
 | 
			
		||||
		input map[string]interface{}
 | 
			
		||||
		want  map[string]interface{}
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:  "empty map",
 | 
			
		||||
			input: map[string]interface{}{},
 | 
			
		||||
			want:  map[string]interface{}{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "flat map",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"b": "value-b",
 | 
			
		||||
				"a": "value-a",
 | 
			
		||||
				"c": "value-c",
 | 
			
		||||
			},
 | 
			
		||||
			want: map[string]interface{}{
 | 
			
		||||
				"a": "value-a",
 | 
			
		||||
				"b": "value-b",
 | 
			
		||||
				"c": "value-c",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "nested map",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"b": "value-b",
 | 
			
		||||
				"a": "value-a",
 | 
			
		||||
				"c": map[string]interface{}{
 | 
			
		||||
					"z": "value-z",
 | 
			
		||||
					"y": "value-y",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: map[string]interface{}{
 | 
			
		||||
				"a": "value-a",
 | 
			
		||||
				"b": "value-b",
 | 
			
		||||
				"c": map[string]interface{}{
 | 
			
		||||
					"y": "value-y",
 | 
			
		||||
					"z": "value-z",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "map with slices",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"b": []interface{}{"apple", "banana", "cherry"},
 | 
			
		||||
				"a": []interface{}{"orange", "grape"},
 | 
			
		||||
				"c": []interface{}{"strawberry"},
 | 
			
		||||
			},
 | 
			
		||||
			want: map[string]interface{}{
 | 
			
		||||
				"a": []interface{}{"orange", "grape"},
 | 
			
		||||
				"b": []interface{}{"apple", "banana", "cherry"},
 | 
			
		||||
				"c": []interface{}{"strawberry"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "map with mixed data types",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"b": 50,
 | 
			
		||||
				"a": "value-a",
 | 
			
		||||
				"c": []interface{}{"strawberry", "banana"},
 | 
			
		||||
				"d": map[string]interface{}{
 | 
			
		||||
					"x": true,
 | 
			
		||||
					"y": 123,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: map[string]interface{}{
 | 
			
		||||
				"a": "value-a",
 | 
			
		||||
				"b": 50,
 | 
			
		||||
				"c": []interface{}{"strawberry", "banana"},
 | 
			
		||||
				"d": map[string]interface{}{
 | 
			
		||||
					"x": true,
 | 
			
		||||
					"y": 123,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "map with complex structure",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"a": map[string]interface{}{
 | 
			
		||||
					"c": "value-c",
 | 
			
		||||
					"b": "value-b",
 | 
			
		||||
					"a": "value-a",
 | 
			
		||||
				},
 | 
			
		||||
				"b": "value-b",
 | 
			
		||||
				"c": map[string]interface{}{
 | 
			
		||||
					"z": map[string]interface{}{
 | 
			
		||||
						"a": "value-a",
 | 
			
		||||
						"b": "value-b",
 | 
			
		||||
						"c": "value-c",
 | 
			
		||||
					},
 | 
			
		||||
					"y": "value-y",
 | 
			
		||||
				},
 | 
			
		||||
				"d": map[string]interface{}{
 | 
			
		||||
					"q": "value-q",
 | 
			
		||||
					"p": "value-p",
 | 
			
		||||
					"r": "value-r",
 | 
			
		||||
				},
 | 
			
		||||
				"e": []interface{}{"strawberry", "banana"},
 | 
			
		||||
			},
 | 
			
		||||
			want: map[string]interface{}{
 | 
			
		||||
				"a": map[string]interface{}{
 | 
			
		||||
					"a": "value-a",
 | 
			
		||||
					"b": "value-b",
 | 
			
		||||
					"c": "value-c",
 | 
			
		||||
				},
 | 
			
		||||
				"b": "value-b",
 | 
			
		||||
				"c": map[string]interface{}{
 | 
			
		||||
					"y": "value-y",
 | 
			
		||||
					"z": map[string]interface{}{
 | 
			
		||||
						"a": "value-a",
 | 
			
		||||
						"b": "value-b",
 | 
			
		||||
						"c": "value-c",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"d": map[string]interface{}{
 | 
			
		||||
					"p": "value-p",
 | 
			
		||||
					"q": "value-q",
 | 
			
		||||
					"r": "value-r",
 | 
			
		||||
				},
 | 
			
		||||
				"e": []interface{}{"strawberry", "banana"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "map with empty slices and maps",
 | 
			
		||||
			input: map[string]interface{}{
 | 
			
		||||
				"b": []interface{}{},
 | 
			
		||||
				"a": map[string]interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
			want: map[string]interface{}{
 | 
			
		||||
				"a": map[string]interface{}{},
 | 
			
		||||
				"b": []interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			input := yaml.JSONObjectToYAMLObject(tt.input)
 | 
			
		||||
			SortMapSlice(input)
 | 
			
		||||
 | 
			
		||||
			expect, err := goyaml.Marshal(input)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("error marshalling output: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			actual, err := goyaml.Marshal(tt.want)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("error marshalling want: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !bytes.Equal(expect, actual) {
 | 
			
		||||
				t.Errorf("SortMapSlice() = %s, want %s", expect, actual)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue