From f7f04e7c8a5e9d99956cf43d27d49d98b34f0960 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Thu, 15 May 2025 08:32:20 +0800 Subject: [PATCH] refactor(yaml): upgrade from gopkg.in/yaml.v2 to v3 Signed-off-by: yxxhero --- docs/index.md | 5 ++-- go.mod | 4 ++-- pkg/app/app_template_test.go | 2 +- pkg/app/app_test.go | 4 ++-- pkg/envvar/const.go | 19 +++++++-------- pkg/runtime/runtime.go | 8 +++---- pkg/tmpl/context_funcs_test.go | 24 ++++++------------- pkg/yaml/yaml.go | 26 ++++++--------------- pkg/yaml/yaml_test.go | 6 ++--- test/e2e/template/helmfile/snapshot_test.go | 2 +- 10 files changed, 38 insertions(+), 62 deletions(-) diff --git a/docs/index.md b/docs/index.md index 930e6d26..e230e86c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -570,18 +570,17 @@ Helmfile uses some OS environment variables to override default behaviour: * `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](https://helmfile.readthedocs.io/en/latest/#environment), it has lower priority than CLI argument `--environment` * `HELMFILE_TEMPDIR` - specify directory to store temporary files * `HELMFILE_UPGRADE_NOTICE_DISABLED` - expecting any non-empty value to skip the check for the latest version of Helmfile in [helmfile version](https://helmfile.readthedocs.io/en/latest/#version) -* `HELMFILE_GOCCY_GOYAML` - use *goccy/go-yaml* instead of *gopkg.in/yaml.v2*. It's `false` by default in Helmfile v0.x and `true` by default for Helmfile v1.x. +* `HELMFILE_GOCCY_GOYAML` - use *goccy/go-yaml* instead of *gopkg.in/yaml.v3*. It's `false` by default in Helmfile until *goccy/go-yaml* is stable enough. * `HELMFILE_CACHE_HOME` - specify directory to store cached files for remote operations * `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file * `HELMFILE_INTERACTIVE` - enable interactive mode, expecting `true` lower case. The same as `--interactive` CLI flag -* `HELMFILE_ENABLE_GOCCY_GOYAML_JSON_STYLE`: - enable JSON style for *goccy/go-yaml* instead of *gopkg.in/yaml.v2*. It's `false` by default in Helmfile. it will add quotes to string values. ## CLI Reference ``` Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot V1 mode = false -YAML library = gopkg.in/yaml.v2 +YAML library = gopkg.in/yaml.v3 Usage: helmfile [command] diff --git a/go.mod b/go.mod index 038025d2..68d6bfaa 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/sync v0.14.0 golang.org/x/term v0.32.0 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.17.3 k8s.io/apimachinery v0.33.0 ) @@ -307,7 +307,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/gookit/color.v1 v1.1.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/api v0.33.0 // indirect k8s.io/apiextensions-apiserver v0.32.2 // indirect k8s.io/cli-runtime v0.32.2 // indirect diff --git a/pkg/app/app_template_test.go b/pkg/app/app_template_test.go index e42122e1..7c4419b1 100644 --- a/pkg/app/app_template_test.go +++ b/pkg/app/app_template_test.go @@ -423,7 +423,7 @@ releases: }) }) - t.Run("fail due to unknown field with gopkg.in/yaml.v2", func(t *testing.T) { + t.Run("fail due to unknown field with gopkg.in/yaml.v3", func(t *testing.T) { check(t, testcase{ goccyGoYaml: false, error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1. Started seeing this since Helmfile v1? Add the .gotmpl file extension: yaml: unmarshal errors: diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 435ed7cf..2cb36f51 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -4165,7 +4165,7 @@ func TestSetValuesTemplate(t *testing.T) { testSetValuesTemplate(t, true) }) - t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { + t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { testSetValuesTemplate(t, false) }) } @@ -4175,7 +4175,7 @@ func TestSetStringValuesTemplate(t *testing.T) { testSetStringValuesTemplate(t, true) }) - t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { + t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { testSetStringValuesTemplate(t, false) }) } diff --git a/pkg/envvar/const.go b/pkg/envvar/const.go index 40de9ae9..0235af17 100644 --- a/pkg/envvar/const.go +++ b/pkg/envvar/const.go @@ -6,14 +6,13 @@ const ( // use helm status to check if a release exists before installing it UseHelmStatusToCheckReleaseExistence = "HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE" - DisableRunnerUniqueID = "HELMFILE_DISABLE_RUNNER_UNIQUE_ID" - Experimental = "HELMFILE_EXPERIMENTAL" // environment variable for experimental features, expecting "true" lower case - Environment = "HELMFILE_ENVIRONMENT" - FilePath = "HELMFILE_FILE_PATH" - TempDir = "HELMFILE_TEMPDIR" - UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED" - GoccyGoYaml = "HELMFILE_GOCCY_GOYAML" - CacheHome = "HELMFILE_CACHE_HOME" - Interactive = "HELMFILE_INTERACTIVE" - EnableGoccyGoYamlJSONStyle = "HELMFILE_ENABLE_GOCCY_GOYAML_JSON_STYLE" + DisableRunnerUniqueID = "HELMFILE_DISABLE_RUNNER_UNIQUE_ID" + Experimental = "HELMFILE_EXPERIMENTAL" // environment variable for experimental features, expecting "true" lower case + Environment = "HELMFILE_ENVIRONMENT" + FilePath = "HELMFILE_FILE_PATH" + TempDir = "HELMFILE_TEMPDIR" + UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED" + GoccyGoYaml = "HELMFILE_GOCCY_GOYAML" + CacheHome = "HELMFILE_CACHE_HOME" + Interactive = "HELMFILE_INTERACTIVE" ) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 7c1e1d14..83bd13c2 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -9,13 +9,13 @@ import ( var ( // GoccyGoYaml is set to true in order to let Helmfile use - // goccy/go-yaml instead of gopkg.in/yaml.v2. - // It's false by default in Helmfile v0.x and true by default for Helmfile v1.x. + // goccy/go-yaml instead of gopkg.in/yaml.v3. + // It's false by default in Helmfile until the GoccyGoYaml is ready to be used GoccyGoYaml bool ) func Info() string { - yamlLib := "gopkg.in/yaml.v2" + yamlLib := "gopkg.in/yaml.v3" if GoccyGoYaml { yamlLib = "goccy/go-yaml" } @@ -31,6 +31,6 @@ func init() { case "false": GoccyGoYaml = false default: - GoccyGoYaml = true + GoccyGoYaml = false } } diff --git a/pkg/tmpl/context_funcs_test.go b/pkg/tmpl/context_funcs_test.go index 4a75a9c2..9ce3fa49 100644 --- a/pkg/tmpl/context_funcs_test.go +++ b/pkg/tmpl/context_funcs_test.go @@ -4,14 +4,12 @@ import ( "encoding/json" "fmt" "io/fs" - "os" "path/filepath" goruntime "runtime" "testing" "github.com/stretchr/testify/require" - "github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/runtime" ) @@ -189,17 +187,15 @@ func TestToYaml_NestedMapInterfaceKey(t *testing.T) { func TestToYaml(t *testing.T) { tests := []struct { - name string - input any - expected string - wantErr bool - enableJsonStyle bool + name string + input any + expected string + wantErr bool }{ { // https://github.com/helmfile/helmfile/issues/2024 - name: "test unmarshalling issue 2024", - enableJsonStyle: true, - input: map[string]any{"thisShouldBeString": "01234567890123456789"}, + name: "test unmarshalling issue 2024", + input: map[string]any{"thisShouldBeString": "01234567890123456789"}, expected: `'thisShouldBeString': '01234567890123456789' `, }, @@ -247,12 +243,6 @@ func TestToYaml(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.enableJsonStyle { - _ = os.Setenv(envvar.EnableGoccyGoYamlJSONStyle, "true") - } - defer func() { - _ = os.Unsetenv(envvar.EnableGoccyGoYamlJSONStyle) - }() actual, err := ToYaml(tt.input) if tt.wantErr { require.Error(t, err) @@ -372,7 +362,7 @@ func TestFromYaml(t *testing.T) { testFromYaml(t, true) }) - t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { + t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { testFromYaml(t, false) }) } diff --git a/pkg/yaml/yaml.go b/pkg/yaml/yaml.go index 588bbc4f..8dd02752 100644 --- a/pkg/yaml/yaml.go +++ b/pkg/yaml/yaml.go @@ -3,12 +3,10 @@ package yaml import ( "bytes" "io" - "os" "github.com/goccy/go-yaml" - v2 "gopkg.in/yaml.v2" + v3 "gopkg.in/yaml.v3" - "github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/runtime" ) @@ -20,15 +18,10 @@ type Encoder interface { // NewEncoder creates and returns a function that is used to encode a Go object to a YAML document func NewEncoder(w io.Writer) Encoder { if runtime.GoccyGoYaml { - yamlEncoderOpts := []yaml.EncodeOption{} - // enable JSON style if the envvar is set - if os.Getenv(envvar.EnableGoccyGoYamlJSONStyle) == "true" { - yamlEncoderOpts = append(yamlEncoderOpts, yaml.JSON(), yaml.Flow(false)) - } - return yaml.NewEncoder(w, yamlEncoderOpts...) + return yaml.NewEncoder(w) } - return v2.NewEncoder(w) + return v3.NewEncoder(w) } func Unmarshal(data []byte, v any) error { @@ -36,7 +29,7 @@ func Unmarshal(data []byte, v any) error { return yaml.Unmarshal(data, v) } - return v2.Unmarshal(data, v) + return v3.Unmarshal(data, v) } // NewDecoder creates and returns a function that is used to decode a YAML document @@ -62,8 +55,8 @@ func NewDecoder(data []byte, strict bool) func(any) error { } } - decoder := v2.NewDecoder(bytes.NewReader(data)) - decoder.SetStrict(strict) + decoder := v3.NewDecoder(bytes.NewReader(data)) + decoder.KnownFields(strict) return func(v any) error { return decoder.Decode(v) @@ -78,11 +71,6 @@ func Marshal(v any) ([]byte, error) { yaml.UseSingleQuote(true), yaml.UseLiteralStyleIfMultiline(true), } - // enable JSON style if the envvar is set - if os.Getenv(envvar.EnableGoccyGoYamlJSONStyle) == "true" { - yamlEncoderOpts = append(yamlEncoderOpts, yaml.JSON(), yaml.Flow(false)) - } - yamlEncoder := yaml.NewEncoder( &b, yamlEncoderOpts..., @@ -94,5 +82,5 @@ func Marshal(v any) ([]byte, error) { return b.Bytes(), err } - return v2.Marshal(v) + return v3.Marshal(v) } diff --git a/pkg/yaml/yaml_test.go b/pkg/yaml/yaml_test.go index 35e2a3ba..ff15487f 100644 --- a/pkg/yaml/yaml_test.go +++ b/pkg/yaml/yaml_test.go @@ -15,7 +15,7 @@ func testYamlMarshal(t *testing.T, goccyGoYaml bool) { if goccyGoYaml { yamlLibraryName = "goccy/go-yaml" } else { - yamlLibraryName = "gopkg.in/yaml.v2" + yamlLibraryName = "gopkg.in/yaml.v3" } v := runtime.GoccyGoYaml @@ -50,7 +50,7 @@ func testYamlMarshal(t *testing.T, goccyGoYaml bool) { }}, expected: map[string]string{ "goccy/go-yaml": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: 'on'\n", - "gopkg.in/yaml.v2": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: \"on\"\n", + "gopkg.in/yaml.v3": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: \"on\"\n", }, }, } @@ -67,7 +67,7 @@ func TestYamlMarshal(t *testing.T) { testYamlMarshal(t, true) }) - t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { + t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { testYamlMarshal(t, false) }) } diff --git a/test/e2e/template/helmfile/snapshot_test.go b/test/e2e/template/helmfile/snapshot_test.go index 68f62be5..33a23515 100644 --- a/test/e2e/template/helmfile/snapshot_test.go +++ b/test/e2e/template/helmfile/snapshot_test.go @@ -62,7 +62,7 @@ func TestHelmfileTemplateWithBuildCommand(t *testing.T) { testHelmfileTemplateWithBuildCommand(t, true) }) - t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { + t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { testHelmfileTemplateWithBuildCommand(t, false) }) }