refactor(yaml): upgrade from gopkg.in/yaml.v2 to v3

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero 2025-05-15 08:32:20 +08:00
parent 867bef0f03
commit f7f04e7c8a
10 changed files with 38 additions and 62 deletions

View File

@ -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_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_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_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_CACHE_HOME` - specify directory to store cached files for remote operations
* `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file * `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_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 ## CLI Reference
``` ```
Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot
V1 mode = false V1 mode = false
YAML library = gopkg.in/yaml.v2 YAML library = gopkg.in/yaml.v3
Usage: Usage:
helmfile [command] helmfile [command]

4
go.mod
View File

@ -28,7 +28,7 @@ require (
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/sync v0.14.0 golang.org/x/sync v0.14.0
golang.org/x/term v0.32.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 helm.sh/helm/v3 v3.17.3
k8s.io/apimachinery v0.33.0 k8s.io/apimachinery v0.33.0
) )
@ -307,7 +307,7 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/gookit/color.v1 v1.1.6 // indirect gopkg.in/gookit/color.v1 v1.1.6 // indirect
gopkg.in/inf.v0 v0.9.1 // 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/api v0.33.0 // indirect
k8s.io/apiextensions-apiserver v0.32.2 // indirect k8s.io/apiextensions-apiserver v0.32.2 // indirect
k8s.io/cli-runtime v0.32.2 // indirect k8s.io/cli-runtime v0.32.2 // indirect

View File

@ -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{ check(t, testcase{
goccyGoYaml: false, 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: 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:

View File

@ -4165,7 +4165,7 @@ func TestSetValuesTemplate(t *testing.T) {
testSetValuesTemplate(t, true) 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) testSetValuesTemplate(t, false)
}) })
} }
@ -4175,7 +4175,7 @@ func TestSetStringValuesTemplate(t *testing.T) {
testSetStringValuesTemplate(t, true) 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) testSetStringValuesTemplate(t, false)
}) })
} }

View File

@ -6,14 +6,13 @@ const (
// use helm status to check if a release exists before installing it // use helm status to check if a release exists before installing it
UseHelmStatusToCheckReleaseExistence = "HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE" UseHelmStatusToCheckReleaseExistence = "HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE"
DisableRunnerUniqueID = "HELMFILE_DISABLE_RUNNER_UNIQUE_ID" DisableRunnerUniqueID = "HELMFILE_DISABLE_RUNNER_UNIQUE_ID"
Experimental = "HELMFILE_EXPERIMENTAL" // environment variable for experimental features, expecting "true" lower case Experimental = "HELMFILE_EXPERIMENTAL" // environment variable for experimental features, expecting "true" lower case
Environment = "HELMFILE_ENVIRONMENT" Environment = "HELMFILE_ENVIRONMENT"
FilePath = "HELMFILE_FILE_PATH" FilePath = "HELMFILE_FILE_PATH"
TempDir = "HELMFILE_TEMPDIR" TempDir = "HELMFILE_TEMPDIR"
UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED" UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED"
GoccyGoYaml = "HELMFILE_GOCCY_GOYAML" GoccyGoYaml = "HELMFILE_GOCCY_GOYAML"
CacheHome = "HELMFILE_CACHE_HOME" CacheHome = "HELMFILE_CACHE_HOME"
Interactive = "HELMFILE_INTERACTIVE" Interactive = "HELMFILE_INTERACTIVE"
EnableGoccyGoYamlJSONStyle = "HELMFILE_ENABLE_GOCCY_GOYAML_JSON_STYLE"
) )

View File

@ -9,13 +9,13 @@ import (
var ( var (
// GoccyGoYaml is set to true in order to let Helmfile use // GoccyGoYaml is set to true in order to let Helmfile use
// goccy/go-yaml instead of gopkg.in/yaml.v2. // goccy/go-yaml instead of gopkg.in/yaml.v3.
// It's false by default in Helmfile v0.x and true by default for Helmfile v1.x. // It's false by default in Helmfile until the GoccyGoYaml is ready to be used
GoccyGoYaml bool GoccyGoYaml bool
) )
func Info() string { func Info() string {
yamlLib := "gopkg.in/yaml.v2" yamlLib := "gopkg.in/yaml.v3"
if GoccyGoYaml { if GoccyGoYaml {
yamlLib = "goccy/go-yaml" yamlLib = "goccy/go-yaml"
} }
@ -31,6 +31,6 @@ func init() {
case "false": case "false":
GoccyGoYaml = false GoccyGoYaml = false
default: default:
GoccyGoYaml = true GoccyGoYaml = false
} }
} }

View File

@ -4,14 +4,12 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs" "io/fs"
"os"
"path/filepath" "path/filepath"
goruntime "runtime" goruntime "runtime"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/envvar"
"github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/runtime" "github.com/helmfile/helmfile/pkg/runtime"
) )
@ -189,17 +187,15 @@ func TestToYaml_NestedMapInterfaceKey(t *testing.T) {
func TestToYaml(t *testing.T) { func TestToYaml(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input any input any
expected string expected string
wantErr bool wantErr bool
enableJsonStyle bool
}{ }{
{ {
// https://github.com/helmfile/helmfile/issues/2024 // https://github.com/helmfile/helmfile/issues/2024
name: "test unmarshalling issue 2024", name: "test unmarshalling issue 2024",
enableJsonStyle: true, input: map[string]any{"thisShouldBeString": "01234567890123456789"},
input: map[string]any{"thisShouldBeString": "01234567890123456789"},
expected: `'thisShouldBeString': '01234567890123456789' expected: `'thisShouldBeString': '01234567890123456789'
`, `,
}, },
@ -247,12 +243,6 @@ func TestToYaml(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) actual, err := ToYaml(tt.input)
if tt.wantErr { if tt.wantErr {
require.Error(t, err) require.Error(t, err)
@ -372,7 +362,7 @@ func TestFromYaml(t *testing.T) {
testFromYaml(t, true) 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) testFromYaml(t, false)
}) })
} }

View File

@ -3,12 +3,10 @@ package yaml
import ( import (
"bytes" "bytes"
"io" "io"
"os"
"github.com/goccy/go-yaml" "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" "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 // NewEncoder creates and returns a function that is used to encode a Go object to a YAML document
func NewEncoder(w io.Writer) Encoder { func NewEncoder(w io.Writer) Encoder {
if runtime.GoccyGoYaml { if runtime.GoccyGoYaml {
yamlEncoderOpts := []yaml.EncodeOption{} return yaml.NewEncoder(w)
// 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 v2.NewEncoder(w) return v3.NewEncoder(w)
} }
func Unmarshal(data []byte, v any) error { func Unmarshal(data []byte, v any) error {
@ -36,7 +29,7 @@ func Unmarshal(data []byte, v any) error {
return yaml.Unmarshal(data, v) 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 // 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 := v3.NewDecoder(bytes.NewReader(data))
decoder.SetStrict(strict) decoder.KnownFields(strict)
return func(v any) error { return func(v any) error {
return decoder.Decode(v) return decoder.Decode(v)
@ -78,11 +71,6 @@ func Marshal(v any) ([]byte, error) {
yaml.UseSingleQuote(true), yaml.UseSingleQuote(true),
yaml.UseLiteralStyleIfMultiline(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( yamlEncoder := yaml.NewEncoder(
&b, &b,
yamlEncoderOpts..., yamlEncoderOpts...,
@ -94,5 +82,5 @@ func Marshal(v any) ([]byte, error) {
return b.Bytes(), err return b.Bytes(), err
} }
return v2.Marshal(v) return v3.Marshal(v)
} }

View File

@ -15,7 +15,7 @@ func testYamlMarshal(t *testing.T, goccyGoYaml bool) {
if goccyGoYaml { if goccyGoYaml {
yamlLibraryName = "goccy/go-yaml" yamlLibraryName = "goccy/go-yaml"
} else { } else {
yamlLibraryName = "gopkg.in/yaml.v2" yamlLibraryName = "gopkg.in/yaml.v3"
} }
v := runtime.GoccyGoYaml v := runtime.GoccyGoYaml
@ -50,7 +50,7 @@ func testYamlMarshal(t *testing.T, goccyGoYaml bool) {
}}, }},
expected: map[string]string{ expected: map[string]string{
"goccy/go-yaml": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: 'on'\n", "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) 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) testYamlMarshal(t, false)
}) })
} }

View File

@ -62,7 +62,7 @@ func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
testHelmfileTemplateWithBuildCommand(t, true) 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) testHelmfileTemplateWithBuildCommand(t, false)
}) })
} }