Skip to content

Use gopkg.in/yaml.v2 for Helmfile v0.x#609

Merged
mumoshu merged 14 commits into
mainfrom
go-back-to-gopkgin-yaml-v2-for-helmfile-v0.x
Jan 4, 2023
Merged

Use gopkg.in/yaml.v2 for Helmfile v0.x#609
mumoshu merged 14 commits into
mainfrom
go-back-to-gopkgin-yaml-v2-for-helmfile-v0.x

Conversation

@mumoshu

@mumoshu mumoshu commented Dec 28, 2022

Copy link
Copy Markdown
Contributor

This should fix #435 for Helmfile v0.x releases since the next v0.150.0.
We introduce a new envvar to opt-in to the new YAML library, so that you can give it a shot before upgrading your Helmfile to v1. The same envvar can be used to opt-out of the new YAML library after you upgrade to Helmfile v1, giving you a more flexible migration story.

Signed-off-by: Yusuke Kuoka [email protected]

@mumoshu mumoshu force-pushed the go-back-to-gopkgin-yaml-v2-for-helmfile-v0.x branch 5 times, most recently from af1880c to ca9da1d Compare December 28, 2022 03:46
Comment thread pkg/yaml/yaml.go
@mumoshu mumoshu force-pushed the go-back-to-gopkgin-yaml-v2-for-helmfile-v0.x branch from ca9da1d to cf0d5b9 Compare December 28, 2022 08:43
@mumoshu mumoshu force-pushed the go-back-to-gopkgin-yaml-v2-for-helmfile-v0.x branch from 1a19e6d to d12c768 Compare December 29, 2022 00:07
@mumoshu

mumoshu commented Dec 29, 2022

Copy link
Copy Markdown
Contributor Author

@yxxhero FYI, it seems like goccy/go-yaml is more strict when parsing {{ ... }} at the dict value position:

TEST: happypath - simple rollout of httpbin chart
INFO: Diffing test/integration/test-cases/happypath/input/happypath.yaml
in test/integration/test-cases/happypath/input/happypath.yaml: failed to read happypath.yaml: reading document at index 1: [28:20] unexpected mapping key
      25 |     chart: incubator/raw
      26 |     version: 0.2.3
      27 |     values:
    > 28 |       - mysecret: {{ .Environment.Values.mysecret }}
                              ^
      29 |       - values.yaml
      30 | 
      31 |   - name: helmx
      32 |     
FAIL: unexpected exit code returned by helmfile diff

I'm going to update - mysecret: {{ .Environment.Values.mysecret }} to - mysecret: "{{ .Environment.Values.mysecret }}" so that it's considered a valid YAML both by yaml.v2 and goccy/go-yaml.
Probably we might better document about the change in the v1 proposal too?

@mumoshu

mumoshu commented Dec 29, 2022

Copy link
Copy Markdown
Contributor Author

@yxxhero NVM. Apparently, it is due to that v1 mode requires .gotmpl ext for helmfile.yaml to enable the templating feature. We might need to rename every integration test input.yaml to input.yaml.gotmpl or come up with another way to make the integration test suite runnable for both helmfile v0 and v1 builds...

@yxxhero

yxxhero commented Dec 29, 2022

Copy link
Copy Markdown
Member

@mumoshu I agreed with you so much.

@yxxhero

yxxhero commented Dec 29, 2022

Copy link
Copy Markdown
Member

@yxxhero FYI, it seems like goccy/go-yaml is more strict when parsing {{ ... }} at the dict value position:

TEST: happypath - simple rollout of httpbin chart
INFO: Diffing test/integration/test-cases/happypath/input/happypath.yaml
in test/integration/test-cases/happypath/input/happypath.yaml: failed to read happypath.yaml: reading document at index 1: [28:20] unexpected mapping key
      25 |     chart: incubator/raw
      26 |     version: 0.2.3
      27 |     values:
    > 28 |       - mysecret: {{ .Environment.Values.mysecret }}
                              ^
      29 |       - values.yaml
      30 | 
      31 |   - name: helmx
      32 |     
FAIL: unexpected exit code returned by helmfile diff

I'm going to update - mysecret: {{ .Environment.Values.mysecret }} to - mysecret: "{{ .Environment.Values.mysecret }}" so that it's considered a valid YAML both by yaml.v2 and goccy/go-yaml. Probably we might better document about the change in the v1 proposal too?

it's more conform to a standard of yaml spec. right? @mumoshu

… fixup! Use gopkg.in/yaml.v2 for Helmfile v0.x

Signed-off-by: Yusuke Kuoka <[email protected]>
@mumoshu

mumoshu commented Dec 29, 2022

Copy link
Copy Markdown
Contributor Author

@yxxhero Right. And it seems like every yaml implementation (yaml.v2, yaml.v3, and goccy/go-yaml) seems to fail if we tried to parse that. The failure was due to that I missed .gotmpl ext for helmfile v1 integration test.

@yxxhero

yxxhero commented Dec 29, 2022

Copy link
Copy Markdown
Member

@mumoshu yeah. you are right. we should update the ext of integration test files.

… fixup! fixup! Use gopkg.in/yaml.v2 for Helmfile v0.x

Signed-off-by: Yusuke Kuoka <[email protected]>
… fixup! fixup! fixup! Use gopkg.in/yaml.v2 for Helmfile v0.x

Signed-off-by: Yusuke Kuoka <[email protected]>
Comment thread .github/workflows/ci.yaml
Comment on lines +88 to +94
# Helmfile v1
- helm-version: v3.10.3
kustomize-version: v4.5.7
plugin-secrets-version: 4.0.0
plugin-diff-version: 3.6.0
extra-helmfile-flags:
v1mode: "true"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yxxhero We now run the integration test suite for the v1 mode once per build, with the representative versions of helmfile dependencies, so that we won't break any v1 functionality until we finally stop maintaining v0.

Comment thread .github/workflows/ci.yaml
HELMFILE_HELM3: 1
TERM: xterm
EXTRA_HELMFILE_FLAGS: ${{ matrix.extra-helmfile-flags }}
HELMFILE_V1MODE: ${{ matrix.v1mode }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, HELMFILE_V1MODE is defined in pkg/runtime/runtime.go

Comment on lines +321 to +325
v := runtime.GoccyGoYaml
runtime.GoccyGoYaml = tc.goccyGoYaml
t.Cleanup(func() {
runtime.GoccyGoYaml = v
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an idiom I used widely across many test sources to ensure we modify the GoccyGoYaml runtime variable only while running the (sub-)test.

}

t.Run("fail due to known field", func(t *testing.T) {
t.Run("fail due to unknown field with goccy/go-yaml", func(t *testing.T) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also see this idiom, the same test is run with goccy/goyaml and with yaml.v2, so that we are sure that we don't break anything while migrating to goccy/goyaml or helmfile v1.

Comment thread pkg/envvar/const.go
UpgradeNoticeDisabled = "HELMFILE_UPGRADE_NOTICE_DISABLED"
V1Mode = "HELMFILE_V1MODE"
GoccyGoYaml = "HELMFILE_GOCCY_GOYAML"
CacheHome = "HELMFILE_CACHE_HOME"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't strictly necessary to change our yaml library. However I added this because this helped tidy up our e2e/snapshot test to be runnable with/without v1 mode.
The envvar is used in pkg/remote/remote.go and set in snapshot_test.go

Comment thread pkg/runtime/runtime.go
}

// You can switch the YAML library at runtime via an envvar:
switch os.Getenv(envvar.GoccyGoYaml) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although goccy/go-yaml is the default for v1, you can opt in/out in both v0 and v1, by setting this envvar.

Comment thread pkg/state/release.go
}

var deserialized map[interface{}]interface{}
var deserialized map[string]interface{}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yaml.v2 deserializes nested yaml dict to map[interface{}]interface{} while yaml.v3 and goccy/go-yaml serializes it to map[string]interface{}.
My original design was to ensure any nested dict is deserialized to the latter, by calling maputil.CastKeysToStrings

Comment thread pkg/state/release.go
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%s\": %v", r.Name, i, ts, err)
}
result.Values[i] = s.String()
case map[interface{}]interface{}:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name: "app",
Namespace: "dev",
Values: []interface{}{map[interface{}]interface{}{"key": "app-val0"}},
Values: []interface{}{map[string]interface{}{"key": "app-val0"}},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread pkg/tmpl/context_funcs.go
return nil, fmt.Errorf("%s, offending yaml: %s", err, str)
}

m, err := maputil.CastKeysToStrings(m)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/helmfile/helmfile/pull/609/files#r1058925709

This is especially important here because this function is supposed to be used like fromYaml | toJson within helmfile templates. Go's json pkg which powers toJson is unable to serialize map[interface{}]interface{} deserialized by yaml.v2. This CastKeysToStrings call normalizes it so that both yaml.v2 and goccy/go-yaml implementation of FromYaml is compatible with ToJson.

}

func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
t.Run("with goccy/go-yaml", func(t *testing.T) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now run all the snapshot tests for both goccy/go-yaml and yaml.v2


tmpDir := t.TempDir()
// HELM_CACHE_HOME contains downloaded chart archives
helmCacheHome := filepath.Join(tmpDir, "helm_cache")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to give clean HELM_CACHE_HOME and HELM_CONFIG_HOME to every snapshot test case to make it not affected by the previous tests.

skipping missing secrets file matching "git::https://github.com/helmfile/helmfile.git@test/e2e/template/helmfile/testdata/snapshot/pr_560/secrets.yaml?ref=main"
Templating release=foo, chart=../../charts/raw-0.1.0
exec: helm template foo ../../charts/raw-0.1.0 --values /tmp/helmfile/foo-values-d459bc67c --debug
helm> install.go:192: [debug] Original chart version: ""

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to disable debug logging for this test case because running it with yaml.v2 resulted in adding an extra helm> line here due to an unknown reason.
It looked harmless but hard to solve so I just disabled the debug log to make it stable.

values:
- template1: template1
valuesTemplate:
- template1Label: "{{` '{{ .Release.Labels.template1 }}' `}}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not completely sure, but goccy/go-yaml seemed to translate single quotes enclosed in double quotes to triple single quotes, which resulted in this test case failing.
You don't need the single quotes anyway to write a valid helmfile yaml for this configuration so I just omitted it.

inheritedBaseLabel: base
template2: template2
template2Label: template2
templates:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yaml.v2 encoded this in the flow style but we are not interested in a specific yaml library to encode the same go map to a yaml dict in flow style or non-flow style. I just removed this key out of values to make the output look the same between yaml.v2 and goccy/go-yaml. See input.yaml for the change.

Comment on lines +5 to +11
config_file="helmfile.yaml"
if [[ ${HELMFILE_V1MODE} = true ]]; then
pushd "${chart_need_case_input_dir}"
mv "${config_file}" "${config_file}.gotmpl"
config_file="${config_file}.gotmpl"
popd
fi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new idiom I used in all the integration test runner script to rename helmfile.yaml to helmfile.yaml.gotmpl only when it's run with v1 mode.

@mumoshu

mumoshu commented Dec 29, 2022

Copy link
Copy Markdown
Contributor Author

@yxxhero DCO is failing due to perhaps a few commits missed signoffs but I'm too lazy to fix it. It would be great if we could handle it by using the squash-and-merge option and including the signoff line in the squashed commit.

@yxxhero

yxxhero commented Jan 1, 2023

Copy link
Copy Markdown
Member

@mumoshu why test/integration/test-cases/yaml-overwrite/input/issue.657.yaml.gotmpl be renamed?

@mumoshu

mumoshu commented Jan 2, 2023

Copy link
Copy Markdown
Contributor Author

@yxxhero That's because it's supposed work only with goccy/go-yamlm and therefore helmfile v1, which requires .gotmpl. Remember that we now run the integration test suite against both helmfile v0 and v1, and that we run the yaml-overwrite test only for v1!

@yxxhero

yxxhero commented Jan 4, 2023

Copy link
Copy Markdown
Member

@yxxhero That's because it's supposed work only with goccy/go-yamlm and therefore helmfile v1, which requires .gotmpl. Remember that we now run the integration test suite against both helmfile v0 and v1, and that we run the yaml-overwrite test only for v1!

got.

@mumoshu

mumoshu commented Jan 4, 2023

Copy link
Copy Markdown
Contributor Author

@yxxhero Thanks for your review! Let's see how this works in the wild.

@mumoshu mumoshu merged commit 4688cf0 into main Jan 4, 2023
@mumoshu mumoshu deleted the go-back-to-gopkgin-yaml-v2-for-helmfile-v0.x branch January 4, 2023 09:17
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jan 6, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.Release.Labels are not available within values templates

2 participants